diff --git a/common/src/app/common/types/plugins.cljc b/common/src/app/common/types/plugins.cljc index 1be3578cc1..7fe8a4c7d4 100644 --- a/common/src/app/common/types/plugins.cljc +++ b/common/src/app/common/types/plugins.cljc @@ -30,6 +30,7 @@ (def schema:registry-entry [:map [:plugin-id :string] + [:version {:optional true} :int] [:name :string] [:description {:optional true} :string] [:host :string] diff --git a/frontend/src/app/main/data/plugins.cljs b/frontend/src/app/main/data/plugins.cljs index 77f5d55013..6635fc070b 100644 --- a/frontend/src/app/main/data/plugins.cljs +++ b/frontend/src/app/main/data/plugins.cljs @@ -82,7 +82,7 @@ (defn- load-plugin! [{:keys [plugin-id name version description host code icon permissions]}] - (st/emit! (pflag/clear plugin-id) + (st/emit! (pflag/initialize plugin-id version) (save-current-plugin plugin-id)) (let [load-plugin (unchecked-get ug/global "ɵloadPlugin")] diff --git a/frontend/src/app/plugins/flags.cljs b/frontend/src/app/plugins/flags.cljs index ef0ca57ab6..b804df4d9c 100644 --- a/frontend/src/app/plugins/flags.cljs +++ b/frontend/src/app/plugins/flags.cljs @@ -6,17 +6,24 @@ (ns app.plugins.flags (:require + [app.common.data :as d] [app.main.store :as st] [app.plugins.utils :as u] [app.util.object :as obj] [potok.v2.core :as ptk])) -(defn clear - [id] - (ptk/reify ::reset +(defn initialize + "Initialize flags values for plugins" + [id version] + (ptk/reify ::initialize ptk/UpdateEvent (update [_ state] - (update-in state [:plugins :flags] assoc id {})))) + (let [version (d/nilv version 1)] + (update-in state [:plugins :flags] assoc id + {:natural-child-ordering false + ;; For version >= 2 harden the contract by throwing errors + ;; on validation failures + :throw-validation-errors (>= version 2)}))))) (defn- set-flag [id key value] diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index e08e792ada..2e0db5d324 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -323,14 +323,27 @@ message to the console." [plugin-id] (fn [cause] - (let [message - (if-let [explain (-> cause ex-data ::sm/explain)] - (do - (js/console.error (sm/humanize-explain explain)) - (error-messages explain)) - (ex-data cause))] - (js/console.log (.-stack cause)) - (not-valid plugin-id :error message)))) + (let [explain (-> cause ex-data ::sm/explain) + throw? (throw-validation-errors? plugin-id)] + (cond + ;; If it's a clojure error we throw as a validation error + (and throw? explain) + (throw-not-valid :error (error-messages explain)) + + ;; Unexpected errors we just propagate them + throw? + (throw cause) + + ;; If not throw is active we log the caught error + :else + (let [message + (if explain + (do + (js/console.error (sm/humanize-explain explain)) + (error-messages explain)) + (ex-data cause))] + (js/console.log (.-stack cause)) + (not-valid plugin-id :error message)))))) (defn is-main-component-proxy? [p] diff --git a/frontend/test/frontend_tests/plugins/context_shapes_test.cljs b/frontend/test/frontend_tests/plugins/context_shapes_test.cljs index dd1a7e3634..0bba39cd40 100644 --- a/frontend/test/frontend_tests/plugins/context_shapes_test.cljs +++ b/frontend/test/frontend_tests/plugins/context_shapes_test.cljs @@ -14,7 +14,8 @@ [app.util.object :as obj] [cljs.test :as t :include-macros true] [frontend-tests.helpers.state :as ths] - [frontend-tests.helpers.wasm :as thw])) + [frontend-tests.helpers.wasm :as thw] + [potok.v2.core :as ptk])) (t/deftest test-common-shape-properties (thw/with-wasm-mocks* @@ -25,6 +26,7 @@ ^js context (api/create-context "00000000-0000-0000-0000-000000000000") _ (set! st/state store) + _ (ptk/emit! store #(assoc-in % [:plugins :flags "00000000-0000-0000-0000-000000000000" :throw-validation-errors] true)) ^js file (. context -currentFile) ^js page (. context -currentPage) @@ -65,7 +67,7 @@ (t/is (= (.-x shape) 10)) (t/is (= (get-in @store (get-shape-path :x)) 10)) - (set! (.-x shape) "fail") + (t/is (thrown? js/Error (set! (.-x shape) "fail"))) (t/is (= (.-x shape) 10)) (t/is (= (get-in @store (get-shape-path :x)) 10))) @@ -74,7 +76,7 @@ (t/is (= (.-y shape) 50)) (t/is (= (get-in @store (get-shape-path :y)) 50)) - (set! (.-y shape) "fail") + (t/is (thrown? js/Error (set! (.-y shape) "fail"))) (t/is (= (.-y shape) 50)) (t/is (= (get-in @store (get-shape-path :y)) 50))) @@ -85,7 +87,7 @@ (t/is (= (get-in @store (get-shape-path :width)) 250)) (t/is (= (get-in @store (get-shape-path :height)) 300)) - (.resize shape 0 0) + (t/is (thrown? js/Error (.resize shape 0 0))) (t/is (= (.-width shape) 250)) (t/is (= (.-height shape) 300)) (t/is (= (get-in @store (get-shape-path :width)) 250)) @@ -115,7 +117,7 @@ (t/is (= (get-in @store (get-shape-path :proportion-lock)) true))) (t/testing " - constraintsHorizontal" - (set! (.-constraintsHorizontal shape) "fail") + (t/is (thrown? js/Error (set! (.-constraintsHorizontal shape) "fail"))) (t/is (not= (.-constraintsHorizontal shape) "fail")) (t/is (not= (get-in @store (get-shape-path :constraints-h)) "fail")) @@ -124,7 +126,7 @@ (t/is (= (get-in @store (get-shape-path :constraints-h)) :right))) (t/testing " - constraintsVertical" - (set! (.-constraintsVertical shape) "fail") + (t/is (thrown? js/Error (set! (.-constraintsVertical shape) "fail"))) (t/is (not= (.-constraintsVertical shape) "fail")) (t/is (not= (get-in @store (get-shape-path :constraints-v)) "fail")) @@ -175,7 +177,7 @@ (t/is (= (.-blendMode shape) "multiply")) (t/is (= (get-in @store (get-shape-path :blend-mode)) :multiply)) - (set! (.-blendMode shape) "fail") + (t/is (thrown? js/Error (set! (.-blendMode shape) "fail"))) (t/is (= (.-blendMode shape) "multiply")) (t/is (= (get-in @store (get-shape-path :blend-mode)) :multiply))) @@ -194,7 +196,7 @@ :color {:color "#fabada" :opacity 1} :hidden false}])))) (let [shadow #js {:style "fail"}] - (set! (.-shadows shape) #js [shadow]) + (t/is (thrown? js/Error (set! (.-shadows shape) #js [shadow]))) (t/is (= (-> (. shape -shadows) (aget 0) (aget "style")) "drop-shadow")))) (t/testing " - blur" @@ -211,7 +213,7 @@ (t/is (= (-> (. shape -exports) (aget 0) (aget "suffix")) "test")) (t/is (= (get-in @store (get-shape-path :exports)) [{:type :pdf :scale 2 :suffix "test" :skip-children false}])) - (set! (.-exports shape) #js [#js {:type 10 :scale 2 :suffix "test"}]) + (t/is (thrown? js/Error (set! (.-exports shape) #js [#js {:type 10 :scale 2 :suffix "test"}]))) (t/is (= (get-in @store (get-shape-path :exports)) [{:type :pdf :scale 2 :suffix "test" :skip-children false}]))) (t/testing " - flipX" @@ -234,7 +236,7 @@ (t/is (= (get-in @store (get-shape-path :rotation)) 0))) (t/testing " - fills" - (set! (.-fills shape) #js [#js {:fillColor 100}]) + (t/is (thrown? js/Error (set! (.-fills shape) #js [#js {:fillColor 100}]))) (t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#B1B2B5" :fill-opacity 1}])) (t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#B1B2B5")) diff --git a/frontend/test/frontend_tests/plugins/page_test.cljs b/frontend/test/frontend_tests/plugins/page_test.cljs index bcc59c4ce8..d29149e846 100644 --- a/frontend/test/frontend_tests/plugins/page_test.cljs +++ b/frontend/test/frontend_tests/plugins/page_test.cljs @@ -32,6 +32,7 @@ store (ths/setup-store file) _ (set! st/state store) _ (set! st/stream (ptk/input-stream store)) + _ (ptk/emit! store #(assoc-in % [:plugins :flags "00000000-0000-0000-0000-000000000000" :throw-validation-errors] true)) context (api/create-context "00000000-0000-0000-0000-000000000000")] {:file file :store store :context context})) @@ -89,9 +90,11 @@ ^js page2 (aget pages 1)] (t/is (instance? js/Promise (.openPage context page2 true))))) -(t/deftest test-open-page-invalid-arg-returns-nil +(t/deftest test-open-page-invalid-arg-throws + ;; With throwValidationErrors enabled an invalid argument surfaces as an + ;; exception instead of being silently logged. (let [^js context (:context (setup))] - (t/is (nil? (.openPage context "not-a-page"))))) + (t/is (thrown? js/Error (.openPage context "not-a-page"))))) (t/deftest test-open-page-resolves-when-page-changes (t/async done diff --git a/plugins/apps/poc-state-plugin/src/app/app.component.ts b/plugins/apps/poc-state-plugin/src/app/app.component.ts index 0eae67b448..7a293a1210 100644 --- a/plugins/apps/poc-state-plugin/src/app/app.component.ts +++ b/plugins/apps/poc-state-plugin/src/app/app.component.ts @@ -45,6 +45,13 @@ import type { Shape } from '@penpot/plugin-types'; +