From e05747b54694b1ab5f60c16ca352bc61fcf4625f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 22 Sep 2026 09:45:50 +0200 Subject: [PATCH 1/3] :sparkles: Restrict optional RPC ids to user-provided UUIDs (#11777) * :sparkles: Restrict optional RPC ids to user-provided UUIDs Add ::sm/user-provided-uuid, backed by a version and variant aware regex that only accepts v4, v7 and v8 instances. Use it for the optional :id of the creation RPC commands so reserved versions such as v3 are rejected at validation time. Reads such as get-team keep the lax ::sm/uuid. Cover the predicate and the schema on both JVM and JS runtimes. AI-assisted-by: muse-spark-1.3-contributor * :sparkles: Cover id version restriction at the RPC boundary Add backend regression tests proving the seven creation commands reject reserved-version ids (v3) with :params-validation and accept v4 ids (plus v7/v8 on create-team) through the real decode and validate path. Also drop two duplicated assertions and document the version and variant of every fixture UUID in user-provided-test. AI-assisted-by: muse-spark-1.3-contributor --- backend/src/app/rpc/commands/files_create.clj | 2 +- backend/src/app/rpc/commands/media.clj | 6 +- backend/src/app/rpc/commands/projects.clj | 2 +- backend/src/app/rpc/commands/teams.clj | 2 +- .../app/rpc/commands/teams_invitations.clj | 2 +- backend/test/backend_tests/rpc_file_test.clj | 27 +++++ backend/test/backend_tests/rpc_media_test.clj | 105 ++++++++++++++++++ .../test/backend_tests/rpc_project_test.clj | 27 +++++ backend/test/backend_tests/rpc_team_test.clj | 56 ++++++++++ common/src/app/common/schema.cljc | 15 +++ common/src/app/common/uuid.cljc | 14 +++ common/test/common_tests/schema_test.cljc | 38 +++++++ common/test/common_tests/uuid_test.cljc | 43 +++++++ 13 files changed, 332 insertions(+), 7 deletions(-) diff --git a/backend/src/app/rpc/commands/files_create.clj b/backend/src/app/rpc/commands/files_create.clj index dd297e223b..2e2bbebea6 100644 --- a/backend/src/app/rpc/commands/files_create.clj +++ b/backend/src/app/rpc/commands/files_create.clj @@ -75,7 +75,7 @@ [:map {:title "create-file"} [:name [:string {:max 250}]] [:project-id ::sm/uuid] - [:id {:optional true} ::sm/uuid] + [:id {:optional true} ::sm/user-provided-uuid] [:is-shared {:optional true} ::sm/boolean] [:features {:optional true} ::cfeat/features]]) diff --git a/backend/src/app/rpc/commands/media.clj b/backend/src/app/rpc/commands/media.clj index 51f87c320c..99d5daea5a 100644 --- a/backend/src/app/rpc/commands/media.clj +++ b/backend/src/app/rpc/commands/media.clj @@ -48,7 +48,7 @@ (def ^:private schema:upload-file-media-object [:map {:title "upload-file-media-object"} - [:id {:optional true} ::sm/uuid] + [:id {:optional true} ::sm/user-provided-uuid] [:file-id ::sm/uuid] [:is-local ::sm/boolean] [:name [:string {:max 250}]] @@ -225,7 +225,7 @@ [:file-id ::sm/uuid] [:is-local ::sm/boolean] [:url ::sm/uri] - [:id {:optional true} ::sm/uuid] + [:id {:optional true} ::sm/user-provided-uuid] [:name {:optional true} [:string {:max 250}]]]) (sv/defmethod ::create-file-media-object-from-url @@ -489,7 +489,7 @@ [:is-local ::sm/boolean] [:name [:string {:max 250}]] [:mtype :string] - [:id {:optional true} ::sm/uuid]]) + [:id {:optional true} ::sm/user-provided-uuid]]) (sv/defmethod ::assemble-file-media-object {::doc/added "2.17" diff --git a/backend/src/app/rpc/commands/projects.clj b/backend/src/app/rpc/commands/projects.clj index cfb03a2f0d..05d5ae79aa 100644 --- a/backend/src/app/rpc/commands/projects.clj +++ b/backend/src/app/rpc/commands/projects.clj @@ -199,7 +199,7 @@ [:map {:title "create-project"} [:team-id ::sm/uuid] [:name [:string {:max 250 :min 1}]] - [:id {:optional true} ::sm/uuid]]) + [:id {:optional true} ::sm/user-provided-uuid]]) (sv/defmethod ::create-project {::doc/added "1.18" diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index 728e97bef1..9c13d6b37d 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -523,7 +523,7 @@ [:map {:title "create-team"} [:name types.team/schema:team-name] [:features {:optional true} ::cfeat/features] - [:id {:optional true} ::sm/uuid] + [:id {:optional true} ::sm/user-provided-uuid] [:organization-id {:optional true} ::sm/uuid] [:is-default {:optional true} :boolean]]) diff --git a/backend/src/app/rpc/commands/teams_invitations.clj b/backend/src/app/rpc/commands/teams_invitations.clj index 7051c2b594..dae96b1bf5 100644 --- a/backend/src/app/rpc/commands/teams_invitations.clj +++ b/backend/src/app/rpc/commands/teams_invitations.clj @@ -538,7 +538,7 @@ [:map {:title "create-team-with-invitations"} [:name [:string {:max 250}]] [:features {:optional true} ::cfeat/features] - [:id {:optional true} ::sm/uuid] + [:id {:optional true} ::sm/user-provided-uuid] [:emails [::sm/set ::sm/email]] [:role types.team/schema:role]]) diff --git a/backend/test/backend_tests/rpc_file_test.clj b/backend/test/backend_tests/rpc_file_test.clj index da8fbbb1f7..b0076da05c 100644 --- a/backend/test/backend_tests/rpc_file_test.clj +++ b/backend/test/backend_tests/rpc_file_test.clj @@ -166,6 +166,33 @@ (t/is (th/ex-info? err)) (t/is (th/ex-of-type? err :not-found)))))) +(t/deftest create-file-id-version + (let [prof (th/create-profile* 1 {:is-active true}) + proj-id (:default-project-id prof) + v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e" + v4-id "550e8400-e29b-41d4-a716-446655440000"] + + ;; reserved version (v3) must be rejected at the RPC boundary + (let [data {::th/type :create-file + ::rpc/profile-id (:id prof) + :project-id proj-id + :id v3-id + :name "file with v3 id"} + out (th/command! data)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))) + + ;; v4 id is accepted + (let [data {::th/type :create-file + ::rpc/profile-id (:id prof) + :project-id proj-id + :id v4-id + :name "file with v4 id"} + out (th/command! data)] + (t/is (th/success? out)) + (t/is (= v4-id (str (:id (:result out)))))))) + (t/deftest file-gc-with-fragments (let [profile (th/create-profile* 1) file (th/create-file* 1 {:profile-id (:id profile) diff --git a/backend/test/backend_tests/rpc_media_test.clj b/backend/test/backend_tests/rpc_media_test.clj index e22ddb5afd..958b425b32 100644 --- a/backend/test/backend_tests/rpc_media_test.clj +++ b/backend/test/backend_tests/rpc_media_test.clj @@ -137,6 +137,66 @@ (t/is (uuid? (:thumbnail-id result)))))) +(t/deftest upload-file-media-object-id-version + (let [prof (th/create-profile* 1) + _ (th/create-project* 1 {:profile-id (:id prof) + :team-id (:default-team-id prof)}) + file (th/create-file* 1 {:profile-id (:id prof) + :project-id (:default-project-id prof) + :is-shared false}) + mfile {:filename "sample.jpg" + :path (th/tempfile "backend_tests/test_files/sample.jpg") + :mtype "image/jpeg" + :size 312043} + v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e" + v4-id "550e8400-e29b-41d4-a716-446655440000"] + + ;; reserved version (v3) must be rejected at the RPC boundary + (let [params {::th/type :upload-file-media-object + ::rpc/profile-id (:id prof) + :file-id (:id file) + :is-local true + :name "testfile" + :content mfile + :id v3-id} + out (th/command! params)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))) + + ;; v4 id is accepted + (let [params {::th/type :upload-file-media-object + ::rpc/profile-id (:id prof) + :file-id (:id file) + :is-local true + :name "testfile" + :content mfile + :id v4-id} + out (th/command! params)] + (t/is (th/success? out)) + (t/is (= v4-id (str (:id (:result out)))))))) + +(t/deftest create-file-media-object-from-url-id-version + (let [prof (th/create-profile* 1) + _ (th/create-project* 1 {:profile-id (:id prof) + :team-id (:default-team-id prof)}) + file (th/create-file* 1 {:profile-id (:id prof) + :project-id (:default-project-id prof) + :is-shared false}) + v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e"] + + ;; reserved version (v3) must be rejected before any download happens + (let [params {::th/type :create-file-media-object-from-url + ::rpc/profile-id (:id prof) + :file-id (:id file) + :is-local true + :url "https://example.com/sample.jpg" + :id v3-id} + out (th/command! params)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))))) + (t/deftest media-object-from-url-command (let [prof (th/create-profile* 1) proj (th/create-project* 1 {:profile-id (:id prof) @@ -1078,3 +1138,48 @@ (t/is (th/ex-info? error)) (t/is (= :not-found (:type error-data))) (t/is (= :object-not-found (:code error-data)))))) + +(t/deftest assemble-file-media-object-id-version + (let [prof (th/create-profile* 1) + _ (th/create-project* 1 {:profile-id (:id prof) + :team-id (:default-team-id prof)}) + file (th/create-file* 1 {:profile-id (:id prof) + :project-id (:default-project-id prof) + :is-shared false}) + v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e" + v4-id "550e8400-e29b-41d4-a716-446655440000" + mtype "image/jpeg"] + + ;; reserved version (v3) must be rejected without touching any session + (let [out (th/command! {::th/type :assemble-file-media-object + ::rpc/profile-id (:id prof) + :session-id (uuid/next) + :file-id (:id file) + :is-local true + :name "assembled-image" + :mtype mtype + :id v3-id})] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))) + + ;; v4 id is accepted through the full chunked flow + (let [source-path (th/tempfile "backend_tests/test_files/sample.jpg") + chunks (split-file-into-chunks source-path 312043) + session-id (create-session! prof 1) + mfile (make-chunk-mfile (first chunks) mtype) + _ (th/command! {::th/type :upload-chunk + ::rpc/profile-id (:id prof) + :session-id session-id + :index 0 + :content mfile}) + out (th/command! {::th/type :assemble-file-media-object + ::rpc/profile-id (:id prof) + :session-id session-id + :file-id (:id file) + :is-local true + :name "assembled-image" + :mtype mtype + :id v4-id})] + (t/is (th/success? out)) + (t/is (= v4-id (str (:id (:result out)))))))) diff --git a/backend/test/backend_tests/rpc_project_test.clj b/backend/test/backend_tests/rpc_project_test.clj index 3f80f06f6b..01cfb0a853 100644 --- a/backend/test/backend_tests/rpc_project_test.clj +++ b/backend/test/backend_tests/rpc_project_test.clj @@ -123,6 +123,33 @@ (t/is (th/ex-info? error)) (t/is (th/ex-of-type? error :not-found)))) +(t/deftest create-project-id-version + (let [profile (th/create-profile* 1) + team (th/create-team* 1 {:profile-id (:id profile)}) + v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e" + v4-id "550e8400-e29b-41d4-a716-446655440000"] + + ;; reserved version (v3) must be rejected at the RPC boundary + (let [data {::th/type :create-project + ::rpc/profile-id (:id profile) + :team-id (:id team) + :id v3-id + :name "project with v3 id"} + out (th/command! data)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))) + + ;; v4 id is accepted + (let [data {::th/type :create-project + ::rpc/profile-id (:id profile) + :team-id (:id team) + :id v4-id + :name "project with v4 id"} + out (th/command! data)] + (t/is (th/success? out)) + (t/is (= v4-id (str (:id (:result out)))))))) + (t/deftest permissions-checks-rename-project (let [profile1 (th/create-profile* 1) profile2 (th/create-profile* 2) diff --git a/backend/test/backend_tests/rpc_team_test.clj b/backend/test/backend_tests/rpc_team_test.clj index b087eb6819..61acf23cb8 100644 --- a/backend/test/backend_tests/rpc_team_test.clj +++ b/backend/test/backend_tests/rpc_team_test.clj @@ -1116,6 +1116,62 @@ out (th/command! data)] (t/is (th/success? out))))) +(t/deftest create-team-id-version + (let [profile (th/create-profile* 1 {:is-active true}) + v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e" + v4-id "550e8400-e29b-41d4-a716-446655440000" + v7-id "0191062e-3f50-7a5e-9f5a-1a2b3c4d5e6f"] + + ;; reserved version (v3) must be rejected at the RPC boundary + (let [data {::th/type :create-team + ::rpc/profile-id (:id profile) + :name "team with v3 id" + :id v3-id} + out (th/command! data)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation))) + + ;; v4, v7 and v8 ids are accepted + (doseq [id [v4-id v7-id (str (uuid/next))]] + (let [data {::th/type :create-team + ::rpc/profile-id (:id profile) + :name (str "team with id " id) + :id id} + out (th/command! data)] + (t/is (th/success? out)) + (t/is (= id (str (:id (:result out))))))))) + +(t/deftest create-team-with-invitations-id-version + (with-mocks [mock {:target 'app.email/send! :return nil}] + (let [profile (th/create-profile* 1 {:is-active true}) + v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e" + v4-id "550e8400-e29b-41d4-a716-446655440000"] + + ;; reserved version (v3) must be rejected before any invitation is sent + (let [data {::th/type :create-team-with-invitations + ::rpc/profile-id (:id profile) + :name "team with v3 id" + :id v3-id + :emails #{"invitee@example.com"} + :role :editor} + out (th/command! data)] + (t/is (not (th/success? out))) + (t/is (th/ex-of-type? (:error out) :validation)) + (t/is (th/ex-of-code? (:error out) :params-validation)) + (t/is (= 0 (:call-count @mock)))) + + ;; v4 id is accepted + (let [data {::th/type :create-team-with-invitations + ::rpc/profile-id (:id profile) + :name "team with v4 id" + :id v4-id + :emails #{"invitee@example.com"} + :role :editor} + out (th/command! data)] + (t/is (th/success? out)) + (t/is (= v4-id (str (:id (:result out))))))))) + (t/deftest create-team-invitations-email-cooldown (with-mocks [mock {:target 'app.email/send! :return nil}] (let [profile1 (th/create-profile* 1 {:is-active true}) diff --git a/common/src/app/common/schema.cljc b/common/src/app/common/schema.cljc index fba8169bcd..9014bf1cf6 100644 --- a/common/src/app/common/schema.cljc +++ b/common/src/app/common/schema.cljc @@ -448,6 +448,21 @@ ::oapi/type "string" ::oapi/format "uuid"}}) +(register! + {:type ::user-provided-uuid + :pred uuid/user-provided? + :type-properties + {:title "user-provided-uuid" + :description "UUID provided by the user (v4, v7 or v8)" + :error/message "should be a user provided uuid (v4, v7 or v8)" + :gen/gen (sg/uuid) + :decode/string parse-uuid + :decode/json parse-uuid + :encode/string encode-uuid + :encode/json encode-uuid + ::oapi/type "string" + ::oapi/format "uuid"}}) + ;; Strict email regex aligned with app.common.spec/email-re. ;; Local part: valid RFC chars, no leading/trailing dot, no consecutive dots. ;; Domain: labels can't start/end with hyphen, no empty labels. diff --git a/common/src/app/common/uuid.cljc b/common/src/app/common/uuid.cljc index d094d4c06d..fce87bcd7d 100644 --- a/common/src/app/common/uuid.cljc +++ b/common/src/app/common/uuid.cljc @@ -70,6 +70,20 @@ [v] (= zero v)) +(def ^:private user-provided-regex + #"(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[478][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$") + +(defn user-provided? + "Returns true when `v` is a UUID instance penpot can accept when + it is provided by the user (v4 random, v7 time-ordered or v8 penpot + time-ordered). Anything else, including UUID strings, returns false; + strings are meant to be decoded into UUID instances first, mirroring + how ::sm/uuid behaves." + [v] + (boolean + (and (uuid? v) + (re-matches user-provided-regex (str v))))) + (defn get-word-high [id] #?(:clj (.getMostSignificantBits ^UUID id) diff --git a/common/test/common_tests/schema_test.cljc b/common/test/common_tests/schema_test.cljc index b14f1df0f5..9dc98b118e 100644 --- a/common/test/common_tests/schema_test.cljc +++ b/common/test/common_tests/schema_test.cljc @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.schema :as sm] [app.common.schema.generators :as sg] + [app.common.uuid :as uuid] [clojure.test :as t])) (t/deftest test-set-of-email @@ -245,3 +246,40 @@ (t/is (false? (sm/validate ::sm/email "user@"))) (t/is (false? (sm/validate ::sm/email "userdomain.com"))) (t/is (false? (sm/validate ::sm/email "user@@domain.com"))))) + +(t/deftest test-user-provided-uuid + (let [v4 (uuid/uuid "550e8400-e29b-41d4-a716-446655440000") + v7 (uuid/uuid "0191062e-3f50-7a5e-9f5a-1a2b3c4d5e6f") + v8 (uuid/uuid "0227df82-63d7-8016-8005-48d9c0f33011") + v1 (uuid/uuid "6ba7b810-9dad-11d1-80b4-00c04fd430c8") + v3 (uuid/uuid "6fa459ea-ee8a-3ca4-894e-db77e160355e") + v5 (uuid/uuid "886313e1-3b8a-5372-9b90-0c9aee199e5d")] + + (t/testing "accepts v4, v7 and v8 instances" + (t/is (true? (sm/validate ::sm/user-provided-uuid v4))) + (t/is (true? (sm/validate ::sm/user-provided-uuid v7))) + (t/is (true? (sm/validate ::sm/user-provided-uuid v8))) + (t/is (true? (sm/validate ::sm/user-provided-uuid (uuid/random)))) + (t/is (true? (sm/validate ::sm/user-provided-uuid (uuid/next))))) + + (t/testing "rejects reserved and other versions" + (t/is (false? (sm/validate ::sm/user-provided-uuid v1))) + (t/is (false? (sm/validate ::sm/user-provided-uuid v3))) + (t/is (false? (sm/validate ::sm/user-provided-uuid v5))) + (t/is (false? (sm/validate ::sm/user-provided-uuid uuid/zero))) + (t/is (false? (sm/validate ::sm/user-provided-uuid nil))) + (t/is (false? (sm/validate ::sm/user-provided-uuid "not-an-uuid")))) + + (t/testing "decodes strings like the RPC layer does" + (let [decode (sm/decoder ::sm/user-provided-uuid sm/json-transformer)] + (t/is (= v4 (decode (str v4)))) + (t/is (= v3 (decode (str v3)))) + (t/is (true? (sm/validate ::sm/user-provided-uuid (decode (str v4))))) + (t/is (false? (sm/validate ::sm/user-provided-uuid (decode (str v3))))))) + + (t/testing "rejects raw strings without decoding, like ::sm/uuid does" + (t/is (false? (sm/validate ::sm/user-provided-uuid (str v4)))) + (t/is (false? (sm/validate ::sm/user-provided-uuid (str v3))))) + + (t/testing "generate" + (t/is (true? (sm/validate ::sm/user-provided-uuid (sg/generate ::sm/user-provided-uuid))))))) diff --git a/common/test/common_tests/uuid_test.cljc b/common/test/common_tests/uuid_test.cljc index ec6f14ceee..6d6d982e5d 100644 --- a/common/test/common_tests/uuid_test.cljc +++ b/common/test/common_tests/uuid_test.cljc @@ -95,3 +95,46 @@ (t/is (= (nth expected 1) (aget parts 1))) (t/is (= (nth expected 2) (aget parts 2))) (t/is (= (nth expected 3) (aget parts 3)))))))) + +(t/deftest user-provided-test + ;; The version is the first nibble of the 3rd group, the variant is + ;; the first nibble of the 4th group (8, 9, a or b means RFC 4122). + (let [v4 (uuid/uuid "550e8400-e29b-41d4-a716-446655440000") ; v4, variant a: valid + v7 (uuid/uuid "0191062e-3f50-7a5e-9f5a-1a2b3c4d5e6f") ; v7, variant 9: valid + v8 (uuid/uuid "0227df82-63d7-8016-8005-48d9c0f33011") ; v8, variant 8: valid + v4-upper (uuid/uuid "550E8400-E29B-41D4-A716-446655440000") ; v4 uppercase, variant A: valid + v1 (uuid/uuid "6ba7b810-9dad-11d1-80b4-00c04fd430c8") ; v1 time-based: rejected + v3 (uuid/uuid "6fa459ea-ee8a-3ca4-894e-db77e160355e") ; v3 MD5 name-based (reserved): rejected + v5 (uuid/uuid "886313e1-3b8a-5372-9b90-0c9aee199e5d") ; v5 SHA-1 name-based: rejected + v6 (uuid/uuid "1e29effc-7a68-6d2c-9b6f-7f6a5f9b5b5b") ; v6 time-ordered: rejected + v4-bad-variant (uuid/uuid "550e8400-e29b-41d4-0716-446655440000")] ; v4 but variant 0 (NCS reserved): rejected + + (t/testing "accepts v4, v7 and v8 instances" + (t/is (true? (uuid/user-provided? v4))) + (t/is (true? (uuid/user-provided? v7))) + (t/is (true? (uuid/user-provided? v8))) + (t/is (true? (uuid/user-provided? v4-upper))) + (t/is (true? (uuid/user-provided? (uuid/random)))) ; generates v4 + (t/is (true? (uuid/user-provided? (uuid/next))))) ; generates v8 + + (t/testing "rejects strings, they must be decoded first" + (t/is (false? (uuid/user-provided? (str v4)))) + (t/is (false? (uuid/user-provided? (str v7)))) + (t/is (false? (uuid/user-provided? (str v8)))) + (t/is (false? (uuid/user-provided? (str v3))))) + + (t/testing "rejects nil uuid and reserved and other versions" + (t/is (false? (uuid/user-provided? uuid/zero))) ; version 0, variant 0 + (t/is (false? (uuid/user-provided? v1))) + (t/is (false? (uuid/user-provided? v3))) + (t/is (false? (uuid/user-provided? v5))) + (t/is (false? (uuid/user-provided? v6))) + (t/is (false? (uuid/user-provided? (uuid/custom 1))))) ; version 0, variant 0 + + (t/testing "rejects right version with wrong variant" + (t/is (false? (uuid/user-provided? v4-bad-variant)))) + + (t/testing "rejects non-uuid values" + (t/is (false? (uuid/user-provided? nil))) + (t/is (false? (uuid/user-provided? 42))) + (t/is (false? (uuid/user-provided? "not-an-uuid")))))) From d68531b783f5736b0b1a81453353756afc998870 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 22 Sep 2026 10:22:31 +0200 Subject: [PATCH 2/3] :arrow_up: Update devenv dependencies (#11790) * :arrow_up: Update devenv dependencies Update Node.js, OpenCode, clj-kondo, Babashka, Pixi, GitHub CLI, uv, and Serena to their current stable releases. AI-assisted-by: gpt-5.6-sol * :arrow_up: Update devenv to Java 27 Use Zulu JDK 27 in the development image for compatibility testing. Update the official checksums for both supported architectures. AI-assisted-by: gpt-5.6-sol * :whale: Replace MinIO with RustFS in devenv Run RustFS as the development S3 service and wait for its health check. Install a pinned AWS CLI with checksums and use it to create the bucket idempotently from each backend entry point. Keep the old MinIO volume untouched and use a new RustFS volume. AI-assisted-by: gpt-5.6-sol * :whale: Replace MailCatcher with persistent Mailpit Run Mailpit as the devenv SMTP sink while preserving mailer:1025 and the localhost:1080 UI. Store its SQLite inbox in a named volume and wait for the readiness endpoint before starting runtime containers. Bind the web UI to loopback so development emails stay local. AI-assisted-by: gpt-5.6-sol * :arrow_up: Update Node.js to 24.21.0 Align the host NVM version with the Node.js version used by devenv. AI-assisted-by: gpt-5.6-sol * :arrow_up: Update devenv to PostgreSQL 18.6 Run PostgreSQL 18 with its versioned volume layout and a TCP readiness check that ignores the temporary initialization server. Install the matching client, create penpot_nexus, and preserve the old PostgreSQL 16 volume for rollback or logical migration. AI-assisted-by: gpt-5.6-sol * :whale: Expose RustFS ports in devenv Publish the RustFS S3 API and management console on localhost port 9000 and 9001. Keep both bindings on loopback so object storage is not exposed to the local network. AI-assisted-by: gpt-5.6-sol * :whale: Install standalone pnpm in devenv Install pnpm 12.5.0 from architecture-specific release archives and verify their published checksums. Remove the Corepack setup while allowing pnpm to honor the project packageManager pins. AI-assisted-by: gpt-5.6-sol * :fire: Remove corepack, use system pnpm everywhere Corepack is gone from Node 25+, so every `corepack enable` call fails. pnpm now ships as a system binary (devenv, CI runners and Docker images install it directly) and auto-downloads the version pinned in `packageManager` on mismatch. Scripts, workflows and Dockerfiles call `pnpm` straight away; the three deploy workflows use a single `pnpm/setup@v2` step; and the new `scripts/sync-pnpm-version` stamps all 35 `packageManager` fields from the system pnpm, replacing the `corepack use` sweep. AI-assisted-by: muse-spark-1.3-contributor * :bug: Fix exporter watch missing render-wasm build step The exporter watch compiled CLJS requiring the generated src/app/wasm/shared.js, which only render-wasm/build export produces. Without it shadow-cljs failed with a cryptic missing ./shared.js dependency. Run build:wasm before watching, as the frontend watch:app and exporter scripts/build already do. AI-assisted-by: muse-spark-1.3-contributor * :wrench: Add opencode V2 support and adapt plugins Register the penpot tools for both opencode V1 (server()) and V2 (setup() with JSON Schema inputs) from a single dependency-free plugin file, sharing the psql and paren-repair runners between both paths. Install the opencode2 binary side-by-side with V1 in the devenv image and document the dual registration in the paren-repair and psql memories. AI-assisted-by: muse-spark-1.3-contributor * :arrow_up: Update pnpm and opencode --- .github/workflows/plugins-deploy-api-doc.yml | 15 +- .github/workflows/plugins-deploy-package.yml | 15 +- .../workflows/plugins-deploy-styles-doc.yml | 15 +- .github/workflows/tests-common.yml | 2 - .github/workflows/tests-composable-suite.yml | 2 - .github/workflows/tests-exporter.yml | 2 - .github/workflows/tests-frontend.yml | 2 - .github/workflows/tests-integration.yml | 4 - .github/workflows/tests-library.yml | 2 - .github/workflows/tests-plugin-api-suite.yml | 4 - .github/workflows/tests-plugins.yml | 2 - .nvmrc | 2 +- .opencode/plugins/penpot.js | 421 ++++++++---------- .../auth-permissions-product-domains.md | 4 +- .serena/memories/critical-info.md | 4 +- .serena/memories/devenv/core.md | 14 +- .serena/memories/prod-infra/core.md | 2 +- .serena/memories/scripts/paren-repair.md | 7 +- .serena/memories/scripts/psql.md | 4 +- .serena/memories/workflow/updating-pnpm.md | 61 ++- backend/package.json | 2 +- backend/pnpm-lock.yaml | 131 ++++-- backend/scripts/_env | 28 +- backend/scripts/repl | 4 +- backend/scripts/run | 3 + backend/scripts/start-dev | 4 +- common/package.json | 2 +- common/pnpm-lock.yaml | 131 ++++-- common/scripts/test | 2 - docker/devenv/Dockerfile | 91 +++- docker/devenv/defaults.env | 15 +- docker/devenv/docker-compose.infra.yml | 81 ++-- docker/devenv/docker-compose.main.yml | 2 + docker/devenv/files/postgresql_init.sql | 2 +- docker/images/Dockerfile.exporter | 32 +- docker/images/Dockerfile.mcp | 30 ++ docker/images/Dockerfile.media-processor | 13 +- docs/README.md | 1 - docs/package.json | 2 +- docs/pnpm-lock.yaml | 131 ++++-- docs/scripts/build | 3 - docs/technical-guide/developer/devenv.md | 46 +- docs/technical-guide/developer/frontend.md | 2 +- exporter/package.json | 5 +- exporter/pnpm-lock.yaml | 131 ++++-- exporter/scripts/build | 5 +- exporter/scripts/setup | 2 - exporter/scripts/test | 2 - frontend/package.json | 2 +- frontend/packages/draft-js/package.json | 2 +- frontend/packages/mousetrap/package.json | 2 +- frontend/packages/tokenscript/package.json | 2 +- frontend/packages/ui/package.json | 2 +- frontend/pnpm-lock.yaml | 131 ++++-- frontend/scripts/build | 3 - frontend/scripts/build-storybook | 2 - frontend/scripts/setup | 2 - frontend/scripts/test | 2 - frontend/scripts/watch | 2 - frontend/text-editor/package.json | 2 +- library/package.json | 2 +- library/pnpm-lock.yaml | 131 ++++-- library/scripts/test | 2 - manage.sh | 17 +- mcp/README.md | 2 +- mcp/package.json | 2 +- mcp/packages/common/package.json | 2 +- mcp/packages/plugin/package.json | 2 +- mcp/packages/server/package.json | 2 +- mcp/pnpm-lock.yaml | 131 ++++-- mcp/scripts/build | 6 +- mcp/scripts/setup | 2 - media-processor/package.json | 2 +- media-processor/pnpm-lock.yaml | 131 ++++-- media-processor/scripts/setup | 2 - package.json | 2 +- .../apps/colors-to-tokens-plugin/package.json | 2 +- .../apps/composable-test-suite/package.json | 2 +- plugins/apps/contrast-plugin/package.json | 2 +- .../apps/create-palette-plugin/package.json | 2 +- plugins/apps/e2e/package.json | 2 +- plugins/apps/example-styles/package.json | 2 +- plugins/apps/icons-plugin/package.json | 2 +- plugins/apps/lorem-ipsum-plugin/package.json | 2 +- .../apps/plugin-api-test-suite/package.json | 2 +- plugins/apps/poc-state-plugin/package.json | 2 +- plugins/apps/poc-tokens-plugin/package.json | 2 +- .../apps/rename-layers-plugin/package.json | 2 +- plugins/apps/table-plugin/package.json | 2 +- plugins/libs/plugin-types/package.json | 2 +- plugins/libs/plugins-runtime/package.json | 2 +- plugins/libs/plugins-styles/package.json | 2 +- plugins/package.json | 2 +- plugins/pnpm-lock.yaml | 131 ++++-- pnpm-lock.yaml | 131 ++++-- render-wasm/_build_env | 2 - render-wasm/package.json | 2 +- render-wasm/pnpm-lock.yaml | 131 ++++-- scripts/sync-pnpm-version | 185 ++++++++ scripts/test_sync_pnpm_version.py | 152 +++++++ 100 files changed, 1935 insertions(+), 912 deletions(-) create mode 100755 scripts/sync-pnpm-version create mode 100644 scripts/test_sync_pnpm_version.py diff --git a/.github/workflows/plugins-deploy-api-doc.yml b/.github/workflows/plugins-deploy-api-doc.yml index 7208646c1a..4657740a58 100644 --- a/.github/workflows/plugins-deploy-api-doc.yml +++ b/.github/workflows/plugins-deploy-api-doc.yml @@ -43,17 +43,12 @@ jobs: ref: ${{ steps.vars.outputs.gh_ref }} # START: Setup Node and PNPM enabling cache - - name: Setup Node.js - uses: actions/setup-node@v6 + - name: Setup pnpm + Node.js + uses: pnpm/setup@v2 with: - node-version-file: .nvmrc - - - name: Enable PNPM - working-directory: ./plugins - shell: bash - run: | - corepack enable; - corepack install; + working-directory: plugins + runtime: node@24.21.0 + install: false - name: Get pnpm store path id: pnpm-store diff --git a/.github/workflows/plugins-deploy-package.yml b/.github/workflows/plugins-deploy-package.yml index 2666957893..cb6cbc704c 100644 --- a/.github/workflows/plugins-deploy-package.yml +++ b/.github/workflows/plugins-deploy-package.yml @@ -43,17 +43,12 @@ jobs: ref: ${{ inputs.gh_ref }} # START: Setup Node and PNPM enabling cache - - name: Setup Node.js - uses: actions/setup-node@v6 + - name: Setup pnpm + Node.js + uses: pnpm/setup@v2 with: - node-version-file: .nvmrc - - - name: Enable PNPM - working-directory: ./plugins - shell: bash - run: | - corepack enable; - corepack install; + working-directory: plugins + runtime: node@24.21.0 + install: false - name: Get pnpm store path id: pnpm-store diff --git a/.github/workflows/plugins-deploy-styles-doc.yml b/.github/workflows/plugins-deploy-styles-doc.yml index 29d2ac4fea..53a379b854 100644 --- a/.github/workflows/plugins-deploy-styles-doc.yml +++ b/.github/workflows/plugins-deploy-styles-doc.yml @@ -41,17 +41,12 @@ jobs: ref: ${{ steps.vars.outputs.gh_ref }} # START: Setup Node and PNPM enabling cache - - name: Setup Node.js - uses: actions/setup-node@v6 + - name: Setup pnpm + Node.js + uses: pnpm/setup@v2 with: - node-version-file: .nvmrc - - - name: Enable PNPM - working-directory: ./plugins - shell: bash - run: | - corepack enable; - corepack install; + working-directory: plugins + runtime: node@24.21.0 + install: false - name: Get pnpm store path id: pnpm-store diff --git a/.github/workflows/tests-common.yml b/.github/workflows/tests-common.yml index 3164fe5089..e513efc54f 100644 --- a/.github/workflows/tests-common.yml +++ b/.github/workflows/tests-common.yml @@ -44,8 +44,6 @@ jobs: - name: Lint working-directory: ./common run: | - corepack enable; - corepack install; pnpm install; pnpm run check-fmt:clj pnpm run check-fmt:js diff --git a/.github/workflows/tests-composable-suite.yml b/.github/workflows/tests-composable-suite.yml index 3b1d68be21..2ebea5ab59 100644 --- a/.github/workflows/tests-composable-suite.yml +++ b/.github/workflows/tests-composable-suite.yml @@ -56,8 +56,6 @@ jobs: - name: Install deps working-directory: ./plugins run: | - corepack enable; - corepack install; pnpm install; - name: Install Playwright Chromium diff --git a/.github/workflows/tests-exporter.yml b/.github/workflows/tests-exporter.yml index ed5c17883a..0c8317499c 100644 --- a/.github/workflows/tests-exporter.yml +++ b/.github/workflows/tests-exporter.yml @@ -46,8 +46,6 @@ jobs: - name: Lint working-directory: ./exporter run: | - corepack enable; - corepack install; pnpm install; pnpm run check-fmt:clj pnpm run lint:clj diff --git a/.github/workflows/tests-frontend.yml b/.github/workflows/tests-frontend.yml index 58ba335e38..f08f34054d 100644 --- a/.github/workflows/tests-frontend.yml +++ b/.github/workflows/tests-frontend.yml @@ -48,8 +48,6 @@ jobs: - name: Lint working-directory: ./frontend run: | - corepack enable; - corepack install; pnpm install; pnpm run check-fmt:js pnpm run check-fmt:clj diff --git a/.github/workflows/tests-integration.yml b/.github/workflows/tests-integration.yml index 5c2a4bcc98..59aa8182f4 100644 --- a/.github/workflows/tests-integration.yml +++ b/.github/workflows/tests-integration.yml @@ -135,8 +135,6 @@ jobs: - name: Install deps working-directory: ./frontend run: | - corepack enable; - corepack install; pnpm install --frozen-lockfile; # No-op once the shared volume is warm; keeps the first run working. @@ -200,8 +198,6 @@ jobs: - name: Install deps working-directory: ./frontend run: | - corepack enable; - corepack install; pnpm install --frozen-lockfile; - name: Download blob reports diff --git a/.github/workflows/tests-library.yml b/.github/workflows/tests-library.yml index a5b565a892..84ab5c9dc9 100644 --- a/.github/workflows/tests-library.yml +++ b/.github/workflows/tests-library.yml @@ -46,8 +46,6 @@ jobs: - name: Lint working-directory: ./library run: | - corepack enable; - corepack install; pnpm install; pnpm run check-fmt pnpm run lint diff --git a/.github/workflows/tests-plugin-api-suite.yml b/.github/workflows/tests-plugin-api-suite.yml index de6e2080bb..0bc070ae30 100644 --- a/.github/workflows/tests-plugin-api-suite.yml +++ b/.github/workflows/tests-plugin-api-suite.yml @@ -71,8 +71,6 @@ jobs: - name: Install deps working-directory: ./plugins run: | - corepack enable; - corepack install; pnpm install; - name: Install Playwright Chromium @@ -115,8 +113,6 @@ jobs: # - name: Install deps # working-directory: ./plugins # run: | - # corepack enable; - # corepack install; # pnpm install; # # - name: Install Playwright Chromium diff --git a/.github/workflows/tests-plugins.yml b/.github/workflows/tests-plugins.yml index b1bbdd0992..9f6c461da2 100644 --- a/.github/workflows/tests-plugins.yml +++ b/.github/workflows/tests-plugins.yml @@ -44,8 +44,6 @@ jobs: working-directory: ./plugins shell: bash run: | - corepack enable; - corepack install; pnpm install -r; - name: Run Lint diff --git a/.nvmrc b/.nvmrc index 3648bfc346..166aae1151 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v24.19.0 +v24.21.0 diff --git a/.opencode/plugins/penpot.js b/.opencode/plugins/penpot.js index 5f0fbba554..c231907396 100644 --- a/.opencode/plugins/penpot.js +++ b/.opencode/plugins/penpot.js @@ -1,7 +1,113 @@ +// Penpot opencode plugin: custom tools for Penpot development. +// +// Dual V1 + V2 implementation from a single file: +// - OpenCode V1 (>= 1.18.29) calls the default export's `server()` and uses +// the returned `tool` map (built with the `tool()` helper from +// `@opencode-ai/plugin`). +// - OpenCode V2 reads the default export's `id` and `setup()` and ignores +// `server()`. Tools are registered via `ctx.tool.transform()` with JSON +// Schema inputs, and `execute` returns `{ content }`. +// See https://opencode.ai/v2/docs/build/plugins/migrate-v1 +// +// NOTE: the V2 side intentionally does NOT +// `import { Plugin } from "@opencode/plugin"`. At runtime `Plugin.define` is +// the identity function, so a plain `{ id, setup }` object is equivalent, and +// skipping the import keeps this plugin dependency-free +// (`.opencode/package.json` is gitignored, so a new dependency declared there +// would not travel with this file). + import { tool } from "@opencode-ai/plugin" import path from "path" import { spawn } from "child_process" +function runCommand(command, args, options = {}) { + const { + cwd, + env, + stdin, + closeStdin = false, + successMessage = "Command executed successfully", + } = options + + return new Promise((resolve) => { + let stdout = "" + let stderr = "" + + const proc = spawn(command, args, { cwd, env }) + + proc.stdout.on("data", (data) => { + stdout += data.toString() + }) + + proc.stderr.on("data", (data) => { + stderr += data.toString() + }) + + proc.on("error", (error) => { + resolve(`Error: ${error.message}`) + }) + + proc.on("close", (exitCode) => { + const output = + exitCode === 0 + ? stdout.trim() || successMessage + : `Error (exit ${exitCode}): ${ + (stderr || stdout).trim() || "No error output" + }` + resolve(output) + }) + + // Close stdin so the child cannot wait on it indefinitely. `psql -c` + // never reads stdin, so only the paren-repair pipe mode needs this, but + // closing it unconditionally is harmless there. + if (stdin !== undefined) { + proc.stdin.end(stdin) + } else if (closeStdin) { + proc.stdin.end() + } + }) +} + +function executePsql(sql, useTestDb, cwd) { + const host = process.env.PENPOT_DB_HOST || "postgres" + const user = process.env.PENPOT_DB_USER || "penpot" + const db = useTestDb + ? "penpot_test" + : process.env.PENPOT_DB_NAME || "penpot" + const password = process.env.PENPOT_DB_PASSWORD || "penpot" + + const psqlArgs = ["-h", host, "-U", user, "-d", db, "-c", sql] + + return runCommand("psql", psqlArgs, { + cwd, + env: { ...process.env, PGPASSWORD: password }, + successMessage: "Query executed successfully", + }) +} + +function executeParenRepair({ files, code }, directory) { + const script = path.join(directory, "scripts/paren-repair") + + const fileList = files + ? files + .split(",") + .map((file) => file.trim()) + .filter(Boolean) + : [] + + const childArgs = + fileList.length > 0 ? [script, ...fileList] : [script] + + return runCommand("bb", childArgs, { + cwd: directory, + stdin: code, + closeStdin: true, + successMessage: "No changes needed", + }) +} + +// --- V1 tool definitions (OpenCode V1 calls `server()` below) --- + const penpotPsqlTool = tool({ description: "Execute a SQL command against the Penpot database. Uses the defaults from scripts/psql.", @@ -18,46 +124,7 @@ const penpotPsqlTool = tool({ }, async execute(args, context) { - const host = process.env.PENPOT_DB_HOST || "postgres" - const user = process.env.PENPOT_DB_USER || "penpot" - const db = args.test - ? "penpot_test" - : process.env.PENPOT_DB_NAME || "penpot" - const password = process.env.PENPOT_DB_PASSWORD || "penpot" - - const psqlArgs = ["-h", host, "-U", user, "-d", db, "-c", args.sql] - - return new Promise((resolve) => { - let stdout = "" - let stderr = "" - - const proc = spawn("psql", psqlArgs, { - cwd: context.worktree, - env: { ...process.env, PGPASSWORD: password }, - }) - - proc.stdout.on("data", (data) => { - stdout += data.toString() - }) - - proc.stderr.on("data", (data) => { - stderr += data.toString() - }) - - proc.on("error", (error) => { - resolve(`Error: ${error.message}`) - }) - - proc.on("close", (exitCode) => { - const output = - exitCode === 0 - ? stdout.trim() || "Query executed successfully" - : `Error (exit ${exitCode}): ${ - (stderr || stdout).trim() || "No error output" - }` - resolve(output) - }) - }) + return executePsql(args.sql, args.test === true, context.worktree) }, }) @@ -82,69 +149,11 @@ const parenRepairTool = tool({ }, async execute(args, context) { - const script = path.join(context.worktree, "scripts/paren-repair") - - const files = args.files - ? args.files - .split(",") - .map((file) => file.trim()) - .filter(Boolean) - : [] - - const paramInfo = - files.length > 0 - ? `files=[${files.join(", ")}]` - : args.code !== undefined - ? `code=(${args.code.length} chars)` - : "none" - - return new Promise((resolve) => { - const childArgs = - files.length > 0 - ? [script, ...files] - : [script] - - const proc = spawn("bb", childArgs, { - cwd: context.worktree, - }) - - let stdout = "" - let stderr = "" - - proc.stdout.on("data", (data) => { - stdout += data.toString() - }) - - proc.stderr.on("data", (data) => { - stderr += data.toString() - }) - - proc.on("error", (error) => { - resolve(`Error: ${error.message}`) - }) - - proc.on("close", (exitCode) => { - const output = - exitCode === 0 - ? stdout.trim() || "No changes needed" - : `Error (exit ${exitCode}): ${ - (stderr || stdout).trim() || "No error output" - }` - - resolve(output) - }) - - // Close stdin in all cases so the process cannot wait indefinitely. - if (args.code !== undefined) { - proc.stdin.end(args.code) - } else { - proc.stdin.end() - } - }) + return executeParenRepair(args, context.worktree) }, }) -export default async function plugin() { +async function server() { return { tool: { "paren-repair": parenRepairTool, @@ -153,147 +162,83 @@ export default async function plugin() { } } +// --- V2 setup (OpenCode V2 calls `setup()` and ignores `server()`) --- +const penpotPsqlInputSchema = { + type: "object", + properties: { + sql: { + type: "string", + description: "SQL command to execute", + }, + test: { + type: "boolean", + description: "Use the penpot_test database", + }, + }, + required: ["sql"], + additionalProperties: false, +} +const parenRepairInputSchema = { + type: "object", + properties: { + // A string is used instead of an array so OpenCode displays it + // in the generic tool invocation. + files: { + type: "string", + description: + "Comma-separated file paths to fix, for example: frontend/src/app/config.cljs, backend/src/core.clj", + }, + code: { + type: "string", + description: "Code string to fix via stdin", + }, + }, + additionalProperties: false, +} +async function setup(ctx) { + // Plugin instance location. This is not the location of every session the + // tools may run for, but it is the closest V2 equivalent of the V1 + // per-execution `context.worktree` (the repo checkout the plugin loaded + // from), which is what both tools need as cwd / script base. + const directory = + ctx.location.directory ?? ctx.location.project?.canonical + // Keep this callback synchronous: transforms are replayable state edits. + // The async work happens later, inside each tool's `execute`. + await ctx.tool.transform((editor) => { + editor.add({ + name: "penpot-psql", + description: + "Execute a SQL command against the Penpot database. Uses the defaults from scripts/psql.", + input: penpotPsqlInputSchema, + async execute(input) { + const content = await executePsql( + input.sql, + input.test === true, + directory, + ) + return { content } + }, + }) + editor.add({ + name: "paren-repair", + description: + "Fix mismatched parentheses/braces in Clojure files (.clj, .cljs, .cljc) then reformat with cljfmt.", + input: parenRepairInputSchema, + async execute(input) { + const content = await executeParenRepair(input, directory) + return { content } + }, + }) + }) +} - - - - -// import { tool } from "@opencode-ai/plugin" -// import path from "path" -// import { spawn } from "child_process" - -// function formatFiles(files) { -// if (files.length === 0) return "stdin" - -// // Keep the visible tool title reasonably short. -// if (files.length <= 3) return files.join(", ") - -// return `${files.slice(0, 3).join(", ")} (+${files.length - 3} more)` -// } - -// const parenRepairTool = tool({ -// description: -// "Fix mismatched parentheses/braces in Clojure files, then reformat with cljfmt.", - -// args: { -// files: tool.schema -// .array(tool.schema.string()) -// .describe("Array of file paths to fix") -// .optional(), - -// code: tool.schema -// .string() -// .describe("Code string to fix via stdin") -// .optional(), -// }, - -// async execute(args, context) { -// const script = path.join(context.worktree, "scripts/paren-repair") - -// const files = (args.files ?? []).map((file) => { -// const absolute = path.isAbsolute(file) -// ? file -// : path.resolve(context.worktree, file) - -// return path.relative(context.worktree, absolute) -// }) - -// const targetSummary = -// files.length > 0 -// ? formatFiles(files) -// : args.code !== undefined -// ? `stdin (${args.code.length} chars)` -// : "no input" - -// // This updates the tool-call title immediately, while it is running. -// await context.metadata({ -// title: `Paren repair: ${targetSummary}`, -// metadata: { -// files, -// codeChars: args.code?.length, -// }, -// }) - -// const childArgs = -// args.files && args.files.length > 0 -// ? [script, ...args.files] -// : [script] - -// return new Promise((resolve) => { -// const proc = spawn("bb", childArgs, { -// cwd: context.worktree, -// }) - -// let stdout = "" -// let stderr = "" - -// if (args.code !== undefined) { -// proc.stdin.end(args.code) -// } - -// proc.stdout.on("data", (data) => { -// stdout += data.toString() -// }) - -// proc.stderr.on("data", (data) => { -// stderr += data.toString() -// }) - -// proc.on("close", (exitCode) => { -// const successful = exitCode === 0 - -// const commandOutput = successful -// ? stdout.trim() || "No changes needed" -// : `Error (exit ${exitCode}): ${(stderr || stdout).trim()}` - -// const parameterOutput = -// files.length > 0 -// ? `Files passed:\n${files.map((file) => `- ${file}`).join("\n")}` -// : args.code !== undefined -// ? `Input passed through stdin: ${args.code.length} characters` -// : "No files or stdin input were passed" - -// resolve({ -// title: `Paren repair: ${targetSummary}`, -// output: `${parameterOutput}\n\n${commandOutput}`, -// metadata: { -// files, -// codeChars: args.code?.length, -// exitCode, -// successful, -// }, -// }) -// }) - -// proc.on("error", (error) => { -// resolve({ -// title: `Paren repair failed: ${targetSummary}`, -// output: [ -// files.length > 0 -// ? `Files passed:\n${files.map((file) => `- ${file}`).join("\n")}` -// : `Input: ${targetSummary}`, -// `Failed to start bb: ${error.message}`, -// ].join("\n\n"), -// metadata: { -// files, -// codeChars: args.code?.length, -// successful: false, -// }, -// }) -// }) -// }) -// }, -// }) - -// export default async function plugin() { -// return { -// tool: { -// "paren-repair": parenRepairTool, -// }, -// } -// } +export default { + id: "penpot", + setup, + server, +} diff --git a/.serena/memories/backend/auth-permissions-product-domains.md b/.serena/memories/backend/auth-permissions-product-domains.md index dbce17a440..c3057118d5 100644 --- a/.serena/memories/backend/auth-permissions-product-domains.md +++ b/.serena/memories/backend/auth-permissions-product-domains.md @@ -23,7 +23,7 @@ - Team/project commands mix DB changes, email, message bus notifications, media/storage cleanup, feature flags, quotas, and audit metadata. Keep mutations transactional when the existing command does so. - Invitation flows validate muted/bounced emails before sending and use tokenized invitation state. Accepting an invitation is tied to the invited member identity, not just possession of a token. - Logical deletion is used for many product objects; prefer existing logical-deletion helpers over hard deletes unless the command already performs permanent cleanup. -- Bounced/spam-complaint emails can mute/block a profile for login/registration and email sending. Devenv MailCatcher is the normal local path for registration/email-flow testing. +- Bounced/spam-complaint emails can mute/block a profile for login/registration and email sending. Devenv Mailpit is the normal local path for registration/email-flow testing. ## Comments, webhooks, and audit @@ -37,4 +37,4 @@ - Enable LDAP login locally with frontend flag `enable-login-with-ldap`; the devenv includes a configured test LDAP service. - OIDC testing requires external provider app credentials plus matching backend/frontend config. - Backend domain tests usually live under `backend/test/backend_tests/rpc/commands/*_test.clj` or nearby backend test namespaces. Use focused `clojure -M:dev:test --focus ...` from `backend/` when possible. -- For auth/session or HTTP behavior, combine backend tests with the HTTP/session notes in `mem:backend/http-storage-filedata-subtleties` because RPC-level tests may not exercise cookie/header transforms. \ No newline at end of file +- For auth/session or HTTP behavior, combine backend tests with the HTTP/session notes in `mem:backend/http-storage-filedata-subtleties` because RPC-level tests may not exercise cookie/header transforms. diff --git a/.serena/memories/critical-info.md b/.serena/memories/critical-info.md index 892e5687f2..ce87caee30 100644 --- a/.serena/memories/critical-info.md +++ b/.serena/memories/critical-info.md @@ -18,7 +18,7 @@ You are working on the GitHub project `penpot/penpot`, a monorepo. - Before `gh issue create` → `mem:workflow/creating-issues` (title derivation, body template, labels, Issue Type) - Before `gh pr create` / `gh pr edit` → `mem:workflow/creating-prs` (title format, body structure, "Note:" line) - Before a repo-wide pnpm version update → `mem:workflow/updating-pnpm` (workspace - layout, `corepack use` sweep order, the stamp-missing-field and + layout, `scripts/sync-pnpm-version` flow, the stamp-missing-field and ignored-builds gotchas, verification steps) - **Never `git push`, force-push, or modify `git origin`** (or any other remote). The user pushes from their own shell; if a push is required, say so and wait. Never amend a commit that the user has already pushed unless explicitly asked. - **Never edit `CHANGES.md` by hand.** The changelog is generated from GitHub milestones during the release process; update it only via the `update-changelog` skill flow or on explicit user request. @@ -55,7 +55,7 @@ module. You can read it from `mem:/core` - `docker/` contains devenv related code, not needed unless specifically instructed. When working on devenv startup, compose layout, instance config (`defaults.env`), - tmux session lifecycle, MinIO provisioning, or anything in `manage.sh`'s + tmux session lifecycle, RustFS provisioning, or anything in `manage.sh`'s `*-devenv` commands, read `mem:devenv/core`. - `experiments/` contains standalone experimental HTML/JS/scripts; treat it as non-core unless the user explicitly asks about it. - `sample_media/` contains sample image/icon media and config used as fixtures/demo material; do not infer app behavior from it. diff --git a/.serena/memories/devenv/core.md b/.serena/memories/devenv/core.md index db46542c99..ddc83b1845 100644 --- a/.serena/memories/devenv/core.md +++ b/.serena/memories/devenv/core.md @@ -1,10 +1,10 @@ # Devenv startup and configuration -Compose-based dev environment under `docker/devenv/`, driven by `manage.sh`. Parallel instances share infra + Postgres + MinIO; each instance has its own `main` container, Valkey, source checkout, tmux session. +Compose-based dev environment under `docker/devenv/`, driven by `manage.sh`. Parallel instances share infra + Postgres + RustFS; each instance has its own `main` container, Valkey, source checkout, tmux session. ## Compose project layout -- `penpotdev-infra`: shared `postgres`, `minio`, `minio-setup`, `mailer`, `ldap`. File: `docker-compose.infra.yml`. +- `penpotdev-infra`: shared `postgres`, `rustfs`, `valkey`, `mailer`, `ldap`. File: `docker-compose.infra.yml`. - `penpotdev-wsN` (N=0,1,…): per-instance `main` + `redis` (Valkey). File: `docker-compose.main.yml`. ws0 (a.k.a. `main`) binds `$PWD`; ws1+ bind clones at `${PENPOT_WORKSPACES_DIR}/wsN/` (default `~/.penpot/penpot_workspaces/`), maintained by the developer. - Optional overlay `docker-compose.opencode.yml`: added by `instance-compose` as an extra `-f` only when `PENPOT_OPENCODE_CONFIG_DIR` is set (i.e. `run-devenv --opencode-config-dir DIR` ran in this process). Bind-mounts the host dir at `/home/penpot/.config/opencode` (`:z`). Flag-only, per-call; not read from ambient env. Parser `parse-opencode-config-dir` absolutizes (`~`, realpath) because compose resolves relative bind sources against the compose file's dir. Only instances brought up with the flag get the mount. - All projects join external network `penpot_shared`. Created idempotently by `ensure-devenv-network`, never removed by lifecycle commands. @@ -13,22 +13,22 @@ Compose-based dev environment under `docker/devenv/`, driven by `manage.sh`. Par - `docker/devenv/defaults.env`: ws0 baseline — container/volume names, runtime env, published host ports, tmux defaults. `manage.sh` aborts if unreadable. - For ws1+, `instance-env-overrides` computes the per-instance overrides (container/volume names, host ports offset `10000·N`, `PENPOT_PUBLIC_URI`, `PENPOT_REDIS_URI`, `PENPOT_BACKEND_WORKER=false`) and `instance-compose` injects them as env vars at compose time — never written to disk, recomputed each call so they can't drift. ws0 uses `defaults.env` as-is. -- `backend/scripts/_env`: backend-internal only — secret keys, `PENPOT_FLAGS` (with `enable-backend-worker` gated on `PENPOT_BACKEND_WORKER`), `JAVA_OPTS`, `setup_minio()`. Never duplicates `defaults.env`. +- `backend/scripts/_env`: backend-internal only — secret keys, `PENPOT_FLAGS` (with `enable-backend-worker` gated on `PENPOT_BACKEND_WORKER`), `JAVA_OPTS`, `setup_s3_bucket()`. Never duplicates `defaults.env`. - Compose files use pure `${VAR}` substitution; missing var = compose fails. ## Invariants - `infra-compose` / `instance-compose` wrap `docker compose` with `env -i`, then re-inject what compose needs. Stripping is required because `defaults.env` is sourced into manage.sh's shell at startup (stale values would leak); the ws1+ overrides are deliberately re-injected as shell env vars precisely because Compose gives shell precedence over `--env-file`, so they override the `defaults.env` baseline. -- Volume names pinned via `name:` (PENPOT_*_VOLUME), decoupled from the compose project name. ws1+ inject distinct per-instance volume names; ws0 keeps the historical `penpotdev_*` physical names so project renames never require data migration. +- Volume names pinned via `name:` (PENPOT_*_VOLUME), decoupled from the compose project name. Replacement services use new volumes and leave old volumes untouched. PostgreSQL 18 mounts `penpotdev_postgres_data_pg18` at `/var/lib/postgresql`; the old PG16 volume is not migrated automatically. - Network aliases (`- main`, `- redis`) are not declared in main.yml. Compose's auto-service-alias still registers `redis` on the shared network, so DNS for `redis` is non-deterministic with multiple instances. Backend uses `PENPOT_REDIS_URI=redis://penpot-devenv-wsN-valkey/0` (container_name) instead. -- No cross-project `depends_on`. `manage.sh ensure-infra-up` `docker wait`s on the `minio-setup` one-shot. +- No cross-project `depends_on`. `manage.sh ensure-infra-up` uses Compose `--wait`; PostgreSQL, RustFS, and Mailpit expose healthchecks. PostgreSQL checks TCP so its temporary init server cannot report ready. - `JAVA_OPTS` in `manage.sh` is shadowed inside the container by `_env`. The `-e JAVA_OPTS=...` flag only matters for processes that don't source `_env`. ## Worker policy Backend workers run only on ws0. `_env` gates `enable-backend-worker` on `PENPOT_BACKEND_WORKER`; ws1+ inject it as false. Workers are pure fire-and-forget: `wrk/submit!` inserts a row into the shared Postgres `task` table and returns; RPC handlers never wait on completion and workers never publish to msgbus. The reason for "ws0 only" is avoiding multi-instance worker races (cron dedup is best-effort across instances, `wrk/submit!` `dedupe` is racy across submitters); details in `mem:prod-infra/core`. -Each workspace is independent and can be started/stopped in any order. Shared infra (postgres, minio, etc.) is shut down only when no instances remain running. +Each workspace is independent and can be started/stopped in any order. Shared infra (Postgres, RustFS, etc.) is shut down only when no instances remain running. ## Port layout @@ -43,7 +43,7 @@ Container-internal ports fixed; host side offset `10000·N`. | 14181 | 24181 | … | 14281 | Serena MCP | | 14182 | 24182 | … | 24282 | Serena dashboard | -Everything else (frontend dev, backend API, exporter, storybook, REPLs, plugin dev, MCP inspector/WebSocket) is in-process or same-origin via Caddy/nginx. Infra publishes: mailer 1080, ldap 10389/10636 (singletons, not offset). +Everything else (frontend dev, backend API, exporter, storybook, REPLs, plugin dev, MCP inspector/WebSocket) is in-process or same-origin via Caddy/nginx. Infra publishes RustFS S3 API 9000, RustFS console 9001, and Mailpit UI 1080 on loopback; ldap 10389/10636 remains a singleton without offsets. Mailpit stores its SQLite database in `penpotdev_mailpit_data`. ## Tmux + MCP routing diff --git a/.serena/memories/prod-infra/core.md b/.serena/memories/prod-infra/core.md index 1ec5af0308..ee7dc542b0 100644 --- a/.serena/memories/prod-infra/core.md +++ b/.serena/memories/prod-infra/core.md @@ -6,7 +6,7 @@ Backend (`app.config`, `PENPOT_*` env vars) is parameterized; deployments choose - **PostgreSQL**: durable store. Profiles, teams, files, sessions, audit, `storage_object` metadata, the `task` queue, `scheduled_task` cron registry, migrations. File-data also lives here when the file-data backend is `legacy-db`/`db`. One shared DB across all backends. - **Redis (Valkey-compatible)**: per-backend message bus and cache. Concrete uses: msgbus Pub/Sub for collaborative-editing broadcasts and team/profile-org notifications fired by RPC handlers (`app.rpc.notifications`, `files_update`, `teams`, `websocket`); file-summary cache gated by `enable-redis-cache`; rate-limit counters; and the dispatcher→runner work hand-off list `penpot.worker.queue::`. `PENPOT_REDIS_URI`. -- **Object storage**: backends `:s3` and `:fs`. S3 in prod; devenv uses MinIO. Holds uploaded media, file-data when the file-data backend is `storage`, exports. Backend-side details (resolve, dedup, bucket set, object lifecycle, and file-data backends): `mem:backend/storage`. +- **Object storage**: backends `:s3` and `:fs`. S3 in prod; devenv uses RustFS. Holds uploaded media, file-data when the file-data backend is `storage`, exports. Backend-side details (resolve, dedup, bucket set, object lifecycle, and file-data backends): `mem:backend/storage`. - **SMTP mailer**: invitations, password resets, email verification (sent via the `:sendmail` worker task). - **LDAP** (optional auth provider): helpers in `app.auth.*`, gated by `enable-login-with-ldap`. diff --git a/.serena/memories/scripts/paren-repair.md b/.serena/memories/scripts/paren-repair.md index 234a4c8688..2774058fbc 100644 --- a/.serena/memories/scripts/paren-repair.md +++ b/.serena/memories/scripts/paren-repair.md @@ -29,9 +29,10 @@ bb scripts/paren-repair --help ## Native Tool Available (opencode) -A native opencode tool `paren-repair` is available at `.opencode/scripts/paren-repair.ts`. -The LLM can call it directly with: -- `files`: Array of file paths to fix +A native opencode tool `paren-repair` is available (defined in +`.opencode/plugins/penpot.js`, which registers it for both opencode V1 via +`server()` and opencode V2 via `setup()`). The LLM can call it directly with: +- `files`: comma-separated file paths to fix (a string, not an array) - `code`: Code string to fix via stdin Example usage by the LLM: diff --git a/.serena/memories/scripts/psql.md b/.serena/memories/scripts/psql.md index c969fd6742..68d7b66d0f 100644 --- a/.serena/memories/scripts/psql.md +++ b/.serena/memories/scripts/psql.md @@ -27,7 +27,9 @@ scripts/psql --host myhost --user myuser --db mydb ## Native Tool Available (opencode) -A native opencode tool `penpot-psql` is available. The LLM can call it directly +A native opencode tool `penpot-psql` is available (defined in +`.opencode/plugins/penpot.js`, which registers it for both opencode V1 via +`server()` and opencode V2 via `setup()`). The LLM can call it directly with: - `sql`: SQL command string to execute - `test`: Boolean flag to use the `penpot_test` database diff --git a/.serena/memories/workflow/updating-pnpm.md b/.serena/memories/workflow/updating-pnpm.md index 54a5bf26e3..1bb1596959 100644 --- a/.serena/memories/workflow/updating-pnpm.md +++ b/.serena/memories/workflow/updating-pnpm.md @@ -23,33 +23,46 @@ file (never pipe tool output through filters). - The store survives `node_modules` cleans. It is content-addressed and integrity-verified, so it cannot go stale; staleness lives in node_modules. Only `scripts/clean-node-modules --store` removes it. -- Every `package.json` (about 35 of them) must carry a `packageManager` field - with the identical `pnpm@+sha512.` value. Do not let them drift. -- CI pins no pnpm version; workflows rely on corepack reading - `packageManager`. Fixing the fields fixes CI. +- Every first-party `package.json` (35 files: workspace roots plus + members) must carry a `packageManager` field with the identical + `pnpm@+sha512.` value. Do not let them drift. + `external/` (vendored trees with their own lifecycles), `.opencode/`, + `.pnpm-store/`, `bundles/` and `docker/images/bundle-*` copies are + never stamped. +- pnpm is a system binary everywhere (devenv image, CI runners, Docker + images). Nothing may call corepack: it is gone from Node 25+. pnpm + auto-downloads the `packageManager` version on mismatch + (`pmOnFail: download`, the default), so drift self-heals; aligned pins + just skip the download. +- The pnpm version pin lives in three places that move together: + `PNPM_VERSION` (+ arch SHAs) in `docker/devenv/Dockerfile` and in + `docker/images/Dockerfile.{media-processor,exporter,mcp}`, and the + `packageManager` fields (stamped by the script below). The + `plugins-deploy-*` workflows need no pnpm pin: a single `pnpm/setup` + step (`working-directory: plugins`, `install: false`) reads it from + the manifest and installs the pinned Node via `runtime: node@`; + bump that pin together with `.nvmrc` on Node updates. ## Procedure -1. Resolve the target tag first and note the version. Example: - `npm view pnpm dist-tags --json` for `next-12` (latest 12.x). The tag - moves over time; always re-check. -2. List every directory with a `package.json`, excluding `node_modules` - (`fd -H -t f package.json -E node_modules`). This list is the work set; - do not maintain a hand-written list. -3. Run `corepack use pnpm@` in workspace roots first, then members. - `corepack use` stamps `packageManager` in the nearest package.json and - runs an install. Member runs repeat the workspace install; after the root - run they are quick no-ops. -4. If a run fails, fix the cause (see gotchas) and re-run that directory. +1. Resolve the target version first and note it. Example: + `pnpm view pnpm dist-tags --json` for `latest-12` (latest 12.x). The + tag moves over time; always re-check. +2. Bump `PNPM_VERSION` (+ arch SHAs) in `docker/devenv/Dockerfile` and in + `docker/images/Dockerfile.{media-processor,exporter,mcp}`. +3. From the repo root, on a host whose system pnpm is the target version + (rebuilt devenv), run `scripts/sync-pnpm-version`. It resolves the + integrity hash via `pnpm view` + node (no npm, no corepack, no + python3) and stamps the identical field into all 35 files, replacing + only the value line (each file keeps its own indent) and inserting + the key after `"name"`/`"version"` where missing. Explicit version + instead: `scripts/sync-pnpm-version `. +4. Run `scripts/sync-pnpm-version --install` (plain `pnpm install` in + each of the 11 workspace roots) to refresh lockfile metadata, then + fix any failing workspace (see gotchas) and re-run that directory. ## Gotchas -- `corepack use` only updates an existing `packageManager` field. If a - package.json lacks the field, corepack walks up to the nearest ancestor - that has one and stamps that file instead; the member stays unstamped. - After the sweep, assert every package.json carries the field. For a - missing one, insert the identical `pnpm@+sha512.` string, - then re-run `corepack use pnpm@` in that directory. - A workspace may fail with `ERR_PNPM_IGNORED_BUILDS`, and pnpm then writes a placeholder scaffold into its `pnpm-workspace.yaml`: `allowBuilds: esbuild: set this to true or false`. Current pnpm writes @@ -70,10 +83,12 @@ file (never pipe tool output through filters). ## Verification -- Every `packageManager` field is byte-identical (same version and hash). -- `pnpm --version` in each workspace prints the target version. +- `scripts/sync-pnpm-version --check` passes: every `packageManager` + field is byte-identical (same version and hash). - `pnpm install --frozen-lockfile` succeeds in each of the 11 workspaces. - `git diff` on lockfiles matches the expectations above. +- No `corepack` call remains in scripts, workflows, Dockerfiles or docs + (`rg corepack` shows only `CHANGES.md` history). ## Cleaning stale node_modules diff --git a/backend/package.json b/backend/package.json index f29469a28e..6b65767aec 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "repository": { "type": "git", "url": "https://github.com/penpot/penpot" diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index dfe9e50563..ce36558530 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -7,96 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.3.4 - version: 12.3.4 + specifier: 12.5.1 + version: 12.5.1 packages: - '@pnpm/exe.darwin-arm64@12.3.4': - resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + '@pnpm/exe.android-arm64@12.5.1': + resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.5.1': + resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.5.1': + resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.3.4': - resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + '@pnpm/exe.darwin-x64@12.5.1': + resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.3.4': - resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + '@pnpm/exe.freebsd-x64@12.5.1': + resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.5.1': + resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.3.4': - resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + '@pnpm/exe.linux-arm64@12.5.1': + resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.3.4': - resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + '@pnpm/exe.linux-ppc64@12.5.1': + resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.5.1': + resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.5.1': + resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.5.1': + resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.3.4': - resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + '@pnpm/exe.linux-x64@12.5.1': + resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.3.4': - resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + '@pnpm/exe.win32-arm64@12.5.1': + resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.3.4': - resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + '@pnpm/exe.win32-x64@12.5.1': + resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} cpu: [x64] os: [win32] - pnpm@12.3.4: - resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + pnpm@12.5.1: + resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.3.4': + '@pnpm/exe.android-arm64@12.5.1': optional: true - '@pnpm/exe.darwin-x64@12.3.4': + '@pnpm/exe.android-x64@12.5.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.3.4': + '@pnpm/exe.darwin-arm64@12.5.1': optional: true - '@pnpm/exe.linux-arm64@12.3.4': + '@pnpm/exe.darwin-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64-musl@12.3.4': + '@pnpm/exe.freebsd-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64@12.3.4': + '@pnpm/exe.linux-arm64-musl@12.5.1': optional: true - '@pnpm/exe.win32-arm64@12.3.4': + '@pnpm/exe.linux-arm64@12.5.1': optional: true - '@pnpm/exe.win32-x64@12.3.4': + '@pnpm/exe.linux-ppc64@12.5.1': optional: true - pnpm@12.3.4: + '@pnpm/exe.linux-riscv64@12.5.1': + optional: true + + '@pnpm/exe.linux-s390x@12.5.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.5.1': + optional: true + + '@pnpm/exe.linux-x64@12.5.1': + optional: true + + '@pnpm/exe.win32-arm64@12.5.1': + optional: true + + '@pnpm/exe.win32-x64@12.5.1': + optional: true + + pnpm@12.5.1: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 + '@pnpm/exe.android-arm64': 12.5.1 + '@pnpm/exe.android-x64': 12.5.1 + '@pnpm/exe.darwin-arm64': 12.5.1 + '@pnpm/exe.darwin-x64': 12.5.1 + '@pnpm/exe.freebsd-x64': 12.5.1 + '@pnpm/exe.linux-arm64': 12.5.1 + '@pnpm/exe.linux-arm64-musl': 12.5.1 + '@pnpm/exe.linux-ppc64': 12.5.1 + '@pnpm/exe.linux-riscv64': 12.5.1 + '@pnpm/exe.linux-s390x': 12.5.1 + '@pnpm/exe.linux-x64': 12.5.1 + '@pnpm/exe.linux-x64-musl': 12.5.1 + '@pnpm/exe.win32-arm64': 12.5.1 + '@pnpm/exe.win32-x64': 12.5.1 --- lockfileVersion: '9.0' diff --git a/backend/scripts/_env b/backend/scripts/_env index 7bcd8f5ccb..3a90765589 100644 --- a/backend/scripts/_env +++ b/backend/scripts/_env @@ -91,15 +91,33 @@ export JAVA_OPTS="\ --enable-preview \ --enable-native-access=ALL-UNNAMED"; -function setup_minio() { +function setup_s3_bucket() { if [ "${PENPOT_OBJECTS_STORAGE_BACKEND}" != "s3" ]; then return 0 fi - # Shared MinIO user/policy provisioning is handled by docker-compose.infra.yml. - # Per process startup only ensures that the configured bucket exists. - mc alias set penpot-s3/ "${PENPOT_OBJECTS_STORAGE_S3_ENDPOINT}" minioadmin minioadmin -q - mc mb "penpot-s3/${PENPOT_OBJECTS_STORAGE_S3_BUCKET}" -p -q + local attempts=0 + local endpoint="${PENPOT_OBJECTS_STORAGE_S3_ENDPOINT}" + local bucket="${PENPOT_OBJECTS_STORAGE_S3_BUCKET}" + local region="${PENPOT_OBJECTS_STORAGE_S3_REGION}" + + until aws s3api head-bucket \ + --endpoint-url "$endpoint" \ + --region "$region" \ + --bucket "$bucket" >/dev/null 2>&1; do + if aws s3 mb "s3://${bucket}" \ + --endpoint-url "$endpoint" \ + --region "$region" >/dev/null 2>&1; then + return 0 + fi + + attempts=$((attempts + 1)) + if [ "$attempts" -ge 30 ]; then + echo "setup_s3_bucket: unable to create s3://${bucket} after ${attempts} attempts" >&2 + return 1 + fi + sleep 1 + done } diff --git a/backend/scripts/repl b/backend/scripts/repl index a2c179065e..291b0fe1c7 100755 --- a/backend/scripts/repl +++ b/backend/scripts/repl @@ -7,8 +7,8 @@ if [ -f $SCRIPT_DIR/_env.local ]; then source $SCRIPT_DIR/_env.local; fi -# Initialize MINIO config -setup_minio; +# Ensure the object storage bucket exists. +setup_s3_bucket; export JAVA_OPTS="$JAVA_OPTS -Dlog4j2.configurationFile=log4j2-devenv-repl.xml" export OPTIONS="-A:jmx-remote -A:dev" diff --git a/backend/scripts/run b/backend/scripts/run index 49b2bd4ba6..51ab6d6002 100755 --- a/backend/scripts/run +++ b/backend/scripts/run @@ -8,6 +8,9 @@ if [ -f $SCRIPT_DIR/_env.local ]; then source $SCRIPT_DIR/_env.local; fi +# Ensure the object storage bucket exists. +setup_s3_bucket; + export OPTIONS="-A:dev" entrypoint=${1:-app.main}; diff --git a/backend/scripts/start-dev b/backend/scripts/start-dev index d3389a0a6f..6376d69110 100755 --- a/backend/scripts/start-dev +++ b/backend/scripts/start-dev @@ -7,8 +7,8 @@ if [ -f $SCRIPT_DIR/_env.local ]; then source $SCRIPT_DIR/_env.local; fi -# Initialize MINIO config -setup_minio; +# Ensure the object storage bucket exists. +setup_s3_bucket; shift 1; set -ex diff --git a/common/package.json b/common/package.json index 4269da7ba6..c149915f43 100644 --- a/common/package.json +++ b/common/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "type": "module", "repository": { "type": "git", diff --git a/common/pnpm-lock.yaml b/common/pnpm-lock.yaml index ef70c46056..6026b397e6 100644 --- a/common/pnpm-lock.yaml +++ b/common/pnpm-lock.yaml @@ -7,96 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.3.4 - version: 12.3.4 + specifier: 12.5.1 + version: 12.5.1 packages: - '@pnpm/exe.darwin-arm64@12.3.4': - resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + '@pnpm/exe.android-arm64@12.5.1': + resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.5.1': + resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.5.1': + resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.3.4': - resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + '@pnpm/exe.darwin-x64@12.5.1': + resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.3.4': - resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + '@pnpm/exe.freebsd-x64@12.5.1': + resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.5.1': + resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.3.4': - resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + '@pnpm/exe.linux-arm64@12.5.1': + resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.3.4': - resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + '@pnpm/exe.linux-ppc64@12.5.1': + resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.5.1': + resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.5.1': + resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.5.1': + resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.3.4': - resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + '@pnpm/exe.linux-x64@12.5.1': + resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.3.4': - resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + '@pnpm/exe.win32-arm64@12.5.1': + resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.3.4': - resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + '@pnpm/exe.win32-x64@12.5.1': + resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} cpu: [x64] os: [win32] - pnpm@12.3.4: - resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + pnpm@12.5.1: + resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.3.4': + '@pnpm/exe.android-arm64@12.5.1': optional: true - '@pnpm/exe.darwin-x64@12.3.4': + '@pnpm/exe.android-x64@12.5.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.3.4': + '@pnpm/exe.darwin-arm64@12.5.1': optional: true - '@pnpm/exe.linux-arm64@12.3.4': + '@pnpm/exe.darwin-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64-musl@12.3.4': + '@pnpm/exe.freebsd-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64@12.3.4': + '@pnpm/exe.linux-arm64-musl@12.5.1': optional: true - '@pnpm/exe.win32-arm64@12.3.4': + '@pnpm/exe.linux-arm64@12.5.1': optional: true - '@pnpm/exe.win32-x64@12.3.4': + '@pnpm/exe.linux-ppc64@12.5.1': optional: true - pnpm@12.3.4: + '@pnpm/exe.linux-riscv64@12.5.1': + optional: true + + '@pnpm/exe.linux-s390x@12.5.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.5.1': + optional: true + + '@pnpm/exe.linux-x64@12.5.1': + optional: true + + '@pnpm/exe.win32-arm64@12.5.1': + optional: true + + '@pnpm/exe.win32-x64@12.5.1': + optional: true + + pnpm@12.5.1: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 + '@pnpm/exe.android-arm64': 12.5.1 + '@pnpm/exe.android-x64': 12.5.1 + '@pnpm/exe.darwin-arm64': 12.5.1 + '@pnpm/exe.darwin-x64': 12.5.1 + '@pnpm/exe.freebsd-x64': 12.5.1 + '@pnpm/exe.linux-arm64': 12.5.1 + '@pnpm/exe.linux-arm64-musl': 12.5.1 + '@pnpm/exe.linux-ppc64': 12.5.1 + '@pnpm/exe.linux-riscv64': 12.5.1 + '@pnpm/exe.linux-s390x': 12.5.1 + '@pnpm/exe.linux-x64': 12.5.1 + '@pnpm/exe.linux-x64-musl': 12.5.1 + '@pnpm/exe.win32-arm64': 12.5.1 + '@pnpm/exe.win32-x64': 12.5.1 --- lockfileVersion: '9.0' diff --git a/common/scripts/test b/common/scripts/test index ced37269e4..b22dbff38d 100755 --- a/common/scripts/test +++ b/common/scripts/test @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -ex -corepack enable; -corepack install; pnpm install; pnpm run test; clojure -M:dev:test; diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index c2aa3c35e2..f28a277b07 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -66,7 +66,8 @@ RUN set -ex; \ FROM base AS setup-node -ENV NODE_VERSION=v24.20.0 \ +ENV NODE_VERSION=v24.21.0 \ + PNPM_VERSION=12.5.1 \ PATH=/opt/node/bin:$PATH RUN set -eux; \ @@ -75,10 +76,14 @@ RUN set -eux; \ aarch64|arm64) \ OPENSSL_ARCH='linux-aarch64'; \ BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \ + PNPM_ARCH='arm64'; \ + PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \ ;; \ amd64|x86_64) \ OPENSSL_ARCH='linux-x86_64'; \ BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \ + PNPM_ARCH='x64'; \ + PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \ ;; \ *) \ echo "Unsupported arch: ${ARCH}"; \ @@ -89,10 +94,14 @@ RUN set -eux; \ mkdir -p /opt/node; \ cd /opt/node; \ tar -xf /tmp/nodejs.tar.gz --strip-components=1; \ + PNPM_URL="https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/pnpm-linux-${PNPM_ARCH}.tar.gz"; \ + curl -LfsSo /tmp/pnpm.tar.gz "${PNPM_URL}"; \ + echo "${PNPM_SHA256} */tmp/pnpm.tar.gz" | sha256sum -c -; \ + tar -xzf /tmp/pnpm.tar.gz -C /opt/node/bin pnpm; \ + chmod 755 /opt/node/bin/pnpm; \ chown -R root /opt/node; \ find /opt/node/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; ; \ - corepack enable; \ - rm -rf /tmp/nodejs.tar.gz; + rm -f /tmp/nodejs.tar.gz /tmp/pnpm.tar.gz; ################################################################################ ## OPENCODE SETUP @@ -100,7 +109,7 @@ RUN set -eux; \ FROM base AS setup-opencode -ENV OPENCODE_VERSION=1.18.25 +ENV OPENCODE_VERSION=1.18.31 RUN set -ex; \ ARCH="$(dpkg --print-architecture)"; \ @@ -121,6 +130,37 @@ RUN set -ex; \ chmod +x /tmp/opencode/opencode; \ rm -f /tmp/opencode.tar.gz; +################################################################################ +## OPENCODE2 SETUP (V2, side-by-side with V1 `opencode`) +################################################################################ + +FROM base AS setup-opencode2 + +ENV OPENCODE2_VERSION=2.0.12 + +RUN set -eux; \ + ARCH="$(dpkg --print-architecture)"; \ + case "${ARCH}" in \ + aarch64|arm64) \ + BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-arm64/-/cli-linux-arm64-${OPENCODE2_VERSION}.tgz"; \ + ESUM='33f0dde9f0f055b6a3665d290371bcfc8c63daceade7a535c4b94d386f2bd917'; \ + ;; \ + amd64|x86_64) \ + BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-${OPENCODE2_VERSION}.tgz"; \ + ESUM='2a79beb2e24382cb2bdbb709237dbc22f498cf52a106d029a6a3a3243760c78b'; \ + ;; \ + *) \ + echo "Unsupported arch: ${ARCH}"; exit 1; \ + ;; \ + esac; \ + curl -LfsSo /tmp/opencode2.tgz "${BINARY_URL}"; \ + echo "${ESUM} */tmp/opencode2.tgz" | sha256sum -c -; \ + mkdir -p /tmp/opencode2; \ + tar -xzf /tmp/opencode2.tgz -C /tmp/opencode2 --strip-components=2 package/bin; \ + mv /tmp/opencode2/opencode /tmp/opencode2/opencode2; \ + chmod +x /tmp/opencode2/opencode2; \ + rm -f /tmp/opencode2.tgz; + ################################################################################ ## CADDYSERVER SETUP @@ -166,12 +206,12 @@ RUN set -eux; \ ARCH="$(dpkg --print-architecture)"; \ case "${ARCH}" in \ aarch64|arm64) \ - ESUM='5b222fce0b7076a10ac7ae3b1009a6c2caf4f35bc4e81de72010af6750c5e146'; \ - BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.32.13-ca-jdk26.0.2-linux_aarch64.tar.gz'; \ + ESUM='9fa5bf865783c43840101fcdc3a5222b60daf4fe11f26806247bf807c08aba38'; \ + BINARY_URL='https://cdn.azul.com/zulu/bin/zulu27.28.101-ca-jdk27.0.0-linux_aarch64.tar.gz'; \ ;; \ amd64|x86_64) \ - ESUM='4b7c114917aebd0fc6284fc7111245d7747a4d9603bd12d86b384b1abc9d575d'; \ - BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.32.13-ca-jdk26.0.2-linux_x64.tar.gz'; \ + ESUM='ccbc15c4edbedfdcc03c2d29a2aa2c6daf9e6ffb4cda7dd5ece0fcbb37350267'; \ + BINARY_URL='https://cdn.azul.com/zulu/bin/zulu27.28.101-ca-jdk27.0.0-linux_x64.tar.gz'; \ ;; \ *) \ echo "Unsupported arch: ${ARCH}"; \ @@ -242,16 +282,17 @@ RUN set -eux; \ FROM base AS setup-utils -ENV CLJKONDO_VERSION=2026.07.24 \ - BABASHKA_VERSION=1.13.219 \ +ENV CLJKONDO_VERSION=2026.08.04 \ + BABASHKA_VERSION=1.13.223 \ CLJFMT_VERSION=0.16.5 \ - PIXI_VERSION=0.75.0 \ - GITHUB_CLI_VERSION=2.97.0 \ - UV_VERSION=0.12.1 \ + PIXI_VERSION=0.81.0 \ + GITHUB_CLI_VERSION=2.101.0 \ + AWS_CLI_VERSION=2.36.49 \ + UV_VERSION=0.12.17 \ UV_TOOL_DIR=/opt/uv/tools \ UV_TOOL_BIN_DIR=/opt/utils/bin \ UV_PYTHON_INSTALL_DIR=/opt/uv/python \ - SERENA_VERSION=1.6.1 + SERENA_VERSION=1.7.0 RUN set -ex; \ ARCH="$(dpkg --print-architecture)"; \ @@ -356,24 +397,30 @@ RUN set -ex; \ tar -xv --strip-components=1 -f /tmp/gh.tar.gz; \ rm -rf /tmp/gh.tar.gz; -# Install minio client +# Install AWS CLI RUN set -ex; \ ARCH="$(dpkg --print-architecture)"; \ case "${ARCH}" in \ aarch64|arm64) \ - BINARY_URL="https://dl.min.io/client/mc/release/linux-arm64/mc"; \ + BINARY_ARCH="aarch64"; \ + BINARY_SHA256="2dec26fdaff2f5e34961ea98f456297104f73d8409fc3599c8f2909c6e27a07e"; \ ;; \ amd64|x86_64) \ - BINARY_URL="https://dl.min.io/client/mc/release/linux-amd64/mc"; \ + BINARY_ARCH="x86_64"; \ + BINARY_SHA256="6b51b62d232217350e72d8f2493f47d3461239d011d1d5eef960dfcd8cb07069"; \ ;; \ *) \ echo "Unsupported arch: ${ARCH}"; \ exit 1; \ ;; \ esac; \ - wget -O /tmp/mc ${BINARY_URL}; \ - mv /tmp/mc /opt/utils/bin/; \ - chmod +x /opt/utils/bin/mc; + BINARY_URL="https://awscli.amazonaws.com/awscli-exe-linux-${BINARY_ARCH}-${AWS_CLI_VERSION}.zip"; \ + curl -LfsSo /tmp/awscliv2.zip ${BINARY_URL}; \ + echo "${BINARY_SHA256} */tmp/awscliv2.zip" | sha256sum -c -; \ + cd /tmp; \ + unzip -q awscliv2.zip; \ + ./aws/install --install-dir /opt/utils/aws-cli --bin-dir /opt/utils/bin; \ + rm -rf /tmp/aws /tmp/awscliv2.zip; # Install uv RUN set -ex; \ @@ -503,7 +550,7 @@ RUN set -ex; \ curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc; \ echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt trixie-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; \ apt-get -qq update; \ - apt-get -qqy install postgresql-client-16; \ + apt-get -qqy install postgresql-client-18; \ apt-get clean; \ rm -rf /var/lib/apt/lists/*; @@ -540,6 +587,7 @@ COPY --from=setup-rust /opt/rustup /opt/rustup COPY --from=setup-rust /opt/emsdk /opt/emsdk COPY --from=setup-caddy /usr/bin/caddy /usr/bin/caddy COPY --from=setup-opencode /tmp/opencode/opencode /opt/utils/bin/opencode +COPY --from=setup-opencode2 /tmp/opencode2/opencode2 /opt/utils/bin/opencode2 COPY files/nginx.conf /etc/nginx/nginx.conf COPY files/nginx-mime.types /etc/nginx/mime.types @@ -560,4 +608,3 @@ COPY files/init.sh /home/init.sh ENTRYPOINT ["/home/entrypoint.sh"] CMD ["/home/init.sh"] - diff --git a/docker/devenv/defaults.env b/docker/devenv/defaults.env index 41276dfe86..7f9c107494 100644 --- a/docker/devenv/defaults.env +++ b/docker/devenv/defaults.env @@ -10,11 +10,12 @@ # Container names and volume names. Volumes are pinned by explicit name # (rather than relying on COMPOSE_PROJECT_NAME prefixing) so the physical -# volumes survive project renames without a data migration. ws0 reuses the -# pre-Stage-2 physical volume names (penpotdev_*). +# volumes survive project renames. Replacement services use new volume names +# so data from the previous service remains available for rollback. PENPOT_MAIN_CONTAINER_NAME=penpot-devenv-ws0-main -PENPOT_POSTGRES_DATA_VOLUME=penpotdev_postgres_data_pg16 -PENPOT_MINIO_DATA_VOLUME=penpotdev_minio_data +PENPOT_POSTGRES_DATA_VOLUME=penpotdev_postgres_data_pg18 +PENPOT_RUSTFS_DATA_VOLUME=penpotdev_rustfs_data +PENPOT_MAILPIT_DATA_VOLUME=penpotdev_mailpit_data PENPOT_USER_DATA_VOLUME=penpotdev_user_data # Backend runtime config (passed to the container env block). Valkey is a @@ -28,12 +29,14 @@ PENPOT_DATABASE_PASSWORD=penpot PENPOT_DATABASE_MAX_POOL_SIZE=20 PENPOT_REDIS_URI=redis://valkey/0 -# Object storage (MinIO user/policy are provisioned by the infra compose file). +# Object storage (RustFS uses these credentials for its root S3 user). PENPOT_OBJECTS_STORAGE_BACKEND=s3 -PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://minio:9000 +PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://rustfs:9000 PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot +PENPOT_OBJECTS_STORAGE_S3_REGION=us-east-1 AWS_ACCESS_KEY_ID=penpot-devenv AWS_SECRET_ACCESS_KEY=penpot-devenv +AWS_DEFAULT_REGION=us-east-1 # Published host ports. Only ports that need to be reachable from outside the # container are exposed; everything else (frontend dev server, backend API, diff --git a/docker/devenv/docker-compose.infra.yml b/docker/devenv/docker-compose.infra.yml index c32c6a6163..6a876b36ad 100644 --- a/docker/devenv/docker-compose.infra.yml +++ b/docker/devenv/docker-compose.infra.yml @@ -4,53 +4,47 @@ networks: external: true volumes: - postgres_data_pg16: + postgres_data_pg18: name: ${PENPOT_POSTGRES_DATA_VOLUME} - minio_data: - name: ${PENPOT_MINIO_DATA_VOLUME} + rustfs_data: + name: ${PENPOT_RUSTFS_DATA_VOLUME} + mailpit_data: + name: ${PENPOT_MAILPIT_DATA_VOLUME} valkey_data: name: penpotdev_valkey_data services: - minio: - image: "minio/minio:RELEASE.2025-04-03T14-56-28Z" - command: minio server /mnt/data --console-address ":9001" + rustfs: + image: "rustfs/rustfs:1.0.0" + command: /data volumes: - - "minio_data:/mnt/data" + - "rustfs_data:/data" environment: - - MINIO_ROOT_USER=minioadmin - - MINIO_ROOT_PASSWORD=minioadmin + - RUSTFS_ACCESS_KEY=${AWS_ACCESS_KEY_ID} + - RUSTFS_SECRET_KEY=${AWS_SECRET_ACCESS_KEY} + - RUSTFS_ADDRESS=:9000 + - RUSTFS_CONSOLE_ADDRESS=:9001 + - RUSTFS_CONSOLE_ENABLE=true + + ports: + - "127.0.0.1:9000:9000" + - "127.0.0.1:9001:9001" + + healthcheck: + test: ["CMD", "curl", "--fail", "http://localhost:9000/health"] + interval: 1s + timeout: 5s + retries: 30 networks: default: aliases: - - minio - - minio-setup: - image: "minio/mc:latest" - depends_on: - - minio - entrypoint: ["/bin/sh", "-c"] - command: - - | - attempts=0 - until mc alias set penpot-s3 http://minio:9000 minioadmin minioadmin -q; do - attempts=$$((attempts + 1)) - if [ "$$attempts" -ge 30 ]; then - echo "minio-setup: gave up waiting for MinIO after $$attempts attempts" >&2 - exit 1 - fi - sleep 1 - done - mc admin user info penpot-s3 penpot-devenv >/dev/null 2>&1 || mc admin user add penpot-s3 penpot-devenv penpot-devenv -q - mc admin policy attach penpot-s3 readwrite --user=penpot-devenv -q - networks: - default: + - rustfs postgres: - image: postgres:16.8 + image: postgres:18.6 command: postgres -c config_file=/etc/postgresql.conf restart: always stop_signal: SIGINT @@ -62,7 +56,12 @@ services: volumes: - ./files/postgresql.conf:/etc/postgresql.conf:z - ./files/postgresql_init.sql:/docker-entrypoint-initdb.d/init.sql:z - - postgres_data_pg16:/var/lib/postgresql/data + - postgres_data_pg18:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 2s + timeout: 5s + retries: 30 networks: default: aliases: @@ -80,12 +79,24 @@ services: - valkey mailer: - image: sj26/mailcatcher:latest + image: axllent/mailpit:v1.31.1 restart: always + volumes: + - "mailpit_data:/data" + environment: + - MP_DATABASE=/data/mailpit.db + - MP_MAX_MESSAGES=5000 + - MP_DISABLE_VERSION_CHECK=true expose: - '1025' ports: - - "1080:1080" + - "127.0.0.1:1080:8025" + + healthcheck: + test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:8025/readyz"] + interval: 2s + timeout: 5s + retries: 15 networks: default: diff --git a/docker/devenv/docker-compose.main.yml b/docker/devenv/docker-compose.main.yml index 35bac398ca..e593ad1ba7 100644 --- a/docker/devenv/docker-compose.main.yml +++ b/docker/devenv/docker-compose.main.yml @@ -84,8 +84,10 @@ services: - PENPOT_OBJECTS_STORAGE_BACKEND=${PENPOT_OBJECTS_STORAGE_BACKEND} - PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=${PENPOT_OBJECTS_STORAGE_S3_ENDPOINT} - PENPOT_OBJECTS_STORAGE_S3_BUCKET=${PENPOT_OBJECTS_STORAGE_S3_BUCKET} + - PENPOT_OBJECTS_STORAGE_S3_REGION=${PENPOT_OBJECTS_STORAGE_S3_REGION} - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} - PENPOT_TENANT=${PENPOT_TENANT} - PENPOT_TMUX_ATTACH=${PENPOT_TMUX_ATTACH} diff --git a/docker/devenv/files/postgresql_init.sql b/docker/devenv/files/postgresql_init.sql index c36960e258..8e3d7d3c1c 100644 --- a/docker/devenv/files/postgresql_init.sql +++ b/docker/devenv/files/postgresql_init.sql @@ -1,2 +1,2 @@ CREATE DATABASE penpot_test; -CREATE DATABASE penpot_telemetry; +CREATE DATABASE penpot_nexus; diff --git a/docker/images/Dockerfile.exporter b/docker/images/Dockerfile.exporter index 362b107ad9..3b12e37303 100644 --- a/docker/images/Dockerfile.exporter +++ b/docker/images/Dockerfile.exporter @@ -91,9 +91,35 @@ COPY --from=penpotapp/imagemagick:7.1.2-27 /opt/imagick /opt/imagick WORKDIR /opt/penpot/exporter -# DHI Node image installs Node at the system level (symlinked into -# /usr/bin), so `./setup`'s internal `corepack enable` needs root to write -# there. Ownership is fixed right after. +# pnpm ships as a system binary (same tarball + SHA pin as +# docker/devenv/Dockerfile); the generated ./setup expects it on PATH. +# Corepack is gone from Node 25+, so nothing here may use it. +ARG PNPM_VERSION=12.5.1 +RUN set -eux; \ + ARCH="$(dpkg --print-architecture)"; \ + case "${ARCH}" in \ + aarch64|arm64) \ + PNPM_ARCH='arm64'; \ + PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \ + ;; \ + amd64|x86_64) \ + PNPM_ARCH='x64'; \ + PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \ + ;; \ + *) \ + echo "Unsupported arch: ${ARCH}"; \ + exit 1; \ + ;; \ + esac; \ + curl -LfsSo /tmp/pnpm.tar.gz "https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/pnpm-linux-${PNPM_ARCH}.tar.gz"; \ + echo "${PNPM_SHA256} */tmp/pnpm.tar.gz" | sha256sum -c -; \ + tar -xzf /tmp/pnpm.tar.gz -C /usr/local/bin pnpm; \ + chmod 755 /usr/local/bin/pnpm; \ + rm -f /tmp/pnpm.tar.gz; \ + pnpm --version; + +# `./setup` only needs pnpm on PATH (installed above as root). +# Ownership is fixed right after. RUN ./setup && chown -R penpot:penpot /opt/penpot/exporter USER penpot:penpot diff --git a/docker/images/Dockerfile.mcp b/docker/images/Dockerfile.mcp index bad957b07c..043fa5493c 100644 --- a/docker/images/Dockerfile.mcp +++ b/docker/images/Dockerfile.mcp @@ -8,6 +8,36 @@ COPY $BUNDLE_PATH /opt/penpot/mcp/ WORKDIR /opt/penpot/mcp +# pnpm ships as a system binary (same tarball + SHA pin as +# docker/devenv/Dockerfile); ./setup expects it on PATH. +# Corepack is gone from Node 25+, so nothing here may use it. +ARG PNPM_VERSION=12.5.1 +RUN set -eux; \ + apt-get -qq update; \ + apt-get -qqy --no-install-recommends install curl ca-certificates; \ + rm -rf /var/lib/apt/lists/*; \ + ARCH="$(dpkg --print-architecture)"; \ + case "${ARCH}" in \ + aarch64|arm64) \ + PNPM_ARCH='arm64'; \ + PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \ + ;; \ + amd64|x86_64) \ + PNPM_ARCH='x64'; \ + PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \ + ;; \ + *) \ + echo "Unsupported arch: ${ARCH}"; \ + exit 1; \ + ;; \ + esac; \ + curl -LfsSo /tmp/pnpm.tar.gz "https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/pnpm-linux-${PNPM_ARCH}.tar.gz"; \ + echo "${PNPM_SHA256} */tmp/pnpm.tar.gz" | sha256sum -c -; \ + tar -xzf /tmp/pnpm.tar.gz -C /usr/local/bin pnpm; \ + chmod 755 /usr/local/bin/pnpm; \ + rm -f /tmp/pnpm.tar.gz; \ + pnpm --version; + RUN ./setup diff --git a/docker/images/Dockerfile.media-processor b/docker/images/Dockerfile.media-processor index 9e30b0f3d4..a147b9d8b3 100644 --- a/docker/images/Dockerfile.media-processor +++ b/docker/images/Dockerfile.media-processor @@ -4,6 +4,7 @@ LABEL maintainer="Penpot " ENV LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 \ NODE_VERSION=v24.20.0 \ + PNPM_VERSION=12.5.1 \ DEBIAN_FRONTEND=noninteractive \ PATH=/opt/node/bin:$PATH @@ -56,9 +57,13 @@ RUN set -eux; \ case "${ARCH}" in \ aarch64|arm64) \ BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \ + PNPM_ARCH='arm64'; \ + PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \ ;; \ amd64|x86_64) \ BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \ + PNPM_ARCH='x64'; \ + PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \ ;; \ *) \ echo "Unsupported arch: ${ARCH}"; \ @@ -71,7 +76,13 @@ RUN set -eux; \ tar -xf /tmp/nodejs.tar.gz --strip-components=1; \ chown -R root /opt/node; \ rm -rf /tmp/nodejs.tar.gz; \ - corepack enable; \ + PNPM_URL="https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/pnpm-linux-${PNPM_ARCH}.tar.gz"; \ + curl -LfsSo /tmp/pnpm.tar.gz "${PNPM_URL}"; \ + echo "${PNPM_SHA256} */tmp/pnpm.tar.gz" | sha256sum -c -; \ + tar -xzf /tmp/pnpm.tar.gz -C /opt/node/bin pnpm; \ + chmod 755 /opt/node/bin/pnpm; \ + rm -f /tmp/pnpm.tar.gz; \ + pnpm --version; \ mkdir -p /opt/penpot; \ chown -R penpot:penpot /opt/penpot; diff --git a/docs/README.md b/docs/README.md index 1acb98bb6a..caf7b226d7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,6 @@ To view this site locally, first set up the environment: # only if necessary nvm install nvm use -corepack enable pnpm install ``` diff --git a/docs/package.json b/docs/package.json index 208cd18089..8a9c016f80 100644 --- a/docs/package.json +++ b/docs/package.json @@ -39,5 +39,5 @@ "markdown-it-anchor": "^9.2.1", "markdown-it-plantuml": "^1.4.1" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 3527b910e8..0ca49e8270 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -7,96 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.3.4 - version: 12.3.4 + specifier: 12.5.1 + version: 12.5.1 packages: - '@pnpm/exe.darwin-arm64@12.3.4': - resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + '@pnpm/exe.android-arm64@12.5.1': + resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.5.1': + resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.5.1': + resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.3.4': - resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + '@pnpm/exe.darwin-x64@12.5.1': + resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.3.4': - resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + '@pnpm/exe.freebsd-x64@12.5.1': + resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.5.1': + resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.3.4': - resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + '@pnpm/exe.linux-arm64@12.5.1': + resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.3.4': - resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + '@pnpm/exe.linux-ppc64@12.5.1': + resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.5.1': + resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.5.1': + resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.5.1': + resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.3.4': - resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + '@pnpm/exe.linux-x64@12.5.1': + resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.3.4': - resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + '@pnpm/exe.win32-arm64@12.5.1': + resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.3.4': - resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + '@pnpm/exe.win32-x64@12.5.1': + resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} cpu: [x64] os: [win32] - pnpm@12.3.4: - resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + pnpm@12.5.1: + resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.3.4': + '@pnpm/exe.android-arm64@12.5.1': optional: true - '@pnpm/exe.darwin-x64@12.3.4': + '@pnpm/exe.android-x64@12.5.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.3.4': + '@pnpm/exe.darwin-arm64@12.5.1': optional: true - '@pnpm/exe.linux-arm64@12.3.4': + '@pnpm/exe.darwin-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64-musl@12.3.4': + '@pnpm/exe.freebsd-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64@12.3.4': + '@pnpm/exe.linux-arm64-musl@12.5.1': optional: true - '@pnpm/exe.win32-arm64@12.3.4': + '@pnpm/exe.linux-arm64@12.5.1': optional: true - '@pnpm/exe.win32-x64@12.3.4': + '@pnpm/exe.linux-ppc64@12.5.1': optional: true - pnpm@12.3.4: + '@pnpm/exe.linux-riscv64@12.5.1': + optional: true + + '@pnpm/exe.linux-s390x@12.5.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.5.1': + optional: true + + '@pnpm/exe.linux-x64@12.5.1': + optional: true + + '@pnpm/exe.win32-arm64@12.5.1': + optional: true + + '@pnpm/exe.win32-x64@12.5.1': + optional: true + + pnpm@12.5.1: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 + '@pnpm/exe.android-arm64': 12.5.1 + '@pnpm/exe.android-x64': 12.5.1 + '@pnpm/exe.darwin-arm64': 12.5.1 + '@pnpm/exe.darwin-x64': 12.5.1 + '@pnpm/exe.freebsd-x64': 12.5.1 + '@pnpm/exe.linux-arm64': 12.5.1 + '@pnpm/exe.linux-arm64-musl': 12.5.1 + '@pnpm/exe.linux-ppc64': 12.5.1 + '@pnpm/exe.linux-riscv64': 12.5.1 + '@pnpm/exe.linux-s390x': 12.5.1 + '@pnpm/exe.linux-x64': 12.5.1 + '@pnpm/exe.linux-x64-musl': 12.5.1 + '@pnpm/exe.win32-arm64': 12.5.1 + '@pnpm/exe.win32-x64': 12.5.1 --- lockfileVersion: '9.0' diff --git a/docs/scripts/build b/docs/scripts/build index 675605f180..5aa4de5bc7 100755 --- a/docs/scripts/build +++ b/docs/scripts/build @@ -2,9 +2,6 @@ set -ex -corepack enable; -corepack install; - rm -rf ./_dist pnpm install pnpm run build diff --git a/docs/technical-guide/developer/devenv.md b/docs/technical-guide/developer/devenv.md index 12a7e53ee8..055db27a36 100644 --- a/docs/technical-guide/developer/devenv.md +++ b/docs/technical-guide/developer/devenv.md @@ -62,7 +62,7 @@ See the dedicated section [Agentic Dev Environment](../agentic-devenv/) for deta ### Parallel workspaces The devenv runs as separate compose projects: - * shared infra (`penpotdev-infra`: Postgres, MinIO, mailer, LDAP) + * shared infra (`penpotdev-infra`: Postgres, RustFS, Valkey, mailer, LDAP) * `penpotdev-wsN` project per runtime instance. - `ws0` (a.k.a. `main`) is the current state of your repo; - `ws1` and up are clones that you maintain explicitly under `${PENPOT_WORKSPACES_DIR}/wsN/` @@ -168,7 +168,7 @@ the container's data volume regardless of this flag. ### Shared state and workers -All instances share one Penpot database and one MinIO bucket; users, teams, +All instances share one Penpot database and one RustFS bucket; users, teams, files, and MCP tokens are visible from every instance. Per-instance Valkey keeps msgbus Pub/Sub channels (collab broadcasts, team-org notifications, file-summary cache, rate-limit counters) isolated. @@ -176,7 +176,7 @@ file-summary cache, rate-limit counters) isolated. Background workers (`enable-backend-worker`) run only on ws0 — ws1+ overlays disable it. ws1+ RPC handlers still enqueue tasks into the shared Postgres `task` table; ws0's dispatcher claims them via `FOR UPDATE SKIP LOCKED` and -runs them against the shared DB and MinIO. Workers are fire-and-forget: +runs them against the shared DB and RustFS. Workers are fire-and-forget: `wrk/submit!` inserts a row and returns; RPC handlers never wait on completion. The "ws0 only" policy avoids multi-instance worker races (cron dedup is best-effort across instances, `wrk/submit!` `dedupe` is racy across @@ -190,7 +190,8 @@ Shared infrastructure shuts down only when no instances remain running. The devenv compose configuration has been split into two files and reorganized into separate compose projects per runtime instance: -- `docker/devenv/docker-compose.infra.yml` (Postgres, MinIO, mailer, LDAP) +- `docker/devenv/docker-compose.infra.yml` (Postgres, RustFS, Valkey, mailer, + LDAP) runs under the compose project `penpotdev-infra`. - `docker/devenv/docker-compose.main.yml` (one main container + its Valkey) runs once per runtime instance under `penpotdev-ws0`, `penpotdev-ws1`, …. @@ -202,10 +203,17 @@ into separate compose projects per runtime instance: If you had the devenv running on the previous single-project (`penpotdev`) layout, leftover containers and the auto-generated `penpotdev_default` network must be removed before bringing the new ws0 instance up. The named -data volumes (`penpotdev_postgres_data_pg16`, `penpotdev_minio_data`, -`penpotdev_user_data`, `penpotdev_valkey_data`) are pinned by explicit -`name:` entries in the new compose files and are preserved through the -transition — your Postgres DB, MinIO objects, and home cache survive. +data volumes (`penpotdev_postgres_data_pg18`, `penpotdev_rustfs_data`, +`penpotdev_mailpit_data`, `penpotdev_user_data`, `penpotdev_valkey_data`) are +pinned by explicit `name:` entries in the new compose files. The legacy +`penpotdev_postgres_data_pg16` and `penpotdev_minio_data` volumes remain +untouched. PostgreSQL 16 data and MinIO objects are not migrated automatically. + +PostgreSQL 18 stores its versioned data directory under +`/var/lib/postgresql/18/docker`, so the devenv mounts its volume at +`/var/lib/postgresql`. To retain data from PostgreSQL 16, export and restore it +with `pg_dump` and `pg_restore`; do not mount the PostgreSQL 16 volume directly +in the PostgreSQL 18 container. One-time cleanup, then bring up ws0: @@ -388,15 +396,29 @@ An example of your cursor configuration can be: } ``` +## Object storage + +The devenv uses RustFS for S3-compatible object storage. Its API is available +at [http://localhost:9000](http://localhost:9000), and its management console +is available at [http://localhost:9001](http://localhost:9001). Log in to the +console with `penpot-devenv` as both the access key and secret key. + +Both ports bind only to the host loopback interface and are not exposed to the +local network. + ## Email -To test email sending, the devenv includes [MailCatcher](https://mailcatcher.me/), -a SMTP server that is used for develop. It does not send any mail outbounds. -Instead, it stores them in memory and allows to browse them via a web interface -similar to a webmail client. Simply navigate to: +To test email sending, the devenv includes +[Mailpit](https://mailpit.axllent.org/), an SMTP server for development. It does +not send mail externally. Instead, it stores messages in a persistent Docker +volume and provides a webmail-like interface. Simply navigate to: [http://localhost:1080](http://localhost:1080) +The inbox persists when the container is recreated. `drop-devenv` preserves +the `penpotdev_mailpit_data` volume, together with the other devenv data +volumes. + ## Create user You can register a new user manually, or create new users automatically with this script. From your tmux instance, run: diff --git a/docs/technical-guide/developer/frontend.md b/docs/technical-guide/developer/frontend.md index 85dd9d7056..6a28339a8a 100644 --- a/docs/technical-guide/developer/frontend.md +++ b/docs/technical-guide/developer/frontend.md @@ -356,7 +356,7 @@ npx playwright test --ui > ❗️ **IMPORTANT**: You might need to [install Playwright's browsers and dependencies](https://playwright.dev/docs/intro) in your host machine with: npx playwright install --with-deps. In case you are using a Linux distribution other than Ubuntu, [you might need to install the dependencies manually](https://github.com/microsoft/playwright/issues/11122). -> You will also need pnpm in your host nodejs. For this, do corepack enable and then just pnpm. +> You will also need pnpm in your host nodejs (the devenv already ships it; outside it, run npm install -g pnpm) and then just pnpm. ### How to write a test diff --git a/exporter/package.json b/exporter/package.json index 47429d2541..d04dafe2a5 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "repository": { "type": "git", "url": "https://github.com/penpot/penpot" @@ -30,7 +30,8 @@ }, "scripts": { "clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target", - "watch:app": "pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs watch main", + "build:wasm": "exit 0", + "watch:app": "pnpm run build:wasm && pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs watch main", "watch": "pnpm run watch:app", "build:app": "clojure -M:dev:shadow-cljs release main", "build": "pnpm run clear:shadow-cache && pnpm run build:app", diff --git a/exporter/pnpm-lock.yaml b/exporter/pnpm-lock.yaml index 660f44b821..e30ff1e950 100644 --- a/exporter/pnpm-lock.yaml +++ b/exporter/pnpm-lock.yaml @@ -7,96 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.3.4 - version: 12.3.4 + specifier: 12.5.1 + version: 12.5.1 packages: - '@pnpm/exe.darwin-arm64@12.3.4': - resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + '@pnpm/exe.android-arm64@12.5.1': + resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.5.1': + resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.5.1': + resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.3.4': - resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + '@pnpm/exe.darwin-x64@12.5.1': + resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.3.4': - resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + '@pnpm/exe.freebsd-x64@12.5.1': + resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.5.1': + resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.3.4': - resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + '@pnpm/exe.linux-arm64@12.5.1': + resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.3.4': - resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + '@pnpm/exe.linux-ppc64@12.5.1': + resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.5.1': + resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.5.1': + resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.5.1': + resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.3.4': - resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + '@pnpm/exe.linux-x64@12.5.1': + resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.3.4': - resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + '@pnpm/exe.win32-arm64@12.5.1': + resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.3.4': - resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + '@pnpm/exe.win32-x64@12.5.1': + resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} cpu: [x64] os: [win32] - pnpm@12.3.4: - resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + pnpm@12.5.1: + resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.3.4': + '@pnpm/exe.android-arm64@12.5.1': optional: true - '@pnpm/exe.darwin-x64@12.3.4': + '@pnpm/exe.android-x64@12.5.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.3.4': + '@pnpm/exe.darwin-arm64@12.5.1': optional: true - '@pnpm/exe.linux-arm64@12.3.4': + '@pnpm/exe.darwin-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64-musl@12.3.4': + '@pnpm/exe.freebsd-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64@12.3.4': + '@pnpm/exe.linux-arm64-musl@12.5.1': optional: true - '@pnpm/exe.win32-arm64@12.3.4': + '@pnpm/exe.linux-arm64@12.5.1': optional: true - '@pnpm/exe.win32-x64@12.3.4': + '@pnpm/exe.linux-ppc64@12.5.1': optional: true - pnpm@12.3.4: + '@pnpm/exe.linux-riscv64@12.5.1': + optional: true + + '@pnpm/exe.linux-s390x@12.5.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.5.1': + optional: true + + '@pnpm/exe.linux-x64@12.5.1': + optional: true + + '@pnpm/exe.win32-arm64@12.5.1': + optional: true + + '@pnpm/exe.win32-x64@12.5.1': + optional: true + + pnpm@12.5.1: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 + '@pnpm/exe.android-arm64': 12.5.1 + '@pnpm/exe.android-x64': 12.5.1 + '@pnpm/exe.darwin-arm64': 12.5.1 + '@pnpm/exe.darwin-x64': 12.5.1 + '@pnpm/exe.freebsd-x64': 12.5.1 + '@pnpm/exe.linux-arm64': 12.5.1 + '@pnpm/exe.linux-arm64-musl': 12.5.1 + '@pnpm/exe.linux-ppc64': 12.5.1 + '@pnpm/exe.linux-riscv64': 12.5.1 + '@pnpm/exe.linux-s390x': 12.5.1 + '@pnpm/exe.linux-x64': 12.5.1 + '@pnpm/exe.linux-x64-musl': 12.5.1 + '@pnpm/exe.win32-arm64': 12.5.1 + '@pnpm/exe.win32-x64': 12.5.1 --- lockfileVersion: '9.0' diff --git a/exporter/scripts/build b/exporter/scripts/build index 40eba8f44c..487b697654 100755 --- a/exporter/scripts/build +++ b/exporter/scripts/build @@ -5,8 +5,6 @@ set -ex export CURRENT_VERSION=$1; export NODE_ENV=production; -corepack enable; -corepack install || exit 1; pnpm install || exit 1; rm -rf target @@ -21,8 +19,7 @@ touch target/pnpm-workspace.yaml; cat </dev/null || { echo "error: pnpm not found in PATH" >&2; exit 1; }; pnpm install pnpm exec playwright install chromium; EOF diff --git a/exporter/scripts/setup b/exporter/scripts/setup index a99157468d..3607ca3d67 100755 --- a/exporter/scripts/setup +++ b/exporter/scripts/setup @@ -2,7 +2,5 @@ set -e; -corepack enable; -corepack install; pnpm install; pnpm exec playwright install chromium diff --git a/exporter/scripts/test b/exporter/scripts/test index 6402c5afd1..169cdc8a9e 100755 --- a/exporter/scripts/test +++ b/exporter/scripts/test @@ -1,7 +1,5 @@ #!/usr/bin/env bash set -ex -corepack enable; -corepack install; pnpm install; pnpm run test; diff --git a/frontend/package.json b/frontend/package.json index 705f080055..894e6e651d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "browserslist": [ "defaults" ], diff --git a/frontend/packages/draft-js/package.json b/frontend/packages/draft-js/package.json index e9f6c0ffd7..d257b4bec2 100644 --- a/frontend/packages/draft-js/package.json +++ b/frontend/packages/draft-js/package.json @@ -4,7 +4,7 @@ "description": "Penpot Draft-JS Wrapper", "main": "index.js", "type": "module", - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "author": "Andrey Antukh", "license": "MPL-2.0", "dependencies": { diff --git a/frontend/packages/mousetrap/package.json b/frontend/packages/mousetrap/package.json index 509d95b0ed..e71ccbf825 100644 --- a/frontend/packages/mousetrap/package.json +++ b/frontend/packages/mousetrap/package.json @@ -4,7 +4,7 @@ "description": "Simple library for handling keyboard shortcuts", "main": "index.js", "type": "module", - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "author": "Craig Campbell", "license": "Apache-2.0 WITH LLVM-exception" } diff --git a/frontend/packages/tokenscript/package.json b/frontend/packages/tokenscript/package.json index cc8330b50e..32d7442bf5 100644 --- a/frontend/packages/tokenscript/package.json +++ b/frontend/packages/tokenscript/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "type": "module", - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "author": "Andrey Antukh", "license": "MPL-2.0", "dependencies": { diff --git a/frontend/packages/ui/package.json b/frontend/packages/ui/package.json index bd2351e197..38657cbad1 100644 --- a/frontend/packages/ui/package.json +++ b/frontend/packages/ui/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "types": "./dist/index.d.ts", "type": "module", - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "exports": { ".": { "import": "./dist/index.js" diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 64aa97b44e..eb187d1c4a 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -7,96 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.3.4 - version: 12.3.4 + specifier: 12.5.1 + version: 12.5.1 packages: - '@pnpm/exe.darwin-arm64@12.3.4': - resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + '@pnpm/exe.android-arm64@12.5.1': + resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.5.1': + resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.5.1': + resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.3.4': - resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + '@pnpm/exe.darwin-x64@12.5.1': + resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.3.4': - resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + '@pnpm/exe.freebsd-x64@12.5.1': + resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.5.1': + resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.3.4': - resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + '@pnpm/exe.linux-arm64@12.5.1': + resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.3.4': - resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + '@pnpm/exe.linux-ppc64@12.5.1': + resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.5.1': + resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.5.1': + resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.5.1': + resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.3.4': - resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + '@pnpm/exe.linux-x64@12.5.1': + resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.3.4': - resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + '@pnpm/exe.win32-arm64@12.5.1': + resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.3.4': - resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + '@pnpm/exe.win32-x64@12.5.1': + resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} cpu: [x64] os: [win32] - pnpm@12.3.4: - resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + pnpm@12.5.1: + resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.3.4': + '@pnpm/exe.android-arm64@12.5.1': optional: true - '@pnpm/exe.darwin-x64@12.3.4': + '@pnpm/exe.android-x64@12.5.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.3.4': + '@pnpm/exe.darwin-arm64@12.5.1': optional: true - '@pnpm/exe.linux-arm64@12.3.4': + '@pnpm/exe.darwin-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64-musl@12.3.4': + '@pnpm/exe.freebsd-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64@12.3.4': + '@pnpm/exe.linux-arm64-musl@12.5.1': optional: true - '@pnpm/exe.win32-arm64@12.3.4': + '@pnpm/exe.linux-arm64@12.5.1': optional: true - '@pnpm/exe.win32-x64@12.3.4': + '@pnpm/exe.linux-ppc64@12.5.1': optional: true - pnpm@12.3.4: + '@pnpm/exe.linux-riscv64@12.5.1': + optional: true + + '@pnpm/exe.linux-s390x@12.5.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.5.1': + optional: true + + '@pnpm/exe.linux-x64@12.5.1': + optional: true + + '@pnpm/exe.win32-arm64@12.5.1': + optional: true + + '@pnpm/exe.win32-x64@12.5.1': + optional: true + + pnpm@12.5.1: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 + '@pnpm/exe.android-arm64': 12.5.1 + '@pnpm/exe.android-x64': 12.5.1 + '@pnpm/exe.darwin-arm64': 12.5.1 + '@pnpm/exe.darwin-x64': 12.5.1 + '@pnpm/exe.freebsd-x64': 12.5.1 + '@pnpm/exe.linux-arm64': 12.5.1 + '@pnpm/exe.linux-arm64-musl': 12.5.1 + '@pnpm/exe.linux-ppc64': 12.5.1 + '@pnpm/exe.linux-riscv64': 12.5.1 + '@pnpm/exe.linux-s390x': 12.5.1 + '@pnpm/exe.linux-x64': 12.5.1 + '@pnpm/exe.linux-x64-musl': 12.5.1 + '@pnpm/exe.win32-arm64': 12.5.1 + '@pnpm/exe.win32-x64': 12.5.1 --- lockfileVersion: '9.0' diff --git a/frontend/scripts/build b/frontend/scripts/build index 4d4cc79d10..f19a643fe5 100755 --- a/frontend/scripts/build +++ b/frontend/scripts/build @@ -18,8 +18,6 @@ export NODE_ENV=production; rm -rf node_modules; -corepack enable; -corepack install; pnpm install; rm -rf target/dist; @@ -43,7 +41,6 @@ popd; pushd ../plugins rm -rf node_modules; rm -rf dist/apps/; -corepack install; pnpm -r install; pnpm run build:plugins; popd diff --git a/frontend/scripts/build-storybook b/frontend/scripts/build-storybook index f94b434df4..d40a73aa14 100755 --- a/frontend/scripts/build-storybook +++ b/frontend/scripts/build-storybook @@ -12,8 +12,6 @@ export VERSION_TAG="${VERSION}-${BUILD_TS}"; export NODE_ENV=production; -corepack enable; -corepack install; pnpm install; pnpm run build:storybook; diff --git a/frontend/scripts/setup b/frontend/scripts/setup index bf54c0440c..73bf2038ae 100755 --- a/frontend/scripts/setup +++ b/frontend/scripts/setup @@ -1,6 +1,4 @@ #!/usr/bin/env bash -corepack enable; -corepack install; pnpm install; pnpm exec playwright install chromium; diff --git a/frontend/scripts/test b/frontend/scripts/test index 0da76e90c2..6d822b9542 100755 --- a/frontend/scripts/test +++ b/frontend/scripts/test @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -ex -corepack enable; -corepack install; pnpm install; # Build render wasm binary diff --git a/frontend/scripts/watch b/frontend/scripts/watch index 80d579b73f..9fc909dfa9 100755 --- a/frontend/scripts/watch +++ b/frontend/scripts/watch @@ -4,7 +4,5 @@ TARGET=${1:-app}; set -ex -corepack enable; -corepack install; pnpm install; pnpm run watch:$TARGET diff --git a/frontend/text-editor/package.json b/frontend/text-editor/package.json index 42c442f5f0..94d61aaaaa 100644 --- a/frontend/text-editor/package.json +++ b/frontend/text-editor/package.json @@ -28,5 +28,5 @@ "vite": "^8.2.0", "vitest": "^4.1.10" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/library/package.json b/library/package.json index aff0eb2082..dec0f1bdc6 100644 --- a/library/package.json +++ b/library/package.json @@ -3,7 +3,7 @@ "version": "1.2.0-RC1", "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "type": "module", "repository": { "type": "git", diff --git a/library/pnpm-lock.yaml b/library/pnpm-lock.yaml index 9f4a4d81a3..8b8daa71b9 100644 --- a/library/pnpm-lock.yaml +++ b/library/pnpm-lock.yaml @@ -7,96 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.3.4 - version: 12.3.4 + specifier: 12.5.1 + version: 12.5.1 packages: - '@pnpm/exe.darwin-arm64@12.3.4': - resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + '@pnpm/exe.android-arm64@12.5.1': + resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.5.1': + resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.5.1': + resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.3.4': - resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + '@pnpm/exe.darwin-x64@12.5.1': + resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.3.4': - resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + '@pnpm/exe.freebsd-x64@12.5.1': + resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.5.1': + resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.3.4': - resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + '@pnpm/exe.linux-arm64@12.5.1': + resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.3.4': - resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + '@pnpm/exe.linux-ppc64@12.5.1': + resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.5.1': + resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.5.1': + resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.5.1': + resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.3.4': - resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + '@pnpm/exe.linux-x64@12.5.1': + resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.3.4': - resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + '@pnpm/exe.win32-arm64@12.5.1': + resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.3.4': - resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + '@pnpm/exe.win32-x64@12.5.1': + resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} cpu: [x64] os: [win32] - pnpm@12.3.4: - resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + pnpm@12.5.1: + resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.3.4': + '@pnpm/exe.android-arm64@12.5.1': optional: true - '@pnpm/exe.darwin-x64@12.3.4': + '@pnpm/exe.android-x64@12.5.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.3.4': + '@pnpm/exe.darwin-arm64@12.5.1': optional: true - '@pnpm/exe.linux-arm64@12.3.4': + '@pnpm/exe.darwin-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64-musl@12.3.4': + '@pnpm/exe.freebsd-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64@12.3.4': + '@pnpm/exe.linux-arm64-musl@12.5.1': optional: true - '@pnpm/exe.win32-arm64@12.3.4': + '@pnpm/exe.linux-arm64@12.5.1': optional: true - '@pnpm/exe.win32-x64@12.3.4': + '@pnpm/exe.linux-ppc64@12.5.1': optional: true - pnpm@12.3.4: + '@pnpm/exe.linux-riscv64@12.5.1': + optional: true + + '@pnpm/exe.linux-s390x@12.5.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.5.1': + optional: true + + '@pnpm/exe.linux-x64@12.5.1': + optional: true + + '@pnpm/exe.win32-arm64@12.5.1': + optional: true + + '@pnpm/exe.win32-x64@12.5.1': + optional: true + + pnpm@12.5.1: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 + '@pnpm/exe.android-arm64': 12.5.1 + '@pnpm/exe.android-x64': 12.5.1 + '@pnpm/exe.darwin-arm64': 12.5.1 + '@pnpm/exe.darwin-x64': 12.5.1 + '@pnpm/exe.freebsd-x64': 12.5.1 + '@pnpm/exe.linux-arm64': 12.5.1 + '@pnpm/exe.linux-arm64-musl': 12.5.1 + '@pnpm/exe.linux-ppc64': 12.5.1 + '@pnpm/exe.linux-riscv64': 12.5.1 + '@pnpm/exe.linux-s390x': 12.5.1 + '@pnpm/exe.linux-x64': 12.5.1 + '@pnpm/exe.linux-x64-musl': 12.5.1 + '@pnpm/exe.win32-arm64': 12.5.1 + '@pnpm/exe.win32-x64': 12.5.1 --- lockfileVersion: '9.0' diff --git a/library/scripts/test b/library/scripts/test index d724fbc9c3..11f90bfaaf 100755 --- a/library/scripts/test +++ b/library/scripts/test @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -ex -corepack enable; -corepack install; pnpm install; pnpm run build:bundle; diff --git a/manage.sh b/manage.sh index c3fb41d07c..d1b0db408b 100755 --- a/manage.sh +++ b/manage.sh @@ -209,9 +209,7 @@ function ensure-devenv-network { # Compose-project plumbing for the parallel-workspaces layout. # -# - Shared infrastructure (postgres, minio, mailer, ldap, minio-setup) runs -# under project `penpotdev-infra`. -# - Shared infrastructure (postgres, minio, mailer, ldap, valkey, minio-setup) +# - Shared infrastructure (postgres, RustFS, mailer, LDAP, Valkey) # runs under project `penpotdev-infra`. # - Each runtime instance (ws0, ws1, ...) runs only its own main container # under project `penpotdev-wsN`. All workspaces uniformly overlay their @@ -296,16 +294,11 @@ function devenv-main-running { [[ -n "$container" ]] && [[ "$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)" = "true" ]] } -# Bring shared infra up and block until minio-setup has provisioned the -# shared MinIO user/policy. Idempotent: a second call when everything is -# already up returns immediately. +# Bring shared infra up and block until services with healthchecks are healthy. +# Removing orphaned containers retires old infra services without deleting +# their named volumes. function ensure-infra-up { - infra-compose up -d - local setup_container - setup_container=$(infra-compose ps -aq minio-setup 2>/dev/null) - if [[ -n "$setup_container" ]]; then - docker wait "$setup_container" >/dev/null 2>&1 || true - fi + infra-compose up -d --wait --wait-timeout 60 --remove-orphans } # Refuse to sync workspaces if the live repo is in a fragile Git state. diff --git a/mcp/README.md b/mcp/README.md index 6842adce7e..702eec3d60 100644 --- a/mcp/README.md +++ b/mcp/README.md @@ -68,7 +68,7 @@ Once the servers are running, continue with step 2. #### Running the Source Version from the Repository -The tools `corepack` and `npx` should be available in your terminal. +The tools `pnpm` and `npx` should be available in your terminal. On Windows, use the Git Bash terminal to ensure compatibility with the provided scripts. diff --git a/mcp/package.json b/mcp/package.json index abd1b8c8be..58eedd05f6 100644 --- a/mcp/package.json +++ b/mcp/package.json @@ -23,7 +23,7 @@ "type": "git", "url": "https://github.com/penpot/penpot.git" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "devDependencies": { "concurrently": "^10.0.5", "prettier": "^3.9.6" diff --git a/mcp/packages/common/package.json b/mcp/packages/common/package.json index fae32707ef..4e3dda0cc8 100644 --- a/mcp/packages/common/package.json +++ b/mcp/packages/common/package.json @@ -4,7 +4,7 @@ "description": "Shared type definitions and interfaces for Penpot MCP", "main": "dist/index.js", "types": "dist/index.d.ts", - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "scripts": { "build": "tsc --build --clean && tsc --build", "watch": "tsc --watch", diff --git a/mcp/packages/plugin/package.json b/mcp/packages/plugin/package.json index 534fef6226..33b37f2442 100644 --- a/mcp/packages/plugin/package.json +++ b/mcp/packages/plugin/package.json @@ -3,7 +3,7 @@ "private": true, "version": "1.0.0", "type": "module", - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "scripts": { "start": "vite build --watch --config vite.config.ts", "start:multi-user": "pnpm run start", diff --git a/mcp/packages/server/package.json b/mcp/packages/server/package.json index 4711b793a5..c30679f6f6 100644 --- a/mcp/packages/server/package.json +++ b/mcp/packages/server/package.json @@ -24,7 +24,7 @@ ], "author": "", "license": "MIT", - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0", "class-transformer": "^0.5.1", diff --git a/mcp/pnpm-lock.yaml b/mcp/pnpm-lock.yaml index 6b15ed28ab..13321c823a 100644 --- a/mcp/pnpm-lock.yaml +++ b/mcp/pnpm-lock.yaml @@ -7,96 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.3.4 - version: 12.3.4 + specifier: 12.5.1 + version: 12.5.1 packages: - '@pnpm/exe.darwin-arm64@12.3.4': - resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + '@pnpm/exe.android-arm64@12.5.1': + resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.5.1': + resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.5.1': + resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.3.4': - resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + '@pnpm/exe.darwin-x64@12.5.1': + resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.3.4': - resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + '@pnpm/exe.freebsd-x64@12.5.1': + resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.5.1': + resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.3.4': - resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + '@pnpm/exe.linux-arm64@12.5.1': + resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.3.4': - resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + '@pnpm/exe.linux-ppc64@12.5.1': + resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.5.1': + resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.5.1': + resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.5.1': + resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.3.4': - resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + '@pnpm/exe.linux-x64@12.5.1': + resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.3.4': - resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + '@pnpm/exe.win32-arm64@12.5.1': + resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.3.4': - resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + '@pnpm/exe.win32-x64@12.5.1': + resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} cpu: [x64] os: [win32] - pnpm@12.3.4: - resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + pnpm@12.5.1: + resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.3.4': + '@pnpm/exe.android-arm64@12.5.1': optional: true - '@pnpm/exe.darwin-x64@12.3.4': + '@pnpm/exe.android-x64@12.5.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.3.4': + '@pnpm/exe.darwin-arm64@12.5.1': optional: true - '@pnpm/exe.linux-arm64@12.3.4': + '@pnpm/exe.darwin-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64-musl@12.3.4': + '@pnpm/exe.freebsd-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64@12.3.4': + '@pnpm/exe.linux-arm64-musl@12.5.1': optional: true - '@pnpm/exe.win32-arm64@12.3.4': + '@pnpm/exe.linux-arm64@12.5.1': optional: true - '@pnpm/exe.win32-x64@12.3.4': + '@pnpm/exe.linux-ppc64@12.5.1': optional: true - pnpm@12.3.4: + '@pnpm/exe.linux-riscv64@12.5.1': + optional: true + + '@pnpm/exe.linux-s390x@12.5.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.5.1': + optional: true + + '@pnpm/exe.linux-x64@12.5.1': + optional: true + + '@pnpm/exe.win32-arm64@12.5.1': + optional: true + + '@pnpm/exe.win32-x64@12.5.1': + optional: true + + pnpm@12.5.1: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 + '@pnpm/exe.android-arm64': 12.5.1 + '@pnpm/exe.android-x64': 12.5.1 + '@pnpm/exe.darwin-arm64': 12.5.1 + '@pnpm/exe.darwin-x64': 12.5.1 + '@pnpm/exe.freebsd-x64': 12.5.1 + '@pnpm/exe.linux-arm64': 12.5.1 + '@pnpm/exe.linux-arm64-musl': 12.5.1 + '@pnpm/exe.linux-ppc64': 12.5.1 + '@pnpm/exe.linux-riscv64': 12.5.1 + '@pnpm/exe.linux-s390x': 12.5.1 + '@pnpm/exe.linux-x64': 12.5.1 + '@pnpm/exe.linux-x64-musl': 12.5.1 + '@pnpm/exe.win32-arm64': 12.5.1 + '@pnpm/exe.win32-x64': 12.5.1 --- lockfileVersion: '9.0' diff --git a/mcp/scripts/build b/mcp/scripts/build index 09523164fe..c7e2b1f922 100755 --- a/mcp/scripts/build +++ b/mcp/scripts/build @@ -9,9 +9,6 @@ echo "Preparing bundle for types from $URL" set -ex -corepack enable; -corepack install; - # Ensure clean working directory rm -rf dist; rm -rf node_modules; @@ -37,8 +34,7 @@ touch ./dist/pnpm-workspace.yaml; cat </dev/null || { echo "error: pnpm not found in PATH" >&2; exit 1; }; pnpm install -P EOF diff --git a/mcp/scripts/setup b/mcp/scripts/setup index 83a3074583..ccb52413c0 100755 --- a/mcp/scripts/setup +++ b/mcp/scripts/setup @@ -2,6 +2,4 @@ set -e; -corepack enable; -corepack install; pnpm -r install; diff --git a/media-processor/package.json b/media-processor/package.json index ea012c5246..8190c530bf 100644 --- a/media-processor/package.json +++ b/media-processor/package.json @@ -14,7 +14,7 @@ "fmt:check": "prettier --check src/ test/", "clean": "rm -rf dist/" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "dependencies": { "express": "^5.2.1", "multer": "^2.2.0", diff --git a/media-processor/pnpm-lock.yaml b/media-processor/pnpm-lock.yaml index a35ada5f76..98803e8f77 100644 --- a/media-processor/pnpm-lock.yaml +++ b/media-processor/pnpm-lock.yaml @@ -7,96 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.3.4 - version: 12.3.4 + specifier: 12.5.1 + version: 12.5.1 packages: - '@pnpm/exe.darwin-arm64@12.3.4': - resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + '@pnpm/exe.android-arm64@12.5.1': + resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.5.1': + resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.5.1': + resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.3.4': - resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + '@pnpm/exe.darwin-x64@12.5.1': + resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.3.4': - resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + '@pnpm/exe.freebsd-x64@12.5.1': + resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.5.1': + resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.3.4': - resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + '@pnpm/exe.linux-arm64@12.5.1': + resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.3.4': - resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + '@pnpm/exe.linux-ppc64@12.5.1': + resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.5.1': + resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.5.1': + resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.5.1': + resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.3.4': - resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + '@pnpm/exe.linux-x64@12.5.1': + resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.3.4': - resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + '@pnpm/exe.win32-arm64@12.5.1': + resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.3.4': - resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + '@pnpm/exe.win32-x64@12.5.1': + resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} cpu: [x64] os: [win32] - pnpm@12.3.4: - resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + pnpm@12.5.1: + resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.3.4': + '@pnpm/exe.android-arm64@12.5.1': optional: true - '@pnpm/exe.darwin-x64@12.3.4': + '@pnpm/exe.android-x64@12.5.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.3.4': + '@pnpm/exe.darwin-arm64@12.5.1': optional: true - '@pnpm/exe.linux-arm64@12.3.4': + '@pnpm/exe.darwin-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64-musl@12.3.4': + '@pnpm/exe.freebsd-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64@12.3.4': + '@pnpm/exe.linux-arm64-musl@12.5.1': optional: true - '@pnpm/exe.win32-arm64@12.3.4': + '@pnpm/exe.linux-arm64@12.5.1': optional: true - '@pnpm/exe.win32-x64@12.3.4': + '@pnpm/exe.linux-ppc64@12.5.1': optional: true - pnpm@12.3.4: + '@pnpm/exe.linux-riscv64@12.5.1': + optional: true + + '@pnpm/exe.linux-s390x@12.5.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.5.1': + optional: true + + '@pnpm/exe.linux-x64@12.5.1': + optional: true + + '@pnpm/exe.win32-arm64@12.5.1': + optional: true + + '@pnpm/exe.win32-x64@12.5.1': + optional: true + + pnpm@12.5.1: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 + '@pnpm/exe.android-arm64': 12.5.1 + '@pnpm/exe.android-x64': 12.5.1 + '@pnpm/exe.darwin-arm64': 12.5.1 + '@pnpm/exe.darwin-x64': 12.5.1 + '@pnpm/exe.freebsd-x64': 12.5.1 + '@pnpm/exe.linux-arm64': 12.5.1 + '@pnpm/exe.linux-arm64-musl': 12.5.1 + '@pnpm/exe.linux-ppc64': 12.5.1 + '@pnpm/exe.linux-riscv64': 12.5.1 + '@pnpm/exe.linux-s390x': 12.5.1 + '@pnpm/exe.linux-x64': 12.5.1 + '@pnpm/exe.linux-x64-musl': 12.5.1 + '@pnpm/exe.win32-arm64': 12.5.1 + '@pnpm/exe.win32-x64': 12.5.1 --- lockfileVersion: '9.0' diff --git a/media-processor/scripts/setup b/media-processor/scripts/setup index c7be37d33d..43cf18877f 100755 --- a/media-processor/scripts/setup +++ b/media-processor/scripts/setup @@ -1,6 +1,4 @@ #!/bin/bash set -e cd "$(dirname "$0")/.." -corepack enable -corepack install pnpm install diff --git a/package.json b/package.json index 965f175b3d..150214c26f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "repository": { "type": "git", "url": "https://github.com/penpot/penpot" diff --git a/plugins/apps/colors-to-tokens-plugin/package.json b/plugins/apps/colors-to-tokens-plugin/package.json index 2bec61bf8b..30715f65e5 100644 --- a/plugins/apps/colors-to-tokens-plugin/package.json +++ b/plugins/apps/colors-to-tokens-plugin/package.json @@ -13,5 +13,5 @@ "lint": "eslint .", "test": "vitest" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/composable-test-suite/package.json b/plugins/apps/composable-test-suite/package.json index 949296d0b4..f2a776ec8f 100644 --- a/plugins/apps/composable-test-suite/package.json +++ b/plugins/apps/composable-test-suite/package.json @@ -26,5 +26,5 @@ "vite": "^8.2.2", "vite-live-preview": "^0.4.0" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/contrast-plugin/package.json b/plugins/apps/contrast-plugin/package.json index 6036de7b96..ee854f007f 100644 --- a/plugins/apps/contrast-plugin/package.json +++ b/plugins/apps/contrast-plugin/package.json @@ -13,5 +13,5 @@ "lint": "eslint .", "test": "vitest" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/create-palette-plugin/package.json b/plugins/apps/create-palette-plugin/package.json index ee5d641bf9..c99b93bf8c 100644 --- a/plugins/apps/create-palette-plugin/package.json +++ b/plugins/apps/create-palette-plugin/package.json @@ -12,5 +12,5 @@ "lint": "eslint .", "test": "vitest" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/e2e/package.json b/plugins/apps/e2e/package.json index 866566cfa8..c0501e2733 100644 --- a/plugins/apps/e2e/package.json +++ b/plugins/apps/e2e/package.json @@ -7,5 +7,5 @@ "test": "vitest", "lint": "eslint ." }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/example-styles/package.json b/plugins/apps/example-styles/package.json index 3e919d958b..714fe4228d 100644 --- a/plugins/apps/example-styles/package.json +++ b/plugins/apps/example-styles/package.json @@ -11,5 +11,5 @@ "serve": "vite preview", "lint": "eslint ." }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/icons-plugin/package.json b/plugins/apps/icons-plugin/package.json index 749eebfdff..4c6eee5406 100644 --- a/plugins/apps/icons-plugin/package.json +++ b/plugins/apps/icons-plugin/package.json @@ -13,5 +13,5 @@ "lint": "eslint .", "test": "vitest" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/lorem-ipsum-plugin/package.json b/plugins/apps/lorem-ipsum-plugin/package.json index fffd54a5d3..6e1dcc3de4 100644 --- a/plugins/apps/lorem-ipsum-plugin/package.json +++ b/plugins/apps/lorem-ipsum-plugin/package.json @@ -13,5 +13,5 @@ "lint": "eslint .", "test": "vitest" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/plugin-api-test-suite/package.json b/plugins/apps/plugin-api-test-suite/package.json index 3d2fa7ec9b..92eeee54fe 100644 --- a/plugins/apps/plugin-api-test-suite/package.json +++ b/plugins/apps/plugin-api-test-suite/package.json @@ -19,5 +19,5 @@ "devDependencies": { "playwright": "^1.62.1" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/poc-state-plugin/package.json b/plugins/apps/poc-state-plugin/package.json index 7a79003310..7c593ede9f 100644 --- a/plugins/apps/poc-state-plugin/package.json +++ b/plugins/apps/poc-state-plugin/package.json @@ -13,5 +13,5 @@ "lint": "eslint .", "test": "vitest" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/poc-tokens-plugin/package.json b/plugins/apps/poc-tokens-plugin/package.json index 852e7f1764..06fcd3e9cc 100644 --- a/plugins/apps/poc-tokens-plugin/package.json +++ b/plugins/apps/poc-tokens-plugin/package.json @@ -13,5 +13,5 @@ "lint": "eslint .", "test": "exit 0" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/rename-layers-plugin/package.json b/plugins/apps/rename-layers-plugin/package.json index 87279ccc4b..ca69e49f14 100644 --- a/plugins/apps/rename-layers-plugin/package.json +++ b/plugins/apps/rename-layers-plugin/package.json @@ -13,5 +13,5 @@ "lint": "eslint .", "test": "vitest" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/apps/table-plugin/package.json b/plugins/apps/table-plugin/package.json index 20bfb2245f..be103b05ff 100644 --- a/plugins/apps/table-plugin/package.json +++ b/plugins/apps/table-plugin/package.json @@ -13,5 +13,5 @@ "lint": "eslint .", "test": "vitest" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/libs/plugin-types/package.json b/plugins/libs/plugin-types/package.json index c63c7069ed..0f0da1e1e3 100644 --- a/plugins/libs/plugin-types/package.json +++ b/plugins/libs/plugin-types/package.json @@ -7,5 +7,5 @@ "build": "node ../../tools/scripts/build-types.mjs", "lint": "tsc -p . --noEmit" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/libs/plugins-runtime/package.json b/plugins/libs/plugins-runtime/package.json index 4dc44175bb..cf15cc95aa 100644 --- a/plugins/libs/plugins-runtime/package.json +++ b/plugins/libs/plugins-runtime/package.json @@ -16,5 +16,5 @@ "lint": "eslint .", "test": "vitest" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/libs/plugins-styles/package.json b/plugins/libs/plugins-styles/package.json index 62f2b3c21b..301ac323ef 100644 --- a/plugins/libs/plugins-styles/package.json +++ b/plugins/libs/plugins-styles/package.json @@ -6,5 +6,5 @@ "build": "node ../../tools/scripts/build-css.mjs", "lint": "echo 0" }, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f" } diff --git a/plugins/package.json b/plugins/package.json index 25fa71a7fb..912a0a7f70 100644 --- a/plugins/package.json +++ b/plugins/package.json @@ -3,7 +3,7 @@ "version": "0.6.0", "type": "module", "license": "MIT", - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "scripts": { "start": "pnpm run start:app:runtime", "start:app:runtime": "concurrently --kill-others --names build,server \"pnpm --filter @penpot/plugins-runtime run build:watch\" \"pnpm --filter @penpot/plugins-runtime run preview\"", diff --git a/plugins/pnpm-lock.yaml b/plugins/pnpm-lock.yaml index 70b95a2f81..0f3df9cca1 100644 --- a/plugins/pnpm-lock.yaml +++ b/plugins/pnpm-lock.yaml @@ -7,96 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.3.4 - version: 12.3.4 + specifier: 12.5.1 + version: 12.5.1 packages: - '@pnpm/exe.darwin-arm64@12.3.4': - resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + '@pnpm/exe.android-arm64@12.5.1': + resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.5.1': + resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.5.1': + resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.3.4': - resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + '@pnpm/exe.darwin-x64@12.5.1': + resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.3.4': - resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + '@pnpm/exe.freebsd-x64@12.5.1': + resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.5.1': + resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.3.4': - resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + '@pnpm/exe.linux-arm64@12.5.1': + resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.3.4': - resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + '@pnpm/exe.linux-ppc64@12.5.1': + resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.5.1': + resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.5.1': + resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.5.1': + resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.3.4': - resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + '@pnpm/exe.linux-x64@12.5.1': + resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.3.4': - resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + '@pnpm/exe.win32-arm64@12.5.1': + resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.3.4': - resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + '@pnpm/exe.win32-x64@12.5.1': + resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} cpu: [x64] os: [win32] - pnpm@12.3.4: - resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + pnpm@12.5.1: + resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.3.4': + '@pnpm/exe.android-arm64@12.5.1': optional: true - '@pnpm/exe.darwin-x64@12.3.4': + '@pnpm/exe.android-x64@12.5.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.3.4': + '@pnpm/exe.darwin-arm64@12.5.1': optional: true - '@pnpm/exe.linux-arm64@12.3.4': + '@pnpm/exe.darwin-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64-musl@12.3.4': + '@pnpm/exe.freebsd-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64@12.3.4': + '@pnpm/exe.linux-arm64-musl@12.5.1': optional: true - '@pnpm/exe.win32-arm64@12.3.4': + '@pnpm/exe.linux-arm64@12.5.1': optional: true - '@pnpm/exe.win32-x64@12.3.4': + '@pnpm/exe.linux-ppc64@12.5.1': optional: true - pnpm@12.3.4: + '@pnpm/exe.linux-riscv64@12.5.1': + optional: true + + '@pnpm/exe.linux-s390x@12.5.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.5.1': + optional: true + + '@pnpm/exe.linux-x64@12.5.1': + optional: true + + '@pnpm/exe.win32-arm64@12.5.1': + optional: true + + '@pnpm/exe.win32-x64@12.5.1': + optional: true + + pnpm@12.5.1: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 + '@pnpm/exe.android-arm64': 12.5.1 + '@pnpm/exe.android-x64': 12.5.1 + '@pnpm/exe.darwin-arm64': 12.5.1 + '@pnpm/exe.darwin-x64': 12.5.1 + '@pnpm/exe.freebsd-x64': 12.5.1 + '@pnpm/exe.linux-arm64': 12.5.1 + '@pnpm/exe.linux-arm64-musl': 12.5.1 + '@pnpm/exe.linux-ppc64': 12.5.1 + '@pnpm/exe.linux-riscv64': 12.5.1 + '@pnpm/exe.linux-s390x': 12.5.1 + '@pnpm/exe.linux-x64': 12.5.1 + '@pnpm/exe.linux-x64-musl': 12.5.1 + '@pnpm/exe.win32-arm64': 12.5.1 + '@pnpm/exe.win32-x64': 12.5.1 --- lockfileVersion: '9.0' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c35d255221..6ef1b2c144 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,96 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.3.4 - version: 12.3.4 + specifier: 12.5.1 + version: 12.5.1 packages: - '@pnpm/exe.darwin-arm64@12.3.4': - resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + '@pnpm/exe.android-arm64@12.5.1': + resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.5.1': + resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.5.1': + resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.3.4': - resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + '@pnpm/exe.darwin-x64@12.5.1': + resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.3.4': - resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + '@pnpm/exe.freebsd-x64@12.5.1': + resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.5.1': + resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.3.4': - resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + '@pnpm/exe.linux-arm64@12.5.1': + resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.3.4': - resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + '@pnpm/exe.linux-ppc64@12.5.1': + resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.5.1': + resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.5.1': + resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.5.1': + resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.3.4': - resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + '@pnpm/exe.linux-x64@12.5.1': + resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.3.4': - resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + '@pnpm/exe.win32-arm64@12.5.1': + resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.3.4': - resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + '@pnpm/exe.win32-x64@12.5.1': + resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} cpu: [x64] os: [win32] - pnpm@12.3.4: - resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + pnpm@12.5.1: + resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.3.4': + '@pnpm/exe.android-arm64@12.5.1': optional: true - '@pnpm/exe.darwin-x64@12.3.4': + '@pnpm/exe.android-x64@12.5.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.3.4': + '@pnpm/exe.darwin-arm64@12.5.1': optional: true - '@pnpm/exe.linux-arm64@12.3.4': + '@pnpm/exe.darwin-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64-musl@12.3.4': + '@pnpm/exe.freebsd-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64@12.3.4': + '@pnpm/exe.linux-arm64-musl@12.5.1': optional: true - '@pnpm/exe.win32-arm64@12.3.4': + '@pnpm/exe.linux-arm64@12.5.1': optional: true - '@pnpm/exe.win32-x64@12.3.4': + '@pnpm/exe.linux-ppc64@12.5.1': optional: true - pnpm@12.3.4: + '@pnpm/exe.linux-riscv64@12.5.1': + optional: true + + '@pnpm/exe.linux-s390x@12.5.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.5.1': + optional: true + + '@pnpm/exe.linux-x64@12.5.1': + optional: true + + '@pnpm/exe.win32-arm64@12.5.1': + optional: true + + '@pnpm/exe.win32-x64@12.5.1': + optional: true + + pnpm@12.5.1: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 + '@pnpm/exe.android-arm64': 12.5.1 + '@pnpm/exe.android-x64': 12.5.1 + '@pnpm/exe.darwin-arm64': 12.5.1 + '@pnpm/exe.darwin-x64': 12.5.1 + '@pnpm/exe.freebsd-x64': 12.5.1 + '@pnpm/exe.linux-arm64': 12.5.1 + '@pnpm/exe.linux-arm64-musl': 12.5.1 + '@pnpm/exe.linux-ppc64': 12.5.1 + '@pnpm/exe.linux-riscv64': 12.5.1 + '@pnpm/exe.linux-s390x': 12.5.1 + '@pnpm/exe.linux-x64': 12.5.1 + '@pnpm/exe.linux-x64-musl': 12.5.1 + '@pnpm/exe.win32-arm64': 12.5.1 + '@pnpm/exe.win32-x64': 12.5.1 --- lockfileVersion: '9.0' diff --git a/render-wasm/_build_env b/render-wasm/_build_env index 0b506e415d..4bc9759958 100644 --- a/render-wasm/_build_env +++ b/render-wasm/_build_env @@ -67,8 +67,6 @@ function clean { } function setup { - corepack enable; - corepack install; pnpm install; } diff --git a/render-wasm/package.json b/render-wasm/package.json index 3f8b581f51..4b56c15f73 100644 --- a/render-wasm/package.json +++ b/render-wasm/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", + "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", "repository": { "type": "git", "url": "https://github.com/penpot/penpot" diff --git a/render-wasm/pnpm-lock.yaml b/render-wasm/pnpm-lock.yaml index 58127cd45a..c00e4d1b27 100644 --- a/render-wasm/pnpm-lock.yaml +++ b/render-wasm/pnpm-lock.yaml @@ -7,96 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.3.4 - version: 12.3.4 + specifier: 12.5.1 + version: 12.5.1 packages: - '@pnpm/exe.darwin-arm64@12.3.4': - resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + '@pnpm/exe.android-arm64@12.5.1': + resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.5.1': + resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.5.1': + resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.3.4': - resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + '@pnpm/exe.darwin-x64@12.5.1': + resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.3.4': - resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + '@pnpm/exe.freebsd-x64@12.5.1': + resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.5.1': + resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.3.4': - resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + '@pnpm/exe.linux-arm64@12.5.1': + resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.3.4': - resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + '@pnpm/exe.linux-ppc64@12.5.1': + resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.5.1': + resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.5.1': + resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.5.1': + resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.3.4': - resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + '@pnpm/exe.linux-x64@12.5.1': + resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.3.4': - resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + '@pnpm/exe.win32-arm64@12.5.1': + resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.3.4': - resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + '@pnpm/exe.win32-x64@12.5.1': + resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} cpu: [x64] os: [win32] - pnpm@12.3.4: - resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + pnpm@12.5.1: + resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.3.4': + '@pnpm/exe.android-arm64@12.5.1': optional: true - '@pnpm/exe.darwin-x64@12.3.4': + '@pnpm/exe.android-x64@12.5.1': optional: true - '@pnpm/exe.linux-arm64-musl@12.3.4': + '@pnpm/exe.darwin-arm64@12.5.1': optional: true - '@pnpm/exe.linux-arm64@12.3.4': + '@pnpm/exe.darwin-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64-musl@12.3.4': + '@pnpm/exe.freebsd-x64@12.5.1': optional: true - '@pnpm/exe.linux-x64@12.3.4': + '@pnpm/exe.linux-arm64-musl@12.5.1': optional: true - '@pnpm/exe.win32-arm64@12.3.4': + '@pnpm/exe.linux-arm64@12.5.1': optional: true - '@pnpm/exe.win32-x64@12.3.4': + '@pnpm/exe.linux-ppc64@12.5.1': optional: true - pnpm@12.3.4: + '@pnpm/exe.linux-riscv64@12.5.1': + optional: true + + '@pnpm/exe.linux-s390x@12.5.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.5.1': + optional: true + + '@pnpm/exe.linux-x64@12.5.1': + optional: true + + '@pnpm/exe.win32-arm64@12.5.1': + optional: true + + '@pnpm/exe.win32-x64@12.5.1': + optional: true + + pnpm@12.5.1: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.3.4 - '@pnpm/exe.darwin-x64': 12.3.4 - '@pnpm/exe.linux-arm64': 12.3.4 - '@pnpm/exe.linux-arm64-musl': 12.3.4 - '@pnpm/exe.linux-x64': 12.3.4 - '@pnpm/exe.linux-x64-musl': 12.3.4 - '@pnpm/exe.win32-arm64': 12.3.4 - '@pnpm/exe.win32-x64': 12.3.4 + '@pnpm/exe.android-arm64': 12.5.1 + '@pnpm/exe.android-x64': 12.5.1 + '@pnpm/exe.darwin-arm64': 12.5.1 + '@pnpm/exe.darwin-x64': 12.5.1 + '@pnpm/exe.freebsd-x64': 12.5.1 + '@pnpm/exe.linux-arm64': 12.5.1 + '@pnpm/exe.linux-arm64-musl': 12.5.1 + '@pnpm/exe.linux-ppc64': 12.5.1 + '@pnpm/exe.linux-riscv64': 12.5.1 + '@pnpm/exe.linux-s390x': 12.5.1 + '@pnpm/exe.linux-x64': 12.5.1 + '@pnpm/exe.linux-x64-musl': 12.5.1 + '@pnpm/exe.win32-arm64': 12.5.1 + '@pnpm/exe.win32-x64': 12.5.1 --- lockfileVersion: '9.0' diff --git a/scripts/sync-pnpm-version b/scripts/sync-pnpm-version new file mode 100755 index 0000000000..e87cf17500 --- /dev/null +++ b/scripts/sync-pnpm-version @@ -0,0 +1,185 @@ +#!/usr/bin/env bash +# Sync the `packageManager` field of every first-party package.json to the +# system pnpm version. +# +# Only `pnpm` and `node` are required (plus registry access to resolve the +# integrity hash, unless --field is given). +# +# Run it from the repo root with the log redirected to a file (never pipe +# tool output through filters). + +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: scripts/sync-pnpm-version [options] [version] + +Stamp every first-party package.json's `packageManager` field with the +system pnpm version (default: `pnpm --version`). + +Options: + --field FIELD Use FIELD verbatim, skip the registry lookup. + For tests and offline use. + --check Verify all fields match; do not write. Exit 1 on drift. + --install After stamping, run `pnpm install` in each workspace + root to refresh lockfile metadata. + --root DIR Scan DIR instead of the repo root. For tests. + -h, --help Show this help. + +external/ (vendored trees with their own lifecycles), .opencode/, +.pnpm-store/, bundles/ and docker build-context copies are never stamped. +EOF +} + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +FIELD="" +CHECK=0 +INSTALL=0 +VERSION="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --field) FIELD="$2"; shift 2 ;; + --field=*) FIELD="${1#--field=}"; shift ;; + --check) CHECK=1; shift ;; + --install) INSTALL=1; shift ;; + --root) ROOT="$2"; shift 2 ;; + --root=*) ROOT="${1#--root=}"; shift ;; + -h | --help) usage; exit 0 ;; + -*) { + echo "error: unknown option: $1" >&2 + usage >&2 + exit 64 + } ;; + *) + if [[ -n "$VERSION" ]]; then + echo "error: only one version argument allowed" >&2 + usage >&2 + exit 64 + fi + VERSION="$1"; shift ;; + esac +done + +if [[ -z "$FIELD" ]]; then + if [[ -z "$VERSION" ]]; then + VERSION="$(pnpm --version | tr -d '[:space:]')" + fi + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]]; then + echo "error: invalid pnpm version: $VERSION" >&2 + exit 64 + fi + # Same value `npm view pnpm@ dist.integrity` returns; pnpm is the one + # tool guaranteed to exist everywhere, so no npm needed. + integrity="$(pnpm view "pnpm@${VERSION}" dist.integrity | tr -d '[:space:]')" + b64="${integrity#sha512-}" + if ! [[ "$b64" =~ ^[A-Za-z0-9+/=]+$ ]]; then + echo "error: unexpected integrity for pnpm@${VERSION}: ${integrity}" >&2 + exit 1 + fi + # node ships with every pnpm, so no python3 needed for base64 -> hex. + hex="$(node -p "Buffer.from('${b64}','base64').toString('hex')")" + FIELD="pnpm@${VERSION}+sha512.${hex}" +fi + +cd "$ROOT" + +# Same exclusions as scripts/clean-node-modules, plus build-context copies +# (bundles/, docker/images/bundle-*) which builds regenerate from the +# stamped sources. +mapfile -t files < <( + find . \ + \( -name .git -o -name node_modules -o -name .pnpm-store \ + -o -name external -o -name .opencode -o -name bundles \ + -o -name .angular \) -type d -prune \ + -o -path './docker/images/bundle-*' -prune \ + -o -name package.json -type f -print | sort +) + +if [[ ${#files[@]} -eq 0 ]]; then + echo "error: no package.json files found under $ROOT" >&2 + exit 1 +fi + +# Stamp one file with node: replaces only the packageManager value when the +# key exists (indent, key order and the rest stay byte-identical), else +# inserts the key after "name" using the file's own indent. Prints +# "updated " / "unchanged " / "missing " / "mismatch ". +stamp_one() { + node -e ' + const fs = require("fs"); + const [file, field, check] = process.argv.slice(1); + const raw = fs.readFileSync(file, "utf8"); + const data = JSON.parse(raw); + const rel = file.replace(/^\.\//, ""); + if (data.packageManager === field) { + console.log(`unchanged ${rel}`); + } else if (check === "1") { + console.log(`${data.packageManager === undefined ? "missing" : "mismatch"} ${rel}`); + process.exit(3); + } else { + let out; + const valueRe = /("packageManager"\s*:\s*")[^"]*(")/; + if (valueRe.test(raw)) { + out = raw.replace(valueRe, `$1${field}$2`); + } else { + const anchor = raw.match(/^(\s*)"(name|version)"\s*:\s*"[^"]*",\s*$/m); + if (!anchor) { + console.error(`error: cannot find insertion point in ${rel}`); + process.exit(1); + } + out = raw.replace( + anchor[0], + `${anchor[0]}\n${anchor[1]}"packageManager": "${field}",` + ); + } + JSON.parse(out); // refuse to write invalid JSON + fs.writeFileSync(file, out); + console.log(`updated ${rel}`); + } + ' "$1" "$FIELD" "$CHECK" +} + +failures=0 +updated=0 +unchanged=0 +for file in "${files[@]}"; do + if [[ "$CHECK" -eq 1 ]]; then + if stamp_one "$file"; then + unchanged=$((unchanged + 1)) + else + rc=$? + if [[ "$rc" -eq 3 ]]; then + failures=$((failures + 1)) + else + exit "$rc" + fi + fi + else + if [[ "$(stamp_one "$file")" == updated* ]]; then + updated=$((updated + 1)) + else + unchanged=$((unchanged + 1)) + fi + fi +done + +if [[ "$CHECK" -eq 1 ]]; then + if [[ "$failures" -gt 0 ]]; then + echo "check failed: ${failures} file(s) differ from ${FIELD}" >&2 + exit 1 + fi + echo "all ${unchanged} package.json files match ${FIELD}" + exit 0 +fi + +echo "stamped ${FIELD}: ${updated} updated, ${unchanged} unchanged" + +if [[ "$INSTALL" -eq 1 ]]; then + # The 11 pnpm workspaces: repo root plus one per module. Members resolve + # through their parent workspace lockfile, so no per-member install. + for dir in . backend common docs exporter frontend library mcp media-processor plugins render-wasm; do + echo "--- pnpm install in ${dir}" + (cd "$dir" && pnpm install) + done +fi diff --git a/scripts/test_sync_pnpm_version.py b/scripts/test_sync_pnpm_version.py new file mode 100644 index 0000000000..889250f433 --- /dev/null +++ b/scripts/test_sync_pnpm_version.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Tests for scripts/sync-pnpm-version. + +Run with: + + python3 scripts/test_sync_pnpm_version.py + +Covers stamping the `packageManager` field across a package.json tree: +stale values get updated, missing fields get inserted, formatting of each +file is preserved (only the stamped line may change), --check reports +drift without writing, and stamping is idempotent. +""" + +import json +import pathlib +import shutil +import subprocess +import sys +import tempfile +import unittest + +# Loading scripts/sync-pnpm-version via subprocess; no bytecode emitted. +sys.dont_write_bytecode = True + +SCRIPT_PATH = pathlib.Path(__file__).resolve().parent / "sync-pnpm-version" + +FIELD = "pnpm@99.0.0+sha512." + "ab" * 64 + + +def run_script(*args): + """Run the script, returning the CompletedProcess.""" + return subprocess.run( + ["bash", str(SCRIPT_PATH), *args], + capture_output=True, + text=True, + ) + + +def make_fixture(root): + """Build a 3-file tree: stale value, already synced, missing field.""" + stale = root / "stale" + stale.mkdir() + (stale / "package.json").write_text( + '{\n' + ' "name": "stale",\n' + ' "version": "1.0.0",\n' + ' "private": true,\n' + ' "packageManager": "pnpm@10.0.0+sha512.' + "00" * 64 + '",\n' + ' "scripts": {\n' + ' "test": "echo ok"\n' + ' }\n' + '}\n' + ) + synced = root / "synced" + synced.mkdir() + # 4-space indent on purpose: the stamp must not reformat the file. + (synced / "package.json").write_text( + '{\n' + ' "name": "synced",\n' + ' "version": "1.0.0",\n' + f' "packageManager": "{FIELD}"\n' + '}\n' + ) + missing = root / "missing" + missing.mkdir() + (missing / "package.json").write_text( + '{\n' + ' "name": "missing",\n' + ' "version": "1.0.0",\n' + ' "scripts": {\n' + ' "test": "echo ok"\n' + ' }\n' + '}\n' + ) + return stale, synced, missing + + +class SyncPnpmVersionTests(unittest.TestCase): + def setUp(self): + self.tmp = pathlib.Path(tempfile.mkdtemp(prefix="sync-pnpm-")) + self.stale, self.synced, self.missing = make_fixture(self.tmp) + self.synced_before = (self.synced / "package.json").read_bytes() + + def tearDown(self): + shutil.rmtree(self.tmp, ignore_errors=True) + + def stamp(self, *extra): + return run_script("--root", str(self.tmp), "--field", FIELD, *extra) + + def read(self, path): + return (path / "package.json").read_text() + + def test_stamp_updates_stale_inserts_missing_keeps_synced(self): + proc = self.stamp() + self.assertEqual(proc.returncode, 0, proc.stderr) + + stale_lines = self.read(self.stale).splitlines() + self.assertIn(f' "packageManager": "{FIELD}",', stale_lines) + data = json.loads(self.read(self.stale)) + self.assertEqual(data["packageManager"], FIELD) + # Only the packageManager line changed in the stale file. + self.assertEqual(len(stale_lines), 9) + + # The already-synced file is byte-identical (indent preserved). + self.assertEqual( + (self.synced / "package.json").read_bytes(), self.synced_before + ) + + # The missing field was inserted; the file stays valid JSON. + data = json.loads(self.read(self.missing)) + self.assertEqual(data["packageManager"], FIELD) + self.assertEqual(data["name"], "missing") + + def test_check_passes_once_synced(self): + self.assertEqual(self.stamp().returncode, 0) + proc = self.stamp("--check") + self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) + + def test_check_fails_on_drift_without_writing(self): + self.assertEqual(self.stamp().returncode, 0) + before = (self.stale / "package.json").read_bytes() + (self.stale / "package.json").write_text( + self.read(self.stale).replace(FIELD, "pnpm@1.0.0+sha512." + "ff" * 64) + ) + proc = self.stamp("--check") + self.assertNotEqual(proc.returncode, 0) + self.assertIn("stale/package.json", proc.stdout + proc.stderr) + # --check must not write: reverting the tamper restores a pass. + (self.stale / "package.json").write_bytes(before) + self.assertEqual(self.stamp("--check").returncode, 0) + + def test_check_reports_missing_field(self): + proc = self.stamp("--check") + self.assertNotEqual(proc.returncode, 0) + self.assertIn("missing/package.json", proc.stdout + proc.stderr) + + def test_stamp_is_idempotent(self): + self.assertEqual(self.stamp().returncode, 0) + first = { + p: (self.tmp / p / "package.json").read_bytes() + for p in ("stale", "synced", "missing") + } + second = self.stamp() + self.assertEqual(second.returncode, 0, second.stderr) + for name, content in first.items(): + self.assertEqual( + (self.tmp / name / "package.json").read_bytes(), content + ) + + +if __name__ == "__main__": + unittest.main() From 5c22f5bfb77800348d1afadee267505de358c47a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 22 Sep 2026 10:23:15 +0200 Subject: [PATCH 3/3] :zap: Build the frontend bundle once for all E2E suites (#11792) * :zap: Build the frontend bundle once for all E2E suites Merge tests-integration, tests-composable-suite and tests-plugin-api-suite into one "CI: E2E" workflow. Each of the three ran its own full frontend/scripts/build on every PR, so one PR paid the build three times. The new build-bundle job restores actions/cache key frontend-bundle-, runs frontend/scripts/build only on a miss and saves the key before the job ends. The integration shards, the composable suite and the mocked Plugin API suite now all need build-bundle and restore the same key with fail-on-cache-miss, so none of them builds. A workflow re-run of the same SHA reuses the cached bundle instead of rebuilding it. Triggers become the union of the previous paths (frontend, common, render-wasm, plugins): the bundle embeds the built plugins, so a plugins change runs the whole set. workflow_dispatch keeps running the integration job only, as before. Job names are kept identical on purpose: they are the GitHub check contexts and branch protection may match them by name. Docs: new mem:frontend/e2e-ci-workflow records the build-once contract, referenced from mem:frontend/core and mem:frontend/testing; the composable memory and both suite READMEs are updated. AI-assisted-by: deepseek-v4.1-flash * :bug: Fix mocked plugin suites crashing without frontend deps The mocked CI drivers shelled out to frontend/scripts/e2e-server.js, which imports express from frontend/node_modules. CI jobs install only plugins/ deps, so the import failed with ERR_MODULE_NOT_FOUND and the run timed out waiting for localhost:3000. Serve the prebuilt bundle with a zero-dependency static server built into each driver (ci/static-server.ts, kept in sync in both suites) plus node:test coverage for it. AI-assisted-by: muse-spark-1.3-contributor --- .github/workflows/tests-composable-suite.yml | 67 ------- .../{tests-integration.yml => tests-e2e.yml} | 186 +++++++++++++++++- .github/workflows/tests-plugin-api-suite.yml | 129 ------------ .../frontend/composable-component-tests.md | 6 +- .serena/memories/frontend/core.md | 1 + .serena/memories/frontend/e2e-ci-workflow.md | 43 ++++ .serena/memories/frontend/testing.md | 4 + plugins/apps/composable-test-suite/README.md | 9 +- .../apps/composable-test-suite/ci/run-ci.ts | 25 +-- .../ci/static-server.test.ts | 79 ++++++++ .../composable-test-suite/ci/static-server.ts | 141 +++++++++++++ .../apps/composable-test-suite/package.json | 1 + plugins/apps/plugin-api-test-suite/README.md | 7 +- .../apps/plugin-api-test-suite/ci/run-ci.ts | 33 ++-- .../ci/static-server.test.ts | 79 ++++++++ .../plugin-api-test-suite/ci/static-server.ts | 147 ++++++++++++++ .../apps/plugin-api-test-suite/package.json | 3 +- 17 files changed, 722 insertions(+), 238 deletions(-) delete mode 100644 .github/workflows/tests-composable-suite.yml rename .github/workflows/{tests-integration.yml => tests-e2e.yml} (50%) delete mode 100644 .github/workflows/tests-plugin-api-suite.yml create mode 100644 .serena/memories/frontend/e2e-ci-workflow.md create mode 100644 plugins/apps/composable-test-suite/ci/static-server.test.ts create mode 100644 plugins/apps/composable-test-suite/ci/static-server.ts create mode 100644 plugins/apps/plugin-api-test-suite/ci/static-server.test.ts create mode 100644 plugins/apps/plugin-api-test-suite/ci/static-server.ts diff --git a/.github/workflows/tests-composable-suite.yml b/.github/workflows/tests-composable-suite.yml deleted file mode 100644 index 2ebea5ab59..0000000000 --- a/.github/workflows/tests-composable-suite.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: "CI: Composable Test Suite" - -# Runs the composable component test suite (it exercises component semantics -# through the real Plugin API against the full frontend, so it needs the -# frontend bundle + the plugin runtime, but no backend): the driver serves the -# prebuilt frontend bundle and intercepts every backend RPC with Playwright -# fixtures. See plugins/apps/composable-test-suite/README.md ("Running in CI"). - -defaults: - run: - shell: bash - -on: - pull_request: - paths: - - 'plugins/**' - - 'frontend/**' - - 'common/**' - types: - - opened - - synchronize - - ready_for_review - - push: - branches: - - develop - - staging - paths: - - 'plugins/**' - - 'frontend/**' - - 'common/**' - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - composable-test-suite: - if: ${{ !github.event.pull_request.draft }} - name: "Run composable test suite (mocked backend)" - runs-on: penpot-extended-runner - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - steps: - - uses: actions/checkout@v6 - - # The driver serves the prebuilt bundle from frontend/resources/public. - - name: Build frontend bundle - working-directory: ./frontend - run: ./scripts/build - - - name: Install deps - working-directory: ./plugins - run: | - pnpm install; - - - name: Install Playwright Chromium - working-directory: ./plugins - run: pnpm --filter composable-test-suite exec playwright install --with-deps chromium - - - name: Run composable test suite (mocked) - working-directory: ./plugins - run: pnpm --filter composable-test-suite run test:ci diff --git a/.github/workflows/tests-integration.yml b/.github/workflows/tests-e2e.yml similarity index 50% rename from .github/workflows/tests-integration.yml rename to .github/workflows/tests-e2e.yml index 59aa8182f4..a70008b4e4 100644 --- a/.github/workflows/tests-integration.yml +++ b/.github/workflows/tests-e2e.yml @@ -1,4 +1,26 @@ -name: "CI: Integration" +name: "CI: E2E" + +# Single entry point for every suite that drives a real frontend bundle. +# The bundle is built ONCE by `build-bundle` and restored by every consumer, +# so adding a suite no longer adds another `frontend/scripts/build` run. +# +# Suites and what they need from the bundle: +# +# - Integration Tests: Playwright specs, backend faked with frontend mocks. +# - Composable test suite: Plugin runtime, backend faked with Playwright RPC +# fixtures. See plugins/apps/composable-test-suite/README.md ("Running in CI"). +# - Plugin API Test Suite (mocked): Plugin runtime, backend faked with +# Playwright RPC fixtures and MOCK_BACKEND=1. +# See plugins/apps/plugin-api-test-suite/README.md. +# +# Hand-off: `build-bundle` restores `actions/cache` keyed by the checked-out +# SHA, builds only on a miss, and saves it; every consumer restores the same +# key after `needs: build-bundle` completes. A re-run of the same SHA reuses +# the cached bundle instead of rebuilding it. +# +# NOTE: the job `name:` values are the GitHub check contexts, so they are kept +# stable on purpose (branch protection may require them by name). Only the +# workflow file/name changed. defaults: run: @@ -30,12 +52,18 @@ on: required: true default: '2' + base_url: + description: 'Penpot base URL for the (disabled) live Plugin API suite' + required: false + default: 'https://localhost:3449' + pull_request: paths: - 'frontend/**' - 'common/**' - 'render-wasm/**' - - '.github/workflows/tests-integration.yml' + - 'plugins/**' + - '.github/workflows/tests-e2e.yml' types: - opened @@ -51,18 +79,21 @@ on: - 'frontend/**' - 'common/**' - 'render-wasm/**' - - '.github/workflows/tests-integration.yml' + - 'plugins/**' + - '.github/workflows/tests-e2e.yml' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.gh_ref || github.ref }} cancel-in-progress: true jobs: - build-integration: + # ── 1. Build the frontend bundle once ────────────────────────────────── + build-bundle: if: ${{ !github.event.pull_request.draft }} name: "Build Integration Bundle" runs-on: penpot-extended-runner timeout-minutes: 30 + container: image: penpotapp/devenv:latest volumes: @@ -85,26 +116,36 @@ jobs: - name: Extract cache key id: vars run: | - echo "bundle_key=integration-bundle-$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + echo "bundle_key=frontend-bundle-$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + - name: Restore Bundle Cache + id: restore + uses: actions/cache/restore@v5 + with: + key: ${{ steps.vars.outputs.bundle_key }} + path: frontend/resources/public - name: Build Bundle + if: steps.restore.outputs.cache-hit != 'true' working-directory: ./frontend run: | ./scripts/build - name: Store Bundle Cache - uses: actions/cache@v5 + if: steps.restore.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 with: key: ${{ steps.vars.outputs.bundle_key }} path: frontend/resources/public + # ── 2. Consumers: restore the bundle, never rebuild it ───────────────── test-integration: if: ${{ !github.event.pull_request.draft }} name: "Integration Tests (${{ matrix.shard }})" runs-on: penpot-extended-runner timeout-minutes: 40 - needs: build-integration + needs: build-bundle strategy: fail-fast: false @@ -129,8 +170,9 @@ jobs: - name: Restore Cache uses: actions/cache/restore@v5 with: - key: ${{ needs.build-integration.outputs.bundle_key }} + key: ${{ needs.build-bundle.outputs.bundle_key }} path: frontend/resources/public + fail-on-cache-miss: true - name: Install deps working-directory: ./frontend @@ -175,6 +217,89 @@ jobs: if-no-files-found: ignore retention-days: 3 + composable-test-suite: + if: ${{ github.event_name != 'workflow_dispatch' && !github.event.pull_request.draft }} + name: "Run composable test suite (mocked backend)" + runs-on: penpot-extended-runner + timeout-minutes: 30 + + needs: build-bundle + + container: + image: penpotapp/devenv:latest + volumes: + - /var/cache/github-runner/m2:/root/.m2 + - /var/cache/github-runner/gitlib:/root/.gitlibs + + steps: + - uses: actions/checkout@v6 + + # The driver serves the prebuilt bundle from frontend/resources/public. + - name: Restore Cache + uses: actions/cache/restore@v5 + with: + key: ${{ needs.build-bundle.outputs.bundle_key }} + path: frontend/resources/public + fail-on-cache-miss: true + + - name: Install deps + working-directory: ./plugins + run: | + pnpm install; + + - name: Install Playwright Chromium + working-directory: ./plugins + run: pnpm --filter composable-test-suite exec playwright install --with-deps chromium + + - name: Run composable test suite (mocked) + working-directory: ./plugins + run: pnpm --filter composable-test-suite run test:ci + + api-test-suite-mocked: + if: ${{ github.event_name != 'workflow_dispatch' && !github.event.pull_request.draft }} + name: "Run Plugin API Test Suite (mocked)" + runs-on: penpot-extended-runner + timeout-minutes: 30 + + needs: build-bundle + + container: + image: penpotapp/devenv:latest + volumes: + - /var/cache/github-runner/m2:/root/.m2 + - /var/cache/github-runner/gitlib:/root/.gitlibs + + steps: + - uses: actions/checkout@v6 + + # Mocked mode serves the prebuilt bundle from frontend/resources/public. + - name: Restore Cache + uses: actions/cache/restore@v5 + with: + key: ${{ needs.build-bundle.outputs.bundle_key }} + path: frontend/resources/public + fail-on-cache-miss: true + + - name: Install deps + working-directory: ./plugins + run: | + pnpm install; + + - name: Install Playwright Chromium + working-directory: ./plugins + run: pnpm --filter plugin-api-test-suite exec playwright install --with-deps chromium + + - name: Generate API surface + working-directory: ./plugins + run: pnpm --filter plugin-api-test-suite run gen:api + + - name: Run API test suite (mocked) + working-directory: ./plugins + env: + MOCK_BACKEND: "1" + run: pnpm --filter plugin-api-test-suite run test:ci + + # ── 3. Merge the per-shard integration reports ───────────────────────── merge-reports: if: ${{ !cancelled() && !github.event.pull_request.draft && needs.test-integration.result != 'skipped' }} name: "Merge Integration Reports" @@ -241,8 +366,53 @@ jobs: - name: Upload HTML report uses: actions/upload-artifact@v7 + if: always() with: name: integration-html-report path: frontend/playwright-report/ overwrite: true retention-days: 7 + + # ── 4. Live Plugin API suite (disabled) ──────────────────────────────── + # + # True end-to-end against a LIVE instance: point PENPOT_BASE_URL at a + # reachable instance and provide login credentials via repo secrets. Manual + # because the CI runner has no Docker to stand up a full stack. + # + # api-test-suite-live: + # if: ${{ github.event_name == 'workflow_dispatch' }} + # name: Run Plugin API Test Suite (live) + # runs-on: penpot-extended-runner + # container: + # image: penpotapp/devenv:latest + # + # env: + # PENPOT_BASE_URL: ${{ inputs.base_url }} + # E2E_LOGIN_EMAIL: ${{ secrets.E2E_LOGIN_EMAIL }} + # E2E_LOGIN_PASSWORD: ${{ secrets.E2E_LOGIN_PASSWORD }} + # + # steps: + # - uses: actions/checkout@v6 + # + # - name: Setup Node + # uses: actions/setup-node@v6 + # with: + # node-version-file: .nvmrc + # + # - name: Install deps + # working-directory: ./plugins + # run: | + # pnpm install; + # + # - name: Install Playwright Chromium + # working-directory: ./plugins + # run: pnpm --filter plugin-api-test-suite exec playwright install --with-deps chromium + # + # - name: Generate API surface + # working-directory: ./plugins + # run: pnpm --filter plugin-api-test-suite run gen:api + # + # # Note: requires a running Penpot instance reachable at PENPOT_BASE_URL. + # - name: Run API test suite + # working-directory: ./plugins + # run: pnpm --filter plugin-api-test-suite run test:ci diff --git a/.github/workflows/tests-plugin-api-suite.yml b/.github/workflows/tests-plugin-api-suite.yml deleted file mode 100644 index 0bc070ae30..0000000000 --- a/.github/workflows/tests-plugin-api-suite.yml +++ /dev/null @@ -1,129 +0,0 @@ -name: "CI: Plugin API Test Suite" - -# Runs the Plugin API Test Suite (it exercises the real Penpot Plugin API, so it -# needs a running frontend + the plugin runtime). Two jobs: -# -# - api-test-suite-mocked (pull_request / push): the per-PR gate. Serves the -# prebuilt frontend bundle and intercepts every backend RPC with Playwright -# (MOCK_BACKEND=1). No backend / no login. Validates the frontend Plugin API -# binding + in-memory store; backend-result-dependent tests are skipped via the -# `skipIfMocked` tag. See plugins/apps/plugin-api-test-suite/README.md. -# -# - api-test-suite-live (workflow_dispatch): true end-to-end against a LIVE -# instance. Point PENPOT_BASE_URL at a reachable instance and provide login -# credentials via repo secrets. Manual because the CI runner has no Docker to -# stand up a full stack. - -defaults: - run: - shell: bash - -on: - workflow_dispatch: - inputs: - base_url: - description: "Penpot base URL (e.g. https://localhost:3449)" - required: false - default: "https://localhost:3449" - - pull_request: - paths: - - 'plugins/**' - - 'frontend/**' - - 'common/**' - types: - - opened - - synchronize - - ready_for_review - - push: - branches: - - develop - - staging - paths: - - 'plugins/**' - - 'frontend/src/app/plugins/**' - - 'common/**' - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - api-test-suite-mocked: - if: ${{ github.event_name != 'workflow_dispatch' && !github.event.pull_request.draft }} - name: "Run Plugin API Test Suite (mocked)" - runs-on: penpot-extended-runner - container: - image: penpotapp/devenv:latest - volumes: - - /var/cache/github-runner/m2:/root/.m2 - - /var/cache/github-runner/gitlib:/root/.gitlibs - - steps: - - uses: actions/checkout@v6 - - # Mocked mode serves the prebuilt bundle from frontend/resources/public. - - name: Build frontend bundle - working-directory: ./frontend - run: ./scripts/build - - - name: Install deps - working-directory: ./plugins - run: | - pnpm install; - - - name: Install Playwright Chromium - working-directory: ./plugins - run: pnpm --filter plugin-api-test-suite exec playwright install --with-deps chromium - - - name: Generate API surface - working-directory: ./plugins - run: pnpm --filter plugin-api-test-suite run gen:api - - - name: Run API test suite (mocked) - working-directory: ./plugins - env: - MOCK_BACKEND: "1" - run: pnpm --filter plugin-api-test-suite run test:ci - - ## The following job will launch the whole suite of tests but we need - ## to have a full environment in the CI for this to work. - - # api-test-suite-live: - # if: ${{ github.event_name == 'workflow_dispatch' }} - # name: Run Plugin API Test Suite (live) - # runs-on: penpot-extended-runner - # container: - # image: penpotapp/devenv:latest - # - # env: - # PENPOT_BASE_URL: ${{ github.event.inputs.base_url }} - # E2E_LOGIN_EMAIL: ${{ secrets.E2E_LOGIN_EMAIL }} - # E2E_LOGIN_PASSWORD: ${{ secrets.E2E_LOGIN_PASSWORD }} - # - # steps: - # - uses: actions/checkout@v6 - # - # - name: Setup Node - # uses: actions/setup-node@v6 - # with: - # node-version-file: .nvmrc - # - # - name: Install deps - # working-directory: ./plugins - # run: | - # pnpm install; - # - # - name: Install Playwright Chromium - # working-directory: ./plugins - # run: pnpm --filter plugin-api-test-suite exec playwright install --with-deps chromium - # - # - name: Generate API surface - # working-directory: ./plugins - # run: pnpm --filter plugin-api-test-suite run gen:api - # - # # Note: requires a running Penpot instance reachable at PENPOT_BASE_URL. - # - name: Run API test suite - # working-directory: ./plugins - # run: pnpm --filter plugin-api-test-suite run test:ci diff --git a/.serena/memories/frontend/composable-component-tests.md b/.serena/memories/frontend/composable-component-tests.md index 865a381133..6618203d88 100644 --- a/.serena/memories/frontend/composable-component-tests.md +++ b/.serena/memories/frontend/composable-component-tests.md @@ -138,13 +138,15 @@ Distinguishing abstractions (the OOP articulation of the shared principles): control via Playwright; recipe in the README. ## CI -Headless per-PR gate: `.github/workflows/tests-composable-suite.yml` runs -`pnpm --filter composable-test-suite run test:ci` — mocked backend (frontend e2e static server + +Headless per-PR gate: the `composable-test-suite` job in +`.github/workflows/tests-e2e.yml` runs `pnpm --filter composable-test-suite run test:ci` — mocked +backend (frontend e2e static server + Playwright RPC fixtures, no backend/login), the in-sandbox bundle injected via `ɵloadPlugin`, results streamed via console markers, `TEST_FILTER` by identifier substring. The mocked backend is NOT a limitation for this suite (everything asserted is frontend store logic; empirically confirmed against the interactive runs). Architecture mirrors `plugin-api-test-suite`'s CI driver; the mock harness exists in THREE places that must stay in sync (provenance note in `ci/run-ci.ts`). +The frontend bundle is built once per SHA and restored by this job (`mem:frontend/e2e-ci-workflow`). Details: README, "Running in CI". ## Substrate diff --git a/.serena/memories/frontend/core.md b/.serena/memories/frontend/core.md index 1233aaaf31..bd9b6c9276 100644 --- a/.serena/memories/frontend/core.md +++ b/.serena/memories/frontend/core.md @@ -53,6 +53,7 @@ Diagnostics and validation: - Source-edit compile/hot-reload diagnostics: `mem:frontend/compile-diagnostics`. - Runtime crash recovery: `mem:frontend/handling-crashes`. - Tests and live verification: `mem:frontend/testing`. +- CI end-to-end workflow (build-once frontend bundle, check names): `mem:frontend/e2e-ci-workflow`. - Cross-cutting testing principles and anti-patterns: `mem:testing`. - Real pointer/keyboard gesture reproduction: `mem:frontend/playwright-gestures`. diff --git a/.serena/memories/frontend/e2e-ci-workflow.md b/.serena/memories/frontend/e2e-ci-workflow.md new file mode 100644 index 0000000000..ed94f51488 --- /dev/null +++ b/.serena/memories/frontend/e2e-ci-workflow.md @@ -0,0 +1,43 @@ +# E2E CI workflow (build-once frontend bundle) + +`.github/workflows/tests-e2e.yml` ("CI: E2E") is the single workflow for every +suite that drives a real frontend bundle: + +- `Integration Tests` — Playwright specs under `frontend/playwright` (sharded). +- `Run composable test suite (mocked backend)` — `mem:frontend/composable-component-tests`. +- `Run Plugin API Test Suite (mocked)` — `plugins/apps/plugin-api-test-suite`. + +Triggers: PR/push touching `frontend/**`, `common/**`, `render-wasm/**`, +`plugins/**` (or the workflow file), plus `workflow_dispatch` (integration only). +A `plugins/**` change runs the whole set on purpose: the bundle embeds the +built plugins. + +## Invariants + +- ONE `frontend/scripts/build` per SHA. The `build-bundle` job restores + `actions/cache` key `frontend-bundle-`, builds only on a miss, and saves + the key before the job ends. A re-run of the same SHA reuses the cache. +- Consumer jobs (`needs: build-bundle`) restore the same key with + `fail-on-cache-miss: true` and NEVER run `frontend/scripts/build`. +- The bundle is `frontend/resources/public`. The integration specs serve it + with `frontend/scripts/e2e-server.js`; each mocked plugin driver serves it + with its own zero-dependency `ci/static-server.ts` (duplicated in both + suites — keep the copies in sync). +- Mocked plugin jobs install only `plugins/` deps, so their drivers must not + import anything from `frontend/node_modules` at runtime (e.g. no + `frontend/scripts/e2e-server.js`, which needs `express`). +- Cache key comes from `git rev-parse HEAD` (the checked-out ref), not + `github.sha`, because `workflow_dispatch` can target a different ref. +- Job `name:` values are the GitHub check contexts. Keep them stable: branch + protection may match them by name. Renaming the workflow file/name is safe. + +## Adding a bundle-consuming suite + +Add a job with `needs: build-bundle`, a `Restore Cache` step +(`actions/cache/restore@v5`, key `needs.build-bundle.outputs.bundle_key`, +`fail-on-cache-miss: true`), then that suite's own deps. Never add a build step. + +## Scope + +Distinct from `Bundles Builder` (`.github/workflows/build-bundle.yml`), the +release path that zips the bundle (`manage.sh build-bundle`) and uploads it to S3. diff --git a/.serena/memories/frontend/testing.md b/.serena/memories/frontend/testing.md index c390be9755..1065d48659 100644 --- a/.serena/memories/frontend/testing.md +++ b/.serena/memories/frontend/testing.md @@ -31,6 +31,10 @@ Integration tests fake backend behavior by intercepting network/websocket traffi Locator priority should follow user-facing semantics: `getByRole`, `getByLabel`, `getByPlaceholder`, `getByText`, then semantic alternatives such as alt/title, with `getByTestId` as the last resort. Name tests from the user's perspective and prefer positive, single-purpose assertions. +## CI (E2E) + +`.github/workflows/tests-e2e.yml` runs the integration specs, the composable component suite, and the mocked Plugin API suite from one workflow that builds the frontend bundle once per SHA. Before adding a job that needs the bundle, read `mem:frontend/e2e-ci-workflow` (build-once contract, cache key, stable check names). + ## Live browser verification Because CLJC compiles to both JVM and CLJS, JVM/common tests can miss frontend-only state caused by browser runtime, WASM modifier math, or real pointer events. Use `mem:frontend/cljs-repl` to inspect live app state and `mem:frontend/playwright-gestures` when real input is needed. diff --git a/plugins/apps/composable-test-suite/README.md b/plugins/apps/composable-test-suite/README.md index f324a490ee..aaacd9797f 100644 --- a/plugins/apps/composable-test-suite/README.md +++ b/plugins/apps/composable-test-suite/README.md @@ -172,7 +172,9 @@ pnpm --filter composable-test-suite run test:ci This builds the in-sandbox entry (`src/ci/headless.ts`) as a single self-executing bundle and hands it to the driver (`ci/run-ci.ts`), which -serves the prebuilt frontend bundle via the frontend e2e static server, +serves the prebuilt frontend bundle with a zero-dependency static server +built into the driver (`ci/static-server.ts`, same bundle on the same port — +no `frontend/` install needed), intercepts every backend RPC with Playwright fixtures (no backend, no login), opens the mocked workspace file, injects the bundle directly into the plugin sandbox, and streams each test's result from the page console — failing the @@ -182,9 +184,10 @@ backend's only role is persistence, which the mock answers with a canned response. Prerequisites: the frontend bundle must exist at `frontend/resources/public` -(the devenv watch build suffices; CI builds it via `frontend/scripts/build`), -and the Playwright browser must be installed +(the devenv watch build suffices), and the Playwright browser must be installed (`pnpm --filter composable-test-suite exec playwright install chromium`). +In CI the shared E2E workflow (`.github/workflows/tests-e2e.yml`) builds that +bundle once per commit and this job restores it; do not add a build step. Options via environment variables: diff --git a/plugins/apps/composable-test-suite/ci/run-ci.ts b/plugins/apps/composable-test-suite/ci/run-ci.ts index 7f5820ccd1..d9403c6793 100644 --- a/plugins/apps/composable-test-suite/ci/run-ci.ts +++ b/plugins/apps/composable-test-suite/ci/run-ci.ts @@ -1,8 +1,8 @@ -import { spawn, type ChildProcess } from "node:child_process"; import { readFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { chromium, type Page } from "playwright"; +import { startStaticServer, type StaticServer } from "./static-server.ts"; // Out-of-sandbox CI driver (Node + Playwright) for the composable test suite, // following the plugin-api-test-suite's CI driver. NOTE on provenance: the mock @@ -11,7 +11,8 @@ import { chromium, type Page } from "playwright"; // (frontend/playwright, the origin), the plugin-api-test-suite driver, and this // file. If workspace loading changes and this driver times out waiting for the // viewport, diff against those two first. It serves the prebuilt -// frontend bundle via the frontend e2e static server, intercepts every backend +// frontend bundle with the zero-dependency static server in +// `ci/static-server.ts`, intercepts every backend // RPC with Playwright `page.route` (reusing the frontend e2e mock fixtures), // injects the prebuilt `headless.js` bundle into the plugin sandbox via // `globalThis.ɵloadPlugin`, and captures the results from the page console. @@ -30,6 +31,7 @@ const here = dirname(fileURLToPath(import.meta.url)); // here = /plugins/apps/composable-test-suite/ci const repoRoot = resolve(here, "../../../../"); const frontendDir = resolve(repoRoot, "frontend"); +const staticRoot = resolve(frontendDir, "resources/public"); const e2eDataDir = resolve(frontendDir, "playwright/data"); const BASE_URL = "http://localhost:3000"; @@ -96,14 +98,15 @@ async function waitForServer(url: string, timeoutMs = 30000): Promise { } } -function startE2eServer(): ChildProcess { - // Reuse the frontend e2e static server: it serves frontend/resources/public - // on port 3000, which is also the host the app opens its notifications +function startE2eServer(): Promise { + // Serve the prebuilt frontend bundle from `frontend/resources/public` on + // port 3000, which is also the host the app opens its notifications // WebSocket against — so the WS mock below matches without extra config. - return spawn("node", ["scripts/e2e-server.js"], { - cwd: frontendDir, - stdio: "inherit", - }); + // This used to shell out to the express-based + // `frontend/scripts/e2e-server.js`, but that resolves `express` from + // `frontend/node_modules`, which the CI jobs never install (only + // `plugins/` deps), so the driver crashed before serving anything. + return startStaticServer(staticRoot, 3000); } // Install the frontend e2e WebSocket mock so the workspace's notifications @@ -204,7 +207,7 @@ function printReport(results: ReportedResult[]) { async function main() { const bundle = readFileSync(headlessBundlePath, "utf-8"); - const server = startE2eServer(); + const server = await startE2eServer(); await waitForServer(BASE_URL); const browser = await chromium.launch(); @@ -280,7 +283,7 @@ async function main() { ]); await browser.close(); - server.kill(); + await server.close(); printReport(results); diff --git a/plugins/apps/composable-test-suite/ci/static-server.test.ts b/plugins/apps/composable-test-suite/ci/static-server.test.ts new file mode 100644 index 0000000000..3818f72e40 --- /dev/null +++ b/plugins/apps/composable-test-suite/ci/static-server.test.ts @@ -0,0 +1,79 @@ +import { strict as assert } from "node:assert"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { after, before, describe, it } from "node:test"; +import { startStaticServer, type StaticServer } from "./static-server.ts"; + +describe("static-server", () => { + let dir: string = ""; + let server: StaticServer | undefined; + const baseUrl = (): string => { + if (!server) throw new Error("static server not started"); + return server.url; + }; + + before(async () => { + dir = await mkdtemp(join(tmpdir(), "penpot-static-server-")); + await mkdir(join(dir, "js"), { recursive: true }); + await writeFile(join(dir, "index.html"), ""); + await writeFile(join(dir, "js", "app.js"), 'console.log("hi");'); + await writeFile(join(dir, "data.bin"), Buffer.from([0, 1, 2])); + server = await startStaticServer(dir, 0); + }); + + after(async () => { + await server?.close(); + // Closing twice must be safe (the driver closes unconditionally). + await server?.close(); + await rm(dir, { recursive: true, force: true }); + }); + + it("serves / as index.html", async () => { + const res = await fetch(`${baseUrl()}/`); + assert.equal(res.status, 200); + assert.match(res.headers.get("content-type") ?? "", /text\/html/); + assert.match(await res.text(), //); + }); + + it("serves nested files with a javascript content type", async () => { + const res = await fetch(`${baseUrl()}/js/app.js`); + assert.equal(res.status, 200); + assert.match(res.headers.get("content-type") ?? "", /javascript/); + assert.equal(await res.text(), 'console.log("hi");'); + }); + + it("ignores query strings", async () => { + const res = await fetch(`${baseUrl()}/index.html?rev=123`); + assert.equal(res.status, 200); + assert.match(await res.text(), //); + }); + + it("falls back to octet-stream for unknown extensions", async () => { + const res = await fetch(`${baseUrl()}/data.bin`); + assert.equal(res.status, 200); + assert.equal(res.headers.get("content-type"), "application/octet-stream"); + }); + + it("answers HEAD without a body", async () => { + const res = await fetch(`${baseUrl()}/index.html`, { method: "HEAD" }); + assert.equal(res.status, 200); + assert.match(res.headers.get("content-type") ?? "", /text\/html/); + assert.equal(await res.text(), ""); + }); + + it("rejects other methods", async () => { + const res = await fetch(`${baseUrl()}/index.html`, { method: "POST" }); + assert.equal(res.status, 405); + }); + + it("returns 404 for missing files", async () => { + const res = await fetch(`${baseUrl()}/nope/missing.js`); + assert.equal(res.status, 404); + }); + + it("blocks path traversal outside the root", async () => { + const res = await fetch(`${baseUrl()}/..%2f..%2fsecret`); + assert.equal(res.status, 403); + }); +}); diff --git a/plugins/apps/composable-test-suite/ci/static-server.ts b/plugins/apps/composable-test-suite/ci/static-server.ts new file mode 100644 index 0000000000..294eef9a35 --- /dev/null +++ b/plugins/apps/composable-test-suite/ci/static-server.ts @@ -0,0 +1,141 @@ +import { createServer, type Server } from "node:http"; +import { readFile, stat } from "node:fs/promises"; +import { extname, join, resolve, sep } from "node:path"; + +// Zero-dependency static file server for the mocked-backend CI driver. +// +// It replaces `frontend/scripts/e2e-server.js` (express-based) on purpose: +// that script resolves `express`/`compression` from `frontend/node_modules`, +// which the CI jobs never install (they only run `pnpm install` inside +// `plugins/` and restore the prebuilt bundle), so the driver crashed with +// ERR_MODULE_NOT_FOUND and timed out waiting for localhost:3000. Serving the +// bundle from here keeps the suite runnable with only `plugins/` +// dependencies — the documented local workflow — and identical in CI. +// +// NOTE on provenance: this file is duplicated in +// `plugins/apps/plugin-api-test-suite/ci/static-server.ts`. Keep the two in +// sync (same as the mock harness in `run-ci.ts`). + +const MIME_TYPES: Record = { + ".css": "text/css; charset=utf-8", + ".gif": "image/gif", + ".html": "text/html; charset=utf-8", + ".ico": "image/x-icon", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".js": "application/javascript; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".map": "application/json; charset=utf-8", + ".mjs": "application/javascript; charset=utf-8", + ".otf": "font/otf", + ".png": "image/png", + ".svg": "image/svg+xml", + ".ttf": "font/ttf", + ".txt": "text/plain; charset=utf-8", + ".wasm": "application/wasm", + ".webmanifest": "application/manifest+json", + ".webp": "image/webp", + ".woff": "font/woff", + ".woff2": "font/woff2", + ".xml": "application/xml; charset=utf-8", +}; + +const INDEX = "index.html"; + +export interface StaticServer { + /** Base URL the server listens on (e.g. `http://localhost:3000`). */ + url: string; + /** Stop accepting connections; safe to call more than once. */ + close: () => Promise; +} + +/** + * Serve `root` over HTTP on `port` (`0` picks a free port, reported in + * `url`). Directory requests fall back to `index.html`; the app uses hash + * routing, so no other fallback is needed. + */ +export function startStaticServer(root: string, port: number): Promise { + const docRoot = resolve(root); + const server: Server = createServer(async (req, res) => { + try { + if (req.method !== "GET" && req.method !== "HEAD") { + res.writeHead(405, { "Content-Type": "text/plain; charset=utf-8" }); + res.end("Method Not Allowed"); + return; + } + + const rawPath = (req.url ?? "/").split(/[?#]/, 1)[0] ?? "/"; + let pathname: string; + try { + pathname = decodeURIComponent(rawPath); + } catch { + res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" }); + res.end("Bad Request"); + return; + } + + const resolved = resolve(docRoot, `.${sep}${pathname}`); + if (resolved !== docRoot && !resolved.startsWith(docRoot + sep)) { + res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" }); + res.end("Forbidden"); + return; + } + + let filePath = resolved; + const info = await stat(filePath).catch((error: unknown) => { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return null; + throw error; + }); + if (info === null) { + res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }); + res.end("Not Found"); + return; + } + if (info.isDirectory()) { + filePath = join(filePath, INDEX); + } + + const body = await readFile(filePath).catch((error: unknown) => { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return null; + throw error; + }); + if (body === null) { + res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }); + res.end("Not Found"); + return; + } + + const contentType = MIME_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream"; + res.writeHead(200, { + "Content-Type": contentType, + "Content-Length": body.length, + }); + res.end(req.method === "GET" ? body : undefined); + } catch { + if (!res.headersSent) { + res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" }); + } + res.end("Internal Server Error"); + } + }); + + return new Promise((fulfill, reject) => { + server.once("error", reject); + server.listen(port, "0.0.0.0", () => { + server.off("error", reject); + const address = server.address(); + const actualPort = typeof address === "object" && address !== null ? address.port : port; + fulfill({ + url: `http://localhost:${actualPort}`, + close: () => + new Promise((done, fail) => { + if (!server.listening) { + done(); + return; + } + server.close((error) => (error ? fail(error) : done())); + }), + }); + }); + }); +} diff --git a/plugins/apps/composable-test-suite/package.json b/plugins/apps/composable-test-suite/package.json index f2a776ec8f..dbdc016c54 100644 --- a/plugins/apps/composable-test-suite/package.json +++ b/plugins/apps/composable-test-suite/package.json @@ -9,6 +9,7 @@ "build": "tsc && vite build", "build:headless": "vite build --config vite.config.headless.ts", "test:ci": "pnpm run build:headless && tsx ci/run-ci.ts", + "test:unit": "tsx --test ci/static-server.test.ts", "preview": "vite preview", "bootstrap": "pnpm install --ignore-workspace && pnpm run build && pnpm run start", "types:check": "tsc --noEmit", diff --git a/plugins/apps/plugin-api-test-suite/README.md b/plugins/apps/plugin-api-test-suite/README.md index 4de9a3adfb..af01f7e4ca 100644 --- a/plugins/apps/plugin-api-test-suite/README.md +++ b/plugins/apps/plugin-api-test-suite/README.md @@ -75,7 +75,9 @@ them the same way the plugin does). ### Mocked-backend mode The same runner can run without a live instance — it serves the prebuilt -frontend via the frontend e2e static server and intercepts every backend RPC +frontend with a zero-dependency static server built into the driver +(`ci/static-server.ts`, same bundle on the same port 3000 — no `frontend/` +install needed) and intercepts every backend RPC with Playwright `page.route`, reusing the frontend e2e mock fixtures: ``` @@ -83,6 +85,9 @@ pnpm --filter plugin-api-test-suite run test:ci:mocked ``` (equivalently `MOCK_BACKEND=1 … run test:ci`). No login or backend is needed. +This is the per-PR CI gate: the `Run Plugin API Test Suite (mocked)` job in +`.github/workflows/tests-e2e.yml` restores the frontend bundle that the shared +workflow builds once per commit (never build it in the job). This validates the frontend Plugin API binding + in-memory store only, so it can't faithfully reproduce results that depend on real backend behaviour (validation, persistence, generated ids, …). Tests that need the real backend diff --git a/plugins/apps/plugin-api-test-suite/ci/run-ci.ts b/plugins/apps/plugin-api-test-suite/ci/run-ci.ts index 37f865ac0d..801d62779d 100644 --- a/plugins/apps/plugin-api-test-suite/ci/run-ci.ts +++ b/plugins/apps/plugin-api-test-suite/ci/run-ci.ts @@ -1,9 +1,9 @@ -import { spawn, type ChildProcess } from 'node:child_process'; import { readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { chromium, type Page } from 'playwright'; import type { CoverageReport, TestResult } from '../src/framework/types'; +import { startStaticServer, type StaticServer } from './static-server.ts'; // Out-of-sandbox CI driver (Node + Playwright). Injects the prebuilt // `headless.js` bundle (built from the in-sandbox entry `src/ci/headless.ts` — @@ -19,8 +19,9 @@ import type { CoverageReport, TestResult } from '../src/framework/types'; // the given substring (case-insensitive). // Optional env: RENDER_WASM — force the workspace renderer (`true`/`false`). // -// - MOCKED (`MOCK_BACKEND=1`): serves the prebuilt frontend bundle via the e2e -// static server and intercepts every backend RPC with Playwright `page.route`, +// - MOCKED (`MOCK_BACKEND=1`): serves the prebuilt frontend bundle with the +// zero-dependency static server in `ci/static-server.ts` and intercepts +// every backend RPC with Playwright `page.route`, // reusing the frontend e2e mock fixtures. No backend/login needed. Validates // the frontend Plugin API binding + in-memory store only; results that depend // on real backend behaviour are not faithfully reproduced, so those tests are @@ -30,6 +31,7 @@ const here = dirname(fileURLToPath(import.meta.url)); // here = /plugins/apps/plugin-api-test-suite/ci const repoRoot = resolve(here, '../../../../'); const frontendDir = resolve(repoRoot, 'frontend'); +const staticRoot = resolve(frontendDir, 'resources/public'); const e2eDataDir = resolve(frontendDir, 'playwright/data'); const MOCKED = !!process.env['MOCK_BACKEND']; @@ -179,16 +181,15 @@ async function waitForServer(url: string, timeoutMs = 30000): Promise { } } -function startE2eServer(): ChildProcess { - // Reuse the frontend e2e static server: it serves frontend/resources/public - // on port 3000, which is also the host the app opens its notifications - // WebSocket against (ws://localhost:3000/ws/notifications) — so the WS mock - // below matches without extra config. - const child = spawn('node', ['scripts/e2e-server.js'], { - cwd: frontendDir, - stdio: 'inherit', - }); - return child; +function startE2eServer(): Promise { + // Serve the prebuilt bundle from `frontend/resources/public` on port 3000, + // which is also the host the app opens its notifications WebSocket against + // (ws://localhost:3000/ws/notifications) — so the WS mock below matches + // without extra config. This used to shell out to the express-based + // `frontend/scripts/e2e-server.js`, but that resolves `express` from + // `frontend/node_modules`, which the CI jobs never install (only + // `plugins/` deps), so the driver crashed before serving anything. + return startStaticServer(staticRoot, 3000); } // Install the frontend e2e WebSocket mock so the workspace's notifications @@ -337,12 +338,12 @@ function printReport( async function main() { const bundle = readFileSync(headlessBundlePath, 'utf-8'); - let server: ChildProcess | undefined; + let server: StaticServer | undefined; let fileUrl: string; let authToken: string | undefined; if (MOCKED) { - server = startE2eServer(); + server = await startE2eServer(); await waitForServer(MOCK_BASE_URL); fileUrl = mockedFileUrl(); } else { @@ -463,7 +464,7 @@ async function main() { ]); await browser.close(); - server?.kill(); + await server?.close(); printReport(results, coverage, skipped); diff --git a/plugins/apps/plugin-api-test-suite/ci/static-server.test.ts b/plugins/apps/plugin-api-test-suite/ci/static-server.test.ts new file mode 100644 index 0000000000..b85bd8dc75 --- /dev/null +++ b/plugins/apps/plugin-api-test-suite/ci/static-server.test.ts @@ -0,0 +1,79 @@ +import { strict as assert } from 'node:assert'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { after, before, describe, it } from 'node:test'; +import { startStaticServer, type StaticServer } from './static-server.ts'; + +describe('static-server', () => { + let dir: string = ''; + let server: StaticServer | undefined; + const baseUrl = (): string => { + if (!server) throw new Error('static server not started'); + return server.url; + }; + + before(async () => { + dir = await mkdtemp(join(tmpdir(), 'penpot-static-server-')); + await mkdir(join(dir, 'js'), { recursive: true }); + await writeFile(join(dir, 'index.html'), ''); + await writeFile(join(dir, 'js', 'app.js'), 'console.log("hi");'); + await writeFile(join(dir, 'data.bin'), Buffer.from([0, 1, 2])); + server = await startStaticServer(dir, 0); + }); + + after(async () => { + await server?.close(); + // Closing twice must be safe (the driver closes unconditionally). + await server?.close(); + await rm(dir, { recursive: true, force: true }); + }); + + it('serves / as index.html', async () => { + const res = await fetch(`${baseUrl()}/`); + assert.equal(res.status, 200); + assert.match(res.headers.get('content-type') ?? '', /text\/html/); + assert.match(await res.text(), //); + }); + + it('serves nested files with a javascript content type', async () => { + const res = await fetch(`${baseUrl()}/js/app.js`); + assert.equal(res.status, 200); + assert.match(res.headers.get('content-type') ?? '', /javascript/); + assert.equal(await res.text(), 'console.log("hi");'); + }); + + it('ignores query strings', async () => { + const res = await fetch(`${baseUrl()}/index.html?rev=123`); + assert.equal(res.status, 200); + assert.match(await res.text(), //); + }); + + it('falls back to octet-stream for unknown extensions', async () => { + const res = await fetch(`${baseUrl()}/data.bin`); + assert.equal(res.status, 200); + assert.equal(res.headers.get('content-type'), 'application/octet-stream'); + }); + + it('answers HEAD without a body', async () => { + const res = await fetch(`${baseUrl()}/index.html`, { method: 'HEAD' }); + assert.equal(res.status, 200); + assert.match(res.headers.get('content-type') ?? '', /text\/html/); + assert.equal(await res.text(), ''); + }); + + it('rejects other methods', async () => { + const res = await fetch(`${baseUrl()}/index.html`, { method: 'POST' }); + assert.equal(res.status, 405); + }); + + it('returns 404 for missing files', async () => { + const res = await fetch(`${baseUrl()}/nope/missing.js`); + assert.equal(res.status, 404); + }); + + it('blocks path traversal outside the root', async () => { + const res = await fetch(`${baseUrl()}/..%2f..%2fsecret`); + assert.equal(res.status, 403); + }); +}); diff --git a/plugins/apps/plugin-api-test-suite/ci/static-server.ts b/plugins/apps/plugin-api-test-suite/ci/static-server.ts new file mode 100644 index 0000000000..25ddee5e1e --- /dev/null +++ b/plugins/apps/plugin-api-test-suite/ci/static-server.ts @@ -0,0 +1,147 @@ +import { createServer, type Server } from 'node:http'; +import { readFile, stat } from 'node:fs/promises'; +import { extname, join, resolve, sep } from 'node:path'; + +// Zero-dependency static file server for the mocked-backend CI driver. +// +// It replaces `frontend/scripts/e2e-server.js` (express-based) on purpose: +// that script resolves `express`/`compression` from `frontend/node_modules`, +// which the CI jobs never install (they only run `pnpm install` inside +// `plugins/` and restore the prebuilt bundle), so the driver crashed with +// ERR_MODULE_NOT_FOUND and timed out waiting for localhost:3000. Serving the +// bundle from here keeps the suite runnable with only `plugins/` +// dependencies — the documented local workflow — and identical in CI. +// +// NOTE on provenance: this file is duplicated in +// `plugins/apps/composable-test-suite/ci/static-server.ts`. Keep the two in +// sync (same as the mock harness in `run-ci.ts`). + +const MIME_TYPES: Record = { + '.css': 'text/css; charset=utf-8', + '.gif': 'image/gif', + '.html': 'text/html; charset=utf-8', + '.ico': 'image/x-icon', + '.jpeg': 'image/jpeg', + '.jpg': 'image/jpeg', + '.js': 'application/javascript; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.map': 'application/json; charset=utf-8', + '.mjs': 'application/javascript; charset=utf-8', + '.otf': 'font/otf', + '.png': 'image/png', + '.svg': 'image/svg+xml', + '.ttf': 'font/ttf', + '.txt': 'text/plain; charset=utf-8', + '.wasm': 'application/wasm', + '.webmanifest': 'application/manifest+json', + '.webp': 'image/webp', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.xml': 'application/xml; charset=utf-8', +}; + +const INDEX = 'index.html'; + +export interface StaticServer { + /** Base URL the server listens on (e.g. `http://localhost:3000`). */ + url: string; + /** Stop accepting connections; safe to call more than once. */ + close: () => Promise; +} + +/** + * Serve `root` over HTTP on `port` (`0` picks a free port, reported in + * `url`). Directory requests fall back to `index.html`; the app uses hash + * routing, so no other fallback is needed. + */ +export function startStaticServer( + root: string, + port: number, +): Promise { + const docRoot = resolve(root); + const server: Server = createServer(async (req, res) => { + try { + if (req.method !== 'GET' && req.method !== 'HEAD') { + res.writeHead(405, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Method Not Allowed'); + return; + } + + const rawPath = (req.url ?? '/').split(/[?#]/, 1)[0] ?? '/'; + let pathname: string; + try { + pathname = decodeURIComponent(rawPath); + } catch { + res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Bad Request'); + return; + } + + const resolved = resolve(docRoot, `.${sep}${pathname}`); + if (resolved !== docRoot && !resolved.startsWith(docRoot + sep)) { + res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Forbidden'); + return; + } + + let filePath = resolved; + const info = await stat(filePath).catch((error: unknown) => { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null; + throw error; + }); + if (info === null) { + res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Not Found'); + return; + } + if (info.isDirectory()) { + filePath = join(filePath, INDEX); + } + + const body = await readFile(filePath).catch((error: unknown) => { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null; + throw error; + }); + if (body === null) { + res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Not Found'); + return; + } + + const contentType = + MIME_TYPES[extname(filePath).toLowerCase()] ?? + 'application/octet-stream'; + res.writeHead(200, { + 'Content-Type': contentType, + 'Content-Length': body.length, + }); + res.end(req.method === 'GET' ? body : undefined); + } catch { + if (!res.headersSent) { + res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' }); + } + res.end('Internal Server Error'); + } + }); + + return new Promise((fulfill, reject) => { + server.once('error', reject); + server.listen(port, '0.0.0.0', () => { + server.off('error', reject); + const address = server.address(); + const actualPort = + typeof address === 'object' && address !== null ? address.port : port; + fulfill({ + url: `http://localhost:${actualPort}`, + close: () => + new Promise((done, fail) => { + if (!server.listening) { + done(); + return; + } + server.close((error) => (error ? fail(error) : done())); + }), + }); + }); + }); +} diff --git a/plugins/apps/plugin-api-test-suite/package.json b/plugins/apps/plugin-api-test-suite/package.json index 92eeee54fe..e49ce3dcf1 100644 --- a/plugins/apps/plugin-api-test-suite/package.json +++ b/plugins/apps/plugin-api-test-suite/package.json @@ -14,7 +14,8 @@ "lint": "eslint .", "gen:api": "tsx tools/gen-api-surface.ts", "test:ci": "pnpm run build:headless && tsx ci/run-ci.ts", - "test:ci:mocked": "pnpm run build:headless && MOCK_BACKEND=1 tsx ci/run-ci.ts" + "test:ci:mocked": "pnpm run build:headless && MOCK_BACKEND=1 tsx ci/run-ci.ts", + "test:unit": "tsx --test ci/static-server.test.ts" }, "devDependencies": { "playwright": "^1.62.1"