Merge remote-tracking branch 'origin/main' into staging

This commit is contained in:
Andrey Antukh 2026-04-21 19:22:50 +02:00
commit 2bbd63287f
7 changed files with 260 additions and 136 deletions

View File

@ -428,52 +428,61 @@
(ex/print-throwable instance :prefix "Server Error"))
(st/async-emit! (rt/assign-exception error)))
(defn- from-extension?
"True when the error stack trace originates from a browser extension."
[cause]
(let [stack (.-stack cause)]
(and (string? stack)
(or (str/includes? stack "chrome-extension://")
(str/includes? stack "moz-extension://")))))
(defn- from-posthog?
"True when the error stack trace originates from PostHog analytics."
[cause]
(let [stack (.-stack cause)]
(and (string? stack)
(str/includes? stack "posthog"))))
(defn is-ignorable-exception?
"True when the error is known to be harmless (browser extensions, analytics,
React/extension DOM conflicts, etc.) and should NOT be surfaced to the user."
[cause]
(let [message (ex-message cause)]
(or (from-extension? cause)
(from-posthog? cause)
(= message "Possible side-effect in debug-evaluate")
(= message "Unexpected end of input")
(str/starts-with? message "invalid props on component")
(str/starts-with? message "Unexpected token ")
;; Native AbortError DOMException: raised when an in-flight
;; HTTP fetch is cancelled via AbortController (e.g. by an
;; RxJS unsubscription / take-until chain). These are
;; handled gracefully inside app.util.http/fetch and must NOT
;; be surfaced as application errors.
(= (.-name ^js cause) "AbortError")
;; Zone.js (injected by browser extensions such as Angular
;; DevTools) wraps event listeners and assigns a custom
;; .toString to its wrapper functions using
;; Object.defineProperty. When the wrapper was previously
;; defined with {writable: false}, a subsequent plain assignment
;; in strict mode (our libs.js uses "use strict") throws this
;; TypeError. This is a known Zone.js / browser-extension
;; incompatibility and is NOT a Penpot bug.
(str/starts-with? message "Cannot assign to read only property 'toString'")
;; NotFoundError DOMException: "Failed to execute
;; 'removeChild' on 'Node'" — Thrown by React's commit
;; phase when the DOM tree has been modified externally
;; (typically by browser extensions like Grammarly,
;; LastPass, translation tools, or ad blockers that
;; inject/remove nodes). The entire stack trace is inside
;; React internals (libs.js) with no application code,
;; so there is nothing actionable on our side. React's
;; error boundary already handles recovery.
(and (= (.-name ^js cause) "NotFoundError")
(str/includes? message "removeChild")))))
(defonce uncaught-error-handler
(letfn [(from-extension? [cause]
(let [stack (.-stack cause)]
(and (string? stack)
(or (str/includes? stack "chrome-extension://")
(str/includes? stack "moz-extension://")))))
(from-posthog? [cause]
(let [stack (.-stack cause)]
(and (string? stack)
(str/includes? stack "posthog"))))
;; Check if the error is marked as originating from plugin code.
;; The plugin runtime tracks plugin errors in a WeakMap, which works
;; even in SES hardened environments where error objects may be frozen.
(from-plugin? [cause]
(try
(is-plugin-error? cause)
(catch :default _
false)))
(is-ignorable-exception? [cause]
(let [message (ex-message cause)]
(or (from-extension? cause)
(from-posthog? cause)
(= message "Possible side-effect in debug-evaluate")
(= message "Unexpected end of input")
(str/starts-with? message "invalid props on component")
(str/starts-with? message "Unexpected token ")
;; Native AbortError DOMException: raised when an in-flight
;; HTTP fetch is cancelled via AbortController (e.g. by an
;; RxJS unsubscription / take-until chain). These are
;; handled gracefully inside app.util.http/fetch and must NOT
;; be surfaced as application errors.
(= (.-name ^js cause) "AbortError")
;; Zone.js (injected by browser extensions such as Angular
;; DevTools) wraps event listeners and assigns a custom
;; .toString to its wrapper functions using
;; Object.defineProperty. When the wrapper was previously
;; defined with {writable: false}, a subsequent plain assignment
;; in strict mode (our libs.js uses "use strict") throws this
;; TypeError. This is a known Zone.js / browser-extension
;; incompatibility and is NOT a Penpot bug.
(str/starts-with? message "Cannot assign to read only property 'toString'"))))
(on-unhandled-error [event]
(letfn [(on-unhandled-error [event]
(.preventDefault ^js event)
(when-let [cause (unchecked-get event "error")]
(cond

View File

@ -265,54 +265,68 @@
prev-transforms (mf/use-var nil)]
(mf/with-effect [add-children]
(ts/raf
#(doseq [{:keys [shape]} add-children-prev]
(let [shape-node (get-shape-node shape)
mirror-node (dom/query (dm/fmt ".mirror-shape[href='#shape-%'" shape))]
(when mirror-node (.remove mirror-node))
(dom/remove-attribute! (dom/get-parent shape-node) "display"))))
(let [raf-id1
(ts/raf
#(doseq [{:keys [shape]} add-children-prev]
(let [shape-node (get-shape-node shape)
mirror-node (dom/query (dm/fmt ".mirror-shape[href='#shape-%'" shape))]
(when mirror-node (.remove mirror-node))
(when-let [parent (some-> shape-node dom/get-parent)]
(dom/remove-attribute! parent "display")))))
(ts/raf
#(doseq [{:keys [frame shape]} add-children]
(let [frame-node (get-shape-node frame)
shape-node (get-shape-node shape)
raf-id2
(ts/raf
#(doseq [{:keys [frame shape]} add-children]
(let [frame-node (get-shape-node frame)
shape-node (get-shape-node shape)]
(when (and (some? frame-node) (some? shape-node))
(let [clip-id
(-> (dom/query frame-node ":scope > defs > .frame-clip-def")
(dom/get-attribute "id"))
clip-id
(-> (dom/query frame-node ":scope > defs > .frame-clip-def")
(dom/get-attribute "id"))
use-node
(dom/create-element "http://www.w3.org/2000/svg" "use")
use-node
(dom/create-element "http://www.w3.org/2000/svg" "use")
contents-node
(or (dom/query frame-node ".frame-children") frame-node)]
contents-node
(or (dom/query frame-node ".frame-children") frame-node)]
(dom/set-attribute! use-node "href" (dm/fmt "#shape-%" shape))
(dom/set-attribute! use-node "clip-path" (dm/fmt "url(#%)" clip-id))
(dom/add-class! use-node "mirror-shape")
(dom/append-child! contents-node use-node)
(dom/set-attribute! (dom/get-parent shape-node) "display" "none")))))
(dom/set-attribute! use-node "href" (dm/fmt "#shape-%" shape))
(dom/set-attribute! use-node "clip-path" (dm/fmt "url(#%)" clip-id))
(dom/add-class! use-node "mirror-shape")
(dom/append-child! contents-node use-node)
(dom/set-attribute! (dom/get-parent shape-node) "display" "none"))))))]
(fn []
(js/cancelAnimationFrame raf-id1)
(js/cancelAnimationFrame raf-id2))))
(mf/with-effect [transforms]
(let [curr-shapes-set (into #{} (map :id) shapes)
prev-shapes-set (into #{} (map :id) @prev-shapes)
new-shapes (->> shapes (remove #(contains? prev-shapes-set (:id %))))
removed-shapes (->> @prev-shapes (remove #(contains? curr-shapes-set (:id %))))]
removed-shapes (->> @prev-shapes (remove #(contains? curr-shapes-set (:id %))))
;; NOTE: we schedule the dom modifications to be executed
;; asynchronously for avoid component flickering when react18
;; is used.
;; NOTE: we schedule the dom modifications to be executed
;; asynchronously for avoid component flickering when react18
;; is used.
(when (d/not-empty? new-shapes)
(ts/raf #(start-transform! node new-shapes)))
raf-id1
(when (d/not-empty? new-shapes)
(ts/raf #(start-transform! node new-shapes)))
(when (d/not-empty? shapes)
(ts/raf #(update-transform! node shapes transforms modifiers)))
raf-id2
(when (d/not-empty? shapes)
(ts/raf #(update-transform! node shapes transforms modifiers)))
(when (d/not-empty? removed-shapes)
(ts/raf #(remove-transform! node removed-shapes))))
raf-id3
(when (d/not-empty? removed-shapes)
(ts/raf #(remove-transform! node removed-shapes)))]
(reset! prev-modifiers modifiers)
(reset! prev-transforms transforms)
(reset! prev-shapes shapes))))
(reset! prev-modifiers modifiers)
(reset! prev-transforms transforms)
(reset! prev-shapes shapes)
(fn []
(when raf-id1 (js/cancelAnimationFrame raf-id1))
(when raf-id2 (js/cancelAnimationFrame raf-id2))
(when raf-id3 (js/cancelAnimationFrame raf-id3)))))))

View File

@ -244,7 +244,11 @@
;; afterwards, in the next render cycle.
(dom/append-child! item-el counter-el)
(dnd/set-drag-image! event item-el (:x offset) (:y offset))
(ts/raf #(.removeChild ^js item-el counter-el))))
;; Guard against race condition: if the user navigates away
;; before the RAF fires, item-el may have been unmounted and
;; counter-el is no longer a child — removeChild would throw.
(ts/raf #(when (dom/child? counter-el item-el)
(dom/remove-child! item-el counter-el)))))
(defn on-asset-drag-start
[event file-id asset selected item-ref asset-type on-drag-start]

View File

@ -0,0 +1,95 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns frontend-tests.errors-test
(:require
[app.main.errors :as errors]
[cljs.test :as t :include-macros true]))
(defn- make-error
"Create a JS Error-like object with the given name, message, and optional stack."
[error-name message & {:keys [stack] :or {stack ""}}]
(let [err (js/Error. message)]
(set! (.-name err) error-name)
(when (some? stack)
(set! (.-stack err) stack))
err))
;; ---------------------------------------------------------------------------
;; is-ignorable-exception? tests
;; ---------------------------------------------------------------------------
(t/deftest test-ignorable-chrome-extension
(t/testing "Errors from Chrome extensions are ignorable"
(let [cause (make-error "Error" "some error"
:stack "Error: some error\n at chrome-extension://abc123/content.js:1:1")]
(t/is (true? (errors/is-ignorable-exception? cause))))))
(t/deftest test-ignorable-moz-extension
(t/testing "Errors from Firefox extensions are ignorable"
(let [cause (make-error "Error" "some error"
:stack "Error: some error\n at moz-extension://abc123/content.js:1:1")]
(t/is (true? (errors/is-ignorable-exception? cause))))))
(t/deftest test-ignorable-posthog
(t/testing "Errors from PostHog are ignorable"
(let [cause (make-error "Error" "some error"
:stack "Error: some error\n at https://app.posthog.com/static/array.js:1:1")]
(t/is (true? (errors/is-ignorable-exception? cause))))))
(t/deftest test-ignorable-debug-evaluate
(t/testing "Debug-evaluate side-effect errors are ignorable"
(let [cause (make-error "Error" "Possible side-effect in debug-evaluate")]
(t/is (true? (errors/is-ignorable-exception? cause))))))
(t/deftest test-ignorable-unexpected-end-of-input
(t/testing "Unexpected end of input errors are ignorable"
(let [cause (make-error "SyntaxError" "Unexpected end of input")]
(t/is (true? (errors/is-ignorable-exception? cause))))))
(t/deftest test-ignorable-invalid-props
(t/testing "Invalid React props errors are ignorable"
(let [cause (make-error "Error" "invalid props on component Foo")]
(t/is (true? (errors/is-ignorable-exception? cause))))))
(t/deftest test-ignorable-unexpected-token
(t/testing "Unexpected token errors are ignorable"
(let [cause (make-error "SyntaxError" "Unexpected token <")]
(t/is (true? (errors/is-ignorable-exception? cause))))))
(t/deftest test-ignorable-abort-error
(t/testing "AbortError DOMException is ignorable"
(let [cause (make-error "AbortError" "The operation was aborted")]
(t/is (true? (errors/is-ignorable-exception? cause))))))
(t/deftest test-ignorable-zone-js-tostring
(t/testing "Zone.js toString read-only property error is ignorable"
(let [cause (make-error "TypeError"
"Cannot assign to read only property 'toString' of function 'function () { [native code] }'")]
(t/is (true? (errors/is-ignorable-exception? cause))))))
(t/deftest test-ignorable-not-found-error-remove-child
(t/testing "NotFoundError with removeChild message is ignorable"
(let [cause (make-error "NotFoundError"
"Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node."
:stack "NotFoundError: Failed to execute 'removeChild'\n at zLe (libs.js:1:1)")]
(t/is (true? (errors/is-ignorable-exception? cause))))))
(t/deftest test-not-ignorable-not-found-error-other
(t/testing "NotFoundError without removeChild is NOT ignorable"
(let [cause (make-error "NotFoundError"
"Failed to execute 'insertBefore' on 'Node': something else")]
(t/is (false? (errors/is-ignorable-exception? cause))))))
(t/deftest test-not-ignorable-regular-error
(t/testing "Regular application errors are NOT ignorable"
(let [cause (make-error "Error" "Cannot read property 'x' of undefined")]
(t/is (false? (errors/is-ignorable-exception? cause))))))
(t/deftest test-not-ignorable-type-error
(t/testing "Regular TypeError is NOT ignorable"
(let [cause (make-error "TypeError" "undefined is not a function")]
(t/is (false? (errors/is-ignorable-exception? cause))))))

View File

@ -9,6 +9,7 @@
[frontend-tests.data.workspace-media-test]
[frontend-tests.data.workspace-texts-test]
[frontend-tests.data.workspace-thumbnails-test]
[frontend-tests.errors-test]
[frontend-tests.helpers-shapes-test]
[frontend-tests.logic.comp-remove-swap-slots-test]
[frontend-tests.logic.components-and-tokens]
@ -44,6 +45,7 @@
(t/run-tests
'frontend-tests.basic-shapes-test
'frontend-tests.data.repo-test
'frontend-tests.errors-test
'frontend-tests.main-errors-test
'frontend-tests.data.uploads-test
'frontend-tests.data.viewer-test

View File

@ -19,6 +19,6 @@
"@github/copilot": "^1.0.21",
"@types/node": "^25.5.2",
"esbuild": "^0.28.0",
"opencode-ai": "^1.4.3"
"opencode-ai": "^1.14.19"
}
}

106
pnpm-lock.yaml generated
View File

@ -18,8 +18,8 @@ importers:
specifier: ^0.28.0
version: 0.28.0
opencode-ai:
specifier: ^1.4.3
version: 1.4.3
specifier: ^1.14.19
version: 1.14.19
packages:
@ -227,67 +227,67 @@ packages:
engines: {node: '>=18'}
hasBin: true
opencode-ai@1.4.3:
resolution: {integrity: sha512-WwCSrLgJiS+sLIWoi9pa62vAw3l6VI3a+ShhjDDMUJBBG2FxU18xEhk8xhEedLMKyHo1p0nwD41+iKZ1y+rdAw==}
opencode-ai@1.14.19:
resolution: {integrity: sha512-67h56qYcJivd2U9VK8LJvyMBCc3ZE3HcJ/qL4YtaidSnjEumy4SxO+HHlDISsLase7TUQ0w1nULOAibdZxGzbQ==}
hasBin: true
opencode-darwin-arm64@1.4.3:
resolution: {integrity: sha512-d/MT28Is5yhdFY+36AqKc5r31zx8lXTQIYblfn5R8kdhamXijZVGdD0pHl3eJc1ZolUHNwzg2B+IqV22uyU9GQ==}
opencode-darwin-arm64@1.14.19:
resolution: {integrity: sha512-gjJ97dTiBbCas3Y7K6KQMQm5/KNIBOM9+10KZHqNr2bQfn0N09O977ZkXoX6IVBBE2632Ahaa71d4pzLKN9ULw==}
cpu: [arm64]
os: [darwin]
opencode-darwin-x64-baseline@1.4.3:
resolution: {integrity: sha512-WTqf7WBNRZcv6pClqnN4F7X/T/osgcPGikNHkHUSLszKWg9flqz7Z68kHR4i9ae8Bn3ke9MQRgzRdOt2PgLL0w==}
opencode-darwin-x64-baseline@1.14.19:
resolution: {integrity: sha512-UuwLpa511h7qQ+rTmOUmsHch/N4NBQT64dzg+iLWnR5yoR/81inENpbwxiS7hXpVdzCUGo/YnxI1u6SIBoMlTQ==}
cpu: [x64]
os: [darwin]
opencode-darwin-x64@1.4.3:
resolution: {integrity: sha512-8FUHeybVmaCYt4S2YmWcf32o/xa/ahCfI258bpWssrzs7Xg51JgUB/Csoble0I1mH7RpW39SKy/hHUtHGuJfJg==}
opencode-darwin-x64@1.14.19:
resolution: {integrity: sha512-uksrjOtWI7Ob5JvjZBSsrKuy3JVF9d89oYZYfWS5m8ordNyv1Nob39MXJXizv85ozsXjSb0rqjpJurnJw8K+tQ==}
cpu: [x64]
os: [darwin]
opencode-linux-arm64-musl@1.4.3:
resolution: {integrity: sha512-3Ej2klaep8+fxcc44UyEuRpb/UFiNkwfzIDLIST83hFUtjzprjpTRqg6zHmOfzyfjNAaNpB4VZw6e9y3mGBpiQ==}
opencode-linux-arm64-musl@1.14.19:
resolution: {integrity: sha512-R1BJuBGWHfBxfKvIA/Hb4nhYaJgCKl1B+mAGNydu+z0CLtGtwU8r+kQWF/G2N0y8Vx6Y6DRfJiv1X0eZEfD1BQ==}
cpu: [arm64]
os: [linux]
opencode-linux-arm64@1.4.3:
resolution: {integrity: sha512-9jpVSOEF7TX3gPPAHVAsBT9XEO3LgYafI+IUmOzbBB9CDiVVNJw6JmEffmSpSxY4nkAh322xnMbNjVGEyXQBRA==}
opencode-linux-arm64@1.14.19:
resolution: {integrity: sha512-Bo+aZOppLF366mgGfK0CnIcAVy1EmsrBv93eot1CmPSN1oeud07GpGdn3Bjl5f6KuBx1io6JsvjQyHno+MH5AA==}
cpu: [arm64]
os: [linux]
opencode-linux-x64-baseline-musl@1.4.3:
resolution: {integrity: sha512-aned/3FQTHXXQv2PPKDprJwQaQkoadriQ6AByGhRl6/bHhSkhkiVl6cHHvYMKxYEwN4bVOydWhasfgm/xru/xw==}
opencode-linux-x64-baseline-musl@1.14.19:
resolution: {integrity: sha512-+K8MuGoHugtUec4P/nKcTwZFUipHfW7oPpwlIoPiAQou3bNFTzzP6rslbzzNwjXlQRsUw9GAtuIPDOCL6CkgDg==}
cpu: [x64]
os: [linux]
opencode-linux-x64-baseline@1.4.3:
resolution: {integrity: sha512-HpzdgYaI90qqt0WokcyBhadgFQ0EYMhq4TZ4EcaSPuZTssS2Drb6kp70Si54uOJL/MUAdc9+E0BYYIAdOJ6h1g==}
opencode-linux-x64-baseline@1.14.19:
resolution: {integrity: sha512-KuvITzg4iK0hdIjpNZepwu3bLZ/dUZDI6BwCoV4w//VEP1j3UfDyeS3vWghKcQLd2T1+yybuEMM/3RXcwm/lGQ==}
cpu: [x64]
os: [linux]
opencode-linux-x64-musl@1.4.3:
resolution: {integrity: sha512-ibUevyDxVrwkp6FWu8UBCBsrzlKDT/uEug2NHCKaHIwo9uwVf5zsL/0ueHYqmH14SHK+M6wzWewYk6WuW9f0zQ==}
opencode-linux-x64-musl@1.14.19:
resolution: {integrity: sha512-0qe2+X76UJdrCdhdlJyfubMC4tveHAVxjSmPq7g9Zm95heBeJdcQDCLeyQk/lGgeXgsZzVPfLmyTWNtBvCZYFQ==}
cpu: [x64]
os: [linux]
opencode-linux-x64@1.4.3:
resolution: {integrity: sha512-RS6TsDqTUrW5sefxD1KD9Xy9mSYGXAlr2DlGrdi8vNm9e/Bt4r4u557VB7f/Uj2CxTt2Gf7OWl08ZoPlxMJ5Gg==}
opencode-linux-x64@1.14.19:
resolution: {integrity: sha512-2GljfL7BeG4xALBJVRwaAGCM/dzYF5aQf6bfLTKsQIl6QpLUguYSF+fkStBHLeehyqbDP5MtiEEuXjC0+mecjA==}
cpu: [x64]
os: [linux]
opencode-windows-arm64@1.4.3:
resolution: {integrity: sha512-2ViH17WpIQbRVfQaOBMi49pu73gqTQYT/4/WxFjShmRagX40/KkG18fhvyDAZrBKfkhPtdwgFsFxMSYP9F6QCQ==}
opencode-windows-arm64@1.14.19:
resolution: {integrity: sha512-8/vRHe5tHexikfPceLmpjsQiEhuDTOSCSlEmP4s0Yq3UAkVaDAxpiWq7Bx4g8hjr1gzfXD9vbjV+WHq/BtMC/w==}
cpu: [arm64]
os: [win32]
opencode-windows-x64-baseline@1.4.3:
resolution: {integrity: sha512-SWYDli9SAKQd/pS/hVfuq1KEsc+gnAJdv+YtBmxaHOw57y0euqLwbGFUYFq78GAMGt/RnTYWZIEUbRK/ZiX3UA==}
opencode-windows-x64-baseline@1.14.19:
resolution: {integrity: sha512-Z8imEJK/srE/r1fr7oNLvpLTeRJQyuL7vsbXvCt3T7j2Ew9BOZ7RuYa8EE0R6bNqQ+MLhBGPiAG7NWc++MgK8Q==}
cpu: [x64]
os: [win32]
opencode-windows-x64@1.4.3:
resolution: {integrity: sha512-UxmKDIw3t4XHST6JSUWHmSrCGIEK1LRTAOpO82HBC3XkIjH78gVIeauRR6RULjWAApmy9I1C3TukO2sDUi7Gvw==}
opencode-windows-x64@1.14.19:
resolution: {integrity: sha512-/TqGN91WiUzx7IPMPwmpMIzRixi5TMjaBcC9FH1TgD7DCqKnP6pokvu+ak0C9xwA4wKorE9PoZzeRt/+c3rDCQ==}
cpu: [x64]
os: [win32]
@ -434,55 +434,55 @@ snapshots:
'@esbuild/win32-ia32': 0.28.0
'@esbuild/win32-x64': 0.28.0
opencode-ai@1.4.3:
opencode-ai@1.14.19:
optionalDependencies:
opencode-darwin-arm64: 1.4.3
opencode-darwin-x64: 1.4.3
opencode-darwin-x64-baseline: 1.4.3
opencode-linux-arm64: 1.4.3
opencode-linux-arm64-musl: 1.4.3
opencode-linux-x64: 1.4.3
opencode-linux-x64-baseline: 1.4.3
opencode-linux-x64-baseline-musl: 1.4.3
opencode-linux-x64-musl: 1.4.3
opencode-windows-arm64: 1.4.3
opencode-windows-x64: 1.4.3
opencode-windows-x64-baseline: 1.4.3
opencode-darwin-arm64: 1.14.19
opencode-darwin-x64: 1.14.19
opencode-darwin-x64-baseline: 1.14.19
opencode-linux-arm64: 1.14.19
opencode-linux-arm64-musl: 1.14.19
opencode-linux-x64: 1.14.19
opencode-linux-x64-baseline: 1.14.19
opencode-linux-x64-baseline-musl: 1.14.19
opencode-linux-x64-musl: 1.14.19
opencode-windows-arm64: 1.14.19
opencode-windows-x64: 1.14.19
opencode-windows-x64-baseline: 1.14.19
opencode-darwin-arm64@1.4.3:
opencode-darwin-arm64@1.14.19:
optional: true
opencode-darwin-x64-baseline@1.4.3:
opencode-darwin-x64-baseline@1.14.19:
optional: true
opencode-darwin-x64@1.4.3:
opencode-darwin-x64@1.14.19:
optional: true
opencode-linux-arm64-musl@1.4.3:
opencode-linux-arm64-musl@1.14.19:
optional: true
opencode-linux-arm64@1.4.3:
opencode-linux-arm64@1.14.19:
optional: true
opencode-linux-x64-baseline-musl@1.4.3:
opencode-linux-x64-baseline-musl@1.14.19:
optional: true
opencode-linux-x64-baseline@1.4.3:
opencode-linux-x64-baseline@1.14.19:
optional: true
opencode-linux-x64-musl@1.4.3:
opencode-linux-x64-musl@1.14.19:
optional: true
opencode-linux-x64@1.4.3:
opencode-linux-x64@1.14.19:
optional: true
opencode-windows-arm64@1.4.3:
opencode-windows-arm64@1.14.19:
optional: true
opencode-windows-x64-baseline@1.4.3:
opencode-windows-x64-baseline@1.14.19:
optional: true
opencode-windows-x64@1.4.3:
opencode-windows-x64@1.14.19:
optional: true
undici-types@7.18.2: {}