diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn deleted file mode 100644 index 1b91e14..0000000 --- a/.clj-kondo/config.edn +++ /dev/null @@ -1,101 +0,0 @@ -{:lint-as - {promesa.core/let clojure.core/let - promesa.core/->> clojure.core/->> - promesa.core/-> clojure.core/-> - promesa.exec.csp/go-loop clojure.core/loop - promesa.util/with-open clojure.core/with-open - app.common.schema.generators/let clojure.core/let - app.common.data/export clojure.core/def - app.common.data.macros/get-in clojure.core/get-in - app.common.data.macros/with-open clojure.core/with-open - app.common.data.macros/select-keys clojure.core/select-keys - app.common.logging/with-context clojure.core/do} - - :hooks - {:analyze-call - {app.common.data.macros/export hooks.export/export - app.util.services/defmethod hooks.export/service-defmethod - app.common.record/defrecord hooks.export/penpot-defrecord - app.db/with-atomic hooks.export/penpot-with-atomic - potok.v2.core/reify hooks.export/potok-reify - rumext.v2/fnc hooks.export/rumext-fnc - rumext.v2/defc hooks.export/rumext-defc - rumext.v2/lazy-component hooks.export/rumext-lazycomponent - shadow.lazy/loadable hooks.export/rumext-lazycomponent - }} - - :output - {:exclude-files - ["data_readers.clj" - "src/app/util/perf.cljs" - "src/app/common/logging.cljc" - "src/app/common/exceptions.cljc" - "^(?:backend|frontend|exporter|common)/build.clj" - "^(?:backend|frontend|exporter|common)/deps.edn" - "^(?:backend|frontend|exporter|common)/scripts/" - "^(?:backend|frontend|exporter|common)/dev/" - "^(?:backend|frontend|exporter|common)/test/"] - - :linter-name true} - - :linters - {:unsorted-required-namespaces - {:level :warning} - - :potok/reify-type - {:level :error} - - :redundant-primitive-coercion - {:level :off} - - :unused-excluded-var - {:level :off} - - :unresolved-excluded-var - {:level :off} - - :missing-protocol-method - {:level :off} - - :unresolved-namespace - {:level :warning - :exclude [data_readers]} - - :unused-value - {:level :off} - - :single-key-in - {:level :warning} - - :non-arg-vec-return-type-hint - {:level :off} - - :redundant-do - {:level :off} - - :redundant-ignore - {:level :off} - - :redundant-nested-call - {:level :off} - - :redundant-str-call - {:level :off} - - :earmuffed-var-not-dynamic - {:level :off} - - :dynamic-var-not-earmuffed - {:level :off} - - :type-mismatch - {:level :off} - - :used-underscored-binding - {:level :warning} - - :unused-binding - {:exclude-destructured-as true - :exclude-destructured-keys-in-fn-args false - } - }} diff --git a/.clj-kondo/hooks/export.clj b/.clj-kondo/hooks/export.clj deleted file mode 100644 index 50e617d..0000000 --- a/.clj-kondo/hooks/export.clj +++ /dev/null @@ -1,231 +0,0 @@ -(ns hooks.export - (:require [clj-kondo.hooks-api :as api])) - -(defn export - [{:keys [:node]}] - (let [[_ sname] (:children node) - result (api/list-node - [(api/token-node (symbol "def")) - (api/token-node (symbol (name (:value sname)))) - sname])] - {:node result})) - -(def registry (atom {})) - - -(defn potok-reify - [{:keys [:node :filename] :as params}] - (let [[rnode rtype & other] (:children node) - rsym (symbol (str "event-type-" (name (:k rtype)))) - reg (get @registry filename #{})] - (when-not (:namespaced? rtype) - (let [{:keys [:row :col]} (meta rtype)] - (api/reg-finding! {:message "ptk/reify type should be namespaced" - :type :potok/reify-type - :row row - :col col}))) - - (if (contains? reg rsym) - (let [{:keys [:row :col]} (meta rtype)] - (api/reg-finding! {:message (str "duplicate type: " (name (:k rtype))) - :type :potok/reify-type - :row row - :col col})) - (swap! registry update filename (fnil conj #{}) rsym)) - - (let [result (api/list-node - (into [(api/token-node (symbol "deftype")) - (api/token-node rsym) - (api/vector-node [])] - other))] - - ;; (prn (api/sexpr result)) - - {:node result}))) - -(defn penpot-with-atomic - [{:keys [node]}] - (let [[params & body] (rest (:children node))] - (if (api/vector-node? params) - (let [[sym val opts] (:children params)] - (when-not (and sym val) - (throw (ex-info "No sym and val provided" {}))) - {:node (api/list-node - (list* - (api/token-node 'let) - (api/vector-node [sym val]) - opts - body))}) - - {:node (api/list-node - (into [(api/token-node 'let) - (api/vector-node [params params])] - body))}))) - -(defn rumext-fnc - [{:keys [node]}] - (let [[cname mdata params & body] (rest (:children node)) - [params body] (if (api/vector-node? mdata) - [mdata (cons params body)] - [params body]) - - result (api/list-node - (into [(api/token-node 'fn) params] - (cons mdata body)))] - - {:node result})) - - -(defn- parse-defc - [{:keys [children] :as node}] - (let [args (rest children) - - [cname args] - (if (api/token-node? (first args)) - [(first args) (rest args)] - (throw (ex-info "unexpected1" {}))) - - [docs args] - (if (api/string-node? (first args)) - [(first args) (rest args)] - ["" args]) - - [mdata args] - (if (api/map-node? (first args)) - [(first args) (rest args)] - [(api/map-node []) args]) - - [params body] - (if (api/vector-node? (first args)) - [(first args) (rest args)] - (throw (ex-info "unexpected2" {})))] - - [cname docs mdata params body])) - -(defn rumext-defc - [{:keys [node]}] - (let [[cname docs mdata params body] (parse-defc node) - - param1 (first (:children params)) - paramN (rest (:children params)) - - param1 (if (api/map-node? param1) - (let [param1 (into {} (comp - (partition-all 2) - (map (fn [[k v]] - [(if (api/keyword-node? k) - (:k k) - k) - (if (api/vector-node? v) - (vec (:children v)) - v)]))) - (:children param1)) - - binding (:rest param1) - param1 (if binding - (if (contains? param1 :as) - (update param1 :keys (fnil conj []) binding) - (assoc param1 :as binding)) - param1)] - (->> (dissoc param1 :rest) - (mapcat (fn [[k v]] - [(if (keyword? k) - (api/keyword-node k) - k) - (if (vector? v) - (api/vector-node v) - v)])) - (api/map-node))) - param1) - - result (api/list-node - (into [(api/token-node 'defn) - cname - (api/vector-node (filter some? (cons param1 paramN)))] - (cons mdata body)))] - - ;; (prn (api/sexpr result)) - - {:node result})) - - -(defn rumext-lazycomponent - [{:keys [node]}] - (let [[cname mdata params & body] (rest (:children node)) - [params body] (if (api/vector-node? mdata) - [mdata (cons params body)] - [params body])] - (let [result (api/list-node [(api/token-node 'constantly) nil])] - ;; (prn (api/sexpr result)) - {:node result}))) - - -(defn penpot-defrecord - [{:keys [:node]}] - (let [[rnode rtype rparams & other] (:children node) - - nodes [(api/token-node (symbol "do")) - (api/list-node - (into [(api/token-node (symbol (name (:value rnode)))) rtype rparams] other)) - (api/list-node - [(api/token-node (symbol "defn")) - (api/token-node (symbol (str "pos->" (:string-value rtype)))) - (api/vector-node - (->> (:children rparams) - (mapv (fn [t] - (api/token-node (symbol (str "_" (:string-value t)))))))) - (api/token-node nil)])] - - result (api/list-node nodes)] - - ;; (prn "=====>" (into {} rparams)) - ;; (prn (api/sexpr result)) - {:node result})) - -(defn clojure-specify - [{:keys [:node]}] - (let [[rnode rtype & other] (:children node) - result (api/list-node - (into [(api/token-node (symbol "extend-type")) - (api/token-node (gensym (:string-value rtype)))] - other))] - {:node result})) - -(defn service-defmethod - [{:keys [:node]}] - (let [[rnode rtype ?meta & other] (:children node) - rsym (gensym (name (:k rtype))) - - [?docs other] (if (api/string-node? ?meta) - [?meta other] - [nil (cons ?meta other)]) - - [?meta other] (let [?meta (first other)] - (if (api/map-node? ?meta) - [?meta (rest other)] - [nil other])) - - nodes [(api/token-node (symbol "do")) - (api/list-node - [(api/token-node (symbol "declare")) - (api/token-node rsym)]) - - (when ?docs - (api/list-node - [(api/token-node (symbol "comment")) ?docs])) - - (when ?meta - (api/list-node - [(api/token-node (symbol "reset-meta!")) - (api/token-node rsym) - ?meta])) - (api/list-node - (into [(api/token-node (symbol "defmethod")) - (api/token-node rsym) - rtype] - other))] - result (api/list-node (filterv some? nodes))] - - ;; (prn "=====>" rtype) - ;; (prn (api/sexpr result)) - {:node result})) diff --git a/.devenv/shared/opencode.json b/.devenv/shared/opencode.json deleted file mode 100644 index 15d8f93..0000000 --- a/.devenv/shared/opencode.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "mcp": { - "playwright": { - "type": "local", - "command": ["npx", "@playwright/mcp@latest", "--cdp-endpoint=http://127.0.0.1:9222"], - "enabled": true - } - } -} diff --git a/.devenv/templates/opencode.json b/.devenv/templates/opencode.json deleted file mode 100644 index 4d2db43..0000000 --- a/.devenv/templates/opencode.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "mcp": { - "penpot": { - "type": "remote", - "url": "http://localhost:${PENPOT_MCP_PORT}/mcp", - "enabled": true - }, - "serena-devenv": { - "type": "remote", - "url": "http://localhost:${SERENA_MCP_PORT}/mcp", - "enabled": true - } - } -} diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index fa95b4c..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,156 +0,0 @@ -# AI AGENT GUIDE - -## HARD RULES (always apply — no exceptions) - -- **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 to surface the - agent's work (e.g. force-push after an amend), state this in the response and - wait for the user to push. Do not change the remote URL, do not switch SSH↔HTTPS. -- **Never amend a commit that has been pushed** unless the user explicitly asks. - If the user pushes, treat that commit as final from the agent's side. -- **Never edit `CHANGES.md` by hand** in commits or PRs. 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. -- **Never pipe test output directly to filters** (`| head`, `| tail`, `| grep`, etc.). - Always redirect to a file first: `command > /tmp/output.txt 2>&1`, then read/grep the file. - This prevents hiding test failures. See `mem:testing` for details. -- **Read `AGENTS.local.md` after this file whenever it exists.** It carries - the developer's steering for this project and overrides this file on prose style and - workflow, but not on project policy, where instructions here prevail. -- **Commit message body lines MUST wrap at ≤76 chars** (subject ≤70 chars) and - the commit MUST pass `./scripts/check-commit` with exit code 0 before you - consider it done. This is mechanically checked — do not eyeball it. -- **Read the workflow memory BEFORE the corresponding action**: - - Before `git commit` → `mem:workflow/creating-commits` (commit format, AI-assisted-by trailer) - - Before `gh issue create` → `mem:workflow/creating-issues` (title derivation, body template, Issue Type) - - Before `gh pr create` / `gh pr edit` → `mem:workflow/creating-prs` (title format, body structure, AI note) - Don't infer format from the title of a previous commit/issue/PR — the memory - is the source of truth. - -## CRITICAL: Read module memories BEFORE writing any code - -Do this **before planning, before coding, before touching any file**: - -1. Read `critical-info` (use `serena_read_memory critical-info` or read `.serena/memories/critical-info.md`). - It describes the project structure and tells you which modules exist. -2. From `critical-info`, identify which modules your task affects. -3. Read each affected module's **core memory** — the name is `/core` - (e.g. `frontend/core`, `backend/core`, `common/core`). -4. If the core memory references deeper `mem:` memories relevant to your task, read those too. - -**STOP: Do not proceed until you have read the core memory of every affected module.** -Skipping this step is the #1 cause of incorrect or incomplete work. - ---- - -## Auto-triggers - -- **Security advisory URL pasted** — When the user pastes a URL matching - `github.com/penpot/penpot/security/advisories/GHSA-*`, extract the GHSA ID - from the URL and run `python3 scripts/gh.py advisories ` to fetch - full advisory details before proceeding. -- **Issue or PR mentioned** — When the user mentions a penpot/penpot issue or - PR (URL like `github.com/penpot/penpot/issues/` / `.../pull/`, or a - bare `#` when context clearly refers to this repo), fetch details via CLI - instead of WebFetch: - - Issue → `gh issue view --repo penpot/penpot` (add `--comments` when - discussion context matters). - - Single PR → `gh pr view --repo penpot/penpot`. - - Multiple PRs (list, file, or milestone) → `python3 scripts/gh.py prs ...`. - Do this before proceeding. Only use WebFetch if the CLI fails. - -## Writing Rules - -Writing rules, from Orwell, 1946. These govern prose: docs, PR text, messages. Never touch code or technical terms; swap in everyday words only where precision survives. - -1. Never use a metaphor, simile or other figure of speech which you are used to seeing in print. -2. Never use a long word where a short one will do. -3. If it is possible to cut a word out, always cut it out. -4. Never use the passive where you can use the active. -5. Never use a foreign phrase, a scientific word or a jargon word if you can think of an everyday English equivalent. -6. Break any of these rules sooner than say anything outright barbarous. -Review every prose output against these rules before delivering. - ---- - -# Memory system - -Memories are the **primary project guidance** — not docs or readme files. -They are dense, agent-oriented notes: terse bullets, invariants, no prose. - -## Entry point - -Start at `critical-info` (the graph root). It describes the project structure, -module dependency graph, and references section-level core memories. - -## Progressive discovery model - -Memories form a **reference graph**, not a flat list: - -``` -critical-info ← read first (graph root) - └─
/core ← top-level memory per section (e.g. frontend/core, backend/core) - └─ ← focused memories (e.g. frontend/handling-errors-and-debugging) - └─ ... ← deeper memories as needed -``` - -When working on a task: -1. Read `critical-info` to identify which sections are affected. -2. Read the affected section's `core` memory for an overview. -3. Follow `mem:` references in the core memory to focused memories relevant to your task. -4. Continue following references deeper as needed. - -## Accessing memories - -- **If `serena_read_memory` / `serena_list_memories` tools are available**: use them. - `serena_read_memory` takes a memory name (e.g. `critical-info`, `frontend/core`). -- **If tools are NOT available**: read the filesystem directly. - Memory name `mem:foo/bar` maps to file `.serena/memories/foo/bar.md`. - -## Cross-reference convention - -Memories reference other memories with `mem:
/` inside backticks. -Example: `mem:common/changes-architecture`. -When you encounter a `mem:` reference relevant to your task, read that memory next. - -## Topic/folder organization - -Memories are grouped into folders that mirror project modules or topics: -`backend/`, `common/`, `frontend/`, `render-wasm/`, `exporter/`, `workflow/`, etc. -Each folder's top-level memory is `/core`. - ---- - -# Role: Senior Software Engineer - -You are a high-autonomy Senior Full-Stack Software Engineer. You have full -permission to navigate the codebase, modify files, and execute commands to -fulfill your tasks. Your goal is to solve complex technical tasks with high -precision while maintaining a strong focus on maintainability and performance. - -## Operational Guidelines - -1. Before writing code, describe your plan. If the task is complex, break it - down into atomic steps. -2. Be concise and autonomous. -3. Do **not** touch unrelated modules unless the task explicitly requires it. - ---- - -# Available Scripts & Tools - -## Native opencode Tools (callable directly by the LLM) - -- `paren-repair` — Fix mismatched delimiters + reformat Clojure files. Example: `paren-repair(files="src/foo.clj, src/bar.cljs")` -- `penpot-psql` — Execute SQL against the Penpot database. Example: `penpot-psql(sql="SELECT version();")` - -## Scripts (from repo root via `scripts/`) - -- `scripts/paren-repair` — Fix mismatched delimiters in Clojure/CLJS files + reformat with cljfmt. See `mem:scripts/paren-repair`. -- `scripts/psql` — Connect to the Penpot PostgreSQL database (wraps `psql` with env-var defaults). See `mem:scripts/psql`. -- `scripts/nrepl-eval.mjs` — Evaluate Clojure code via nREPL (backend + frontend). -- `scripts/check-commit` — Validate commit messages against Penpot's commit guidelines. -- `scripts/check-fmt-clj` — Check Clojure formatting without modifying files. -- `scripts/ci` — CI orchestration script for running lint, tests, and format checks across modules. See `mem:scripts/ci`. -- `scripts/gh.py` — Multi-purpose GitHub CLI helper. Subcommands: `issues` (list issues in a milestone), `prs` (fetch PR details), `advisories` (list/inspect security advisories). See `python3 scripts/gh.py --help`. - diff --git a/THANKYOU.md b/THANKYOU.md deleted file mode 100644 index 8651946..0000000 --- a/THANKYOU.md +++ /dev/null @@ -1,109 +0,0 @@ -# THANK YOU - -We want to thank to the amazing people that help us! Thank you! You're the best! - -Feel free you make a PR updating this file if you miss you in the -list. - -## Security - -* Noob Researcher -* Kirubakaran N -* Alisher (@7megaumka7) -* Husnain Iqbal (CEO OF ALPHA INFERNO PVT LTD) -* [Shiraz Ali Khan](https://www.linkedin.com/in/shiraz-ali-khan-1ba508180/) -* Vaibhav Shukla -* Hassan Ahmed (Alias Xen Lee) -* Michal Biesiada (@mbiesiad) - -## Internationalization - -* [00ff88](https://hosted.weblate.org/user/00ff88) -* [AhmadHB](https://hosted.weblate.org/user/AhmadHB) -* [Aimee](https://hosted.weblate.org/user/Aimee) -* [alejandro.alonso](https://hosted.weblate.org/user/alejandro.alonso) -* [alexpawlak](https://hosted.weblate.org/user/alexpawlak) -* [allytiago](https://hosted.weblate.org/user/allytiago) -* [alonso.torres](https://hosted.weblate.org/user/alonso.torres) -* [andres.moya](https://hosted.weblate.org/user/andres.moya) -* [antoniofsm](https://hosted.weblate.org/user/antoniofsm) -* [ascarida](https://hosted.weblate.org/user/ascarida) -* [Bechii](https://hosted.weblate.org/user/Bechii) -* [Beeby](https://hosted.weblate.org/user/Beeby) -* [bingling-sama](https://hosted.weblate.org/user/bingling-sama) -* [devadarta](https://hosted.weblate.org/user/devadarta) -* [diacritica](https://hosted.weblate.org/user/diacritica) -* [dundzys.vincas](https://hosted.weblate.org/user/dundzys.vincas) -* [Eranot](https://hosted.weblate.org/user/Eranot) -* [erral](https://hosted.weblate.org/user/erral) -* [ersen](https://hosted.weblate.org/user/ersen) -* [filipepessanha](https://hosted.weblate.org/user/filipepessanha) -* [fortx](https://hosted.weblate.org/user/fortx) -* [foxbit](https://hosted.weblate.org/user/foxbit) -* [georgelemon](https://hosted.weblate.org/user/georgelemon) -* [girafic](https://hosted.weblate.org/user/girafic) -* [gizemb](https://hosted.weblate.org/user/gizemb) -* [greench](https://hosted.weblate.org/user/greench) -* [guidimas](https://hosted.weblate.org/user/guidimas) -* [hfigueira_1](https://hosted.weblate.org/user/hfigueira_1) -* [hifiaz](https://hosted.weblate.org/user/hifiaz) -* [httpsterio](https://hosted.weblate.org/user/httpsterio) -* [humteus](https://hosted.weblate.org/user/humteus) -* [iblueer](https://hosted.weblate.org/user/iblueer) -* [insan](https://hosted.weblate.org/user/insan) -* [Iphi](https://hosted.weblate.org/user/Iphi) -* [iWangJiaxiang](https://hosted.weblate.org/user/iWangJiaxiang) -* [jancborchardt](https://hosted.weblate.org/user/jancborchardt) -* [jazz](https://hosted.weblate.org/user/jazz) -* [johnterroa](https://hosted.weblate.org/user/johnterroa) -* [jponsa](https://hosted.weblate.org/user/jponsa) -* [kapler](https://hosted.weblate.org/user/kapler) -* [kingu](https://hosted.weblate.org/user/kingu) -* [KnahkAmath](https://hosted.weblate.org/user/KnahkAmath) -* [laminne](https://hosted.weblate.org/user/laminne) -* [lenildoleite](https://hosted.weblate.org/user/lenildoleite) -* [liimee](https://hosted.weblate.org/user/liimee) -* [lixeix](https://hosted.weblate.org/user/lixeix) -* [locness3](https://hosted.weblate.org/user/locness3) -* [maiwann](https://hosted.weblate.org/user/maiwann) -* [MidooDj](https://hosted.weblate.org/user/MidooDj) -* [Mohamed_amine_gdoura](https://hosted.weblate.org/user/Mohamed_amine_gdoura) -* [myfunnyandy](https://hosted.weblate.org/user/myfunnyandy) -* [NampoinaRal](https://hosted.weblate.org/user/NampoinaRal) -* [nautilusx](https://hosted.weblate.org/user/nautilusx) -* [niwinz](https://hosted.weblate.org/user/niwinz) -* [pablo.alba](pablo.https://hosted.weblate.org/user/alba) -* [PhilippeAccorsi](https://hosted.weblate.org/user/PhilippeAccorsi) -* [rnarius](https://hosted.weblate.org/user/rnarius) -* [rnd](https://hosted.weblate.org/user/rnd) -* [RuanAragao](https://hosted.weblate.org/user/RuanAragao) -* [ruben](https://hosted.weblate.org/user/ruben) -* [semonxue](https://hosted.weblate.org/user/semonxue) -* [shahab](https://hosted.weblate.org/user/shahab) -* [shuaib85](https://hosted.weblate.org/user/shuaib85) -* [SiderealArt](https://hosted.weblate.org/user/SiderealArt) -* [swapnil.cx](swapnil.https://hosted.weblate.org/user/cx) -* [syuza](https://hosted.weblate.org/user/syuza) -* [th3ph4nt0m](https://hosted.weblate.org/user/th3ph4nt0m) -* [tiwb](https://hosted.weblate.org/user/tiwb) -* [tommi](https://hosted.weblate.org/user/tommi) -* [val](https://hosted.weblate.org/user/val) -* [vikt](https://hosted.weblate.org/user/vikt) -* [VinLin](https://hosted.weblate.org/user/VinLin) -* [vintprox](https://hosted.weblate.org/user/vintprox) -* [Voxybuns](https://hosted.weblate.org/user/Voxybuns) -* [winie](https://hosted.weblate.org/user/winie) -* [Yaron](https://hosted.weblate.org/user/Yaron) -* [yrd](https://hosted.weblate.org/user/yrd) -* [YukiYuigishi](https://hosted.weblate.org/user/YukiYuigishi) -* [zcraber](https://hosted.weblate.org/user/zcraber) - -## Libraries & templates - -* systxema -* plumilla -* victor crespo -* xtech -* candidexmedia -* merih güz -* klarr agency diff --git a/backend/resources/app/email/change-email/en.html b/backend/resources/app/email/change-email/en.html index 238f9f4..ddc43e4 100644 --- a/backend/resources/app/email/change-email/en.html +++ b/backend/resources/app/email/change-email/en.html @@ -57,9 +57,9 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + {{#isDebug}} {{/isDebug}} + @@ -34,26 +56,50 @@ {{# manifest}} - - + + + {{/manifest}} +
{{> ../public/images/sprites/symbol/icons.svg }} {{> ../public/images/sprites/symbol/cursors.svg }} {{> ../public/images/sprites/assets.svg }} +
{{# manifest}} - {{/manifest}} diff --git a/frontend/resources/templates/rasterizer.mustache b/frontend/resources/templates/rasterizer.mustache index 6a3d815..045f015 100644 --- a/frontend/resources/templates/rasterizer.mustache +++ b/frontend/resources/templates/rasterizer.mustache @@ -2,7 +2,7 @@ - Penpot - Rasterizer + BlueAI Board - Rasterizer - - diff --git a/mcp/packages/plugin/package.json b/mcp/packages/plugin/package.json deleted file mode 100644 index 33b37f2..0000000 --- a/mcp/packages/plugin/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "mcp-plugin", - "private": true, - "version": "1.0.0", - "type": "module", - "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", - "scripts": { - "start": "vite build --watch --config vite.config.ts", - "start:multi-user": "pnpm run start", - "build": "tsc && vite build --config vite.release.config.ts", - "test": "node --experimental-strip-types --test src/*.test.ts", - "types:check": "tsc --noEmit", - "clean": "rm -rf dist/" - }, - "dependencies": { - "@penpot/plugin-styles": "1.5.0", - "@penpot/plugin-types": "1.5.0" - }, - "devDependencies": { - "cross-env": "^10.1.0", - "typescript": "^6.0.3", - "vite": "^8.2.0", - "vite-live-preview": "^0.4.0" - } -} diff --git a/mcp/packages/plugin/public/icon.jpg b/mcp/packages/plugin/public/icon.jpg deleted file mode 100644 index 9df8dd2..0000000 Binary files a/mcp/packages/plugin/public/icon.jpg and /dev/null differ diff --git a/mcp/packages/plugin/public/manifest.json b/mcp/packages/plugin/public/manifest.json deleted file mode 100644 index aa97095..0000000 --- a/mcp/packages/plugin/public/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "Penpot MCP Plugin", - "code": "plugin.js", - "icon": "icon.jpg", - "version": 2, - "description": "This plugin enables interaction with the Penpot MCP server", - "permissions": ["content:read", "content:write", "library:read", "library:write", "comment:read", "comment:write"] -} diff --git a/mcp/packages/plugin/src/ErrorUtils.test.ts b/mcp/packages/plugin/src/ErrorUtils.test.ts deleted file mode 100644 index e88b444..0000000 --- a/mcp/packages/plugin/src/ErrorUtils.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import assert from "node:assert/strict"; -import test from "node:test"; -import { formatTaskError } from "./ErrorUtils.ts"; - -test("includes ClojureScript exception data in the task error message", () => { - const keyword = (name: string) => ({ name, fqn: name }); - const entries = [ - { key: keyword("type"), val: keyword("validation") }, - { key: keyword("code"), val: keyword("request-body-too-large") }, - { key: keyword("status"), val: 413 }, - ]; - const data = { - *[Symbol.iterator]() { - yield* entries; - }, - }; - const error = Object.assign(new Error("http error"), { data }); - - assert.equal(formatTaskError(error), "http error (type: validation, code: request-body-too-large, status: 413)"); -}); - -test("falls back to the printed representation when map internals are renamed by the Closure compiler", () => { - // In release builds MapEntry/Keyword field names are minified, so entry - // extraction finds nothing; only toString keeps the readable CLJS form. - const entries = [{ a7: { Eb: "type" }, gb: { Eb: "validation" } }]; - const data = { - *[Symbol.iterator]() { - yield* entries; - }, - toString: () => "{:type :validation, :code :request-body-too-large}", - }; - const error = Object.assign(new Error("http error"), { data }); - - assert.equal(formatTaskError(error), "http error ({:type :validation, :code :request-body-too-large})"); -}); - -test("formats plain object data and nested printable values", () => { - const data = { - status: 413, - uri: "https://example.test/api/export", - detail: { toString: () => "{:code :too-large}" }, - }; - const error = Object.assign(new Error("http error"), { data }); - - assert.equal( - formatTaskError(error), - "http error (status: 413, uri: https://example.test/api/export, detail: {:code :too-large})" - ); -}); - -test("keeps string values verbatim", () => { - const error = Object.assign(new Error("http error"), { data: { hint: ":not-a-keyword" } }); - - assert.equal(formatTaskError(error), "http error (hint: :not-a-keyword)"); -}); - -test("returns the original message when no structured data is available", () => { - assert.equal(formatTaskError(new Error("export timed out")), "export timed out"); -}); - -test("formats non-Error values", () => { - assert.equal(formatTaskError("connection closed"), "connection closed"); -}); diff --git a/mcp/packages/plugin/src/ErrorUtils.ts b/mcp/packages/plugin/src/ErrorUtils.ts deleted file mode 100644 index c898037..0000000 --- a/mcp/packages/plugin/src/ErrorUtils.ts +++ /dev/null @@ -1,145 +0,0 @@ -type ErrorWithData = Error & { - data?: unknown; -}; - -/** - * Produces a useful task error message, including structured ClojureScript exception data. - * - * @param error - the value thrown while handling a plugin task - * @returns A human-readable error message - */ -export function formatTaskError(error: unknown): string { - if (!(error instanceof Error)) { - return String(error); - } - - const details = formatErrorData((error as ErrorWithData).data); - return details ? `${error.message} (${details})` : error.message; -} - -/** - * Formats plain objects and iterable ClojureScript maps as key-value pairs. - * - * Falls back to the value's printed representation: in release builds the - * Closure compiler renames ClojureScript internals (MapEntry `key`/`val`, - * Keyword `fqn`), so entry extraction can come up empty even though `toString` - * still prints the data readably (e.g. `{:type :validation}`). - * - * @param data - structured exception data - * @returns Formatted exception details, or an empty string when unavailable - */ -function formatErrorData(data: unknown): string { - const entries = getEntries(data); - if (entries.length > 0) { - return entries.map(([key, value]) => `${formatKey(key)}: ${formatValue(value)}`).join(", "); - } - - return printedForm(data); -} - -/** - * Returns the value's own printed representation, or an empty string when it - * only has the default `Object.prototype.toString` one. - */ -function printedForm(value: unknown): string { - if (!value || typeof value !== "object") { - return ""; - } - - try { - const text = String(value); - return text === "[object Object]" ? "" : text; - } catch { - return ""; - } -} - -/** - * Extracts entries from JavaScript objects or ClojureScript map-like values. - */ -function getEntries(data: unknown): Array<[unknown, unknown]> { - if (!data || typeof data !== "object") { - return []; - } - - if (Symbol.iterator in data) { - try { - return Array.from(data as Iterable).flatMap((entry) => { - if (Array.isArray(entry) && entry.length >= 2) { - return [[entry[0], entry[1]]]; - } - - if (entry && typeof entry === "object") { - const mapEntry = entry as { key?: unknown; val?: unknown }; - if ("key" in mapEntry && "val" in mapEntry) { - return [[mapEntry.key, mapEntry.val]]; - } - - if (Symbol.iterator in entry) { - const pair = Array.from(entry as Iterable); - return pair.length >= 2 ? [[pair[0], pair[1]]] : []; - } - } - - return []; - }); - } catch { - // fall through to ordinary object properties - } - } - - return Object.entries(data); -} - -/** - * Formats ClojureScript keywords without their leading colon. - */ -function formatKey(key: unknown): string { - if (key && typeof key === "object") { - const keyword = key as { fqn?: unknown; name?: unknown }; - if (typeof keyword.fqn === "string") { - return keyword.fqn; - } - if (typeof keyword.name === "string") { - return keyword.name; - } - } - - return String(key).replace(/^:/, ""); -} - -/** - * Formats detail values while keeping nested data readable. - */ -function formatValue(value: unknown): string { - if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { - return String(value); - } - - if (value && typeof value === "object") { - const keyword = value as { fqn?: unknown; name?: unknown }; - if (typeof keyword.fqn === "string") { - return keyword.fqn; - } - if (typeof keyword.name === "string") { - return keyword.name; - } - - if (!Array.isArray(value)) { - const printed = printedForm(value); - if (printed) { - return printed.replace(/^:/, ""); - } - } - } - - if (value === null || value === undefined) { - return String(value); - } - - try { - return JSON.stringify(value); - } catch { - return String(value); - } -} diff --git a/mcp/packages/plugin/src/PenpotUtils.test.ts b/mcp/packages/plugin/src/PenpotUtils.test.ts deleted file mode 100644 index 1ed2d51..0000000 --- a/mcp/packages/plugin/src/PenpotUtils.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -import assert from "node:assert/strict"; -import test from "node:test"; -import { PenpotUtils } from "./PenpotUtils.ts"; - -// --------------------------------------------------------------------------- -// Minimal penpot global mock -// --------------------------------------------------------------------------- - -function makeVariantComponent(calls: string[]) { - return { - isVariant: () => true, - setVariantProperty: (pos: number, value: string) => { - calls.push(`setVariantProperty(${pos}, ${value})`); - }, - }; -} - -function makeMockPenpot(variantComps: ReturnType[]) { - const variants = { - renamePropertyCalls: [] as string[], - addPropertyCount: 0, - renameProperty(pos: number, name: string) { - this.renamePropertyCalls.push(`${pos}:${name}`); - }, - addProperty() { - this.addPropertyCount++; - }, - variantComponents() { - return variantComps; - }, - }; - - const container = { variants }; - - return { - mock: { - utils: { - types: { - isVariantComponent: (c: any) => c.isVariant(), - }, - }, - createVariantFromComponents: (_shapes: any[]) => container, - } as any, - variants, - container, - }; -} - -// --------------------------------------------------------------------------- -// Tests -// --------------------------------------------------------------------------- - -test("createVariantContainer — single property: renames Property 1 and sets values", () => { - const calls: string[] = []; - const comps = [makeVariantComponent(calls), makeVariantComponent(calls), makeVariantComponent(calls)]; - const { mock, variants } = makeMockPenpot(comps); - - (globalThis as any).penpot = mock; - - PenpotUtils.createVariantContainer([ - { shape: {} as any, properties: { Size: "Small" } }, - { shape: {} as any, properties: { Size: "Medium" } }, - { shape: {} as any, properties: { Size: "Large" } }, - ]); - - // renameProperty(0, "Size") — no addProperty since there's only one property - assert.deepEqual(variants.renamePropertyCalls, ["0:Size"]); - assert.equal(variants.addPropertyCount, 0); - - // each component gets setVariantProperty(0, value) - assert.deepEqual(calls, [ - "setVariantProperty(0, Small)", - "setVariantProperty(0, Medium)", - "setVariantProperty(0, Large)", - ]); -}); - -test("createVariantContainer — two properties: renames first, adds and renames second, sets all values", () => { - const calls: string[] = []; - const comps = [makeVariantComponent(calls), makeVariantComponent(calls)]; - const { mock, variants } = makeMockPenpot(comps); - - (globalThis as any).penpot = mock; - - PenpotUtils.createVariantContainer([ - { shape: {} as any, properties: { Size: "Small", State: "Default" } }, - { shape: {} as any, properties: { Size: "Large", State: "Hover" } }, - ]); - - // Property 1 renamed to Size; addProperty() called once; Property 2 renamed to State - assert.deepEqual(variants.renamePropertyCalls, ["0:Size", "1:State"]); - assert.equal(variants.addPropertyCount, 1); - - // comp 0: pos 0 = Small, pos 1 = Default; comp 1: pos 0 = Large, pos 1 = Hover - assert.deepEqual(calls, [ - "setVariantProperty(0, Small)", - "setVariantProperty(1, Default)", - "setVariantProperty(0, Large)", - "setVariantProperty(1, Hover)", - ]); -}); - -test("createVariantContainer — property name order follows first-seen order across components", () => { - const calls: string[] = []; - const comps = [makeVariantComponent(calls), makeVariantComponent(calls)]; - const { mock, variants } = makeMockPenpot(comps); - - (globalThis as any).penpot = mock; - - // "Color" appears first in comp[0]; "Size" appears first in comp[0] too - PenpotUtils.createVariantContainer([ - { shape: {} as any, properties: { Color: "Red", Size: "Small" } }, - { shape: {} as any, properties: { Color: "Blue", Size: "Large" } }, - ]); - - assert.deepEqual(variants.renamePropertyCalls, ["0:Color", "1:Size"]); - assert.deepEqual(calls, [ - "setVariantProperty(0, Red)", - "setVariantProperty(1, Small)", - "setVariantProperty(0, Blue)", - "setVariantProperty(1, Large)", - ]); -}); - -test("createVariantContainer — returns the container from createVariantFromComponents", () => { - const { mock, container } = makeMockPenpot([]); - (globalThis as any).penpot = mock; - - const result = PenpotUtils.createVariantContainer([{ shape: {} as any, properties: { X: "1" } }]); - - assert.equal(result, container); -}); - -// --------------------------------------------------------------------------- -// findShapes root handling -// (community report https://community.penpot.app/t/mcp-api-issue-list/10700 -// issue #15: including the matching root itself is a footgun — a predicate -// meant for descendants can accidentally select and mutate the container). -// --------------------------------------------------------------------------- - -function makeShapeTree() { - const childA = { name: "child-a", fills: [{}] }; - const childB = { name: "child-b", fills: [{}] }; - const root = { name: "root", fills: [{}], children: [childA, childB] }; - return { root, childA, childB }; -} - -test("findShapes — matches descendants of the given root", () => { - const { root, childA, childB } = makeShapeTree(); - - const result = PenpotUtils.findShapes((s: any) => s.fills.length > 0, root as any); - - assert.ok(result.includes(childA as any)); - assert.ok(result.includes(childB as any)); -}); - -test("findShapes — excludes the root shape itself from the results", () => { - const { root } = makeShapeTree(); - - const result = PenpotUtils.findShapes((s: any) => s.fills.length > 0, root as any); - - assert.ok(!result.includes(root as any)); -}); diff --git a/mcp/packages/plugin/src/PenpotUtils.ts b/mcp/packages/plugin/src/PenpotUtils.ts deleted file mode 100644 index f856f14..0000000 --- a/mcp/packages/plugin/src/PenpotUtils.ts +++ /dev/null @@ -1,632 +0,0 @@ -import type { - Board, - Bounds, - Fill, - FlexLayout, - GridLayout, - Page, - Rectangle, - Shape, - Text, - VariantContainer, -} from "@penpot/plugin-types"; - -export class PenpotUtils { - /** - * Generates an overview structure of the given shape, - * providing its id, name and type, and recursively its children's attributes. - * The `type` field indicates the type in the Penpot API. - * If the shape has a layout system (flex or grid), includes layout information. - * - * @param shape - The root shape to generate the structure from - * @param maxDepth - Optional maximum depth to traverse (leave undefined for unlimited) - * @returns An object representing the shape structure - */ - public static shapeStructure(shape: Shape, maxDepth: number | undefined = undefined): object { - let children = undefined; - if (maxDepth === undefined || maxDepth > 0) { - if ("children" in shape && shape.children) { - children = shape.children.map((child) => - this.shapeStructure(child, maxDepth === undefined ? undefined : maxDepth - 1) - ); - } - } - - const result: any = { - id: shape.id, - name: shape.name, - type: shape.type, - }; - - // add layout information if present - if ("flex" in shape && shape.flex) { - const flex: FlexLayout = shape.flex; - result.layout = { - type: "flex", - dir: flex.dir, - rowGap: flex.rowGap, - columnGap: flex.columnGap, - }; - } else if ("grid" in shape && shape.grid) { - const grid: GridLayout = shape.grid; - result.layout = { - type: "grid", - rows: grid.rows, - columns: grid.columns, - rowGap: grid.rowGap, - columnGap: grid.columnGap, - }; - } - - // add component instance information if present - if (shape.isComponentInstance()) { - result.componentInstance = {}; - const component = shape.component(); - if (component) { - result.componentInstance.componentId = component.id; - result.componentInstance.componentName = component.name; - const mainInstance = component.mainInstance(); - if (mainInstance) { - result.componentInstance.mainInstanceId = mainInstance.id; - } - } - } - - // finally, add children (last for more readable nesting order) - result.children = children; - - return result; - } - - /** - * Finds all shapes that matches the given predicate in the given shape tree. - * - * @param predicate - A function that takes a shape and returns true if it matches the criteria - * @param root - The root shape to start the search from (if null, searches all pages) - */ - public static findShapes(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape[] { - let result = new Array(); - - // Walk the descendants of the given shape, testing the predicate on each - // one. The starting shape is never tested itself: findShapes searches - // *within* a container, so a predicate meant for descendants cannot - // accidentally select (and later mutate) the container it was handed. - let walk = function (shape: Shape | null) { - if (!shape) { - return; - } - if ("children" in shape && shape.children) { - for (let child of shape.children) { - if (predicate(child)) { - result.push(child); - } - walk(child); - } - } - }; - - if (root === null) { - const pages = penpot.currentFile?.pages; - if (pages) { - for (let page of pages) { - walk(page.root); - } - } - } else { - walk(root); - } - return result; - } - - /** - * Finds the first shape that matches the given predicate in the given shape tree. - * - * @param predicate - A function that takes a shape and returns true if it matches the criteria - * @param root - The root shape to start the search from (if null, searches all pages) - */ - public static findShape(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape | null { - let find = function (shape: Shape | null): Shape | null { - if (!shape) { - return null; - } - if (predicate(shape)) { - return shape; - } - if ("children" in shape && shape.children) { - for (let child of shape.children) { - let result = find(child); - if (result) { - return result; - } - } - } - return null; - }; - - if (root === null) { - const pages = penpot.currentFile?.pages; - if (pages) { - for (let page of pages) { - let result = find(page.root); - if (result) { - return result; - } - } - } - return null; - } else { - return find(root); - } - } - - /** - * Finds a shape by its unique ID. - * - * @param id - The unique ID of the shape to find - * @returns The shape with the matching ID, or null if not found - */ - public static findShapeById(id: string): Shape | null { - return this.findShape((shape) => shape.id === id); - } - - public static findPage(predicate: (page: Page) => boolean): Page | null { - let page = penpot.currentFile!.pages.find(predicate); - return page || null; - } - - public static getPages(): { id: string; name: string }[] { - return penpot.currentFile!.pages.map((page) => ({ id: page.id, name: page.name })); - } - - public static getPageById(id: string): Page | null { - return this.findPage((page) => page.id === id); - } - - public static getPageByName(name: string): Page | null { - return this.findPage((page) => page.name.toLowerCase() === name.toLowerCase()); - } - - public static getPageForShape(shape: Shape): Page | null { - for (const page of penpot.currentFile!.pages) { - if (page.getShapeById(shape.id)) { - return page; - } - } - return null; - } - - public static generateCss(shape: Shape): string { - const page = this.getPageForShape(shape); - if (!page) { - throw new Error("Shape is not part of any page"); - } - penpot.openPage(page); - return penpot.generateStyle([shape], { type: "css", includeChildren: true }); - } - - /** - * Gets the actual rendering bounds of a shape. For most shapes, this is simply the `bounds` property. - * However, for Text shapes, the `bounds` may not reflect the true size of the rendered text content, - * so we use the `textBounds` property instead. - * - * @param shape - The shape to get the bounds for - */ - public static getBounds(shape: Shape): Bounds { - if (shape.type === "text") { - const text = shape as Text; - return text.textBounds; - } else { - return shape.bounds; - } - } - - /** - * Checks if a child shape is fully contained within its parent's bounds. - * Visual containment means all edges of the child are within the parent's bounding box. - * - * @param child - The child shape to check - * @param parent - The parent shape to check against - * @returns true if child is fully contained within parent bounds, false otherwise - */ - public static isContainedIn(child: Shape, parent: Shape): boolean { - const childBounds = this.getBounds(child); - const parentBounds = this.getBounds(parent); - return ( - childBounds.x >= parentBounds.x && - childBounds.y >= parentBounds.y && - childBounds.x + childBounds.width <= parentBounds.x + parentBounds.width && - childBounds.y + childBounds.height <= parentBounds.y + parentBounds.height - ); - } - - /** - * Sets the position of a shape relative to its parent's position. - * This is a convenience method since parentX and parentY are read-only properties. - * - * @param shape - The shape to position - * @param parentX - The desired X position relative to the parent - * @param parentY - The desired Y position relative to the parent - * @throws Error if the shape has no parent - */ - public static setParentXY(shape: Shape, parentX: number, parentY: number): void { - if (!shape.parent) { - throw new Error("Shape has no parent - cannot set parent-relative position"); - } - shape.x = shape.parent.x + parentX; - shape.y = shape.parent.y + parentY; - } - - /** - * Adds a flex layout to a container while preserving the visual order of existing children. - * Without this, adding a flex layout can arbitrarily reorder children. - * - * The method sorts children by their current position (x for "row", y for "column") before - * adding the layout, then reorders them to maintain that visual sequence. - * - * @param container - The container (board) to add the flex layout to - * @param dir - The layout direction: "row" for horizontal, "column" for vertical - * @returns The created FlexLayout instance - */ - public static addFlexLayout(container: Board, dir: "column" | "row"): FlexLayout { - // obtain children sorted by position (ascending) - const children = "children" in container && container.children ? [...container.children] : []; - const sortedChildren = children.sort((a, b) => (dir === "row" ? a.x - b.x : a.y - b.y)); - - // add the flex layout - const flexLayout = container.addFlexLayout(); - flexLayout.dir = dir; - - // reorder children to preserve visual order; since the children array is reversed - // relative to visual order for dir="column" or dir="row", we insert each child at - // index 0 in sorted order, which places the first (smallest position) at the highest - // index, making it appear first visually - for (const child of sortedChildren) { - child.setParentIndex(0); - } - - return flexLayout; - } - - /** - * Analyzes all descendants of a shape by applying an evaluator function to each. - * Only descendants for which the evaluator returns a non-null/non-undefined value are included in the result. - * This is a general-purpose utility for validation, analysis, or collecting corrector functions. - * - * @param root - The root shape whose descendants to analyze - * @param evaluator - Function called for each descendant with (root, descendant); return null/undefined to skip - * @param maxDepth - Optional maximum depth to traverse (undefined for unlimited) - * @returns Array of objects containing the shape and the evaluator's result - */ - public static analyzeDescendants( - root: Shape, - evaluator: (root: Shape, descendant: Shape) => T | null | undefined, - maxDepth: number | undefined = undefined - ): Array<{ shape: Shape; result: NonNullable }> { - const results: Array<{ shape: Shape; result: NonNullable }> = []; - - const traverse = (shape: Shape, currentDepth: number): void => { - const result = evaluator(root, shape); - if (result !== null && result !== undefined) { - results.push({ shape, result: result as NonNullable }); - } - - if (maxDepth === undefined || currentDepth < maxDepth) { - if ("children" in shape && shape.children) { - for (const child of shape.children) { - traverse(child, currentDepth + 1); - } - } - } - }; - - // Start traversal with root's children (not root itself) - if ("children" in root && root.children) { - for (const child of root.children) { - traverse(child, 1); - } - } - - return results; - } - - /** - * Decodes a base64 string to a Uint8Array. - * - * @param base64 - The base64-encoded string to decode - * @returns The decoded data as a Uint8Array - */ - public static base64ToByteArray(base64: string): Uint8Array { - const binary = atob(base64); - const bytes = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.charCodeAt(i); - } - return bytes; - } - - /** - * Imports an image from base64 data into the Penpot design as a Rectangle shape filled with the image. - * The rectangle has the image's original proportions by default. - * Optionally accepts position (x, y) and dimensions (width, height) parameters. - * If only one dimension is provided, the other is calculated to maintain the image's aspect ratio. - * - * This function is used internally by the ImportImageTool in the MCP server. - * - * @param base64 - The base64-encoded image data - * @param mimeType - The MIME type of the image (e.g., "image/png") - * @param name - The name to assign to the newly created rectangle shape - * @param x - The x-coordinate for positioning the rectangle (optional) - * @param y - The y-coordinate for positioning the rectangle (optional) - * @param width - The desired width of the rectangle (optional) - * @param height - The desired height of the rectangle (optional) - */ - public static async importImage( - base64: string, - mimeType: string, - name: string, - x: number | undefined, - y: number | undefined, - width: number | undefined, - height: number | undefined - ): Promise { - // convert base64 to Uint8Array - const bytes = PenpotUtils.base64ToByteArray(base64); - - // upload the image data to Penpot - const imageData = await penpot.uploadMediaData(name, bytes, mimeType); - - // create a rectangle shape - const rect = penpot.createRectangle(); - rect.name = name; - - // calculate dimensions - let rectWidth, rectHeight; - const hasWidth = width !== undefined; - const hasHeight = height !== undefined; - - if (hasWidth && hasHeight) { - // both width and height provided - use them directly - rectWidth = width; - rectHeight = height; - } else if (hasWidth) { - // only width provided - maintain aspect ratio - rectWidth = width; - rectHeight = rectWidth * (imageData.height / imageData.width); - } else if (hasHeight) { - // only height provided - maintain aspect ratio - rectHeight = height; - rectWidth = rectHeight * (imageData.width / imageData.height); - } else { - // neither provided - use original dimensions - rectWidth = imageData.width; - rectHeight = imageData.height; - } - - // set rectangle dimensions - rect.resize(rectWidth, rectHeight); - - // set position if provided - if (x !== undefined) { - rect.x = x; - } - if (y !== undefined) { - rect.y = y; - } - - // apply the image as a fill - rect.fills = [{ fillOpacity: 1, fillImage: imageData }]; - - return rect; - } - - /** - * Exports the given shape (or its fill) to BASE64 image data. - * - * This function is used internally by the ExportImageTool in the MCP server. - * - * @param shape - The shape whose image data to export - * @param mode - Either "shape" (to export the entire shape, including descendants) or "fill" - * to export the shape's raw fill image data - * @param asSVG - Whether to export as SVG rather than as a pixel image (only supported for mode "shape") - * @returns A byte array containing the exported image data. - * - For mode="shape", it will be PNG or SVG data depending on the value of `asSVG`. - * - For mode="fill", it will be whatever format the fill image is stored in. - */ - public static async exportImage(shape: Shape, mode: "shape" | "fill", asSVG: boolean): Promise { - // Updates are asynchronous in Penpot, so wait a tick to ensure any pending updates are applied before export. - // The constant wait time is a temporary workardound until a better solution for penpot/penpot-mcp#27 - // is implemented. - await new Promise((resolve) => setTimeout(resolve, 200)); - // Perform export - switch (mode) { - case "shape": - return shape.export({ type: asSVG ? "svg" : "png" }); - case "fill": - if (asSVG) { - throw new Error("Image fills cannot be exported as SVG"); - } - // check whether the shape has the `fills` member - if (!("fills" in shape)) { - throw new Error("Shape with `fills` member is required for fill export mode"); - } - // find first fill that has fillImage - const fills: Fill[] = (shape as any).fills; - for (const fill of fills) { - if (fill.fillImage) { - const imageData = fill.fillImage; - return imageData.data(); - } - } - throw new Error("No fill with image data found in the shape"); - default: - throw new Error(`Unsupported export mode: ${mode}`); - } - } - - /** - * Finds all tokens that match the given name across all token sets. - * - * @param name - The name of the token to search for (case-sensitive exact match) - * @returns An array of all matching tokens (may be empty) - */ - public static findTokensByName(name: string): any[] { - const tokens: any[] = []; - const tokenCatalog = penpot.library.local.tokens; - - for (const set of tokenCatalog.sets) { - for (const token of set.tokens) { - if (token.name === name) { - tokens.push(token); - } - } - } - - return tokens; - } - - /** - * Finds the first token that matches the given name across all token sets. - * - * @param name - The name of the token to search for (case-sensitive exact match) - * @returns The first matching token, or null if not found - */ - public static findTokenByName(name: string): any | null { - const tokenCatalog = penpot.library.local.tokens; - - for (const set of tokenCatalog.sets) { - for (const token of set.tokens) { - if (token.name === name) { - return token; - } - } - } - - return null; - } - - /** - * Gets the token set that contains the given token. - * - * @param token - The token whose set to find - * @returns The TokenSet containing this token, or null if not found - */ - public static getTokenSet(token: any): any | null { - const tokenCatalog = penpot.library.local.tokens; - - for (const set of tokenCatalog.sets) { - if (set.tokens.includes(token)) { - return set; - } - } - - return null; - } - - /** - * Creates a variant group from a list of main component instances and immediately - * configures its property names and values. - * - * Encapsulates the full multi-step workflow in a single call: - * 1. `penpot.createVariantFromComponents()` — groups the components - * 2. `variants.renameProperty()` / `variants.addProperty()` — sets property names - * 3. `comp.setVariantProperty()` — sets each component's property values - * - * @param components - Array of main-instance boards to combine. Each element carries - * a `shape` (the Board on the canvas) and a `properties` map of - * `{ propertyName: value }` for that component. - * @returns The newly created `VariantContainer`. - * - * @example - * // Three button variants differing in Size - * // Given three main components s, m, l (here: the first three main components on the page) - * const [s, m, l] = penpot.currentPage.findAllShapes(sh => sh.isMainComponent()).slice(0, 3) as Board[]; - * const container = PenpotUtils.createVariantContainer([ - * { shape: s, properties: { Size: 'Small' } }, - * { shape: m, properties: { Size: 'Medium' } }, - * { shape: l, properties: { Size: 'Large' } }, - * ]); - * - * @example - * // Two properties: Size × State - * const container = PenpotUtils.createVariantContainer([ - * { shape: s, properties: { Size: 'Small', State: 'Default' } }, - * { shape: m, properties: { Size: 'Medium', State: 'Default' } }, - * { shape: l, properties: { Size: 'Large', State: 'Hover' } }, - * ]); - */ - public static createVariantContainer( - components: Array<{ shape: Board; properties: Record }> - ): VariantContainer { - // Collect all unique property names (preserving first-seen order) - const propNames: string[] = []; - for (const { properties } of components) { - for (const name of Object.keys(properties)) { - if (!propNames.includes(name)) propNames.push(name); - } - } - - // 1. Create the variant container - const container: VariantContainer = (penpot as any).createVariantFromComponents(components.map((c) => c.shape)); - const variants = container.variants; - if (!variants) { - throw new Error( - "createVariantContainer: the created container has no `variants`. " + - "Ensure every provided shape is a main component instance (Board)." - ); - } - - // 2. Rename / add properties - // createVariantFromComponents always creates exactly one property ("Property 1") - for (let i = 0; i < propNames.length; i++) { - if (i === 0) { - variants.renameProperty(0, propNames[0]); - } else { - variants.addProperty(); - variants.renameProperty(i, propNames[i]); - } - } - - // 3. Set each component's property values - const variantComps = variants.variantComponents(); - for (let compIdx = 0; compIdx < variantComps.length; compIdx++) { - const comp = variantComps[compIdx]; - if (!penpot.utils.types.isVariantComponent(comp)) continue; - const props = components[compIdx]?.properties ?? {}; - for (let propIdx = 0; propIdx < propNames.length; propIdx++) { - const value = props[propNames[propIdx]]; - if (value !== undefined) { - comp.setVariantProperty(propIdx, value); - } - } - } - - return container; - } - - /** - * Generates an overview of all tokens organized by token set name, token type, and token name. - * The result is a nested object structure: {tokenSetName: {tokenType: [tokenName, ...]}}. - * - * @returns An object mapping token set names to objects that map token types to arrays of token names - */ - public static tokenOverview(): Record> { - const overview: Record> = {}; - const tokenCatalog = penpot.library.local.tokens; - - for (const set of tokenCatalog.sets) { - const setOverview: Record = {}; - - for (const token of set.tokens) { - const tokenType = token.type; - if (!setOverview[tokenType]) { - setOverview[tokenType] = []; - } - setOverview[tokenType].push(token.name); - } - - overview[set.name] = setOverview; - } - - return overview; - } -} diff --git a/mcp/packages/plugin/src/TaskHandler.ts b/mcp/packages/plugin/src/TaskHandler.ts deleted file mode 100644 index 7bc0aa3..0000000 --- a/mcp/packages/plugin/src/TaskHandler.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Represents a task received from the MCP server in the Penpot MCP plugin - */ -export class Task { - public isResponseSent: boolean = false; - - /** - * @param requestId Unique identifier for the task request - * @param taskType The type of the task to execute - * @param params Task parameters/arguments - */ - constructor( - public requestId: string, - public taskType: string, - public params: TParams - ) {} - - /** - * Sends a task response back to the MCP server. - */ - protected sendResponse(success: boolean, data: any = undefined, error: any = undefined): void { - if (this.isResponseSent) { - console.error("Response already sent for task:", this.requestId); - return; - } - - // create response object - const response = { - type: "task-response", - // NOTE: This inner response schema also constructed in main.ts/sendTaskResponse. - response: { - id: this.requestId, - success: success, - data: data, - error: error, - }, - }; - - // Send to main.ts which will forward to MCP server via WebSocket - try { - penpot.ui.sendMessage(response, true); - } catch (sendError) { - console.error("Failed to transfer task response for transmission to MCP server", sendError); - if (success) { - console.info("Original response was a success response, sending an error response instead."); - // If we were trying to send a success response, send an error response instead - let cause = sendError instanceof Error ? sendError.message : String(sendError); - this.sendError( - "Unexpected error sending task response: " + - cause + - " If you are an LLM and tried to serialize a regular object (shape, token, etc.), " + - "tell the user to report this error, providing information on the request you made." - ); - } else { - console.info( - "Original response was an error response, abandoning attempt to send alternative response to avoid infinite error loop." - ); - } - return; - } - console.log("Sent task response:", response); - this.isResponseSent = true; - } - - public sendSuccess(data: any = undefined): void { - this.sendResponse(true, data); - } - - public sendError(error: string): void { - this.sendResponse(false, undefined, error); - } -} - -/** - * Abstract base class for task handlers in the Penpot MCP plugin. - * - * @template TParams - The type of parameters this handler expects - */ -export abstract class TaskHandler { - /** The task type this handler is responsible for */ - abstract readonly taskType: string; - - /** - * Checks if this handler can process the given task. - * - * @param task - The task identifier to check - * @returns True if this handler applies to the given task - */ - isApplicableTo(task: Task): boolean { - return this.taskType === task.taskType; - } - - /** - * Handles the task with the provided parameters. - * - * @param task - The task to be handled - */ - abstract handle(task: Task): Promise; -} diff --git a/mcp/packages/plugin/src/index.d.ts b/mcp/packages/plugin/src/index.d.ts deleted file mode 100644 index a0eda65..0000000 --- a/mcp/packages/plugin/src/index.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -interface McpOptions { - getToken(): string; - getServerUrl(): string; - setMcpStatus(status: string); - on(eventType: "disconnect" | "connect", cb: () => void); -} - -declare global { - const mcp: undefined | McpOptions; -} - -export {}; diff --git a/mcp/packages/plugin/src/main.ts b/mcp/packages/plugin/src/main.ts deleted file mode 100644 index 4cd19e1..0000000 --- a/mcp/packages/plugin/src/main.ts +++ /dev/null @@ -1,362 +0,0 @@ -import "./style.css"; - -/** - * the maximum allowed size for task responses sent back to the MCP server in the integrated remote MCP mode. - * This bounds the JSON response size. - * Note that in the remote MCP case, responses are transferred to LLMs (not the file system) and LLMs have - * size limitations. This serves to bound the size of returned images in particular. - * Too many overly large simultaneous responses can cause OOM issues in the MCP server, so this contributes - * to bounding memory usage in the centrally provided MCP server. - */ -const MAX_TASK_RESPONSE_SIZE_REMOTE_MCP = 15_000_000; - -// get the current theme from the URL -const searchParams = new URLSearchParams(window.location.hash.split("?")[1]); -document.body.dataset.theme = searchParams.get("theme") ?? "light"; - -// WebSocket connection to the MCP server -let ws: WebSocket | null = null; - -const HEARTBEAT_INTERVAL_MS = 10_000; -const RECONNECT_BASE_DELAY_MS = 1_000; -const RECONNECT_MAX_DELAY_MS = 30_000; - -// transport-level reconnect state for the plugin WebSocket -let shouldReconnect = false; -let lastConnectionUrl: string | undefined; -let lastConnectionToken: string | undefined; -let reconnectAttempts = 0; -let reconnectTimer: ReturnType | null = null; -let heartbeatTimer: ReturnType | null = null; - -/** - * indicates whether the plugin is running with the Penpot-integrated remote MCP server enabled - * (as opposed to a local server used with the explicitly loaded plugin); - * set via the "mcp-mode" message sent by plugin.ts on initialization - */ -let isIntegratedRemoteMcp = false; - -const statusPill = document.getElementById("connection-status") as HTMLElement; -const statusText = document.getElementById("status-text") as HTMLElement; -const currentTaskEl = document.getElementById("current-task") as HTMLElement; -const executedCodeEl = document.getElementById("executed-code") as HTMLTextAreaElement; -const copyCodeBtn = document.getElementById("copy-code-btn") as HTMLButtonElement; -const connectBtn = document.getElementById("connect-btn") as HTMLButtonElement; -const disconnectBtn = document.getElementById("disconnect-btn") as HTMLButtonElement; -const versionWarningEl = document.getElementById("version-warning") as HTMLElement; -const versionWarningTextEl = document.getElementById("version-warning-text") as HTMLElement; - -/** - * Updates the status pill and button visibility based on connection state. - * - * @param code - the connection state code ("idle" | "connecting" | "connected" | "disconnected" | "error") - * @param label - human-readable label to display inside the pill - */ -function updateConnectionStatus(code: string, label: string): void { - if (statusPill) { - statusPill.dataset.status = code; - } - if (statusText) { - statusText.textContent = label; - } - - const isConnected = code === "connected"; - if (connectBtn) connectBtn.hidden = isConnected; - if (disconnectBtn) disconnectBtn.hidden = !isConnected; - - parent.postMessage( - { - type: "update-connection-status", - status: code, - }, - "*" - ); -} - -/** - * Updates the "Current task" display with the currently executing task name. - * - * @param taskName - the task name to display, or null to reset to "---" - */ -function updateCurrentTask(taskName: string | null): void { - if (currentTaskEl) { - currentTaskEl.textContent = taskName ?? "---"; - } - if (taskName === null) { - updateExecutedCode(null); - } -} - -/** - * Updates the executed code textarea with the last code run during task execution. - * - * @param code - the code string to display, or null to clear - */ -function updateExecutedCode(code: string | null): void { - if (executedCodeEl) { - executedCodeEl.value = code ?? ""; - } - if (copyCodeBtn) { - copyCodeBtn.disabled = !code; - } -} - -/** - * Sends a task response back to the MCP server via WebSocket. - * - * @param response - The response containing task ID and result - */ -function sendTaskResponse(response: any): void { - if (ws && ws.readyState === WebSocket.OPEN) { - let responseString = JSON.stringify(response); - if (isIntegratedRemoteMcp && responseString.length > MAX_TASK_RESPONSE_SIZE_REMOTE_MCP) { - const errorMessage = `Serialised response size (${responseString.length}) exceeds maximum of ${MAX_TASK_RESPONSE_SIZE_REMOTE_MCP}.`; - console.warn( - errorMessage + - " [integrated remote MCP mode restriction]; sending error response instead; original response:", - response - ); - response = { - id: response.id, - success: false, - error: errorMessage, - }; - responseString = JSON.stringify(response); - } - ws.send(responseString); - console.log("Sent response to MCP server:", response); - } else { - console.error("WebSocket not connected, cannot send response"); - } -} - -/** - * Emits a liveness signal from the plugin event loop. - * - * WebSocket ping/pong is not enough here: browsers can answer protocol pings while - * page JavaScript is frozen and unable to run MCP tasks. - */ -function sendHeartbeat(): boolean { - if (ws && ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify({ type: "heartbeat" })); - return true; - } - return false; -} - -/** Starts heartbeat emission for the active WebSocket. */ -function startHeartbeat(): void { - stopHeartbeat(); - heartbeatTimer = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL_MS); -} - -/** Stops heartbeat emission for the active WebSocket. */ -function stopHeartbeat(): void { - if (heartbeatTimer !== null) { - clearInterval(heartbeatTimer); - heartbeatTimer = null; - } -} - -/** - * Delay before the next reconnect, using capped exponential backoff. - * - * Keeps recovery fast for short drops without hammering an unavailable server. - */ -function computeReconnectDelay(attempts: number): number { - return Math.min(RECONNECT_BASE_DELAY_MS * 2 ** attempts, RECONNECT_MAX_DELAY_MS); -} - -/** Schedules one WebSocket reconnect attempt with backoff. */ -function scheduleReconnect(): void { - if (!shouldReconnect || reconnectTimer !== null) { - return; - } - const delay = computeReconnectDelay(reconnectAttempts); - reconnectAttempts++; - reconnectTimer = setTimeout(() => { - reconnectTimer = null; - if (shouldReconnect && ws?.readyState !== WebSocket.OPEN && ws?.readyState !== WebSocket.CONNECTING) { - connectToMcpServer(lastConnectionUrl, lastConnectionToken); - } - }, delay); -} - -/** Cancels pending reconnection and resets backoff. */ -function cancelReconnect(): void { - if (reconnectTimer !== null) { - clearTimeout(reconnectTimer); - reconnectTimer = null; - } - reconnectAttempts = 0; -} - -/** - * Establishes a WebSocket connection to the MCP server. - */ -function connectToMcpServer(baseUrl?: string, token?: string): void { - shouldReconnect = true; - lastConnectionUrl = baseUrl; - lastConnectionToken = token; - - if (ws?.readyState === WebSocket.OPEN) { - updateConnectionStatus("connected", "Connected"); - return; - } - if (ws?.readyState === WebSocket.CONNECTING) { - return; - } - - try { - let wsUrl = baseUrl || PENPOT_MCP_WEBSOCKET_URL; - let wsError: unknown | undefined; - - if (token) { - wsUrl += `?userToken=${encodeURIComponent(token)}`; - } - - ws = new WebSocket(wsUrl); - updateConnectionStatus("connecting", "Connecting..."); - - ws.onopen = () => { - cancelReconnect(); - startHeartbeat(); - setTimeout(() => { - if (ws) { - console.log("Connected to MCP server"); - updateConnectionStatus("connected", "Connected"); - } - }, 100); - }; - - ws.onmessage = (event) => { - try { - console.log("Received from MCP server:", event.data); - const request = JSON.parse(event.data); - // Track the current task received from the MCP server - if (request.task) { - updateCurrentTask(request.task); - updateExecutedCode(request.params?.code ?? null); - } - // Forward the task request to the plugin for execution - parent.postMessage(request, "*"); - } catch (error) { - console.error("Failed to parse WebSocket message:", error); - } - }; - - ws.onclose = (event: CloseEvent) => { - stopHeartbeat(); - // keep the explicit error state if one was already shown - if (!wsError) { - console.log("Disconnected from MCP server"); - const label = event.reason ? `Disconnected: ${event.reason}` : "Disconnected"; - updateConnectionStatus("disconnected", label); - updateCurrentTask(null); - } - ws = null; - scheduleReconnect(); - }; - - ws.onerror = (error) => { - console.error("WebSocket error:", error); - wsError = error; - // note: WebSocket error events typically don't contain detailed error messages - updateConnectionStatus("error", "Connection error"); - }; - } catch (error) { - console.error("Failed to connect to MCP server:", error); - const reason = error instanceof Error ? error.message : undefined; - const label = reason ? `Connection failed: ${reason}` : "Connection failed"; - updateConnectionStatus("error", label); - } -} - -/** Closes the socket without reconnecting. */ -function disconnectFromMcpServer(): void { - shouldReconnect = false; - cancelReconnect(); - stopHeartbeat(); - ws?.close(); -} - -copyCodeBtn?.addEventListener("click", () => { - const code = executedCodeEl?.value; - if (!code) return; - - navigator.clipboard.writeText(code).then(() => { - copyCodeBtn.classList.add("copied"); - setTimeout(() => copyCodeBtn.classList.remove("copied"), 1500); - }); -}); - -connectBtn?.addEventListener("click", () => { - connectToMcpServer(); -}); - -disconnectBtn?.addEventListener("click", () => { - disconnectFromMcpServer(); -}); - -// Listen plugin.ts messages -window.addEventListener("message", (event) => { - if (event.data.type === "mcp-mode") { - isIntegratedRemoteMcp = event.data.integratedRemoteMcp; - } - if (event.data.type === "start-server") { - connectToMcpServer(event.data.url, event.data.token); - } - if (event.data.type === "version-mismatch") { - if (versionWarningEl && versionWarningTextEl) { - versionWarningTextEl.innerHTML = - `Version mismatch detected: This version of the MCP server is intended for Penpot ` + - `${event.data.mcpVersion} while the current version is ${event.data.penpotVersion}. ` + - `Executions may not work or produce suboptimal results.`; - versionWarningEl.hidden = false; - } - } - if (event.data.type === "stop-server") { - disconnectFromMcpServer(); - } else if (event.data.source === "penpot") { - document.body.dataset.theme = event.data.theme; - } else if (event.data.type === "task-response") { - // Forward task response back to MCP server - sendTaskResponse(event.data.response); - } -}); - -/** Sends a heartbeat or reconnects after the tab becomes active again. */ -function handleTabResumed(): void { - if (!shouldReconnect) { - return; - } - if (ws?.readyState === WebSocket.OPEN) { - sendHeartbeat(); - } else if (ws?.readyState !== WebSocket.CONNECTING) { - cancelReconnect(); - connectToMcpServer(lastConnectionUrl, lastConnectionToken); - } -} - -// Chrome supports freeze/resume; Firefox does not. That is acceptable because -// Firefox still supports visibilitychange, and stale heartbeats detect any tab -// suspension that prevents plugin JavaScript from running tasks. - -// Chrome: about to pause page JavaScript. -document.addEventListener("freeze", () => { - if (ws && ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify({ type: "freeze" })); - } -}); - -// Chrome: frozen page resumed. -document.addEventListener("resume", handleTabResumed); - -// Chrome and Firefox: tab visibility changed. -document.addEventListener("visibilitychange", () => { - if (document.visibilityState === "visible") { - handleTabResumed(); - } -}); - -parent.postMessage({ type: "ui-initialized" }, "*"); diff --git a/mcp/packages/plugin/src/plugin.ts b/mcp/packages/plugin/src/plugin.ts deleted file mode 100644 index 5021167..0000000 --- a/mcp/packages/plugin/src/plugin.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { ExecuteCodeTaskHandler } from "./task-handlers/ExecuteCodeTaskHandler"; -import { Task, TaskHandler } from "./TaskHandler"; -import { formatTaskError } from "./ErrorUtils"; - -/** - * indicates whether the plugin is running in an environment with the Penpot-integrated remote MCP server - * enabled (as opposed to a local server used with the explicitly loaded plugin) - */ -const isIntegratedRemoteMcp = !!mcp; - -/** - * Extracts the major.minor.patch prefix from a version string. - * - * @param version - a version string starting with major.minor.patch - * @returns the major.minor.patch prefix, or the original string if it does not match - */ -function extractVersionPrefix(version: string): string { - const match = version.match(/^(\d+\.\d+\.\d+)/); - return match ? match[1] : version; -} - -mcp?.setMcpStatus("connecting"); - -/** - * Registry of all available task handlers. - */ -const taskHandlers: TaskHandler[] = [new ExecuteCodeTaskHandler()]; - -// Open the plugin UI (main.ts) -penpot.ui.open("Penpot MCP Plugin", `?theme=${penpot.theme}`, { - width: 236, - height: 210, - hidden: isIntegratedRemoteMcp, -} as any); - -// Register message handlers -penpot.ui.onMessage((message) => { - if (typeof message === "object" && message.type === "ui-initialized") { - // Inform the UI about the operating mode - penpot.ui.sendMessage({ - type: "mcp-mode", - integratedRemoteMcp: isIntegratedRemoteMcp, - }); - // Check Penpot version compatibility - const penpotVersionPrefix = penpot.version ? extractVersionPrefix(penpot.version) : "<2.15"; // pre-2.15 versions don't have version info - const mcpVersionPrefix = extractVersionPrefix(PENPOT_MCP_VERSION); - console.log(`Penpot version: ${penpotVersionPrefix}, MCP version: ${mcpVersionPrefix}`); - const isLocalPenpotVersion = penpotVersionPrefix == "0.0.0"; - if (penpotVersionPrefix !== mcpVersionPrefix && !isLocalPenpotVersion) { - penpot.ui.sendMessage({ - type: "version-mismatch", - mcpVersion: mcpVersionPrefix, - penpotVersion: penpotVersionPrefix, - }); - } - // Initiate connection to remote MCP server (if enabled) - if (isIntegratedRemoteMcp) { - penpot.ui.sendMessage({ - type: "start-server", - url: mcp?.getServerUrl(), - token: mcp?.getToken(), - }); - } - } else if (typeof message === "object" && message.type === "update-connection-status") { - mcp?.setMcpStatus(message.status || "unknown"); - } else if (typeof message === "object" && message.task && message.id) { - // Handle plugin tasks submitted by the MCP server - handlePluginTaskRequest(message).catch((error) => { - console.error("Error in handlePluginTaskRequest:", error); - }); - } -}); - -/** - * Handles plugin task requests received from the MCP server via WebSocket. - * - * @param request - The task request containing ID, task type and parameters - */ -async function handlePluginTaskRequest(request: { id: string; task: string; params: any }): Promise { - console.log("Executing plugin task:", request.task, request.params); - const task = new Task(request.id, request.task, request.params); - - // Find the appropriate handler - const handler = taskHandlers.find((h) => h.isApplicableTo(task)); - - if (handler) { - try { - // Cast the params to the expected type and handle the task - console.log("Processing task with handler:", handler); - await handler.handle(task); - - // check whether a response was sent and send a generic success if not - if (!task.isResponseSent) { - console.warn("Handler did not send a response, sending generic success."); - task.sendSuccess("Task completed without a specific response."); - } - - console.log("Task handled successfully:", task); - } catch (error) { - console.error("Error handling task:", error); - task.sendError(`Error handling task: ${formatTaskError(error)}`); - } - } else { - console.error("Unknown plugin task:", request.task); - task.sendError(`Unknown task type: ${request.task}`); - } -} - -if (mcp) { - mcp.on("disconnect", async () => { - penpot.ui.sendMessage({ - type: "stop-server", - }); - }); - mcp.on("connect", async () => { - penpot.ui.sendMessage({ - type: "start-server", - url: mcp?.getServerUrl(), - token: mcp?.getToken(), - }); - }); -} - -// Handle theme change in the iframe -penpot.on("themechange", (theme) => { - penpot.ui.sendMessage({ - source: "penpot", - type: "themechange", - theme, - }); -}); diff --git a/mcp/packages/plugin/src/style.css b/mcp/packages/plugin/src/style.css deleted file mode 100644 index 53e0a9d..0000000 --- a/mcp/packages/plugin/src/style.css +++ /dev/null @@ -1,190 +0,0 @@ -@import "@penpot/plugin-styles/styles.css"; - -body { - margin: 0; - padding: 0; -} - -.plugin-container { - display: flex; - flex-direction: column; - gap: var(--spacing-8); - padding: var(--spacing-16) var(--spacing-8); - box-sizing: border-box; -} - -/* ── Status pill ─────────────────────────────────────────────────── */ - -.status-pill { - display: flex; - align-items: center; - justify-content: center; - gap: var(--spacing-8); - padding: var(--spacing-8) var(--spacing-16); - border-radius: var(--spacing-8); - border: 1px solid var(--background-quaternary); - color: var(--foreground-secondary); - width: 100%; - box-sizing: border-box; -} - -.status-pill[data-status="connected"] { - border-color: var(--accent-primary); - color: var(--accent-primary); -} - -.status-pill[data-status="disconnected"], -.status-pill[data-status="error"] { - border-color: var(--error-500); - color: var(--error-500); -} - -.status-dot { - display: inline-block; - width: 8px; - height: 8px; - border-radius: 50%; - background-color: currentColor; - flex-shrink: 0; -} - -/* ── Collapsible section ─────────────────────────────────────────── */ - -.collapsible-section { - border: 1px solid var(--background-quaternary); - border-radius: var(--spacing-8); - overflow: hidden; -} - -.collapsible-header { - display: flex; - align-items: center; - gap: var(--spacing-8); - padding: var(--spacing-8) var(--spacing-12); - cursor: pointer; - color: var(--foreground-secondary); - list-style: none; - user-select: none; -} - -.collapsible-header::-webkit-details-marker { - display: none; -} - -.collapsible-arrow { - flex-shrink: 0; - transition: transform 0.2s ease; -} - -details[open] > .collapsible-header .collapsible-arrow { - transform: rotate(90deg); -} - -.collapsible-body { - display: flex; - flex-direction: column; - gap: var(--spacing-4); - padding: var(--spacing-4) var(--spacing-12) var(--spacing-12); - border-top: 1px solid var(--background-quaternary); -} - -/* ── Tool section ────────────────────────────────────────────────── */ - -.tool-label { - color: var(--foreground-secondary); -} - -.tool-display { - display: flex; - align-items: center; - gap: var(--spacing-8); - padding: var(--spacing-8) var(--spacing-12); - border-radius: var(--spacing-8); - background-color: var(--background-tertiary); - color: var(--foreground-secondary); - min-height: 32px; - box-sizing: border-box; -} - -.tool-icon { - flex-shrink: 0; - opacity: 0.7; -} - -/* ── Code section ────────────────────────────────────────────────── */ - -.code-section-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-top: var(--spacing-8); -} - -.code-textarea { - width: 100%; - height: 100px; - resize: vertical; - padding: var(--spacing-8) var(--spacing-12); - border-radius: var(--spacing-8); - border: 1px solid var(--background-quaternary); - background-color: var(--background-tertiary); - color: var(--foreground-secondary); - font-family: monospace; - font-size: 11px; - line-height: 1.5; - box-sizing: border-box; - outline: none; -} - -.copy-btn { - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - padding: 0; - border: 1px solid var(--background-quaternary); - border-radius: var(--spacing-4); - background-color: transparent; - color: var(--foreground-secondary); - cursor: pointer; - flex-shrink: 0; - transition: - background-color 0.15s ease, - color 0.15s ease; -} - -.copy-btn:hover:not(:disabled) { - background-color: var(--background-tertiary); - color: var(--foreground-primary); -} - -.copy-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.copy-btn.copied { - color: var(--accent-primary); - border-color: var(--accent-primary); -} - -/* ── Version warning ─────────────────────────────────────────────── */ - -.version-warning { - align-items: flex-start; - padding: var(--spacing-8) var(--spacing-12); - border-radius: var(--spacing-8); - border: 1px solid var(--warning-500, #f59e0b); - color: var(--warning-500, #f59e0b); - width: 100%; - box-sizing: border-box; -} - -/* ── Action buttons ──────────────────────────────────────────────── */ - -#connect-btn, -#disconnect-btn { - width: 100%; - margin-top: var(--spacing-4); -} diff --git a/mcp/packages/plugin/src/task-handlers/ExecuteCodeTaskHandler.ts b/mcp/packages/plugin/src/task-handlers/ExecuteCodeTaskHandler.ts deleted file mode 100644 index df2fb65..0000000 --- a/mcp/packages/plugin/src/task-handlers/ExecuteCodeTaskHandler.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { Task, TaskHandler } from "../TaskHandler"; -import { ExecuteCodeTaskParams, ExecuteCodeTaskResultData } from "../../../common/src"; -import { PenpotUtils } from "../PenpotUtils.ts"; - -/** - * Console implementation that captures all log output for code execution. - * - * Provides the same interface as the native console object but appends - * all output to an internal log string that can be retrieved. - */ -class ExecuteCodeTaskConsole { - /** - * Accumulated log output from all console method calls. - */ - private logOutput: string = ""; - - /** - * Resets the accumulated log output to empty string. - * Should be called before each code execution to start with clean logs. - */ - resetLog(): void { - this.logOutput = ""; - } - - /** - * Gets the accumulated log output from all console method calls. - * @returns The complete log output as a string - */ - getLog(): string { - return this.logOutput; - } - - /** - * Appends a formatted message to the log output. - * @param level - Log level prefix (e.g., "LOG", "WARN", "ERROR") - * @param args - Arguments to log, will be stringified and joined - */ - private appendToLog(level: string, ...args: any[]): void { - const message = args - .map((arg) => (typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg))) - .join(" "); - this.logOutput += `[${level}] ${message}\n`; - } - - /** - * Logs a message to the captured output. - */ - log(...args: any[]): void { - this.appendToLog("LOG", ...args); - } - - /** - * Logs a warning message to the captured output. - */ - warn(...args: any[]): void { - this.appendToLog("WARN", ...args); - } - - /** - * Logs an error message to the captured output. - */ - error(...args: any[]): void { - this.appendToLog("ERROR", ...args); - } - - /** - * Logs an informational message to the captured output. - */ - info(...args: any[]): void { - this.appendToLog("INFO", ...args); - } - - /** - * Logs a debug message to the captured output. - */ - debug(...args: any[]): void { - this.appendToLog("DEBUG", ...args); - } - - /** - * Logs a message with trace information to the captured output. - */ - trace(...args: any[]): void { - this.appendToLog("TRACE", ...args); - } - - /** - * Logs a table to the captured output (simplified as JSON). - */ - table(data: any): void { - this.appendToLog("TABLE", data); - } - - /** - * Starts a timer (simplified implementation that just logs). - */ - time(label?: string): void { - this.appendToLog("TIME", `Timer started: ${label || "default"}`); - } - - /** - * Ends a timer (simplified implementation that just logs). - */ - timeEnd(label?: string): void { - this.appendToLog("TIME_END", `Timer ended: ${label || "default"}`); - } - - /** - * Logs messages in a group (simplified to just log the label). - */ - group(label?: string): void { - this.appendToLog("GROUP", label || ""); - } - - /** - * Logs messages in a collapsed group (simplified to just log the label). - */ - groupCollapsed(label?: string): void { - this.appendToLog("GROUP_COLLAPSED", label || ""); - } - - /** - * Ends the current group (simplified implementation). - */ - groupEnd(): void { - this.appendToLog("GROUP_END", ""); - } - - /** - * Clears the console (no-op in this implementation since we want to capture logs). - */ - clear(): void { - // intentionally empty - we don't want to clear captured logs - } - - /** - * Counts occurrences of calls with the same label (simplified implementation). - */ - count(label?: string): void { - this.appendToLog("COUNT", label || "default"); - } - - /** - * Resets the count for a label (simplified implementation). - */ - countReset(label?: string): void { - this.appendToLog("COUNT_RESET", label || "default"); - } - - /** - * Logs an assertion (simplified to just log if condition is false). - */ - assert(condition: boolean, ...args: any[]): void { - if (!condition) { - this.appendToLog("ASSERT", ...args); - } - } -} - -/** - * Task handler for executing JavaScript code in the plugin context. - * - * Maintains a persistent context object that preserves state between code executions - * and captures all console output during execution. - */ -export class ExecuteCodeTaskHandler extends TaskHandler { - readonly taskType = "executeCode"; - - /** - * Persistent context object that maintains state between code executions. - * Contains the penpot API, storage object, and custom console implementation. - */ - private readonly context: any; - - constructor() { - super(); - - // initialize context, making penpot, penpotUtils, storage and the custom console available - this.context = { - penpot: penpot, - storage: {}, - console: new ExecuteCodeTaskConsole(), - penpotUtils: PenpotUtils, - }; - } - - async handle(task: Task): Promise { - if (!task.params.code) { - task.sendError("executeCode task requires 'code' parameter"); - return; - } - - this.context.console.resetLog(); - - const context = this.context; - const code = task.params.code; - - // set the flags naturalChildOrdering and throwValidationErrors to true during code execution. - let originalNaturalChildOrdering: any, originalThrowValidationErrors: any; - if (penpot.flags) { - originalNaturalChildOrdering = penpot.flags.naturalChildOrdering; - penpot.flags.naturalChildOrdering = true; - originalThrowValidationErrors = penpot.flags.throwValidationErrors; - penpot.flags.throwValidationErrors = true; - } else { - throw new Error( - "You are using a version of the Penpot MCP server which is incompatible " + - "with the connected Penpot version. " + - "Read the documentation for instructions on which version to use: " + - "https://github.com/penpot/penpot/tree/develop/mcp\n" + - "If you are an LLM, tell the user about this!" - ); - } - - let result: any; - try { - // execute the code in an async function with the context variables as parameters - result = await (async (ctx) => { - const fn = new Function(...Object.keys(ctx), `return (async () => { ${code} })();`); - return fn(...Object.values(ctx)); - })(context); - } finally { - // restore the original value of the flags - penpot.flags.naturalChildOrdering = originalNaturalChildOrdering; - penpot.flags.throwValidationErrors = originalThrowValidationErrors; - } - - console.log("Code execution result:", result); - - // transform a top-level Uint8Array result into a compact base64 envelope to avoid the - // ~10x JSON expansion that occurs when JSON.stringify serializes typed arrays as objects - // with numeric string keys (see penpot/penpot#9420) - if (result instanceof Uint8Array) { - result = ExecuteCodeTaskHandler.encodeBytesAsBase64Envelope(result); - } - - // return result and captured log - let resultData: ExecuteCodeTaskResultData = { - result: result, - log: this.context.console.getLog(), - }; - task.sendSuccess(resultData); - } - - /** - * Base64-encodes the given bytes and wraps the result in a tagged envelope that the - * server side recognizes (see `ImageContent.byteData`). - * - * @param bytes - the raw binary data to encode - * @returns an envelope of the form `{ __type: "base64", data: }` - */ - private static encodeBytesAsBase64Envelope(bytes: Uint8Array): { __type: "base64"; data: string } { - // build the binary string in chunks; calling `String.fromCharCode(...bytes)` directly - // would overflow the call stack for large arrays - const chunkSize = 0x8000; - let binary = ""; - for (let i = 0; i < bytes.length; i += chunkSize) { - const chunk = bytes.subarray(i, i + chunkSize); - binary += String.fromCharCode.apply(null, chunk as unknown as number[]); - } - return { __type: "base64", data: btoa(binary) }; - } -} diff --git a/mcp/packages/plugin/src/vite-env.d.ts b/mcp/packages/plugin/src/vite-env.d.ts deleted file mode 100644 index 252ff65..0000000 --- a/mcp/packages/plugin/src/vite-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// - -declare const IS_MULTI_USER_MODE: boolean; -declare const PENPOT_MCP_WEBSOCKET_URL: string; -declare const PENPOT_MCP_VERSION: string; diff --git a/mcp/packages/plugin/tsconfig.json b/mcp/packages/plugin/tsconfig.json deleted file mode 100644 index de5c7b8..0000000 --- a/mcp/packages/plugin/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - "typeRoots": ["./node_modules/@types", "./node_modules/@penpot"], - "types": ["plugin-types"], - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "exclude": ["src/*.test.ts"] -} diff --git a/mcp/packages/plugin/vite.config.ts b/mcp/packages/plugin/vite.config.ts deleted file mode 100644 index 9e79ca7..0000000 --- a/mcp/packages/plugin/vite.config.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { defineConfig } from "vite"; -import livePreview from "vite-live-preview"; -import { createRequire } from "module"; - -const require = createRequire(import.meta.url); -const rootPkg = require("../../package.json"); - -let WS_URI = process.env.WS_URI || "http://localhost:4402"; -let SERVER_HOST = process.env.PENPOT_MCP_PLUGIN_SERVER_HOST ?? "localhost"; -let MCP_VERSION = JSON.stringify(rootPkg.version); - -console.log("PENPOT_MCP_WEBSOCKET_URL:", JSON.stringify(WS_URI)); -console.log("PENPOT_MCP_VERSION:", MCP_VERSION); - -export default defineConfig({ - base: "./", - plugins: [ - livePreview({ - reload: true, - config: { - build: { - sourcemap: true, - }, - }, - }), - ], - build: { - rollupOptions: { - input: { - plugin: "src/plugin.ts", - index: "./index.html", - }, - output: { - entryFileNames: "[name].js", - }, - }, - }, - preview: { - host: SERVER_HOST, - port: 4400, - cors: true, - allowedHosts: [], - }, - define: { - PENPOT_MCP_WEBSOCKET_URL: JSON.stringify(WS_URI), - PENPOT_MCP_VERSION: MCP_VERSION, - }, -}); diff --git a/mcp/packages/plugin/vite.release.config.ts b/mcp/packages/plugin/vite.release.config.ts deleted file mode 100644 index 8449280..0000000 --- a/mcp/packages/plugin/vite.release.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig, mergeConfig } from "vite"; -import baseConfig from "./vite.config.ts"; - -export default mergeConfig( - baseConfig, - defineConfig({ - plugins: [], - }) -); diff --git a/mcp/packages/server/.gitignore b/mcp/packages/server/.gitignore deleted file mode 100644 index 54e8e7d..0000000 --- a/mcp/packages/server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/pnpm-lock.yaml diff --git a/mcp/packages/server/README.md b/mcp/packages/server/README.md deleted file mode 100644 index 78be5e1..0000000 --- a/mcp/packages/server/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Penpot MCP Server - -A Model Context Protocol (MCP) server that provides Penpot integration -capabilities for AI clients supporting the model context protocol (MCP). - -## Setup - -1. Install Dependencies - - pnpm install - -2. Build the Project - - pnpm run build - -3. Run the Server - - pnpm run start - - -## Penpot Plugin API REPL - -The MCP server includes a REPL interface for testing Penpot Plugin API calls. -To use it, connect to the URL reported at startup. diff --git a/mcp/packages/server/data/api_types.yml b/mcp/packages/server/data/api_types.yml deleted file mode 100644 index 0b53f1d..0000000 --- a/mcp/packages/server/data/api_types.yml +++ /dev/null @@ -1,23490 +0,0 @@ -Penpot: - overview: |- - Interface Penpot - ================ - - These are methods and properties available on the `penpot` global object. - - ``` - interface Penpot { - ui: { - open: ( - name: string, - url: string, - options?: { width: number; height: number; hidden?: boolean }, - ) => void; - size: { width: number; height: number } | null; - resize: (width: number, height: number) => void; - sendMessage: (message: unknown, throwOnError?: boolean) => void; - onMessage: (callback: (message: T) => void) => void; - }; - utils: ContextUtils; - closePlugin: () => void; - on( - type: T, - callback: (event: EventsMap[T]) => void, - props?: { [key: string]: unknown }, - ): symbol; - off(listenerId: symbol): void; - version: string; - root: Shape | null; - currentFile: File | null; - currentPage: Page | null; - viewport: Viewport; - flags: Flags; - history: HistoryContext; - library: LibraryContext; - fonts: FontsContext; - currentUser: User; - activeUsers: ActiveUser[]; - theme: Theme; - localStorage: LocalStorage; - selection: Shape[]; - shapesColors(shapes: Shape[]): (Color & ColorShapeInfo)[]; - replaceColor(shapes: Shape[], oldColor: Color, newColor: Color): void; - uploadMediaUrl(name: string, url: string): Promise; - uploadMediaData( - name: string, - data: Uint8Array, - mimeType: string, - ): Promise; - group(shapes: Shape[]): Group | null; - ungroup(group: Group, ...other: Group[]): void; - createRectangle(): Rectangle; - createBoard(): Board; - createEllipse(): Ellipse; - createPath(): Path; - createBoolean(boolType: BooleanType, shapes: Shape[]): Boolean | null; - createShapeFromSvg(svgString: string): Group | null; - createShapeFromSvgWithImages(svgString: string): Promise; - createText(text: string): Text | null; - generateMarkup( - shapes: Shape[], - options?: { type?: "html" | "svg" }, - ): string; - generateStyle( - shapes: Shape[], - options?: { - type?: "css"; - withPrelude?: boolean; - includeChildren?: boolean; - }, - ): string; - generateFontFaces(shapes: Shape[]): Promise; - openViewer(): void; - createPage(): Page; - openPage(page: string | Page, newWindow?: boolean): Promise; - alignHorizontal( - shapes: Shape[], - direction: "center" | "left" | "right", - ): void; - alignVertical( - shapes: Shape[], - direction: "center" | "top" | "bottom", - ): void; - distributeHorizontal(shapes: Shape[]): void; - distributeVertical(shapes: Shape[]): void; - flatten(shapes: Shape[]): Path[]; - createVariantFromComponents(shapes: Board[]): VariantContainer; - } - ``` - - Hierarchy - - * Omit - + Penpot - members: - Properties: - ui: |- - ``` - readonly ui: { - open: ( - name: string, - url: string, - options?: { width: number; height: number; hidden?: boolean }, - ) => void; - size: { width: number; height: number } | null; - resize: (width: number, height: number) => void; - sendMessage: (message: unknown, throwOnError?: boolean) => void; - onMessage: (callback: (message: T) => void) => void; - } - ``` - - Type Declaration - - * open: ( name: string, url: string, options?: { width: number; height: number; hidden?: boolean },) => void - - Opens the plugin UI. It is possible to develop a plugin without interface (see Palette color example) but if you need, the way to open this UI is using `penpot.ui.open`. - There is a minimum and maximum size for this modal and a default size but it's possible to customize it anyway with the options parameter. - - Example - ``` - penpot.ui.open('Plugin name', 'url', {width: 150, height: 300}); - ``` - * size: { width: number; height: number } | null - - The current size of the modal, or `null` if the plugin UI is not open. - - Example - ``` - const size = penpot.ui.size;console.log(size); - ``` - * resize: (width: number, height: number) => void - - Resizes the plugin UI. - - Example - ``` - penpot.ui.resize(300, 400); - ``` - * sendMessage: (message: unknown, throwOnError?: boolean) => void - - Sends a message to the plugin UI. - - Example - ``` - penpot.ui.sendMessage({ type: 'example-type', content: 'data we want to share' }); - ``` - * onMessage: (callback: (message: T) => void) => void - - This is usually used in the `plugin.ts` file in order to handle the messages sent from the plugin UI. - - Example - ``` - penpot.ui.onMessage((message) => { if (message.type === 'example-type') { ...do something } }); - ``` - utils: |- - ``` - readonly utils: ContextUtils - ``` - - Provides access to utility functions and context-specific operations. - closePlugin: |- - ``` - closePlugin: () => void - ``` - - Closes the plugin. When this method is called the UI will be closed. - - Example - ``` - penpot.closePlugin(); - ``` - version: |- - ``` - readonly version: string - ``` - - Returns the current penpot version. - root: |- - ``` - readonly root: Shape | null - ``` - - The root shape in the current Penpot context. Requires `content:read` permission. - - Example - ``` - const rootShape = context.root;console.log(rootShape); - ``` - currentFile: |- - ``` - readonly currentFile: File | null - ``` - - Retrieves file data from the current Penpot context. Requires `content:read` permission. - - Returns - - Returns the file data or `null` if no file is available. - - Example - ``` - const fileData = context.currentFile;console.log(fileData); - ``` - currentPage: |- - ``` - readonly currentPage: Page | null - ``` - - The current page in the Penpot context. Requires `content:read` permission. - - Example - ``` - const currentPage = context.currentPage;console.log(currentPage); - ``` - viewport: |- - ``` - readonly viewport: Viewport - ``` - - The viewport settings in the Penpot context. - - Example - ``` - const viewportSettings = context.viewport;console.log(viewportSettings); - ``` - flags: |- - ``` - readonly flags: Flags - ``` - - Provides flags to customize the API behavior. - history: |- - ``` - readonly history: HistoryContext - ``` - - Context encapsulating the history operations - - Example - ``` - const historyContext = context.history;console.log(historyContext); - ``` - library: |- - ``` - readonly library: LibraryContext - ``` - - The library context in the Penpot context, including both local and connected libraries. Requires `library:read` permission. - - Example - ``` - const libraryContext = context.library;console.log(libraryContext); - ``` - fonts: |- - ``` - readonly fonts: FontsContext - ``` - - The fonts context in the Penpot context, providing methods to manage fonts. Requires `content:read` permission. - - Example - ``` - const fontsContext = context.fonts;console.log(fontsContext); - ``` - currentUser: |- - ``` - readonly currentUser: User - ``` - - The current user in the Penpot context. Requires `user:read` permission. - - Example - ``` - const currentUser = context.currentUser;console.log(currentUser); - ``` - activeUsers: |- - ``` - readonly activeUsers: ActiveUser[] - ``` - - An array of active users in the Penpot context. Requires `user:read` permission. - - Example - ``` - const activeUsers = context.activeUsers;console.log(activeUsers); - ``` - theme: |- - ``` - readonly theme: Theme - ``` - - The current theme (light or dark) in Penpot. - - Example - ``` - const currentTheme = context.theme;console.log(currentTheme); - ``` - localStorage: |- - ``` - readonly localStorage: LocalStorage - ``` - - Access to the localStorage proxy - selection: |- - ``` - selection: Shape[] - ``` - - The currently selected shapes in Penpot. Requires `content:read` permission. - - Example - ``` - const selectedShapes = context.selection;console.log(selectedShapes); - ``` - Methods: - on: |- - ``` - on( - type: T, - callback: (event: EventsMap[T]) => void, - props?: { [key: string]: unknown }, - ): symbol - ``` - - Adds an event listener for the specified event type. - Subscribing to events requires `content:read` permission. - - The following are the possible event types: - - * pagechange: event emitted when the current page changes. The callback will receive the new page. - * shapechange: event emitted when the shape changes. This event requires to send inside the `props` object the shape - that will be observed. For example: - - ``` - // Observe the current selected shapepenpot.on('shapechange', (shape) => console.log(shape.name), { shapeId: penpot.selection[0].id }); - ``` - - * selectionchange: event emitted when the current selection changes. The callback will receive the list of ids for the new selection - * themechange: event emitted when the user changes its theme. The callback will receive the new theme (currently: either `dark` or `light`) - * filechange: event emitted when a different file is opened. The callback will receive the new file. - * contentsave: event emitted after the file content is saved in the backend. The callback receives no arguments. - * finish: event emitted when the current file is closed. The callback will receive the id of the closed file. - - Type Parameters - - * T extends keyof EventsMap - - Parameters - - * type: T - - The event type to listen for. - * callback: (event: EventsMap[T]) => void - - The callback function to execute when the event is triggered. - * props: { [key: string]: unknown } - - The properties for the current event handler. Only makes sense for specific events. - - Returns symbol - - the listener id that can be used to call `off` and cancel the listener - - Example - ``` - penpot.on('pagechange', () => {...do something}); - ``` - off: |- - ``` - off(listenerId: symbol): void - ``` - - Removes an event listener for the specified event type. - - Parameters - - * listenerId: symbol - - the id returned by the `on` method when the callback was set - - Returns void - - Example - ``` - const listenerId = penpot.on('contentsave', () => console.log("Changed"));penpot.off(listenerId); - ``` - shapesColors: |- - ``` - shapesColors(shapes: Shape[]): (Color & ColorShapeInfo)[] - ``` - - Retrieves colors applied to the given shapes in Penpot. Requires `content:read` permission. - - Parameters - - * shapes: Shape[] - - Returns (Color & ColorShapeInfo)[] - - Returns an array of colors and their shape information. - - Example - ``` - const colors = context.shapesColors(shapes);console.log(colors); - ``` - replaceColor: |- - ``` - replaceColor(shapes: Shape[], oldColor: Color, newColor: Color): void - ``` - - Replaces a specified old color with a new color in the given shapes. Requires `content:write` permission. - - Parameters - - * shapes: Shape[] - * oldColor: Color - * newColor: Color - - Returns void - - Example - ``` - context.replaceColor(shapes, oldColor, newColor); - ``` - uploadMediaUrl: |- - ``` - uploadMediaUrl(name: string, url: string): Promise - ``` - - Uploads media to Penpot and retrieves its image data. Requires `content:write` permission. - - Parameters - - * name: string - - The name of the media. - * url: string - - The URL of the media to be uploaded. - - Returns Promise - - Returns a promise that resolves to the image data of the uploaded media. - - Example - ``` - const imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');console.log(imageData);// to insert the image in a shape we can doconst board = penpot.createBoard();const shape = penpot.createRectangle();board.appendChild(shape);shape.fills = [{ fillOpacity: 1, fillImage: imageData }]; - ``` - uploadMediaData: |- - ``` - uploadMediaData( - name: string, - data: Uint8Array, - mimeType: string, - ): Promise - ``` - - Uploads media to penpot and retrieves the image data. Requires `content:write` permission. - - Parameters - - * name: string - - The name of the media. - * data: Uint8Array - - The image content data - * mimeType: string - - Returns Promise - - Returns a promise that resolves to the image data of the uploaded media. - - Example - ``` - const imageData = await context.uploadMediaData('example', imageData, 'image/jpeg');console.log(imageData); - ``` - group: |- - ``` - group(shapes: Shape[]): Group | null - ``` - - Groups the specified shapes. Requires `content:write` permission. - - Parameters - - * shapes: Shape[] - - An array of shapes to group. - - Returns Group | null - - Returns the newly created group or `null` if the group could not be created. - - Example - ``` - const penpotShapesArray = penpot.selection;penpot.group(penpotShapesArray); - ``` - ungroup: |- - ``` - ungroup(group: Group, ...other: Group[]): void - ``` - - Ungroups the specified group. Requires `content:write` permission. - - Parameters - - * group: Group - - The group to ungroup. - * ...other: Group[] - - Additional groups to ungroup. - - Returns void - - Example - ``` - const penpotShapesArray = penpot.selection;// We need to make sure that something is selected, and that the selected shape is a groupif (penpotShapesArray.length && penpot.utils.types.isGroup(penpotShapesArray[0])) { penpot.ungroup(penpotShapesArray[0]);} - ``` - createRectangle: |- - ``` - createRectangle(): Rectangle - ``` - - Use this method to create the shape of a rectangle. Requires `content:write` permission. - - Returns Rectangle - - Example - ``` - const shape = penpot.createRectangle();// just change the values like thisshape.name = "Example rectangle";// for solid colorshape.fills = [{ fillColor: "#7EFFF5" }];// for linear gradient colorshape.fills = [{ fillColorGradient: { "type": "linear", "startX": 0.5, "startY": 0, "endX": 0.5, "endY": 1, "width": 1, "stops": [ { "color": "#003ae9", "opacity": 1, "offset": 0 }, { "color": "#003ae9", "opacity": 0, "offset": 1 } ] }}];// for a image fillconst imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');shape.fills = [{ fillOpacity: 1, fillImage: imageData }];shape.borderRadius = 8;shape.strokes = [ { strokeColor: "#2e3434", strokeStyle: "solid", strokeWidth: 2, strokeAlignment: "center", },]; - ``` - createBoard: |- - ``` - createBoard(): Board - ``` - - Use this method to create a board. This is the first step before anything else, the container. Requires `content:write` permission. - Then you can add a gridlayout, flexlayout or add a shape inside the board. - Just a heads-up: board is a board in Penpot UI. - - Returns Board - - Example - ``` - const board = penpot.createBoard();// to add grid layoutboard.addGridLayout();// to add flex layoutboard.addFlexLayout();// to create a shape inside the boardconst shape = penpot.createRectangle();board.appendChild(shape); - ``` - createEllipse: |- - ``` - createEllipse(): Ellipse - ``` - - Use this method to create the shape of an ellipse. Requires `content:write` permission. - - Returns Ellipse - - Example - ``` - const shape = penpot.createEllipse();// just change the values like thisshape.name = "Example ellipse";// for solid colorshape.fills = [{ fillColor: "#7EFFF5" }];// for linear gradient colorshape.fills = [{ fillColorGradient: { "type": "linear", "startX": 0.5, "startY": 0, "endX": 0.5, "endY": 1, "width": 1, "stops": [ { "color": "#003ae9", "opacity": 1, "offset": 0 }, { "color": "#003ae9", "opacity": 0, "offset": 1 } ] }}];// for an image fillconst imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');shape.fills = [{ fillOpacity: 1, fillImage: imageData }];shape.strokes = [ { strokeColor: "#2e3434", strokeStyle: "solid", strokeWidth: 2, strokeAlignment: "center", },]; - ``` - createPath: |- - ``` - createPath(): Path - ``` - - Use this method to create a path. Requires `content:write` permission. - - Returns Path - - Example - ``` - const path = penpot.createPath();path.name = "My path";// for solid colorpath.fills = [{ fillColor: "#7EFFF5" }]; - ``` - createBoolean: |- - ``` - createBoolean(boolType: BooleanType, shapes: Shape[]): Boolean | null - ``` - - Creates a Boolean shape based on the specified boolean operation and shapes. Requires `content:write` permission. - - Parameters - - * boolType: BooleanType - - The type of boolean operation ('union', 'difference', 'exclude', 'intersection'). - * shapes: Shape[] - - An array of shapes to perform the boolean operation on. - - Returns Boolean | null - - Returns the newly created Boolean shape resulting from the boolean operation. - - Example - ``` - const booleanShape = context.createBoolean('union', [shape1, shape2]); - ``` - createShapeFromSvg: |- - ``` - createShapeFromSvg(svgString: string): Group | null - ``` - - Creates a Group from an SVG string. Requires `content:write` permission. - - Parameters - - * svgString: string - - The SVG string representing the shapes to be converted into a group. - - Returns Group | null - - Returns the newly created Group containing the shapes from the SVG. - - Example - ``` - const svgGroup = context.createShapeFromSvg('...'); - ``` - createShapeFromSvgWithImages: |- - ``` - createShapeFromSvgWithImages(svgString: string): Promise - ``` - - Creates a Group from an SVG string. The SVG can have images and the method returns - a Promise because the shape will be available after all images are uploaded. - Requires `content:write` permission. - - Parameters - - * svgString: string - - The SVG string representing the shapes to be converted into a group. - - Returns Promise - - Returns a promise with the newly created Group containing the shapes from the SVG. - - Example - ``` - const svgGroup = await context.createShapeFromSvgWithImages('...'); - ``` - createText: |- - ``` - createText(text: string): Text | null - ``` - - Creates a Text shape with the specified text content. Requires `content:write` permission. - - Parameters - - * text: string - - The text content for the Text shape. Must be a non-empty string. - - Returns Text | null - - Returns the new created shape. Returns null if an empty string is provided or the shape couldn't be created. - - Example - ``` - const board = penpot.createBoard();let text;text = penpot.createText();// just change the values like thistext.growType = 'auto-height';text.fontFamily = 'Work Sans';text.fontSize = '12';text.fills = [{fillColor: '#9f05ff', fillOpacity: 1}];text.strokes = [{strokeOpacity: 1, strokeStyle: 'solid', strokeWidth: 2, strokeColor: '#deabff', strokeAlignment: 'outer'}];board.appendChild(text); - ``` - generateMarkup: |- - ``` - generateMarkup(shapes: Shape[], options?: { type?: "html" | "svg" }): string - ``` - - Generates markup for the given shapes. Requires `content:read` permission - - Parameters - - * shapes: Shape[] - - the shapes to generate the markup for - * options: { type?: "html" | "svg" } - - `type` of the markup to generate. Defaults to `'html'`. - - Returns string - - Example - ``` - const markup = context.generateMarkup(shapes, { type: 'html' });console.log(markup); - ``` - generateStyle: |- - ``` - generateStyle( - shapes: Shape[], - options?: { - type?: "css"; - withPrelude?: boolean; - includeChildren?: boolean; - }, - ): string - ``` - - Generates styles for the given shapes. Requires `content:read` permission - - Parameters - - * shapes: Shape[] - - the shapes to generate the styles for - * options: { type?: "css"; withPrelude?: boolean; includeChildren?: boolean } - - `type` of the styles to generate (defaults to `'css'`), `withPrelude` to include the style prelude - (defaults to `false`) and `includeChildren` to also generate the styles for the shape children (defaults to `true`). - - Returns string - - Example - ``` - const styles = context.generateStyle(shapes, { type: 'css' });console.log(styles); - ``` - generateFontFaces: |- - ``` - generateFontFaces(shapes: Shape[]): Promise - ``` - - Generates the fontfaces styles necessaries to render the shapes. - Requires `content:read` permission - - Parameters - - * shapes: Shape[] - - Returns Promise - - Example - ``` - const fontfaces = context.generateFontFaces(penpot.selection);console.log(fontfaces); - ``` - openViewer: |- - ``` - openViewer(): void - ``` - - Opens the viewer section. Requires `content:read` permission. - - Returns void - createPage: |- - ``` - createPage(): Page - ``` - - Creates a new page and returns it. Requires `content:write` permission. - - IMPORTANT: creating a page does **not** make it the active page. - To build content inside the new page, activate it first with - Context.openPage (and `await` it) before mutate shapes: - - Returns Page - - Example - ``` - const page = penpot.createPage();page.name = 'New Page';await penpot.openPage(page); // make the new page active firstconst board = penpot.createBoard();board.resize(375, 812);page.root.appendChild(board); - ``` - openPage: |- - ``` - openPage(page: string | Page, newWindow?: boolean): Promise - ``` - - Changes the current open page to the given page, making it the **active page**. - The active page is the one all shape creation and structural operations - (`createBoard`, `appendChild`, `insertChild`, property setters, etc.) act upon, - so call this (and `await` it) after Context.createPage before adding - shapes to the newly created page. Requires `content:read` permission. - - Parameters - - * page: string | Page - - the page to open (a Page object or a page UUID string) - * newWindow: boolean - - if true opens the page in a new window, defaults to false - - Returns Promise - - Example - ``` - await context.openPage(page); - ``` - alignHorizontal: |- - ``` - alignHorizontal(shapes: Shape[], direction: "center" | "left" | "right"): void - ``` - - Aligning will move all the selected layers to a position relative to one - of them in the horizontal direction. - - Parameters - - * shapes: Shape[] - - to align - * direction: "center" | "left" | "right" - - where the shapes will be aligned - - Returns void - alignVertical: |- - ``` - alignVertical(shapes: Shape[], direction: "center" | "top" | "bottom"): void - ``` - - Aligning will move all the selected layers to a position relative to one - of them in the vertical direction. - - Parameters - - * shapes: Shape[] - - to align - * direction: "center" | "top" | "bottom" - - where the shapes will be aligned - - Returns void - distributeHorizontal: |- - ``` - distributeHorizontal(shapes: Shape[]): void - ``` - - Distributing objects to position them horizontally with equal distances between them. - - Parameters - - * shapes: Shape[] - - to distribute - - Returns void - distributeVertical: |- - ``` - distributeVertical(shapes: Shape[]): void - ``` - - Distributing objects to position them vertically with equal distances between them. - - Parameters - - * shapes: Shape[] - - to distribute - - Returns void - flatten: |- - ``` - flatten(shapes: Shape[]): Path[] - ``` - - Converts the shapes into Paths. If the shapes are complex will put together - all its paths into one. - - Parameters - - * shapes: Shape[] - - to flatten - - Returns Path[] - createVariantFromComponents: |- - ``` - createVariantFromComponents(shapes: Board[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it - with the contextual menu on the Penpot interface. - All the shapes passed as arguments should be main instances. - - Parameters - - * shapes: Board[] - - A list of main instances of the components to combine. - - Returns VariantContainer - - The variant container created -ActiveUser: - overview: |- - Interface ActiveUser - ==================== - - Represents an active user in Penpot, extending the `User` interface. - This interface includes additional properties specific to active users. - - ``` - interface ActiveUser { - position?: { x: number; y: number }; - zoom?: number; - id: string; - name?: string; - avatarUrl?: string; - color: string; - sessionId?: string; - } - ``` - - Hierarchy (View Summary) - - * User - + ActiveUser - - Referenced by: Context, Penpot - members: - Properties: - position: |- - ``` - position?: { x: number; y: number } - ``` - - The position of the active user. - - Example - ``` - const userPosition = activeUser.position;console.log(userPosition); - ``` - zoom: |- - ``` - readonly zoom?: number - ``` - - The zoom level of the active user. - - Example - ``` - const userZoom = activeUser.zoom;console.log(userZoom); - ``` - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the user. - - Example - ``` - const userId = user.id;console.log(userId); - ``` - name: |- - ``` - readonly name?: string - ``` - - The name of the user. - - Example - ``` - const userName = user.name;console.log(userName); - ``` - avatarUrl: |- - ``` - readonly avatarUrl?: string - ``` - - The URL of the user's avatar image. - - Example - ``` - const avatarUrl = user.avatarUrl;console.log(avatarUrl); - ``` - color: |- - ``` - readonly color: string - ``` - - The color associated with the user. - - Example - ``` - const userColor = user.color;console.log(userColor); - ``` - sessionId: |- - ``` - readonly sessionId?: string - ``` - - The session ID of the user. - - Example - ``` - const sessionId = user.sessionId;console.log(sessionId); - ``` -Blur: - overview: |- - Interface Blur - ============== - - Represents blur properties in Penpot. - This interface includes properties for defining the type and intensity of a blur effect, along with its visibility. - - ``` - interface Blur { - id?: string; - value?: number; - hidden?: boolean; - } - ``` - - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer - members: - Properties: - id: |- - ``` - id?: string - ``` - - The optional unique identifier for the blur effect. - value: |- - ``` - value?: number - ``` - - The optional intensity value of the blur effect. - hidden: |- - ``` - hidden?: boolean - ``` - - Specifies whether the blur effect is hidden. - Defaults to false if omitted. -Board: - overview: |- - Interface Board - =============== - - Represents a board in Penpot. - This interface extends `ShapeBase` and includes properties and methods specific to board. - - ``` - interface Board { - type: "board"; - clipContent: boolean; - showInViewMode: boolean; - grid?: GridLayout; - flex?: FlexLayout; - guides: Guide[]; - rulerGuides: RulerGuide[]; - horizontalSizing?: "auto" | "fix"; - verticalSizing?: "auto" | "fix"; - fills: Fill[]; - children: Shape[]; - appendChild(child: Shape): void; - insertChild(index: number, child: Shape): void; - addFlexLayout(): FlexLayout; - addGridLayout(): GridLayout; - addRulerGuide( - orientation: RulerGuideOrientation, - value: number, - ): RulerGuide; - removeRulerGuide(guide: RulerGuide): void; - isVariantContainer(): boolean; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - id: string; - name: string; - parent: Shape | null; - parentIndex: number; - x: number; - y: number; - width: number; - height: number; - bounds: Bounds; - center: Point; - blocked: boolean; - hidden: boolean; - visible: boolean; - proportionLock: boolean; - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; - fixedWhenScrolling: boolean; - borderRadius: number; - borderRadiusTopLeft: number; - borderRadiusTopRight: number; - borderRadiusBottomRight: number; - borderRadiusBottomLeft: number; - opacity: number; - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity"; - shadows: Shadow[]; - blur?: Blur; - backgroundBlur?: Blur; - exports: Export[]; - boardX: number; - boardY: number; - parentX: number; - parentY: number; - flipX: boolean; - flipY: boolean; - rotation: number; - strokes: Stroke[]; - layoutChild?: LayoutChildProperties; - layoutCell?: LayoutCellProperties; - setParentIndex(index: number): void; - tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - }; - isComponentInstance(): boolean; - isComponentMainInstance(): boolean; - isComponentCopyInstance(): boolean; - isComponentRoot(): boolean; - isComponentHead(): boolean; - componentRefShape(): Shape | null; - componentRoot(): Shape | null; - componentHead(): Shape | null; - component(): LibraryComponent | null; - detach(): void; - swapComponent(component: LibraryComponent): void; - resetOverrides(): void; - switchVariant(pos: number, value: string): void; - combineAsVariants(ids: string[]): VariantContainer; - isVariantHead(): boolean; - resize(width: number, height: number): void; - rotate(angle: number, center?: { x: number; y: number } | null): void; - bringToFront(): void; - bringForward(): void; - sendToBack(): void; - sendBackward(): void; - export(config: Export): Promise>; - interactions: Interaction[]; - addInteraction( - trigger: Trigger, - action: Action, - delay?: number, - ): Interaction; - removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties?: TokenProperty[]): void; - clone(): Shape; - remove(): void; - } - ``` - - Hierarchy (View Summary) - - * ShapeBase - + Board - - VariantContainer - - Referenced by: CloseOverlay, CommentThread, Context, ContextTypesUtils, Flow, NavigateTo, OpenOverlay, OverlayAction, Page, Penpot, RulerGuide, Shape, ToggleOverlay - members: - Properties: - type: |- - ``` - readonly type: "board" - ``` - - The type of the shape, which is always 'board' for boards. - clipContent: |- - ``` - clipContent: boolean - ``` - - When true the board will clip the children inside - showInViewMode: |- - ``` - showInViewMode: boolean - ``` - - When true the board will be displayed in the view mode - grid: |- - ``` - readonly grid?: GridLayout - ``` - - The grid layout configuration of the board, if applicable. - flex: |- - ``` - readonly flex?: FlexLayout - ``` - - The flex layout configuration of the board, if applicable. - guides: |- - ``` - guides: Guide[] - ``` - - The guides associated with the board. - rulerGuides: |- - ``` - readonly rulerGuides: RulerGuide[] - ``` - - The ruler guides attached to the board - horizontalSizing: |- - ``` - horizontalSizing?: "auto" | "fix" - ``` - - The horizontal sizing behavior of the board. - It can be one of the following values: - - * 'fix': The container has its own intrinsic fixed size. - * 'auto': The container fits the content. - verticalSizing: |- - ``` - verticalSizing?: "auto" | "fix" - ``` - - The vertical sizing behavior of the board. - It can be one of the following values: - - * 'fix': The container has its own intrinsic fixed size. - * 'auto': The container fits the content. - fills: |- - ``` - fills: Fill[] - ``` - - The fills applied to the shape. - - Overrides ShapeBase.fills - children: |- - ``` - children: Shape[] - ``` - - The children shapes contained within the board. - When writing into this property, you can only reorder the shapes, not - changing the structure. If the new shapes don't match the current shapes - it will give a validation error. - - Example - ``` - board.children = board.children.reverse(); - ``` - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the shape. - name: |- - ``` - name: string - ``` - - The name of the shape. - parent: |- - ``` - readonly parent: Shape | null - ``` - - The parent shape. If the shape is the first level the parent will be the root shape. - For the root shape the parent is null - parentIndex: |- - ``` - readonly parentIndex: number - ``` - - Returns the index of the current shape in the parent - x: |- - ``` - x: number - ``` - - The x-coordinate of the shape's position. - y: |- - ``` - y: number - ``` - - The y-coordinate of the shape's position. - width: |- - ``` - readonly width: number - ``` - - The width of the shape. - height: |- - ``` - readonly height: number - ``` - - The height of the shape. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - The bounding box surrounding the current shape - center: |- - ``` - readonly center: Point - ``` - - The geometric center of the shape - blocked: |- - ``` - blocked: boolean - ``` - - Indicates whether the shape is blocked. - hidden: |- - ``` - hidden: boolean - ``` - - Indicates whether the shape is hidden. - visible: |- - ``` - visible: boolean - ``` - - Indicates whether the shape is visible. - proportionLock: |- - ``` - proportionLock: boolean - ``` - - Indicates whether the shape has proportion lock enabled. - constraintsHorizontal: |- - ``` - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale" - ``` - - The horizontal constraints applied to the shape. - constraintsVertical: |- - ``` - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom" - ``` - - The vertical constraints applied to the shape. - fixedWhenScrolling: |- - ``` - fixedWhenScrolling: boolean - ``` - - Indicates whether the shape stays fixed in place while scrolling. - borderRadius: |- - ``` - borderRadius: number - ``` - - The border radius of the shape. - borderRadiusTopLeft: |- - ``` - borderRadiusTopLeft: number - ``` - - The border radius of the top-left corner of the shape. - borderRadiusTopRight: |- - ``` - borderRadiusTopRight: number - ``` - - The border radius of the top-right corner of the shape. - borderRadiusBottomRight: |- - ``` - borderRadiusBottomRight: number - ``` - - The border radius of the bottom-right corner of the shape. - borderRadiusBottomLeft: |- - ``` - borderRadiusBottomLeft: number - ``` - - The border radius of the bottom-left corner of the shape. - opacity: |- - ``` - opacity: number - ``` - - The opacity of the shape. - blendMode: |- - ``` - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity" - ``` - - The blend mode applied to the shape. - shadows: |- - ``` - shadows: Shadow[] - ``` - - The shadows applied to the shape. - blur: |- - ``` - blur?: Blur - ``` - - The blur effect applied to the shape. - backgroundBlur: |- - ``` - backgroundBlur?: Blur - ``` - - The background blur effect applied to the shape. - Background blur creates a blur effect on the content behind the shape, - rather than on the shape's own content. - exports: |- - ``` - exports: Export[] - ``` - - The export settings of the shape. - boardX: |- - ``` - boardX: number - ``` - - The x-coordinate of the shape relative to its board. - boardY: |- - ``` - boardY: number - ``` - - The y-coordinate of the shape relative to its board. - parentX: |- - ``` - parentX: number - ``` - - The x-coordinate of the shape relative to its parent. - parentY: |- - ``` - parentY: number - ``` - - The y-coordinate of the shape relative to its parent. - flipX: |- - ``` - flipX: boolean - ``` - - Indicates whether the shape is flipped horizontally. - flipY: |- - ``` - flipY: boolean - ``` - - Indicates whether the shape is flipped vertically. - rotation: |- - ``` - rotation: number - ``` - - The rotation in degrees of the shape with respect to its center. - strokes: |- - ``` - strokes: Stroke[] - ``` - - The strokes applied to the shape. - layoutChild: |- - ``` - readonly layoutChild?: LayoutChildProperties - ``` - - Layout properties for children of the shape. - layoutCell: |- - ``` - readonly layoutCell?: LayoutCellProperties - ``` - - Layout properties for cells in a grid layout. - tokens: |- - ``` - readonly tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - } - ``` - - The design tokens applied to this shape. - It's a map property name -> token name. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - interactions: |- - ``` - readonly interactions: Interaction[] - ``` - - The interactions for the current shape. - Methods: - appendChild: |- - ``` - appendChild(child: Shape): void - ``` - - Appends a child shape to the board. - - Parameters - - * child: Shape - - The child shape to append. - - Returns void - - Example - ``` - board.appendChild(childShape); - ``` - insertChild: |- - ``` - insertChild(index: number, child: Shape): void - ``` - - Inserts a child shape at the specified index within the board. - - Parameters - - * index: number - - The index at which to insert the child shape. - * child: Shape - - The child shape to insert. - - Returns void - - Example - ``` - board.insertChild(0, childShape); - ``` - addFlexLayout: |- - ``` - addFlexLayout(): FlexLayout - ``` - - Adds a flex layout configuration to the board (so it's necessary to create a board first of all). - - Returns FlexLayout - - Returns the flex layout configuration added to the board. - - Example - ``` - const board = penpot.createBoard();const flex = board.addFlexLayout();// You can change the flex properties as follows.flex.dir = "column";flex.wrap = "wrap";flex.alignItems = "center";flex.justifyContent = "center";flex.horizontalSizing = "fill";flex.verticalSizing = "fill"; - ``` - addGridLayout: |- - ``` - addGridLayout(): GridLayout - ``` - - Adds a grid layout configuration to the board (so it's necessary to create a board first of all). You can add rows and columns, check addRow/addColumn. - - Returns GridLayout - - Returns the grid layout configuration added to the board. - - Example - ``` - const board = penpot.createBoard();const grid = board.addGridLayout();// You can change the grid properties as follows.grid.alignItems = "center";grid.justifyItems = "start";grid.rowGap = 10;grid.columnGap = 10;grid.verticalPadding = 5;grid.horizontalPadding = 5; - ``` - addRulerGuide: |- - ``` - addRulerGuide(orientation: RulerGuideOrientation, value: number): RulerGuide - ``` - - Creates a new ruler guide attached to the board. - - Parameters - - * orientation: RulerGuideOrientation - - `horizontal` or `vertical` - * value: number - - the position of the guide relative to the board - - Returns RulerGuide - removeRulerGuide: |- - ``` - removeRulerGuide(guide: RulerGuide): void - ``` - - Removes the `guide` from the board. - - Parameters - - * guide: RulerGuide - - Returns void - isVariantContainer: |- - ``` - isVariantContainer(): boolean - ``` - - Returns boolean - - Returns true when the current board is a VariantContainer - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` - setParentIndex: |- - ``` - setParentIndex(index: number): void - ``` - - Changes the index inside the parent of the current shape. - This method will shift the indexes of the shapes around that position to - match the index. - If the index is greater than the number of elements it will positioned last. - - Parameters - - * index: number - - the new index for the shape to be in - - Returns void - isComponentInstance: |- - ``` - isComponentInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component instance - isComponentMainInstance: |- - ``` - isComponentMainInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **main** instance - isComponentCopyInstance: |- - ``` - isComponentCopyInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **copy** instance - isComponentRoot: |- - ``` - isComponentRoot(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the root of a component tree - isComponentHead: |- - ``` - isComponentHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure - componentRefShape: |- - ``` - componentRefShape(): Shape | null - ``` - - Returns Shape | null - - Returns the equivalent shape in the component main instance. If the current shape is inside a - main instance will return `null`; - componentRoot: |- - ``` - componentRoot(): Shape | null - ``` - - Returns Shape | null - - Returns the root of the component tree structure for the current shape. If the current shape - is already a root will return itself. - componentHead: |- - ``` - componentHead(): Shape | null - ``` - - Returns Shape | null - - Returns the head of the component tree structure for the current shape. If the current shape - is already a head will return itself. - component: |- - ``` - component(): LibraryComponent | null - ``` - - Returns LibraryComponent | null - - If the shape is a component instance, returns the reference to the component associated - otherwise will return null - detach: |- - ``` - detach(): void - ``` - - If the current shape is a component it will remove the component information and leave the - shape as a "basic shape" - - Returns void - swapComponent: |- - ``` - swapComponent(component: LibraryComponent): void - ``` - - Swaps the component instance for another component, keeping the overrides - when possible. Similar to the "swap component" action on the Penpot interface. - The current shape must be a component copy instance. - - Parameters - - * component: LibraryComponent - - The new component to replace the current one - - Returns void - resetOverrides: |- - ``` - resetOverrides(): void - ``` - - Resets the overrides of the component copy, restoring all its attributes - (and those of its children) to the ones in the linked main component. - Similar to the "reset overrides" action on the Penpot interface. - The current shape must be a component copy instance. - - Returns void - switchVariant: |- - ``` - switchVariant(pos: number, value: string): void - ``` - - Switch a VariantComponent copy to the nearest one that has the specified property value - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - combineAsVariants: |- - ``` - combineAsVariants(ids: string[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu - on the Penpot interface. - The current shape must be a component main instance. - - Parameters - - * ids: string[] - - A list of ids of the main instances of the components to combine with this one. - - Returns VariantContainer - - The variant container created - isVariantHead: |- - ``` - isVariantHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure, - and that component is a VariantComponent - resize: |- - ``` - resize(width: number, height: number): void - ``` - - Resizes the shape to the specified width and height. - - Parameters - - * width: number - - The new width of the shape. - * height: number - - The new height of the shape. - - Returns void - - Example - ``` - shape.resize(200, 100); - ``` - rotate: |- - ``` - rotate(angle: number, center?: { x: number; y: number } | null): void - ``` - - Rotates the shape in relation with the given center. - - Parameters - - * angle: number - - Angle in degrees to rotate. - * center: { x: number; y: number } | null - - Center of the transform rotation. If not sent it will use the geometric center of the shape. - - Returns void - - Example - ``` - shape.rotate(45); - ``` - bringToFront: |- - ``` - bringToFront(): void - ``` - - Moves the current shape to the front of its siblings - - Returns void - bringForward: |- - ``` - bringForward(): void - ``` - - Moves the current shape one position forward in its list of siblings - - Returns void - sendToBack: |- - ``` - sendToBack(): void - ``` - - Moves the current shape to the back of its siblings - - Returns void - sendBackward: |- - ``` - sendBackward(): void - ``` - - Moves the current shape one position backwards in its list of siblings - - Returns void - export: |- - ``` - export(config: Export): Promise> - ``` - - Generates an export from the current shape. - - Parameters - - * config: Export - - Returns Promise> - - Example - ``` - shape.export({ type: 'png', scale: 2 }); - ``` - addInteraction: |- - ``` - addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction - ``` - - Adds a new interaction to the shape. - - If the interaction starts a flow (for example a `navigate-to` action) and - the shape's board is not already part of any flow, a new flow starting at - that board is created automatically, matching the behavior of the editor. - - Parameters - - * trigger: Trigger - - defines the conditions under which the action will be triggered - * action: Action - - defines what will be executed when the trigger happens - * delay: number - - for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. - - Returns Interaction - - Example - ``` - shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); - ``` - removeInteraction: |- - ``` - removeInteraction(interaction: Interaction): void - ``` - - Removes the interaction from the shape. - - Parameters - - * interaction: Interaction - - is the interaction to remove from the shape - - Returns void - - Example - ``` - shape.removeInteraction(interaction); - ``` - applyToken: |- - ``` - applyToken(token: Token, properties?: TokenProperty[]): void - ``` - - Applies one design token to one or more properties of the shape. - - Parameters - - * token: Token - - is the Token to apply - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - clone: |- - ``` - clone(): Shape - ``` - - Creates a clone of the shape. - - Returns Shape - - Returns a new instance of the shape with identical properties. - remove: |- - ``` - remove(): void - ``` - - Removes the shape from its parent. - - Returns void -VariantContainer: - overview: |- - Interface VariantContainer - ========================== - - Represents a VariantContainer in Penpot - This interface extends `Board` and includes properties and methods specific to VariantContainer. - - ``` - interface VariantContainer { - type: "board"; - clipContent: boolean; - showInViewMode: boolean; - grid?: GridLayout; - flex?: FlexLayout; - guides: Guide[]; - rulerGuides: RulerGuide[]; - horizontalSizing?: "auto" | "fix"; - verticalSizing?: "auto" | "fix"; - fills: Fill[]; - children: Shape[]; - appendChild(child: Shape): void; - insertChild(index: number, child: Shape): void; - addFlexLayout(): FlexLayout; - addGridLayout(): GridLayout; - addRulerGuide( - orientation: RulerGuideOrientation, - value: number, - ): RulerGuide; - removeRulerGuide(guide: RulerGuide): void; - isVariantContainer(): boolean; - variants: Variants | null; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - id: string; - name: string; - parent: Shape | null; - parentIndex: number; - x: number; - y: number; - width: number; - height: number; - bounds: Bounds; - center: Point; - blocked: boolean; - hidden: boolean; - visible: boolean; - proportionLock: boolean; - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; - fixedWhenScrolling: boolean; - borderRadius: number; - borderRadiusTopLeft: number; - borderRadiusTopRight: number; - borderRadiusBottomRight: number; - borderRadiusBottomLeft: number; - opacity: number; - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity"; - shadows: Shadow[]; - blur?: Blur; - backgroundBlur?: Blur; - exports: Export[]; - boardX: number; - boardY: number; - parentX: number; - parentY: number; - flipX: boolean; - flipY: boolean; - rotation: number; - strokes: Stroke[]; - layoutChild?: LayoutChildProperties; - layoutCell?: LayoutCellProperties; - setParentIndex(index: number): void; - tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - }; - isComponentInstance(): boolean; - isComponentMainInstance(): boolean; - isComponentCopyInstance(): boolean; - isComponentRoot(): boolean; - isComponentHead(): boolean; - componentRefShape(): Shape | null; - componentRoot(): Shape | null; - componentHead(): Shape | null; - component(): LibraryComponent | null; - detach(): void; - swapComponent(component: LibraryComponent): void; - resetOverrides(): void; - switchVariant(pos: number, value: string): void; - combineAsVariants(ids: string[]): VariantContainer; - isVariantHead(): boolean; - resize(width: number, height: number): void; - rotate(angle: number, center?: { x: number; y: number } | null): void; - bringToFront(): void; - bringForward(): void; - sendToBack(): void; - sendBackward(): void; - export(config: Export): Promise>; - interactions: Interaction[]; - addInteraction( - trigger: Trigger, - action: Action, - delay?: number, - ): Interaction; - removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties?: TokenProperty[]): void; - clone(): Shape; - remove(): void; - } - ``` - - Hierarchy (View Summary) - - * Board - + VariantContainer - - Referenced by: Board, Boolean, Context, ContextTypesUtils, Ellipse, Group, Image, Path, Penpot, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer - members: - Properties: - type: |- - ``` - readonly type: "board" - ``` - - The type of the shape, which is always 'board' for boards. - clipContent: |- - ``` - clipContent: boolean - ``` - - When true the board will clip the children inside - showInViewMode: |- - ``` - showInViewMode: boolean - ``` - - When true the board will be displayed in the view mode - grid: |- - ``` - readonly grid?: GridLayout - ``` - - The grid layout configuration of the board, if applicable. - flex: |- - ``` - readonly flex?: FlexLayout - ``` - - The flex layout configuration of the board, if applicable. - guides: |- - ``` - guides: Guide[] - ``` - - The guides associated with the board. - rulerGuides: |- - ``` - readonly rulerGuides: RulerGuide[] - ``` - - The ruler guides attached to the board - horizontalSizing: |- - ``` - horizontalSizing?: "auto" | "fix" - ``` - - The horizontal sizing behavior of the board. - It can be one of the following values: - - * 'fix': The container has its own intrinsic fixed size. - * 'auto': The container fits the content. - verticalSizing: |- - ``` - verticalSizing?: "auto" | "fix" - ``` - - The vertical sizing behavior of the board. - It can be one of the following values: - - * 'fix': The container has its own intrinsic fixed size. - * 'auto': The container fits the content. - fills: |- - ``` - fills: Fill[] - ``` - - The fills applied to the shape. - children: |- - ``` - children: Shape[] - ``` - - The children shapes contained within the board. - When writing into this property, you can only reorder the shapes, not - changing the structure. If the new shapes don't match the current shapes - it will give a validation error. - - Example - ``` - board.children = board.children.reverse(); - ``` - variants: |- - ``` - readonly variants: Variants | null - ``` - - Access to the Variant interface, for attributes and actions over the full Variant (not only this VariantContainer) - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the shape. - name: |- - ``` - name: string - ``` - - The name of the shape. - parent: |- - ``` - readonly parent: Shape | null - ``` - - The parent shape. If the shape is the first level the parent will be the root shape. - For the root shape the parent is null - parentIndex: |- - ``` - readonly parentIndex: number - ``` - - Returns the index of the current shape in the parent - x: |- - ``` - x: number - ``` - - The x-coordinate of the shape's position. - y: |- - ``` - y: number - ``` - - The y-coordinate of the shape's position. - width: |- - ``` - readonly width: number - ``` - - The width of the shape. - height: |- - ``` - readonly height: number - ``` - - The height of the shape. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - The bounding box surrounding the current shape - center: |- - ``` - readonly center: Point - ``` - - The geometric center of the shape - blocked: |- - ``` - blocked: boolean - ``` - - Indicates whether the shape is blocked. - hidden: |- - ``` - hidden: boolean - ``` - - Indicates whether the shape is hidden. - visible: |- - ``` - visible: boolean - ``` - - Indicates whether the shape is visible. - proportionLock: |- - ``` - proportionLock: boolean - ``` - - Indicates whether the shape has proportion lock enabled. - constraintsHorizontal: |- - ``` - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale" - ``` - - The horizontal constraints applied to the shape. - constraintsVertical: |- - ``` - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom" - ``` - - The vertical constraints applied to the shape. - fixedWhenScrolling: |- - ``` - fixedWhenScrolling: boolean - ``` - - Indicates whether the shape stays fixed in place while scrolling. - borderRadius: |- - ``` - borderRadius: number - ``` - - The border radius of the shape. - borderRadiusTopLeft: |- - ``` - borderRadiusTopLeft: number - ``` - - The border radius of the top-left corner of the shape. - borderRadiusTopRight: |- - ``` - borderRadiusTopRight: number - ``` - - The border radius of the top-right corner of the shape. - borderRadiusBottomRight: |- - ``` - borderRadiusBottomRight: number - ``` - - The border radius of the bottom-right corner of the shape. - borderRadiusBottomLeft: |- - ``` - borderRadiusBottomLeft: number - ``` - - The border radius of the bottom-left corner of the shape. - opacity: |- - ``` - opacity: number - ``` - - The opacity of the shape. - blendMode: |- - ``` - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity" - ``` - - The blend mode applied to the shape. - shadows: |- - ``` - shadows: Shadow[] - ``` - - The shadows applied to the shape. - blur: |- - ``` - blur?: Blur - ``` - - The blur effect applied to the shape. - backgroundBlur: |- - ``` - backgroundBlur?: Blur - ``` - - The background blur effect applied to the shape. - Background blur creates a blur effect on the content behind the shape, - rather than on the shape's own content. - exports: |- - ``` - exports: Export[] - ``` - - The export settings of the shape. - boardX: |- - ``` - boardX: number - ``` - - The x-coordinate of the shape relative to its board. - boardY: |- - ``` - boardY: number - ``` - - The y-coordinate of the shape relative to its board. - parentX: |- - ``` - parentX: number - ``` - - The x-coordinate of the shape relative to its parent. - parentY: |- - ``` - parentY: number - ``` - - The y-coordinate of the shape relative to its parent. - flipX: |- - ``` - flipX: boolean - ``` - - Indicates whether the shape is flipped horizontally. - flipY: |- - ``` - flipY: boolean - ``` - - Indicates whether the shape is flipped vertically. - rotation: |- - ``` - rotation: number - ``` - - The rotation in degrees of the shape with respect to its center. - strokes: |- - ``` - strokes: Stroke[] - ``` - - The strokes applied to the shape. - layoutChild: |- - ``` - readonly layoutChild?: LayoutChildProperties - ``` - - Layout properties for children of the shape. - layoutCell: |- - ``` - readonly layoutCell?: LayoutCellProperties - ``` - - Layout properties for cells in a grid layout. - tokens: |- - ``` - readonly tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - } - ``` - - The design tokens applied to this shape. - It's a map property name -> token name. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - interactions: |- - ``` - readonly interactions: Interaction[] - ``` - - The interactions for the current shape. - Methods: - appendChild: |- - ``` - appendChild(child: Shape): void - ``` - - Appends a child shape to the board. - - Parameters - - * child: Shape - - The child shape to append. - - Returns void - - Example - ``` - board.appendChild(childShape); - ``` - insertChild: |- - ``` - insertChild(index: number, child: Shape): void - ``` - - Inserts a child shape at the specified index within the board. - - Parameters - - * index: number - - The index at which to insert the child shape. - * child: Shape - - The child shape to insert. - - Returns void - - Example - ``` - board.insertChild(0, childShape); - ``` - addFlexLayout: |- - ``` - addFlexLayout(): FlexLayout - ``` - - Adds a flex layout configuration to the board (so it's necessary to create a board first of all). - - Returns FlexLayout - - Returns the flex layout configuration added to the board. - - Example - ``` - const board = penpot.createBoard();const flex = board.addFlexLayout();// You can change the flex properties as follows.flex.dir = "column";flex.wrap = "wrap";flex.alignItems = "center";flex.justifyContent = "center";flex.horizontalSizing = "fill";flex.verticalSizing = "fill"; - ``` - addGridLayout: |- - ``` - addGridLayout(): GridLayout - ``` - - Adds a grid layout configuration to the board (so it's necessary to create a board first of all). You can add rows and columns, check addRow/addColumn. - - Returns GridLayout - - Returns the grid layout configuration added to the board. - - Example - ``` - const board = penpot.createBoard();const grid = board.addGridLayout();// You can change the grid properties as follows.grid.alignItems = "center";grid.justifyItems = "start";grid.rowGap = 10;grid.columnGap = 10;grid.verticalPadding = 5;grid.horizontalPadding = 5; - ``` - addRulerGuide: |- - ``` - addRulerGuide(orientation: RulerGuideOrientation, value: number): RulerGuide - ``` - - Creates a new ruler guide attached to the board. - - Parameters - - * orientation: RulerGuideOrientation - - `horizontal` or `vertical` - * value: number - - the position of the guide relative to the board - - Returns RulerGuide - removeRulerGuide: |- - ``` - removeRulerGuide(guide: RulerGuide): void - ``` - - Removes the `guide` from the board. - - Parameters - - * guide: RulerGuide - - Returns void - isVariantContainer: |- - ``` - isVariantContainer(): boolean - ``` - - Returns boolean - - Returns true when the current board is a VariantContainer - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` - setParentIndex: |- - ``` - setParentIndex(index: number): void - ``` - - Changes the index inside the parent of the current shape. - This method will shift the indexes of the shapes around that position to - match the index. - If the index is greater than the number of elements it will positioned last. - - Parameters - - * index: number - - the new index for the shape to be in - - Returns void - isComponentInstance: |- - ``` - isComponentInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component instance - isComponentMainInstance: |- - ``` - isComponentMainInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **main** instance - isComponentCopyInstance: |- - ``` - isComponentCopyInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **copy** instance - isComponentRoot: |- - ``` - isComponentRoot(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the root of a component tree - isComponentHead: |- - ``` - isComponentHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure - componentRefShape: |- - ``` - componentRefShape(): Shape | null - ``` - - Returns Shape | null - - Returns the equivalent shape in the component main instance. If the current shape is inside a - main instance will return `null`; - componentRoot: |- - ``` - componentRoot(): Shape | null - ``` - - Returns Shape | null - - Returns the root of the component tree structure for the current shape. If the current shape - is already a root will return itself. - componentHead: |- - ``` - componentHead(): Shape | null - ``` - - Returns Shape | null - - Returns the head of the component tree structure for the current shape. If the current shape - is already a head will return itself. - component: |- - ``` - component(): LibraryComponent | null - ``` - - Returns LibraryComponent | null - - If the shape is a component instance, returns the reference to the component associated - otherwise will return null - detach: |- - ``` - detach(): void - ``` - - If the current shape is a component it will remove the component information and leave the - shape as a "basic shape" - - Returns void - swapComponent: |- - ``` - swapComponent(component: LibraryComponent): void - ``` - - Swaps the component instance for another component, keeping the overrides - when possible. Similar to the "swap component" action on the Penpot interface. - The current shape must be a component copy instance. - - Parameters - - * component: LibraryComponent - - The new component to replace the current one - - Returns void - resetOverrides: |- - ``` - resetOverrides(): void - ``` - - Resets the overrides of the component copy, restoring all its attributes - (and those of its children) to the ones in the linked main component. - Similar to the "reset overrides" action on the Penpot interface. - The current shape must be a component copy instance. - - Returns void - switchVariant: |- - ``` - switchVariant(pos: number, value: string): void - ``` - - Switch a VariantComponent copy to the nearest one that has the specified property value - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - combineAsVariants: |- - ``` - combineAsVariants(ids: string[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu - on the Penpot interface. - The current shape must be a component main instance. - - Parameters - - * ids: string[] - - A list of ids of the main instances of the components to combine with this one. - - Returns VariantContainer - - The variant container created - isVariantHead: |- - ``` - isVariantHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure, - and that component is a VariantComponent - resize: |- - ``` - resize(width: number, height: number): void - ``` - - Resizes the shape to the specified width and height. - - Parameters - - * width: number - - The new width of the shape. - * height: number - - The new height of the shape. - - Returns void - - Example - ``` - shape.resize(200, 100); - ``` - rotate: |- - ``` - rotate(angle: number, center?: { x: number; y: number } | null): void - ``` - - Rotates the shape in relation with the given center. - - Parameters - - * angle: number - - Angle in degrees to rotate. - * center: { x: number; y: number } | null - - Center of the transform rotation. If not sent it will use the geometric center of the shape. - - Returns void - - Example - ``` - shape.rotate(45); - ``` - bringToFront: |- - ``` - bringToFront(): void - ``` - - Moves the current shape to the front of its siblings - - Returns void - bringForward: |- - ``` - bringForward(): void - ``` - - Moves the current shape one position forward in its list of siblings - - Returns void - sendToBack: |- - ``` - sendToBack(): void - ``` - - Moves the current shape to the back of its siblings - - Returns void - sendBackward: |- - ``` - sendBackward(): void - ``` - - Moves the current shape one position backwards in its list of siblings - - Returns void - export: |- - ``` - export(config: Export): Promise> - ``` - - Generates an export from the current shape. - - Parameters - - * config: Export - - Returns Promise> - - Example - ``` - shape.export({ type: 'png', scale: 2 }); - ``` - addInteraction: |- - ``` - addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction - ``` - - Adds a new interaction to the shape. - - If the interaction starts a flow (for example a `navigate-to` action) and - the shape's board is not already part of any flow, a new flow starting at - that board is created automatically, matching the behavior of the editor. - - Parameters - - * trigger: Trigger - - defines the conditions under which the action will be triggered - * action: Action - - defines what will be executed when the trigger happens - * delay: number - - for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. - - Returns Interaction - - Example - ``` - shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); - ``` - removeInteraction: |- - ``` - removeInteraction(interaction: Interaction): void - ``` - - Removes the interaction from the shape. - - Parameters - - * interaction: Interaction - - is the interaction to remove from the shape - - Returns void - - Example - ``` - shape.removeInteraction(interaction); - ``` - applyToken: |- - ``` - applyToken(token: Token, properties?: TokenProperty[]): void - ``` - - Applies one design token to one or more properties of the shape. - - Parameters - - * token: Token - - is the Token to apply - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - clone: |- - ``` - clone(): Shape - ``` - - Creates a clone of the shape. - - Returns Shape - - Returns a new instance of the shape with identical properties. - remove: |- - ``` - remove(): void - ``` - - Removes the shape from its parent. - - Returns void -Boolean: - overview: |- - Interface Boolean - ================= - - Represents a boolean operation shape in Penpot. - This interface extends `ShapeBase` and includes properties and methods specific to boolean operations. - - ``` - interface Boolean { - type: "boolean"; - toD(): string; - content: string; - d: string; - commands: PathCommand[]; - fills: Fill[]; - children: Shape[]; - appendChild(child: Shape): void; - insertChild(index: number, child: Shape): void; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - id: string; - name: string; - parent: Shape | null; - parentIndex: number; - x: number; - y: number; - width: number; - height: number; - bounds: Bounds; - center: Point; - blocked: boolean; - hidden: boolean; - visible: boolean; - proportionLock: boolean; - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; - fixedWhenScrolling: boolean; - borderRadius: number; - borderRadiusTopLeft: number; - borderRadiusTopRight: number; - borderRadiusBottomRight: number; - borderRadiusBottomLeft: number; - opacity: number; - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity"; - shadows: Shadow[]; - blur?: Blur; - backgroundBlur?: Blur; - exports: Export[]; - boardX: number; - boardY: number; - parentX: number; - parentY: number; - flipX: boolean; - flipY: boolean; - rotation: number; - strokes: Stroke[]; - layoutChild?: LayoutChildProperties; - layoutCell?: LayoutCellProperties; - setParentIndex(index: number): void; - tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - }; - isComponentInstance(): boolean; - isComponentMainInstance(): boolean; - isComponentCopyInstance(): boolean; - isComponentRoot(): boolean; - isComponentHead(): boolean; - componentRefShape(): Shape | null; - componentRoot(): Shape | null; - componentHead(): Shape | null; - component(): LibraryComponent | null; - detach(): void; - swapComponent(component: LibraryComponent): void; - resetOverrides(): void; - switchVariant(pos: number, value: string): void; - combineAsVariants(ids: string[]): VariantContainer; - isVariantHead(): boolean; - resize(width: number, height: number): void; - rotate(angle: number, center?: { x: number; y: number } | null): void; - bringToFront(): void; - bringForward(): void; - sendToBack(): void; - sendBackward(): void; - export(config: Export): Promise>; - interactions: Interaction[]; - addInteraction( - trigger: Trigger, - action: Action, - delay?: number, - ): Interaction; - removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties?: TokenProperty[]): void; - clone(): Shape; - remove(): void; - } - ``` - - Hierarchy (View Summary) - - * ShapeBase - + Boolean - - Referenced by: Context, ContextTypesUtils, Penpot, Shape - members: - Properties: - type: |- - ``` - readonly type: "boolean" - ``` - - The type of the shape, which is always 'boolean' for boolean operation shapes. - content: |- - ``` - readonly content: string - ``` - - The content of the boolean shape, defined as the path string. - - Deprecated - - Use either `d` or `commands`. - d: |- - ``` - readonly d: string - ``` - - The content of the boolean shape, defined as the path string. - commands: |- - ``` - readonly commands: PathCommand[] - ``` - - The content of the boolean shape, defined as an array of path commands. - fills: |- - ``` - fills: Fill[] - ``` - - The fills applied to the shape. - - Overrides ShapeBase.fills - children: |- - ``` - readonly children: Shape[] - ``` - - The children shapes contained within the boolean shape. - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the shape. - name: |- - ``` - name: string - ``` - - The name of the shape. - parent: |- - ``` - readonly parent: Shape | null - ``` - - The parent shape. If the shape is the first level the parent will be the root shape. - For the root shape the parent is null - parentIndex: |- - ``` - readonly parentIndex: number - ``` - - Returns the index of the current shape in the parent - x: |- - ``` - x: number - ``` - - The x-coordinate of the shape's position. - y: |- - ``` - y: number - ``` - - The y-coordinate of the shape's position. - width: |- - ``` - readonly width: number - ``` - - The width of the shape. - height: |- - ``` - readonly height: number - ``` - - The height of the shape. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - The bounding box surrounding the current shape - center: |- - ``` - readonly center: Point - ``` - - The geometric center of the shape - blocked: |- - ``` - blocked: boolean - ``` - - Indicates whether the shape is blocked. - hidden: |- - ``` - hidden: boolean - ``` - - Indicates whether the shape is hidden. - visible: |- - ``` - visible: boolean - ``` - - Indicates whether the shape is visible. - proportionLock: |- - ``` - proportionLock: boolean - ``` - - Indicates whether the shape has proportion lock enabled. - constraintsHorizontal: |- - ``` - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale" - ``` - - The horizontal constraints applied to the shape. - constraintsVertical: |- - ``` - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom" - ``` - - The vertical constraints applied to the shape. - fixedWhenScrolling: |- - ``` - fixedWhenScrolling: boolean - ``` - - Indicates whether the shape stays fixed in place while scrolling. - borderRadius: |- - ``` - borderRadius: number - ``` - - The border radius of the shape. - borderRadiusTopLeft: |- - ``` - borderRadiusTopLeft: number - ``` - - The border radius of the top-left corner of the shape. - borderRadiusTopRight: |- - ``` - borderRadiusTopRight: number - ``` - - The border radius of the top-right corner of the shape. - borderRadiusBottomRight: |- - ``` - borderRadiusBottomRight: number - ``` - - The border radius of the bottom-right corner of the shape. - borderRadiusBottomLeft: |- - ``` - borderRadiusBottomLeft: number - ``` - - The border radius of the bottom-left corner of the shape. - opacity: |- - ``` - opacity: number - ``` - - The opacity of the shape. - blendMode: |- - ``` - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity" - ``` - - The blend mode applied to the shape. - shadows: |- - ``` - shadows: Shadow[] - ``` - - The shadows applied to the shape. - blur: |- - ``` - blur?: Blur - ``` - - The blur effect applied to the shape. - backgroundBlur: |- - ``` - backgroundBlur?: Blur - ``` - - The background blur effect applied to the shape. - Background blur creates a blur effect on the content behind the shape, - rather than on the shape's own content. - exports: |- - ``` - exports: Export[] - ``` - - The export settings of the shape. - boardX: |- - ``` - boardX: number - ``` - - The x-coordinate of the shape relative to its board. - boardY: |- - ``` - boardY: number - ``` - - The y-coordinate of the shape relative to its board. - parentX: |- - ``` - parentX: number - ``` - - The x-coordinate of the shape relative to its parent. - parentY: |- - ``` - parentY: number - ``` - - The y-coordinate of the shape relative to its parent. - flipX: |- - ``` - flipX: boolean - ``` - - Indicates whether the shape is flipped horizontally. - flipY: |- - ``` - flipY: boolean - ``` - - Indicates whether the shape is flipped vertically. - rotation: |- - ``` - rotation: number - ``` - - The rotation in degrees of the shape with respect to its center. - strokes: |- - ``` - strokes: Stroke[] - ``` - - The strokes applied to the shape. - layoutChild: |- - ``` - readonly layoutChild?: LayoutChildProperties - ``` - - Layout properties for children of the shape. - layoutCell: |- - ``` - readonly layoutCell?: LayoutCellProperties - ``` - - Layout properties for cells in a grid layout. - tokens: |- - ``` - readonly tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - } - ``` - - The design tokens applied to this shape. - It's a map property name -> token name. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - interactions: |- - ``` - readonly interactions: Interaction[] - ``` - - The interactions for the current shape. - Methods: - toD: |- - ``` - toD(): string - ``` - - Converts the boolean shape to its path data representation. - - Returns string - - Returns the path data (d attribute) as a string. - - Deprecated - - Use the `d` attribute - appendChild: |- - ``` - appendChild(child: Shape): void - ``` - - Appends a child shape to the boolean shape. - - Parameters - - * child: Shape - - The child shape to append. - - Returns void - - Example - ``` - boolShape.appendChild(childShape); - ``` - insertChild: |- - ``` - insertChild(index: number, child: Shape): void - ``` - - Inserts a child shape at the specified index within the boolean shape. - - Parameters - - * index: number - - The index at which to insert the child shape. - * child: Shape - - The child shape to insert. - - Returns void - - Example - ``` - boolShape.insertChild(0, childShape); - ``` - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` - setParentIndex: |- - ``` - setParentIndex(index: number): void - ``` - - Changes the index inside the parent of the current shape. - This method will shift the indexes of the shapes around that position to - match the index. - If the index is greater than the number of elements it will positioned last. - - Parameters - - * index: number - - the new index for the shape to be in - - Returns void - isComponentInstance: |- - ``` - isComponentInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component instance - isComponentMainInstance: |- - ``` - isComponentMainInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **main** instance - isComponentCopyInstance: |- - ``` - isComponentCopyInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **copy** instance - isComponentRoot: |- - ``` - isComponentRoot(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the root of a component tree - isComponentHead: |- - ``` - isComponentHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure - componentRefShape: |- - ``` - componentRefShape(): Shape | null - ``` - - Returns Shape | null - - Returns the equivalent shape in the component main instance. If the current shape is inside a - main instance will return `null`; - componentRoot: |- - ``` - componentRoot(): Shape | null - ``` - - Returns Shape | null - - Returns the root of the component tree structure for the current shape. If the current shape - is already a root will return itself. - componentHead: |- - ``` - componentHead(): Shape | null - ``` - - Returns Shape | null - - Returns the head of the component tree structure for the current shape. If the current shape - is already a head will return itself. - component: |- - ``` - component(): LibraryComponent | null - ``` - - Returns LibraryComponent | null - - If the shape is a component instance, returns the reference to the component associated - otherwise will return null - detach: |- - ``` - detach(): void - ``` - - If the current shape is a component it will remove the component information and leave the - shape as a "basic shape" - - Returns void - swapComponent: |- - ``` - swapComponent(component: LibraryComponent): void - ``` - - Swaps the component instance for another component, keeping the overrides - when possible. Similar to the "swap component" action on the Penpot interface. - The current shape must be a component copy instance. - - Parameters - - * component: LibraryComponent - - The new component to replace the current one - - Returns void - resetOverrides: |- - ``` - resetOverrides(): void - ``` - - Resets the overrides of the component copy, restoring all its attributes - (and those of its children) to the ones in the linked main component. - Similar to the "reset overrides" action on the Penpot interface. - The current shape must be a component copy instance. - - Returns void - switchVariant: |- - ``` - switchVariant(pos: number, value: string): void - ``` - - Switch a VariantComponent copy to the nearest one that has the specified property value - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - combineAsVariants: |- - ``` - combineAsVariants(ids: string[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu - on the Penpot interface. - The current shape must be a component main instance. - - Parameters - - * ids: string[] - - A list of ids of the main instances of the components to combine with this one. - - Returns VariantContainer - - The variant container created - isVariantHead: |- - ``` - isVariantHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure, - and that component is a VariantComponent - resize: |- - ``` - resize(width: number, height: number): void - ``` - - Resizes the shape to the specified width and height. - - Parameters - - * width: number - - The new width of the shape. - * height: number - - The new height of the shape. - - Returns void - - Example - ``` - shape.resize(200, 100); - ``` - rotate: |- - ``` - rotate(angle: number, center?: { x: number; y: number } | null): void - ``` - - Rotates the shape in relation with the given center. - - Parameters - - * angle: number - - Angle in degrees to rotate. - * center: { x: number; y: number } | null - - Center of the transform rotation. If not sent it will use the geometric center of the shape. - - Returns void - - Example - ``` - shape.rotate(45); - ``` - bringToFront: |- - ``` - bringToFront(): void - ``` - - Moves the current shape to the front of its siblings - - Returns void - bringForward: |- - ``` - bringForward(): void - ``` - - Moves the current shape one position forward in its list of siblings - - Returns void - sendToBack: |- - ``` - sendToBack(): void - ``` - - Moves the current shape to the back of its siblings - - Returns void - sendBackward: |- - ``` - sendBackward(): void - ``` - - Moves the current shape one position backwards in its list of siblings - - Returns void - export: |- - ``` - export(config: Export): Promise> - ``` - - Generates an export from the current shape. - - Parameters - - * config: Export - - Returns Promise> - - Example - ``` - shape.export({ type: 'png', scale: 2 }); - ``` - addInteraction: |- - ``` - addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction - ``` - - Adds a new interaction to the shape. - - If the interaction starts a flow (for example a `navigate-to` action) and - the shape's board is not already part of any flow, a new flow starting at - that board is created automatically, matching the behavior of the editor. - - Parameters - - * trigger: Trigger - - defines the conditions under which the action will be triggered - * action: Action - - defines what will be executed when the trigger happens - * delay: number - - for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. - - Returns Interaction - - Example - ``` - shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); - ``` - removeInteraction: |- - ``` - removeInteraction(interaction: Interaction): void - ``` - - Removes the interaction from the shape. - - Parameters - - * interaction: Interaction - - is the interaction to remove from the shape - - Returns void - - Example - ``` - shape.removeInteraction(interaction); - ``` - applyToken: |- - ``` - applyToken(token: Token, properties?: TokenProperty[]): void - ``` - - Applies one design token to one or more properties of the shape. - - Parameters - - * token: Token - - is the Token to apply - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - clone: |- - ``` - clone(): Shape - ``` - - Creates a clone of the shape. - - Returns Shape - - Returns a new instance of the shape with identical properties. - remove: |- - ``` - remove(): void - ``` - - Removes the shape from its parent. - - Returns void -CloseOverlay: - overview: |- - Interface CloseOverlay - ====================== - - This action will close a targeted board that is opened as an overlay. - - ``` - interface CloseOverlay { - type: "close-overlay"; - destination?: Board; - animation?: Animation; - } - ``` - - Referenced by: Action - members: - Properties: - type: |- - ``` - readonly type: "close-overlay" - ``` - - The action type - destination: |- - ``` - readonly destination?: Board - ``` - - The overlay to be closed with this action. - animation: |- - ``` - readonly animation?: Animation - ``` - - Animation displayed with this interaction. Omit it to close with no transition. -Color: - overview: |- - Interface Color - =============== - - Represents color properties in Penpot. - This interface includes properties for defining solid colors, gradients, and image fills, along with metadata. - - ``` - interface Color { - id?: string; - fileId?: string; - name?: string; - path?: string; - color?: string; - opacity?: number; - refId?: string; - refFile?: string; - gradient?: Gradient; - image?: ImageData; - } - ``` - - Referenced by: Context, Penpot, Shadow - members: - Properties: - id: |- - ``` - id?: string - ``` - - The optional reference ID for an external color definition. - fileId: |- - ``` - fileId?: string - ``` - - The optional reference to an external file for the color definition. - name: |- - ``` - name?: string - ``` - - The optional name of the color. - path: |- - ``` - path?: string - ``` - - The optional path or category to which this color belongs. - color: |- - ``` - color?: string - ``` - - The optional solid color, represented as a string (e.g., '#FF5733'). - opacity: |- - ``` - opacity?: number - ``` - - The optional opacity level of the color, ranging from 0 (fully transparent) to 1 (fully opaque). - Defaults to 1 if omitted. - refId: |- - ``` - refId?: string - ``` - - The optional reference ID for an external color definition. - - Deprecated - - Use `id` instead - refFile: |- - ``` - refFile?: string - ``` - - The optional reference to an external file for the color definition. - - Deprecated - - Use `fileId` - gradient: |- - ``` - gradient?: Gradient - ``` - - The optional gradient fill defined by a Gradient object. - image: |- - ``` - image?: ImageData - ``` - - The optional image fill defined by an ImageData object. -ColorShapeInfo: - overview: |- - Interface ColorShapeInfo - ======================== - - Additional color information for the methods to extract colors from a list of shapes. - - ``` - interface ColorShapeInfo { - shapesInfo: ColorShapeInfoEntry[]; - } - ``` - - Referenced by: Context, Penpot - members: - Properties: - shapesInfo: |- - ``` - readonly shapesInfo: ColorShapeInfoEntry[] - ``` - - List of shapes with additional information -ColorShapeInfoEntry: - overview: |- - Interface ColorShapeInfoEntry - ============================= - - Entry for the color shape additional information. - - ``` - interface ColorShapeInfoEntry { - property: string; - index?: number; - shapeId: string; - } - ``` - - Referenced by: ColorShapeInfo - members: - Properties: - property: |- - ``` - readonly property: string - ``` - - Property that has the color (example: fill, stroke...) - index: |- - ``` - readonly index?: number - ``` - - For properties that are indexes (such as fill) represent the index - of the color inside that property. - shapeId: |- - ``` - readonly shapeId: string - ``` - - Identifier of the shape that contains the color -Comment: - overview: |- - Interface Comment - ================= - - Comments allow the team to have one priceless conversation getting and - providing feedback right over the designs and prototypes. - - ``` - interface Comment { - user: User; - date: Date; - content: string; - remove(): void; - } - ``` - - Referenced by: CommentThread - members: - Properties: - user: |- - ``` - readonly user: User - ``` - - The `user` that has created the comment. - date: |- - ``` - readonly date: Date - ``` - - The `date` the comment has been created. - content: |- - ``` - content: string - ``` - - The `content` for the commentary. The owner can modify the comment. - Methods: - remove: |- - ``` - remove(): void - ``` - - Remove the current comment from its comment thread. Only the owner can remove their comments. - Requires the `comment:write` permission. - - Returns void -CommentThread: - overview: |- - Interface CommentThread - ======================= - - Represents a list of comments one after the other. Usually these threads - are conversations the users have in Penpot. - - ``` - interface CommentThread { - seqNumber: number; - board?: Board; - owner?: User; - position: Point; - resolved: boolean; - findComments(): Promise; - reply(content: string): Promise; - remove(): void; - } - ``` - - Referenced by: Page - members: - Properties: - seqNumber: |- - ``` - readonly seqNumber: number - ``` - - This is the number that is displayed on the workspace. Is an increasing - sequence for each comment. - board: |- - ``` - readonly board?: Board - ``` - - If the thread is attached to a `board` this will have that board - reference. - owner: |- - ``` - readonly owner?: User - ``` - - Owner of the comment thread - position: |- - ``` - position: Point - ``` - - The `position` in absolute coordinates in the canvas. - resolved: |- - ``` - resolved: boolean - ``` - - Whether the thread has been marked as `resolved` or not. - Methods: - findComments: |- - ``` - findComments(): Promise - ``` - - List of `comments` ordered by creation date. - Requires the `comment:read` or `comment:write` permission. - - Returns Promise - reply: |- - ``` - reply(content: string): Promise - ``` - - Creates a new comment after the last one in the thread. The current user will - be used as the creation user. - Requires the `comment:write` permission. - - Parameters - - * content: string - - Returns Promise - remove: |- - ``` - remove(): void - ``` - - Removes the current comment thread. Only the user that created the thread can - remove it. - Requires the `comment:write` permission. - - Returns void -CommonLayout: - overview: |- - Interface CommonLayout - ====================== - - CommonLayout represents a common layout interface in the Penpot application. - It includes various properties for alignment, spacing, padding, and sizing, as well as a method to remove the layout. - - ``` - interface CommonLayout { - alignItems?: "end" | "start" | "center" | "stretch"; - alignContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly"; - justifyItems?: "end" - | "start" - | "center" - | "stretch"; - justifyContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly"; - rowGap: number; - columnGap: number; - verticalPadding: number; - horizontalPadding: number; - topPadding: number; - rightPadding: number; - bottomPadding: number; - leftPadding: number; - horizontalSizing: "fill" - | "auto" - | "fix"; - verticalSizing: "fill" | "auto" | "fix"; - remove(): void; - } - ``` - - Hierarchy (View Summary) - - * CommonLayout - + FlexLayout - + GridLayout - members: - Properties: - alignItems: |- - ``` - alignItems?: "end" | "start" | "center" | "stretch" - ``` - - The `alignItems` property specifies the default alignment for items inside the container. - It can be one of the following values: - - * 'start': Items are aligned at the start. - * 'end': Items are aligned at the end. - * 'center': Items are centered. - * 'stretch': Items are stretched to fill the container. - alignContent: |- - ``` - alignContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly" - ``` - - The `alignContent` property specifies how the content is aligned within the container when there is extra space. - It can be one of the following values: - - * 'start': Content is aligned at the start. - * 'end': Content is aligned at the end. - * 'center': Content is centered. - * 'space-between': Content is distributed with space between. - * 'space-around': Content is distributed with space around. - * 'space-evenly': Content is distributed with even space around. - * 'stretch': Content is stretched to fill the container. - justifyItems: |- - ``` - justifyItems?: "end" | "start" | "center" | "stretch" - ``` - - The `justifyItems` property specifies the default justification for items inside the container. - It can be one of the following values: - - * 'start': Items are justified at the start. - * 'end': Items are justified at the end. - * 'center': Items are centered. - * 'stretch': Items are stretched to fill the container. - justifyContent: |- - ``` - justifyContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly" - ``` - - The `justifyContent` property specifies how the content is justified within the container when there is extra space. - It can be one of the following values: - - * 'start': Content is justified at the start. - * 'center': Content is centered. - * 'end': Content is justified at the end. - * 'space-between': Content is distributed with space between. - * 'space-around': Content is distributed with space around. - * 'space-evenly': Content is distributed with even space around. - * 'stretch': Content is stretched to fill the container. - rowGap: |- - ``` - rowGap: number - ``` - - The `rowGap` property specifies the gap between rows in the layout. - columnGap: |- - ``` - columnGap: number - ``` - - The `columnGap` property specifies the gap between columns in the layout. - verticalPadding: |- - ``` - verticalPadding: number - ``` - - The `verticalPadding` property specifies the vertical padding inside the container. - horizontalPadding: |- - ``` - horizontalPadding: number - ``` - - The `horizontalPadding` property specifies the horizontal padding inside the container. - topPadding: |- - ``` - topPadding: number - ``` - - The `topPadding` property specifies the padding at the top of the container. - rightPadding: |- - ``` - rightPadding: number - ``` - - The `rightPadding` property specifies the padding at the right of the container. - bottomPadding: |- - ``` - bottomPadding: number - ``` - - The `bottomPadding` property specifies the padding at the bottom of the container. - leftPadding: |- - ``` - leftPadding: number - ``` - - The `leftPadding` property specifies the padding at the left of the container. - horizontalSizing: |- - ``` - horizontalSizing: "fill" | "auto" | "fix" - ``` - - The `horizontalSizing` property specifies the horizontal sizing behavior of the container. - It can be one of the following values: - - * 'fix': The container has its own intrinsic fixed size. - * 'fill': The container fills the available space. Only can be set if it's inside another layout. - * 'auto': The container fits the content. - verticalSizing: |- - ``` - verticalSizing: "fill" | "auto" | "fix" - ``` - - The `verticalSizing` property specifies the vertical sizing behavior of the container. - It can be one of the following values: - - * 'fix': The container has its own intrinsic fixed size. - * 'fill': The container fills the available space. Only can be set if it's inside another layout. - * 'auto': The container fits the content. - Methods: - remove: |- - ``` - remove(): void - ``` - - The `remove` method removes the layout. - - Returns void -Context: - overview: |- - Interface Context - ================= - - Represents the context of Penpot, providing access to various Penpot functionalities and data. - - ``` - interface Context { - version: string; - root: Shape | null; - currentFile: File | null; - currentPage: Page | null; - viewport: Viewport; - flags: Flags; - history: HistoryContext; - library: LibraryContext; - fonts: FontsContext; - currentUser: User; - activeUsers: ActiveUser[]; - theme: Theme; - localStorage: LocalStorage; - selection: Shape[]; - shapesColors(shapes: Shape[]): (Color & ColorShapeInfo)[]; - replaceColor(shapes: Shape[], oldColor: Color, newColor: Color): void; - uploadMediaUrl(name: string, url: string): Promise; - uploadMediaData( - name: string, - data: Uint8Array, - mimeType: string, - ): Promise; - group(shapes: Shape[]): Group | null; - ungroup(group: Group, ...other: Group[]): void; - createRectangle(): Rectangle; - createBoard(): Board; - createEllipse(): Ellipse; - createPath(): Path; - createBoolean(boolType: BooleanType, shapes: Shape[]): Boolean | null; - createShapeFromSvg(svgString: string): Group | null; - createShapeFromSvgWithImages(svgString: string): Promise; - createText(text: string): Text | null; - generateMarkup( - shapes: Shape[], - options?: { type?: "html" | "svg" }, - ): string; - generateStyle( - shapes: Shape[], - options?: { - type?: "css"; - withPrelude?: boolean; - includeChildren?: boolean; - }, - ): string; - generateFontFaces(shapes: Shape[]): Promise; - addListener( - type: T, - callback: (event: EventsMap[T]) => void, - props?: { [key: string]: unknown }, - ): symbol; - removeListener(listenerId: symbol): void; - openViewer(): void; - createPage(): Page; - openPage(page: string | Page, newWindow?: boolean): Promise; - alignHorizontal( - shapes: Shape[], - direction: "center" | "left" | "right", - ): void; - alignVertical( - shapes: Shape[], - direction: "center" | "top" | "bottom", - ): void; - distributeHorizontal(shapes: Shape[]): void; - distributeVertical(shapes: Shape[]): void; - flatten(shapes: Shape[]): Path[]; - createVariantFromComponents(shapes: Board[]): VariantContainer; - } - ``` - members: - Properties: - version: |- - ``` - readonly version: string - ``` - - Returns the current penpot version. - root: |- - ``` - readonly root: Shape | null - ``` - - The root shape in the current Penpot context. Requires `content:read` permission. - - Example - ``` - const rootShape = context.root;console.log(rootShape); - ``` - currentFile: |- - ``` - readonly currentFile: File | null - ``` - - Retrieves file data from the current Penpot context. Requires `content:read` permission. - - Returns - - Returns the file data or `null` if no file is available. - - Example - ``` - const fileData = context.currentFile;console.log(fileData); - ``` - currentPage: |- - ``` - readonly currentPage: Page | null - ``` - - The current page in the Penpot context. Requires `content:read` permission. - - Example - ``` - const currentPage = context.currentPage;console.log(currentPage); - ``` - viewport: |- - ``` - readonly viewport: Viewport - ``` - - The viewport settings in the Penpot context. - - Example - ``` - const viewportSettings = context.viewport;console.log(viewportSettings); - ``` - flags: |- - ``` - readonly flags: Flags - ``` - - Provides flags to customize the API behavior. - history: |- - ``` - readonly history: HistoryContext - ``` - - Context encapsulating the history operations - - Example - ``` - const historyContext = context.history;console.log(historyContext); - ``` - library: |- - ``` - readonly library: LibraryContext - ``` - - The library context in the Penpot context, including both local and connected libraries. Requires `library:read` permission. - - Example - ``` - const libraryContext = context.library;console.log(libraryContext); - ``` - fonts: |- - ``` - readonly fonts: FontsContext - ``` - - The fonts context in the Penpot context, providing methods to manage fonts. Requires `content:read` permission. - - Example - ``` - const fontsContext = context.fonts;console.log(fontsContext); - ``` - currentUser: |- - ``` - readonly currentUser: User - ``` - - The current user in the Penpot context. Requires `user:read` permission. - - Example - ``` - const currentUser = context.currentUser;console.log(currentUser); - ``` - activeUsers: |- - ``` - readonly activeUsers: ActiveUser[] - ``` - - An array of active users in the Penpot context. Requires `user:read` permission. - - Example - ``` - const activeUsers = context.activeUsers;console.log(activeUsers); - ``` - theme: |- - ``` - readonly theme: Theme - ``` - - The current theme (light or dark) in Penpot. - - Example - ``` - const currentTheme = context.theme;console.log(currentTheme); - ``` - localStorage: |- - ``` - readonly localStorage: LocalStorage - ``` - - Access to the localStorage proxy - selection: |- - ``` - selection: Shape[] - ``` - - The currently selected shapes in Penpot. Requires `content:read` permission. - - Example - ``` - const selectedShapes = context.selection;console.log(selectedShapes); - ``` - Methods: - shapesColors: |- - ``` - shapesColors(shapes: Shape[]): (Color & ColorShapeInfo)[] - ``` - - Retrieves colors applied to the given shapes in Penpot. Requires `content:read` permission. - - Parameters - - * shapes: Shape[] - - Returns (Color & ColorShapeInfo)[] - - Returns an array of colors and their shape information. - - Example - ``` - const colors = context.shapesColors(shapes);console.log(colors); - ``` - replaceColor: |- - ``` - replaceColor(shapes: Shape[], oldColor: Color, newColor: Color): void - ``` - - Replaces a specified old color with a new color in the given shapes. Requires `content:write` permission. - - Parameters - - * shapes: Shape[] - * oldColor: Color - * newColor: Color - - Returns void - - Example - ``` - context.replaceColor(shapes, oldColor, newColor); - ``` - uploadMediaUrl: |- - ``` - uploadMediaUrl(name: string, url: string): Promise - ``` - - Uploads media to Penpot and retrieves its image data. Requires `content:write` permission. - - Parameters - - * name: string - - The name of the media. - * url: string - - The URL of the media to be uploaded. - - Returns Promise - - Returns a promise that resolves to the image data of the uploaded media. - - Example - ``` - const imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');console.log(imageData);// to insert the image in a shape we can doconst board = penpot.createBoard();const shape = penpot.createRectangle();board.appendChild(shape);shape.fills = [{ fillOpacity: 1, fillImage: imageData }]; - ``` - uploadMediaData: |- - ``` - uploadMediaData( - name: string, - data: Uint8Array, - mimeType: string, - ): Promise - ``` - - Uploads media to penpot and retrieves the image data. Requires `content:write` permission. - - Parameters - - * name: string - - The name of the media. - * data: Uint8Array - - The image content data - * mimeType: string - - Returns Promise - - Returns a promise that resolves to the image data of the uploaded media. - - Example - ``` - const imageData = await context.uploadMediaData('example', imageData, 'image/jpeg');console.log(imageData); - ``` - group: |- - ``` - group(shapes: Shape[]): Group | null - ``` - - Groups the specified shapes. Requires `content:write` permission. - - Parameters - - * shapes: Shape[] - - An array of shapes to group. - - Returns Group | null - - Returns the newly created group or `null` if the group could not be created. - - Example - ``` - const penpotShapesArray = penpot.selection;penpot.group(penpotShapesArray); - ``` - ungroup: |- - ``` - ungroup(group: Group, ...other: Group[]): void - ``` - - Ungroups the specified group. Requires `content:write` permission. - - Parameters - - * group: Group - - The group to ungroup. - * ...other: Group[] - - Additional groups to ungroup. - - Returns void - - Example - ``` - const penpotShapesArray = penpot.selection;// We need to make sure that something is selected, and that the selected shape is a groupif (penpotShapesArray.length && penpot.utils.types.isGroup(penpotShapesArray[0])) { penpot.ungroup(penpotShapesArray[0]);} - ``` - createRectangle: |- - ``` - createRectangle(): Rectangle - ``` - - Use this method to create the shape of a rectangle. Requires `content:write` permission. - - Returns Rectangle - - Example - ``` - const shape = penpot.createRectangle();// just change the values like thisshape.name = "Example rectangle";// for solid colorshape.fills = [{ fillColor: "#7EFFF5" }];// for linear gradient colorshape.fills = [{ fillColorGradient: { "type": "linear", "startX": 0.5, "startY": 0, "endX": 0.5, "endY": 1, "width": 1, "stops": [ { "color": "#003ae9", "opacity": 1, "offset": 0 }, { "color": "#003ae9", "opacity": 0, "offset": 1 } ] }}];// for a image fillconst imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');shape.fills = [{ fillOpacity: 1, fillImage: imageData }];shape.borderRadius = 8;shape.strokes = [ { strokeColor: "#2e3434", strokeStyle: "solid", strokeWidth: 2, strokeAlignment: "center", },]; - ``` - createBoard: |- - ``` - createBoard(): Board - ``` - - Use this method to create a board. This is the first step before anything else, the container. Requires `content:write` permission. - Then you can add a gridlayout, flexlayout or add a shape inside the board. - Just a heads-up: board is a board in Penpot UI. - - Returns Board - - Example - ``` - const board = penpot.createBoard();// to add grid layoutboard.addGridLayout();// to add flex layoutboard.addFlexLayout();// to create a shape inside the boardconst shape = penpot.createRectangle();board.appendChild(shape); - ``` - createEllipse: |- - ``` - createEllipse(): Ellipse - ``` - - Use this method to create the shape of an ellipse. Requires `content:write` permission. - - Returns Ellipse - - Example - ``` - const shape = penpot.createEllipse();// just change the values like thisshape.name = "Example ellipse";// for solid colorshape.fills = [{ fillColor: "#7EFFF5" }];// for linear gradient colorshape.fills = [{ fillColorGradient: { "type": "linear", "startX": 0.5, "startY": 0, "endX": 0.5, "endY": 1, "width": 1, "stops": [ { "color": "#003ae9", "opacity": 1, "offset": 0 }, { "color": "#003ae9", "opacity": 0, "offset": 1 } ] }}];// for an image fillconst imageData = await context.uploadMediaUrl('example', 'https://example.com/image.jpg');shape.fills = [{ fillOpacity: 1, fillImage: imageData }];shape.strokes = [ { strokeColor: "#2e3434", strokeStyle: "solid", strokeWidth: 2, strokeAlignment: "center", },]; - ``` - createPath: |- - ``` - createPath(): Path - ``` - - Use this method to create a path. Requires `content:write` permission. - - Returns Path - - Example - ``` - const path = penpot.createPath();path.name = "My path";// for solid colorpath.fills = [{ fillColor: "#7EFFF5" }]; - ``` - createBoolean: |- - ``` - createBoolean(boolType: BooleanType, shapes: Shape[]): Boolean | null - ``` - - Creates a Boolean shape based on the specified boolean operation and shapes. Requires `content:write` permission. - - Parameters - - * boolType: BooleanType - - The type of boolean operation ('union', 'difference', 'exclude', 'intersection'). - * shapes: Shape[] - - An array of shapes to perform the boolean operation on. - - Returns Boolean | null - - Returns the newly created Boolean shape resulting from the boolean operation. - - Example - ``` - const booleanShape = context.createBoolean('union', [shape1, shape2]); - ``` - createShapeFromSvg: |- - ``` - createShapeFromSvg(svgString: string): Group | null - ``` - - Creates a Group from an SVG string. Requires `content:write` permission. - - Parameters - - * svgString: string - - The SVG string representing the shapes to be converted into a group. - - Returns Group | null - - Returns the newly created Group containing the shapes from the SVG. - - Example - ``` - const svgGroup = context.createShapeFromSvg('...'); - ``` - createShapeFromSvgWithImages: |- - ``` - createShapeFromSvgWithImages(svgString: string): Promise - ``` - - Creates a Group from an SVG string. The SVG can have images and the method returns - a Promise because the shape will be available after all images are uploaded. - Requires `content:write` permission. - - Parameters - - * svgString: string - - The SVG string representing the shapes to be converted into a group. - - Returns Promise - - Returns a promise with the newly created Group containing the shapes from the SVG. - - Example - ``` - const svgGroup = await context.createShapeFromSvgWithImages('...'); - ``` - createText: |- - ``` - createText(text: string): Text | null - ``` - - Creates a Text shape with the specified text content. Requires `content:write` permission. - - Parameters - - * text: string - - The text content for the Text shape. Must be a non-empty string. - - Returns Text | null - - Returns the new created shape. Returns null if an empty string is provided or the shape couldn't be created. - - Example - ``` - const board = penpot.createBoard();let text;text = penpot.createText();// just change the values like thistext.growType = 'auto-height';text.fontFamily = 'Work Sans';text.fontSize = '12';text.fills = [{fillColor: '#9f05ff', fillOpacity: 1}];text.strokes = [{strokeOpacity: 1, strokeStyle: 'solid', strokeWidth: 2, strokeColor: '#deabff', strokeAlignment: 'outer'}];board.appendChild(text); - ``` - generateMarkup: |- - ``` - generateMarkup(shapes: Shape[], options?: { type?: "html" | "svg" }): string - ``` - - Generates markup for the given shapes. Requires `content:read` permission - - Parameters - - * shapes: Shape[] - - the shapes to generate the markup for - * options: { type?: "html" | "svg" } - - `type` of the markup to generate. Defaults to `'html'`. - - Returns string - - Example - ``` - const markup = context.generateMarkup(shapes, { type: 'html' });console.log(markup); - ``` - generateStyle: |- - ``` - generateStyle( - shapes: Shape[], - options?: { - type?: "css"; - withPrelude?: boolean; - includeChildren?: boolean; - }, - ): string - ``` - - Generates styles for the given shapes. Requires `content:read` permission - - Parameters - - * shapes: Shape[] - - the shapes to generate the styles for - * options: { type?: "css"; withPrelude?: boolean; includeChildren?: boolean } - - `type` of the styles to generate (defaults to `'css'`), `withPrelude` to include the style prelude - (defaults to `false`) and `includeChildren` to also generate the styles for the shape children (defaults to `true`). - - Returns string - - Example - ``` - const styles = context.generateStyle(shapes, { type: 'css' });console.log(styles); - ``` - generateFontFaces: |- - ``` - generateFontFaces(shapes: Shape[]): Promise - ``` - - Generates the fontfaces styles necessaries to render the shapes. - Requires `content:read` permission - - Parameters - - * shapes: Shape[] - - Returns Promise - - Example - ``` - const fontfaces = context.generateFontFaces(penpot.selection);console.log(fontfaces); - ``` - addListener: |- - ``` - addListener( - type: T, - callback: (event: EventsMap[T]) => void, - props?: { [key: string]: unknown }, - ): symbol - ``` - - Adds the current callback as an event listener - - Type Parameters - - * T extends keyof EventsMap - - Parameters - - * type: T - * callback: (event: EventsMap[T]) => void - * props: { [key: string]: unknown } - - Returns symbol - - Example - ``` - const listenerId = context.addListener('selectionchange', (event) => { console.log(event);}); - ``` - removeListener: |- - ``` - removeListener(listenerId: symbol): void - ``` - - Removes the listenerId from the list of listeners - - Parameters - - * listenerId: symbol - - Returns void - - Example - ``` - context.removeListener(listenerId); - ``` - openViewer: |- - ``` - openViewer(): void - ``` - - Opens the viewer section. Requires `content:read` permission. - - Returns void - createPage: |- - ``` - createPage(): Page - ``` - - Creates a new page and returns it. Requires `content:write` permission. - - IMPORTANT: creating a page does **not** make it the active page. - To build content inside the new page, activate it first with - Context.openPage (and `await` it) before mutate shapes: - - Returns Page - - Example - ``` - const page = penpot.createPage();page.name = 'New Page';await penpot.openPage(page); // make the new page active firstconst board = penpot.createBoard();board.resize(375, 812);page.root.appendChild(board); - ``` - openPage: |- - ``` - openPage(page: string | Page, newWindow?: boolean): Promise - ``` - - Changes the current open page to the given page, making it the **active page**. - The active page is the one all shape creation and structural operations - (`createBoard`, `appendChild`, `insertChild`, property setters, etc.) act upon, - so call this (and `await` it) after Context.createPage before adding - shapes to the newly created page. Requires `content:read` permission. - - Parameters - - * page: string | Page - - the page to open (a Page object or a page UUID string) - * newWindow: boolean - - if true opens the page in a new window, defaults to false - - Returns Promise - - Example - ``` - await context.openPage(page); - ``` - alignHorizontal: |- - ``` - alignHorizontal(shapes: Shape[], direction: "center" | "left" | "right"): void - ``` - - Aligning will move all the selected layers to a position relative to one - of them in the horizontal direction. - - Parameters - - * shapes: Shape[] - - to align - * direction: "center" | "left" | "right" - - where the shapes will be aligned - - Returns void - alignVertical: |- - ``` - alignVertical(shapes: Shape[], direction: "center" | "top" | "bottom"): void - ``` - - Aligning will move all the selected layers to a position relative to one - of them in the vertical direction. - - Parameters - - * shapes: Shape[] - - to align - * direction: "center" | "top" | "bottom" - - where the shapes will be aligned - - Returns void - distributeHorizontal: |- - ``` - distributeHorizontal(shapes: Shape[]): void - ``` - - Distributing objects to position them horizontally with equal distances between them. - - Parameters - - * shapes: Shape[] - - to distribute - - Returns void - distributeVertical: |- - ``` - distributeVertical(shapes: Shape[]): void - ``` - - Distributing objects to position them vertically with equal distances between them. - - Parameters - - * shapes: Shape[] - - to distribute - - Returns void - flatten: |- - ``` - flatten(shapes: Shape[]): Path[] - ``` - - Converts the shapes into Paths. If the shapes are complex will put together - all its paths into one. - - Parameters - - * shapes: Shape[] - - to flatten - - Returns Path[] - createVariantFromComponents: |- - ``` - createVariantFromComponents(shapes: Board[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it - with the contextual menu on the Penpot interface. - All the shapes passed as arguments should be main instances. - - Parameters - - * shapes: Board[] - - A list of main instances of the components to combine. - - Returns VariantContainer - - The variant container created -ContextGeometryUtils: - overview: |- - Interface ContextGeometryUtils - ============================== - - Utility methods for geometric calculations in Penpot. - - Example - ``` - const centerPoint = geometryUtils.center(shapes);console.log(centerPoint); - ``` - - ``` - interface ContextGeometryUtils { - center(shapes: Shape[]): { x: number; y: number } | null; - } - ``` - - Referenced by: ContextUtils - members: - Methods: - center: |- - ``` - center(shapes: Shape[]): { x: number; y: number } | null - ``` - - Calculates the center point of a given array of shapes. - This method computes the geometric center (centroid) of the bounding boxes of the provided shapes. - - Parameters - - * shapes: Shape[] - - The array of shapes to calculate the center for. - - Returns { x: number; y: number } | null - - Returns the center point as an object with `x` and `y` coordinates, or null if the array is empty. - - Example - ``` - const centerPoint = geometryUtils.center(shapes);console.log(centerPoint); - ``` -ContextTypesUtils: - overview: |- - Interface ContextTypesUtils - =========================== - - Utility methods for determining the types of Penpot shapes. - - Example - ``` - const isBoard = typesUtils.isBoard(shape);console.log(isBoard); - ``` - - ``` - interface ContextTypesUtils { - isBoard(shape: Shape): shape is Board; - isGroup(shape: Shape): shape is Group; - isMask(shape: Shape): shape is Group; - isBool(shape: Shape): shape is Boolean; - isRectangle(shape: Shape): shape is Rectangle; - isPath(shape: Shape): shape is Path; - isText(shape: Shape): shape is Text; - isEllipse(shape: Shape): shape is Ellipse; - isSVG(shape: Shape): shape is SvgRaw; - isVariantContainer(shape: Shape): shape is VariantContainer; - isVariantComponent( - component: LibraryComponent, - ): component is LibraryVariantComponent; - } - ``` - - Referenced by: ContextUtils - members: - Methods: - isBoard: |- - ``` - isBoard(shape: Shape): shape is Board - ``` - - Checks if the given shape is a board. - - Parameters - - * shape: Shape - - The shape to check. - - Returns shape is Board - - Returns true if the shape is a board, otherwise false. - isGroup: |- - ``` - isGroup(shape: Shape): shape is Group - ``` - - Checks if the given shape is a group. - - Parameters - - * shape: Shape - - The shape to check. - - Returns shape is Group - - Returns true if the shape is a Group, otherwise false. - isMask: |- - ``` - isMask(shape: Shape): shape is Group - ``` - - Checks if the given shape is a mask. - - Parameters - - * shape: Shape - - The shape to check. - - Returns shape is Group - - Returns true if the shape is a Group (acting as a mask), otherwise false. - isBool: |- - ``` - isBool(shape: Shape): shape is Boolean - ``` - - Checks if the given shape is a boolean operation. - - Parameters - - * shape: Shape - - The shape to check. - - Returns shape is Boolean - - Returns true if the shape is a Bool, otherwise false. - isRectangle: |- - ``` - isRectangle(shape: Shape): shape is Rectangle - ``` - - Checks if the given shape is a rectangle. - - Parameters - - * shape: Shape - - The shape to check. - - Returns shape is Rectangle - - Returns true if the shape is a Rectangle, otherwise false. - isPath: |- - ``` - isPath(shape: Shape): shape is Path - ``` - - Checks if the given shape is a path. - - Parameters - - * shape: Shape - - The shape to check. - - Returns shape is Path - - Returns true if the shape is a Path, otherwise false. - isText: |- - ``` - isText(shape: Shape): shape is Text - ``` - - Checks if the given shape is a text element. - - Parameters - - * shape: Shape - - The shape to check. - - Returns shape is Text - - Returns true if the shape is a Text, otherwise false. - isEllipse: |- - ``` - isEllipse(shape: Shape): shape is Ellipse - ``` - - Checks if the given shape is an ellipse. - - Parameters - - * shape: Shape - - The shape to check. - - Returns shape is Ellipse - - Returns true if the shape is an Ellipse, otherwise false. - isSVG: |- - ``` - isSVG(shape: Shape): shape is SvgRaw - ``` - - Checks if the given shape is an SVG. - - Parameters - - * shape: Shape - - The shape to check. - - Returns shape is SvgRaw - - Returns true if the shape is a SvgRaw, otherwise false. - isVariantContainer: |- - ``` - isVariantContainer(shape: Shape): shape is VariantContainer - ``` - - Checks if the given shape is a variant container. - - Parameters - - * shape: Shape - - The shape to check. - - Returns shape is VariantContainer - - Returns true if the shape is a variant container, otherwise false. - isVariantComponent: |- - ``` - isVariantComponent( - component: LibraryComponent, - ): component is LibraryVariantComponent - ``` - - Checks if the given component is a VariantComponent. - - Parameters - - * component: LibraryComponent - - The component to check. - - Returns component is LibraryVariantComponent - - Returns true if the component is a VariantComponent, otherwise false. -ContextUtils: - overview: |- - Interface ContextUtils - ====================== - - Utility methods for various operations in Penpot. - - ``` - interface ContextUtils { - geometry: ContextGeometryUtils; - types: ContextTypesUtils; - } - ``` - - Referenced by: Penpot - members: - Properties: - geometry: |- - ``` - readonly geometry: ContextGeometryUtils - ``` - - Geometry utility methods for Penpot. - Provides methods for geometric calculations, such as finding the center of a group of shapes. - - Example - ``` - const centerPoint = penpot.utils.geometry.center(shapes);console.log(centerPoint); - ``` - types: |- - ``` - readonly types: ContextTypesUtils - ``` - - Type utility methods for Penpot. - Provides methods for determining the types of various shapes in Penpot. - - Example - ``` - const isBoard = utils.types.isBoard(shape);console.log(isBoard); - ``` -Dissolve: - overview: |- - Interface Dissolve - ================== - - Dissolve animation - - ``` - interface Dissolve { - type: "dissolve"; - duration: number; - easing?: "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out"; - } - ``` - - Referenced by: Animation - members: - Properties: - type: |- - ``` - readonly type: "dissolve" - ``` - - Type of the animation - duration: |- - ``` - readonly duration: number - ``` - - Duration of the animation effect - easing: |- - ``` - readonly easing?: "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" - ``` - - Function that the dissolve effect will follow for the interpolation. - Defaults to `linear`. -Ellipse: - overview: |- - Interface Ellipse - ================= - - Represents an ellipse shape in Penpot. - This interface extends `ShapeBase` and includes properties specific to ellipses. - - ``` - interface Ellipse { - type: "ellipse"; - fills: Fill[]; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - id: string; - name: string; - parent: Shape | null; - parentIndex: number; - x: number; - y: number; - width: number; - height: number; - bounds: Bounds; - center: Point; - blocked: boolean; - hidden: boolean; - visible: boolean; - proportionLock: boolean; - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; - fixedWhenScrolling: boolean; - borderRadius: number; - borderRadiusTopLeft: number; - borderRadiusTopRight: number; - borderRadiusBottomRight: number; - borderRadiusBottomLeft: number; - opacity: number; - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity"; - shadows: Shadow[]; - blur?: Blur; - backgroundBlur?: Blur; - exports: Export[]; - boardX: number; - boardY: number; - parentX: number; - parentY: number; - flipX: boolean; - flipY: boolean; - rotation: number; - strokes: Stroke[]; - layoutChild?: LayoutChildProperties; - layoutCell?: LayoutCellProperties; - setParentIndex(index: number): void; - tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - }; - isComponentInstance(): boolean; - isComponentMainInstance(): boolean; - isComponentCopyInstance(): boolean; - isComponentRoot(): boolean; - isComponentHead(): boolean; - componentRefShape(): Shape | null; - componentRoot(): Shape | null; - componentHead(): Shape | null; - component(): LibraryComponent | null; - detach(): void; - swapComponent(component: LibraryComponent): void; - resetOverrides(): void; - switchVariant(pos: number, value: string): void; - combineAsVariants(ids: string[]): VariantContainer; - isVariantHead(): boolean; - resize(width: number, height: number): void; - rotate(angle: number, center?: { x: number; y: number } | null): void; - bringToFront(): void; - bringForward(): void; - sendToBack(): void; - sendBackward(): void; - export(config: Export): Promise>; - interactions: Interaction[]; - addInteraction( - trigger: Trigger, - action: Action, - delay?: number, - ): Interaction; - removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties?: TokenProperty[]): void; - clone(): Shape; - remove(): void; - } - ``` - - Hierarchy (View Summary) - - * ShapeBase - + Ellipse - - Referenced by: Context, ContextTypesUtils, Penpot, Shape - members: - Properties: - type: |- - ``` - readonly type: "ellipse" - ``` - - The type of the shape, which is always 'ellipse' for ellipse shapes. - fills: |- - ``` - fills: Fill[] - ``` - - The fills applied to the shape. - - Overrides ShapeBase.fills - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the shape. - name: |- - ``` - name: string - ``` - - The name of the shape. - parent: |- - ``` - readonly parent: Shape | null - ``` - - The parent shape. If the shape is the first level the parent will be the root shape. - For the root shape the parent is null - parentIndex: |- - ``` - readonly parentIndex: number - ``` - - Returns the index of the current shape in the parent - x: |- - ``` - x: number - ``` - - The x-coordinate of the shape's position. - y: |- - ``` - y: number - ``` - - The y-coordinate of the shape's position. - width: |- - ``` - readonly width: number - ``` - - The width of the shape. - height: |- - ``` - readonly height: number - ``` - - The height of the shape. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - The bounding box surrounding the current shape - center: |- - ``` - readonly center: Point - ``` - - The geometric center of the shape - blocked: |- - ``` - blocked: boolean - ``` - - Indicates whether the shape is blocked. - hidden: |- - ``` - hidden: boolean - ``` - - Indicates whether the shape is hidden. - visible: |- - ``` - visible: boolean - ``` - - Indicates whether the shape is visible. - proportionLock: |- - ``` - proportionLock: boolean - ``` - - Indicates whether the shape has proportion lock enabled. - constraintsHorizontal: |- - ``` - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale" - ``` - - The horizontal constraints applied to the shape. - constraintsVertical: |- - ``` - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom" - ``` - - The vertical constraints applied to the shape. - fixedWhenScrolling: |- - ``` - fixedWhenScrolling: boolean - ``` - - Indicates whether the shape stays fixed in place while scrolling. - borderRadius: |- - ``` - borderRadius: number - ``` - - The border radius of the shape. - borderRadiusTopLeft: |- - ``` - borderRadiusTopLeft: number - ``` - - The border radius of the top-left corner of the shape. - borderRadiusTopRight: |- - ``` - borderRadiusTopRight: number - ``` - - The border radius of the top-right corner of the shape. - borderRadiusBottomRight: |- - ``` - borderRadiusBottomRight: number - ``` - - The border radius of the bottom-right corner of the shape. - borderRadiusBottomLeft: |- - ``` - borderRadiusBottomLeft: number - ``` - - The border radius of the bottom-left corner of the shape. - opacity: |- - ``` - opacity: number - ``` - - The opacity of the shape. - blendMode: |- - ``` - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity" - ``` - - The blend mode applied to the shape. - shadows: |- - ``` - shadows: Shadow[] - ``` - - The shadows applied to the shape. - blur: |- - ``` - blur?: Blur - ``` - - The blur effect applied to the shape. - backgroundBlur: |- - ``` - backgroundBlur?: Blur - ``` - - The background blur effect applied to the shape. - Background blur creates a blur effect on the content behind the shape, - rather than on the shape's own content. - exports: |- - ``` - exports: Export[] - ``` - - The export settings of the shape. - boardX: |- - ``` - boardX: number - ``` - - The x-coordinate of the shape relative to its board. - boardY: |- - ``` - boardY: number - ``` - - The y-coordinate of the shape relative to its board. - parentX: |- - ``` - parentX: number - ``` - - The x-coordinate of the shape relative to its parent. - parentY: |- - ``` - parentY: number - ``` - - The y-coordinate of the shape relative to its parent. - flipX: |- - ``` - flipX: boolean - ``` - - Indicates whether the shape is flipped horizontally. - flipY: |- - ``` - flipY: boolean - ``` - - Indicates whether the shape is flipped vertically. - rotation: |- - ``` - rotation: number - ``` - - The rotation in degrees of the shape with respect to its center. - strokes: |- - ``` - strokes: Stroke[] - ``` - - The strokes applied to the shape. - layoutChild: |- - ``` - readonly layoutChild?: LayoutChildProperties - ``` - - Layout properties for children of the shape. - layoutCell: |- - ``` - readonly layoutCell?: LayoutCellProperties - ``` - - Layout properties for cells in a grid layout. - tokens: |- - ``` - readonly tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - } - ``` - - The design tokens applied to this shape. - It's a map property name -> token name. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - interactions: |- - ``` - readonly interactions: Interaction[] - ``` - - The interactions for the current shape. - Methods: - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` - setParentIndex: |- - ``` - setParentIndex(index: number): void - ``` - - Changes the index inside the parent of the current shape. - This method will shift the indexes of the shapes around that position to - match the index. - If the index is greater than the number of elements it will positioned last. - - Parameters - - * index: number - - the new index for the shape to be in - - Returns void - isComponentInstance: |- - ``` - isComponentInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component instance - isComponentMainInstance: |- - ``` - isComponentMainInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **main** instance - isComponentCopyInstance: |- - ``` - isComponentCopyInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **copy** instance - isComponentRoot: |- - ``` - isComponentRoot(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the root of a component tree - isComponentHead: |- - ``` - isComponentHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure - componentRefShape: |- - ``` - componentRefShape(): Shape | null - ``` - - Returns Shape | null - - Returns the equivalent shape in the component main instance. If the current shape is inside a - main instance will return `null`; - componentRoot: |- - ``` - componentRoot(): Shape | null - ``` - - Returns Shape | null - - Returns the root of the component tree structure for the current shape. If the current shape - is already a root will return itself. - componentHead: |- - ``` - componentHead(): Shape | null - ``` - - Returns Shape | null - - Returns the head of the component tree structure for the current shape. If the current shape - is already a head will return itself. - component: |- - ``` - component(): LibraryComponent | null - ``` - - Returns LibraryComponent | null - - If the shape is a component instance, returns the reference to the component associated - otherwise will return null - detach: |- - ``` - detach(): void - ``` - - If the current shape is a component it will remove the component information and leave the - shape as a "basic shape" - - Returns void - swapComponent: |- - ``` - swapComponent(component: LibraryComponent): void - ``` - - Swaps the component instance for another component, keeping the overrides - when possible. Similar to the "swap component" action on the Penpot interface. - The current shape must be a component copy instance. - - Parameters - - * component: LibraryComponent - - The new component to replace the current one - - Returns void - resetOverrides: |- - ``` - resetOverrides(): void - ``` - - Resets the overrides of the component copy, restoring all its attributes - (and those of its children) to the ones in the linked main component. - Similar to the "reset overrides" action on the Penpot interface. - The current shape must be a component copy instance. - - Returns void - switchVariant: |- - ``` - switchVariant(pos: number, value: string): void - ``` - - Switch a VariantComponent copy to the nearest one that has the specified property value - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - combineAsVariants: |- - ``` - combineAsVariants(ids: string[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu - on the Penpot interface. - The current shape must be a component main instance. - - Parameters - - * ids: string[] - - A list of ids of the main instances of the components to combine with this one. - - Returns VariantContainer - - The variant container created - isVariantHead: |- - ``` - isVariantHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure, - and that component is a VariantComponent - resize: |- - ``` - resize(width: number, height: number): void - ``` - - Resizes the shape to the specified width and height. - - Parameters - - * width: number - - The new width of the shape. - * height: number - - The new height of the shape. - - Returns void - - Example - ``` - shape.resize(200, 100); - ``` - rotate: |- - ``` - rotate(angle: number, center?: { x: number; y: number } | null): void - ``` - - Rotates the shape in relation with the given center. - - Parameters - - * angle: number - - Angle in degrees to rotate. - * center: { x: number; y: number } | null - - Center of the transform rotation. If not sent it will use the geometric center of the shape. - - Returns void - - Example - ``` - shape.rotate(45); - ``` - bringToFront: |- - ``` - bringToFront(): void - ``` - - Moves the current shape to the front of its siblings - - Returns void - bringForward: |- - ``` - bringForward(): void - ``` - - Moves the current shape one position forward in its list of siblings - - Returns void - sendToBack: |- - ``` - sendToBack(): void - ``` - - Moves the current shape to the back of its siblings - - Returns void - sendBackward: |- - ``` - sendBackward(): void - ``` - - Moves the current shape one position backwards in its list of siblings - - Returns void - export: |- - ``` - export(config: Export): Promise> - ``` - - Generates an export from the current shape. - - Parameters - - * config: Export - - Returns Promise> - - Example - ``` - shape.export({ type: 'png', scale: 2 }); - ``` - addInteraction: |- - ``` - addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction - ``` - - Adds a new interaction to the shape. - - If the interaction starts a flow (for example a `navigate-to` action) and - the shape's board is not already part of any flow, a new flow starting at - that board is created automatically, matching the behavior of the editor. - - Parameters - - * trigger: Trigger - - defines the conditions under which the action will be triggered - * action: Action - - defines what will be executed when the trigger happens - * delay: number - - for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. - - Returns Interaction - - Example - ``` - shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); - ``` - removeInteraction: |- - ``` - removeInteraction(interaction: Interaction): void - ``` - - Removes the interaction from the shape. - - Parameters - - * interaction: Interaction - - is the interaction to remove from the shape - - Returns void - - Example - ``` - shape.removeInteraction(interaction); - ``` - applyToken: |- - ``` - applyToken(token: Token, properties?: TokenProperty[]): void - ``` - - Applies one design token to one or more properties of the shape. - - Parameters - - * token: Token - - is the Token to apply - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - clone: |- - ``` - clone(): Shape - ``` - - Creates a clone of the shape. - - Returns Shape - - Returns a new instance of the shape with identical properties. - remove: |- - ``` - remove(): void - ``` - - Removes the shape from its parent. - - Returns void -EventsMap: - overview: |- - Interface EventsMap - =================== - - Represents a mapping of events to their corresponding types in Penpot. - This interface provides information about various events that can be triggered in the application. - - Example - ``` - penpot.on('pagechange', (event) => { console.log(event);}); - ``` - - ``` - interface EventsMap { - pagechange: Page; - filechange: File; - selectionchange: string[]; - themechange: Theme; - finish: string; - shapechange: Shape; - contentsave: void; - } - ``` - - Referenced by: Context, Penpot - members: - Properties: - pagechange: |- - ``` - readonly pagechange: Page - ``` - - The `pagechange` event is triggered when the active page in the project is changed. - filechange: |- - ``` - readonly filechange: File - ``` - - The `filechange` event is triggered when a different file is opened. - The callback will receive the new file. - selectionchange: |- - ``` - readonly selectionchange: string[] - ``` - - The `selectionchange` event is triggered when the selection of elements changes. - This event passes a list of identifiers of the selected elements. - themechange: |- - ``` - readonly themechange: Theme - ``` - - The `themechange` event is triggered when the application theme is changed. - finish: |- - ``` - readonly finish: string - ``` - - The `finish` event is triggered when the current file is closed. - The callback will receive the id of the closed file. - shapechange: |- - ``` - readonly shapechange: Shape - ``` - - This event will trigger whenever the shape in the props change. It's mandatory to send - with the props an object like `{ shapeId: '' }` - contentsave: |- - ``` - readonly contentsave: void - ``` - - The `contentsave` event will trigger after the file content is saved in the backend. -Export: - overview: |- - Interface Export - ================ - - Represents export settings in Penpot. - This interface includes properties for defining export configurations. - - ``` - interface Export { - type: "svg" | "png" | "jpeg" | "webp" | "pdf"; - scale?: number; - suffix?: string; - skipChildren?: boolean; - } - ``` - - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer - members: - Properties: - type: |- - ``` - type: "svg" | "png" | "jpeg" | "webp" | "pdf" - ``` - - Type of the file to export. Can be one of the following values: png, jpeg, webp, svg, pdf - scale: |- - ``` - scale?: number - ``` - - For bitmap formats represent the scale of the original size to resize the export - suffix: |- - ``` - suffix?: string - ``` - - Suffix that will be appended to the resulting exported file - skipChildren: |- - ``` - skipChildren?: boolean - ``` - - If true will ignore the children when exporting the shape -File: - overview: |- - Interface File - ============== - - File represents a file in the Penpot application. - It includes properties for the file's identifier, name, and revision number. - - ``` - interface File { - id: string; - name: string; - revn: number; - pages: Page[]; - export( - exportType: "penpot" | "zip", - libraryExportType?: "all" | "merge" | "detach", - ): Promise>; - findVersions(criteria?: { createdBy: User }): Promise; - saveVersion(label: string): Promise; - validate(): FileValidationError[]; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - } - ``` - - Hierarchy (View Summary) - - * PluginData - + File - - Referenced by: Context, EventsMap, Penpot - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The `id` property is a unique identifier for the file. - name: |- - ``` - readonly name: string - ``` - - The `name` for the file - revn: |- - ``` - readonly revn: number - ``` - - The `revn` will change for every document update - pages: |- - ``` - readonly pages: Page[] - ``` - - List all the pages for the current file - Methods: - export: |- - ``` - export( - exportType: "penpot" | "zip", - libraryExportType?: "all" | "merge" | "detach", - ): Promise> - ``` - - Export the current file to an archive. - - Parameters - - * exportType: "penpot" | "zip" - * libraryExportType: "all" | "merge" | "detach" - - Returns Promise> - - Example - ``` - const exportedData = await file.export('penpot', 'all'); - ``` - findVersions: |- - ``` - findVersions(criteria?: { createdBy: User }): Promise - ``` - - Retrieves the versions for the file. - - Parameters - - * criteria: { createdBy: User } - - Returns Promise - saveVersion: |- - ``` - saveVersion(label: string): Promise - ``` - - Saves the current version into the versions history. - Requires the `content:write` permission. - - Parameters - - * label: string - - Returns Promise - validate: |- - ``` - validate(): FileValidationError[] - ``` - - Runs the referential-integrity validation on the file and returns the list - of errors found. An empty array means the file is valid. Useful to detect - inconsistencies (dangling references, broken components/variants, …) that - the backend would otherwise reject when the file is saved. - - Returns FileValidationError[] - - Example - ``` - const errors = file.validate();if (errors.length) console.error(errors); - ``` - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` -FileValidationError: - overview: |- - Interface FileValidationError - ============================= - - A single referential-integrity error reported by File.validate. - - ``` - interface FileValidationError { - code: string; - hint: string; - shapeId: string | null; - pageId: string | null; - } - ``` - - Referenced by: File - members: - Properties: - code: |- - ``` - readonly code: string - ``` - - The validation error code (e.g. `'variant-component-bad-name'`, - `'child-not-found'`). - hint: |- - ``` - readonly hint: string - ``` - - A human-readable description of the error. - shapeId: |- - ``` - readonly shapeId: string | null - ``` - - The id of the offending shape, when the error is attached to one. - pageId: |- - ``` - readonly pageId: string | null - ``` - - The id of the page the offending shape lives in, when applicable. -FileVersion: - overview: |- - Interface FileVersion - ===================== - - Type defining the file version properties. - - ``` - interface FileVersion { - label: string; - createdBy?: User; - createdAt: Date; - isAutosave: boolean; - restore(): void; - remove(): Promise; - pin(): Promise; - } - ``` - - Referenced by: File, FileVersion - members: - Properties: - label: |- - ``` - label: string - ``` - - The current label to identify the version. - createdBy: |- - ``` - readonly createdBy?: User - ``` - - The user that created the version. If not present, the - version is an autosave. - createdAt: |- - ``` - readonly createdAt: Date - ``` - - The date when the version was created. - isAutosave: |- - ``` - readonly isAutosave: boolean - ``` - - If the current version has been generated automatically. - Methods: - restore: |- - ``` - restore(): void - ``` - - Restores the current version and replaces the content of the active file - for the contents of this version. - Requires the `content:write` permission. - Warning: Calling this will close the plugin because the workspace will reload - - Returns void - remove: |- - ``` - remove(): Promise - ``` - - Remove the current version. - Requires the `content:write` permission. - - Returns Promise - pin: |- - ``` - pin(): Promise - ``` - - Converts an autosave version into a permanent version. - Requires the `content:write` permission. - - Returns Promise -Fill: - overview: |- - Interface Fill - ============== - - Represents fill properties in Penpot. You can add a fill to any shape except for groups. - This interface includes properties for defining solid color fills, gradient fills, and image fills. - - ``` - interface Fill { - fillColor?: string; - fillOpacity?: number; - fillColorGradient?: Gradient; - fillColorRefFile?: string; - fillColorRefId?: string; - fillImage?: ImageData; - } - ``` - - Referenced by: Board, Boolean, Ellipse, Group, Image, LibraryColor, Path, Rectangle, ShapeBase, SvgRaw, Text, TextRange, VariantContainer - members: - Properties: - fillColor: |- - ``` - fillColor?: string - ``` - - The optional solid fill color, represented as a string (e.g., '#FF5733'). - fillOpacity: |- - ``` - fillOpacity?: number - ``` - - The optional opacity level of the solid fill color, ranging from 0 (fully transparent) to 1 (fully opaque). - Defaults to 1 if omitted. - fillColorGradient: |- - ``` - fillColorGradient?: Gradient - ``` - - The optional gradient fill defined by a Gradient object. - fillColorRefFile: |- - ``` - fillColorRefFile?: string - ``` - - The optional reference to an external file for the fill color. - fillColorRefId: |- - ``` - fillColorRefId?: string - ``` - - The optional reference ID within the external file for the fill color. - fillImage: |- - ``` - fillImage?: ImageData - ``` - - The optional image fill defined by an ImageData object. -Flags: - overview: |- - Interface Flags - =============== - - This subcontext allows the API to change certain defaults - - ``` - interface Flags { - naturalChildOrdering: boolean; - throwValidationErrors: boolean; - } - ``` - - Referenced by: Context, Penpot - members: - Properties: - naturalChildOrdering: |- - ``` - naturalChildOrdering: boolean - ``` - - If `true` the .children property will always be sorted in the z-index ordering. - Also, the appendChild method will append the children in the top-most position. - The insertChild method is changed accordingly to respect this ordering. - Defaults to false - throwValidationErrors: |- - ``` - throwValidationErrors: boolean - ``` - - If `true` the validation errors will throw an exception instead of displaying an - error in the debugger console. - Defaults to `true` for plugins whose manifest declares `"version": 2` (or higher) - and to `false` for v1 plugins (or manifests that omit the version field). -FlexLayout: - overview: |- - Interface FlexLayout - ==================== - - Represents a flexible layout configuration in Penpot. - This interface extends `CommonLayout` and includes properties for defining the direction, - wrapping behavior, and child management of a flex layout. - - ``` - interface FlexLayout { - alignItems?: "end" | "start" | "center" | "stretch"; - alignContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly"; - justifyItems?: "end" - | "start" - | "center" - | "stretch"; - justifyContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly"; - rowGap: number; - columnGap: number; - verticalPadding: number; - horizontalPadding: number; - topPadding: number; - rightPadding: number; - bottomPadding: number; - leftPadding: number; - horizontalSizing: "fill" - | "auto" - | "fix"; - verticalSizing: "fill" | "auto" | "fix"; - remove(): void; - dir: "row" | "row-reverse" | "column" | "column-reverse"; - wrap?: "wrap" | "nowrap"; - appendChild(child: Shape): void; - } - ``` - - Hierarchy (View Summary) - - * CommonLayout - + FlexLayout - - Referenced by: Board, VariantContainer - members: - Properties: - alignItems: |- - ``` - alignItems?: "end" | "start" | "center" | "stretch" - ``` - - The `alignItems` property specifies the default alignment for items inside the container. - It can be one of the following values: - - * 'start': Items are aligned at the start. - * 'end': Items are aligned at the end. - * 'center': Items are centered. - * 'stretch': Items are stretched to fill the container. - alignContent: |- - ``` - alignContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly" - ``` - - The `alignContent` property specifies how the content is aligned within the container when there is extra space. - It can be one of the following values: - - * 'start': Content is aligned at the start. - * 'end': Content is aligned at the end. - * 'center': Content is centered. - * 'space-between': Content is distributed with space between. - * 'space-around': Content is distributed with space around. - * 'space-evenly': Content is distributed with even space around. - * 'stretch': Content is stretched to fill the container. - justifyItems: |- - ``` - justifyItems?: "end" | "start" | "center" | "stretch" - ``` - - The `justifyItems` property specifies the default justification for items inside the container. - It can be one of the following values: - - * 'start': Items are justified at the start. - * 'end': Items are justified at the end. - * 'center': Items are centered. - * 'stretch': Items are stretched to fill the container. - justifyContent: |- - ``` - justifyContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly" - ``` - - The `justifyContent` property specifies how the content is justified within the container when there is extra space. - It can be one of the following values: - - * 'start': Content is justified at the start. - * 'center': Content is centered. - * 'end': Content is justified at the end. - * 'space-between': Content is distributed with space between. - * 'space-around': Content is distributed with space around. - * 'space-evenly': Content is distributed with even space around. - * 'stretch': Content is stretched to fill the container. - rowGap: |- - ``` - rowGap: number - ``` - - The `rowGap` property specifies the gap between rows in the layout. - columnGap: |- - ``` - columnGap: number - ``` - - The `columnGap` property specifies the gap between columns in the layout. - verticalPadding: |- - ``` - verticalPadding: number - ``` - - The `verticalPadding` property specifies the vertical padding inside the container. - horizontalPadding: |- - ``` - horizontalPadding: number - ``` - - The `horizontalPadding` property specifies the horizontal padding inside the container. - topPadding: |- - ``` - topPadding: number - ``` - - The `topPadding` property specifies the padding at the top of the container. - rightPadding: |- - ``` - rightPadding: number - ``` - - The `rightPadding` property specifies the padding at the right of the container. - bottomPadding: |- - ``` - bottomPadding: number - ``` - - The `bottomPadding` property specifies the padding at the bottom of the container. - leftPadding: |- - ``` - leftPadding: number - ``` - - The `leftPadding` property specifies the padding at the left of the container. - horizontalSizing: |- - ``` - horizontalSizing: "fill" | "auto" | "fix" - ``` - - The `horizontalSizing` property specifies the horizontal sizing behavior of the container. - It can be one of the following values: - - * 'fix': The container has its own intrinsic fixed size. - * 'fill': The container fills the available space. Only can be set if it's inside another layout. - * 'auto': The container fits the content. - verticalSizing: |- - ``` - verticalSizing: "fill" | "auto" | "fix" - ``` - - The `verticalSizing` property specifies the vertical sizing behavior of the container. - It can be one of the following values: - - * 'fix': The container has its own intrinsic fixed size. - * 'fill': The container fills the available space. Only can be set if it's inside another layout. - * 'auto': The container fits the content. - dir: |- - ``` - dir: "row" | "row-reverse" | "column" | "column-reverse" - ``` - - The direction of the flex layout. - - * 'row': Main axis is horizontal, from left to right. - * 'row-reverse': Main axis is horizontal, from right to left. - * 'column': Main axis is vertical, from top to bottom. - * 'column-reverse': Main axis is vertical, from bottom to top. - wrap: |- - ``` - wrap?: "wrap" | "nowrap" - ``` - - The optional wrapping behavior of the flex layout. - - * 'wrap': Child elements will wrap onto multiple lines. - * 'nowrap': Child elements will not wrap. - Methods: - remove: |- - ``` - remove(): void - ``` - - The `remove` method removes the layout. - - Returns void - appendChild: |- - ``` - appendChild(child: Shape): void - ``` - - Appends a child element to the flex layout. - - Parameters - - * child: Shape - - The child element to be appended, of type `Shape`. - - Returns void - - Example - ``` - flexLayout.appendChild(childShape); - ``` -Flow: - overview: |- - Interface Flow - ============== - - Defines an interaction flow inside penpot. A flow is defined by a starting board for an interaction. - - ``` - interface Flow { - page: Page; - name: string; - startingBoard: Board; - remove(): void; - } - ``` - - Referenced by: Page - members: - Properties: - page: |- - ``` - readonly page: Page - ``` - - The page in which the flow is defined - name: |- - ``` - name: string - ``` - - The name for the current flow - startingBoard: |- - ``` - startingBoard: Board - ``` - - The starting board for this interaction flow - Methods: - remove: |- - ``` - remove(): void - ``` - - Removes the flow from the page - - Returns void -Font: - overview: |- - Interface Font - ============== - - Represents a font in Penpot, which includes details about the font family, variants, and styling options. - This interface provides properties and methods for describing and applying fonts within Penpot. - - ``` - interface Font { - name: string; - fontId: string; - fontFamily: string; - fontStyle?: "normal" | "italic" | null; - fontVariantId: string; - fontWeight: string; - variants: FontVariant[]; - applyToText(text: Text, variant?: FontVariant): void; - applyToRange(range: TextRange, variant?: FontVariant): void; - } - ``` - - Referenced by: FontsContext, LibraryTypography - members: - Properties: - name: |- - ``` - readonly name: string - ``` - - This property holds the human-readable name of the font. - fontId: |- - ``` - readonly fontId: string - ``` - - The unique identifier of the font. - fontFamily: |- - ``` - readonly fontFamily: string - ``` - - The font family of the font. - fontStyle: |- - ``` - readonly fontStyle?: "normal" | "italic" | null - ``` - - The default font style of the font. - fontVariantId: |- - ``` - readonly fontVariantId: string - ``` - - The default font variant ID of the font. - fontWeight: |- - ``` - readonly fontWeight: string - ``` - - The default font weight of the font. - variants: |- - ``` - readonly variants: FontVariant[] - ``` - - An array of font variants available for the font. - Methods: - applyToText: |- - ``` - applyToText(text: Text, variant?: FontVariant): void - ``` - - Applies the font styles to a text shape. - - Parameters - - * text: Text - - The text shape to apply the font styles to. - * variant: FontVariant - - Optional. The specific font variant to apply. If not provided, applies the default variant. - - Returns void - - Example - ``` - font.applyToText(textShape, fontVariant); - ``` - applyToRange: |- - ``` - applyToRange(range: TextRange, variant?: FontVariant): void - ``` - - Applies the font styles to a text range within a text shape. - - Parameters - - * range: TextRange - - The text range to apply the font styles to. - * variant: FontVariant - - Optional. The specific font variant to apply. If not provided, applies the default variant. - - Returns void - - Example - ``` - font.applyToRange(textRange, fontVariant); - ``` -FontVariant: - overview: |- - Interface FontVariant - ===================== - - Represents a font variant in Penpot, which defines a specific style variation of a font. - This interface provides properties for describing the characteristics of a font variant. - - ``` - interface FontVariant { - name: string; - fontVariantId: string; - fontWeight: string; - fontStyle: "normal" | "italic"; - } - ``` - - Referenced by: Font, LibraryTypography - members: - Properties: - name: |- - ``` - readonly name: string - ``` - - The name of the font variant. - fontVariantId: |- - ``` - readonly fontVariantId: string - ``` - - The unique identifier of the font variant. - fontWeight: |- - ``` - readonly fontWeight: string - ``` - - The font weight of the font variant. - fontStyle: |- - ``` - readonly fontStyle: "normal" | "italic" - ``` - - The font style of the font variant. -FontsContext: - overview: |- - Interface FontsContext - ====================== - - Represents the context for managing fonts in Penpot. - This interface provides methods to interact with fonts, such as retrieving fonts by ID or name. - - ``` - interface FontsContext { - all: Font[]; - findById(id: string): Font | null; - findByName(name: string): Font | null; - findAllById(id: string): Font[]; - findAllByName(name: string): Font[]; - } - ``` - - Referenced by: Context, Penpot - members: - Properties: - all: |- - ``` - readonly all: Font[] - ``` - - An array containing all available fonts. - Methods: - findById: |- - ``` - findById(id: string): Font | null - ``` - - Finds a font by its unique identifier. - - Parameters - - * id: string - - The ID of the font to find. - - Returns Font | null - - Returns the `Font` object if found, otherwise `null`. - - Example - ``` - const font = fontsContext.findById('font-id');if (font) { console.log(font.name);} - ``` - findByName: |- - ``` - findByName(name: string): Font | null - ``` - - Finds a font by its name. - - Parameters - - * name: string - - The name of the font to find. - - Returns Font | null - - Returns the `Font` object if found, otherwise `null`. - - Example - ``` - const font = fontsContext.findByName('font-name');if (font) { console.log(font.name);} - ``` - findAllById: |- - ``` - findAllById(id: string): Font[] - ``` - - Finds all fonts matching a specific ID. - - Parameters - - * id: string - - The ID to match against. - - Returns Font[] - - Returns an array of `Font` objects matching the provided ID. - - Example - ``` - const fonts = fontsContext.findAllById('font-id');console.log(fonts); - ``` - findAllByName: |- - ``` - findAllByName(name: string): Font[] - ``` - - Finds all fonts matching a specific name. - - Parameters - - * name: string - - The name to match against. - - Returns Font[] - - Returns an array of `Font` objects matching the provided name. - - Example - ``` - const fonts = fontsContext.findAllByName('font-name');console.log(fonts); - ``` -GridLayout: - overview: |- - Interface GridLayout - ==================== - - GridLayout represents a grid layout in the Penpot application, extending the common layout interface. - It includes properties and methods to manage rows, columns, and child elements within the grid. - - ``` - interface GridLayout { - alignItems?: "end" | "start" | "center" | "stretch"; - alignContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly"; - justifyItems?: "end" - | "start" - | "center" - | "stretch"; - justifyContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly"; - rowGap: number; - columnGap: number; - verticalPadding: number; - horizontalPadding: number; - topPadding: number; - rightPadding: number; - bottomPadding: number; - leftPadding: number; - horizontalSizing: "fill" - | "auto" - | "fix"; - verticalSizing: "fill" | "auto" | "fix"; - remove(): void; - dir: "row" | "column"; - rows: Track[]; - columns: Track[]; - addRow(type: TrackType, value?: number): void; - addRowAtIndex(index: number, type: TrackType, value?: number): void; - addColumn(type: TrackType, value?: number): void; - addColumnAtIndex(index: number, type: TrackType, value: number): void; - removeRow(index: number): void; - removeColumn(index: number): void; - setColumn(index: number, type: TrackType, value?: number): void; - setRow(index: number, type: TrackType, value?: number): void; - appendChild(child: Shape, row: number, column: number): void; - } - ``` - - Hierarchy (View Summary) - - * CommonLayout - + GridLayout - - Referenced by: Board, VariantContainer - members: - Properties: - alignItems: |- - ``` - alignItems?: "end" | "start" | "center" | "stretch" - ``` - - The `alignItems` property specifies the default alignment for items inside the container. - It can be one of the following values: - - * 'start': Items are aligned at the start. - * 'end': Items are aligned at the end. - * 'center': Items are centered. - * 'stretch': Items are stretched to fill the container. - alignContent: |- - ``` - alignContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly" - ``` - - The `alignContent` property specifies how the content is aligned within the container when there is extra space. - It can be one of the following values: - - * 'start': Content is aligned at the start. - * 'end': Content is aligned at the end. - * 'center': Content is centered. - * 'space-between': Content is distributed with space between. - * 'space-around': Content is distributed with space around. - * 'space-evenly': Content is distributed with even space around. - * 'stretch': Content is stretched to fill the container. - justifyItems: |- - ``` - justifyItems?: "end" | "start" | "center" | "stretch" - ``` - - The `justifyItems` property specifies the default justification for items inside the container. - It can be one of the following values: - - * 'start': Items are justified at the start. - * 'end': Items are justified at the end. - * 'center': Items are centered. - * 'stretch': Items are stretched to fill the container. - justifyContent: |- - ``` - justifyContent?: - | "end" - | "start" - | "center" - | "stretch" - | "space-between" - | "space-around" - | "space-evenly" - ``` - - The `justifyContent` property specifies how the content is justified within the container when there is extra space. - It can be one of the following values: - - * 'start': Content is justified at the start. - * 'center': Content is centered. - * 'end': Content is justified at the end. - * 'space-between': Content is distributed with space between. - * 'space-around': Content is distributed with space around. - * 'space-evenly': Content is distributed with even space around. - * 'stretch': Content is stretched to fill the container. - rowGap: |- - ``` - rowGap: number - ``` - - The `rowGap` property specifies the gap between rows in the layout. - columnGap: |- - ``` - columnGap: number - ``` - - The `columnGap` property specifies the gap between columns in the layout. - verticalPadding: |- - ``` - verticalPadding: number - ``` - - The `verticalPadding` property specifies the vertical padding inside the container. - horizontalPadding: |- - ``` - horizontalPadding: number - ``` - - The `horizontalPadding` property specifies the horizontal padding inside the container. - topPadding: |- - ``` - topPadding: number - ``` - - The `topPadding` property specifies the padding at the top of the container. - rightPadding: |- - ``` - rightPadding: number - ``` - - The `rightPadding` property specifies the padding at the right of the container. - bottomPadding: |- - ``` - bottomPadding: number - ``` - - The `bottomPadding` property specifies the padding at the bottom of the container. - leftPadding: |- - ``` - leftPadding: number - ``` - - The `leftPadding` property specifies the padding at the left of the container. - horizontalSizing: |- - ``` - horizontalSizing: "fill" | "auto" | "fix" - ``` - - The `horizontalSizing` property specifies the horizontal sizing behavior of the container. - It can be one of the following values: - - * 'fix': The container has its own intrinsic fixed size. - * 'fill': The container fills the available space. Only can be set if it's inside another layout. - * 'auto': The container fits the content. - verticalSizing: |- - ``` - verticalSizing: "fill" | "auto" | "fix" - ``` - - The `verticalSizing` property specifies the vertical sizing behavior of the container. - It can be one of the following values: - - * 'fix': The container has its own intrinsic fixed size. - * 'fill': The container fills the available space. Only can be set if it's inside another layout. - * 'auto': The container fits the content. - dir: |- - ``` - dir: "row" | "column" - ``` - - The `dir` property specifies the primary direction of the grid layout. - It can be either 'column' or 'row'. - rows: |- - ``` - readonly rows: Track[] - ``` - - The `rows` property represents the collection of rows in the grid. - This property is read-only. - columns: |- - ``` - readonly columns: Track[] - ``` - - The `columns` property represents the collection of columns in the grid. - This property is read-only. - Methods: - remove: |- - ``` - remove(): void - ``` - - The `remove` method removes the layout. - - Returns void - addRow: |- - ``` - addRow(type: TrackType, value?: number): void - ``` - - Adds a new row to the grid. - - Parameters - - * type: TrackType - - The type of the row to add. - * value: number - - The value associated with the row type (optional). - - Returns void - - Example - ``` - const board = penpot.createBoard();const grid = board.addGridLayout();grid.addRow("flex", 1); - ``` - addRowAtIndex: |- - ``` - addRowAtIndex(index: number, type: TrackType, value?: number): void - ``` - - Adds a new row to the grid at the specified index. - - Parameters - - * index: number - - The index at which to add the row. - * type: TrackType - - The type of the row to add. - * value: number - - The value associated with the row type (optional). - - Returns void - - Example - ``` - gridLayout.addRowAtIndex(0, 'fixed', 100); - ``` - addColumn: |- - ``` - addColumn(type: TrackType, value?: number): void - ``` - - Adds a new column to the grid. - - Parameters - - * type: TrackType - - The type of the column to add. - * value: number - - The value associated with the column type (optional). - - Returns void - - Example - ``` - const board = penpot.createBoard();const grid = board.addGridLayout();grid.addColumn('percent', 50); - ``` - addColumnAtIndex: |- - ``` - addColumnAtIndex(index: number, type: TrackType, value: number): void - ``` - - Adds a new column to the grid at the specified index. - - Parameters - - * index: number - - The index at which to add the column. - * type: TrackType - - The type of the column to add. - * value: number - - The value associated with the column type. - - Returns void - - Example - ``` - gridLayout.addColumnAtIndex(1, 'auto'); - ``` - removeRow: |- - ``` - removeRow(index: number): void - ``` - - Removes a row from the grid at the specified index. - - Parameters - - * index: number - - The index of the row to remove. - - Returns void - - Example - ``` - gridLayout.removeRow(2); - ``` - removeColumn: |- - ``` - removeColumn(index: number): void - ``` - - Removes a column from the grid at the specified index. - - Parameters - - * index: number - - The index of the column to remove. - - Returns void - - Example - ``` - gridLayout.removeColumn(3); - ``` - setColumn: |- - ``` - setColumn(index: number, type: TrackType, value?: number): void - ``` - - Sets the properties of a column at the specified index. - - Parameters - - * index: number - - The index of the column to set. - * type: TrackType - - The type of the column. - * value: number - - The value associated with the column type (optional). - - Returns void - - Example - ``` - gridLayout.setColumn(0, 'fixed', 200); - ``` - setRow: |- - ``` - setRow(index: number, type: TrackType, value?: number): void - ``` - - Sets the properties of a row at the specified index. - - Parameters - - * index: number - - The index of the row to set. - * type: TrackType - - The type of the row. - * value: number - - The value associated with the row type (optional). - - Returns void - - Example - ``` - gridLayout.setRow(1, 'flex'); - ``` - appendChild: |- - ``` - appendChild(child: Shape, row: number, column: number): void - ``` - - Appends a child element to the grid at the specified row and column. - - Parameters - - * child: Shape - - The child element to append. - * row: number - - The row index where the child will be placed. - * column: number - - The column index where the child will be placed. - - Returns void - - Example - ``` - gridLayout.appendChild(childShape, 0, 1); - ``` -Group: - overview: |- - Interface Group - =============== - - Represents a group of shapes in Penpot. - This interface extends `ShapeBase` and includes properties and methods specific to groups. - - ``` - interface Group { - type: "group"; - children: Shape[]; - appendChild(child: Shape): void; - insertChild(index: number, child: Shape): void; - isMask(): boolean; - makeMask(): void; - removeMask(): void; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - id: string; - name: string; - parent: Shape | null; - parentIndex: number; - x: number; - y: number; - width: number; - height: number; - bounds: Bounds; - center: Point; - blocked: boolean; - hidden: boolean; - visible: boolean; - proportionLock: boolean; - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; - fixedWhenScrolling: boolean; - borderRadius: number; - borderRadiusTopLeft: number; - borderRadiusTopRight: number; - borderRadiusBottomRight: number; - borderRadiusBottomLeft: number; - opacity: number; - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity"; - shadows: Shadow[]; - blur?: Blur; - backgroundBlur?: Blur; - exports: Export[]; - boardX: number; - boardY: number; - parentX: number; - parentY: number; - flipX: boolean; - flipY: boolean; - rotation: number; - fills: Fill[] - | "mixed"; - strokes: Stroke[]; - layoutChild?: LayoutChildProperties; - layoutCell?: LayoutCellProperties; - setParentIndex(index: number): void; - tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - }; - isComponentInstance(): boolean; - isComponentMainInstance(): boolean; - isComponentCopyInstance(): boolean; - isComponentRoot(): boolean; - isComponentHead(): boolean; - componentRefShape(): Shape | null; - componentRoot(): Shape | null; - componentHead(): Shape | null; - component(): LibraryComponent | null; - detach(): void; - swapComponent(component: LibraryComponent): void; - resetOverrides(): void; - switchVariant(pos: number, value: string): void; - combineAsVariants(ids: string[]): VariantContainer; - isVariantHead(): boolean; - resize(width: number, height: number): void; - rotate(angle: number, center?: { x: number; y: number } | null): void; - bringToFront(): void; - bringForward(): void; - sendToBack(): void; - sendBackward(): void; - export(config: Export): Promise>; - interactions: Interaction[]; - addInteraction( - trigger: Trigger, - action: Action, - delay?: number, - ): Interaction; - removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties?: TokenProperty[]): void; - clone(): Shape; - remove(): void; - } - ``` - - Hierarchy (View Summary) - - * ShapeBase - + Group - - Referenced by: Context, ContextTypesUtils, Penpot, Shape - members: - Properties: - type: |- - ``` - readonly type: "group" - ``` - - The type of the shape, which is always 'group' for groups. - children: |- - ``` - readonly children: Shape[] - ``` - - The children shapes contained within the group. - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the shape. - name: |- - ``` - name: string - ``` - - The name of the shape. - parent: |- - ``` - readonly parent: Shape | null - ``` - - The parent shape. If the shape is the first level the parent will be the root shape. - For the root shape the parent is null - parentIndex: |- - ``` - readonly parentIndex: number - ``` - - Returns the index of the current shape in the parent - x: |- - ``` - x: number - ``` - - The x-coordinate of the shape's position. - y: |- - ``` - y: number - ``` - - The y-coordinate of the shape's position. - width: |- - ``` - readonly width: number - ``` - - The width of the shape. - height: |- - ``` - readonly height: number - ``` - - The height of the shape. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - The bounding box surrounding the current shape - center: |- - ``` - readonly center: Point - ``` - - The geometric center of the shape - blocked: |- - ``` - blocked: boolean - ``` - - Indicates whether the shape is blocked. - hidden: |- - ``` - hidden: boolean - ``` - - Indicates whether the shape is hidden. - visible: |- - ``` - visible: boolean - ``` - - Indicates whether the shape is visible. - proportionLock: |- - ``` - proportionLock: boolean - ``` - - Indicates whether the shape has proportion lock enabled. - constraintsHorizontal: |- - ``` - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale" - ``` - - The horizontal constraints applied to the shape. - constraintsVertical: |- - ``` - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom" - ``` - - The vertical constraints applied to the shape. - fixedWhenScrolling: |- - ``` - fixedWhenScrolling: boolean - ``` - - Indicates whether the shape stays fixed in place while scrolling. - borderRadius: |- - ``` - borderRadius: number - ``` - - The border radius of the shape. - borderRadiusTopLeft: |- - ``` - borderRadiusTopLeft: number - ``` - - The border radius of the top-left corner of the shape. - borderRadiusTopRight: |- - ``` - borderRadiusTopRight: number - ``` - - The border radius of the top-right corner of the shape. - borderRadiusBottomRight: |- - ``` - borderRadiusBottomRight: number - ``` - - The border radius of the bottom-right corner of the shape. - borderRadiusBottomLeft: |- - ``` - borderRadiusBottomLeft: number - ``` - - The border radius of the bottom-left corner of the shape. - opacity: |- - ``` - opacity: number - ``` - - The opacity of the shape. - blendMode: |- - ``` - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity" - ``` - - The blend mode applied to the shape. - shadows: |- - ``` - shadows: Shadow[] - ``` - - The shadows applied to the shape. - blur: |- - ``` - blur?: Blur - ``` - - The blur effect applied to the shape. - backgroundBlur: |- - ``` - backgroundBlur?: Blur - ``` - - The background blur effect applied to the shape. - Background blur creates a blur effect on the content behind the shape, - rather than on the shape's own content. - exports: |- - ``` - exports: Export[] - ``` - - The export settings of the shape. - boardX: |- - ``` - boardX: number - ``` - - The x-coordinate of the shape relative to its board. - boardY: |- - ``` - boardY: number - ``` - - The y-coordinate of the shape relative to its board. - parentX: |- - ``` - parentX: number - ``` - - The x-coordinate of the shape relative to its parent. - parentY: |- - ``` - parentY: number - ``` - - The y-coordinate of the shape relative to its parent. - flipX: |- - ``` - flipX: boolean - ``` - - Indicates whether the shape is flipped horizontally. - flipY: |- - ``` - flipY: boolean - ``` - - Indicates whether the shape is flipped vertically. - rotation: |- - ``` - rotation: number - ``` - - The rotation in degrees of the shape with respect to its center. - fills: |- - ``` - fills: Fill[] | "mixed" - ``` - - The fills applied to the shape. - strokes: |- - ``` - strokes: Stroke[] - ``` - - The strokes applied to the shape. - layoutChild: |- - ``` - readonly layoutChild?: LayoutChildProperties - ``` - - Layout properties for children of the shape. - layoutCell: |- - ``` - readonly layoutCell?: LayoutCellProperties - ``` - - Layout properties for cells in a grid layout. - tokens: |- - ``` - readonly tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - } - ``` - - The design tokens applied to this shape. - It's a map property name -> token name. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - interactions: |- - ``` - readonly interactions: Interaction[] - ``` - - The interactions for the current shape. - Methods: - appendChild: |- - ``` - appendChild(child: Shape): void - ``` - - Appends a child shape to the group. - - Parameters - - * child: Shape - - The child shape to append. - - Returns void - - Example - ``` - group.appendChild(childShape); - ``` - insertChild: |- - ``` - insertChild(index: number, child: Shape): void - ``` - - Inserts a child shape at the specified index within the group. - - Parameters - - * index: number - - The index at which to insert the child shape. - * child: Shape - - The child shape to insert. - - Returns void - - Example - ``` - group.insertChild(0, childShape); - ``` - isMask: |- - ``` - isMask(): boolean - ``` - - Checks if the group is currently a mask. - A mask defines a clipping path for its child shapes. - - Returns boolean - makeMask: |- - ``` - makeMask(): void - ``` - - Converts the group into a mask. - - Returns void - removeMask: |- - ``` - removeMask(): void - ``` - - Removes the mask from the group. - - Returns void - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` - setParentIndex: |- - ``` - setParentIndex(index: number): void - ``` - - Changes the index inside the parent of the current shape. - This method will shift the indexes of the shapes around that position to - match the index. - If the index is greater than the number of elements it will positioned last. - - Parameters - - * index: number - - the new index for the shape to be in - - Returns void - isComponentInstance: |- - ``` - isComponentInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component instance - isComponentMainInstance: |- - ``` - isComponentMainInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **main** instance - isComponentCopyInstance: |- - ``` - isComponentCopyInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **copy** instance - isComponentRoot: |- - ``` - isComponentRoot(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the root of a component tree - isComponentHead: |- - ``` - isComponentHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure - componentRefShape: |- - ``` - componentRefShape(): Shape | null - ``` - - Returns Shape | null - - Returns the equivalent shape in the component main instance. If the current shape is inside a - main instance will return `null`; - componentRoot: |- - ``` - componentRoot(): Shape | null - ``` - - Returns Shape | null - - Returns the root of the component tree structure for the current shape. If the current shape - is already a root will return itself. - componentHead: |- - ``` - componentHead(): Shape | null - ``` - - Returns Shape | null - - Returns the head of the component tree structure for the current shape. If the current shape - is already a head will return itself. - component: |- - ``` - component(): LibraryComponent | null - ``` - - Returns LibraryComponent | null - - If the shape is a component instance, returns the reference to the component associated - otherwise will return null - detach: |- - ``` - detach(): void - ``` - - If the current shape is a component it will remove the component information and leave the - shape as a "basic shape" - - Returns void - swapComponent: |- - ``` - swapComponent(component: LibraryComponent): void - ``` - - Swaps the component instance for another component, keeping the overrides - when possible. Similar to the "swap component" action on the Penpot interface. - The current shape must be a component copy instance. - - Parameters - - * component: LibraryComponent - - The new component to replace the current one - - Returns void - resetOverrides: |- - ``` - resetOverrides(): void - ``` - - Resets the overrides of the component copy, restoring all its attributes - (and those of its children) to the ones in the linked main component. - Similar to the "reset overrides" action on the Penpot interface. - The current shape must be a component copy instance. - - Returns void - switchVariant: |- - ``` - switchVariant(pos: number, value: string): void - ``` - - Switch a VariantComponent copy to the nearest one that has the specified property value - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - combineAsVariants: |- - ``` - combineAsVariants(ids: string[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu - on the Penpot interface. - The current shape must be a component main instance. - - Parameters - - * ids: string[] - - A list of ids of the main instances of the components to combine with this one. - - Returns VariantContainer - - The variant container created - isVariantHead: |- - ``` - isVariantHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure, - and that component is a VariantComponent - resize: |- - ``` - resize(width: number, height: number): void - ``` - - Resizes the shape to the specified width and height. - - Parameters - - * width: number - - The new width of the shape. - * height: number - - The new height of the shape. - - Returns void - - Example - ``` - shape.resize(200, 100); - ``` - rotate: |- - ``` - rotate(angle: number, center?: { x: number; y: number } | null): void - ``` - - Rotates the shape in relation with the given center. - - Parameters - - * angle: number - - Angle in degrees to rotate. - * center: { x: number; y: number } | null - - Center of the transform rotation. If not sent it will use the geometric center of the shape. - - Returns void - - Example - ``` - shape.rotate(45); - ``` - bringToFront: |- - ``` - bringToFront(): void - ``` - - Moves the current shape to the front of its siblings - - Returns void - bringForward: |- - ``` - bringForward(): void - ``` - - Moves the current shape one position forward in its list of siblings - - Returns void - sendToBack: |- - ``` - sendToBack(): void - ``` - - Moves the current shape to the back of its siblings - - Returns void - sendBackward: |- - ``` - sendBackward(): void - ``` - - Moves the current shape one position backwards in its list of siblings - - Returns void - export: |- - ``` - export(config: Export): Promise> - ``` - - Generates an export from the current shape. - - Parameters - - * config: Export - - Returns Promise> - - Example - ``` - shape.export({ type: 'png', scale: 2 }); - ``` - addInteraction: |- - ``` - addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction - ``` - - Adds a new interaction to the shape. - - If the interaction starts a flow (for example a `navigate-to` action) and - the shape's board is not already part of any flow, a new flow starting at - that board is created automatically, matching the behavior of the editor. - - Parameters - - * trigger: Trigger - - defines the conditions under which the action will be triggered - * action: Action - - defines what will be executed when the trigger happens - * delay: number - - for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. - - Returns Interaction - - Example - ``` - shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); - ``` - removeInteraction: |- - ``` - removeInteraction(interaction: Interaction): void - ``` - - Removes the interaction from the shape. - - Parameters - - * interaction: Interaction - - is the interaction to remove from the shape - - Returns void - - Example - ``` - shape.removeInteraction(interaction); - ``` - applyToken: |- - ``` - applyToken(token: Token, properties?: TokenProperty[]): void - ``` - - Applies one design token to one or more properties of the shape. - - Parameters - - * token: Token - - is the Token to apply - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - clone: |- - ``` - clone(): Shape - ``` - - Creates a clone of the shape. - - Returns Shape - - Returns a new instance of the shape with identical properties. - remove: |- - ``` - remove(): void - ``` - - Removes the shape from its parent. - - Returns void -GuideColumn: - overview: |- - Interface GuideColumn - ===================== - - Represents a board guide for columns in Penpot. - This interface includes properties for defining the type, visibility, and parameters of column guides within a board. - - ``` - interface GuideColumn { - type: "column"; - display: boolean; - params: GuideColumnParams; - } - ``` - - Referenced by: Guide - members: - Properties: - type: |- - ``` - readonly type: "column" - ``` - - The type of the guide, which is always 'column' for column guides. - display: |- - ``` - readonly display: boolean - ``` - - Specifies whether the column guide is displayed. - params: |- - ``` - readonly params: GuideColumnParams - ``` - - The parameters defining the appearance and layout of the column guides. -GuideColumnParams: - overview: |- - Interface GuideColumnParams - =========================== - - Represents parameters for board guide columns in Penpot. - This interface includes properties for defining the appearance and layout of column guides within a board. - - ``` - interface GuideColumnParams { - color: { color: string; opacity: number }; - type?: "center" | "left" | "right" | "stretch"; - size?: number; - margin?: number; - itemLength?: number; - gutter?: number; - } - ``` - - Referenced by: GuideColumn, GuideRow - members: - Properties: - color: |- - ``` - readonly color: { color: string; opacity: number } - ``` - - The color configuration for the column guides. - type: |- - ``` - readonly type?: "center" | "left" | "right" | "stretch" - ``` - - The optional alignment type of the column guides. - - * 'stretch': Columns stretch to fit the available space. - * 'left': Columns align to the left. - * 'center': Columns align to the center. - * 'right': Columns align to the right. - size: |- - ``` - readonly size?: number - ``` - - The optional size of each column. - margin: |- - ``` - readonly margin?: number - ``` - - The optional margin between the columns and the board edges. - itemLength: |- - ``` - readonly itemLength?: number - ``` - - The optional length of each item within the columns. - gutter: |- - ``` - readonly gutter?: number - ``` - - The optional gutter width between columns. -GuideRow: - overview: |- - Interface GuideRow - ================== - - Represents a board guide for rows in Penpot. - This interface includes properties for defining the type, visibility, and parameters of row guides within a board. - - ``` - interface GuideRow { - type: "row"; - display: boolean; - params: GuideColumnParams; - } - ``` - - Referenced by: Guide - members: - Properties: - type: |- - ``` - readonly type: "row" - ``` - - The type of the guide, which is always 'row' for row guides. - display: |- - ``` - readonly display: boolean - ``` - - Specifies whether the row guide is displayed. - params: |- - ``` - readonly params: GuideColumnParams - ``` - - The parameters defining the appearance and layout of the row guides. - Note: This reuses the same parameter structure as column guides. -GuideSquare: - overview: |- - Interface GuideSquare - ===================== - - Represents a board guide for squares in Penpot. - This interface includes properties for defining the type, visibility, and parameters of square guides within a board. - - ``` - interface GuideSquare { - type: "square"; - display: boolean; - params: GuideSquareParams; - } - ``` - - Referenced by: Guide - members: - Properties: - type: |- - ``` - readonly type: "square" - ``` - - The type of the guide, which is always 'square' for square guides. - display: |- - ``` - readonly display: boolean - ``` - - Specifies whether the square guide is displayed. - params: |- - ``` - readonly params: GuideSquareParams - ``` - - The parameters defining the appearance and layout of the square guides. -GuideSquareParams: - overview: |- - Interface GuideSquareParams - =========================== - - Represents parameters for board guide squares in Penpot. - This interface includes properties for defining the appearance and size of square guides within a board. - - ``` - interface GuideSquareParams { - color: { color: string; opacity: number }; - size?: number; - } - ``` - - Referenced by: GuideSquare - members: - Properties: - color: |- - ``` - readonly color: { color: string; opacity: number } - ``` - - The color configuration for the square guides. - size: |- - ``` - readonly size?: number - ``` - - The optional size of each square guide. -HistoryContext: - overview: |- - Interface HistoryContext - ======================== - - This object allows to access to some history functions - - ``` - interface HistoryContext { - undoBlockBegin(): Symbol; - undoBlockFinish(blockId: Symbol): void; - } - ``` - - Referenced by: Context, Penpot - members: - Methods: - undoBlockBegin: |- - ``` - undoBlockBegin(): Symbol - ``` - - Starts an undo block. All operations done inside this block will be undone together until - a call to `undoBlockFinish` is called. - - Returns Symbol - - the block identifier - undoBlockFinish: |- - ``` - undoBlockFinish(blockId: Symbol): void - ``` - - Ends the undo block started with `undoBlockBegin` - - Parameters - - * blockId: Symbol - - is the id returned by `undoBlockBegin` - - Returns void - - Example - ``` - historyContext.undoBlockFinish(blockId); - ``` -Image: - overview: |- - Interface Image - =============== - - Represents an image shape in Penpot. - This interface extends `ShapeBase` and includes properties specific to image shapes. - - Deprecated - - Image shapes exist only for backward compatibility with old files. - New images are embedded in a `Fill` via its `fillImage` (an `ImageData`). - - ``` - interface Image { - type: "image"; - fills: Fill[]; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - id: string; - name: string; - parent: Shape | null; - parentIndex: number; - x: number; - y: number; - width: number; - height: number; - bounds: Bounds; - center: Point; - blocked: boolean; - hidden: boolean; - visible: boolean; - proportionLock: boolean; - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; - fixedWhenScrolling: boolean; - borderRadius: number; - borderRadiusTopLeft: number; - borderRadiusTopRight: number; - borderRadiusBottomRight: number; - borderRadiusBottomLeft: number; - opacity: number; - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity"; - shadows: Shadow[]; - blur?: Blur; - backgroundBlur?: Blur; - exports: Export[]; - boardX: number; - boardY: number; - parentX: number; - parentY: number; - flipX: boolean; - flipY: boolean; - rotation: number; - strokes: Stroke[]; - layoutChild?: LayoutChildProperties; - layoutCell?: LayoutCellProperties; - setParentIndex(index: number): void; - tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - }; - isComponentInstance(): boolean; - isComponentMainInstance(): boolean; - isComponentCopyInstance(): boolean; - isComponentRoot(): boolean; - isComponentHead(): boolean; - componentRefShape(): Shape | null; - componentRoot(): Shape | null; - componentHead(): Shape | null; - component(): LibraryComponent | null; - detach(): void; - swapComponent(component: LibraryComponent): void; - resetOverrides(): void; - switchVariant(pos: number, value: string): void; - combineAsVariants(ids: string[]): VariantContainer; - isVariantHead(): boolean; - resize(width: number, height: number): void; - rotate(angle: number, center?: { x: number; y: number } | null): void; - bringToFront(): void; - bringForward(): void; - sendToBack(): void; - sendBackward(): void; - export(config: Export): Promise>; - interactions: Interaction[]; - addInteraction( - trigger: Trigger, - action: Action, - delay?: number, - ): Interaction; - removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties?: TokenProperty[]): void; - clone(): Shape; - remove(): void; - } - ``` - - Hierarchy (View Summary) - - * ShapeBase - + Image - - Referenced by: Shape - members: - Properties: - type: |- - ``` - readonly type: "image" - ``` - - The type of the shape, which is always 'image' for image shapes. - fills: |- - ``` - fills: Fill[] - ``` - - The fills applied to the shape. - - Overrides ShapeBase.fills - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the shape. - name: |- - ``` - name: string - ``` - - The name of the shape. - parent: |- - ``` - readonly parent: Shape | null - ``` - - The parent shape. If the shape is the first level the parent will be the root shape. - For the root shape the parent is null - parentIndex: |- - ``` - readonly parentIndex: number - ``` - - Returns the index of the current shape in the parent - x: |- - ``` - x: number - ``` - - The x-coordinate of the shape's position. - y: |- - ``` - y: number - ``` - - The y-coordinate of the shape's position. - width: |- - ``` - readonly width: number - ``` - - The width of the shape. - height: |- - ``` - readonly height: number - ``` - - The height of the shape. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - The bounding box surrounding the current shape - center: |- - ``` - readonly center: Point - ``` - - The geometric center of the shape - blocked: |- - ``` - blocked: boolean - ``` - - Indicates whether the shape is blocked. - hidden: |- - ``` - hidden: boolean - ``` - - Indicates whether the shape is hidden. - visible: |- - ``` - visible: boolean - ``` - - Indicates whether the shape is visible. - proportionLock: |- - ``` - proportionLock: boolean - ``` - - Indicates whether the shape has proportion lock enabled. - constraintsHorizontal: |- - ``` - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale" - ``` - - The horizontal constraints applied to the shape. - constraintsVertical: |- - ``` - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom" - ``` - - The vertical constraints applied to the shape. - fixedWhenScrolling: |- - ``` - fixedWhenScrolling: boolean - ``` - - Indicates whether the shape stays fixed in place while scrolling. - borderRadius: |- - ``` - borderRadius: number - ``` - - The border radius of the shape. - borderRadiusTopLeft: |- - ``` - borderRadiusTopLeft: number - ``` - - The border radius of the top-left corner of the shape. - borderRadiusTopRight: |- - ``` - borderRadiusTopRight: number - ``` - - The border radius of the top-right corner of the shape. - borderRadiusBottomRight: |- - ``` - borderRadiusBottomRight: number - ``` - - The border radius of the bottom-right corner of the shape. - borderRadiusBottomLeft: |- - ``` - borderRadiusBottomLeft: number - ``` - - The border radius of the bottom-left corner of the shape. - opacity: |- - ``` - opacity: number - ``` - - The opacity of the shape. - blendMode: |- - ``` - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity" - ``` - - The blend mode applied to the shape. - shadows: |- - ``` - shadows: Shadow[] - ``` - - The shadows applied to the shape. - blur: |- - ``` - blur?: Blur - ``` - - The blur effect applied to the shape. - backgroundBlur: |- - ``` - backgroundBlur?: Blur - ``` - - The background blur effect applied to the shape. - Background blur creates a blur effect on the content behind the shape, - rather than on the shape's own content. - exports: |- - ``` - exports: Export[] - ``` - - The export settings of the shape. - boardX: |- - ``` - boardX: number - ``` - - The x-coordinate of the shape relative to its board. - boardY: |- - ``` - boardY: number - ``` - - The y-coordinate of the shape relative to its board. - parentX: |- - ``` - parentX: number - ``` - - The x-coordinate of the shape relative to its parent. - parentY: |- - ``` - parentY: number - ``` - - The y-coordinate of the shape relative to its parent. - flipX: |- - ``` - flipX: boolean - ``` - - Indicates whether the shape is flipped horizontally. - flipY: |- - ``` - flipY: boolean - ``` - - Indicates whether the shape is flipped vertically. - rotation: |- - ``` - rotation: number - ``` - - The rotation in degrees of the shape with respect to its center. - strokes: |- - ``` - strokes: Stroke[] - ``` - - The strokes applied to the shape. - layoutChild: |- - ``` - readonly layoutChild?: LayoutChildProperties - ``` - - Layout properties for children of the shape. - layoutCell: |- - ``` - readonly layoutCell?: LayoutCellProperties - ``` - - Layout properties for cells in a grid layout. - tokens: |- - ``` - readonly tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - } - ``` - - The design tokens applied to this shape. - It's a map property name -> token name. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - interactions: |- - ``` - readonly interactions: Interaction[] - ``` - - The interactions for the current shape. - Methods: - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` - setParentIndex: |- - ``` - setParentIndex(index: number): void - ``` - - Changes the index inside the parent of the current shape. - This method will shift the indexes of the shapes around that position to - match the index. - If the index is greater than the number of elements it will positioned last. - - Parameters - - * index: number - - the new index for the shape to be in - - Returns void - isComponentInstance: |- - ``` - isComponentInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component instance - isComponentMainInstance: |- - ``` - isComponentMainInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **main** instance - isComponentCopyInstance: |- - ``` - isComponentCopyInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **copy** instance - isComponentRoot: |- - ``` - isComponentRoot(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the root of a component tree - isComponentHead: |- - ``` - isComponentHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure - componentRefShape: |- - ``` - componentRefShape(): Shape | null - ``` - - Returns Shape | null - - Returns the equivalent shape in the component main instance. If the current shape is inside a - main instance will return `null`; - componentRoot: |- - ``` - componentRoot(): Shape | null - ``` - - Returns Shape | null - - Returns the root of the component tree structure for the current shape. If the current shape - is already a root will return itself. - componentHead: |- - ``` - componentHead(): Shape | null - ``` - - Returns Shape | null - - Returns the head of the component tree structure for the current shape. If the current shape - is already a head will return itself. - component: |- - ``` - component(): LibraryComponent | null - ``` - - Returns LibraryComponent | null - - If the shape is a component instance, returns the reference to the component associated - otherwise will return null - detach: |- - ``` - detach(): void - ``` - - If the current shape is a component it will remove the component information and leave the - shape as a "basic shape" - - Returns void - swapComponent: |- - ``` - swapComponent(component: LibraryComponent): void - ``` - - Swaps the component instance for another component, keeping the overrides - when possible. Similar to the "swap component" action on the Penpot interface. - The current shape must be a component copy instance. - - Parameters - - * component: LibraryComponent - - The new component to replace the current one - - Returns void - resetOverrides: |- - ``` - resetOverrides(): void - ``` - - Resets the overrides of the component copy, restoring all its attributes - (and those of its children) to the ones in the linked main component. - Similar to the "reset overrides" action on the Penpot interface. - The current shape must be a component copy instance. - - Returns void - switchVariant: |- - ``` - switchVariant(pos: number, value: string): void - ``` - - Switch a VariantComponent copy to the nearest one that has the specified property value - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - combineAsVariants: |- - ``` - combineAsVariants(ids: string[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu - on the Penpot interface. - The current shape must be a component main instance. - - Parameters - - * ids: string[] - - A list of ids of the main instances of the components to combine with this one. - - Returns VariantContainer - - The variant container created - isVariantHead: |- - ``` - isVariantHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure, - and that component is a VariantComponent - resize: |- - ``` - resize(width: number, height: number): void - ``` - - Resizes the shape to the specified width and height. - - Parameters - - * width: number - - The new width of the shape. - * height: number - - The new height of the shape. - - Returns void - - Example - ``` - shape.resize(200, 100); - ``` - rotate: |- - ``` - rotate(angle: number, center?: { x: number; y: number } | null): void - ``` - - Rotates the shape in relation with the given center. - - Parameters - - * angle: number - - Angle in degrees to rotate. - * center: { x: number; y: number } | null - - Center of the transform rotation. If not sent it will use the geometric center of the shape. - - Returns void - - Example - ``` - shape.rotate(45); - ``` - bringToFront: |- - ``` - bringToFront(): void - ``` - - Moves the current shape to the front of its siblings - - Returns void - bringForward: |- - ``` - bringForward(): void - ``` - - Moves the current shape one position forward in its list of siblings - - Returns void - sendToBack: |- - ``` - sendToBack(): void - ``` - - Moves the current shape to the back of its siblings - - Returns void - sendBackward: |- - ``` - sendBackward(): void - ``` - - Moves the current shape one position backwards in its list of siblings - - Returns void - export: |- - ``` - export(config: Export): Promise> - ``` - - Generates an export from the current shape. - - Parameters - - * config: Export - - Returns Promise> - - Example - ``` - shape.export({ type: 'png', scale: 2 }); - ``` - addInteraction: |- - ``` - addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction - ``` - - Adds a new interaction to the shape. - - If the interaction starts a flow (for example a `navigate-to` action) and - the shape's board is not already part of any flow, a new flow starting at - that board is created automatically, matching the behavior of the editor. - - Parameters - - * trigger: Trigger - - defines the conditions under which the action will be triggered - * action: Action - - defines what will be executed when the trigger happens - * delay: number - - for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. - - Returns Interaction - - Example - ``` - shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); - ``` - removeInteraction: |- - ``` - removeInteraction(interaction: Interaction): void - ``` - - Removes the interaction from the shape. - - Parameters - - * interaction: Interaction - - is the interaction to remove from the shape - - Returns void - - Example - ``` - shape.removeInteraction(interaction); - ``` - applyToken: |- - ``` - applyToken(token: Token, properties?: TokenProperty[]): void - ``` - - Applies one design token to one or more properties of the shape. - - Parameters - - * token: Token - - is the Token to apply - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - clone: |- - ``` - clone(): Shape - ``` - - Creates a clone of the shape. - - Returns Shape - - Returns a new instance of the shape with identical properties. - remove: |- - ``` - remove(): void - ``` - - Removes the shape from its parent. - - Returns void -Interaction: - overview: |- - Interface Interaction - ===================== - - Penpot allows you to prototype interactions by connecting boards, which can act as screens. - - ``` - interface Interaction { - shape?: Shape; - trigger: Trigger; - delay?: number | null; - action: Action; - remove(): void; - } - ``` - - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer - members: - Properties: - shape: |- - ``` - readonly shape?: Shape - ``` - - The shape that owns the interaction - trigger: |- - ``` - trigger: Trigger - ``` - - The user action that will start the interaction. - delay: |- - ``` - delay?: number | null - ``` - - Time in **milliseconds** after the action will happen. Only applies to `after-delay` triggers. - action: |- - ``` - action: Action - ``` - - The action that will execute after the trigger happens. - Methods: - remove: |- - ``` - remove(): void - ``` - - Removes the interaction - - Returns void -LayoutCellProperties: - overview: |- - Interface LayoutCellProperties - ============================== - - Properties for defining the layout of a cell in Penpot. - - ``` - interface LayoutCellProperties { - row?: number; - rowSpan?: number; - column?: number; - columnSpan?: number; - areaName?: string; - position?: "area" | "auto" | "manual"; - } - ``` - - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer - members: - Properties: - row: |- - ``` - row?: number - ``` - - The row index of the cell. - This value is optional and indicates the starting row of the cell. - rowSpan: |- - ``` - rowSpan?: number - ``` - - The number of rows the cell should span. - This value is optional and determines the vertical span of the cell. - column: |- - ``` - column?: number - ``` - - The column index of the cell. - This value is optional and indicates the starting column of the cell. - columnSpan: |- - ``` - columnSpan?: number - ``` - - The number of columns the cell should span. - This value is optional and determines the horizontal span of the cell. - areaName: |- - ``` - areaName?: string - ``` - - The name of the grid area that this cell belongs to. - This value is optional and can be used to define named grid areas. - position: |- - ``` - position?: "area" | "auto" | "manual" - ``` - - The positioning mode of the cell. - This value can be 'auto', 'manual', or 'area' and determines how the cell is positioned within the layout. -LayoutChildProperties: - overview: |- - Interface LayoutChildProperties - =============================== - - Properties for defining the layout of a child element in Penpot. - - ``` - interface LayoutChildProperties { - absolute: boolean; - zIndex: number; - horizontalSizing: "fill" | "auto" | "fix"; - verticalSizing: "fill" | "auto" | "fix"; - alignSelf: "end" | "start" | "center" | "auto" | "stretch"; - horizontalMargin: number; - verticalMargin: number; - topMargin: number; - rightMargin: number; - bottomMargin: number; - leftMargin: number; - maxWidth: number | null; - maxHeight: number | null; - minWidth: number | null; - minHeight: number | null; - } - ``` - - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer - members: - Properties: - absolute: |- - ``` - absolute: boolean - ``` - - Specifies whether the child element is positioned absolutely. - When set to true, the element is taken out of the normal document flow and positioned relative to its nearest positioned ancestor. - zIndex: |- - ``` - zIndex: number - ``` - - Defines the stack order of the child element - Elements with a higher zIndex will be displayed in front of those with a lower zIndex. - horizontalSizing: |- - ``` - horizontalSizing: "fill" | "auto" | "fix" - ``` - - Determines the horizontal sizing behavior of the child element - - * 'auto': The width is determined by the content. - * 'fill': The element takes up the available width. - * 'fix': The width is fixed. - verticalSizing: |- - ``` - verticalSizing: "fill" | "auto" | "fix" - ``` - - Determines the vertical sizing behavior of the child element. - - * 'auto': The height is determined by the content. - * 'fill': The element takes up the available height. - * 'fix': The height is fixed. - alignSelf: |- - ``` - alignSelf: "end" | "start" | "center" | "auto" | "stretch" - ``` - - Aligns the child element within its container. - - * 'auto': Default alignment. - * 'start': Aligns the element at the start of the container. - * 'center': Centers the element within the container. - * 'end': Aligns the element at the end of the container. - * 'stretch': Stretches the element to fill the container. - horizontalMargin: |- - ``` - horizontalMargin: number - ``` - - Sets the horizontal margin of the child element. - This is the space on the left and right sides of the element. - verticalMargin: |- - ``` - verticalMargin: number - ``` - - Sets the vertical margin of the child element. - This is the space on the top and bottom sides of the element. - topMargin: |- - ``` - topMargin: number - ``` - - Sets the top margin of the child element. - This is the space above the element. - rightMargin: |- - ``` - rightMargin: number - ``` - - Sets the right margin of the child element. - This is the space to the right of the element. - bottomMargin: |- - ``` - bottomMargin: number - ``` - - Sets the bottom margin of the child element. - This is the space below the element. - leftMargin: |- - ``` - leftMargin: number - ``` - - Sets the left margin of the child element. - This is the space to the left of the element. - maxWidth: |- - ``` - maxWidth: number | null - ``` - - Defines the maximum width of the child element. - If set to null, there is no maximum width constraint. - maxHeight: |- - ``` - maxHeight: number | null - ``` - - Defines the maximum height of the child element. - If set to null, there is no maximum height constraint. - minWidth: |- - ``` - minWidth: number | null - ``` - - Defines the minimum width of the child element. - If set to null, there is no minimum width constraint. - minHeight: |- - ``` - minHeight: number | null - ``` - - Defines the minimum height of the child element. - If set to null, there is no minimum height constraint. -Library: - overview: |- - Interface Library - ================= - - Represents a library in Penpot, containing colors, typographies, and components. - - ``` - interface Library { - id: string; - name: string; - colors: LibraryColor[]; - typographies: LibraryTypography[]; - components: LibraryComponent[]; - tokens: TokenCatalog; - createColor(): LibraryColor; - createTypography(): LibraryTypography; - createComponent(shapes: Shape[]): LibraryComponent; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - } - ``` - - Hierarchy (View Summary) - - * PluginData - + Library - - Referenced by: LibraryContext - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the library. - name: |- - ``` - readonly name: string - ``` - - The name of the library. - colors: |- - ``` - readonly colors: LibraryColor[] - ``` - - An array of color elements in the library. - - Example - ``` - console.log(penpot.library.local.colors); - ``` - typographies: |- - ``` - readonly typographies: LibraryTypography[] - ``` - - An array of typography elements in the library. - components: |- - ``` - readonly components: LibraryComponent[] - ``` - - An array of component elements in the library. - - Example - ``` - console.log(penpot.library.local.components - ``` - tokens: |- - ``` - readonly tokens: TokenCatalog - ``` - - A catalog of Design Tokens in the library. - - See `TokenCatalog` type to see usage. - Methods: - createColor: |- - ``` - createColor(): LibraryColor - ``` - - Creates a new color element in the library. - - Returns LibraryColor - - Returns a new `LibraryColor` object representing the created color element. - - Example - ``` - const newColor = penpot.library.local.createColor();console.log(newColor); - ``` - createTypography: |- - ``` - createTypography(): LibraryTypography - ``` - - Creates a new typography element in the library. - - Returns LibraryTypography - - Returns a new `LibraryTypography` object representing the created typography element. - - Example - ``` - const newTypography = library.createTypography(); - ``` - createComponent: |- - ``` - createComponent(shapes: Shape[]): LibraryComponent - ``` - - Creates a new component element in the library using the provided shapes. - - Parameters - - * shapes: Shape[] - - An array of `Shape` objects representing the shapes to be included in the component. - - Returns LibraryComponent - - Returns a new `LibraryComponent` object representing the created component element. - - Example - ``` - const newComponent = penpot.library.local.createComponent([shape1, shape2]); - ``` - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` -LibraryColor: - overview: |- - Interface LibraryColor - ====================== - - Represents a color element from a library in Penpot. - This interface extends `LibraryElement` and includes properties specific to color elements. - - ``` - interface LibraryColor { - color?: string; - opacity?: number; - gradient?: Gradient; - image?: ImageData; - asFill(): Fill; - asStroke(): Stroke; - id: string; - libraryId: string; - name: string; - path: string; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - } - ``` - - Hierarchy (View Summary) - - * LibraryElement - + LibraryColor - - Referenced by: Library - members: - Properties: - color: |- - ``` - color?: string - ``` - - The color value of the library color. - opacity: |- - ``` - opacity?: number - ``` - - The opacity value of the library color. - gradient: |- - ``` - gradient?: Gradient - ``` - - The gradient value of the library color, if it's a gradient. - image: |- - ``` - image?: ImageData - ``` - - The image data of the library color, if it's an image fill. - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the library element. - libraryId: |- - ``` - readonly libraryId: string - ``` - - The unique identifier of the library to which the element belongs. - name: |- - ``` - name: string - ``` - - The name of the library element. - path: |- - ``` - path: string - ``` - - The path of the library element. - Methods: - asFill: |- - ``` - asFill(): Fill - ``` - - Converts the library color into a fill object. - - Returns Fill - - Returns a `Fill` object representing the color as a fill. - - Example - ``` - const fill = libraryColor.asFill(); - ``` - asStroke: |- - ``` - asStroke(): Stroke - ``` - - Converts the library color into a stroke object. - - Returns Stroke - - Returns a `Stroke` object representing the color as a stroke. - - Example - ``` - const stroke = libraryColor.asStroke(); - ``` - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` -LibraryComponent: - overview: |- - Interface LibraryComponent - ========================== - - Represents a component element from a library in Penpot. - This interface extends `LibraryElement` and includes properties specific to component elements. - - ``` - interface LibraryComponent { - instance(): Shape; - mainInstance(): Shape; - isVariant(): this is LibraryVariantComponent; - transformInVariant(): void; - id: string; - libraryId: string; - name: string; - path: string; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - } - ``` - - Hierarchy (View Summary) - - * LibraryElement - + LibraryComponent - - LibraryVariantComponent - - Referenced by: Board, Boolean, ContextTypesUtils, Ellipse, Group, Image, Library, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer, Variants - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the library element. - libraryId: |- - ``` - readonly libraryId: string - ``` - - The unique identifier of the library to which the element belongs. - name: |- - ``` - name: string - ``` - - The name of the library element. - path: |- - ``` - path: string - ``` - - The path of the library element. - Methods: - instance: |- - ``` - instance(): Shape - ``` - - Creates an instance of the component. - - Returns Shape - - Returns a `Shape` object representing the instance of the component. - - Example - ``` - const componentInstance = libraryComponent.instance(); - ``` - mainInstance: |- - ``` - mainInstance(): Shape - ``` - - Returns Shape - - Returns the reference to the main component shape. - isVariant: |- - ``` - isVariant(): this is LibraryVariantComponent - ``` - - Checks whether this component is a variant component. - If true, the component can be used as a `LibraryVariantComponent`, - which provides additional attributes (e.g. `variants`) and methods. - - Returns this is LibraryVariantComponent - transformInVariant: |- - ``` - transformInVariant(): void - ``` - - Creates a new Variant from this standard Component. It creates a VariantContainer, transforms this Component into a VariantComponent, duplicates it, and creates a - set of properties based on the component name and path. - Similar to doing it with the contextual menu or the shortcut on the Penpot interface - - Returns void - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` -LibraryVariantComponent: - overview: |- - Interface LibraryVariantComponent - ================================= - - Represents a component element from a library in Penpot. - This interface extends `LibraryElement` and includes properties specific to component elements. - - ``` - interface LibraryVariantComponent { - instance(): Shape; - mainInstance(): Shape; - isVariant(): this is LibraryVariantComponent; - transformInVariant(): void; - variants: Variants | null; - variantProps: { [property: string]: string }; - variantError: string; - addVariant(): void; - setVariantProperty(pos: number, value: string): void; - id: string; - libraryId: string; - name: string; - path: string; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - } - ``` - - Hierarchy (View Summary) - - * LibraryComponent - + LibraryVariantComponent - - Referenced by: ContextTypesUtils, LibraryComponent, LibraryVariantComponent - members: - Properties: - variants: |- - ``` - readonly variants: Variants | null - ``` - - Access to the Variant interface, for attributes and actions over the full Variant (not only this VariantComponent) - variantProps: |- - ``` - readonly variantProps: { [property: string]: string } - ``` - - A list of the variant props of this VariantComponent. Each property has a key and a value - variantError: |- - ``` - variantError: string - ``` - - If this VariantComponent has an invalid name, that doesn't follow the structure [property]=[value], [property]=[value] - this field stores that invalid name - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the library element. - libraryId: |- - ``` - readonly libraryId: string - ``` - - The unique identifier of the library to which the element belongs. - name: |- - ``` - name: string - ``` - - The name of the library element. - path: |- - ``` - path: string - ``` - - The path of the library element. - Methods: - instance: |- - ``` - instance(): Shape - ``` - - Creates an instance of the component. - - Returns Shape - - Returns a `Shape` object representing the instance of the component. - - Example - ``` - const componentInstance = libraryComponent.instance(); - ``` - mainInstance: |- - ``` - mainInstance(): Shape - ``` - - Returns Shape - - Returns the reference to the main component shape. - isVariant: |- - ``` - isVariant(): this is LibraryVariantComponent - ``` - - Checks whether this component is a variant component. - If true, the component can be used as a `LibraryVariantComponent`, - which provides additional attributes (e.g. `variants`) and methods. - - Returns this is LibraryVariantComponent - transformInVariant: |- - ``` - transformInVariant(): void - ``` - - Creates a new Variant from this standard Component. It creates a VariantContainer, transforms this Component into a VariantComponent, duplicates it, and creates a - set of properties based on the component name and path. - Similar to doing it with the contextual menu or the shortcut on the Penpot interface - - Returns void - addVariant: |- - ``` - addVariant(): void - ``` - - Creates a duplicate of the current VariantComponent on its Variant - - Returns void - setVariantProperty: |- - ``` - setVariantProperty(pos: number, value: string): void - ``` - - Sets the value of the variant property on the indicated position - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` -LibraryElement: - overview: |- - Interface LibraryElement - ======================== - - Represents an element in a Penpot library. - This interface provides information about a specific element in a library. - - ``` - interface LibraryElement { - id: string; - libraryId: string; - name: string; - path: string; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - } - ``` - - Hierarchy (View Summary) - - * PluginData - + LibraryElement - - LibraryColor - - LibraryComponent - - LibraryTypography - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the library element. - libraryId: |- - ``` - readonly libraryId: string - ``` - - The unique identifier of the library to which the element belongs. - name: |- - ``` - name: string - ``` - - The name of the library element. - path: |- - ``` - path: string - ``` - - The path of the library element. - Methods: - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` -LibrarySummary: - overview: |- - Interface LibrarySummary - ======================== - - Represents a summary of a Penpot library. - This interface provides properties for summarizing various aspects of a Penpot library. - - ``` - interface LibrarySummary { - id: string; - name: string; - numColors: number; - numComponents: number; - numTypographies: number; - } - ``` - - Referenced by: LibraryContext - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the library. - name: |- - ``` - readonly name: string - ``` - - The name of the library. - numColors: |- - ``` - readonly numColors: number - ``` - - The number of colors in the library. - numComponents: |- - ``` - readonly numComponents: number - ``` - - The number of components in the library. - numTypographies: |- - ``` - readonly numTypographies: number - ``` - - The number of typographies in the library. -LibraryTypography: - overview: |- - Interface LibraryTypography - =========================== - - Represents a typography element from a library in Penpot. - This interface extends `LibraryElement` and includes properties specific to typography elements. - - ``` - interface LibraryTypography { - id: string; - libraryId: string; - name: string; - path: string; - fontId: string; - fontFamily: string; - fontVariantId: string; - fontSize: string; - fontWeight: string; - fontStyle?: "normal" | "italic" | null; - lineHeight: string; - letterSpacing: string; - textTransform?: "uppercase" | "capitalize" | "lowercase" | null; - applyToText(shape: Shape): void; - applyToTextRange(range: TextRange): void; - setFont(font: Font, variant?: FontVariant): void; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - } - ``` - - Hierarchy (View Summary) - - * LibraryElement - + LibraryTypography - - Referenced by: Library, Text, TextRange - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the library element. - libraryId: |- - ``` - readonly libraryId: string - ``` - - The unique identifier of the library to which the element belongs. - name: |- - ``` - name: string - ``` - - The name of the library element. - path: |- - ``` - path: string - ``` - - The path of the library element. - fontId: |- - ``` - fontId: string - ``` - - The unique identifier of the font used in the typography element. - fontFamily: |- - ``` - fontFamily: string - ``` - - The font family of the typography element. - fontVariantId: |- - ``` - fontVariantId: string - ``` - - The unique identifier of the font variant used in the typography element. - fontSize: |- - ``` - fontSize: string - ``` - - The font size of the typography element. - fontWeight: |- - ``` - fontWeight: string - ``` - - The font weight of the typography element. - fontStyle: |- - ``` - fontStyle?: "normal" | "italic" | null - ``` - - The font style of the typography element. - lineHeight: |- - ``` - lineHeight: string - ``` - - The line height of the typography element. - letterSpacing: |- - ``` - letterSpacing: string - ``` - - The letter spacing of the typography element. - textTransform: |- - ``` - textTransform?: "uppercase" | "capitalize" | "lowercase" | null - ``` - - The text transform applied to the typography element. - Methods: - applyToText: |- - ``` - applyToText(shape: Shape): void - ``` - - Applies the typography styles to a text shape. - - Parameters - - * shape: Shape - - The text shape to apply the typography styles to. - - Returns void - - Example - ``` - typographyElement.applyToText(textShape); - ``` - applyToTextRange: |- - ``` - applyToTextRange(range: TextRange): void - ``` - - Applies the typography styles to a range of text within a text shape. - - Parameters - - * range: TextRange - - Represents a range of text within a Text shape. This interface provides properties for styling and formatting text ranges. - - Returns void - - Example - ``` - typographyElement.applyToTextRange(textShape); - ``` - setFont: |- - ``` - setFont(font: Font, variant?: FontVariant): void - ``` - - Sets the font and optionally its variant for the typography element. - - Parameters - - * font: Font - - The font to set. - * variant: FontVariant - - The font variant to set (optional). - - Returns void - - Example - ``` - typographyElement.setFont(newFont, newVariant); - ``` - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` -LocalStorage: - overview: |- - Interface LocalStorage - ====================== - - Proxy for the local storage. Only elements owned by the plugin - can be stored and accessed. - Warning: other plugins won't be able to access this information but - the user could potentially access the data through the browser information. - - ``` - interface LocalStorage { - getItem(key: string): string; - setItem(key: string, value: unknown): void; - removeItem(key: string): void; - getKeys(): string[]; - } - ``` - - Referenced by: Context, Penpot - members: - Methods: - getItem: |- - ``` - getItem(key: string): string - ``` - - Retrieve the element with the given key - Requires the `allow:localstorage` permission. - - Parameters - - * key: string - - Returns string - setItem: |- - ``` - setItem(key: string, value: unknown): void - ``` - - Set the data given the key. If the value already existed it - will be overridden. The value will be stored in a string representation. - Requires the `allow:localstorage` permission. - - Parameters - - * key: string - * value: unknown - - Returns void - removeItem: |- - ``` - removeItem(key: string): void - ``` - - Remove the value stored in the key. - Requires the `allow:localstorage` permission. - - Parameters - - * key: string - - Returns void - getKeys: |- - ``` - getKeys(): string[] - ``` - - Return all the keys for the data stored by the plugin. - Requires the `allow:localstorage` permission. - - Returns string[] -NavigateTo: - overview: |- - Interface NavigateTo - ==================== - - It takes the user from one board to the destination set in the interaction. - - ``` - interface NavigateTo { - type: "navigate-to"; - destination: Board; - preserveScrollPosition?: boolean; - animation?: Animation; - } - ``` - - Referenced by: Action - members: - Properties: - type: |- - ``` - readonly type: "navigate-to" - ``` - - Type of action - destination: |- - ``` - readonly destination: Board - ``` - - Board to which the action targets - preserveScrollPosition: |- - ``` - readonly preserveScrollPosition?: boolean - ``` - - When true the scroll will be preserved. - animation: |- - ``` - readonly animation?: Animation - ``` - - Animation displayed with this interaction. -OpenOverlay: - overview: |- - Interface OpenOverlay - ===================== - - It opens a board right over the current board. - - ``` - interface OpenOverlay { - type: "open-overlay"; - destination: Board; - relativeTo?: Shape; - position?: - | "center" - | "manual" - | "top-left" - | "top-right" - | "top-center" - | "bottom-left" - | "bottom-right" - | "bottom-center"; - manualPositionLocation?: Point; - closeWhenClickOutside?: boolean; - addBackgroundOverlay?: boolean; - animation?: Animation; - } - ``` - - Hierarchy (View Summary) - - * OverlayAction - + OpenOverlay - - Referenced by: Action - members: - Properties: - type: |- - ``` - readonly type: "open-overlay" - ``` - - The action type - destination: |- - ``` - readonly destination: Board - ``` - - Overlay board that will be opened. - relativeTo: |- - ``` - readonly relativeTo?: Shape - ``` - - Base shape to which the overlay will be positioned taking constraints into account. - position: |- - ``` - readonly position?: - | "center" - | "manual" - | "top-left" - | "top-right" - | "top-center" - | "bottom-left" - | "bottom-right" - | "bottom-center" - ``` - - Positioning of the overlay. - manualPositionLocation: |- - ``` - readonly manualPositionLocation?: Point - ``` - - For `position = 'manual'` the location of the overlay. - closeWhenClickOutside: |- - ``` - readonly closeWhenClickOutside?: boolean - ``` - - When true the overlay will be closed when clicking outside - addBackgroundOverlay: |- - ``` - readonly addBackgroundOverlay?: boolean - ``` - - When true a background will be added to the overlay. - animation: |- - ``` - readonly animation?: Animation - ``` - - Animation displayed with this interaction. -OpenUrl: - overview: |- - Interface OpenUrl - ================= - - This action opens an URL in a new tab. - - ``` - interface OpenUrl { - type: "open-url"; - url: string; - } - ``` - - Referenced by: Action - members: - Properties: - type: |- - ``` - readonly type: "open-url" - ``` - - The action type - url: |- - ``` - readonly url: string - ``` - - The URL to open when the action is executed -OverlayAction: - overview: |- - Interface OverlayAction - ======================= - - Base type for the actions "open-overlay" and "toggle-overlay" that share most of their properties - - ``` - interface OverlayAction { - destination: Board; - relativeTo?: Shape; - position?: - | "center" - | "manual" - | "top-left" - | "top-right" - | "top-center" - | "bottom-left" - | "bottom-right" - | "bottom-center"; - manualPositionLocation?: Point; - closeWhenClickOutside?: boolean; - addBackgroundOverlay?: boolean; - animation?: Animation; - } - ``` - - Hierarchy (View Summary) - - * OverlayAction - + OpenOverlay - + ToggleOverlay - members: - Properties: - destination: |- - ``` - readonly destination: Board - ``` - - Overlay board that will be opened. - relativeTo: |- - ``` - readonly relativeTo?: Shape - ``` - - Base shape to which the overlay will be positioned taking constraints into account. - position: |- - ``` - readonly position?: - | "center" - | "manual" - | "top-left" - | "top-right" - | "top-center" - | "bottom-left" - | "bottom-right" - | "bottom-center" - ``` - - Positioning of the overlay. - manualPositionLocation: |- - ``` - readonly manualPositionLocation?: Point - ``` - - For `position = 'manual'` the location of the overlay. - closeWhenClickOutside: |- - ``` - readonly closeWhenClickOutside?: boolean - ``` - - When true the overlay will be closed when clicking outside - addBackgroundOverlay: |- - ``` - readonly addBackgroundOverlay?: boolean - ``` - - When true a background will be added to the overlay. - animation: |- - ``` - readonly animation?: Animation - ``` - - Animation displayed with this interaction. -Page: - overview: |- - Interface Page - ============== - - Page represents a page in the Penpot application. - It includes properties for the page's identifier and name, as well as methods for managing shapes on the page. - - ``` - interface Page { - id: string; - name: string; - rulerGuides: RulerGuide[]; - root: Shape; - remove(): void; - getShapeById(id: string): Shape | null; - findShapes( - criteria?: { - name?: string; - nameLike?: string; - type?: - | "boolean" - | "path" - | "ellipse" - | "image" - | "text" - | "group" - | "board" - | "rectangle" - | "svg-raw"; - }, - ): Shape[]; - flows: Flow[]; - createFlow(name: string, board: Board): Flow; - removeFlow(flow: Flow): void; - addRulerGuide( - orientation: RulerGuideOrientation, - value: number, - board?: Board, - ): RulerGuide; - removeRulerGuide(guide: RulerGuide): void; - addCommentThread(content: string, position: Point): Promise; - removeCommentThread(commentThread: CommentThread): Promise; - findCommentThreads( - criteria?: { onlyYours: boolean; showResolved: boolean }, - ): Promise; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - } - ``` - - Hierarchy (View Summary) - - * PluginData - + Page - - Referenced by: Context, EventsMap, File, Flow, Penpot - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The `id` property is a unique identifier for the page. - name: |- - ``` - name: string - ``` - - The `name` property is the name of the page. - rulerGuides: |- - ``` - readonly rulerGuides: RulerGuide[] - ``` - - The ruler guides attached to the page - root: |- - ``` - readonly root: Shape - ``` - - The root shape of the current page. Will be the parent shape of all the shapes inside the document. - Requires `content:read` permission. - flows: |- - ``` - readonly flows: Flow[] - ``` - - The interaction flows defined for the page. - Methods: - remove: |- - ``` - remove(): void - ``` - - Removes the page from the file. The last remaining page of the file - cannot be removed. If the removed page is the active one, another page - is activated. - Requires `content:write` permission. - - Returns void - - Example - ``` - const page = penpot.createPage();page.remove(); - ``` - getShapeById: |- - ``` - getShapeById(id: string): Shape | null - ``` - - Retrieves a shape by its unique identifier. - - Parameters - - * id: string - - The unique identifier of the shape. - - Returns Shape | null - - Example - ``` - const shape = penpot.currentPage.getShapeById('shapeId'); - ``` - findShapes: |- - ``` - findShapes( - criteria?: { - name?: string; - nameLike?: string; - type?: - | "boolean" - | "path" - | "ellipse" - | "image" - | "text" - | "group" - | "board" - | "rectangle" - | "svg-raw"; - }, - ): Shape[] - ``` - - Finds all shapes on the page. - Optionally it gets a criteria object to search for specific criteria - - Parameters - - * criteria: { - name?: string; - nameLike?: string; - type?: - | "boolean" - | "path" - | "ellipse" - | "image" - | "text" - | "group" - | "board" - | "rectangle" - | "svg-raw"; - } - - Returns Shape[] - - Example - ``` - const shapes = penpot.currentPage.findShapes({ name: 'exampleName' }); - ``` - createFlow: |- - ``` - createFlow(name: string, board: Board): Flow - ``` - - Creates a new flow in the page. - - Parameters - - * name: string - - the name identifying the flow - * board: Board - - the starting board for the current flow - - Returns Flow - - Example - ``` - const flow = penpot.currentPage.createFlow('exampleFlow', board); - ``` - removeFlow: |- - ``` - removeFlow(flow: Flow): void - ``` - - Removes the flow from the page - - Parameters - - * flow: Flow - - the flow to be removed from the page - - Returns void - addRulerGuide: |- - ``` - addRulerGuide( - orientation: RulerGuideOrientation, - value: number, - board?: Board, - ): RulerGuide - ``` - - Creates a new ruler guide. - - Parameters - - * orientation: RulerGuideOrientation - - `horizontal` or `vertical` - * value: number - - the position of the guide in absolute coordinates - * board: Board - - if provided, the guide will be attached to the board - - Returns RulerGuide - removeRulerGuide: |- - ``` - removeRulerGuide(guide: RulerGuide): void - ``` - - Removes the `guide` from the current page. - - Parameters - - * guide: RulerGuide - - Returns void - addCommentThread: |- - ``` - addCommentThread(content: string, position: Point): Promise - ``` - - Creates a new comment thread in the `position`. - Returns the thread created. - Requires the `comment:write` permission. - - Parameters - - * content: string - * position: Point - - Returns Promise - removeCommentThread: |- - ``` - removeCommentThread(commentThread: CommentThread): Promise - ``` - - Removes the comment thread. - Requires the `comment:write` permission. - - Parameters - - * commentThread: CommentThread - - Returns Promise - findCommentThreads: |- - ``` - findCommentThreads( - criteria?: { onlyYours: boolean; showResolved: boolean }, - ): Promise - ``` - - Find all the comments that match the criteria. - - * `onlyYours`: if `true` will return the threads where the current - user has engaged. - * `showResolved`: by default resolved comments will be hidden. If `true` - the resolved will be returned. - Requires the `comment:read` or `comment:write` permission. - - Parameters - - * criteria: { onlyYours: boolean; showResolved: boolean } - - Returns Promise - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` -Path: - overview: |- - Interface Path - ============== - - Represents a path shape in Penpot. - This interface extends `ShapeBase` and includes properties and methods specific to paths. - - ``` - interface Path { - type: "path"; - toD(): string; - content: string; - d: string; - commands: PathCommand[]; - fills: Fill[]; - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - id: string; - name: string; - parent: Shape | null; - parentIndex: number; - x: number; - y: number; - width: number; - height: number; - bounds: Bounds; - center: Point; - blocked: boolean; - hidden: boolean; - visible: boolean; - proportionLock: boolean; - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; - fixedWhenScrolling: boolean; - borderRadius: number; - borderRadiusTopLeft: number; - borderRadiusTopRight: number; - borderRadiusBottomRight: number; - borderRadiusBottomLeft: number; - opacity: number; - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity"; - shadows: Shadow[]; - blur?: Blur; - backgroundBlur?: Blur; - exports: Export[]; - boardX: number; - boardY: number; - parentX: number; - parentY: number; - flipX: boolean; - flipY: boolean; - rotation: number; - strokes: Stroke[]; - layoutChild?: LayoutChildProperties; - layoutCell?: LayoutCellProperties; - setParentIndex(index: number): void; - tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - }; - isComponentInstance(): boolean; - isComponentMainInstance(): boolean; - isComponentCopyInstance(): boolean; - isComponentRoot(): boolean; - isComponentHead(): boolean; - componentRefShape(): Shape | null; - componentRoot(): Shape | null; - componentHead(): Shape | null; - component(): LibraryComponent | null; - detach(): void; - swapComponent(component: LibraryComponent): void; - resetOverrides(): void; - switchVariant(pos: number, value: string): void; - combineAsVariants(ids: string[]): VariantContainer; - isVariantHead(): boolean; - resize(width: number, height: number): void; - rotate(angle: number, center?: { x: number; y: number } | null): void; - bringToFront(): void; - bringForward(): void; - sendToBack(): void; - sendBackward(): void; - export(config: Export): Promise>; - interactions: Interaction[]; - addInteraction( - trigger: Trigger, - action: Action, - delay?: number, - ): Interaction; - removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties?: TokenProperty[]): void; - clone(): Shape; - remove(): void; - } - ``` - - Hierarchy (View Summary) - - * ShapeBase - + Path - - Referenced by: Context, ContextTypesUtils, Penpot, Shape - members: - Properties: - type: |- - ``` - readonly type: "path" - ``` - - The type of the shape, which is always 'path' for path shapes. - content: |- - ``` - content: string - ``` - - The content of the path shape, defined as the path string. - - Deprecated - - Use either `d` or `commands`. - d: |- - ``` - d: string - ``` - - The content of the path shape, defined as the path string. - commands: |- - ``` - commands: PathCommand[] - ``` - - The content of the path shape, defined as an array of path commands. - fills: |- - ``` - fills: Fill[] - ``` - - The fills applied to the shape. - - Overrides ShapeBase.fills - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the shape. - name: |- - ``` - name: string - ``` - - The name of the shape. - parent: |- - ``` - readonly parent: Shape | null - ``` - - The parent shape. If the shape is the first level the parent will be the root shape. - For the root shape the parent is null - parentIndex: |- - ``` - readonly parentIndex: number - ``` - - Returns the index of the current shape in the parent - x: |- - ``` - x: number - ``` - - The x-coordinate of the shape's position. - y: |- - ``` - y: number - ``` - - The y-coordinate of the shape's position. - width: |- - ``` - readonly width: number - ``` - - The width of the shape. - height: |- - ``` - readonly height: number - ``` - - The height of the shape. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - The bounding box surrounding the current shape - center: |- - ``` - readonly center: Point - ``` - - The geometric center of the shape - blocked: |- - ``` - blocked: boolean - ``` - - Indicates whether the shape is blocked. - hidden: |- - ``` - hidden: boolean - ``` - - Indicates whether the shape is hidden. - visible: |- - ``` - visible: boolean - ``` - - Indicates whether the shape is visible. - proportionLock: |- - ``` - proportionLock: boolean - ``` - - Indicates whether the shape has proportion lock enabled. - constraintsHorizontal: |- - ``` - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale" - ``` - - The horizontal constraints applied to the shape. - constraintsVertical: |- - ``` - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom" - ``` - - The vertical constraints applied to the shape. - fixedWhenScrolling: |- - ``` - fixedWhenScrolling: boolean - ``` - - Indicates whether the shape stays fixed in place while scrolling. - borderRadius: |- - ``` - borderRadius: number - ``` - - The border radius of the shape. - borderRadiusTopLeft: |- - ``` - borderRadiusTopLeft: number - ``` - - The border radius of the top-left corner of the shape. - borderRadiusTopRight: |- - ``` - borderRadiusTopRight: number - ``` - - The border radius of the top-right corner of the shape. - borderRadiusBottomRight: |- - ``` - borderRadiusBottomRight: number - ``` - - The border radius of the bottom-right corner of the shape. - borderRadiusBottomLeft: |- - ``` - borderRadiusBottomLeft: number - ``` - - The border radius of the bottom-left corner of the shape. - opacity: |- - ``` - opacity: number - ``` - - The opacity of the shape. - blendMode: |- - ``` - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity" - ``` - - The blend mode applied to the shape. - shadows: |- - ``` - shadows: Shadow[] - ``` - - The shadows applied to the shape. - blur: |- - ``` - blur?: Blur - ``` - - The blur effect applied to the shape. - backgroundBlur: |- - ``` - backgroundBlur?: Blur - ``` - - The background blur effect applied to the shape. - Background blur creates a blur effect on the content behind the shape, - rather than on the shape's own content. - exports: |- - ``` - exports: Export[] - ``` - - The export settings of the shape. - boardX: |- - ``` - boardX: number - ``` - - The x-coordinate of the shape relative to its board. - boardY: |- - ``` - boardY: number - ``` - - The y-coordinate of the shape relative to its board. - parentX: |- - ``` - parentX: number - ``` - - The x-coordinate of the shape relative to its parent. - parentY: |- - ``` - parentY: number - ``` - - The y-coordinate of the shape relative to its parent. - flipX: |- - ``` - flipX: boolean - ``` - - Indicates whether the shape is flipped horizontally. - flipY: |- - ``` - flipY: boolean - ``` - - Indicates whether the shape is flipped vertically. - rotation: |- - ``` - rotation: number - ``` - - The rotation in degrees of the shape with respect to its center. - strokes: |- - ``` - strokes: Stroke[] - ``` - - The strokes applied to the shape. - layoutChild: |- - ``` - readonly layoutChild?: LayoutChildProperties - ``` - - Layout properties for children of the shape. - layoutCell: |- - ``` - readonly layoutCell?: LayoutCellProperties - ``` - - Layout properties for cells in a grid layout. - tokens: |- - ``` - readonly tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - } - ``` - - The design tokens applied to this shape. - It's a map property name -> token name. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - interactions: |- - ``` - readonly interactions: Interaction[] - ``` - - The interactions for the current shape. - Methods: - toD: |- - ``` - toD(): string - ``` - - Converts the path shape to its path data representation. - - Returns string - - Returns the path data (d attribute) as a string. - - Deprecated - - Use the `d` attribute - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` - setParentIndex: |- - ``` - setParentIndex(index: number): void - ``` - - Changes the index inside the parent of the current shape. - This method will shift the indexes of the shapes around that position to - match the index. - If the index is greater than the number of elements it will positioned last. - - Parameters - - * index: number - - the new index for the shape to be in - - Returns void - isComponentInstance: |- - ``` - isComponentInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component instance - isComponentMainInstance: |- - ``` - isComponentMainInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **main** instance - isComponentCopyInstance: |- - ``` - isComponentCopyInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **copy** instance - isComponentRoot: |- - ``` - isComponentRoot(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the root of a component tree - isComponentHead: |- - ``` - isComponentHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure - componentRefShape: |- - ``` - componentRefShape(): Shape | null - ``` - - Returns Shape | null - - Returns the equivalent shape in the component main instance. If the current shape is inside a - main instance will return `null`; - componentRoot: |- - ``` - componentRoot(): Shape | null - ``` - - Returns Shape | null - - Returns the root of the component tree structure for the current shape. If the current shape - is already a root will return itself. - componentHead: |- - ``` - componentHead(): Shape | null - ``` - - Returns Shape | null - - Returns the head of the component tree structure for the current shape. If the current shape - is already a head will return itself. - component: |- - ``` - component(): LibraryComponent | null - ``` - - Returns LibraryComponent | null - - If the shape is a component instance, returns the reference to the component associated - otherwise will return null - detach: |- - ``` - detach(): void - ``` - - If the current shape is a component it will remove the component information and leave the - shape as a "basic shape" - - Returns void - swapComponent: |- - ``` - swapComponent(component: LibraryComponent): void - ``` - - Swaps the component instance for another component, keeping the overrides - when possible. Similar to the "swap component" action on the Penpot interface. - The current shape must be a component copy instance. - - Parameters - - * component: LibraryComponent - - The new component to replace the current one - - Returns void - resetOverrides: |- - ``` - resetOverrides(): void - ``` - - Resets the overrides of the component copy, restoring all its attributes - (and those of its children) to the ones in the linked main component. - Similar to the "reset overrides" action on the Penpot interface. - The current shape must be a component copy instance. - - Returns void - switchVariant: |- - ``` - switchVariant(pos: number, value: string): void - ``` - - Switch a VariantComponent copy to the nearest one that has the specified property value - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - combineAsVariants: |- - ``` - combineAsVariants(ids: string[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu - on the Penpot interface. - The current shape must be a component main instance. - - Parameters - - * ids: string[] - - A list of ids of the main instances of the components to combine with this one. - - Returns VariantContainer - - The variant container created - isVariantHead: |- - ``` - isVariantHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure, - and that component is a VariantComponent - resize: |- - ``` - resize(width: number, height: number): void - ``` - - Resizes the shape to the specified width and height. - - Parameters - - * width: number - - The new width of the shape. - * height: number - - The new height of the shape. - - Returns void - - Example - ``` - shape.resize(200, 100); - ``` - rotate: |- - ``` - rotate(angle: number, center?: { x: number; y: number } | null): void - ``` - - Rotates the shape in relation with the given center. - - Parameters - - * angle: number - - Angle in degrees to rotate. - * center: { x: number; y: number } | null - - Center of the transform rotation. If not sent it will use the geometric center of the shape. - - Returns void - - Example - ``` - shape.rotate(45); - ``` - bringToFront: |- - ``` - bringToFront(): void - ``` - - Moves the current shape to the front of its siblings - - Returns void - bringForward: |- - ``` - bringForward(): void - ``` - - Moves the current shape one position forward in its list of siblings - - Returns void - sendToBack: |- - ``` - sendToBack(): void - ``` - - Moves the current shape to the back of its siblings - - Returns void - sendBackward: |- - ``` - sendBackward(): void - ``` - - Moves the current shape one position backwards in its list of siblings - - Returns void - export: |- - ``` - export(config: Export): Promise> - ``` - - Generates an export from the current shape. - - Parameters - - * config: Export - - Returns Promise> - - Example - ``` - shape.export({ type: 'png', scale: 2 }); - ``` - addInteraction: |- - ``` - addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction - ``` - - Adds a new interaction to the shape. - - If the interaction starts a flow (for example a `navigate-to` action) and - the shape's board is not already part of any flow, a new flow starting at - that board is created automatically, matching the behavior of the editor. - - Parameters - - * trigger: Trigger - - defines the conditions under which the action will be triggered - * action: Action - - defines what will be executed when the trigger happens - * delay: number - - for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. - - Returns Interaction - - Example - ``` - shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); - ``` - removeInteraction: |- - ``` - removeInteraction(interaction: Interaction): void - ``` - - Removes the interaction from the shape. - - Parameters - - * interaction: Interaction - - is the interaction to remove from the shape - - Returns void - - Example - ``` - shape.removeInteraction(interaction); - ``` - applyToken: |- - ``` - applyToken(token: Token, properties?: TokenProperty[]): void - ``` - - Applies one design token to one or more properties of the shape. - - Parameters - - * token: Token - - is the Token to apply - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - clone: |- - ``` - clone(): Shape - ``` - - Creates a clone of the shape. - - Returns Shape - - Returns a new instance of the shape with identical properties. - remove: |- - ``` - remove(): void - ``` - - Removes the shape from its parent. - - Returns void -PathCommand: - overview: |- - Interface PathCommand - ===================== - - Represents a path command in Penpot. - This interface includes a property for defining the type of command. - - ``` - interface PathCommand { - command: - | "M" - | "move-to" - | "Z" - | "close-path" - | "L" - | "line-to" - | "H" - | "line-to-horizontal" - | "V" - | "line-to-vertical" - | "C" - | "curve-to" - | "S" - | "smooth-curve-to" - | "Q" - | "quadratic-bezier-curve-to" - | "T" - | "smooth-quadratic-bezier-curve-to" - | "A" - | "elliptical-arc"; - params?: { - x?: number; - y?: number; - c1x?: number; - c1y?: number; - c2x?: number; - c2y?: number; - rx?: number; - ry?: number; - xAxisRotation?: number; - largeArcFlag?: boolean; - sweepFlag?: boolean; - }; - } - ``` - - Referenced by: Boolean, Path - members: - Properties: - command: |- - ``` - command: - | "M" - | "move-to" - | "Z" - | "close-path" - | "L" - | "line-to" - | "H" - | "line-to-horizontal" - | "V" - | "line-to-vertical" - | "C" - | "curve-to" - | "S" - | "smooth-curve-to" - | "Q" - | "quadratic-bezier-curve-to" - | "T" - | "smooth-quadratic-bezier-curve-to" - | "A" - | "elliptical-arc" - ``` - - The type of path command. - Possible values include: - - * 'M' or 'move-to': Move to a new point. - * 'Z' or 'close-path': Close the current path. - * 'L' or 'line-to': Draw a straight line to a new point. - * 'H' or 'line-to-horizontal': Draw a horizontal line to a new point. - * 'V' or 'line-to-vertical': Draw a vertical line to a new point. - * 'C' or 'curve-to': Draw a cubic Bezier curve to a new point. - * 'S' or 'smooth-curve-to': Draw a smooth cubic Bezier curve to a new point. - * 'Q' or 'quadratic-bezier-curve-to': Draw a quadratic Bezier curve to a new point. - * 'T' or 'smooth-quadratic-bezier-curve-to': Draw a smooth quadratic Bezier curve to a new point. - * 'A' or 'elliptical-arc': Draw an elliptical arc to a new point. - - Example - ``` - const pathCommand: PathCommand = { command: 'M', params: { x: 0, y: 0 } }; - ``` - params: |- - ``` - params?: { - x?: number; - y?: number; - c1x?: number; - c1y?: number; - c2x?: number; - c2y?: number; - rx?: number; - ry?: number; - xAxisRotation?: number; - largeArcFlag?: boolean; - sweepFlag?: boolean; - } - ``` - - Optional parameters associated with the path command. - - Type Declaration - - * Optionalx?: number - - The x-coordinate of the point (or endpoint). - * Optionaly?: number - - The y-coordinate of the point (or endpoint). - * Optionalc1x?: number - - The x-coordinate of the first control point for curves. - * Optionalc1y?: number - - The y-coordinate of the first control point for curves. - * Optionalc2x?: number - - The x-coordinate of the second control point for curves. - * Optionalc2y?: number - - The y-coordinate of the second control point for curves. - * Optionalrx?: number - - The radius of the ellipse's x-axis. - * Optionalry?: number - - The radius of the ellipse's y-axis. - * OptionalxAxisRotation?: number - - The rotation angle of the ellipse's x-axis. - * OptionallargeArcFlag?: boolean - - A flag indicating whether to use the larger arc. - * OptionalsweepFlag?: boolean - - A flag indicating the direction of the arc. -PluginData: - overview: |- - Interface PluginData - ==================== - - Provides methods for managing plugin-specific data associated with a Penpot shape. - - ``` - interface PluginData { - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - } - ``` - - Hierarchy (View Summary) - - * PluginData - + File - + Library - + LibraryElement - + Page - + ShapeBase - members: - Methods: - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` -PreviousScreen: - overview: |- - Interface PreviousScreen - ======================== - - It takes back to the last board shown. - - ``` - interface PreviousScreen { - type: "previous-screen"; - } - ``` - - Referenced by: Action - members: - Properties: - type: |- - ``` - readonly type: "previous-screen" - ``` - - The action type -Push: - overview: |- - Interface Push - ============== - - Push animation - - ``` - interface Push { - type: "push"; - direction: "left" | "right" | "up" | "down"; - duration: number; - easing?: "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out"; - } - ``` - - Referenced by: Animation - members: - Properties: - type: |- - ``` - readonly type: "push" - ``` - - Type of the animation - direction: |- - ``` - readonly direction: "left" | "right" | "up" | "down" - ``` - - Direction for the push animation - duration: |- - ``` - readonly duration: number - ``` - - Duration of the animation effect - easing: |- - ``` - readonly easing?: "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" - ``` - - Function that the push effect will follow for the interpolation. - Defaults to `linear` -Rectangle: - overview: |- - Interface Rectangle - =================== - - Represents a rectangle shape in Penpot. - This interface extends `ShapeBase` and includes properties specific to rectangles. - - ``` - interface Rectangle { - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - type: "rectangle"; - fills: Fill[]; - id: string; - name: string; - parent: Shape | null; - parentIndex: number; - x: number; - y: number; - width: number; - height: number; - bounds: Bounds; - center: Point; - blocked: boolean; - hidden: boolean; - visible: boolean; - proportionLock: boolean; - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; - fixedWhenScrolling: boolean; - borderRadius: number; - borderRadiusTopLeft: number; - borderRadiusTopRight: number; - borderRadiusBottomRight: number; - borderRadiusBottomLeft: number; - opacity: number; - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity"; - shadows: Shadow[]; - blur?: Blur; - backgroundBlur?: Blur; - exports: Export[]; - boardX: number; - boardY: number; - parentX: number; - parentY: number; - flipX: boolean; - flipY: boolean; - rotation: number; - strokes: Stroke[]; - layoutChild?: LayoutChildProperties; - layoutCell?: LayoutCellProperties; - setParentIndex(index: number): void; - tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - }; - isComponentInstance(): boolean; - isComponentMainInstance(): boolean; - isComponentCopyInstance(): boolean; - isComponentRoot(): boolean; - isComponentHead(): boolean; - componentRefShape(): Shape | null; - componentRoot(): Shape | null; - componentHead(): Shape | null; - component(): LibraryComponent | null; - detach(): void; - swapComponent(component: LibraryComponent): void; - resetOverrides(): void; - switchVariant(pos: number, value: string): void; - combineAsVariants(ids: string[]): VariantContainer; - isVariantHead(): boolean; - resize(width: number, height: number): void; - rotate(angle: number, center?: { x: number; y: number } | null): void; - bringToFront(): void; - bringForward(): void; - sendToBack(): void; - sendBackward(): void; - export(config: Export): Promise>; - interactions: Interaction[]; - addInteraction( - trigger: Trigger, - action: Action, - delay?: number, - ): Interaction; - removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties?: TokenProperty[]): void; - clone(): Shape; - remove(): void; - } - ``` - - Hierarchy (View Summary) - - * ShapeBase - + Rectangle - - Referenced by: Context, ContextTypesUtils, Penpot, Shape - members: - Properties: - type: |- - ``` - readonly type: "rectangle" - ``` - - The type of the shape, which is always 'rect' for rectangle shapes. - fills: |- - ``` - fills: Fill[] - ``` - - The fills applied to the shape. - - Overrides ShapeBase.fills - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the shape. - name: |- - ``` - name: string - ``` - - The name of the shape. - parent: |- - ``` - readonly parent: Shape | null - ``` - - The parent shape. If the shape is the first level the parent will be the root shape. - For the root shape the parent is null - parentIndex: |- - ``` - readonly parentIndex: number - ``` - - Returns the index of the current shape in the parent - x: |- - ``` - x: number - ``` - - The x-coordinate of the shape's position. - y: |- - ``` - y: number - ``` - - The y-coordinate of the shape's position. - width: |- - ``` - readonly width: number - ``` - - The width of the shape. - height: |- - ``` - readonly height: number - ``` - - The height of the shape. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - The bounding box surrounding the current shape - center: |- - ``` - readonly center: Point - ``` - - The geometric center of the shape - blocked: |- - ``` - blocked: boolean - ``` - - Indicates whether the shape is blocked. - hidden: |- - ``` - hidden: boolean - ``` - - Indicates whether the shape is hidden. - visible: |- - ``` - visible: boolean - ``` - - Indicates whether the shape is visible. - proportionLock: |- - ``` - proportionLock: boolean - ``` - - Indicates whether the shape has proportion lock enabled. - constraintsHorizontal: |- - ``` - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale" - ``` - - The horizontal constraints applied to the shape. - constraintsVertical: |- - ``` - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom" - ``` - - The vertical constraints applied to the shape. - fixedWhenScrolling: |- - ``` - fixedWhenScrolling: boolean - ``` - - Indicates whether the shape stays fixed in place while scrolling. - borderRadius: |- - ``` - borderRadius: number - ``` - - The border radius of the shape. - borderRadiusTopLeft: |- - ``` - borderRadiusTopLeft: number - ``` - - The border radius of the top-left corner of the shape. - borderRadiusTopRight: |- - ``` - borderRadiusTopRight: number - ``` - - The border radius of the top-right corner of the shape. - borderRadiusBottomRight: |- - ``` - borderRadiusBottomRight: number - ``` - - The border radius of the bottom-right corner of the shape. - borderRadiusBottomLeft: |- - ``` - borderRadiusBottomLeft: number - ``` - - The border radius of the bottom-left corner of the shape. - opacity: |- - ``` - opacity: number - ``` - - The opacity of the shape. - blendMode: |- - ``` - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity" - ``` - - The blend mode applied to the shape. - shadows: |- - ``` - shadows: Shadow[] - ``` - - The shadows applied to the shape. - blur: |- - ``` - blur?: Blur - ``` - - The blur effect applied to the shape. - backgroundBlur: |- - ``` - backgroundBlur?: Blur - ``` - - The background blur effect applied to the shape. - Background blur creates a blur effect on the content behind the shape, - rather than on the shape's own content. - exports: |- - ``` - exports: Export[] - ``` - - The export settings of the shape. - boardX: |- - ``` - boardX: number - ``` - - The x-coordinate of the shape relative to its board. - boardY: |- - ``` - boardY: number - ``` - - The y-coordinate of the shape relative to its board. - parentX: |- - ``` - parentX: number - ``` - - The x-coordinate of the shape relative to its parent. - parentY: |- - ``` - parentY: number - ``` - - The y-coordinate of the shape relative to its parent. - flipX: |- - ``` - flipX: boolean - ``` - - Indicates whether the shape is flipped horizontally. - flipY: |- - ``` - flipY: boolean - ``` - - Indicates whether the shape is flipped vertically. - rotation: |- - ``` - rotation: number - ``` - - The rotation in degrees of the shape with respect to its center. - strokes: |- - ``` - strokes: Stroke[] - ``` - - The strokes applied to the shape. - layoutChild: |- - ``` - readonly layoutChild?: LayoutChildProperties - ``` - - Layout properties for children of the shape. - layoutCell: |- - ``` - readonly layoutCell?: LayoutCellProperties - ``` - - Layout properties for cells in a grid layout. - tokens: |- - ``` - readonly tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - } - ``` - - The design tokens applied to this shape. - It's a map property name -> token name. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - interactions: |- - ``` - readonly interactions: Interaction[] - ``` - - The interactions for the current shape. - Methods: - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` - setParentIndex: |- - ``` - setParentIndex(index: number): void - ``` - - Changes the index inside the parent of the current shape. - This method will shift the indexes of the shapes around that position to - match the index. - If the index is greater than the number of elements it will positioned last. - - Parameters - - * index: number - - the new index for the shape to be in - - Returns void - isComponentInstance: |- - ``` - isComponentInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component instance - isComponentMainInstance: |- - ``` - isComponentMainInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **main** instance - isComponentCopyInstance: |- - ``` - isComponentCopyInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **copy** instance - isComponentRoot: |- - ``` - isComponentRoot(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the root of a component tree - isComponentHead: |- - ``` - isComponentHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure - componentRefShape: |- - ``` - componentRefShape(): Shape | null - ``` - - Returns Shape | null - - Returns the equivalent shape in the component main instance. If the current shape is inside a - main instance will return `null`; - componentRoot: |- - ``` - componentRoot(): Shape | null - ``` - - Returns Shape | null - - Returns the root of the component tree structure for the current shape. If the current shape - is already a root will return itself. - componentHead: |- - ``` - componentHead(): Shape | null - ``` - - Returns Shape | null - - Returns the head of the component tree structure for the current shape. If the current shape - is already a head will return itself. - component: |- - ``` - component(): LibraryComponent | null - ``` - - Returns LibraryComponent | null - - If the shape is a component instance, returns the reference to the component associated - otherwise will return null - detach: |- - ``` - detach(): void - ``` - - If the current shape is a component it will remove the component information and leave the - shape as a "basic shape" - - Returns void - swapComponent: |- - ``` - swapComponent(component: LibraryComponent): void - ``` - - Swaps the component instance for another component, keeping the overrides - when possible. Similar to the "swap component" action on the Penpot interface. - The current shape must be a component copy instance. - - Parameters - - * component: LibraryComponent - - The new component to replace the current one - - Returns void - resetOverrides: |- - ``` - resetOverrides(): void - ``` - - Resets the overrides of the component copy, restoring all its attributes - (and those of its children) to the ones in the linked main component. - Similar to the "reset overrides" action on the Penpot interface. - The current shape must be a component copy instance. - - Returns void - switchVariant: |- - ``` - switchVariant(pos: number, value: string): void - ``` - - Switch a VariantComponent copy to the nearest one that has the specified property value - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - combineAsVariants: |- - ``` - combineAsVariants(ids: string[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu - on the Penpot interface. - The current shape must be a component main instance. - - Parameters - - * ids: string[] - - A list of ids of the main instances of the components to combine with this one. - - Returns VariantContainer - - The variant container created - isVariantHead: |- - ``` - isVariantHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure, - and that component is a VariantComponent - resize: |- - ``` - resize(width: number, height: number): void - ``` - - Resizes the shape to the specified width and height. - - Parameters - - * width: number - - The new width of the shape. - * height: number - - The new height of the shape. - - Returns void - - Example - ``` - shape.resize(200, 100); - ``` - rotate: |- - ``` - rotate(angle: number, center?: { x: number; y: number } | null): void - ``` - - Rotates the shape in relation with the given center. - - Parameters - - * angle: number - - Angle in degrees to rotate. - * center: { x: number; y: number } | null - - Center of the transform rotation. If not sent it will use the geometric center of the shape. - - Returns void - - Example - ``` - shape.rotate(45); - ``` - bringToFront: |- - ``` - bringToFront(): void - ``` - - Moves the current shape to the front of its siblings - - Returns void - bringForward: |- - ``` - bringForward(): void - ``` - - Moves the current shape one position forward in its list of siblings - - Returns void - sendToBack: |- - ``` - sendToBack(): void - ``` - - Moves the current shape to the back of its siblings - - Returns void - sendBackward: |- - ``` - sendBackward(): void - ``` - - Moves the current shape one position backwards in its list of siblings - - Returns void - export: |- - ``` - export(config: Export): Promise> - ``` - - Generates an export from the current shape. - - Parameters - - * config: Export - - Returns Promise> - - Example - ``` - shape.export({ type: 'png', scale: 2 }); - ``` - addInteraction: |- - ``` - addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction - ``` - - Adds a new interaction to the shape. - - If the interaction starts a flow (for example a `navigate-to` action) and - the shape's board is not already part of any flow, a new flow starting at - that board is created automatically, matching the behavior of the editor. - - Parameters - - * trigger: Trigger - - defines the conditions under which the action will be triggered - * action: Action - - defines what will be executed when the trigger happens - * delay: number - - for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. - - Returns Interaction - - Example - ``` - shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); - ``` - removeInteraction: |- - ``` - removeInteraction(interaction: Interaction): void - ``` - - Removes the interaction from the shape. - - Parameters - - * interaction: Interaction - - is the interaction to remove from the shape - - Returns void - - Example - ``` - shape.removeInteraction(interaction); - ``` - applyToken: |- - ``` - applyToken(token: Token, properties?: TokenProperty[]): void - ``` - - Applies one design token to one or more properties of the shape. - - Parameters - - * token: Token - - is the Token to apply - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - clone: |- - ``` - clone(): Shape - ``` - - Creates a clone of the shape. - - Returns Shape - - Returns a new instance of the shape with identical properties. - remove: |- - ``` - remove(): void - ``` - - Removes the shape from its parent. - - Returns void -RulerGuide: - overview: |- - Interface RulerGuide - ==================== - - Represents a ruler guide. These are horizontal or vertical lines that can be - used to position elements in the UI. - - ``` - interface RulerGuide { - orientation: RulerGuideOrientation; - position: number; - board?: Board; - remove(): void; - } - ``` - - Referenced by: Board, Page, VariantContainer - members: - Properties: - orientation: |- - ``` - readonly orientation: RulerGuideOrientation - ``` - - `orientation` indicates whether the ruler is either `horizontal` or `vertical` - position: |- - ``` - position: number - ``` - - `position` is the position in the axis in absolute coordinates. If this is a board - guide it will return the position relative to the board. - board: |- - ``` - board?: Board - ``` - - If the guide is attached to a board this will retrieve the board shape - Methods: - remove: |- - ``` - remove(): void - ``` - - Removes the guide from its page. - - Returns void -Shadow: - overview: |- - Interface Shadow - ================ - - Represents shadow properties in Penpot. - This interface includes properties for defining drop shadows and inner shadows, along with their visual attributes. - - ``` - interface Shadow { - id?: string; - style?: "drop-shadow" | "inner-shadow"; - offsetX?: number; - offsetY?: number; - blur?: number; - spread?: number; - hidden?: boolean; - color?: Color; - } - ``` - - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer - members: - Properties: - id: |- - ``` - id?: string - ``` - - The optional unique identifier for the shadow. - style: |- - ``` - style?: "drop-shadow" | "inner-shadow" - ``` - - The optional style of the shadow. - - * 'drop-shadow': A shadow cast outside the element. - * 'inner-shadow': A shadow cast inside the element. - offsetX: |- - ``` - offsetX?: number - ``` - - The optional X-axis offset of the shadow. - offsetY: |- - ``` - offsetY?: number - ``` - - The optional Y-axis offset of the shadow. - blur: |- - ``` - blur?: number - ``` - - The optional blur radius of the shadow. - spread: |- - ``` - spread?: number - ``` - - The optional spread radius of the shadow. - hidden: |- - ``` - hidden?: boolean - ``` - - Specifies whether the shadow is hidden. - Defaults to false if omitted. - color: |- - ``` - color?: Color - ``` - - The optional color of the shadow, defined by a Color object. -ShapeBase: - overview: |- - Interface ShapeBase - =================== - - Represents the base properties and methods of a shape in Penpot. - This interface provides common properties and methods shared by all shapes. - - ``` - interface ShapeBase { - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - id: string; - name: string; - parent: Shape | null; - parentIndex: number; - x: number; - y: number; - width: number; - height: number; - bounds: Bounds; - center: Point; - blocked: boolean; - hidden: boolean; - visible: boolean; - proportionLock: boolean; - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; - fixedWhenScrolling: boolean; - borderRadius: number; - borderRadiusTopLeft: number; - borderRadiusTopRight: number; - borderRadiusBottomRight: number; - borderRadiusBottomLeft: number; - opacity: number; - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity"; - shadows: Shadow[]; - blur?: Blur; - backgroundBlur?: Blur; - exports: Export[]; - boardX: number; - boardY: number; - parentX: number; - parentY: number; - flipX: boolean; - flipY: boolean; - rotation: number; - fills: Fill[] - | "mixed"; - strokes: Stroke[]; - layoutChild?: LayoutChildProperties; - layoutCell?: LayoutCellProperties; - setParentIndex(index: number): void; - tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - }; - isComponentInstance(): boolean; - isComponentMainInstance(): boolean; - isComponentCopyInstance(): boolean; - isComponentRoot(): boolean; - isComponentHead(): boolean; - componentRefShape(): Shape | null; - componentRoot(): Shape | null; - componentHead(): Shape | null; - component(): LibraryComponent | null; - detach(): void; - swapComponent(component: LibraryComponent): void; - resetOverrides(): void; - switchVariant(pos: number, value: string): void; - combineAsVariants(ids: string[]): VariantContainer; - isVariantHead(): boolean; - resize(width: number, height: number): void; - rotate(angle: number, center?: { x: number; y: number } | null): void; - bringToFront(): void; - bringForward(): void; - sendToBack(): void; - sendBackward(): void; - export(config: Export): Promise>; - interactions: Interaction[]; - addInteraction( - trigger: Trigger, - action: Action, - delay?: number, - ): Interaction; - removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties?: TokenProperty[]): void; - clone(): Shape; - remove(): void; - } - ``` - - Hierarchy (View Summary) - - * PluginData - + ShapeBase - - Board - - Boolean - - Ellipse - - Group - - Image - - Path - - Rectangle - - SvgRaw - - Text - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the shape. - name: |- - ``` - name: string - ``` - - The name of the shape. - parent: |- - ``` - readonly parent: Shape | null - ``` - - The parent shape. If the shape is the first level the parent will be the root shape. - For the root shape the parent is null - parentIndex: |- - ``` - readonly parentIndex: number - ``` - - Returns the index of the current shape in the parent - x: |- - ``` - x: number - ``` - - The x-coordinate of the shape's position. - y: |- - ``` - y: number - ``` - - The y-coordinate of the shape's position. - width: |- - ``` - readonly width: number - ``` - - The width of the shape. - height: |- - ``` - readonly height: number - ``` - - The height of the shape. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - The bounding box surrounding the current shape - center: |- - ``` - readonly center: Point - ``` - - The geometric center of the shape - blocked: |- - ``` - blocked: boolean - ``` - - Indicates whether the shape is blocked. - hidden: |- - ``` - hidden: boolean - ``` - - Indicates whether the shape is hidden. - visible: |- - ``` - visible: boolean - ``` - - Indicates whether the shape is visible. - proportionLock: |- - ``` - proportionLock: boolean - ``` - - Indicates whether the shape has proportion lock enabled. - constraintsHorizontal: |- - ``` - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale" - ``` - - The horizontal constraints applied to the shape. - constraintsVertical: |- - ``` - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom" - ``` - - The vertical constraints applied to the shape. - fixedWhenScrolling: |- - ``` - fixedWhenScrolling: boolean - ``` - - Indicates whether the shape stays fixed in place while scrolling. - borderRadius: |- - ``` - borderRadius: number - ``` - - The border radius of the shape. - borderRadiusTopLeft: |- - ``` - borderRadiusTopLeft: number - ``` - - The border radius of the top-left corner of the shape. - borderRadiusTopRight: |- - ``` - borderRadiusTopRight: number - ``` - - The border radius of the top-right corner of the shape. - borderRadiusBottomRight: |- - ``` - borderRadiusBottomRight: number - ``` - - The border radius of the bottom-right corner of the shape. - borderRadiusBottomLeft: |- - ``` - borderRadiusBottomLeft: number - ``` - - The border radius of the bottom-left corner of the shape. - opacity: |- - ``` - opacity: number - ``` - - The opacity of the shape. - blendMode: |- - ``` - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity" - ``` - - The blend mode applied to the shape. - shadows: |- - ``` - shadows: Shadow[] - ``` - - The shadows applied to the shape. - blur: |- - ``` - blur?: Blur - ``` - - The blur effect applied to the shape. - backgroundBlur: |- - ``` - backgroundBlur?: Blur - ``` - - The background blur effect applied to the shape. - Background blur creates a blur effect on the content behind the shape, - rather than on the shape's own content. - exports: |- - ``` - exports: Export[] - ``` - - The export settings of the shape. - boardX: |- - ``` - boardX: number - ``` - - The x-coordinate of the shape relative to its board. - boardY: |- - ``` - boardY: number - ``` - - The y-coordinate of the shape relative to its board. - parentX: |- - ``` - parentX: number - ``` - - The x-coordinate of the shape relative to its parent. - parentY: |- - ``` - parentY: number - ``` - - The y-coordinate of the shape relative to its parent. - flipX: |- - ``` - flipX: boolean - ``` - - Indicates whether the shape is flipped horizontally. - flipY: |- - ``` - flipY: boolean - ``` - - Indicates whether the shape is flipped vertically. - rotation: |- - ``` - rotation: number - ``` - - The rotation in degrees of the shape with respect to its center. - fills: |- - ``` - fills: Fill[] | "mixed" - ``` - - The fills applied to the shape. - strokes: |- - ``` - strokes: Stroke[] - ``` - - The strokes applied to the shape. - layoutChild: |- - ``` - readonly layoutChild?: LayoutChildProperties - ``` - - Layout properties for children of the shape. - layoutCell: |- - ``` - readonly layoutCell?: LayoutCellProperties - ``` - - Layout properties for cells in a grid layout. - tokens: |- - ``` - readonly tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - } - ``` - - The design tokens applied to this shape. - It's a map property name -> token name. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - interactions: |- - ``` - readonly interactions: Interaction[] - ``` - - The interactions for the current shape. - Methods: - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` - setParentIndex: |- - ``` - setParentIndex(index: number): void - ``` - - Changes the index inside the parent of the current shape. - This method will shift the indexes of the shapes around that position to - match the index. - If the index is greater than the number of elements it will positioned last. - - Parameters - - * index: number - - the new index for the shape to be in - - Returns void - isComponentInstance: |- - ``` - isComponentInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component instance - isComponentMainInstance: |- - ``` - isComponentMainInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **main** instance - isComponentCopyInstance: |- - ``` - isComponentCopyInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **copy** instance - isComponentRoot: |- - ``` - isComponentRoot(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the root of a component tree - isComponentHead: |- - ``` - isComponentHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure - componentRefShape: |- - ``` - componentRefShape(): Shape | null - ``` - - Returns Shape | null - - Returns the equivalent shape in the component main instance. If the current shape is inside a - main instance will return `null`; - componentRoot: |- - ``` - componentRoot(): Shape | null - ``` - - Returns Shape | null - - Returns the root of the component tree structure for the current shape. If the current shape - is already a root will return itself. - componentHead: |- - ``` - componentHead(): Shape | null - ``` - - Returns Shape | null - - Returns the head of the component tree structure for the current shape. If the current shape - is already a head will return itself. - component: |- - ``` - component(): LibraryComponent | null - ``` - - Returns LibraryComponent | null - - If the shape is a component instance, returns the reference to the component associated - otherwise will return null - detach: |- - ``` - detach(): void - ``` - - If the current shape is a component it will remove the component information and leave the - shape as a "basic shape" - - Returns void - swapComponent: |- - ``` - swapComponent(component: LibraryComponent): void - ``` - - Swaps the component instance for another component, keeping the overrides - when possible. Similar to the "swap component" action on the Penpot interface. - The current shape must be a component copy instance. - - Parameters - - * component: LibraryComponent - - The new component to replace the current one - - Returns void - resetOverrides: |- - ``` - resetOverrides(): void - ``` - - Resets the overrides of the component copy, restoring all its attributes - (and those of its children) to the ones in the linked main component. - Similar to the "reset overrides" action on the Penpot interface. - The current shape must be a component copy instance. - - Returns void - switchVariant: |- - ``` - switchVariant(pos: number, value: string): void - ``` - - Switch a VariantComponent copy to the nearest one that has the specified property value - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - combineAsVariants: |- - ``` - combineAsVariants(ids: string[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu - on the Penpot interface. - The current shape must be a component main instance. - - Parameters - - * ids: string[] - - A list of ids of the main instances of the components to combine with this one. - - Returns VariantContainer - - The variant container created - isVariantHead: |- - ``` - isVariantHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure, - and that component is a VariantComponent - resize: |- - ``` - resize(width: number, height: number): void - ``` - - Resizes the shape to the specified width and height. - - Parameters - - * width: number - - The new width of the shape. - * height: number - - The new height of the shape. - - Returns void - - Example - ``` - shape.resize(200, 100); - ``` - rotate: |- - ``` - rotate(angle: number, center?: { x: number; y: number } | null): void - ``` - - Rotates the shape in relation with the given center. - - Parameters - - * angle: number - - Angle in degrees to rotate. - * center: { x: number; y: number } | null - - Center of the transform rotation. If not sent it will use the geometric center of the shape. - - Returns void - - Example - ``` - shape.rotate(45); - ``` - bringToFront: |- - ``` - bringToFront(): void - ``` - - Moves the current shape to the front of its siblings - - Returns void - bringForward: |- - ``` - bringForward(): void - ``` - - Moves the current shape one position forward in its list of siblings - - Returns void - sendToBack: |- - ``` - sendToBack(): void - ``` - - Moves the current shape to the back of its siblings - - Returns void - sendBackward: |- - ``` - sendBackward(): void - ``` - - Moves the current shape one position backwards in its list of siblings - - Returns void - export: |- - ``` - export(config: Export): Promise> - ``` - - Generates an export from the current shape. - - Parameters - - * config: Export - - Returns Promise> - - Example - ``` - shape.export({ type: 'png', scale: 2 }); - ``` - addInteraction: |- - ``` - addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction - ``` - - Adds a new interaction to the shape. - - If the interaction starts a flow (for example a `navigate-to` action) and - the shape's board is not already part of any flow, a new flow starting at - that board is created automatically, matching the behavior of the editor. - - Parameters - - * trigger: Trigger - - defines the conditions under which the action will be triggered - * action: Action - - defines what will be executed when the trigger happens - * delay: number - - for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. - - Returns Interaction - - Example - ``` - shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); - ``` - removeInteraction: |- - ``` - removeInteraction(interaction: Interaction): void - ``` - - Removes the interaction from the shape. - - Parameters - - * interaction: Interaction - - is the interaction to remove from the shape - - Returns void - - Example - ``` - shape.removeInteraction(interaction); - ``` - applyToken: |- - ``` - applyToken(token: Token, properties?: TokenProperty[]): void - ``` - - Applies one design token to one or more properties of the shape. - - Parameters - - * token: Token - - is the Token to apply - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - clone: |- - ``` - clone(): Shape - ``` - - Creates a clone of the shape. - - Returns Shape - - Returns a new instance of the shape with identical properties. - remove: |- - ``` - remove(): void - ``` - - Removes the shape from its parent. - - Returns void -Slide: - overview: |- - Interface Slide - =============== - - Slide animation - - ``` - interface Slide { - type: "slide"; - way: "in" | "out"; - direction: "left" | "right" | "up" | "down"; - duration: number; - offsetEffect?: boolean; - easing?: "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out"; - } - ``` - - Referenced by: Animation - members: - Properties: - type: |- - ``` - readonly type: "slide" - ``` - - Type of the animation. - way: |- - ``` - readonly way: "in" | "out" - ``` - - Indicate if the slide will be either in-to-out `in` or out-to-in `out`. - direction: |- - ``` - readonly direction: "left" | "right" | "up" | "down" - ``` - - Direction for the slide animation. - duration: |- - ``` - readonly duration: number - ``` - - Duration of the animation effect. - offsetEffect: |- - ``` - readonly offsetEffect?: boolean - ``` - - If `true` the offset effect will be used. - easing: |- - ``` - readonly easing?: "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" - ``` - - Function that the slide effect will follow for the interpolation. - Defaults to `linear`. -Stroke: - overview: |- - Interface Stroke - ================ - - Represents stroke properties in Penpot. You can add a stroke to any shape except for groups. - This interface includes properties for defining the color, style, width, alignment, and caps of a stroke. - - ``` - interface Stroke { - strokeColor?: string; - strokeColorRefFile?: string; - strokeColorRefId?: string; - strokeOpacity?: number; - strokeStyle?: "none" | "svg" | "mixed" | "solid" | "dotted" | "dashed"; - strokeWidth?: number; - strokeAlignment?: "center" | "inner" | "outer"; - strokeCapStart?: StrokeCap; - strokeCapEnd?: StrokeCap; - strokeColorGradient?: Gradient; - } - ``` - - Referenced by: Board, Boolean, Ellipse, Group, Image, LibraryColor, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer - members: - Properties: - strokeColor: |- - ``` - strokeColor?: string - ``` - - The optional color of the stroke, represented as a string (e.g., '#FF5733'). - strokeColorRefFile: |- - ``` - strokeColorRefFile?: string - ``` - - The optional reference to an external file for the stroke color. - strokeColorRefId: |- - ``` - strokeColorRefId?: string - ``` - - The optional reference ID within the external file for the stroke color. - strokeOpacity: |- - ``` - strokeOpacity?: number - ``` - - The optional opacity level of the stroke color, ranging from 0 (fully transparent) to 1 (fully opaque). - Defaults to 1 if omitted. - strokeStyle: |- - ``` - strokeStyle?: "none" | "svg" | "mixed" | "solid" | "dotted" | "dashed" - ``` - - The optional style of the stroke. - strokeWidth: |- - ``` - strokeWidth?: number - ``` - - The optional width of the stroke. - strokeAlignment: |- - ``` - strokeAlignment?: "center" | "inner" | "outer" - ``` - - The optional alignment of the stroke relative to the shape's boundary. - strokeCapStart: |- - ``` - strokeCapStart?: StrokeCap - ``` - - The optional cap style for the start of the stroke. - strokeCapEnd: |- - ``` - strokeCapEnd?: StrokeCap - ``` - - The optional cap style for the end of the stroke. - strokeColorGradient: |- - ``` - strokeColorGradient?: Gradient - ``` - - The optional gradient stroke defined by a Gradient object. -SvgRaw: - overview: |- - Interface SvgRaw - ================ - - Represents an SVG raw shape in Penpot. - This interface extends `ShapeBase` and includes properties specific to raw SVG shapes. - - ``` - interface SvgRaw { - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - id: string; - name: string; - parent: Shape | null; - parentIndex: number; - x: number; - y: number; - width: number; - height: number; - bounds: Bounds; - center: Point; - blocked: boolean; - hidden: boolean; - visible: boolean; - proportionLock: boolean; - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; - fixedWhenScrolling: boolean; - borderRadius: number; - borderRadiusTopLeft: number; - borderRadiusTopRight: number; - borderRadiusBottomRight: number; - borderRadiusBottomLeft: number; - opacity: number; - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity"; - shadows: Shadow[]; - blur?: Blur; - backgroundBlur?: Blur; - exports: Export[]; - boardX: number; - boardY: number; - parentX: number; - parentY: number; - flipX: boolean; - flipY: boolean; - rotation: number; - fills: Fill[] - | "mixed"; - strokes: Stroke[]; - layoutChild?: LayoutChildProperties; - layoutCell?: LayoutCellProperties; - setParentIndex(index: number): void; - tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - }; - isComponentInstance(): boolean; - isComponentMainInstance(): boolean; - isComponentCopyInstance(): boolean; - isComponentRoot(): boolean; - isComponentHead(): boolean; - componentRefShape(): Shape | null; - componentRoot(): Shape | null; - componentHead(): Shape | null; - component(): LibraryComponent | null; - detach(): void; - swapComponent(component: LibraryComponent): void; - resetOverrides(): void; - switchVariant(pos: number, value: string): void; - combineAsVariants(ids: string[]): VariantContainer; - isVariantHead(): boolean; - resize(width: number, height: number): void; - rotate(angle: number, center?: { x: number; y: number } | null): void; - bringToFront(): void; - bringForward(): void; - sendToBack(): void; - sendBackward(): void; - export(config: Export): Promise>; - interactions: Interaction[]; - addInteraction( - trigger: Trigger, - action: Action, - delay?: number, - ): Interaction; - removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties?: TokenProperty[]): void; - clone(): Shape; - remove(): void; - type: "svg-raw"; - } - ``` - - Hierarchy (View Summary) - - * ShapeBase - + SvgRaw - - Referenced by: ContextTypesUtils, Shape - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the shape. - name: |- - ``` - name: string - ``` - - The name of the shape. - parent: |- - ``` - readonly parent: Shape | null - ``` - - The parent shape. If the shape is the first level the parent will be the root shape. - For the root shape the parent is null - parentIndex: |- - ``` - readonly parentIndex: number - ``` - - Returns the index of the current shape in the parent - x: |- - ``` - x: number - ``` - - The x-coordinate of the shape's position. - y: |- - ``` - y: number - ``` - - The y-coordinate of the shape's position. - width: |- - ``` - readonly width: number - ``` - - The width of the shape. - height: |- - ``` - readonly height: number - ``` - - The height of the shape. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - The bounding box surrounding the current shape - center: |- - ``` - readonly center: Point - ``` - - The geometric center of the shape - blocked: |- - ``` - blocked: boolean - ``` - - Indicates whether the shape is blocked. - hidden: |- - ``` - hidden: boolean - ``` - - Indicates whether the shape is hidden. - visible: |- - ``` - visible: boolean - ``` - - Indicates whether the shape is visible. - proportionLock: |- - ``` - proportionLock: boolean - ``` - - Indicates whether the shape has proportion lock enabled. - constraintsHorizontal: |- - ``` - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale" - ``` - - The horizontal constraints applied to the shape. - constraintsVertical: |- - ``` - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom" - ``` - - The vertical constraints applied to the shape. - fixedWhenScrolling: |- - ``` - fixedWhenScrolling: boolean - ``` - - Indicates whether the shape stays fixed in place while scrolling. - borderRadius: |- - ``` - borderRadius: number - ``` - - The border radius of the shape. - borderRadiusTopLeft: |- - ``` - borderRadiusTopLeft: number - ``` - - The border radius of the top-left corner of the shape. - borderRadiusTopRight: |- - ``` - borderRadiusTopRight: number - ``` - - The border radius of the top-right corner of the shape. - borderRadiusBottomRight: |- - ``` - borderRadiusBottomRight: number - ``` - - The border radius of the bottom-right corner of the shape. - borderRadiusBottomLeft: |- - ``` - borderRadiusBottomLeft: number - ``` - - The border radius of the bottom-left corner of the shape. - opacity: |- - ``` - opacity: number - ``` - - The opacity of the shape. - blendMode: |- - ``` - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity" - ``` - - The blend mode applied to the shape. - shadows: |- - ``` - shadows: Shadow[] - ``` - - The shadows applied to the shape. - blur: |- - ``` - blur?: Blur - ``` - - The blur effect applied to the shape. - backgroundBlur: |- - ``` - backgroundBlur?: Blur - ``` - - The background blur effect applied to the shape. - Background blur creates a blur effect on the content behind the shape, - rather than on the shape's own content. - exports: |- - ``` - exports: Export[] - ``` - - The export settings of the shape. - boardX: |- - ``` - boardX: number - ``` - - The x-coordinate of the shape relative to its board. - boardY: |- - ``` - boardY: number - ``` - - The y-coordinate of the shape relative to its board. - parentX: |- - ``` - parentX: number - ``` - - The x-coordinate of the shape relative to its parent. - parentY: |- - ``` - parentY: number - ``` - - The y-coordinate of the shape relative to its parent. - flipX: |- - ``` - flipX: boolean - ``` - - Indicates whether the shape is flipped horizontally. - flipY: |- - ``` - flipY: boolean - ``` - - Indicates whether the shape is flipped vertically. - rotation: |- - ``` - rotation: number - ``` - - The rotation in degrees of the shape with respect to its center. - fills: |- - ``` - fills: Fill[] | "mixed" - ``` - - The fills applied to the shape. - strokes: |- - ``` - strokes: Stroke[] - ``` - - The strokes applied to the shape. - layoutChild: |- - ``` - readonly layoutChild?: LayoutChildProperties - ``` - - Layout properties for children of the shape. - layoutCell: |- - ``` - readonly layoutCell?: LayoutCellProperties - ``` - - Layout properties for cells in a grid layout. - tokens: |- - ``` - readonly tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - } - ``` - - The design tokens applied to this shape. - It's a map property name -> token name. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - interactions: |- - ``` - readonly interactions: Interaction[] - ``` - - The interactions for the current shape. - type: |- - ``` - readonly type: "svg-raw" - ``` - - The type of the shape, which is always 'svg-raw' for raw SVG shapes. - Methods: - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` - setParentIndex: |- - ``` - setParentIndex(index: number): void - ``` - - Changes the index inside the parent of the current shape. - This method will shift the indexes of the shapes around that position to - match the index. - If the index is greater than the number of elements it will positioned last. - - Parameters - - * index: number - - the new index for the shape to be in - - Returns void - isComponentInstance: |- - ``` - isComponentInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component instance - isComponentMainInstance: |- - ``` - isComponentMainInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **main** instance - isComponentCopyInstance: |- - ``` - isComponentCopyInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **copy** instance - isComponentRoot: |- - ``` - isComponentRoot(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the root of a component tree - isComponentHead: |- - ``` - isComponentHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure - componentRefShape: |- - ``` - componentRefShape(): Shape | null - ``` - - Returns Shape | null - - Returns the equivalent shape in the component main instance. If the current shape is inside a - main instance will return `null`; - componentRoot: |- - ``` - componentRoot(): Shape | null - ``` - - Returns Shape | null - - Returns the root of the component tree structure for the current shape. If the current shape - is already a root will return itself. - componentHead: |- - ``` - componentHead(): Shape | null - ``` - - Returns Shape | null - - Returns the head of the component tree structure for the current shape. If the current shape - is already a head will return itself. - component: |- - ``` - component(): LibraryComponent | null - ``` - - Returns LibraryComponent | null - - If the shape is a component instance, returns the reference to the component associated - otherwise will return null - detach: |- - ``` - detach(): void - ``` - - If the current shape is a component it will remove the component information and leave the - shape as a "basic shape" - - Returns void - swapComponent: |- - ``` - swapComponent(component: LibraryComponent): void - ``` - - Swaps the component instance for another component, keeping the overrides - when possible. Similar to the "swap component" action on the Penpot interface. - The current shape must be a component copy instance. - - Parameters - - * component: LibraryComponent - - The new component to replace the current one - - Returns void - resetOverrides: |- - ``` - resetOverrides(): void - ``` - - Resets the overrides of the component copy, restoring all its attributes - (and those of its children) to the ones in the linked main component. - Similar to the "reset overrides" action on the Penpot interface. - The current shape must be a component copy instance. - - Returns void - switchVariant: |- - ``` - switchVariant(pos: number, value: string): void - ``` - - Switch a VariantComponent copy to the nearest one that has the specified property value - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - combineAsVariants: |- - ``` - combineAsVariants(ids: string[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu - on the Penpot interface. - The current shape must be a component main instance. - - Parameters - - * ids: string[] - - A list of ids of the main instances of the components to combine with this one. - - Returns VariantContainer - - The variant container created - isVariantHead: |- - ``` - isVariantHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure, - and that component is a VariantComponent - resize: |- - ``` - resize(width: number, height: number): void - ``` - - Resizes the shape to the specified width and height. - - Parameters - - * width: number - - The new width of the shape. - * height: number - - The new height of the shape. - - Returns void - - Example - ``` - shape.resize(200, 100); - ``` - rotate: |- - ``` - rotate(angle: number, center?: { x: number; y: number } | null): void - ``` - - Rotates the shape in relation with the given center. - - Parameters - - * angle: number - - Angle in degrees to rotate. - * center: { x: number; y: number } | null - - Center of the transform rotation. If not sent it will use the geometric center of the shape. - - Returns void - - Example - ``` - shape.rotate(45); - ``` - bringToFront: |- - ``` - bringToFront(): void - ``` - - Moves the current shape to the front of its siblings - - Returns void - bringForward: |- - ``` - bringForward(): void - ``` - - Moves the current shape one position forward in its list of siblings - - Returns void - sendToBack: |- - ``` - sendToBack(): void - ``` - - Moves the current shape to the back of its siblings - - Returns void - sendBackward: |- - ``` - sendBackward(): void - ``` - - Moves the current shape one position backwards in its list of siblings - - Returns void - export: |- - ``` - export(config: Export): Promise> - ``` - - Generates an export from the current shape. - - Parameters - - * config: Export - - Returns Promise> - - Example - ``` - shape.export({ type: 'png', scale: 2 }); - ``` - addInteraction: |- - ``` - addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction - ``` - - Adds a new interaction to the shape. - - If the interaction starts a flow (for example a `navigate-to` action) and - the shape's board is not already part of any flow, a new flow starting at - that board is created automatically, matching the behavior of the editor. - - Parameters - - * trigger: Trigger - - defines the conditions under which the action will be triggered - * action: Action - - defines what will be executed when the trigger happens - * delay: number - - for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. - - Returns Interaction - - Example - ``` - shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); - ``` - removeInteraction: |- - ``` - removeInteraction(interaction: Interaction): void - ``` - - Removes the interaction from the shape. - - Parameters - - * interaction: Interaction - - is the interaction to remove from the shape - - Returns void - - Example - ``` - shape.removeInteraction(interaction); - ``` - applyToken: |- - ``` - applyToken(token: Token, properties?: TokenProperty[]): void - ``` - - Applies one design token to one or more properties of the shape. - - Parameters - - * token: Token - - is the Token to apply - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - clone: |- - ``` - clone(): Shape - ``` - - Creates a clone of the shape. - - Returns Shape - - Returns a new instance of the shape with identical properties. - remove: |- - ``` - remove(): void - ``` - - Removes the shape from its parent. - - Returns void -Text: - overview: |- - Interface Text - ============== - - Text represents a text element in the Penpot application, extending the base shape interface. - It includes various properties to define the text content and its styling attributes. - - ``` - interface Text { - getPluginData(key: string): string; - setPluginData(key: string, value: string): void; - getPluginDataKeys(): string[]; - getSharedPluginData(namespace: string, key: string): string; - setSharedPluginData(namespace: string, key: string, value: string): void; - getSharedPluginDataKeys(namespace: string): string[]; - id: string; - name: string; - parent: Shape | null; - parentIndex: number; - x: number; - y: number; - width: number; - height: number; - bounds: Bounds; - center: Point; - blocked: boolean; - hidden: boolean; - visible: boolean; - proportionLock: boolean; - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale"; - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom"; - fixedWhenScrolling: boolean; - borderRadius: number; - borderRadiusTopLeft: number; - borderRadiusTopRight: number; - borderRadiusBottomRight: number; - borderRadiusBottomLeft: number; - opacity: number; - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity"; - shadows: Shadow[]; - blur?: Blur; - backgroundBlur?: Blur; - exports: Export[]; - boardX: number; - boardY: number; - parentX: number; - parentY: number; - flipX: boolean; - flipY: boolean; - rotation: number; - fills: Fill[] - | "mixed"; - strokes: Stroke[]; - layoutChild?: LayoutChildProperties; - layoutCell?: LayoutCellProperties; - setParentIndex(index: number): void; - tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - }; - isComponentInstance(): boolean; - isComponentMainInstance(): boolean; - isComponentCopyInstance(): boolean; - isComponentRoot(): boolean; - isComponentHead(): boolean; - componentRefShape(): Shape | null; - componentRoot(): Shape | null; - componentHead(): Shape | null; - component(): LibraryComponent | null; - detach(): void; - swapComponent(component: LibraryComponent): void; - resetOverrides(): void; - switchVariant(pos: number, value: string): void; - combineAsVariants(ids: string[]): VariantContainer; - isVariantHead(): boolean; - resize(width: number, height: number): void; - rotate(angle: number, center?: { x: number; y: number } | null): void; - bringToFront(): void; - bringForward(): void; - sendToBack(): void; - sendBackward(): void; - export(config: Export): Promise>; - interactions: Interaction[]; - addInteraction( - trigger: Trigger, - action: Action, - delay?: number, - ): Interaction; - removeInteraction(interaction: Interaction): void; - applyToken(token: Token, properties?: TokenProperty[]): void; - clone(): Shape; - remove(): void; - type: "text"; - characters: string; - growType: "fixed" | "auto-width" | "auto-height"; - fontId: string; - fontFamily: string; - fontVariantId: string; - fontSize: string; - fontWeight: string; - fontStyle: "normal" | "italic" | "mixed" | null; - lineHeight: string; - letterSpacing: string; - textTransform: "mixed" | "uppercase" | "capitalize" | "lowercase" | null; - textDecoration: "mixed" | "underline" | "line-through" | null; - direction: "mixed" | "ltr" | "rtl" | null; - align: "center" | "left" | "right" | "mixed" | "justify" | null; - verticalAlign: "center" | "top" | "bottom" | null; - textBounds: { x: number; y: number; width: number; height: number }; - getRange(start: number, end: number): TextRange; - applyTypography(typography: LibraryTypography): void; - } - ``` - - Hierarchy (View Summary) - - * ShapeBase - + Text - - Referenced by: Context, ContextTypesUtils, Font, Penpot, Shape, TextRange - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the shape. - name: |- - ``` - name: string - ``` - - The name of the shape. - parent: |- - ``` - readonly parent: Shape | null - ``` - - The parent shape. If the shape is the first level the parent will be the root shape. - For the root shape the parent is null - parentIndex: |- - ``` - readonly parentIndex: number - ``` - - Returns the index of the current shape in the parent - x: |- - ``` - x: number - ``` - - The x-coordinate of the shape's position. - y: |- - ``` - y: number - ``` - - The y-coordinate of the shape's position. - width: |- - ``` - readonly width: number - ``` - - The width of the shape. - height: |- - ``` - readonly height: number - ``` - - The height of the shape. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - The bounding box surrounding the current shape - center: |- - ``` - readonly center: Point - ``` - - The geometric center of the shape - blocked: |- - ``` - blocked: boolean - ``` - - Indicates whether the shape is blocked. - hidden: |- - ``` - hidden: boolean - ``` - - Indicates whether the shape is hidden. - visible: |- - ``` - visible: boolean - ``` - - Indicates whether the shape is visible. - proportionLock: |- - ``` - proportionLock: boolean - ``` - - Indicates whether the shape has proportion lock enabled. - constraintsHorizontal: |- - ``` - constraintsHorizontal: "center" | "left" | "right" | "leftright" | "scale" - ``` - - The horizontal constraints applied to the shape. - constraintsVertical: |- - ``` - constraintsVertical: "center" | "top" | "bottom" | "scale" | "topbottom" - ``` - - The vertical constraints applied to the shape. - fixedWhenScrolling: |- - ``` - fixedWhenScrolling: boolean - ``` - - Indicates whether the shape stays fixed in place while scrolling. - borderRadius: |- - ``` - borderRadius: number - ``` - - The border radius of the shape. - borderRadiusTopLeft: |- - ``` - borderRadiusTopLeft: number - ``` - - The border radius of the top-left corner of the shape. - borderRadiusTopRight: |- - ``` - borderRadiusTopRight: number - ``` - - The border radius of the top-right corner of the shape. - borderRadiusBottomRight: |- - ``` - borderRadiusBottomRight: number - ``` - - The border radius of the bottom-right corner of the shape. - borderRadiusBottomLeft: |- - ``` - borderRadiusBottomLeft: number - ``` - - The border radius of the bottom-left corner of the shape. - opacity: |- - ``` - opacity: number - ``` - - The opacity of the shape. - blendMode: |- - ``` - blendMode: - | "difference" - | "normal" - | "darken" - | "multiply" - | "color-burn" - | "lighten" - | "screen" - | "color-dodge" - | "overlay" - | "soft-light" - | "hard-light" - | "exclusion" - | "hue" - | "saturation" - | "color" - | "luminosity" - ``` - - The blend mode applied to the shape. - shadows: |- - ``` - shadows: Shadow[] - ``` - - The shadows applied to the shape. - blur: |- - ``` - blur?: Blur - ``` - - The blur effect applied to the shape. - backgroundBlur: |- - ``` - backgroundBlur?: Blur - ``` - - The background blur effect applied to the shape. - Background blur creates a blur effect on the content behind the shape, - rather than on the shape's own content. - exports: |- - ``` - exports: Export[] - ``` - - The export settings of the shape. - boardX: |- - ``` - boardX: number - ``` - - The x-coordinate of the shape relative to its board. - boardY: |- - ``` - boardY: number - ``` - - The y-coordinate of the shape relative to its board. - parentX: |- - ``` - parentX: number - ``` - - The x-coordinate of the shape relative to its parent. - parentY: |- - ``` - parentY: number - ``` - - The y-coordinate of the shape relative to its parent. - flipX: |- - ``` - flipX: boolean - ``` - - Indicates whether the shape is flipped horizontally. - flipY: |- - ``` - flipY: boolean - ``` - - Indicates whether the shape is flipped vertically. - rotation: |- - ``` - rotation: number - ``` - - The rotation in degrees of the shape with respect to its center. - fills: |- - ``` - fills: Fill[] | "mixed" - ``` - - The fills applied to the shape. - strokes: |- - ``` - strokes: Stroke[] - ``` - - The strokes applied to the shape. - layoutChild: |- - ``` - readonly layoutChild?: LayoutChildProperties - ``` - - Layout properties for children of the shape. - layoutCell: |- - ``` - readonly layoutCell?: LayoutCellProperties - ``` - - Layout properties for cells in a grid layout. - tokens: |- - ``` - readonly tokens: { - width: string; - height: string; - fill: string; - x: string; - y: string; - all: string; - borderRadiusTopLeft: string; - borderRadiusTopRight: string; - borderRadiusBottomRight: string; - borderRadiusBottomLeft: string; - shadow: string; - strokeColor: string; - strokeWidth: string; - fontFamilies: string; - fontSize: string; - fontWeight: string; - letterSpacing: string; - rotation: string; - opacity: string; - layoutItemMinW: string; - layoutItemMaxW: string; - layoutItemMinH: string; - layoutItemMaxH: string; - rowGap: string; - columnGap: string; - paddingLeft: string; - paddingTop: string; - paddingRight: string; - paddingBottom: string; - marginLeft: string; - marginTop: string; - marginRight: string; - marginBottom: string; - textCase: string; - textDecoration: string; - typography: string; - } - ``` - - The design tokens applied to this shape. - It's a map property name -> token name. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - interactions: |- - ``` - readonly interactions: Interaction[] - ``` - - The interactions for the current shape. - type: |- - ``` - readonly type: "text" - ``` - - The type of the shape, which is always 'text' for text shapes. - characters: |- - ``` - characters: string - ``` - - The characters contained within the text shape. - growType: |- - ``` - growType: "fixed" | "auto-width" | "auto-height" - ``` - - The grow type of the text shape, defining how the text box adjusts its size. - Possible values are: - - * 'fixed': Fixed size. - * 'auto-width': Adjusts width automatically. - * 'auto-height': Adjusts height automatically. - fontId: |- - ``` - fontId: string - ``` - - The font ID used in the text shape, or 'mixed' if multiple fonts are used. - fontFamily: |- - ``` - fontFamily: string - ``` - - The font family used in the text shape, or 'mixed' if multiple font families are used. - fontVariantId: |- - ``` - fontVariantId: string - ``` - - The font variant ID used in the text shape, or 'mixed' if multiple font variants are used. - fontSize: |- - ``` - fontSize: string - ``` - - The font size used in the text shape, or 'mixed' if multiple font sizes are used. - fontWeight: |- - ``` - fontWeight: string - ``` - - The font weight used in the text shape, or 'mixed' if multiple font weights are used. - fontStyle: |- - ``` - fontStyle: "normal" | "italic" | "mixed" | null - ``` - - The font style used in the text shape, or 'mixed' if multiple font styles are used. - lineHeight: |- - ``` - lineHeight: string - ``` - - The line height used in the text shape, or 'mixed' if multiple line heights are used. - letterSpacing: |- - ``` - letterSpacing: string - ``` - - The letter spacing used in the text shape, or 'mixed' if multiple letter spacings are used. - textTransform: |- - ``` - textTransform: "mixed" | "uppercase" | "capitalize" | "lowercase" | null - ``` - - The text transform applied to the text shape, or 'mixed' if multiple text transforms are used. - textDecoration: |- - ``` - textDecoration: "mixed" | "underline" | "line-through" | null - ``` - - The text decoration applied to the text shape, or 'mixed' if multiple text decorations are used. - direction: |- - ``` - direction: "mixed" | "ltr" | "rtl" | null - ``` - - The text direction for the text shape, or 'mixed' if multiple directions are used. - align: |- - ``` - align: "center" | "left" | "right" | "mixed" | "justify" | null - ``` - - The horizontal alignment of the text shape. It can be a specific alignment or 'mixed' if multiple alignments are used. - verticalAlign: |- - ``` - verticalAlign: "center" | "top" | "bottom" | null - ``` - - The vertical alignment of the text shape. - textBounds: |- - ``` - readonly textBounds: { x: number; y: number; width: number; height: number } - ``` - - Return the bounding box for the text as a (x, y, width, height) rectangle - This is the box that covers the text even if it overflows its selection rectangle. - Methods: - getPluginData: |- - ``` - getPluginData(key: string): string - ``` - - Retrieves the data for our own plugin, given a specific key. - - Parameters - - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the data associated with the key as a string. - - Example - ``` - const data = shape.getPluginData('exampleKey');console.log(data); - ``` - setPluginData: |- - ``` - setPluginData(key: string, value: string): void - ``` - - Sets the plugin-specific data for the given key. - - Parameters - - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setPluginData('exampleKey', 'exampleValue'); - ``` - getPluginDataKeys: |- - ``` - getPluginDataKeys(): string[] - ``` - - Retrieves all the keys for the plugin-specific data. - - Returns string[] - - Returns an array of strings representing all the keys. - - Example - ``` - const keys = shape.getPluginDataKeys();console.log(keys); - ``` - getSharedPluginData: |- - ``` - getSharedPluginData(namespace: string, key: string): string - ``` - - If we know the namespace of an external plugin, this is the way to get their data. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to retrieve the data. - - Returns string - - Returns the shared data associated with the key as a string. - - Example - ``` - const sharedData = shape.getSharedPluginData('exampleNamespace', 'exampleKey');console.log(sharedData); - ``` - setSharedPluginData: |- - ``` - setSharedPluginData(namespace: string, key: string, value: string): void - ``` - - Sets the shared plugin-specific data for the given namespace and key. - - Parameters - - * namespace: string - - The namespace for the shared data. - * key: string - - The key for which to set the data. - * value: string - - The data to set for the key. - - Returns void - - Example - ``` - shape.setSharedPluginData('exampleNamespace', 'exampleKey', 'exampleValue'); - ``` - getSharedPluginDataKeys: |- - ``` - getSharedPluginDataKeys(namespace: string): string[] - ``` - - Retrieves all the keys for the shared plugin-specific data in the given namespace. - - Parameters - - * namespace: string - - The namespace for the shared data. - - Returns string[] - - Returns an array of strings representing all the keys in the namespace. - - Example - ``` - const sharedKeys = shape.getSharedPluginDataKeys('exampleNamespace');console.log(sharedKeys); - ``` - setParentIndex: |- - ``` - setParentIndex(index: number): void - ``` - - Changes the index inside the parent of the current shape. - This method will shift the indexes of the shapes around that position to - match the index. - If the index is greater than the number of elements it will positioned last. - - Parameters - - * index: number - - the new index for the shape to be in - - Returns void - isComponentInstance: |- - ``` - isComponentInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component instance - isComponentMainInstance: |- - ``` - isComponentMainInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **main** instance - isComponentCopyInstance: |- - ``` - isComponentCopyInstance(): boolean - ``` - - Returns boolean - - Returns true if the current shape is inside a component **copy** instance - isComponentRoot: |- - ``` - isComponentRoot(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the root of a component tree - isComponentHead: |- - ``` - isComponentHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure - componentRefShape: |- - ``` - componentRefShape(): Shape | null - ``` - - Returns Shape | null - - Returns the equivalent shape in the component main instance. If the current shape is inside a - main instance will return `null`; - componentRoot: |- - ``` - componentRoot(): Shape | null - ``` - - Returns Shape | null - - Returns the root of the component tree structure for the current shape. If the current shape - is already a root will return itself. - componentHead: |- - ``` - componentHead(): Shape | null - ``` - - Returns Shape | null - - Returns the head of the component tree structure for the current shape. If the current shape - is already a head will return itself. - component: |- - ``` - component(): LibraryComponent | null - ``` - - Returns LibraryComponent | null - - If the shape is a component instance, returns the reference to the component associated - otherwise will return null - detach: |- - ``` - detach(): void - ``` - - If the current shape is a component it will remove the component information and leave the - shape as a "basic shape" - - Returns void - swapComponent: |- - ``` - swapComponent(component: LibraryComponent): void - ``` - - Swaps the component instance for another component, keeping the overrides - when possible. Similar to the "swap component" action on the Penpot interface. - The current shape must be a component copy instance. - - Parameters - - * component: LibraryComponent - - The new component to replace the current one - - Returns void - resetOverrides: |- - ``` - resetOverrides(): void - ``` - - Resets the overrides of the component copy, restoring all its attributes - (and those of its children) to the ones in the linked main component. - Similar to the "reset overrides" action on the Penpot interface. - The current shape must be a component copy instance. - - Returns void - switchVariant: |- - ``` - switchVariant(pos: number, value: string): void - ``` - - Switch a VariantComponent copy to the nearest one that has the specified property value - - Parameters - - * pos: number - - The position of the property to update - * value: string - - The new value of the property - - Returns void - combineAsVariants: |- - ``` - combineAsVariants(ids: string[]): VariantContainer - ``` - - Combine several standard Components into a VariantComponent. Similar to doing it with the contextual menu - on the Penpot interface. - The current shape must be a component main instance. - - Parameters - - * ids: string[] - - A list of ids of the main instances of the components to combine with this one. - - Returns VariantContainer - - The variant container created - isVariantHead: |- - ``` - isVariantHead(): boolean - ``` - - Returns boolean - - Returns true when the current shape is the head of a components tree nested structure, - and that component is a VariantComponent - resize: |- - ``` - resize(width: number, height: number): void - ``` - - Resizes the shape to the specified width and height. - - Parameters - - * width: number - - The new width of the shape. - * height: number - - The new height of the shape. - - Returns void - - Example - ``` - shape.resize(200, 100); - ``` - rotate: |- - ``` - rotate(angle: number, center?: { x: number; y: number } | null): void - ``` - - Rotates the shape in relation with the given center. - - Parameters - - * angle: number - - Angle in degrees to rotate. - * center: { x: number; y: number } | null - - Center of the transform rotation. If not sent it will use the geometric center of the shape. - - Returns void - - Example - ``` - shape.rotate(45); - ``` - bringToFront: |- - ``` - bringToFront(): void - ``` - - Moves the current shape to the front of its siblings - - Returns void - bringForward: |- - ``` - bringForward(): void - ``` - - Moves the current shape one position forward in its list of siblings - - Returns void - sendToBack: |- - ``` - sendToBack(): void - ``` - - Moves the current shape to the back of its siblings - - Returns void - sendBackward: |- - ``` - sendBackward(): void - ``` - - Moves the current shape one position backwards in its list of siblings - - Returns void - export: |- - ``` - export(config: Export): Promise> - ``` - - Generates an export from the current shape. - - Parameters - - * config: Export - - Returns Promise> - - Example - ``` - shape.export({ type: 'png', scale: 2 }); - ``` - addInteraction: |- - ``` - addInteraction(trigger: Trigger, action: Action, delay?: number): Interaction - ``` - - Adds a new interaction to the shape. - - If the interaction starts a flow (for example a `navigate-to` action) and - the shape's board is not already part of any flow, a new flow starting at - that board is created automatically, matching the behavior of the editor. - - Parameters - - * trigger: Trigger - - defines the conditions under which the action will be triggered - * action: Action - - defines what will be executed when the trigger happens - * delay: number - - for the type of trigger `after-delay` will specify the time after triggered. Ignored otherwise. - - Returns Interaction - - Example - ``` - shape.addInteraction('click', { type: 'navigate-to', destination: anotherBoard }); - ``` - removeInteraction: |- - ``` - removeInteraction(interaction: Interaction): void - ``` - - Removes the interaction from the shape. - - Parameters - - * interaction: Interaction - - is the interaction to remove from the shape - - Returns void - - Example - ``` - shape.removeInteraction(interaction); - ``` - applyToken: |- - ``` - applyToken(token: Token, properties?: TokenProperty[]): void - ``` - - Applies one design token to one or more properties of the shape. - - Parameters - - * token: Token - - is the Token to apply - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - clone: |- - ``` - clone(): Shape - ``` - - Creates a clone of the shape. - - Returns Shape - - Returns a new instance of the shape with identical properties. - remove: |- - ``` - remove(): void - ``` - - Removes the shape from its parent. - - Returns void - getRange: |- - ``` - getRange(start: number, end: number): TextRange - ``` - - Gets a text range within the text shape. - - Parameters - - * start: number - - The start index of the text range. - * end: number - - The end index of the text range. - - Returns TextRange - - Returns a TextRange object representing the specified text range. - - Example - ``` - const textRange = textShape.getRange(0, 10);console.log(textRange.characters); - ``` - applyTypography: |- - ``` - applyTypography(typography: LibraryTypography): void - ``` - - Applies a typography style to the text shape. - - Parameters - - * typography: LibraryTypography - - The typography style to apply. - - Returns void - - Remarks - - This method sets various typography properties for the text shape according to the given typography style. - - Example - ``` - textShape.applyTypography(typography); - ``` -TextRange: - overview: |- - Interface TextRange - =================== - - Represents a range of text within a Text shape. - This interface provides properties for styling and formatting text ranges. - - ``` - interface TextRange { - shape: Text; - characters: string; - fontId: string; - fontFamily: string; - fontVariantId: string; - fontSize: string; - fontWeight: string; - fontStyle: "normal" | "italic" | "mixed" | null; - lineHeight: string; - letterSpacing: string; - textTransform: - | "none" - | "mixed" - | "uppercase" - | "capitalize" - | "lowercase" - | null; - textDecoration: "none" - | "mixed" - | "underline" - | "line-through" - | null; - direction: "mixed" | "ltr" | "rtl" | null; - fills: Fill[] | "mixed"; - align: "center" | "left" | "right" | "mixed" | "justify" | null; - verticalAlign: "center" | "top" | "bottom" | "mixed" | null; - applyTypography(typography: LibraryTypography): void; - } - ``` - - Referenced by: Font, LibraryTypography, Text - members: - Properties: - shape: |- - ``` - readonly shape: Text - ``` - - The Text shape to which this text range belongs. - characters: |- - ``` - readonly characters: string - ``` - - The characters associated with the current text range. - fontId: |- - ``` - fontId: string - ``` - - The font ID of the text range. It can be a specific font ID or 'mixed' if multiple fonts are used. - fontFamily: |- - ``` - fontFamily: string - ``` - - The font family of the text range. It can be a specific font family or 'mixed' if multiple font families are used. - fontVariantId: |- - ``` - fontVariantId: string - ``` - - The font variant ID of the text range. It can be a specific font variant ID or 'mixed' if multiple font variants are used. - fontSize: |- - ``` - fontSize: string - ``` - - The font size of the text range. It can be a specific font size or 'mixed' if multiple font sizes are used. - fontWeight: |- - ``` - fontWeight: string - ``` - - The font weight of the text range. It can be a specific font weight or 'mixed' if multiple font weights are used. - fontStyle: |- - ``` - fontStyle: "normal" | "italic" | "mixed" | null - ``` - - The font style of the text range. It can be a specific font style or 'mixed' if multiple font styles are used. - lineHeight: |- - ``` - lineHeight: string - ``` - - The line height of the text range. It can be a specific line height or 'mixed' if multiple line heights are used. - letterSpacing: |- - ``` - letterSpacing: string - ``` - - The letter spacing of the text range. It can be a specific letter spacing or 'mixed' if multiple letter spacings are used. - textTransform: |- - ``` - textTransform: - | "none" - | "mixed" - | "uppercase" - | "capitalize" - | "lowercase" - | null - ``` - - The text transform applied to the text range. It can be a specific text transform or 'mixed' if multiple text transforms are used. - textDecoration: |- - ``` - textDecoration: "none" | "mixed" | "underline" | "line-through" | null - ``` - - The text decoration applied to the text range. It can be a specific text decoration or 'mixed' if multiple text decorations are used. - direction: |- - ``` - direction: "mixed" | "ltr" | "rtl" | null - ``` - - The text direction for the text range. It can be a specific direction or 'mixed' if multiple directions are used. - fills: |- - ``` - fills: Fill[] | "mixed" - ``` - - The fill styles applied to the text range. - align: |- - ``` - align: "center" | "left" | "right" | "mixed" | "justify" | null - ``` - - The horizontal alignment of the text range. It can be a specific alignment or 'mixed' if multiple alignments are used. - verticalAlign: |- - ``` - verticalAlign: "center" | "top" | "bottom" | "mixed" | null - ``` - - The vertical alignment of the text range. It can be a specific alignment or 'mixed' if multiple alignments are used. - Methods: - applyTypography: |- - ``` - applyTypography(typography: LibraryTypography): void - ``` - - Applies a typography style to the text range. - This method sets various typography properties for the text range according to the given typography style. - - Parameters - - * typography: LibraryTypography - - The typography style to apply. - - Returns void - - Example - ``` - textRange.applyTypography(typography); - ``` -ToggleOverlay: - overview: |- - Interface ToggleOverlay - ======================= - - It opens an overlay if it is not already opened or closes it if it is already opened. - - ``` - interface ToggleOverlay { - destination: Board; - relativeTo?: Shape; - position?: - | "center" - | "manual" - | "top-left" - | "top-right" - | "top-center" - | "bottom-left" - | "bottom-right" - | "bottom-center"; - manualPositionLocation?: Point; - closeWhenClickOutside?: boolean; - addBackgroundOverlay?: boolean; - animation?: Animation; - type: "toggle-overlay"; - } - ``` - - Hierarchy (View Summary) - - * OverlayAction - + ToggleOverlay - - Referenced by: Action - members: - Properties: - destination: |- - ``` - readonly destination: Board - ``` - - Overlay board that will be opened. - relativeTo: |- - ``` - readonly relativeTo?: Shape - ``` - - Base shape to which the overlay will be positioned taking constraints into account. - position: |- - ``` - readonly position?: - | "center" - | "manual" - | "top-left" - | "top-right" - | "top-center" - | "bottom-left" - | "bottom-right" - | "bottom-center" - ``` - - Positioning of the overlay. - manualPositionLocation: |- - ``` - readonly manualPositionLocation?: Point - ``` - - For `position = 'manual'` the location of the overlay. - closeWhenClickOutside: |- - ``` - readonly closeWhenClickOutside?: boolean - ``` - - When true the overlay will be closed when clicking outside - addBackgroundOverlay: |- - ``` - readonly addBackgroundOverlay?: boolean - ``` - - When true a background will be added to the overlay. - animation: |- - ``` - readonly animation?: Animation - ``` - - Animation displayed with this interaction. - type: |- - ``` - readonly type: "toggle-overlay" - ``` - - The action type -Track: - overview: |- - Interface Track - =============== - - Represents a track configuration in Penpot. - This interface includes properties for defining the type and value of a track used in layout configurations. - - ``` - interface Track { - type: TrackType; - value: number | null; - } - ``` - - Referenced by: GridLayout - members: - Properties: - type: |- - ``` - type: TrackType - ``` - - The type of the track. - This can be one of the following values: - - * 'flex': A flexible track type. - * 'fixed': A fixed track type. - * 'percent': A track type defined by a percentage. - * 'auto': An automatic track type. - value: |- - ``` - value: number | null - ``` - - The value of the track. - This can be a number representing the size of the track, or null if not applicable. -TokenBase: - overview: |- - Interface TokenBase - =================== - - Represents the base properties and methods of a Design Token in Penpot, shared by - all token types. - - ``` - interface TokenBase { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenBorderRadius - + TokenShadow - + TokenColor - + TokenDimension - + TokenFontFamilies - + TokenFontSizes - + TokenFontWeights - + TokenLetterSpacing - + TokenNumber - + TokenOpacity - + TokenRotation - + TokenSizing - + TokenSpacing - + TokenBorderWidth - + TokenTextCase - + TokenTextDecoration - + TokenTypography - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenBorderRadius: - overview: |- - Interface TokenBorderRadius - =========================== - - Represents a token of type BorderRadius. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenBorderRadius { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "borderRadius"; - value: string; - resolvedValue: number | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenBorderRadius - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "borderRadius" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a positive number or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: number | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a positive number, or undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenShadowValue: - overview: |- - Interface TokenShadowValue - ========================== - - The value of a TokenShadow in its composite form. - - ``` - interface TokenShadowValue { - color: string; - inset: boolean; - offsetX: number; - offsetY: number; - spread: number; - blur: number; - } - ``` - - Referenced by: TokenShadow - members: - Properties: - color: |- - ``` - color: string - ``` - - The color as a string (e.g. "#FF5733"). - inset: |- - ``` - inset: boolean - ``` - - If the shadow is inset or drop. - offsetX: |- - ``` - offsetX: number - ``` - - The horizontal offset of the shadow in pixels. - offsetY: |- - ``` - offsetY: number - ``` - - The vertical offset of the shadow in pixels. - spread: |- - ``` - spread: number - ``` - - The spread distance of the shadow in pixels. - blur: |- - ``` - blur: number - ``` - - The amount of blur to apply to the shadow. -TokenShadowValueString: - overview: |- - Interface TokenShadowValueString - ================================ - - The value of a TokenShadow in its composite of strings form. - - ``` - interface TokenShadowValueString { - color: string; - inset: string; - offsetX: string; - offsetY: string; - spread: string; - blur: string; - } - ``` - - Referenced by: TokenShadow, TokenValueString - members: - Properties: - color: |- - ``` - color: string - ``` - - The color as a string (e.g. "#FF5733"), or a reference - to a color token. - inset: |- - ``` - inset: string - ``` - - If the shadow is inset or drop, or a reference of a - boolean token. - offsetX: |- - ``` - offsetX: string - ``` - - The horizontal offset of the shadow in pixels, or a reference - to a number token. - offsetY: |- - ``` - offsetY: string - ``` - - The vertical offset of the shadow in pixels, or a reference - to a number token. - spread: |- - ``` - spread: string - ``` - - The spread distance of the shadow in pixels, or a reference - to a number token. - blur: |- - ``` - blur: string - ``` - - The amount of blur to apply to the shadow, or a reference - to a number token. -TokenShadow: - overview: |- - Interface TokenShadow - ===================== - - Represents a token of type Shadow. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenShadow { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "shadow"; - value: string | TokenShadowValueString[]; - resolvedValue: TokenShadowValue[] | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenShadow - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "shadow" - ``` - - The type of the token. - value: |- - ``` - value: string | TokenShadowValueString[] - ``` - - The value as defined in the token itself. - It may be a string with a reference to other token, or else - an array of TokenShadowValueString. - resolvedValue: |- - ``` - readonly resolvedValue: TokenShadowValue[] | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's an array of TokenShadowValue, or undefined if no value has been found - in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenColor: - overview: |- - Interface TokenColor - ==================== - - Represents a token of type Color. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenColor { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "color"; - value: string; - resolvedValue: string | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenColor - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "color" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a rgb color or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a rgb color, or undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenDimension: - overview: |- - Interface TokenDimension - ======================== - - Represents a token of type Dimension. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenDimension { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "dimension"; - value: string; - resolvedValue: number | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenDimension - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "dimension" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a positive number or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: number | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a positive number, or undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenFontFamilies: - overview: |- - Interface TokenFontFamilies - =========================== - - Represents a token of type FontFamilies. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenFontFamilies { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "fontFamilies"; - value: string | string[]; - resolvedValue: string[] | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenFontFamilies - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "fontFamilies" - ``` - - The type of the token. - value: |- - ``` - value: string | string[] - ``` - - The value as defined in the token itself. - It may be a string with a reference to other token, or else - an array of strings with one or more font families (each family - is an item in the array). - resolvedValue: |- - ``` - readonly resolvedValue: string[] | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's an array of strings with one or more font families, - or undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenFontSizes: - overview: |- - Interface TokenFontSizes - ======================== - - Represents a token of type FontSizes. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenFontSizes { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "fontSizes"; - value: string; - resolvedValue: number | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenFontSizes - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "fontSizes" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a positive number or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: number | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a positive number, or undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenFontWeights: - overview: |- - Interface TokenFontWeights - ========================== - - Represents a token of type FontWeights. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenFontWeights { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "fontWeights"; - value: string; - resolvedValue: string | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenFontWeights - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "fontWeights" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a weight string or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a weight string ("bold", "strong", etc.), or undefined if no value has - been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenLetterSpacing: - overview: |- - Interface TokenLetterSpacing - ============================ - - Represents a token of type LetterSpacing. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenLetterSpacing { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "letterSpacing"; - value: string; - resolvedValue: number | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenLetterSpacing - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "letterSpacing" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a number or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: number | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a number, or undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenNumber: - overview: |- - Interface TokenNumber - ===================== - - Represents a token of type Number. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenNumber { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "number"; - value: string; - resolvedValue: number | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenNumber - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "number" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a number or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: number | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a number, or undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenOpacity: - overview: |- - Interface TokenOpacity - ====================== - - Represents a token of type Opacity. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenOpacity { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "opacity"; - value: string; - resolvedValue: number | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenOpacity - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "opacity" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a number between 0 and 1 or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: number | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a number between 0 and 1, or undefined if no value has been found - in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenRotation: - overview: |- - Interface TokenRotation - ======================= - - Represents a token of type Rotation. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenRotation { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "rotation"; - value: string; - resolvedValue: number | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenRotation - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "rotation" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a number in degrees or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: number | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a number in degrees, or undefined if no value has been found - in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenSizing: - overview: |- - Interface TokenSizing - ===================== - - Represents a token of type Sizing. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenSizing { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "sizing"; - value: string; - resolvedValue: number | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenSizing - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "sizing" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a number or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: number | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a number, or undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenSpacing: - overview: |- - Interface TokenSpacing - ====================== - - Represents a token of type Spacing. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenSpacing { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "spacing"; - value: string; - resolvedValue: number | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenSpacing - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "spacing" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a number or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: number | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a number, or undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenBorderWidth: - overview: |- - Interface TokenBorderWidth - ========================== - - Represents a token of type BorderWidth. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenBorderWidth { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "borderWidth"; - value: string; - resolvedValue: number | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenBorderWidth - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "borderWidth" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a positive number or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: number | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a positive number, or undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenTextCase: - overview: |- - Interface TokenTextCase - ======================= - - Represents a token of type TextCase. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenTextCase { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "textCase"; - value: string; - resolvedValue: string | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenTextCase - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "textCase" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a case string or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a case string ("none", "uppercase", "lowercase", "capitalize"), or - undefined if no value has been found in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenTextDecoration: - overview: |- - Interface TokenTextDecoration - ============================= - - Represents a token of type Decoration. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenTextDecoration { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "textDecoration"; - value: string; - resolvedValue: string | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenTextDecoration - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "textDecoration" - ``` - - The type of the token. - value: |- - ``` - value: string - ``` - - The value as defined in the token itself. - It's a decoration string or a reference. - resolvedValue: |- - ``` - readonly resolvedValue: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a decoration string, or undefined if no value has been found - in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenTypographyValue: - overview: |- - Interface TokenTypographyValue - ============================== - - The value of a TokenTypography in its composite form. - - ``` - interface TokenTypographyValue { - letterSpacing: number; - fontFamilies: string[]; - fontSizes: number; - fontWeights: string; - lineHeight: number; - textCase: string; - textDecoration: string; - } - ``` - - Referenced by: TokenTypography - members: - Properties: - letterSpacing: |- - ``` - letterSpacing: number - ``` - - The letter spacing, as a number. - fontFamilies: |- - ``` - fontFamilies: string[] - ``` - - The list of font families. - fontSizes: |- - ``` - fontSizes: number - ``` - - The font size, as a positive number. - fontWeights: |- - ``` - fontWeights: string - ``` - - The font weight, as a weight string ("bold", "strong", etc.). - lineHeight: |- - ``` - lineHeight: number - ``` - - The line height, as a number. - textCase: |- - ``` - textCase: string - ``` - - The text case as a string ("none", "uppercase", "lowercase" "capitalize"). - textDecoration: |- - ``` - textDecoration: string - ``` - - The text decoration as a string ("none", "underline", "strike-through"). -TokenTypographyValueString: - overview: |- - Interface TokenTypographyValueString - ==================================== - - The value of a TokenTypography in its composite of strings form. - - ``` - interface TokenTypographyValueString { - letterSpacing: string; - fontFamilies: string | string[]; - fontSizes: string; - fontWeight: string; - lineHeight: string; - textCase: string; - textDecoration: string; - } - ``` - - Referenced by: TokenTypography, TokenValueString - members: - Properties: - letterSpacing: |- - ``` - letterSpacing: string - ``` - - The letter spacing, as a number, or a reference to a TokenLetterSpacing. - fontFamilies: |- - ``` - fontFamilies: string | string[] - ``` - - The list of font families, or a reference to a TokenFontFamilies. - fontSizes: |- - ``` - fontSizes: string - ``` - - The font size, as a positive number, or a reference to a TokenFontSizes. - fontWeight: |- - ``` - fontWeight: string - ``` - - The font weight, as a weight string ("bold", "strong", etc.), or a - reference to a TokenFontWeights. - lineHeight: |- - ``` - lineHeight: string - ``` - - The line height, as a number. Note that there is no individual line-height - token type; it only exists as part of a Typography token. If you need to - put a reference here, use a Number token. - textCase: |- - ``` - textCase: string - ``` - - The text case as a string ("none", "uppercase", "lowercase" "capitalize"), - or a reference to a TokenTextCase. - textDecoration: |- - ``` - textDecoration: string - ``` - - The text decoration as a string ("none", "underline", "strike-through"), - or a reference to a TokenTextDecoration. -TokenTypography: - overview: |- - Interface TokenTypography - ========================= - - Represents a token of type Typography. - This interface extends `TokenBase` and specifies the data type of the value. - - ``` - interface TokenTypography { - id: string; - name: string; - description: string; - duplicate(): Token; - remove(): void; - resolvedValueString: string | undefined; - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void; - applyToSelected(properties?: TokenProperty[]): void; - type: "typography"; - value: string | TokenTypographyValueString; - resolvedValue: TokenTypographyValue[] | undefined; - } - ``` - - Hierarchy (View Summary) - - * TokenBase - + TokenTypography - - Referenced by: Token - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this token, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the token. It may include a group path separated by `.`. - description: |- - ``` - description: string - ``` - - An optional description text. - resolvedValueString: |- - ``` - readonly resolvedValueString: string | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's converted to string, regardless of the data type of the value depending - on the token type. It can be undefined if no value has been found in active sets. - type: |- - ``` - readonly type: "typography" - ``` - - The type of the token. - value: |- - ``` - value: string | TokenTypographyValueString - ``` - - The value as defined in the token itself. - It may be a string with a reference to other token, or a - TokenTypographyValueString. - resolvedValue: |- - ``` - readonly resolvedValue: TokenTypographyValue[] | undefined - ``` - - The value calculated by finding all tokens with the same name in active sets - and resolving the references. - - It's a TokenTypographyValue, or undefined if no value has been found - in active sets. - Methods: - duplicate: |- - ``` - duplicate(): Token - ``` - - Adds to the set that contains this Token a new one equal to this one - but with a new id. - - Returns Token - remove: |- - ``` - remove(): void - ``` - - Removes this token from the catalog. - - It will NOT be unapplied from any shape, since there may be other tokens - with the same name. - - Returns void - applyToShapes: |- - ``` - applyToShapes(shapes: Shape[], properties?: TokenProperty[]): void - ``` - - Applies this token to one or more properties of the given shapes. - - Parameters - - * shapes: Shape[] - - is an array of shapes to apply it. - * properties: TokenProperty[] - - an optional list of property names. If omitted, the - default properties will be applied. - - NOTE that the tokens application is by name and not by id. If there exist - several tokens with the same name in different sets, the actual token applied - and the value set to the attributes will depend on which sets are active - (and will change if different sets or themes are activated later). - - Returns void - applyToSelected: |- - ``` - applyToSelected(properties?: TokenProperty[]): void - ``` - - Applies this token to the currently selected shapes. - - Parameters and warnings are the same as above. - - Parameters - - * properties: TokenProperty[] - - Returns void -TokenCatalog: - overview: |- - Interface TokenCatalog - ====================== - - The collection of all tokens in a Penpot file's library. - - Tokens are contained in sets, that can be marked as active - or inactive to control the resolved value of the tokens. - - The active status of sets can be handled by presets named - Themes. - - ``` - interface TokenCatalog { - themes: TokenTheme[]; - sets: TokenSet[]; - addTheme(group: { group: string; name: string }): TokenTheme; - addSet(name: { name: string; active?: boolean }): TokenSet; - getThemeById(id: string): TokenTheme | undefined; - getSetById(id: string): TokenSet | undefined; - } - ``` - - Referenced by: Library - members: - Properties: - themes: |- - ``` - readonly themes: TokenTheme[] - ``` - - The list of themes in this catalog, in creation order. - sets: |- - ``` - readonly sets: TokenSet[] - ``` - - The list of sets in this catalog, in the order defined - by the user. The order is important because when the same token name - exists in several active sets, the latter has precedence. - Methods: - addTheme: |- - ``` - addTheme(group: { group: string; name: string }): TokenTheme - ``` - - Creates a new TokenTheme and adds it to the catalog. - - Parameters - - * group: { group: string; name: string } - - The group name of the theme (can be empty string). - - Returns TokenTheme - - Returns the created TokenTheme. - addSet: |- - ``` - addSet(name: { name: string; active?: boolean }): TokenSet - ``` - - Creates a new TokenSet and adds it to the catalog. - - Newly created sets are **inactive** by default: only active sets - affect shapes and reference resolution. Pass `active: true` to create - an already-active set, or activate it later via `set.active = true` / - `set.toggleActive()`. - - Parameters - - * name: { name: string; active?: boolean } - - The name of the set (required). It may contain - a group path, separated by `/`. - - Returns TokenSet - - Returns the created TokenSet. - getThemeById: |- - ``` - getThemeById(id: string): TokenTheme | undefined - ``` - - Retrieves a theme. - - Parameters - - * id: string - - the id of the theme. - - Returns TokenTheme | undefined - - Returns the theme or undefined if not found. - getSetById: |- - ``` - getSetById(id: string): TokenSet | undefined - ``` - - Retrieves a set. - - Parameters - - * id: string - - the id of the set. - - Returns TokenSet | undefined - - Returns the set or undefined if not found. -TokenSet: - overview: |- - Interface TokenSet - ================== - - A collection of Design Tokens. - - Inside a set, tokens have an unique name, that will designate - what token to use if the name is applied to a shape and this - set is active. - - ``` - interface TokenSet { - id: string; - name: string; - active: boolean; - tokens: Token[]; - tokensByType: [string, Token[]][]; - toggleActive(): void; - getTokenById(id: string): Token | undefined; - addToken( - type: { type: TokenType; name: string; value: TokenValueString }, - ): Token; - duplicate(): TokenSet; - remove(): void; - } - ``` - - Referenced by: TokenCatalog, TokenSet, TokenTheme - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this set, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - name: |- - ``` - name: string - ``` - - The name of the set. It may include a group path separated by `/`. - active: |- - ``` - active: boolean - ``` - - Indicates if the set is currently active. - tokens: |- - ``` - readonly tokens: Token[] - ``` - - The tokens contained in this set, in alphabetical order. - tokensByType: |- - ``` - readonly tokensByType: [string, Token[]][] - ``` - - The tokens contained in this set, grouped by type. - Methods: - toggleActive: |- - ``` - toggleActive(): void - ``` - - Toggles the active status of this set. - - Returns void - getTokenById: |- - ``` - getTokenById(id: string): Token | undefined - ``` - - Retrieves a token. - - Parameters - - * id: string - - the id of the token. - - Returns Token | undefined - - Returns the token or undefined if not found. - addToken: |- - ``` - addToken( - type: { type: TokenType; name: string; value: TokenValueString }, - ): Token - ``` - - Creates a new Token and adds it to the set. - - Parameters - - * type: { type: TokenType; name: string; value: TokenValueString } - - The type of the token. - - Returns Token - - Returns the created Token. - duplicate: |- - ``` - duplicate(): TokenSet - ``` - - Adds to the catalog a new TokenSet equal to this one but with a new id. - - Returns TokenSet - remove: |- - ``` - remove(): void - ``` - - Removes this set from the catalog. - - Returns void -TokenTheme: - overview: |- - Interface TokenTheme - ==================== - - A preset of active TokenSets. - - A theme contains a list of references to TokenSets. When the theme - is activated, it sets are activated too. This will not deactivate - sets that are *not* in this theme, because they may have been - activated by other themes. - - Themes may be grouped. At any time only one of the themes in a group - may be active. But there may be active themes in other groups. This - allows defining multiple "axis" for theming (e.g. color scheme, - density or brand). - - When a TokenSet is activated or deactivated directly, all themes - are disabled (indicating that now there is a "custom" manual theme - active). - - ``` - interface TokenTheme { - id: string; - externalId: string | undefined; - group: string; - name: string; - active: boolean; - toggleActive(): void; - activeSets: TokenSet[]; - addSet(tokenSet: string | TokenSet): void; - removeSet(tokenSet: string | TokenSet): void; - duplicate(): TokenTheme; - remove(): void; - } - ``` - - Referenced by: TokenCatalog, TokenTheme - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier for this theme, used only internally inside Penpot. - This one is not exported or synced with external Design Token sources. - externalId: |- - ``` - readonly externalId: string | undefined - ``` - - Optional identifier that may exist if the theme was imported from an - external tool that uses ids in the json file. - group: |- - ``` - group: string - ``` - - The group name of the theme. Can be an empty string. - name: |- - ``` - name: string - ``` - - The name of the theme. - active: |- - ``` - active: boolean - ``` - - Indicates if the theme is currently active. - activeSets: |- - ``` - readonly activeSets: TokenSet[] - ``` - - The sets that will be activated if this theme is activated. - Methods: - toggleActive: |- - ``` - toggleActive(): void - ``` - - Toggles the active status of this theme. - - Returns void - addSet: |- - ``` - addSet(tokenSet: string | TokenSet): void - ``` - - Adds a set to the list of the theme. - - Parameters - - * tokenSet: string | TokenSet - - a `TokenSet` or the id of a token set. - - Returns void - removeSet: |- - ``` - removeSet(tokenSet: string | TokenSet): void - ``` - - Removes a set from the list of the theme. - - Parameters - - * tokenSet: string | TokenSet - - a `TokenSet` or the id of a token set. - - Returns void - duplicate: |- - ``` - duplicate(): TokenTheme - ``` - - Adds to the catalog a new TokenTheme equal to this one but with a new id. - - Returns TokenTheme - remove: |- - ``` - remove(): void - ``` - - Removes this theme from the catalog. - - Returns void -User: - overview: |- - Interface User - ============== - - Represents a user in Penpot. - - ``` - interface User { - id: string; - name?: string; - avatarUrl?: string; - color: string; - sessionId?: string; - } - ``` - - Hierarchy (View Summary) - - * User - + ActiveUser - - Referenced by: Comment, CommentThread, Context, File, FileVersion, Penpot - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the user. - - Example - ``` - const userId = user.id;console.log(userId); - ``` - name: |- - ``` - readonly name?: string - ``` - - The name of the user. - - Example - ``` - const userName = user.name;console.log(userName); - ``` - avatarUrl: |- - ``` - readonly avatarUrl?: string - ``` - - The URL of the user's avatar image. - - Example - ``` - const avatarUrl = user.avatarUrl;console.log(avatarUrl); - ``` - color: |- - ``` - readonly color: string - ``` - - The color associated with the user. - - Example - ``` - const userColor = user.color;console.log(userColor); - ``` - sessionId: |- - ``` - readonly sessionId?: string - ``` - - The session ID of the user. - - Example - ``` - const sessionId = user.sessionId;console.log(sessionId); - ``` -Variants: - overview: |- - Interface Variants - ================== - - Represents a Variant in Penpot: the grouping of all the VariantComponents that - belong to the same VariantContainer, together with their shared properties. - This interface provides attributes and actions that affect the Variant as a - whole (not a single VariantComponent). - - ``` - interface Variants { - id: string; - libraryId: string; - properties: string[]; - currentValues(property: string): string[]; - removeProperty(pos: number): void; - renameProperty(pos: number, name: string): void; - variantComponents(): LibraryComponent[]; - addVariant(): void; - addProperty(): void; - } - ``` - - Referenced by: LibraryVariantComponent, VariantContainer - members: - Properties: - id: |- - ``` - readonly id: string - ``` - - The unique identifier of the variant element. It is the id of the VariantContainer, and all the VariantComponents - that belong to this variant have an attribute variantId which this is as value. - libraryId: |- - ``` - readonly libraryId: string - ``` - - The unique identifier of the library to which the variant belongs. - properties: |- - ``` - readonly properties: string[] - ``` - - A list with the names of the properties of the Variant - Methods: - currentValues: |- - ``` - currentValues(property: string): string[] - ``` - - A list of all the values of a property across all the VariantComponents of this Variant - - Parameters - - * property: string - - The name of the property - - Returns string[] - removeProperty: |- - ``` - removeProperty(pos: number): void - ``` - - Remove a property of the Variant - - Parameters - - * pos: number - - The position of the property to remove - - Returns void - renameProperty: |- - ``` - renameProperty(pos: number, name: string): void - ``` - - Rename a property of the Variant - - Parameters - - * pos: number - - The position of the property to rename - * name: string - - The new name of the property - - Returns void - variantComponents: |- - ``` - variantComponents(): LibraryComponent[] - ``` - - List all the VariantComponents on this Variant. - - Returns LibraryComponent[] - addVariant: |- - ``` - addVariant(): void - ``` - - Creates a duplicate of the main VariantComponent of this Variant - - Returns void - addProperty: |- - ``` - addProperty(): void - ``` - - Adds a new property to this Variant - - Returns void -Viewport: - overview: |- - Interface Viewport - ================== - - Viewport represents the viewport in the Penpot application. - It includes the center point, zoom level, and the bounds of the viewport. - - ``` - interface Viewport { - center: Point; - zoom: number; - bounds: Bounds; - zoomReset(): void; - zoomToFitAll(): void; - zoomIntoView(shapes: Shape[]): void; - } - ``` - - Referenced by: Context, Penpot - members: - Properties: - center: |- - ``` - center: Point - ``` - - the `center` point of the current viewport. If changed will change the - viewport position. - zoom: |- - ``` - zoom: number - ``` - - the `zoom` level as a number where `1` represents 100%. - bounds: |- - ``` - readonly bounds: Bounds - ``` - - the `bounds` are the current coordinates of the viewport. - Methods: - zoomReset: |- - ``` - zoomReset(): void - ``` - - Resets the zoom level. - - Returns void - zoomToFitAll: |- - ``` - zoomToFitAll(): void - ``` - - Changes the viewport and zoom so can fit all the current shapes in the page. - - Returns void - zoomIntoView: |- - ``` - zoomIntoView(shapes: Shape[]): void - ``` - - Changes the viewport and zoom so all the `shapes` in the argument are - visible. - - Parameters - - * shapes: Shape[] - - Returns void -Action: - overview: |- - Type Alias Action - ================= - - ``` - Action: - | NavigateTo - | OpenOverlay - | ToggleOverlay - | CloseOverlay - | PreviousScreen - | OpenUrl - ``` - - Type for all the possible types of actions in an interaction. - - Referenced by: Board, Boolean, Ellipse, Group, Image, Interaction, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer - members: {} -Animation: - overview: |- - Type Alias Animation - ==================== - - ``` - Animation: Dissolve | Slide | Push - ``` - - Type of all the animations that can be added to an interaction. - - Referenced by: CloseOverlay, NavigateTo, OpenOverlay, OverlayAction, ToggleOverlay - members: {} -BooleanType: - overview: |- - Type Alias BooleanType - ====================== - - ``` - BooleanType: "union" | "difference" | "exclude" | "intersection" - ``` - - Represents the boolean operation types available in Penpot. - These types define how shapes can be combined or modified using boolean operations. - - Referenced by: Context, Penpot - members: {} -Bounds: - overview: |- - Type Alias Bounds - ================= - - Bounds represents the boundaries of a rectangular area, - defined by the coordinates of the top-left corner and the dimensions of the rectangle. - - Example - ``` - const bounds = { x: 50, y: 50, width: 200, height: 100 };console.log(bounds); - ``` - - ``` - type Bounds = { - x: number; - y: number; - width: number; - height: number; - } - ``` - - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer, Viewport - members: - Properties: - x: |- - ``` - readonly x: number - ``` - - Top-left x position of the rectangular area defined - y: |- - ``` - readonly y: number - ``` - - Top-left y position of the rectangular area defined - width: |- - ``` - readonly width: number - ``` - - Width of the represented area - height: |- - ``` - readonly height: number - ``` - - Height of the represented area -Gradient: - overview: |- - Type Alias Gradient - =================== - - Represents a gradient configuration in Penpot. - A gradient can be either linear or radial and includes properties to define its shape, position, and color stops. - - ``` - type Gradient = { - type: "linear" | "radial"; - startX: number; - startY: number; - endX: number; - endY: number; - width: number; - stops: { color: string; opacity?: number; offset: number }[]; - } - ``` - - Referenced by: Color, Fill, LibraryColor, Stroke - members: - Properties: - type: |- - ``` - type: "linear" | "radial" - ``` - - Specifies the type of gradient. - - * 'linear': A gradient that transitions colors along a straight line. - * 'radial': A gradient that transitions colors radiating outward from a central point. - - Example - ``` - const gradient: Gradient = { type: 'linear', startX: 0, startY: 0, endX: 100, endY: 100, width: 100, stops: [{ color: '#FF5733', offset: 0 }] }; - ``` - startX: |- - ``` - startX: number - ``` - - The X-coordinate of the starting point of the gradient. - startY: |- - ``` - startY: number - ``` - - The Y-coordinate of the starting point of the gradient. - endX: |- - ``` - endX: number - ``` - - The X-coordinate of the ending point of the gradient. - endY: |- - ``` - endY: number - ``` - - The Y-coordinate of the ending point of the gradient. - width: |- - ``` - width: number - ``` - - The width of the gradient. For radial gradients, this could be interpreted as the radius. - stops: |- - ``` - stops: { color: string; opacity?: number; offset: number }[] - ``` - - An array of color stops that define the gradient. -Guide: - overview: |- - Type Alias Guide - ================ - - ``` - Guide: GuideColumn | GuideRow | GuideSquare - ``` - - Represents a board guide in Penpot. - This type can be one of several specific board guide types: column, row, or square. - - Referenced by: Board, VariantContainer - members: {} -ImageData: - overview: |- - Type Alias ImageData - ==================== - - Represents image data in Penpot. - This includes properties for defining the image's dimensions, metadata, and aspect ratio handling. - - ``` - type ImageData = { - name?: string; - width: number; - height: number; - mtype?: string; - id: string; - keepAspectRatio?: boolean; - data(): Promise>; - } - ``` - - Referenced by: Color, Context, Fill, LibraryColor, Penpot - members: - Properties: - name: |- - ``` - readonly name?: string - ``` - - The optional name of the image. - width: |- - ``` - readonly width: number - ``` - - The width of the image. - height: |- - ``` - readonly height: number - ``` - - The height of the image. - mtype: |- - ``` - readonly mtype?: string - ``` - - The optional media type of the image (e.g., 'image/png', 'image/jpeg'). - id: |- - ``` - readonly id: string - ``` - - The unique identifier for the image. - keepAspectRatio: |- - ``` - readonly keepAspectRatio?: boolean - ``` - - Whether to keep the aspect ratio of the image when resizing. - Defaults to false if omitted. - Methods: - data: |- - ``` - data(): Promise> - ``` - - Returns the image data as a byte array. - - Returns Promise> -LibraryContext: - overview: |- - Type Alias LibraryContext - ========================= - - Represents the context of Penpot libraries, including both local and connected libraries. - This type contains references to the local library and an array of connected libraries. - - ``` - type LibraryContext = { - local: Library; - connected: Library[]; - availableLibraries(): Promise; - connectLibrary(libraryId: string): Promise; - } - ``` - - Referenced by: Context, Penpot - members: - Properties: - local: |- - ``` - readonly local: Library - ``` - - The local library in the Penpot context. - - Example - ``` - const localLibrary = libraryContext.local; - ``` - connected: |- - ``` - readonly connected: Library[] - ``` - - An array of connected libraries in the Penpot context. - - Example - ``` - const connectedLibraries = libraryContext.connected; - ``` - Methods: - availableLibraries: |- - ``` - availableLibraries(): Promise - ``` - - Retrieves a summary of available libraries that can be connected to. - - Returns Promise - - Returns a promise that resolves to an array of `LibrarySummary` objects representing available libraries. - - Example - ``` - const availableLibraries = await libraryContext.availableLibraries(); - ``` - connectLibrary: |- - ``` - connectLibrary(libraryId: string): Promise - ``` - - Connects to a specific library identified by its ID. - - Parameters - - * libraryId: string - - The ID of the library to connect to. - - Returns Promise - - Returns a promise that resolves to the `Library` object representing the connected library. - - Example - ``` - const connectedLibrary = await libraryContext.connectLibrary('library-id'); - ``` -Point: - overview: |- - Type Alias Point - ================ - - Point represents a point in 2D space, typically with x and y coordinates. - - ``` - type Point = { - x: number; - y: number; - } - ``` - - Referenced by: Board, Boolean, CommentThread, Ellipse, Group, Image, OpenOverlay, OverlayAction, Page, Path, Rectangle, ShapeBase, SvgRaw, Text, ToggleOverlay, VariantContainer, Viewport - members: - Properties: - x: |- - ``` - readonly x: number - ``` - y: |- - ``` - readonly y: number - ``` -RulerGuideOrientation: - overview: |- - Type Alias RulerGuideOrientation - ================================ - - ``` - RulerGuideOrientation: "horizontal" | "vertical" - ``` - - The possible orientations for a ruler guide: 'horizontal' or 'vertical'. - - Referenced by: Board, Page, RulerGuide, VariantContainer - members: {} -Shape: - overview: |- - Type Alias Shape - ================ - - ``` - Shape: - | Board - | Group - | Boolean - | Rectangle - | Path - | Text - | Ellipse - | SvgRaw - | Image - ``` - - Shape represents a union of various shape types used in the Penpot project. - This type allows for different shapes to be handled under a single type umbrella. - - Example - ``` - let shape: Shape;if (penpot.utils.types.isRectangle(shape)) { console.log(shape.type);} - ``` - - Referenced by: Board, Boolean, Context, ContextGeometryUtils, ContextTypesUtils, Ellipse, EventsMap, FlexLayout, GridLayout, Group, Image, Interaction, Library, LibraryComponent, LibraryTypography, LibraryVariantComponent, OpenOverlay, OverlayAction, Page, Path, Penpot, Rectangle, ShapeBase, SvgRaw, Text, ToggleOverlay, TokenBase, TokenBorderRadius, TokenBorderWidth, TokenColor, TokenDimension, TokenFontFamilies, TokenFontSizes, TokenFontWeights, TokenLetterSpacing, TokenNumber, TokenOpacity, TokenRotation, TokenShadow, TokenSizing, TokenSpacing, TokenTextCase, TokenTextDecoration, TokenTypography, VariantContainer, Viewport - members: {} -StrokeCap: - overview: |- - Type Alias StrokeCap - ==================== - - ``` - StrokeCap: - | "round" - | "square" - | "line-arrow" - | "triangle-arrow" - | "square-marker" - | "circle-marker" - | "diamond-marker" - ``` - - Represents the cap style of a stroke in Penpot. - This type defines various styles for the ends of a stroke. - - Referenced by: Stroke - members: {} -Theme: - overview: |- - Type Alias Theme - ================ - - ``` - Theme: "light" | "dark" - ``` - - This type specifies the possible themes: 'light' or 'dark'. - - Referenced by: Context, EventsMap, Penpot - members: {} -TrackType: - overview: |- - Type Alias TrackType - ==================== - - ``` - TrackType: "flex" | "fixed" | "percent" | "auto" - ``` - - Represents the type of track in Penpot. - This type defines various track types that can be used in layout configurations. - - Referenced by: GridLayout, Track - members: {} -Trigger: - overview: |- - Type Alias Trigger - ================== - - ``` - Trigger: "click" | "mouse-enter" | "mouse-leave" | "after-delay" - ``` - - Types of triggers defined: - - * `click` triggers when the user uses the mouse to click on a shape - * `mouse-enter` triggers when the user moves the mouse inside the shape (even if no mouse button is pressed) - * `mouse-leave` triggers when the user moves the mouse outside the shape. - * `after-delay` triggers after the `delay` time has passed even if no interaction from the user happens. - - Referenced by: Board, Boolean, Ellipse, Group, Image, Interaction, Path, Rectangle, ShapeBase, SvgRaw, Text, VariantContainer - members: {} -TokenValueString: - overview: |- - Type Alias TokenValueString - =========================== - - ``` - TokenValueString: - | TokenShadowValueString - | TokenTypographyValueString - | string - | string[] - | number - ``` - - Any possible type of value field in a token. - - Token values are always stored as strings, including for numeric token - types such as `spacing`, `dimension` or `borderRadius` (e.g. `"16"` or - `"16px"`). A plain `number` is also accepted on input and coerced to its - string representation. - - Referenced by: TokenSet - members: {} -Token: - overview: |- - Type Alias Token - ================ - - ``` - Token: - | TokenBorderRadius - | TokenShadow - | TokenColor - | TokenDimension - | TokenFontFamilies - | TokenFontSizes - | TokenFontWeights - | TokenLetterSpacing - | TokenNumber - | TokenOpacity - | TokenRotation - | TokenSizing - | TokenSpacing - | TokenBorderWidth - | TokenTextCase - | TokenTextDecoration - | TokenTypography - ``` - - The supported Design Tokens in Penpot. - - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, TokenBase, TokenBorderRadius, TokenBorderWidth, TokenColor, TokenDimension, TokenFontFamilies, TokenFontSizes, TokenFontWeights, TokenLetterSpacing, TokenNumber, TokenOpacity, TokenRotation, TokenSet, TokenShadow, TokenSizing, TokenSpacing, TokenTextCase, TokenTextDecoration, TokenTypography, VariantContainer - members: {} -TokenBorderRadiusProps: - overview: |- - Type Alias TokenBorderRadiusProps - ================================= - - ``` - TokenBorderRadiusProps: - | "borderRadiusTopLeft" - | "borderRadiusTopRight" - | "borderRadiusBottomRight" - | "borderRadiusBottomLeft" - ``` - - The properties that a BorderRadius token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenShadowProps: - overview: |- - Type Alias TokenShadowProps - =========================== - - ``` - TokenShadowProps: "shadow" - ``` - - The properties that a Shadow token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenColorProps: - overview: |- - Type Alias TokenColorProps - ========================== - - ``` - TokenColorProps: "fill" | "strokeColor" - ``` - - The properties that a Color token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenDimensionProps: - overview: |- - Type Alias TokenDimensionProps - ============================== - - ``` - TokenDimensionProps: "x" | "y" | "strokeWidth" - ``` - - The properties that a Dimension token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenFontFamiliesProps: - overview: |- - Type Alias TokenFontFamiliesProps - ================================= - - ``` - TokenFontFamiliesProps: "fontFamilies" - ``` - - The properties that a FontFamilies token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenFontSizesProps: - overview: |- - Type Alias TokenFontSizesProps - ============================== - - ``` - TokenFontSizesProps: "fontSize" - ``` - - The properties that a FontSizes token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenFontWeightProps: - overview: |- - Type Alias TokenFontWeightProps - =============================== - - ``` - TokenFontWeightProps: "fontWeight" - ``` - - The properties that a FontWeight token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenLetterSpacingProps: - overview: |- - Type Alias TokenLetterSpacingProps - ================================== - - ``` - TokenLetterSpacingProps: "letterSpacing" - ``` - - The properties that a LetterSpacing token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenNumberProps: - overview: |- - Type Alias TokenNumberProps - =========================== - - ``` - TokenNumberProps: "rotation" - ``` - - The properties that a Number token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenOpacityProps: - overview: |- - Type Alias TokenOpacityProps - ============================ - - ``` - TokenOpacityProps: "opacity" - ``` - - The properties that an Opacity token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenSizingProps: - overview: |- - Type Alias TokenSizingProps - =========================== - - ``` - TokenSizingProps: - | "width" - | "height" - | "layoutItemMinW" - | "layoutItemMaxW" - | "layoutItemMinH" - | "layoutItemMaxH" - ``` - - The properties that a Sizing token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenSpacingProps: - overview: |- - Type Alias TokenSpacingProps - ============================ - - ``` - TokenSpacingProps: - | "rowGap" - | "columnGap" - | "paddingLeft" - | "paddingTop" - | "paddingRight" - | "paddingBottom" - | "marginLeft" - | "marginTop" - | "marginRight" - | "marginBottom" - ``` - - The properties that a Spacing token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenBorderWidthProps: - overview: |- - Type Alias TokenBorderWidthProps - ================================ - - ``` - TokenBorderWidthProps: "strokeWidth" - ``` - - The properties that a BorderWidth token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenTextCaseProps: - overview: |- - Type Alias TokenTextCaseProps - ============================= - - ``` - TokenTextCaseProps: "textCase" - ``` - - The properties that a TextCase token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenTextDecorationProps: - overview: |- - Type Alias TokenTextDecorationProps - =================================== - - ``` - TokenTextDecorationProps: "textDecoration" - ``` - - The properties that a TextDecoration token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenTypographyProps: - overview: |- - Type Alias TokenTypographyProps - =============================== - - ``` - TokenTypographyProps: "typography" - ``` - - The properties that a Typography token can be applied to. - - Referenced by: TokenProperty - members: {} -TokenProperty: - overview: |- - Type Alias TokenProperty - ======================== - - ``` - TokenProperty: - | "all" - | TokenBorderRadiusProps - | TokenShadowProps - | TokenColorProps - | TokenDimensionProps - | TokenFontFamiliesProps - | TokenFontSizesProps - | TokenFontWeightProps - | TokenLetterSpacingProps - | TokenNumberProps - | TokenOpacityProps - | TokenSizingProps - | TokenSpacingProps - | TokenBorderWidthProps - | TokenTextCaseProps - | TokenTextDecorationProps - | TokenTypographyProps - ``` - - All the properties that a token can be applied to. - Not always correspond to Shape properties. For example, - `fill` property applies to `fillColor` of the first fill - of the shape. - - Referenced by: Board, Boolean, Ellipse, Group, Image, Path, Rectangle, ShapeBase, SvgRaw, Text, TokenBase, TokenBorderRadius, TokenBorderWidth, TokenColor, TokenDimension, TokenFontFamilies, TokenFontSizes, TokenFontWeights, TokenLetterSpacing, TokenNumber, TokenOpacity, TokenRotation, TokenShadow, TokenSizing, TokenSpacing, TokenTextCase, TokenTextDecoration, TokenTypography, VariantContainer - members: {} -TokenType: - overview: |- - Type Alias TokenType - ==================== - - ``` - TokenType: - | "borderRadius" - | "shadow" - | "color" - | "dimension" - | "fontFamilies" - | "fontSizes" - | "fontWeights" - | "letterSpacing" - | "number" - | "opacity" - | "rotation" - | "sizing" - | "spacing" - | "borderWidth" - | "textCase" - | "textDecoration" - | "typography" - ``` - - The supported types of Design Tokens in Penpot. - - Referenced by: TokenSet - members: {} diff --git a/mcp/packages/server/data/base_instructions.md b/mcp/packages/server/data/base_instructions.md deleted file mode 100644 index 10245a2..0000000 --- a/mcp/packages/server/data/base_instructions.md +++ /dev/null @@ -1,2 +0,0 @@ -You have access to Penpot tools in order to interact with Penpot designs. -Before working with these tools, be sure to read the 'Penpot High-Level Overview' via the `high_level_overview` tool. diff --git a/mcp/packages/server/data/initial_instructions.md b/mcp/packages/server/data/initial_instructions.md deleted file mode 100644 index 34aaf95..0000000 --- a/mcp/packages/server/data/initial_instructions.md +++ /dev/null @@ -1,426 +0,0 @@ -You have access to Penpot tools in order to interact with a Penpot design project directly. -As a precondition, the user must connect the Penpot design project to the MCP server using the Penpot MCP Plugin. - -# Executing Code - -One of your key tools is the `execute_code` tool, which allows you to run JavaScript code using the Penpot Plugin API -directly in the connected project. - -VERY IMPORTANT: When writing code, NEVER LOG INFORMATION YOU ARE ALSO RETURNING. It would duplicate the information you receive! - -To execute code correctly, you need to understand the Penpot Plugin API. You can retrieve API documentation via -the `penpot_api_info` tool. - -This is the full list of types/interfaces in the Penpot API: $api_types - -You use the `storage` object extensively to store data and utility functions you define across tool calls. -This allows you to inspect intermediate results while still being able to build on them in subsequent code executions. - -# The Structure of Penpot Designs - -A Penpot design ultimately consists of shapes. -The type `Shape` is a union type, which encompasses both containers and low-level shapes. -Shapes in a Penpot design are organized hierarchically. -At the top level, a design project contains one or more `Page` objects. -Each `Page` contains a tree of elements. For a given instance `page`, its root shape is `page.root`. -A Page is frequently structured into boards. A `Board` is a high-level grouping element. -A `Group` is a more low-level grouping element used to organize low-level shapes into a logical unit. -Actual low-level shape types are `Rectangle`, `Path`, `Text`, `Ellipse`, `Image`, `Boolean`, and `SvgRaw`. -`ShapeBase` is a base type most shapes build upon. - -# Core Shape Properties and Methods - -**Type**: - Any given shape contains information on the concrete type via its `type` field. - -**Position and Dimensions**: - * The location properties `x` and `y` refer to the top left corner of a shape's bounding box in the absolute (Page) coordinate system. - These are writable - set them directly to position shapes. - * `parentX` and `parentY` (as well as `boardX` and `boardY`) are READ-ONLY computed properties showing position relative to parent/board. - To position relative to parent, use `penpotUtils.setParentXY(shape, parentX, parentY)` or manually set `shape.x = parent.x + parentX`. - * `width` and `height` are READ-ONLY. Use `resize(width, height)` method to change dimensions. - * `bounds` is READ-ONLY (members: x, y, width, height). To modify the bounding box, change `x`, `y` or apply `resize()`. - -**Other Writable Properties**: - * `name` - Shape name - * `fills: Fill[]`, `strokes: Stroke[]`, `shadows: Shadow[]` - Styling properties - - Setting fills: `shape.fills = [{ fillColor: "#FF0000", fillOpacity: 1 }]`; no fill (transparent): `shape.fills = []`; - - Reusing objects in another shape: `targetShape.fills = sourceShape.fills` or more granular `targetShape.fills = [{ fillOpacity: 1, fillImage: sourceShape.fills[0].fillImage }]` - The objects are not shared references; you can modify properties of the fills in the target shape without affecting the source shape. - - Colors: Use hex strings with caps only (e.g. '#FF5533') - - IMPORTANT: The contents of the arrays are read-only. You cannot modify individual fills/strokes; you need to replace the entire array to change them! - * `borderRadius` - Uniform border radius for all corners - * `borderRadiusTopLeft`, `borderRadiusTopRight`, `borderRadiusBottomRight`, `borderRadiusBottomLeft` - Individual corner radii. - * `blur: Blur` - Blur properties - * `blendMode` - Blend mode (e.g. `"normal"`, `"multiply"`, `"overlay"`, etc.) - * `rotation` (deg), `opacity`, `blocked`, `hidden`, `visible` - * `proportionLock` - Whether width and height are locked to the same ratio - * `constraintsHorizontal` - Horizontal resize constraint (`"left"`, `"right"`, `"center"`, `"leftright"`, `"scale"`) - * `constraintsVertical` - Vertical resize constraint (`"top"`, `"bottom"`, `"center"`, `"topbottom"`, `"scale"`) - * `flipX`, `flipY` - Horizontal/vertical flip - -**Z-Order**: - * The z-order of shapes is determined by the order in the `children` array of the parent shape. - Therefore, when creating shapes that should be on top of each other, add them to the parent in the correct order - (i.e. add background shapes first, then foreground shapes later). - * To modify z-order after creation, use these methods: `bringToFront()`, `sendToBack()`, `bringForward()`, `sendBackward()`, - and, for precise control, `setParentIndex(index)` (0-based). - -**Modification Methods**: - * `resize(width, height)` - Change dimensions (required for width/height since they're read-only) - * `rotate(angle, center?)` - Rotate shape - * `remove()` - Permanently destroy the shape (use only for deletion, NOT for reparenting). - Exception: When the shape is a descendant of a board that is a component (asset), the shape will not be removed but instead be made invisible. - -**Hierarchical Structure**: - * `parent` - The parent shape (null for root shapes) - Note: Hierarchical nesting does not necessarily imply visual containment - * To add children to a parent shape (e.g. a `Board`): `parent.appendChild(shape)` or `parent.insertChild(index, shape)` - * Reparenting: `newParent.appendChild(shape)` or `newParent.insertChild(index, shape)` will move a shape to new parent - - Automatically removes the shape from its old parent - - Absolute x/y positions are preserved (use `penpotUtils.setParentXY` to adjust relative position) - -Cloning: Use `shape.clone(): Shape` to create an exact duplicate (including all properties and children) of a shape; same position as original. - -Annotations: Don't add text elements to the design that just repeat a shape's name. In the Penpot UI, the name is displayed anyway. - -# Images - -The `Image` type is a legacy type. Images are now typically embedded in a `Fill`, with `fillImage` set to an -`ImageData` object, i.e. the `fills` property of of a shape (e.g. a `Rectangle`) will contain a fill where `fillImage` is set. -Use the `export_shape` and `import_image` tools to export and import images. - -# Layout Systems - -Boards can have layout systems that automatically control the positioning and spacing of their children: - - * If a board has a layout system, then child positions are controlled by the layout system. - After adding a shape to the layout as a child, key properties of the child within the layout are controlled in `child.layoutChild: LayoutChildProperties`: - - `absolute: boolean` - if true, child position is not controlled by layout system. x/y will set *relative* position within parent! - - margins (`topMargin`, `rightMargin`, `bottomMargin`, `leftMargin` or combined `verticalMargin`, `horizontalMargin`) - - sizing (`verticalSizing`, `horizontalSizing`: "fix" | "auto" | "fill") - controls child resizing depending on the layout's sizing mode (see below) - - min/max sizes (`minWidth`, `maxWidth`, `minHeight`, `maxHeight`) - - `zIndex: number` (higher numbers on top) - - * **Flex Layout**: A flexbox-style layout system - - Properties: `dir`, `rowGap`, `columnGap`, `alignItems`, `justifyContent`; - - `dir`: "row" | "column" | "row-reverse" | "column-reverse" - - Padding: `topPadding`, `rightPadding`, `bottomPadding`, `leftPadding`, or combined `verticalPadding`, `horizontalPadding` - - To modify spacing: adjust `rowGap` and `columnGap` properties, not individual child positions. - Optionally, adjust individual child margins via `child.layoutChild`. - - When a board has flex layout, child positions are controlled by the layout system, not by individual x/y coordinates (unless `child.layoutChild.absolute` is true); - appending or inserting children automatically positions them according to the layout rules. - - To append children to a flex layout board such that they appear visually at the end, use the Board's method `board.appendChild(shape)`, i.e. call it in the order of visual appearance. - To insert at a specific index, use `board.insertChild(index, shape)`. - - Add to a board with `board.addFlexLayout(): FlexLayout`; instance then accessible via `board.flex`. - IMPORTANT: When adding a flex layout to a container that already has children, - use `penpotUtils.addFlexLayout(container, dir)` instead! This preserves the existing visual order of children. - Otherwise, children will be arbitrarily reordered when the children order suddenly determines the display order. - - Check with: `if (board.flex) { ... }` - - * **Grid Layout**: A CSS grid-style layout system - - Add to a board with `board.addGridLayout(): GridLayout`; instance then accessibly via `board.grid`; - Check with: `if (board.grid) { ... }` - - Properties: `rows`, `columns`, `rowGap`, `columnGap` - - Children are positioned via 1-based row/column indices - - Add to grid via `board.grid.appendChild(shape, row, column)` - - Modify grid positioning after the fact via `shape.layoutCell: LayoutCellProperties` - - * Auto-sizing: both types of layouts have properties `verticalSizing`, `horizontalSizing`: "fix" | "auto" | "fill" - - `fix` (default): no resizing (size determined by shape's own width/height) - - `auto`: size determined by content (container will resize depending on children's dimensions); ALWAYS set this if you want the container size to adapt to contents/margins/spacings! - - `fill`: resize children to fill the container's size (child resizing is controlled by each child's `layoutChild` properties) - - * When working with boards: - - ALWAYS check if the board has a layout system before attempting to reposition children - - Modify layout properties (gaps, padding) instead of trying to set child x/y positions directly - - Layout systems override manual positioning of children - -# Text Elements - -`Text` elements: - * The text to be rendered is given by the `characters` property. - * To change the size of the text, change the `fontSize` property; applying `resize()` does NOT change the font size, - it only changes the formal bounding box; if the text does not fit it, it will overflow; use `textBounds` for the actual bounding box of the rendered text. - * Property `bounds` is sized automatically (in one dimension) if the `growType` property is set to "auto-width" or "auto-height". - `resize` always sets `growType` to "fixed", so ALWAYS set it back to "auto-width" or "auto-height" if you want automatic sizing! - The auto-sizing is not immediate; sleep for a short time (100ms) if you want to read the updated bounding box. - * Method `getRange(start, end): TextRange` to reference a range of characters as a `TextRange` object, which can be styled separately from the rest of the text; `start` index inclusive, `end` exclusive - * Other Writable font properties: `fontId`, `fontFamily`, `fontWeight`, `fontVariant`, `fontStyle` - - To discover valid values, check available fonts in `penpot.fonts: FontContext` - - `FontContext` provides `Font` instances; each font has property `variants: FontVariant[]` - - Example: Determine available weights for a font using `penpot.fonts.findByName("Laila").variants.map(v => v.fontWeight)` - - To apply a `Font` to a `Text` instance and set all font properties at once: - - `font.applyToText(text: Text, variant?: FontVariant)` - - `applyToRange(range: TextRange, variant?: FontVariant)` - * Further writable properties: `align`, `verticalAlign`, `lineHeight`, `letterSpacing`, `textTransform`, `textDecoration` (see API info) - * Method `applyTypography(typography: LibraryTypography)` - -# The `penpot` and `penpotUtils` Objects, Exploring Designs - -A key object to use in your code is the `penpot` object (which is of type `Penpot`): - * `penpot.selection` provides the list of shapes the user has selected in the Penpot UI. - If it is unclear which elements to work on, you can ask the user to select them for you. - ALWAYS immediately copy the selected shape(s) into `storage`! Do not assume that the selection remains unchanged. - * `penpot.root` provides the root shape of the currently active page. - * Generation of CSS content for elements via `penpot.generateStyle` - * Generation of HTML/SVG content for elements via `penpot.generateMarkup` - -For example, to generate CSS for the currently selected elements, you can execute this: - return penpot.generateStyle(penpot.selection, { type: "css", withChildren: true }); - -CRITICAL: The `penpotUtils` object provides essential utilities - USE THESE INSTEAD OF WRITING YOUR OWN: - * getPages(): { id: string; name: string }[] - * getPageById(id: string): Page | null - * getPageByName(name: string): Page | null - * shapeStructure(shape: Shape, maxDepth: number | undefined = undefined): { id, name, type, children?, layout? } - Generates an overview structure of the given shape. - - children: recursive, limited by maxDepth - - layout: present if shape has flex/grid layout, contains { type: "flex" | "grid", ... } - * findShapeById(id: string): Shape | null - * findShape(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape | null - If no root is provided, search globally (in all pages). - * findShapes(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape[] - * isContainedIn(shape: Shape, container: Shape): boolean - Returns true iff shape is fully within the container's geometric bounds. - Note that a shape's bounds may not always reflect its actual visual content - descendants can overflow; check using analyzeDescendants (see below). - * setParentXY(shape: Shape, parentX: number, parentY: number): void - Sets shape position relative to its parent (since parentX/parentY are read-only) - * analyzeDescendants(root: Shape, evaluator: (root: Shape, descendant: Shape) => T | null | undefined, maxDepth?: number): Array<{ shape: Shape, result: T }> - General-purpose utility for analyzing/validating descendants - Calls evaluator on each descendant; collects non-null/undefined results - Powerful pattern: evaluator can return corrector functions or diagnostic data - * Further functions for specific tasks (described in the sections below) - -General pointers for working with Penpot designs: - * Prefer `penpotUtils` helper functions — avoid reimplementing shape searching. - * To get an overview of a single page, use `penpotUtils.shapeStructure(page.root, 3)`. - Note that `penpot.root` refers to the current page only. When working across pages, first determine the relevant page(s). - * Use `penpotUtils.findShapes()` or `penpotUtils.findShape()` with predicates to locate elements efficiently. - -Common tasks - Quick Reference (ALWAYS use penpotUtils for these): - * Find all images: - const images = penpotUtils.findShapes( - shape => shape.type === 'image' || shape.fills?.some(fill => fill.fillImage), - penpot.root - ); - * Find text elements: - const texts = penpotUtils.findShapes(shape => shape.type === 'text', penpot.root); - * Find (the first) shape with a given name: - const shape = penpotUtils.findShape(shape => shape.name === 'MyShape'); - * Get structure of current selection: - const structure = penpotUtils.shapeStructure(penpot.selection[0]); - * Find shapes in current selection/board: - const shapes = penpotUtils.findShapes(predicate, penpot.selection[0] || penpot.root); - * Validate/analyze descendants (returning corrector functions): - const fixes = penpotUtils.analyzeDescendants(board, (root, shape) => { - const xMod = shape.parentX % 4; - if (xMod !== 0) { - return () => penpotUtils.setParentXY(shape, Math.round(shape.parentX / 4) * 4, shape.parentY); - } - }); - fixes.forEach(f => f.result()); // Apply all fixes - * Find containment violations: - const violations = penpotUtils.analyzeDescendants(board, (root, shape) => { - return !penpotUtils.isContainedIn(shape, root) ? 'outside-bounds' : null; - }); - Always validate against the root container that is supposed to contain the shapes. - -# Asset Libraries - -Libraries in Penpot are collections of reusable design assets (components, colors, and typographies) that can be shared across files. -They enable design systems and consistent styling across projects. -Each Penpot file has its own local library and can connect to external shared libraries. - -Accessing libraries: via `penpot.library` (type: `LibraryContext`): - * `penpot.library.local` (type: `Library`) - The current file's own library - * `penpot.library.connected` (type: `Library[]`) - Array of already-connected external libraries - * `penpot.library.availableLibraries()` (returns: `Promise`) - Libraries available to connect - * `penpot.library.connectLibrary(libraryId: string)` (returns: `Promise`) - Connect a new library - -Each `Library` object has: - * `id: string` - * `name: string` - * `components: LibraryComponent[]` - Array of components - * `colors: LibraryColor[]` - Array of colors - * `typographies: LibraryTypography[]` - Array of typographies - -## Colors and Typographies - -Adding a color: -``` -const newColor: LibraryColor = penpot.library.local.createColor(); -newColor.name = 'Brand Primary'; -newColor.color = '#0066FF'; -``` - -Adding a typography: -``` -const newTypo: LibraryTypography = penpot.library.local.createTypography(); -newTypo.name = 'Heading Large'; -// Set typography properties... -``` - -## Components - -Using library components: - * find a component in the library by name: - `const component: LibraryComponent = library.components.find(comp => comp.name.includes('Button'));` - * create a new instance of the component on the current page: - `const instance: Shape = component.instance();` - This returns a `Shape` (often a `Board` containing child elements). - - After instantiation, modify the instance's properties as desired. - - Get a reference to the component an instance was created from via `instance.component()`. - * get the reference to the main instance (shape that serves as the source for new instances): - `const mainShape: Shape = component.mainInstance();` - -Adding a component to a library: -``` -const shapes: Shape[] = [shape1, shape2]; // shapes to include -const newComponent: LibraryComponent = penpot.library.local.createComponent(shapes); -newComponent.name = 'My Button'; -``` - -Detaching: - * When creating new design elements based on a component instance/copy, use `shape.detach()` to break the link to the main component, allowing independent modification. - * Without detaching, some manipulations will have no effect; e.g. child/descendant removal will not work. - -### Variants - -Variants are a system for grouping related component versions along named property axes (e.g. Type, Style), powering a structured swap UI for designers using component instances. - -* `VariantContainer` (extends `Board`): The board that physically groups all variant components together. - - check with `isVariantContainer()` - - property `variants: Variants`. -* `Variants`: Defines the combinations of property values for which component variants can exist and manages the concrete component variants. - - `properties: string[]` (ordered list of property names); `addProperty(): void`, `renameProperty(pos, name)`, `currentValues(property)` - - `variantComponents(): LibraryVariantComponent[]` -* `LibraryVariantComponent` (extends `LibraryComponent`): full library component with metadata, for which `isVariant()` returns true. - - `variants: Variants` - - `variantProps: { [property: string]: string }` (this component's value for each property) - - `variantError` (non-null if e.g. two variants share the same combination of property values) - - `setVariantProperty(pos, value)` - -Properties are often addressed positionally: `pos` parameter in various methods = index in `Variants.properties`. - -**Creating a variant group**: - -Use `penpotUtils.createVariantContainer(components)` — it handles the full multi-step workflow in one call: -```js -// Given three main components s, m, l (here: the first three main components on the page) -const [s, m, l] = penpot.currentPage.findAllShapes(sh => sh.isMainComponent()).slice(0, 3); -// Single property: -const container = penpotUtils.createVariantContainer([ - { shape: s, properties: { Size: 'Small' } }, - { shape: m, properties: { Size: 'Medium' } }, - { shape: l, properties: { Size: 'Large' } }, -]); -// Multiple properties: -const container2 = penpotUtils.createVariantContainer([ - { shape: s, properties: { Size: 'Small', State: 'Default' } }, - { shape: m, properties: { Size: 'Medium', State: 'Default' } }, - { shape: l, properties: { Size: 'Large', State: 'Hover' } }, -]); -``` - -If you must use the lower-level API, follow this exact order — skipping or reordering steps leaves the variant broken: -1. `penpot.createVariantFromComponents(mainInstances: Board[]): VariantContainer` — combines several main component instances into a new variant group. All components end up inside a single new container on the canvas; always creates one property called `"Property 1"`. -2. `container.variants.renameProperty(0, name)` — rename `Property 1`. -3. For each extra property: `variants.addProperty()` then `variants.renameProperty(pos, name)`. -4. For every component × every property: iterate `variants.variantComponents()` and call `comp.setVariantProperty(pos, value)`. - -**Adding a variant to an existing group**: -Use `variantContainer.appendChild(mainInstance)` to move a component's main instance into the container, then set its position manually and assign property values via `setVariantProperty`. - -**Using Variants**: -- `compInstance.switchVariant(pos, value)`: On a component instance, switches to the nearest variant that has the given value at property position `pos`, keeping all other property values the same. -- To instantiate a specific variant, find the right `LibraryVariantComponent` by checking `variantProps`, then call `.instance()`. -- Given a variant component instance, access the component it was instantiated from via `instance.component()` and the `Variants` instance via `instance.component().variants`. - -# Design Tokens - -Design tokens are reusable design values (colors, dimensions, typography, etc.) for consistent styling. - -The token library: `penpot.library.local.tokens` (type: `TokenCatalog`) - * `sets: TokenSet[]` - Token collections (order matters for precedence) - * `themes: TokenTheme[]` - Presets that activate specific sets - * `addSet({name: string}): TokenSet` - Create new set - * `addTheme({group: string, name: string}): TokenTheme` - Create new theme - -`TokenSet` contains tokens with unique names: - * `active: boolean` - Only active sets affect shapes; use `set.toggleActive()` to change: `if (!set.active) set.toggleActive();` - * `tokens: Token[]` - All tokens in set - * `addToken({type: TokenType, name: string, value: TokenValueString}): Token` - Creates a token, adding it to the set. - - `TokenType`: "color" | "dimension" | "spacing" | "typography" | "shadow" | "opacity" | "borderRadius" | "borderWidth" | "fontWeights" | "fontSizes" | "fontFamilies" | "letterSpacing" | "textDecoration" | "textCase" - - `value`: depends on the type of token (inspect `Token` and related types) - - Examples: - const token = set.addToken({type: "color", name: "color.primary", value: "#0066FF"}); // direct value - const token2 = set.addToken({type: "color", name: "color.accent", value: "{color.primary}"}); // reference to another token - -`Token`: union type encompassing various token types, with common properties: - * `name: string` - Token name (typically structured, e.g. "color.base.white") - * `value` - Raw value (direct value or reference to another token like "{color.primary}") - * `resolvedValue` - Computed final value (follows references) - * `type: TokenType` - -Discovering tokens: - * `penpotUtils.tokenOverview()`: Maps from token set name to a mapping from token type to list of token names - * `penpotUtils.findTokenByName(name: string): Token | null`: Finds the first applicable token matching the given name - * `penpotUtils.findTokensByName(name: string): Token[]`: Finds all tokens that match the given name across all token sets - * `penpotUtils.getTokenSet(token: Token): TokenSet | null`: Gets the token set that contains the given token - -Applying tokens: - * `shape.applyToken(token, properties: undefined | TokenProperty[])` - Apply a token to a shape for one or more properties - (if properties is undefined, use a default property based on the token type - not usually recommended). - `TokenProperty` is a union type; possible values are: - - "all": applies the token to all properties it can control - - TokenBorderRadiusProps: "borderRadiusTopLeft", "borderRadiusTopRight", "borderRadiusBottomRight", "borderRadiusBottomLeft" - - TokenShadowProps: "shadow" - - TokenColorProps: "fill", "strokeColor" - - TokenDimensionProps: "x", "y", "strokeWidth" - - TokenFontFamiliesProps: "fontFamilies" - - TokenFontSizesProps: "fontSize" - - TokenFontWeightProps: "fontWeight" - - TokenLetterSpacingProps: "letterSpacing" - - TokenNumberProps: "rotation" - - TokenOpacityProps: "opacity" - - TokenSizingProps: "width", "height", "layoutItemMinW", "layoutItemMaxW", "layoutItemMinH", "layoutItemMaxH" - - TokenSpacingProps: "rowGap", "columnGap", "paddingLeft", "paddingTop", "paddingRight", "paddingBottom", "marginLeft", "marginTop", "marginRight", "marginBottom" - - TokenBorderWidthProps: "strokeWidth" - - TokenTextCaseProps: "textCase" - - TokenTextDecorationProps: "textDecoration" - - TokenTypographyProps: "typography" - * `token.applyToShapes(shapes, properties)` - Apply from token - * Application is **asynchronous** (wait for ~100ms to see the effects) - * After application: - - `shape.tokens` returns a mapping `{ propertyName: "token.name" }` from `TokenProperty` to token name - - The actual shape properties that the tokens control will reflect the token's resolved value. - -Removing tokens: - Simply set the respective property directly - token binding is automatically removed, e.g. - shape.fills = [{ fillColor: "#000000", fillOpacity: 1 }]; // Removes fill token - -# Visual Inspection of Designs - -For many tasks, it can be critical to visually inspect the design. Remember to use the `export_shape` tool for this purpose! - -# Creating and Translating Designs - -* When transferring styles from a Penpot design to code, make sure that you strictly adhere to the design. - NEVER make assumptions about missing values and don't get overly creative (e.g. don't pick your own colours and stick to - non-creative defaults such as white/black if you are lacking information). -* When creating new designs, - - ensure a clean internal structure by applying flex and grid layouts when appropriate - - ensure proper semantic naming of elements. - -# Revising Designs - -* Before applying design changes, ask: "Would a designer consider this appropriate?" -* When dealing with containment issues, ask: Is the parent too small OR is the child too large? - Container sizes are usually intentional, check content first. -* Check for reasonable font sizes and typefaces -* The use of flex layouts is encouraged for cases where elements are arranged in rows or columns with consistent spacing/positioning. - Consider converting boards to flex layout when appropriate. - --- -You have hereby read the 'Penpot High-Level Overview' and need not use a tool to read it again. diff --git a/mcp/packages/server/package.json b/mcp/packages/server/package.json deleted file mode 100644 index c30679f..0000000 --- a/mcp/packages/server/package.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "mcp-server", - "version": "1.0.0", - "description": "MCP server for Penpot integration", - "type": "module", - "main": "dist/index.js", - "scripts": { - "build:server": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.js --external:@modelcontextprotocol/* --external:ws --external:express --external:class-transformer --external:class-validator --external:reflect-metadata --external:pino --external:pino-pretty --external:pino-loki --external:js-yaml --external:sharp --external:nrepl-client --external:ioredis", - "build": "pnpm run build:server && node scripts/copy-resources.js", - "build:types": "tsc --emitDeclarationOnly --outDir dist", - "start": "node dist/index.js", - "start:multi-user": "node dist/index.js --multi-user", - "start:dev": "node --import ts-node/register src/index.ts", - "start:dev:multi-user": "node --loader ts-node/esm src/index.ts --multi-user", - "test": "tsx --test src/*.test.ts", - "test:integration:export-sema": "tsx scripts/integration-test-export-image-semaphore.ts", - "types:check": "tsc --noEmit", - "clean": "rm -rf dist/" - }, - "keywords": [ - "mcp", - "penpot", - "server" - ], - "author": "", - "license": "MIT", - "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", - "class-transformer": "^0.5.1", - "class-validator": "^0.15.1", - "express": "^5.2.1", - "ioredis": "^6.0.0", - "js-yaml": "^5.2.3", - "nrepl-client": "^0.3.0", - "pino": "^10.3.1", - "pino-loki": "^3.0.0", - "pino-pretty": "^13.1.3", - "reflect-metadata": "^0.2.2", - "sharp": "^0.35.3", - "ws": "^8.21.1", - "zod": "^4.4.3" - }, - "devDependencies": { - "@penpot/mcp-common": "workspace:../common", - "@types/express": "^5.0.6", - "@types/js-yaml": "^4.0.9", - "@types/node": "^26.1.2", - "@types/ws": "^8.18.1", - "cross-env": "^10.1.0", - "esbuild": "^0.28.1", - "ts-node": "^10.9.2", - "tsx": "^4.23.5", - "typescript": "^6.0.3" - }, - "ts-node": { - "esm": true - } -} diff --git a/mcp/packages/server/scripts/copy-resources.js b/mcp/packages/server/scripts/copy-resources.js deleted file mode 100644 index 08b3604..0000000 --- a/mcp/packages/server/scripts/copy-resources.js +++ /dev/null @@ -1,5 +0,0 @@ -import { cpSync } from "fs"; - -// copy static assets and data to dist -cpSync("src/static", "dist/static", { recursive: true }); -cpSync("data", "dist/data", { recursive: true }); diff --git a/mcp/packages/server/scripts/integration-test-export-image-semaphore.ts b/mcp/packages/server/scripts/integration-test-export-image-semaphore.ts deleted file mode 100644 index 807ae82..0000000 --- a/mcp/packages/server/scripts/integration-test-export-image-semaphore.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * One-off integration test for the parallelism bound around image exports. - * - * Setup: - * - Stubs ExportShapeTool.exportImage to sleep SLEEP_MS instead of doing real work, - * so no actual plugin connection is needed. - * - Replaces the static parallelism semaphore with one of size N. - * - Starts a PenpotMcpServer in multi-user mode on three random free ports. - * - Fires M > N parallel MCP clients that each call the export_shape tool. - * - * Expectations (observed manually from the server's console output): - * - "Semaphore 'ExportShapeTool' saturated; request queued (k waiting)" lines appear - * at INFO level (at least M - N of them). - * - All M tool calls return successfully. - * - Total elapsed wall-clock time is approximately ceil(M / N) * SLEEP_MS. - * - * Invoke from packages/server with: - * pnpm run test:integration:export - */ -import * as net from "node:net"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; - -import { PenpotMcpServer } from "../src/PenpotMcpServer"; -import { ExportShapeTool } from "../src/tools/ExportShapeTool"; -import { TextResponse } from "../src/ToolResponse"; -import { Semaphore } from "../src/utils/Semaphore"; - -// === parameters === - -const N = 3; -const M = 6; -const SLEEP_MS = 5_000; - -// === helpers === - -async function findFreePort(): Promise { - return new Promise((resolve, reject) => { - const srv = net.createServer(); - srv.unref(); - srv.on("error", reject); - srv.listen(0, "127.0.0.1", () => { - const port = (srv.address() as net.AddressInfo).port; - srv.close(() => resolve(port)); - }); - }); -} - -async function callExportShape(url: URL, idx: number): Promise { - const client = new Client({ name: `integration-test-client-${idx}`, version: "0.0.0" }); - const transport = new StreamableHTTPClientTransport(url); - await client.connect(transport); - try { - return await client.callTool({ - name: "export_shape", - arguments: { shapeId: "selection" }, - }); - } finally { - await client.close(); - } -} - -// === main === - -async function main(): Promise { - // dynamic ports must be set before PenpotMcpServer is constructed - const httpPort = await findFreePort(); - process.env.PENPOT_MCP_SERVER_HOST = "127.0.0.1"; - process.env.PENPOT_MCP_SERVER_PORT = String(httpPort); - process.env.PENPOT_MCP_WEBSOCKET_PORT = String(await findFreePort()); - process.env.PENPOT_MCP_REPL_PORT = String(await findFreePort()); - - // shrink the gate and stub the worker - (ExportShapeTool as any).parallelismSemaphore = new Semaphore("ExportShapeTool", N); - (ExportShapeTool.prototype as any).exportImage = async (): Promise => { - await new Promise((r) => setTimeout(r, SLEEP_MS)); - return new TextResponse("stubbed export"); - }; - - const server = new PenpotMcpServer(/* isMultiUser */ true); - await server.start(); - - console.log(`\n=== integration test: N=${N} permits, M=${M} clients, sleep=${SLEEP_MS}ms ===\n`); - - // fire M parallel tool calls, each via its own MCP session with a distinct userToken - const start = Date.now(); - const results = await Promise.allSettled( - Array.from({ length: M }, (_, i) => - callExportShape(new URL(`http://127.0.0.1:${httpPort}/mcp?userToken=test-token-${i}`), i) - ) - ); - const elapsed = Date.now() - start; - - await server.stop(); - - // report - const failures = results.map((r, i) => ({ r, i })).filter(({ r }) => r.status === "rejected"); - - console.log(`\n=== results ===`); - console.log(` elapsed: ${elapsed}ms (expected ~${Math.ceil(M / N) * SLEEP_MS}ms)`); - console.log(` succeeded: ${M - failures.length}/${M}`); - if (failures.length > 0) { - console.log(` failures:`); - for (const { r, i } of failures) { - console.log(` [${i}] ${(r as PromiseRejectedResult).reason}`); - } - process.exit(1); - } - console.log(`\nAll ${M} tool calls succeeded. Scroll up to verify saturation log lines.\n`); - process.exit(0); -} - -main().catch((err) => { - console.error("Integration test crashed:", err); - process.exit(1); -}); diff --git a/mcp/packages/server/src/ApiDocs.ts b/mcp/packages/server/src/ApiDocs.ts deleted file mode 100644 index c6374ea..0000000 --- a/mcp/packages/server/src/ApiDocs.ts +++ /dev/null @@ -1,128 +0,0 @@ -import * as yaml from "js-yaml"; -import * as fs from "fs"; -import * as path from "path"; - -/** - * Represents a single type/interface defined in the Penpot API - */ -export class ApiType { - private readonly name: string; - private readonly overview: string; - private readonly members: Record>; - private cachedFullText: string | null = null; - - constructor(name: string, overview: string, members: Record>) { - this.name = name; - this.overview = overview; - this.members = members; - } - - /** - * Returns the original name of this API type. - */ - getName(): string { - return this.name; - } - - /** - * Returns the overview text of this API type (which all signature/type declarations) - */ - getOverviewText() { - return this.overview; - } - - /** - * Creates a single markdown text document from all parts of this API type. - * - * The full text is cached within the object for performance. - */ - getFullText(): string { - if (this.cachedFullText === null) { - let text = this.overview; - - for (const [memberType, memberEntries] of Object.entries(this.members)) { - text += `\n\n## ${memberType}\n`; - - for (const [memberName, memberDescription] of Object.entries(memberEntries)) { - text += `\n### ${memberName}\n\n${memberDescription}`; - } - } - - this.cachedFullText = text; - } - - return this.cachedFullText; - } - - /** - * Returns the description of the member with the given name. - * - * The member type doesn't matter for the search, as member names are unique - * across all member types within a single API type. - */ - getMember(memberName: string): string | null { - for (const memberEntries of Object.values(this.members)) { - if (memberName in memberEntries) { - return memberEntries[memberName]; - } - } - return null; - } -} - -/** - * Loads and manages API documentation from YAML files. - * - * This class provides case-insensitive access to API type documentation - * loaded from the data/api_types.yml file. - */ -export class ApiDocs { - private readonly apiTypes: Map = new Map(); - - /** - * Creates a new ApiDocs instance and loads the API types from the YAML file. - */ - constructor() { - this.loadApiTypes(); - } - - /** - * Loads API types from the data/api_types.yml file. - */ - private loadApiTypes(): void { - const yamlPath = path.join(process.cwd(), "data", "api_types.yml"); - const yamlContent = fs.readFileSync(yamlPath, "utf8"); - const data = yaml.load(yamlContent) as Record; - - for (const [typeName, typeData] of Object.entries(data)) { - const overview = typeData.overview || ""; - const members = typeData.members || {}; - - const apiType = new ApiType(typeName, overview, members); - - // store with lower-case key for case-insensitive retrieval - this.apiTypes.set(typeName.toLowerCase(), apiType); - } - } - - /** - * Retrieves an API type by name (case-insensitive). - */ - getType(typeName: string): ApiType | null { - return this.apiTypes.get(typeName.toLowerCase()) || null; - } - - /** - * Returns all available type names. - */ - getTypeNames(): string[] { - return Array.from(this.apiTypes.values()).map((type) => type.getName()); - } - - /** - * Returns the number of loaded API types. - */ - getTypeCount(): number { - return this.apiTypes.size; - } -} diff --git a/mcp/packages/server/src/ConfigurationLoader.ts b/mcp/packages/server/src/ConfigurationLoader.ts deleted file mode 100644 index 2b4b112..0000000 --- a/mcp/packages/server/src/ConfigurationLoader.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { existsSync, readFileSync } from "fs"; -import { join } from "path"; -import { createLogger } from "./logger.js"; - -/** - * Configuration loader for prompts and server settings. - */ -export class ConfigurationLoader { - private readonly logger = createLogger("ConfigurationLoader"); - private readonly baseDir: string; - private readonly initialInstructions: string; - private readonly baseInstructions: string; - - /** - * Creates a new configuration loader instance. - * - * @param baseDir - Base directory for resolving configuration file paths - */ - constructor(baseDir: string) { - this.baseDir = baseDir; - this.initialInstructions = this.loadFileContent(join(this.baseDir, "data", "initial_instructions.md")); - this.baseInstructions = this.loadFileContent(join(this.baseDir, "data", "base_instructions.md")); - } - - private loadFileContent(filePath: string): string { - if (!existsSync(filePath)) { - throw new Error(`Configuration file not found at ${filePath}`); - } - return readFileSync(filePath, "utf8"); - } - - /** - * Gets the initial instructions for the MCP server corresponding to the - * 'Penpot High-Level Overview' - * - * @returns The initial instructions string - */ - public getInitialInstructions(): string { - return this.initialInstructions; - } - - /** - * Gets the base instructions which shall be provided to clients when connecting to - * the MCP server - * - * @returns The initial instructions string - */ - public getBaseInstructions(): string { - return this.baseInstructions; - } -} diff --git a/mcp/packages/server/src/NreplClient.ts b/mcp/packages/server/src/NreplClient.ts deleted file mode 100644 index d550812..0000000 --- a/mcp/packages/server/src/NreplClient.ts +++ /dev/null @@ -1,217 +0,0 @@ -import nreplClient from "nrepl-client"; -import type { NreplConnection, NreplMessage } from "nrepl-client"; -import { createLogger } from "./logger"; - -/** - * Result of evaluating a ClojureScript expression via nREPL. - */ -export interface NreplEvalResult { - /** the returned value(s) as strings */ - values: string[]; - /** captured stdout output */ - out: string; - /** captured stderr output */ - err: string; - /** the namespace after evaluation */ - ns: string; -} - -/** - * A client for communicating with a shadow-cljs nREPL server. - * - * This client maintains a persistent nREPL session, so that definitions, - * requires, and other state are preserved across evaluations — providing - * a full REPL experience. - */ -export class NreplClient { - private static readonly NREPL_PORT = 3447; - private static readonly NREPL_HOST = "localhost"; - private static readonly EVAL_TIMEOUT_MS = 30_000; - - private readonly logger = createLogger("NreplClient"); - - /** the persistent connection to the nREPL server, established lazily */ - private connection: NreplConnection | null = null; - - /** the cloned session ID that persists state across evaluations */ - private sessionId: string | null = null; - - /** - * Evaluates a Clojure expression on the nREPL server within the persistent session. - * - * @param code - the Clojure expression to evaluate - * @returns the evaluation result - */ - async eval(code: string): Promise { - this.logger.debug("Evaluating Clojure expression: %s", code); - const conn = await this.ensureConnection(); - const sessionId = await this.ensureSession(conn); - - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Error(`nREPL evaluation timed out after ${NreplClient.EVAL_TIMEOUT_MS}ms`)); - }, NreplClient.EVAL_TIMEOUT_MS); - - conn.send({ op: "eval", code, session: sessionId }, (err: Error | null, result: NreplMessage[]) => { - clearTimeout(timeout); - if (err) { - reject(err); - return; - } - try { - resolve(this.parseEvalResult(result)); - } catch (parseErr) { - reject(parseErr); - } - }); - }); - } - - /** - * Evaluates a ClojureScript expression via the shadow-cljs CLJS eval API. - * - * The expression is wrapped in a call to `shadow.cljs.devtools.api/cljs-eval` - * targeting the `:main` build, so it is evaluated in the browser runtime. - * - * @param cljsCode - the ClojureScript expression to evaluate - * @returns the evaluation result - */ - async evalCljs(cljsCode: string): Promise { - // escape the CLJS code for embedding in a Clojure string - const escapedCode = cljsCode.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); - const wrappedCode = `(shadow.cljs.devtools.api/cljs-eval :main "${escapedCode}" {})`; - this.logger.debug("Evaluating CLJS expression via shadow-cljs: %s", cljsCode); - return this.eval(wrappedCode); - } - - /** - * Closes the persistent connection and session, releasing all resources. - */ - async close(): Promise { - if (this.connection) { - this.logger.info("Closing nREPL connection"); - this.connection.end(); - this.connection = null; - this.sessionId = null; - } - } - - /** - * Ensures a connection to the nREPL server is established, creating one if necessary. - * - * If the existing connection has been closed or errored, a new one is created. - */ - private async ensureConnection(): Promise { - if (this.connection && !this.connection.destroyed) { - return this.connection; - } - - // reset state since the old connection is gone - this.connection = null; - this.sessionId = null; - - this.logger.info("Connecting to nREPL server at %s:%d", NreplClient.NREPL_HOST, NreplClient.NREPL_PORT); - - return new Promise((resolve, reject) => { - const conn = nreplClient.connect({ - port: NreplClient.NREPL_PORT, - host: NreplClient.NREPL_HOST, - }); - - conn.once("connect", () => { - this.connection = conn; - - // handle unexpected disconnects so the next eval reconnects - conn.once("close", () => { - this.logger.warn("nREPL connection closed unexpectedly"); - this.connection = null; - this.sessionId = null; - }); - - conn.once("error", (err: Error) => { - this.logger.error("nREPL connection error: %s", err); - this.connection = null; - this.sessionId = null; - }); - - resolve(conn); - }); - - conn.once("error", (err: Error) => { - reject( - new Error( - `Failed to connect to nREPL server at ${NreplClient.NREPL_HOST}:${NreplClient.NREPL_PORT}: ${err.message}` - ) - ); - }); - }); - } - - /** - * Ensures a persistent nREPL session exists, cloning one from the server if necessary. - * - * A cloned session maintains its own state (namespace bindings, definitions, etc.) - * independently of other sessions. - */ - private async ensureSession(conn: NreplConnection): Promise { - if (this.sessionId) { - return this.sessionId; - } - - this.logger.info("Cloning new nREPL session"); - - return new Promise((resolve, reject) => { - conn.clone((err: Error | null, result: NreplMessage[]) => { - if (err) { - reject(new Error(`Failed to clone nREPL session: ${err.message}`)); - return; - } - - const sessionMsg = result.find((msg) => msg["new-session"] !== undefined) as any; - if (!sessionMsg) { - reject(new Error("nREPL clone response did not contain a new session ID")); - return; - } - - this.sessionId = sessionMsg["new-session"]; - this.logger.info("Cloned nREPL session: %s", this.sessionId); - resolve(this.sessionId!); - }); - }); - } - - /** - * Parses the raw nREPL response messages into a structured result. - */ - private parseEvalResult(messages: NreplMessage[]): NreplEvalResult { - const values: string[] = []; - const outParts: string[] = []; - const errParts: string[] = []; - let ns = "user"; - - for (const msg of messages) { - if (msg.value !== undefined) { - values.push(msg.value); - } - if (msg.out) { - outParts.push(msg.out); - } - if (msg.err) { - errParts.push(msg.err); - } - if (msg.ns) { - ns = msg.ns; - } - if (msg.ex) { - throw new Error(`nREPL evaluation error: ${msg.ex}${msg.err ? "\n" + msg.err : ""}`); - } - } - - return { - values, - out: outParts.join(""), - err: errParts.join(""), - ns, - }; - } -} diff --git a/mcp/packages/server/src/PenpotMcpServer.test.ts b/mcp/packages/server/src/PenpotMcpServer.test.ts deleted file mode 100644 index 2d35e70..0000000 --- a/mcp/packages/server/src/PenpotMcpServer.test.ts +++ /dev/null @@ -1,258 +0,0 @@ -import assert from "node:assert/strict"; -import test from "node:test"; -import { PenpotMcpServer, shouldRegisterDeveloperTools, shouldStartReplServer } from "./PenpotMcpServer"; - -test("registers developer tools in local devenv mode", () => { - assert.equal(shouldRegisterDeveloperTools(true, false), true); -}); - -test("does not register developer tools in multi-user devenv mode", () => { - assert.equal(shouldRegisterDeveloperTools(true, true), false); -}); - -test("does not register developer tools when devenv mode is disabled", () => { - assert.equal(shouldRegisterDeveloperTools(false, false), false); -}); - -test("starts REPL server in single-user mode when enabled", () => { - assert.equal(shouldStartReplServer(true, false), true); -}); - -test("does not start REPL server in multi-user mode even when enabled", () => { - assert.equal(shouldStartReplServer(true, true), false); -}); - -test("does not start REPL server when disabled in single-user mode", () => { - assert.equal(shouldStartReplServer(false, false), false); -}); - -test("does not start REPL server when disabled in multi-user mode", () => { - assert.equal(shouldStartReplServer(false, true), false); -}); - -// ── Pure function tests ──────────────────────────────────────── - -test("isDevEnvEnabled returns false when PENPOT_MCP_DEVENV is not set", () => { - assert.equal(PenpotMcpServer.isDevEnvEnabled({}), false); -}); - -test("isDevEnvEnabled returns false when PENPOT_MCP_DEVENV is 'false'", () => { - assert.equal(PenpotMcpServer.isDevEnvEnabled({ PENPOT_MCP_DEVENV: "false" }), false); -}); - -test("isDevEnvEnabled returns true when PENPOT_MCP_DEVENV is 'true'", () => { - assert.equal(PenpotMcpServer.isDevEnvEnabled({ PENPOT_MCP_DEVENV: "true" }), true); -}); - -// ── Pure function tests: isReplEnabled ────────────────────────── - -test("isReplEnabled returns false when neither env var is set", () => { - assert.equal(PenpotMcpServer.isReplEnabled({}), false); -}); - -test("isReplEnabled returns true when PENPOT_MCP_DEVENV is 'true' (fallback)", () => { - assert.equal(PenpotMcpServer.isReplEnabled({ PENPOT_MCP_DEVENV: "true" }), true); -}); - -test("isReplEnabled returns true when PENPOT_MCP_REPL_ENABLE is 'true'", () => { - assert.equal(PenpotMcpServer.isReplEnabled({ PENPOT_MCP_REPL_ENABLE: "true" }), true); -}); - -test("isReplEnabled returns false when PENPOT_MCP_REPL_ENABLE is 'false' even if DEVENV is true", () => { - assert.equal(PenpotMcpServer.isReplEnabled({ PENPOT_MCP_REPL_ENABLE: "false", PENPOT_MCP_DEVENV: "true" }), false); -}); - -test("isReplEnabled returns true when PENPOT_MCP_REPL_ENABLE is 'true' regardless of DEVENV", () => { - assert.equal(PenpotMcpServer.isReplEnabled({ PENPOT_MCP_REPL_ENABLE: "true" }), true); -}); - -// ── Integration tests: constructor gating ────────────────────── -// -// Each test uses unique ports to avoid conflicts when tests run -// in the same process. The server is stopped in the finally block -// to release the WebSocket port. - -let portCounter = 14_500; -function uniquePorts() { - const base = portCounter; - portCounter += 10; - return { server: base, ws: base + 1, repl: base + 2 }; -} - -test("constructor does not create ReplServer when PENPOT_MCP_DEVENV is unset", async () => { - const prev = process.env.PENPOT_MCP_DEVENV; - const prevPorts = setUniqueEnv(); - delete process.env.PENPOT_MCP_DEVENV; - let server: PenpotMcpServer | undefined; - try { - server = new PenpotMcpServer(false); - assert.equal(server.hasReplServer(), false); - } finally { - await server?.stop(); - restoreEnv(prev, prevPorts); - } -}); - -test("constructor creates ReplServer when PENPOT_MCP_DEVENV is 'true'", async () => { - const prev = process.env.PENPOT_MCP_DEVENV; - const prevPorts = setUniqueEnv(); - process.env.PENPOT_MCP_DEVENV = "true"; - let server: PenpotMcpServer | undefined; - try { - server = new PenpotMcpServer(false); - assert.equal(server.hasReplServer(), true); - } finally { - await server?.stop(); - restoreEnv(prev, prevPorts); - } -}); - -test("constructor creates ReplServer when PENPOT_MCP_REPL_ENABLE is 'true' without DEVENV", async () => { - const prevDevEnv = process.env.PENPOT_MCP_DEVENV; - const prevReplEnable = process.env.PENPOT_MCP_REPL_ENABLE; - const prevPorts = setUniqueEnv(); - delete process.env.PENPOT_MCP_DEVENV; - process.env.PENPOT_MCP_REPL_ENABLE = "true"; - let server: PenpotMcpServer | undefined; - try { - server = new PenpotMcpServer(false); - assert.equal(server.hasReplServer(), true); - } finally { - await server?.stop(); - restoreEnv(prevDevEnv, prevPorts); - restoreOrDelete("PENPOT_MCP_REPL_ENABLE", prevReplEnable); - } -}); - -test("constructor does not create ReplServer when PENPOT_MCP_REPL_ENABLE is 'false' even with DEVENV", async () => { - const prevDevEnv = process.env.PENPOT_MCP_DEVENV; - const prevReplEnable = process.env.PENPOT_MCP_REPL_ENABLE; - const prevPorts = setUniqueEnv(); - process.env.PENPOT_MCP_DEVENV = "true"; - process.env.PENPOT_MCP_REPL_ENABLE = "false"; - let server: PenpotMcpServer | undefined; - try { - server = new PenpotMcpServer(false); - assert.equal(server.hasReplServer(), false); - } finally { - await server?.stop(); - restoreEnv(prevDevEnv, prevPorts); - restoreOrDelete("PENPOT_MCP_REPL_ENABLE", prevReplEnable); - } -}); - -test("constructor does not create ReplServer in multi-user mode even with DEVENV", async () => { - const prevDevEnv = process.env.PENPOT_MCP_DEVENV; - const prevReplEnable = process.env.PENPOT_MCP_REPL_ENABLE; - const prevPorts = setUniqueEnv(); - process.env.PENPOT_MCP_DEVENV = "true"; - delete process.env.PENPOT_MCP_REPL_ENABLE; - let server: PenpotMcpServer | undefined; - try { - server = new PenpotMcpServer(true); - assert.equal(server.hasReplServer(), false); - } finally { - await server?.stop(); - restoreEnv(prevDevEnv, prevPorts); - restoreOrDelete("PENPOT_MCP_REPL_ENABLE", prevReplEnable); - } -}); - -test("constructor does not create ReplServer in multi-user mode even with explicit REPL_ENABLE", async () => { - const prevDevEnv = process.env.PENPOT_MCP_DEVENV; - const prevReplEnable = process.env.PENPOT_MCP_REPL_ENABLE; - const prevPorts = setUniqueEnv(); - delete process.env.PENPOT_MCP_DEVENV; - process.env.PENPOT_MCP_REPL_ENABLE = "true"; - let server: PenpotMcpServer | undefined; - try { - server = new PenpotMcpServer(true); - assert.equal(server.hasReplServer(), false); - } finally { - await server?.stop(); - restoreEnv(prevDevEnv, prevPorts); - restoreOrDelete("PENPOT_MCP_REPL_ENABLE", prevReplEnable); - } -}); - -test("replHost defaults to localhost and ignores SERVER_HOST", async () => { - const prevDevEnv = process.env.PENPOT_MCP_DEVENV; - const prevServerHost = process.env.PENPOT_MCP_SERVER_HOST; - const prevReplHost = process.env.PENPOT_MCP_REPL_HOST; - const prevPorts = setUniqueEnv(); - process.env.PENPOT_MCP_DEVENV = "true"; - process.env.PENPOT_MCP_SERVER_HOST = "0.0.0.0"; - delete process.env.PENPOT_MCP_REPL_HOST; - let server: PenpotMcpServer | undefined; - try { - server = new PenpotMcpServer(false); - assert.equal(server.hasReplServer(), true); - assert.equal(server.replHost, "localhost"); - } finally { - await server?.stop(); - restoreEnv(prevDevEnv, prevPorts); - restoreOrDelete("PENPOT_MCP_SERVER_HOST", prevServerHost); - restoreOrDelete("PENPOT_MCP_REPL_HOST", prevReplHost); - } -}); - -test("replHost respects PENPOT_MCP_REPL_HOST", async () => { - const prevDevEnv = process.env.PENPOT_MCP_DEVENV; - const prevReplHost = process.env.PENPOT_MCP_REPL_HOST; - const prevPorts = setUniqueEnv(); - process.env.PENPOT_MCP_DEVENV = "true"; - process.env.PENPOT_MCP_REPL_HOST = "0.0.0.0"; - let server: PenpotMcpServer | undefined; - try { - server = new PenpotMcpServer(false); - assert.equal(server.hasReplServer(), true); - assert.equal(server.replHost, "0.0.0.0"); - } finally { - await server?.stop(); - restoreEnv(prevDevEnv, prevPorts); - restoreOrDelete("PENPOT_MCP_REPL_HOST", prevReplHost); - } -}); - -// ── Helpers ──────────────────────────────────────────────────── - -function setUniqueEnv() { - const ports = uniquePorts(); - const prevServer = process.env.PENPOT_MCP_SERVER_PORT; - const prevWs = process.env.PENPOT_MCP_WEBSOCKET_PORT; - const prevRepl = process.env.PENPOT_MCP_REPL_PORT; - const prevReplHost = process.env.PENPOT_MCP_REPL_HOST; - process.env.PENPOT_MCP_SERVER_PORT = String(ports.server); - process.env.PENPOT_MCP_WEBSOCKET_PORT = String(ports.ws); - process.env.PENPOT_MCP_REPL_PORT = String(ports.repl); - delete process.env.PENPOT_MCP_REPL_HOST; - return { prevServer, prevWs, prevRepl, prevReplHost }; -} - -function restoreEnv( - devEnv: string | undefined, - ports: { - prevServer: string | undefined; - prevWs: string | undefined; - prevRepl: string | undefined; - prevReplHost: string | undefined; - } -) { - if (devEnv !== undefined) { - process.env.PENPOT_MCP_DEVENV = devEnv; - } else { - delete process.env.PENPOT_MCP_DEVENV; - } - restoreOrDelete("PENPOT_MCP_SERVER_PORT", ports.prevServer); - restoreOrDelete("PENPOT_MCP_WEBSOCKET_PORT", ports.prevWs); - restoreOrDelete("PENPOT_MCP_REPL_PORT", ports.prevRepl); - restoreOrDelete("PENPOT_MCP_REPL_HOST", ports.prevReplHost); -} - -function restoreOrDelete(key: string, value: string | undefined) { - if (value !== undefined) { - process.env[key] = value; - } else { - delete process.env[key]; - } -} diff --git a/mcp/packages/server/src/PenpotMcpServer.ts b/mcp/packages/server/src/PenpotMcpServer.ts deleted file mode 100644 index 4691c05..0000000 --- a/mcp/packages/server/src/PenpotMcpServer.ts +++ /dev/null @@ -1,516 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { AsyncLocalStorage } from "async_hooks"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import { ExecuteCodeTool } from "./tools/ExecuteCodeTool"; -import { PluginBridge } from "./PluginBridge"; -import { RedisBridge } from "./RedisBridge"; -import { ConfigurationLoader } from "./ConfigurationLoader"; -import { createLogger } from "./logger"; -import { Tool } from "./Tool"; -import { HighLevelOverviewTool } from "./tools/HighLevelOverviewTool"; -import { PenpotApiInfoTool } from "./tools/PenpotApiInfoTool"; -import { ExportShapeTool } from "./tools/ExportShapeTool"; -import { ImportImageTool } from "./tools/ImportImageTool"; -import { CljsReplTool } from "./tools/CljsReplTool"; -import { ImportPenpotFileTool } from "./tools/ImportPenpotFileTool"; -import { CljsCompilerOutputTool } from "./tools/CljsCompilerOutputTool"; -import { CljCheckParentheses } from "./tools/CljCheckParentheses"; -import { ReadTaigaIssueTool } from "./tools/ReadTaigaIssueTool"; -import { NreplClient } from "./NreplClient"; -import { ReplServer } from "./ReplServer"; -import { ApiDocs } from "./ApiDocs"; - -/** - * Session context for request-scoped data. - */ -export interface SessionContext { - userToken?: string; -} - -/** - * Represents an active Streamable HTTP session, grouping the transport, MCP server, and session metadata. - */ -class StreamableSession { - constructor( - public readonly transport: StreamableHTTPServerTransport, - public readonly userToken: string | undefined, - public lastActiveTime: number - ) {} -} - -/** - * Holds information about a registered tool, including its instance, name, and configuration. - */ -class ToolInfo { - constructor( - public readonly instance: Tool, - public readonly name: string, - public readonly config: { description: string; inputSchema: any } - ) {} -} - -/** - * Indicates whether developer tools may be registered for the current server mode. - */ -export function shouldRegisterDeveloperTools(isDevEnv: boolean, isMultiUserMode: boolean): boolean { - return isDevEnv && !isMultiUserMode; -} - -/** - * Indicates whether the REPL server may be started for the current server mode. - * - * The REPL server never starts in multi-user mode, even when explicitly - * enabled, mirroring the developer tools policy. - */ -export function shouldStartReplServer(isReplEnabled: boolean, isMultiUserMode: boolean): boolean { - return isReplEnabled && !isMultiUserMode; -} - -export class PenpotMcpServer { - /** - * Timeout, in minutes, for idle sessions (Streamable HTTP and SSE) before they are automatically closed and removed. - */ - private static readonly SESSION_TIMEOUT_MINUTES = 60; - - /** - * Determines whether the server is running in a Penpot development - * environment, based on the given environment variables. - * - * Returns ``true`` only when ``PENPOT_MCP_DEVENV`` is ``"true"``. - */ - public static isDevEnvEnabled(env: Record): boolean { - return env.PENPOT_MCP_DEVENV === "true"; - } - - /** - * Determines whether the REPL server should be enabled. - * - * If ``PENPOT_MCP_REPL_ENABLE`` is set, its value controls the result - * (``"true"`` enables, any other value disables). When the variable is - * not set, the result falls back to {@link isDevEnvEnabled}. - */ - public static isReplEnabled(env: Record): boolean { - if (env.PENPOT_MCP_REPL_ENABLE !== undefined) { - return env.PENPOT_MCP_REPL_ENABLE === "true"; - } - return PenpotMcpServer.isDevEnvEnabled(env); - } - - /** - * Returns a short, non-reversible fingerprint of a user token, suitable for - * correlating log lines without exposing the full credential. - * - * Penpot tokens are JWEs in compact serialization (RFC 7516 §7.1) with five - * dot-separated segments; we use the first 8 chars of the wrapped CEK - * (segment 1) as a stable per-token identifier. For malformed tokens (e.g. - * test stubs that aren't real JWEs), we fall back to the first 8 chars of - * the raw token. - * - * @param token - the token to fingerprint, or `undefined` - * @returns a short fingerprint, or `` if no token was given - */ - private static tokenFingerprint(token: string | undefined): string { - if (!token) { - return ""; - } - const segments = token.split("."); - const source = segments.length === 5 ? segments[1] : token; - return source.slice(0, 8); - } - - private readonly logger = createLogger("PenpotMcpServer"); - private readonly tools: ToolInfo[]; - public readonly configLoader: ConfigurationLoader; - private app: any; - public readonly pluginBridge: PluginBridge; - private readonly replServer: ReplServer | null; - private apiDocs: ApiDocs; - private readonly penpotHighLevelOverview: string; - private readonly connectionInstructions: string; - - /** - * Manages session-specific context, particularly user tokens for each request. - */ - private readonly sessionContext = new AsyncLocalStorage(); - - private readonly streamableTransports: Record = {}; - private readonly sseTransports: Record< - string, - { transport: SSEServerTransport; userToken?: string; lastActiveTime: number } - > = {}; - - public readonly host: string; - public readonly port: number; - public readonly webSocketPort: number; - public readonly replHost: string; - public readonly replPort: number; - private sessionTimeoutInterval: ReturnType | undefined; - - /** - * Optional Redis bridge for multi-instance task routing; present only when running - * in multi-user mode with a configured Redis URI. - */ - private readonly redisBridge?: RedisBridge; - - /** - * Tenant identifier, read from the `PENPOT_TENANT` environment variable. - * - * Used to qualify Redis channel names so that multiple environments sharing a - * Redis instance do not interfere with each other. Defaults to `"default"`, - * matching the backend default. - */ - private readonly tenant: string; - - constructor(private isMultiUser: boolean = false) { - // read port configuration from environment variables - this.host = process.env.PENPOT_MCP_SERVER_HOST ?? "localhost"; - this.port = parseInt(process.env.PENPOT_MCP_SERVER_PORT ?? "4401", 10); - this.webSocketPort = parseInt(process.env.PENPOT_MCP_WEBSOCKET_PORT ?? "4402", 10); - this.replHost = process.env.PENPOT_MCP_REPL_HOST ?? "localhost"; - this.replPort = parseInt(process.env.PENPOT_MCP_REPL_PORT ?? "4403", 10); - this.tenant = process.env.PENPOT_TENANT ?? "default"; - const toolTimeoutSecs = parseInt(process.env.PENPOT_MCP_TOOL_TIMEOUT_S ?? "120", 10); - - this.configLoader = new ConfigurationLoader(process.cwd()); - this.apiDocs = new ApiDocs(); - - // prepare instructions - let instructions = this.configLoader.getInitialInstructions(); - instructions = instructions.replace("$api_types", this.apiDocs.getTypeNames().join(", ")); - this.penpotHighLevelOverview = instructions; - this.connectionInstructions = this.configLoader.getBaseInstructions(); - - this.tools = this.initTools(); - - // Enable multi-instance task routing when running in multi-user mode with a - // configured Redis URI. Without it, the server operates in single-instance mode, - // requiring the plugin and the MCP client to connect to the same instance. - const redisUri = process.env.PENPOT_MCP_REDIS_URI; - if (this.isMultiUser && redisUri) { - this.redisBridge = new RedisBridge(redisUri, this.tenant); - } - - this.pluginBridge = new PluginBridge(this, this.webSocketPort, toolTimeoutSecs, this.redisBridge); - - if (shouldStartReplServer(PenpotMcpServer.isReplEnabled(process.env), this.isMultiUserMode())) { - this.replServer = new ReplServer(this.pluginBridge, this.replPort, this.replHost); - } else { - this.replServer = null; - } - } - - /** - * Indicates whether the server is running in multi-user mode, - * where user tokens are required for authentication. - */ - public isMultiUserMode(): boolean { - return this.isMultiUser; - } - - /** - * Indicates whether the server is running in remote mode. - * - * In remote mode, the server is not assumed to be accessed only by a local user on the same machine, - * with corresponding limitations being enforced. - * Remote mode can be explicitly enabled by setting the environment variable PENPOT_MCP_REMOTE_MODE - * to "true". Enabling multi-user mode forces remote mode, regardless of the value of the environment - * variable. - */ - public isRemoteMode(): boolean { - const isRemoteModeRequested: boolean = process.env.PENPOT_MCP_REMOTE_MODE === "true"; - return this.isMultiUserMode() || isRemoteModeRequested; - } - - /** - * Indicates whether file system access is enabled for MCP tools. - * Access is enabled only in local mode, where the file system is assumed - * to belong to the user running the server locally. - */ - public isFileSystemAccessEnabled(): boolean { - return !this.isRemoteMode(); - } - - /** - * Indicates whether the server is running in a Penpot development environment. - * - * When enabled (by setting the environment variable PENPOT_MCP_DEVENV to "true"), - * additional developer tools such as ClojureScript expression evaluation are exposed. - */ - public isDevEnv(): boolean { - return PenpotMcpServer.isDevEnvEnabled(process.env); - } - - /** - * Indicates whether the REPL server was created. - * - * The REPL server is created when {@link isReplEnabled} returns true and - * the server is not running in multi-user mode, which means either - * ``PENPOT_MCP_REPL_ENABLE=true`` or, when that variable is unset, - * ``PENPOT_MCP_DEVENV=true``, in single-user mode. - */ - public hasReplServer(): boolean { - return this.replServer !== null; - } - - /** - * Retrieves the high-level overview instructions explaining core Penpot usage. - */ - public getHighLevelOverviewInstructions(): string { - return this.penpotHighLevelOverview; - } - - /** - * Retrieves the current session context. - * - * @returns The session context for the current request, or undefined if not in a request context - */ - public getSessionContext(): SessionContext | undefined { - return this.sessionContext.getStore(); - } - - private initTools(): ToolInfo[] { - const toolInstances: Tool[] = [ - new ExecuteCodeTool(this), - new HighLevelOverviewTool(this), - new PenpotApiInfoTool(this, this.apiDocs), - new ExportShapeTool(this), - ]; - if (this.isFileSystemAccessEnabled()) { - toolInstances.push(new ImportImageTool(this)); - } - if (shouldRegisterDeveloperTools(this.isDevEnv(), this.isMultiUserMode())) { - const nreplClient = new NreplClient(); - toolInstances.push(new CljsReplTool(this, nreplClient)); - toolInstances.push(new ImportPenpotFileTool(this, nreplClient)); - toolInstances.push(new CljsCompilerOutputTool(this, nreplClient)); - toolInstances.push(new CljCheckParentheses(this)); - toolInstances.push(new ReadTaigaIssueTool(this)); - } - - return toolInstances.map((instance) => { - this.logger.info(`Registering tool: ${instance.getToolName()}`); - return new ToolInfo(instance, instance.getToolName(), { - description: instance.getToolDescription(), - inputSchema: instance.getInputSchema(), - }); - }); - } - - /** - * Creates a fresh {@link McpServer} instance with all tools registered. - */ - private createMcpServer(): McpServer { - const server = new McpServer( - { name: "penpot", version: "1.0.0" }, - { instructions: this.connectionInstructions } - ); - - for (const tool of this.tools) { - server.registerTool(tool.name, tool.config, async (args: any) => tool.instance.execute(args)); - } - - return server; - } - - /** - * Starts a periodic timer that closes and removes Streamable HTTP and SSE sessions that have been - * idle for longer than {@link SESSION_TIMEOUT_MINUTES}. - */ - private startSessionTimeoutChecker(): void { - const timeoutMs = PenpotMcpServer.SESSION_TIMEOUT_MINUTES * 60 * 1000; - const checkIntervalMs = timeoutMs / 2; - this.sessionTimeoutInterval = setInterval(() => { - this.logger.info("Checking for stale sessions..."); - const now = Date.now(); - let removed = 0; - for (const session of Object.values(this.streamableTransports)) { - if (now - session.lastActiveTime > timeoutMs) { - session.transport.close(); - removed++; - } - } - for (const [id, session] of Object.entries(this.sseTransports)) { - if (now - session.lastActiveTime > timeoutMs) { - this.logger.info(`Closing stale SSE session ${id}`); - session.transport.close(); - delete this.sseTransports[id]; - removed++; - } - } - this.logger.info( - `Removed ${removed} stale session(s); total sessions remaining: ${ - Object.keys(this.streamableTransports).length + Object.keys(this.sseTransports).length - }` - ); - }, checkIntervalMs); - } - - private setupHttpEndpoints(): void { - /** - * Modern Streamable HTTP connection endpoint. - * - * New sessions are created on initialize requests (no mcp-session-id header). - * Subsequent requests for an existing session are routed to the stored transport, - * with the session context populated from the stored userToken. - */ - this.app.all("/mcp", async (req: any, res: any) => { - const sessionId = req.headers["mcp-session-id"] as string | undefined; - let userToken: string | undefined = undefined; - let transport: StreamableHTTPServerTransport; - - // obtain transport and user token for the session, either from an existing session or by creating a new one - if (sessionId && this.streamableTransports[sessionId]) { - // existing session: reuse stored transport and token - const session = this.streamableTransports[sessionId]; - transport = session.transport; - userToken = session.userToken; - session.lastActiveTime = Date.now(); - this.logger.info( - `Received request for existing session with id=${sessionId}; userTokenFp=${PenpotMcpServer.tokenFingerprint(session.userToken)}` - ); - } else { - // No locally-known session for this request. Either a brand-new session - // (no session ID) or a session that was initialized on another instance - // and routed here by the load balancer (session ID present but unknown - // locally), which we adopt rather than reject. - const isAdoptedSession = sessionId !== undefined; - userToken = req.query.userToken as string | undefined; - this.logger.info( - `${isAdoptedSession ? `Adopting session initialized on another instance with id=${sessionId}` : "Received new session request"}; userTokenFp=${PenpotMcpServer.tokenFingerprint(userToken)}` - ); - - const { randomUUID } = await import("node:crypto"); - const server = this.createMcpServer(); - transport = new StreamableHTTPServerTransport({ - // For an adopted session, reuse the existing ID; otherwise generate a new one. - sessionIdGenerator: () => (isAdoptedSession ? sessionId! : randomUUID()), - onsessioninitialized: (id) => { - this.streamableTransports[id] = new StreamableSession(transport, userToken, Date.now()); - this.logger.info( - `Session initialized with id=${id} for userTokenFp=${PenpotMcpServer.tokenFingerprint(userToken)}; total sessions: ${Object.keys(this.streamableTransports).length}` - ); - }, - }); - - if (isAdoptedSession) { - // Pre-initialize the transport so that the SDK's validateSession() accepts - // subsequent (non-initialize) requests for this session ID. The SDK stores - // these on the inner WebStandardStreamableHTTPServerTransport as plain - // (non-#private) properties; validateSession() checks exactly _initialized - // and sessionId. Verified against @modelcontextprotocol/sdk 1.25.3. - // - // Since no initialize request will arrive for an adopted session, the - // onsessioninitialized callback will not fire; register the session here. - const inner = (transport as any)._webStandardTransport; - inner._initialized = true; - inner.sessionId = sessionId; - this.streamableTransports[sessionId!] = new StreamableSession(transport, userToken, Date.now()); - } - - transport.onclose = () => { - if (transport.sessionId) { - this.logger.info( - `Closing session with id=${transport.sessionId} for userTokenFp=${PenpotMcpServer.tokenFingerprint(userToken)}` - ); - delete this.streamableTransports[transport.sessionId]; - } - }; - await server.connect(transport); - } - - // handle the request - await this.sessionContext.run({ userToken }, async () => { - await transport.handleRequest(req, res, req.body); - }); - }); - - /** - * Legacy SSE connection endpoint. - */ - this.app.get("/sse", async (req: any, res: any) => { - const userToken = req.query.userToken as string | undefined; - - await this.sessionContext.run({ userToken }, async () => { - const transport = new SSEServerTransport("/messages", res); - this.sseTransports[transport.sessionId] = { transport, userToken, lastActiveTime: Date.now() }; - - const server = this.createMcpServer(); - await server.connect(transport); - res.on("close", () => { - delete this.sseTransports[transport.sessionId]; - server.close(); - }); - }); - }); - - /** - * SSE message POST endpoint (using previously established session) - */ - this.app.post("/messages", async (req: any, res: any) => { - const sessionId = req.query.sessionId as string; - const session = this.sseTransports[sessionId]; - - if (session) { - session.lastActiveTime = Date.now(); - await this.sessionContext.run({ userToken: session.userToken }, async () => { - await session.transport.handlePostMessage(req, res, req.body); - }); - } else { - res.status(400).send("No transport found for sessionId"); - } - }); - } - - async start(): Promise { - const { default: express } = await import("express"); - this.app = express(); - this.app.use(express.json()); - - this.setupHttpEndpoints(); - - return new Promise((resolve) => { - this.app.listen(this.port, this.host, async () => { - this.logger.info(`Multi-user mode: ${this.isMultiUserMode()}`); - this.logger.info( - `Multi-instance mode with Redis-backed transport: ${this.redisBridge ? "true" : "false"}` - ); - this.logger.info(`Remote mode: ${this.isRemoteMode()}`); - this.logger.info(`DevEnv mode: ${this.isDevEnv()}`); - this.logger.info(`Modern Streamable HTTP endpoint: http://${this.host}:${this.port}/mcp`); - this.logger.info(`Legacy SSE endpoint: http://${this.host}:${this.port}/sse`); - this.logger.info(`WebSocket server URL: ws://${this.host}:${this.webSocketPort}`); - - // start the REPL server (devenv only) and session timeout checker - if (this.replServer) { - await this.replServer.start(); - } else if (this.isMultiUserMode()) { - this.logger.info("REPL server disabled in multi-user mode (never started with --multi-user)"); - } else { - this.logger.info( - "REPL server disabled (set PENPOT_MCP_REPL_ENABLE=true or PENPOT_MCP_DEVENV=true to enable)" - ); - } - this.startSessionTimeoutChecker(); - - resolve(); - }); - }); - } - - /** - * Stops the MCP server and associated services. - * - * Gracefully shuts down the REPL server and other components. - */ - public async stop(): Promise { - this.logger.info("Stopping Penpot MCP Server..."); - clearInterval(this.sessionTimeoutInterval); - await this.pluginBridge.close(); - await this.redisBridge?.close(); - if (this.replServer) { - await this.replServer.stop(); - } - this.logger.info("Penpot MCP Server stopped"); - } -} diff --git a/mcp/packages/server/src/PluginBridge.test.ts b/mcp/packages/server/src/PluginBridge.test.ts deleted file mode 100644 index b0d1593..0000000 --- a/mcp/packages/server/src/PluginBridge.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import assert from "node:assert/strict"; -import test from "node:test"; -import { assertPluginResponsive, HEARTBEAT_STALE_THRESHOLD_MS } from "./PluginBridge"; - -test("passes for a responsive connection with a recent heartbeat", () => { - const now = 1_000_000; - assert.doesNotThrow(() => assertPluginResponsive({ frozen: false, lastHeartbeat: now - 5_000 }, now)); -}); - -test("passes when the heartbeat age is exactly at the threshold", () => { - const now = 1_000_000; - const lastHeartbeat = now - HEARTBEAT_STALE_THRESHOLD_MS; - assert.doesNotThrow(() => assertPluginResponsive({ frozen: false, lastHeartbeat }, now)); -}); - -test("throws a frozen-specific error when the tab reported it is being frozen", () => { - const now = 1_000_000; - assert.throws(() => assertPluginResponsive({ frozen: true, lastHeartbeat: now }, now), /has been frozen/); -}); - -test("throws a suspended error when no heartbeat has arrived within the threshold", () => { - const now = 1_000_000; - const lastHeartbeat = now - (HEARTBEAT_STALE_THRESHOLD_MS + 1); - assert.throws( - () => assertPluginResponsive({ frozen: false, lastHeartbeat }, now), - /appears to be suspended by the browser/ - ); -}); - -test("includes the heartbeat age, in seconds, in the suspended error", () => { - const now = 1_000_000; - const lastHeartbeat = now - 45_000; - assert.throws(() => assertPluginResponsive({ frozen: false, lastHeartbeat }, now), /no heartbeat for 45s/); -}); - -test("honours a custom stale threshold", () => { - const now = 1_000_000; - const lastHeartbeat = now - 2_000; - - assert.doesNotThrow(() => assertPluginResponsive({ frozen: false, lastHeartbeat }, now)); - assert.throws( - () => assertPluginResponsive({ frozen: false, lastHeartbeat }, now, 1_000), - /appears to be suspended by the browser/ - ); -}); diff --git a/mcp/packages/server/src/PluginBridge.ts b/mcp/packages/server/src/PluginBridge.ts deleted file mode 100644 index 305f5dc..0000000 --- a/mcp/packages/server/src/PluginBridge.ts +++ /dev/null @@ -1,482 +0,0 @@ -import { WebSocket, WebSocketServer } from "ws"; -import * as http from "http"; -import { AbstractPluginTask, PluginTask } from "./PluginTask"; -import { RemotePluginTask } from "./RemotePluginTask"; -import { PluginTaskRequest, PluginTaskResponse, PluginTaskResult } from "@penpot/mcp-common"; -import { createLogger } from "./logger"; -import type { PenpotMcpServer } from "./PenpotMcpServer"; -import type { RedisBridge } from "./RedisBridge"; - -const KEEP_ALIVE_TIME = 30000; // 30 seconds - -/** - * Maximum plugin heartbeat age before a connection is stale. - * - * This uses plugin heartbeats rather than WebSocket pongs because the browser can answer - * protocol pings while the tab's JavaScript event loop is frozen. - */ -export const HEARTBEAT_STALE_THRESHOLD_MS = 30000; - -/** - * Observable liveness state of a plugin connection. - */ -export interface PluginLivenessState { - /** timestamp of the last plugin message, in ms since epoch. */ - lastHeartbeat: number; - /** whether the plugin reported a browser freeze. */ - frozen: boolean; -} - -interface ClientConnection extends PluginLivenessState { - socket: WebSocket; - userToken: string | null; - pingInterval: NodeJS.Timeout; -} - -/** - * Throws if the plugin tab cannot currently run tasks. - * - * A socket can stay open while the page event loop is paused, so task dispatch must check - * plugin-level liveness before sending work. - */ -export function assertPluginResponsive( - state: PluginLivenessState, - now: number, - staleThresholdMs: number = HEARTBEAT_STALE_THRESHOLD_MS -): void { - if (state.frozen) { - throw new Error( - `The Penpot plugin tab has been frozen by the browser and cannot run tasks. ` + - `Please click/focus the Penpot tab to wake it, then retry.` - ); - } - - const heartbeatAge = now - state.lastHeartbeat; - if (heartbeatAge > staleThresholdMs) { - throw new Error( - `The Penpot plugin tab appears to be suspended by the browser (no heartbeat for ` + - `${Math.round(heartbeatAge / 1000)}s). Please click/focus the Penpot tab to wake it, ` + - `then retry.` - ); - } -} - -/** - * Manages WebSocket connections to Penpot plugin instances and handles plugin tasks - * over these connections. - */ -export class PluginBridge { - public static readonly MULTIUSER_CONNECTION_ERROR_MESSAGE = `No Penpot instance connected for user token. Please ensure that Penpot is connected and that the MCP client connection is using the correct token.`; - - private readonly logger = createLogger("PluginBridge"); - private readonly wsServer: WebSocketServer; - - private readonly connectedClients: Map = new Map(); - private readonly clientsByToken: Map = new Map(); - private readonly pendingTasks: Map> = new Map(); - private readonly taskTimeouts: Map = new Map(); - - /** - * Creates the plugin bridge and starts its WebSocket server. - * - * @param mcpServer - The owning MCP server - * @param port - The port on which to listen for plugin WebSocket connections - * @param redisBridge - Optional Redis bridge enabling multi-instance task routing. - * When provided, tasks handled by this instance are routed to the instance - * holding the relevant plugin's WebSocket connection (which may be this same - * instance) via Redis, rather than dispatched directly over a local socket. - * @param taskTimeoutSecs - Timeout, in seconds, for plugin task execution - * (defaults to {@link DEFAULT_TASK_TIMEOUT_SECS}) - */ - constructor( - public readonly mcpServer: PenpotMcpServer, - private port: number, - private readonly taskTimeoutSecs: number, - private readonly redisBridge?: RedisBridge - ) { - this.wsServer = new WebSocketServer({ port: port, host: mcpServer.host }); - this.setupWebSocketHandlers(); - } - - /** - * Sets up WebSocket connection handlers for plugin communication. - * - * Manages client connections and provides bidirectional communication - * channel between the MCP mcpServer and Penpot plugin instances. - */ - private setupWebSocketHandlers(): void { - this.wsServer.on("connection", (ws: WebSocket, request: http.IncomingMessage) => { - // extract userToken from query parameters - const url = new URL(request.url!, `ws://${request.headers.host}`); - const userToken = url.searchParams.get("userToken"); - - // require userToken if running in multi-user mode - if (this.mcpServer.isMultiUserMode() && !userToken) { - this.logger.warn("Connection attempt without userToken in multi-user mode - rejecting"); - ws.close(1008, "Missing userToken parameter"); - return; - } - - if (userToken) { - this.logger.info("New WebSocket connection established (token provided)"); - } else { - this.logger.info("New WebSocket connection established"); - } - - // start the per-connection keep-alive ping interval - const pingInterval = setInterval(() => { - ws.ping(); - }, KEEP_ALIVE_TIME); - - // register the client connection with both indexes - const connection: ClientConnection = { - socket: ws, - userToken, - pingInterval, - lastHeartbeat: Date.now(), - frozen: false, - }; - this.connectedClients.set(ws, connection); - if (userToken) { - // ensure only one connection per userToken - if (this.clientsByToken.has(userToken)) { - this.logger.warn("Duplicate connection for given user token; rejecting new connection"); - this.removeConnection(ws); - ws.close(1008, "Duplicate connection for given user token; close previous connection first."); - return; - } - - this.clientsByToken.set(userToken, connection); - - // In multi-instance mode, subscribe to this token's Redis request channel so - // that task requests issued by other instances are dispatched to this plugin. - if (this.redisBridge) { - const tokenForSubscription = userToken; - this.redisBridge - .subscribeToTasks(userToken, (request) => - this.dispatchForwardedTask(tokenForSubscription, request) - ) - .catch((error) => this.logger.error(error, "Failed to subscribe to Redis task channel")); - } - } - - ws.on("message", (data: Buffer) => { - this.logger.debug("Received WebSocket message: %s", data.toString()); - try { - // any plugin message proves the page event loop is running - connection.lastHeartbeat = Date.now(); - - const message = JSON.parse(data.toString()); - if (message?.type === "freeze") { - connection.frozen = true; - this.logger.info("Plugin tab reported it is being frozen by the browser"); - return; - } - connection.frozen = false; - if (message?.type === "heartbeat") { - return; - } - this.handlePluginTaskResponse(message as PluginTaskResponse); - } catch (error) { - this.logger.error(error, "Failure while processing WebSocket message"); - } - }); - - ws.on("close", () => { - this.logger.info("WebSocket connection closed"); - this.removeConnection(ws); - }); - - ws.on("error", (error) => { - this.logger.error(error, "WebSocket connection error"); - this.removeConnection(ws); - }); - }); - - this.logger.info("WebSocket mcpServer started on port %d", this.port); - } - - /** - * Removes a client connection and releases all resources associated with it. - * - * Clears the per-connection keep-alive interval and removes the connection from the - * socket-keyed index. The token-keyed index entry (and, in multi-instance mode, the - * token's Redis task subscription) is removed only if it is owned by the given - * connection. Safe to call with a socket that is not (or no longer) registered. - * - * @param ws - The WebSocket whose connection state should be removed - */ - private removeConnection(ws: WebSocket): void { - const connection = this.connectedClients.get(ws); - if (!connection) { - return; - } - clearInterval(connection.pingInterval); - this.connectedClients.delete(ws); - if (connection.userToken) { - // Perform the token-keyed cleanup only if this connection owns the token registration. - // A connection rejected as a duplicate carries the same token but must not remove token associations. - if (this.clientsByToken.get(connection.userToken) !== connection) { - this.logger.debug("Removed connection does not own its token registration; skipping token cleanup"); - } else { - this.clientsByToken.delete(connection.userToken); - - if (this.redisBridge) { - this.redisBridge - .unsubscribeFromTasks(connection.userToken) - .catch((error) => this.logger.error(error, "Failed to unsubscribe from Redis task channel")); - } - } - } - } - - /** - * Handles responses from the plugin for completed tasks. - * - * Finds the pending task by ID and resolves or rejects its promise - * based on the execution result. - * - * @param response - The plugin task response containing ID and result - */ - private handlePluginTaskResponse(response: PluginTaskResponse): void { - const task = this.pendingTasks.get(response.id); - if (!task) { - this.logger.info(`Received response for unknown task ID: ${response.id}`); - return; - } - - // Clear the timeout and remove the task from pending tasks - const timeoutHandle = this.taskTimeouts.get(response.id); - if (timeoutHandle) { - clearTimeout(timeoutHandle); - this.taskTimeouts.delete(response.id); - } - this.pendingTasks.delete(response.id); - - // Resolve or reject the task's promise based on the result - if (response.success) { - task.resolveWithResult({ data: response.data }); - } else { - const error = new Error(response.error || "Task execution failed (details not provided)"); - task.rejectWithError(error); - } - - this.logger.info(`Task ${response.id} completed: success=${response.success}`); - } - - /** - * Rejects a still-pending task with the given error, releasing its correlation state. - * - * Clears the task's timeout (if armed) and removes the task from the pending-task - * index before rejecting its promise. Safe to call for a task that has already been - * settled (e.g. by a response or a timeout), in which case nothing happens. - * - * @param taskId - The ID of the task to reject - * @param error - The error with which to reject the task - * @returns Whether the task was still pending and has been rejected - */ - private rejectPendingTask(taskId: string, error: Error): boolean { - const pendingTask = this.pendingTasks.get(taskId); - if (!pendingTask) { - return false; - } - - const timeoutHandle = this.taskTimeouts.get(taskId); - if (timeoutHandle) { - clearTimeout(timeoutHandle); - this.taskTimeouts.delete(taskId); - } - this.pendingTasks.delete(taskId); - - pendingTask.rejectWithError(error); - this.logger.info(`Task ${taskId} rejected: ${error.message}`); - return true; - } - - /** - * Determines the client connection to use for executing a task. - * - * In single-user mode, returns the single connected client. - * In multi-user mode, returns the client matching the session's userToken. - * - * @returns The client connection to use - * @throws Error if no suitable connection is found or if configuration is invalid - */ - private getClientConnection(): ClientConnection { - if (this.mcpServer.isMultiUserMode()) { - const sessionContext = this.mcpServer.getSessionContext(); - if (!sessionContext?.userToken) { - throw new Error("No userToken found in session context. Multi-user mode requires authentication."); - } - - const connection = this.clientsByToken.get(sessionContext.userToken); - if (!connection) { - throw new Error(PluginBridge.MULTIUSER_CONNECTION_ERROR_MESSAGE); - } - - return connection; - } else { - // single-user mode: return the single connected client - if (this.connectedClients.size === 0) { - throw new Error( - `No Penpot plugin instances are currently connected. Please ensure the plugin is running and connected.` - ); - } - if (this.connectedClients.size > 1) { - throw new Error( - `Multiple (${this.connectedClients.size}) Penpot MCP Plugin instances are connected. ` + - `Ask the user to ensure that only one instance is connected at a time.` - ); - } - - // return the first (and only) connection - const connection = this.connectedClients.values().next().value; - return connection; - } - } - - /** - * Executes a plugin task by sending it to the connected Penpot plugin instance, - * either directly via WebSocket or indirectly via Redis (depending on the configuration), - * and awaiting the result. - * - * @param task - The plugin task to execute - * @throws Error if no plugin instances are connected or available - */ - public async executePluginTask>( - task: PluginTask - ): Promise { - this.sendPluginTask(task, this.redisBridge !== undefined); - return await task.getResultPromise(); - } - - /** - * Registers a task for response correlation, sends its request over the appropriate - * transport, and arms a timeout that rejects the task if no response is received. - * - * The response (whether arriving over the local WebSocket or over Redis) is later - * matched by ID in {@link handlePluginTaskResponse}, which settles the task via its - * `resolveWithResult`/`rejectWithError` methods. The same correlation and timeout - * handling therefore applies regardless of the transport. - * - * When routing via Redis, the task is rejected immediately (rather than timing out) - * if the published request reached no instance, i.e. if no instance holds a plugin - * connection for the session's user token, or if publishing fails outright. - * - * @param task - The task to dispatch - * @param useRedis - Whether to route the request via Redis (multi-instance) rather - * than directly over the local WebSocket connection - * @param connection - The connection to use for a local (non-remote) dispatch; when - * omitted, the session's connection is resolved via {@link getClientConnection}. - * Ignored when `useRedis` is true. - * @throws Error if a local dispatch is required but no suitable connection is available - */ - private sendPluginTask(task: AbstractPluginTask, useRedis: boolean, connection?: ClientConnection): void { - let onTimeout: (() => void) | undefined; - - if (useRedis) { - const sessionContext = this.mcpServer.getSessionContext(); - if (!sessionContext?.userToken) { - throw new Error("No userToken found in session context. Multi-user mode requires authentication."); - } - const userToken = sessionContext.userToken; - const redisBridge = this.redisBridge!; - this.logger.debug("Dispatching task %s via Redis", task.id); - - // register the task for result correlation, then publish the request via Redis - this.pendingTasks.set(task.id, task); - void redisBridge - .sendTaskRequest(userToken, task.toRequest(), (response) => this.handlePluginTaskResponse(response)) - .then((receiverCount) => { - // fail fast when no instance received the request (no connection with matching user token in any instance) - if (receiverCount === 0) { - this.rejectPendingTask(task.id, new Error(PluginBridge.MULTIUSER_CONNECTION_ERROR_MESSAGE)); - } - }) - .catch((error) => { - this.rejectPendingTask(task.id, error instanceof Error ? error : new Error(String(error))); - }); - - // on timeout, release the response-channel subscription, since no response - // will arrive to trigger its self-unsubscribe. - onTimeout = () => void redisBridge.unsubscribeFromResponse(task.id); - } else { - const target = connection ?? this.getClientConnection(); - if (target.socket.readyState !== 1) { - // WebSocket is not open - throw new Error(`Plugin instance is disconnected. Task could not be sent.`); - } - - // the socket can be open while browser-throttled plugin JS cannot run tasks - assertPluginResponsive(target, Date.now()); - - // register the task for result correlation, then send over the socket - this.pendingTasks.set(task.id, task); - target.socket.send(JSON.stringify(task.toRequest())); - } - - // Set up a timeout to reject the task if no response is received - const timeoutHandle = setTimeout(() => { - if ( - this.rejectPendingTask( - task.id, - new Error(`Task ${task.id} timed out after ${this.taskTimeoutSecs} seconds`) - ) - ) { - onTimeout?.(); - } - }, this.taskTimeoutSecs * 1000); - - this.taskTimeouts.set(task.id, timeoutHandle); - this.logger.info(`Sent task ${task.id}`); - } - - /** - * Dispatches a task request received over Redis to the locally-connected plugin. - * - * Invoked on the instance subscribed to a user token's request channel when another - * instance (or this one) issues a task request. A {@link RemotePluginTask} is created - * so that, once the plugin responds, the outcome is published back to the issuing - * instance's Redis response channel via the standard response-handling path. - * - * On failure to dispatch (e.g. the plugin is not connected here), an error response - * is published immediately so the requester need not wait for its timeout. - * - * @param userToken - The user token on whose request channel the request arrived; - * identifies the locally-connected plugin to dispatch to - * @param request - The serialized task request, passed through from Redis - */ - private dispatchForwardedTask(userToken: string, request: PluginTaskRequest): void { - if (!this.redisBridge) { - return; - } - - // The response is published on the channel keyed by the original request ID. - const task = new RemotePluginTask(request.task, request.params, this.redisBridge, request.id); - this.logger.debug("Dispatching remote task %s as %s to Penpot via WebSocket", request.id, task.id); - - const connection = this.clientsByToken.get(userToken); - if (!connection) { - task.rejectWithError(new Error("Plugin not connected on the receiving instance")); - return; - } - - try { - this.sendPluginTask(task, false, connection); - } catch (error) { - task.rejectWithError(error instanceof Error ? error : new Error(String(error))); - } - } - - /** - * Closes the WebSocket server and all connected client sockets. - */ - public async close(): Promise { - return new Promise((resolve) => { - this.wsServer.close(() => { - this.logger.info("WebSocket server closed"); - resolve(); - }); - }); - } -} diff --git a/mcp/packages/server/src/PluginTask.ts b/mcp/packages/server/src/PluginTask.ts deleted file mode 100644 index 0e0d327..0000000 --- a/mcp/packages/server/src/PluginTask.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Base classes for plugin tasks that are dispatched to a Penpot plugin instance - * over a WebSocket connection. - * - * A task defines a specific operation for the plugin to execute along with - * strongly-typed parameters and provides request/response correlation. - */ -import { PluginTaskRequest, PluginTaskResult } from "@penpot/mcp-common"; -import { randomUUID } from "crypto"; - -/** - * Abstract base for plugin tasks, defining the parts that the plugin dispatch and - * response-correlation machinery (`PluginBridge.sendPluginTask` / - * `PluginBridge.handlePluginTaskResponse`) depend upon. - * - * The dispatch path only needs to serialize a task to a request and, upon receiving - * the plugin's response, settle the task via `resolveWithResult`/`rejectWithError`. - * What "settling" actually means is left to subclasses: a local task resolves an - * in-process promise (see {@link PluginTask}), whereas a remote task forwards the - * outcome elsewhere (see {@link RemotePluginTask}). - * - * @template TParams - The strongly-typed parameters for this task - * @template TResult - The expected result type from task execution - */ -export abstract class AbstractPluginTask = PluginTaskResult> { - /** - * Unique identifier for request/response correlation. - */ - public readonly id: string; - - /** - * The name of the task to execute on the plugin side. - */ - public readonly task: string; - - /** - * The parameters for this task execution. - */ - public readonly params: TParams; - - /** - * Creates a new plugin task instance. - * - * @param task - The name of the task to execute - * @param params - The parameters for task execution - */ - protected constructor(task: string, params: TParams) { - this.id = randomUUID(); - this.task = task; - this.params = params; - } - - /** - * Serializes the task to a request message for transmission to the plugin. - * - * @returns The request message containing ID, task name, and parameters - */ - toRequest(): PluginTaskRequest { - return { - id: this.id, - task: this.task, - params: this.params, - }; - } - - /** - * Settles the task successfully with the given result. - * - * Called by the response-correlation machinery when the plugin reports success - * for the task with the matching ID. - * - * @param result - The task execution result - */ - abstract resolveWithResult(result: TResult): void; - - /** - * Settles the task unsuccessfully with the given error. - * - * Called by the response-correlation machinery when task execution fails - * or times out. - * - * @param error - The error that occurred during task execution - */ - abstract rejectWithError(error: Error): void; -} - -/** - * A locally-awaited plugin task. - * - * The task's outcome is exposed as an in-process promise (see {@link getResultPromise}), - * which the caller awaits to obtain the result. This is the task type used by tools that - * execute operations on the plugin and consume the result directly. - * - * @template TParams - The strongly-typed parameters for this task - * @template TResult - The expected result type from task execution - */ -export class PluginTask< - TParams = any, - TResult extends PluginTaskResult = PluginTaskResult, -> extends AbstractPluginTask { - /** - * Promise that resolves when the task execution completes. - */ - private readonly result: Promise; - - /** - * Resolver function for the result promise. - */ - private resolveResult?: (result: TResult) => void; - - /** - * Rejector function for the result promise. - */ - private rejectResult?: (error: Error) => void; - - /** - * Creates a new locally-awaited plugin task. - * - * @param task - The name of the task to execute - * @param params - The parameters for task execution - */ - constructor(task: string, params: TParams) { - super(task, params); - this.result = new Promise((resolve, reject) => { - this.resolveResult = resolve; - this.rejectResult = reject; - }); - } - - /** - * Gets the result promise for this task. - * - * @returns Promise that resolves when the task execution completes - */ - getResultPromise(): Promise { - if (!this.result) { - throw new Error("Result promise not initialized"); - } - return this.result; - } - - resolveWithResult(result: TResult): void { - if (!this.resolveResult) { - throw new Error("Result promise not initialized"); - } - this.resolveResult(result); - } - - rejectWithError(error: Error): void { - if (!this.rejectResult) { - throw new Error("Result promise not initialized"); - } - this.rejectResult(error); - } -} diff --git a/mcp/packages/server/src/RedisBridge.ts b/mcp/packages/server/src/RedisBridge.ts deleted file mode 100644 index 89c01a4..0000000 --- a/mcp/packages/server/src/RedisBridge.ts +++ /dev/null @@ -1,202 +0,0 @@ -import Redis from "ioredis"; -import { PluginTaskRequest, PluginTaskResponse } from "@penpot/mcp-common"; -import { createLogger } from "./logger"; - -/** - * Handler invoked for a task request arriving on a subscribed request channel. - */ -export type TaskRequestHandler = (request: PluginTaskRequest) => void; - -/** - * Handler invoked for a task response arriving on a subscribed response channel. - */ -export type TaskResponseHandler = (response: PluginTaskResponse) => void; - -/** - * Provides a Redis-backed transport for routing plugin task requests and responses - * between MCP server instances. - * - * The bridge is a pure, stateless transport: it moves already-serialized - * `PluginTaskRequest` and `PluginTaskResponse` objects between instances and does not - * interpret their contents, correlate requests with responses, or impose timeouts. - * Correlation and timeout handling remain the responsibility of the caller (see - * `PluginBridge`, which routes Redis-delivered responses through the same - * pending-task machinery used for direct WebSocket dispatch). - * - * It enables a tool call handled on one instance to be executed against a plugin - * whose WebSocket connection lives on another instance: the request is published on a - * channel keyed by user token (to which the instance holding the plugin connection is - * subscribed), and the response is published on a channel keyed by task ID (to which - * the issuing instance subscribes). - * - * Two Redis connections are used, as ioredis requires a dedicated connection while - * subscribed: one for commands and publishing, and one for subscriptions. - */ -export class RedisBridge { - private readonly logger = createLogger("RedisBridge"); - private readonly publisher: Redis; - private readonly subscriber: Redis; - private readonly tenant: string; - - /** - * Message handlers keyed by channel name. - * - * ioredis exposes a single, global message event for all subscribed channels, so - * incoming messages are dispatched to the correct handler by channel name. Both - * request-channel and response-channel handlers are stored here. - */ - private readonly handlers = new Map void>(); - - /** - * Creates a Redis bridge connected to the given Redis instance. - * - * @param redisUri - The Redis connection URI (e.g. `redis://host:6379`) - * @param tenant - The tenant identifier, used to qualify Redis channel names so that - * multiple environments sharing a Redis instance do not interfere. - */ - constructor(redisUri: string, tenant: string) { - this.tenant = tenant; - this.publisher = new Redis(redisUri); - this.subscriber = new Redis(redisUri); - - this.subscriber.on("message", (channel: string, rawMessage: string) => { - const handler = this.handlers.get(channel); - if (handler) { - handler(rawMessage); - } else { - this.logger.warn(`Received message on channel with no registered handler: ${channel}`); - } - }); - } - - /** Builds the Redis Pub/Sub channel name for a task request addressed to a user token. */ - private requestChannel(userToken: string): string { - return `penpot.mcp.${this.tenant}.task.req.${userToken}`; - } - - /** Builds the Redis Pub/Sub channel name for a task response keyed by task ID. */ - private responseChannel(taskId: string): string { - return `penpot.mcp.${this.tenant}.task.res.${taskId}`; - } - - /** - * Subscribes to the response channel for the given task ID and publishes the task - * request to the given user token's request channel. - * - * The response subscription is established *before* the request is published, to - * avoid a race in which the response would be published before the subscription is - * in place. The response handler is invoked at most once and the subscription is - * removed automatically upon delivery (response channels are single-use). - * - * @param userToken - The user token identifying the target plugin's request channel - * @param request - The serialized plugin task request, passed through verbatim - * @param onResponse - Handler invoked with the response when it arrives - * @returns The number of instances that received the request. A count of 0 means no - * instance is subscribed to the token's request channel (i.e. the plugin is not - * connected anywhere); the request was dropped, no response will ever arrive, and - * the response subscription has already been released. - */ - async sendTaskRequest( - userToken: string, - request: PluginTaskRequest, - onResponse: TaskResponseHandler - ): Promise { - const responseChannel = this.responseChannel(request.id); - const requestChannel = this.requestChannel(userToken); - - this.handlers.set(responseChannel, (rawMessage) => { - // a response channel is single-use: remove the handler and unsubscribe on delivery - this.handlers.delete(responseChannel); - void this.subscriber.unsubscribe(responseChannel); - try { - onResponse(JSON.parse(rawMessage) as PluginTaskResponse); - } catch (error) { - this.logger.error(error, "Failed to parse task response message"); - } - }); - - await this.subscriber.subscribe(responseChannel); - // publish only once the response subscription is confirmed - let receiverCount: number; - try { - receiverCount = await this.publisher.publish(requestChannel, JSON.stringify(request)); - } catch (error) { - // the request was never delivered, so no response can arrive - await this.unsubscribeFromResponse(request.id); - throw error; - } - if (receiverCount === 0) { - // no subscriber received the request, so no response can arrive - await this.unsubscribeFromResponse(request.id); - } - return receiverCount; - } - - /** - * Unsubscribes from the response channel for the given task ID. - * - * Used to release a response subscription when no response will be processed (e.g. - * the awaiting task has timed out), since in that case the self-unsubscribe on - * delivery never occurs. - * - * @param taskId - The task ID whose response channel to unsubscribe from - */ - async unsubscribeFromResponse(taskId: string): Promise { - const responseChannel = this.responseChannel(taskId); - this.handlers.delete(responseChannel); - await this.subscriber.unsubscribe(responseChannel); - } - - /** - * Publishes a task response on the response channel for the given task ID. - * - * Used by the instance executing a forwarded task to return its outcome to the - * issuing instance. - * - * @param taskId - The ID of the originally requested task - * @param response - The serialized plugin task response, passed through verbatim - */ - publishTaskResponse(taskId: string, response: PluginTaskResponse): void { - const responseChannel = this.responseChannel(taskId); - void this.publisher.publish(responseChannel, JSON.stringify(response)); - } - - /** - * Subscribes to task requests for the given user token. - * - * The handler is invoked for each request arriving on the token's request channel. - * - * @param userToken - The user token whose request channel to subscribe to - * @param handler - The handler to invoke for incoming requests - */ - async subscribeToTasks(userToken: string, handler: TaskRequestHandler): Promise { - const requestChannel = this.requestChannel(userToken); - this.handlers.set(requestChannel, (rawMessage) => { - try { - handler(JSON.parse(rawMessage) as PluginTaskRequest); - } catch (error) { - this.logger.error(error, "Failed to parse task request message"); - } - }); - await this.subscriber.subscribe(requestChannel); - } - - /** - * Unsubscribes from task requests for the given user token. - * - * @param userToken - The user token whose request channel to unsubscribe from - */ - async unsubscribeFromTasks(userToken: string): Promise { - const requestChannel = this.requestChannel(userToken); - this.handlers.delete(requestChannel); - await this.subscriber.unsubscribe(requestChannel); - } - - /** - * Closes both Redis connections. Call on server shutdown. - */ - async close(): Promise { - await this.subscriber.quit(); - await this.publisher.quit(); - } -} diff --git a/mcp/packages/server/src/RemotePluginTask.ts b/mcp/packages/server/src/RemotePluginTask.ts deleted file mode 100644 index 368623a..0000000 --- a/mcp/packages/server/src/RemotePluginTask.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { AbstractPluginTask } from "./PluginTask"; -import { PluginTaskResult } from "@penpot/mcp-common"; -import type { RedisBridge } from "./RedisBridge"; - -/** - * A plugin task whose outcome is forwarded back to a remote requester via Redis, - * rather than awaited in-process. - * - * This task type is used on the server instance that holds the plugin's WebSocket - * connection when a task request arrives over Redis (published by another instance - * that received the corresponding tool call). It is dispatched to the plugin through - * the ordinary local dispatch path; when the plugin responds, the response-correlation - * machinery settles this task, and the overridden `resolveWithResult`/`rejectWithError` - * publish the outcome back onto the requester's Redis response channel. - * - * Note that this task has its own ID (used to correlate the local WebSocket dispatch), - * distinct from the original requester's task ID, which keys the Redis response channel. - * - * It deliberately carries no result promise: settling the task *is* the side effect - * of publishing to Redis, and nothing awaits it locally. - */ -export class RemotePluginTask extends AbstractPluginTask> { - /** - * Creates a task that forwards its outcome to a Redis response channel. - * - * @param task - The name of the task to execute (from the incoming request) - * @param params - The parameters for task execution (from the incoming request) - * @param redisBridge - The Redis bridge used to publish the outcome - * @param originalTaskId - The ID of the original request, which keys the response - * channel the requesting instance is awaiting - */ - constructor( - task: string, - params: any, - private readonly redisBridge: RedisBridge, - private readonly originalTaskId: string - ) { - super(task, params); - } - - resolveWithResult(result: PluginTaskResult): void { - this.redisBridge.publishTaskResponse(this.originalTaskId, { - id: this.originalTaskId, - success: true, - data: result.data, - }); - } - - rejectWithError(error: Error): void { - this.redisBridge.publishTaskResponse(this.originalTaskId, { - id: this.originalTaskId, - success: false, - error: error.message, - }); - } -} diff --git a/mcp/packages/server/src/ReplServer.ts b/mcp/packages/server/src/ReplServer.ts deleted file mode 100644 index 1894ade..0000000 --- a/mcp/packages/server/src/ReplServer.ts +++ /dev/null @@ -1,113 +0,0 @@ -import express from "express"; -import path from "path"; -import { fileURLToPath } from "url"; -import { PluginBridge } from "./PluginBridge"; -import { ExecuteCodePluginTask } from "./tasks/ExecuteCodePluginTask"; -import { createLogger } from "./logger"; - -/** - * Web-based REPL server for executing code through the PluginBridge. - * - * Provides a REPL-style HTML interface that allows users to input - * JavaScript code and execute it via ExecuteCodePluginTask instances. - * The interface maintains command history, displays logs in <pre> tags, - * and shows results in visually separated blocks. - */ -export class ReplServer { - private readonly logger = createLogger("ReplServer"); - private readonly app: express.Application; - private readonly port: number; - private readonly host: string; - private server: any; - - constructor( - private readonly pluginBridge: PluginBridge, - port: number = 4403, - host: string = "localhost" - ) { - this.port = port; - this.host = host; - this.app = express(); - this.setupMiddleware(); - this.setupRoutes(); - } - - /** - * Sets up Express middleware for request parsing and static content. - */ - private setupMiddleware(): void { - this.app.use(express.json()); - } - - /** - * Sets up HTTP routes for the REPL interface and API endpoints. - */ - private setupRoutes(): void { - // serve the main REPL interface - this.app.get("/", (req, res) => { - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - const htmlPath = path.join(__dirname, "static", "repl.html"); - res.sendFile(htmlPath); - }); - - // API endpoint for executing code - this.app.post("/execute", async (req, res) => { - try { - const { code } = req.body; - - if (!code || typeof code !== "string") { - return res.status(400).json({ - error: "Code parameter is required and must be a string", - }); - } - - const task = new ExecuteCodePluginTask({ code }); - const result = await this.pluginBridge.executePluginTask(task); - - // extract the result member from ExecuteCodeTaskResultData - const executeResult = result.data?.result; - - res.json({ - success: true, - result: executeResult, - log: result.data?.log || "", - }); - } catch (error) { - this.logger.error(error, "Failed to execute code in REPL"); - res.status(500).json({ - error: error instanceof Error ? error.message : "Unknown error occurred", - }); - } - }); - } - - /** - * Starts the REPL web server. - * - * Begins listening on the configured port and logs server startup information. - */ - public async start(): Promise { - return new Promise((resolve) => { - this.server = this.app.listen(this.port, this.host, () => { - this.logger.info(`REPL server started on port ${this.port}`); - this.logger.info(`REPL interface URL: http://${this.host}:${this.port}`); - resolve(); - }); - }); - } - - /** - * Stops the REPL web server. - */ - public async stop(): Promise { - if (this.server) { - return new Promise((resolve) => { - this.server.close(() => { - this.logger.info("REPL server stopped"); - resolve(); - }); - }); - } - } -} diff --git a/mcp/packages/server/src/Tool.ts b/mcp/packages/server/src/Tool.ts deleted file mode 100644 index df4e1f2..0000000 --- a/mcp/packages/server/src/Tool.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { z } from "zod"; -import "reflect-metadata"; -import { TextResponse, ToolResponse } from "./ToolResponse"; -import type { PenpotMcpServer, SessionContext } from "./PenpotMcpServer"; -import { createLogger } from "./logger"; - -/** - * An empty arguments class for tools that do not require any parameters. - */ -export class EmptyToolArgs { - static schema = {}; -} - -/** - * Base class for type-safe tools with automatic schema generation and validation. - * - * This class provides type safety through automatic validation and strongly-typed - * protected methods. All tools should extend this class. - * - * @template TArgs - The strongly-typed arguments class for this tool - */ -export abstract class Tool { - private readonly logger = createLogger("Tool"); - - /** monotonically increasing counter for unique tool execution IDs */ - private static executionCounter = 0; - - protected constructor( - protected mcpServer: PenpotMcpServer, - private inputSchema: z.ZodRawShape - ) {} - - /** - * Executes the tool with automatic validation and type safety. - * - * This method handles the unknown args from the MCP protocol, - * delegating to the type-safe implementation. - */ - async execute(args: unknown): Promise { - const executionId = ++Tool.executionCounter; - try { - let argsInstance: TArgs = args as TArgs; - this.logger.info("Tool execution #%d starting: %s", executionId, this.getToolName()); - if (this.logger.isLevelEnabled("debug")) { - this.logger.debug("Tool execution #%d arguments: %s", executionId, this.formatArgs(argsInstance)); - } - - // execute the actual tool logic - let result = await this.executeCore(argsInstance); - - this.logger.info("Tool execution #%d complete: %s", executionId, this.getToolName()); - return result; - } catch (error) { - this.logger.error("Tool execution #%d failed: %s; error: %s", executionId, this.getToolName(), error); - return new TextResponse(`Tool execution failed: ${String(error)}`); - } - } - - /** - * Formats tool arguments for readable logging. - * - * Multi-line strings are preserved with proper indentation. - */ - protected formatArgs(args: TArgs): string { - const formatted: string[] = []; - - for (const [key, value] of Object.entries(args)) { - if (typeof value === "string" && value.includes("\n")) { - // multi-line string - preserve formatting with indentation - const indentedValue = value - .split("\n") - .map((line, index) => (index === 0 ? line : " " + line)) - .join("\n"); - formatted.push(` ${key}: ${indentedValue}`); - } else if (typeof value === "string") { - // single-line string - formatted.push(` ${key}: "${value}"`); - } else if (value === null || value === undefined) { - formatted.push(` ${key}: ${value}`); - } else { - // other types (numbers, booleans, objects, arrays) - const stringified = JSON.stringify(value, null, 2); - if (stringified.includes("\n")) { - // multi-line JSON - indent it - const indented = stringified - .split("\n") - .map((line, index) => (index === 0 ? line : " " + line)) - .join("\n"); - formatted.push(` ${key}: ${indented}`); - } else { - formatted.push(` ${key}: ${stringified}`); - } - } - } - - return formatted.length > 0 ? "\n" + formatted.join("\n") : "{}"; - } - - /** - * Retrieves the current session context. - * - * @returns The session context for the current request, or undefined if not in a request context - */ - protected getSessionContext(): SessionContext | undefined { - return this.mcpServer.getSessionContext(); - } - - public getInputSchema() { - return this.inputSchema; - } - - /** - * Returns the tool's unique name. - */ - public abstract getToolName(): string; - - /** - * Returns the tool's description. - */ - public abstract getToolDescription(): string; - - /** - * Executes the tool's core logic. - * - * @param args - The (typed) tool arguments - */ - protected abstract executeCore(args: TArgs): Promise; -} diff --git a/mcp/packages/server/src/ToolResponse.ts b/mcp/packages/server/src/ToolResponse.ts deleted file mode 100644 index 305fe6b..0000000 --- a/mcp/packages/server/src/ToolResponse.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; - -type CallToolContent = CallToolResult["content"][number]; -type TextItem = Extract; -type ImageItem = Extract; - -export class TextContent implements TextItem { - [x: string]: unknown; - readonly type = "text" as const; - constructor(public text: string) {} - - /** - * @param data - Text data as string or as object (wrapped base64 data or JSON representation of array where - * indices are mapped to character codes) - */ - public static textData(data: string | object): string { - if (typeof data === "object") { - // check for wrapped base64 data - if ("__type" in data && data["__type"] === "base64" && "data" in data && typeof data["data"] === "string") { - return Buffer.from(data["data"], "base64").toString("utf-8"); - } - // legacy fallback: assume object containing character codes (as obtained from direct JSON conversion) - else { - return String.fromCharCode(...(Object.values(data) as number[])); - } - } else { - return data; - } - } -} - -export class ImageContent implements ImageItem { - [x: string]: unknown; - readonly type = "image" as const; - - /** - * @param data - Base64-encoded image data - * @param mimeType - MIME type of the image (e.g., "image/png") - */ - constructor( - public data: string, - public mimeType: string - ) {} - - /** - * Utility function for ensuring a consistent Uint8Array representation of byte data. - * Input can be one of: - * - a `Uint8Array` (already in the desired form); - * - a base64 envelope `{ __type: "base64", data: }` produced by the plugin - * to avoid the ~10x JSON expansion of typed arrays (see penpot/penpot#9420); - * - a numeric-keyed object obtained from `JSON.stringify`-ing a `Uint8Array` (legacy fallback). - * - * @param data - data as `Uint8Array`, base64 envelope, or numeric-keyed object - * @return data as `Uint8Array` - */ - public static byteData(data: Uint8Array | object): Uint8Array { - if (data instanceof Uint8Array) { - return data; - } - // recognize the base64 envelope produced by the plugin's ExecuteCodeTaskHandler - const envelope = data as { __type?: unknown; data?: unknown }; - if (envelope.__type === "base64" && typeof envelope.data === "string") { - return new Uint8Array(Buffer.from(envelope.data, "base64")); - } - // legacy fallback: object (as obtained from JSON conversion of Uint8Array) back to Uint8Array - return new Uint8Array(Object.values(data) as number[]); - } -} - -export class PNGImageContent extends ImageContent { - /** - * @param data - PNG image data as Uint8Array or as object (from JSON conversion of Uint8Array) - */ - constructor(data: Uint8Array | object) { - let array = ImageContent.byteData(data); - super(Buffer.from(array).toString("base64"), "image/png"); - } -} - -export class ToolResponse implements CallToolResult { - [x: string]: unknown; - content: CallToolContent[]; // <- IMPORTANT: protocol’s union - constructor(content: CallToolContent[]) { - this.content = content; - } -} - -export class TextResponse extends ToolResponse { - constructor(text: string) { - super([new TextContent(text)]); - } - - /** - * Creates a TextResponse from text data given as string or as object (wrapped base64 data or JSON representation - * of array where indices are mapped to character codes). - * - * @param data - Text data as string or as object (from JSON representation where indices are mapped to character codes) - */ - public static fromData(data: string | object): TextResponse { - return new TextResponse(TextContent.textData(data)); - } -} - -export class PNGResponse extends ToolResponse { - /** - * @param data - PNG image data as Uint8Array or as object (from JSON conversion of Uint8Array) - */ - constructor(data: Uint8Array | object) { - super([new PNGImageContent(data)]); - } -} diff --git a/mcp/packages/server/src/index.ts b/mcp/packages/server/src/index.ts deleted file mode 100644 index 84ef23f..0000000 --- a/mcp/packages/server/src/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env node - -import { PenpotMcpServer } from "./PenpotMcpServer"; -import { createLogger, logActiveTransports } from "./logger"; - -/** - * Entry point for Penpot MCP Server - * - * Creates and starts the MCP server instance, handling any startup errors - * gracefully and ensuring proper process termination. - * - * Configuration via environment variables (see README). - */ -async function main(): Promise { - const logger = createLogger("main"); - - // announce active transports early so they appear before any potential errors - logActiveTransports(logger); - - try { - const args = process.argv.slice(2); - let multiUser = false; // default to single-user mode - - // parse command line arguments - for (let i = 0; i < args.length; i++) { - if (args[i] === "--multi-user") { - multiUser = true; - } else if (args[i] === "--help" || args[i] === "-h") { - logger.info("Usage: node dist/index.js [options]"); - logger.info("Options:"); - logger.info(" --multi-user Enable multi-user mode (default: single-user)"); - logger.info(" --help, -h Show this help message"); - logger.info(""); - logger.info("Note that configuration is mostly handled through environment variables."); - logger.info("Refer to the README for more information."); - process.exit(0); - } - } - - const server = new PenpotMcpServer(multiUser); - await server.start(); - - // keep the process alive - process.on("SIGINT", async () => { - logger.info("Received SIGINT, shutting down gracefully..."); - await server.stop(); - process.exit(0); - }); - - process.on("SIGTERM", async () => { - logger.info("Received SIGTERM, shutting down gracefully..."); - await server.stop(); - process.exit(0); - }); - } catch (error) { - logger.error(error, "Failed to start MCP server"); - process.exit(1); - } -} - -// Start the server if this file is run directly -if (import.meta.url.endsWith(process.argv[1]) || process.argv[1].endsWith("index.js")) { - main().catch((error) => { - createLogger("main").error(error, "Unhandled error in main"); - process.exit(1); - }); -} diff --git a/mcp/packages/server/src/logger.ts b/mcp/packages/server/src/logger.ts deleted file mode 100644 index 45cb359..0000000 --- a/mcp/packages/server/src/logger.ts +++ /dev/null @@ -1,237 +0,0 @@ -import pino, { type TransportTargetOptions } from "pino"; -import { join, resolve } from "path"; - -/** - * Configured log level (defaults to `info`). - */ -const LOG_LEVEL = process.env.PENPOT_MCP_LOG_LEVEL || "info"; - -/** - * Configured log directory; file logging is enabled iff this is set to a non-empty value. - */ -const LOG_DIR = process.env.PENPOT_MCP_LOG_DIR; - -/** - * Loki host URI; if set and non-empty, Loki logging is enabled. - */ -const LOKI_URI = process.env.PENPOT_LOGGERS_LOKI_URI; - -/** - * Generates a timestamped log file name. - * - * @returns Log file name - */ -function generateLogFileName(): string { - const now = new Date(); - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, "0"); - const day = String(now.getDate()).padStart(2, "0"); - const hours = String(now.getHours()).padStart(2, "0"); - const minutes = String(now.getMinutes()).padStart(2, "0"); - const seconds = String(now.getSeconds()).padStart(2, "0"); - return `penpot-mcp-${year}${month}${day}-${hours}${minutes}${seconds}.log`; -} - -/** - * The pino transport target spec, as expected in `transport.targets[]`. - */ -type TransportTargetSpec = TransportTargetOptions; - -/** - * Provides a single pino transport target, either active or inactive. - * - * Implementations decide their own activation based on environment configuration. - * An inactive provider returns `null` from {@link getTarget} and is skipped. - */ -interface LogTransportProvider { - /** - * Returns the pino transport target spec, or `null` if this transport is disabled. - */ - getTarget(): TransportTargetSpec | null; - - /** - * Returns a human-readable startup message describing the transport, or `null` if disabled. - */ - getStartupMessage(): string | null; -} - -/** - * Console transport with pretty-printed, colorized output. Always active. - */ -class ConsoleLogTransport implements LogTransportProvider { - public getTarget(): TransportTargetSpec { - return { - target: "pino-pretty", - level: LOG_LEVEL, - options: { - colorize: true, - translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l", - ignore: "pid,hostname", - messageFormat: "{msg}", - levelFirst: true, - }, - }; - } - - public getStartupMessage(): string { - return "Logging to console"; - } -} - -/** - * File transport writing pretty-formatted logs to a timestamped file in a configurable directory. - * Active iff `PENPOT_MCP_LOG_DIR` is set and non-empty. - */ -class FileLogTransport implements LogTransportProvider { - private readonly enabled: boolean; - private readonly filePath: string | null; - - public constructor(logDir: string | undefined) { - this.enabled = logDir !== undefined && logDir !== ""; - this.filePath = this.enabled ? resolve(join(logDir as string, generateLogFileName())) : null; - } - - public isEnabled(): boolean { - return this.enabled; - } - - public getTarget(): TransportTargetSpec | null { - if (!this.enabled) { - return null; - } - return { - target: "pino-pretty", - level: LOG_LEVEL, - options: { - destination: this.filePath, - colorize: false, - translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l", - ignore: "pid,hostname", - messageFormat: "{msg}", - levelFirst: true, - mkdir: true, - }, - }; - } - - public getStartupMessage(): string | null { - return this.enabled ? `Logging to file: ${this.filePath}` : null; - } - - /** - * Returns the absolute path of the active log file, or `undefined` if file logging is disabled. - */ - public getFilePath(): string | undefined { - return this.filePath ?? undefined; - } -} - -/** - * Loki transport forwarding logs to a Grafana Loki instance via `pino-loki`. - * - * Active iff `PENPOT_LOGGERS_LOKI_URI` is set and non-empty. - */ -class LokiLogTransport implements LogTransportProvider { - private readonly host: string | null; - - public constructor(lokiUri: string | undefined) { - this.host = lokiUri !== undefined && lokiUri !== "" ? lokiUri : null; - } - - public isEnabled(): boolean { - return this.host !== null; - } - - public getTarget(): TransportTargetSpec | null { - if (this.host === null) { - return null; - } - return { - target: "pino-loki", - level: LOG_LEVEL, - options: { - host: this.host, - json: false, - batching: true, - interval: 5, - replaceTimestamp: true, - labels: this.buildLabels(), - }, - }; - } - - /** - * Builds the set of static labels to attach to every log entry sent to Loki. - * - * The `environment` and `instance` labels are only included if their respective - * environment variables are set and non-empty. - */ - private buildLabels(): Record { - const labels: Record = { - job: process.env.PENPOT_LOGGERS_LOKI_JOB || "mcp", - }; - const environment = process.env.PENPOT_LOGGERS_LOKI_ENVIRONMENT; - if (environment) { - labels.environment = environment; - } - const instance = process.env.PENPOT_LOGGERS_LOKI_INSTANCE; - if (instance) { - labels.instance = instance; - } - return labels; - } - - public getStartupMessage(): string | null { - return this.host !== null ? `Logging to Loki: ${this.host}` : null; - } -} - -// build the transport providers; each decides its own activation independently -const consoleTransport = new ConsoleLogTransport(); -const fileTransport = new FileLogTransport(LOG_DIR); -const lokiTransport = new LokiLogTransport(LOKI_URI); - -const transports: LogTransportProvider[] = [consoleTransport, fileTransport, lokiTransport]; - -/** - * Absolute path to the log file being written, or `undefined` if file logging is disabled. - */ -export const logFilePath: string | undefined = fileTransport.getFilePath(); - -/** - * Logger instance configured with the active transports (console, optional file, optional Loki). - */ -export const logger = pino({ - level: LOG_LEVEL, - timestamp: pino.stdTimeFunctions.isoTime, - transport: { - targets: transports - .map((t) => t.getTarget()) - .filter((target): target is TransportTargetSpec => target !== null), - }, -}); - -/** - * Logs a startup line for each active transport, allowing the user to see at a glance - * where logs are being written. - * - * @param log - the logger to emit the startup messages on - */ -export function logActiveTransports(log: pino.Logger): void { - for (const t of transports) { - const msg = t.getStartupMessage(); - if (msg !== null) { - log.info(msg); - } - } -} - -/** - * Creates a child logger with the specified name/origin. - * - * @param name - the name/origin identifier for the logger - * @returns child logger instance with the specified name - */ -export function createLogger(name: string) { - return logger.child({ name }); -} diff --git a/mcp/packages/server/src/static/repl.html b/mcp/packages/server/src/static/repl.html deleted file mode 100644 index 9fafa13..0000000 --- a/mcp/packages/server/src/static/repl.html +++ /dev/null @@ -1,554 +0,0 @@ - - - - - - Penpot API REPL - - - - -
- -
- -
Ctrl+Enter to execute • Arrow up/down for command history
- - - - diff --git a/mcp/packages/server/src/tasks/ExecuteCodePluginTask.ts b/mcp/packages/server/src/tasks/ExecuteCodePluginTask.ts deleted file mode 100644 index 130e69c..0000000 --- a/mcp/packages/server/src/tasks/ExecuteCodePluginTask.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { PluginTask } from "../PluginTask"; -import { ExecuteCodeTaskParams, ExecuteCodeTaskResultData, PluginTaskResult } from "@penpot/mcp-common"; - -/** - * Task for executing JavaScript code in the plugin context. - * - * This task instructs the plugin to execute arbitrary JavaScript code - * and return the result of execution. - */ -export class ExecuteCodePluginTask extends PluginTask< - ExecuteCodeTaskParams, - PluginTaskResult> -> { - /** - * Creates a new execute code task. - * - * @param params - The parameters containing the code to execute - */ - constructor(params: ExecuteCodeTaskParams) { - super("executeCode", params); - } -} diff --git a/mcp/packages/server/src/tools/CljCheckParentheses.ts b/mcp/packages/server/src/tools/CljCheckParentheses.ts deleted file mode 100644 index 81e2a96..0000000 --- a/mcp/packages/server/src/tools/CljCheckParentheses.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { z } from "zod"; -import { Tool } from "../Tool"; -import "reflect-metadata"; -import type { ToolResponse } from "../ToolResponse"; -import { TextResponse } from "../ToolResponse"; -import type { PenpotMcpServer } from "../PenpotMcpServer"; -import * as fs from "fs"; - -/** - * Arguments for the FindUnclosedParensTool. - */ -export class CljCheckParenthesesArgs { - static schema = { - file: z.string().min(1).describe("Absolute path to a Clojure/ClojureScript source file"), - }; - - file!: string; -} - -interface OpenDelim { - id: number; - line: number; // 0-based - col: number; // 0-based - char: string; - baselineKey: string; // the baseline key this delimiter owns -} - -interface ParenIssue { - line: number; // 1-based - col: number; // 1-based - char: string; - detectedAtLine?: number; // 1-based line where the stack-state mismatch was observed -} - -/** - * Finds unclosed delimiters in Clojure/ClojureScript source files using a - * stack-state invariant derived from cljfmt formatting conventions. - * - * Invariant: in cljfmt-formatted code, every opening delimiter of type T at - * column C must see the same stack depth each time that (T, C) combination - * occurs. A depth mismatch means delimiters opened between the baseline - * occurrence and the current one were never closed. - * - * The parser correctly handles string literals (including multi-line and escape - * sequences), comment lines, character literals, and regex literals. - */ -export class CljCheckParentheses extends Tool { - constructor(mcpServer: PenpotMcpServer) { - super(mcpServer, CljCheckParenthesesArgs.schema); - } - - public getToolName(): string { - return "clj_check_parentheses"; - } - - public getToolDescription(): string { - return "Analyzes a Clojure/ClojureScript source file for unclosed delimiters and reports the area of interest."; - } - - protected async executeCore(args: CljCheckParenthesesArgs): Promise { - const filePath = args.file; - - if (!fs.existsSync(filePath)) { - return new TextResponse(`File not found: ${filePath}`); - } - - const content = fs.readFileSync(filePath, "utf-8"); - const issues = analyzeParens(content); - - if (issues.length === 0) { - return new TextResponse("All delimiters are properly balanced."); - } - - const sourceLines = content.split("\n"); - const parts: string[] = [`Found ${issues.length} unclosed delimiter(s):\n`]; - - for (const issue of issues) { - const srcLine = (sourceLines[issue.line - 1] ?? "").trimEnd(); - const pointer = " ".repeat(String(issue.line).length) + " " + " ".repeat(issue.col - 1) + "^"; - - if (issue.detectedAtLine != null) { - const detectedSrcLine = (sourceLines[issue.detectedAtLine - 1] ?? "").trimEnd(); - parts.push( - ` Unclosed '${issue.char}' at line ${issue.line}, col ${issue.col}:\n` + - ` ${issue.line} | ${srcLine}\n` + - ` ${pointer}\n` + - ` Stack-state mismatch detected at line ${issue.detectedAtLine}:\n` + - ` ${issue.detectedAtLine} | ${detectedSrcLine}\n` - ); - } else { - parts.push( - ` Unclosed '${issue.char}' at line ${issue.line}, col ${issue.col} (still open at end of file):\n` + - ` ${issue.line} | ${srcLine}\n` + - ` ${pointer}\n` - ); - } - } - - return new TextResponse(parts.join("\n")); - } -} - -/** - * Analyses delimiter balance in a Clojure/ClojureScript source string. - * - * Algorithm - * --------- - * Maintain a stack of open delimiters and a map from (delimiter-type, column) - * to the stack depth recorded on the first occurrence of that combination. - * - * Each time an opening delimiter of type T appears at column C: - * 1. Look up the key (T, C) in the map. - * 2. If absent, record the current stack depth as the baseline. - * 3. If present, compare the current depth with the baseline. - * - If deeper: the extra stack entries (from baseline depth to current - * depth) are delimiters that should have been closed. Report them. - * - If shallower: more delimiters were closed than opened between the - * baseline and here (over-closed). Update the baseline downward so - * subsequent occurrences don't cascade. - * 4. Push the delimiter onto the stack. - * - * After the full file is processed, any delimiter still on the stack is - * unclosed. If it was already reported via a mismatch, the report includes - * the detection line; otherwise it is reported as open-at-EOF. - */ -function analyzeParens(content: string): ParenIssue[] { - // Precompute line-start offsets for O(1) column lookup. - const lineStarts: number[] = [0]; - for (let i = 0; i < content.length; i++) { - if (content[i] === "\n") lineStarts.push(i + 1); - } - - let nextId = 0; - const stack: OpenDelim[] = []; - - // (type, column) → baseline stack depth. - // Each baseline is owned by the delimiter that established it (stored - // as baselineKey on the stack entry). When that delimiter is popped, - // its baseline is discarded — it was scoped to that delimiter's lifetime. - const baseline: Map = new Map(); - - let inString = false; - let inComment = false; - let escape = false; - let currentLine = 0; - - for (let i = 0; i < content.length; i++) { - const ch = content[i]; - - // ── Newline ────────────────────────────────────────────────────── - if (ch === "\n") { - inComment = false; - currentLine++; - if (!inString) escape = false; - continue; - } - - // ── Escape: skip next character ────────────────────────────────── - if (escape) { - escape = false; - continue; - } - - // ── Inside comment: skip until newline ─────────────────────────── - if (inComment) continue; - - // ── Inside string literal ──────────────────────────────────────── - if (inString) { - if (ch === "\\") escape = true; - else if (ch === '"') inString = false; - continue; - } - - // ── Outside string / comment ───────────────────────────────────── - if (ch === "\\") { - escape = true; - continue; - } - if (ch === '"') { - inString = true; - continue; - } - if (ch === ";") { - inComment = true; - continue; - } - - // ── Opening delimiter ──────────────────────────────────────────── - if (ch === "(" || ch === "[" || ch === "{") { - const col = i - lineStarts[currentLine]; - const key = `${ch}:${col}`; - const currentDepth = stack.length; - - const recorded = baseline.get(key); - if (recorded !== undefined && currentDepth > recorded) { - // Stack is deeper than expected. The entries from index - // `recorded` to `currentDepth - 1` are unclosed delimiters - // that should have been closed before reaching this - // position. Return immediately — further parsing would be - // against a corrupted stack and only produce cascading noise. - return stack.slice(recorded, currentDepth).map((delim) => ({ - line: delim.line + 1, - col: delim.col + 1, - char: delim.char, - detectedAtLine: currentLine + 1, - })); - } - - // Establish or re-establish the baseline for this key, - // owned by this delimiter. Discarded when it is popped. - baseline.set(key, currentDepth); - - stack.push({ - id: nextId++, - line: currentLine, - col, - char: ch, - baselineKey: key, - }); - } - - // ── Closing delimiter ──────────────────────────────────────────── - else if (ch === ")" || ch === "]" || ch === "}") { - if (stack.length > 0) { - const closed = stack.pop()!; - - // The baseline this delimiter owned is no longer valid — - // the context it was recorded in has closed. - baseline.delete(closed.baselineKey); - } - } - } - - // ── EOF: no mismatch was found, but the stack is not empty ────────── - // This happens when the unclosed delimiter has no second occurrence of - // the same (type, column) to compare against (e.g. last form in file). - return stack.map((delim) => ({ - line: delim.line + 1, - col: delim.col + 1, - char: delim.char, - })); -} diff --git a/mcp/packages/server/src/tools/CljsCompilerOutputTool.ts b/mcp/packages/server/src/tools/CljsCompilerOutputTool.ts deleted file mode 100644 index 11989c6..0000000 --- a/mcp/packages/server/src/tools/CljsCompilerOutputTool.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Tool, EmptyToolArgs } from "../Tool"; -import "reflect-metadata"; -import type { ToolResponse } from "../ToolResponse"; -import { TextResponse } from "../ToolResponse"; -import { PenpotMcpServer } from "../PenpotMcpServer"; -import { NreplClient } from "../NreplClient"; - -/** - * Reports the compiler status of the shadow-cljs `:main` build. - * - * If the most recent build failed, returns the relevant fields of the failure data - * (tag, message, resource name, line, column, etc.); otherwise returns `:ok`. - */ -export class CljsCompilerOutputTool extends Tool { - private static readonly STATUS_CODE = - "(require (quote [shadow.cljs.devtools.api :as shadow])) " + - "(let [fd (-> (shadow/get-worker :main) :state-ref deref :failure-data)] " + - "(if fd (pr-str fd) :ok))"; - - private readonly nreplClient: NreplClient; - - constructor(mcpServer: PenpotMcpServer, nreplClient: NreplClient) { - super(mcpServer, EmptyToolArgs.schema); - this.nreplClient = nreplClient; - } - - public getToolName(): string { - return "cljs_compiler_output"; - } - - public getToolDescription(): string { - return ( - "Reports the status of the most recent shadow-cljs `:main` build. " + - "Use this to diagnose compilation errors when needed. For syntax errors, " + - "consider using the clj_check_parentheses tool on the relevant source files." - ); - } - - protected async executeCore(_args: EmptyToolArgs): Promise { - const result = await this.nreplClient.eval(CljsCompilerOutputTool.STATUS_CODE); - - // multiple top-level forms produce multiple values; the build status is the last one - const status = result.values[result.values.length - 1] ?? "nil"; - - const parts: string[] = [status]; - if (result.err) { - parts.push(`stderr:\n${result.err}`); - } - - return new TextResponse(parts.join("\n\n")); - } -} diff --git a/mcp/packages/server/src/tools/CljsReplTool.ts b/mcp/packages/server/src/tools/CljsReplTool.ts deleted file mode 100644 index bd894ca..0000000 --- a/mcp/packages/server/src/tools/CljsReplTool.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { z } from "zod"; -import { Tool } from "../Tool"; -import "reflect-metadata"; -import type { ToolResponse } from "../ToolResponse"; -import { TextResponse } from "../ToolResponse"; -import { PenpotMcpServer } from "../PenpotMcpServer"; -import { NreplClient } from "../NreplClient"; - -/** - * Arguments for the CljsReplTool. - */ -export class CljsReplArgs { - static schema = { - code: z.string().min(1, "Code cannot be empty"), - }; - - /** - * The ClojureScript code to evaluate in the frontend runtime. - */ - code!: string; -} - -/** - * A ClojureScript REPL for the Penpot frontend runtime. - * - * This tool provides a persistent REPL session connected to the shadow-cljs nREPL server. - * Definitions, requires, and other state are preserved across calls, enabling iterative - * exploration and manipulation of the running Penpot application. - */ -export class CljsReplTool extends Tool { - private readonly nreplClient: NreplClient; - - /** - * Creates a new CljsReplTool instance. - * - * @param mcpServer - the MCP server instance - * @param nreplClient - the nREPL client for communicating with shadow-cljs - */ - constructor(mcpServer: PenpotMcpServer, nreplClient: NreplClient) { - super(mcpServer, CljsReplArgs.schema); - this.nreplClient = nreplClient; - } - - public getToolName(): string { - return "cljs_repl"; - } - - public getToolDescription(): string { - return ( - "Persistent ClojureScript REPL in the Penpot frontend runtime (via shadow-cljs nREPL). " + - "Definitions, requires, and state are preserved across calls — use it to build up helpers incrementally. " + - "Multiple top-level expressions per call are supported; each produces a result line." - ); - } - - protected async executeCore(args: CljsReplArgs): Promise { - const result = await this.nreplClient.evalCljs(args.code); - - const parts: string[] = []; - if (result.values.length > 0) { - parts.push(result.values.join("\n")); - } - if (result.out) { - parts.push(`stdout:\n${result.out}`); - } - if (result.err) { - parts.push(`stderr:\n${result.err}`); - } - if (parts.length === 0) { - parts.push("nil"); - } - - return new TextResponse(parts.join("\n\n")); - } -} diff --git a/mcp/packages/server/src/tools/ExecuteCodeTool.ts b/mcp/packages/server/src/tools/ExecuteCodeTool.ts deleted file mode 100644 index 6a514e0..0000000 --- a/mcp/packages/server/src/tools/ExecuteCodeTool.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { z } from "zod"; -import { Tool } from "../Tool"; -import type { ToolResponse } from "../ToolResponse"; -import { TextResponse } from "../ToolResponse"; -import "reflect-metadata"; -import { PenpotMcpServer } from "../PenpotMcpServer"; -import { ExecuteCodePluginTask } from "../tasks/ExecuteCodePluginTask"; -import { ExecuteCodeTaskParams } from "@penpot/mcp-common"; - -/** - * Arguments class for ExecuteCodeTool - */ -export class ExecuteCodeArgs { - static schema = { - code: z - .string() - .min(1, "Code cannot be empty") - .describe("The JavaScript code to execute in the plugin context."), - }; - - /** - * The JavaScript code to execute in the plugin context. - */ - code!: string; -} - -/** - * Tool for executing JavaScript code in the Penpot plugin context - */ -export class ExecuteCodeTool extends Tool { - /** - * Creates a new ExecuteCode tool instance. - * - * @param mcpServer - The MCP server instance - */ - constructor(mcpServer: PenpotMcpServer) { - super(mcpServer, ExecuteCodeArgs.schema); - } - - public getToolName(): string { - return "execute_code"; - } - - public getToolDescription(): string { - return ( - "Executes JavaScript code in the Penpot plugin context.\n" + - "IMPORTANT: Before using this tool, make sure you have read the 'Penpot High-Level Overview' and know " + - "which Penpot API functionality is necessary and how to use it.\n" + - "You have access two main objects: `penpot` (the Penpot API, of type `Penpot`), `penpotUtils`, " + - "and `storage`.\n" + - "`storage` is an object in which arbitrary data can be stored, simply by adding a new attribute; " + - "stored attributes can be referenced in future calls to this tool, so any intermediate results that " + - "could come in handy later should be stored in `storage` instead of just a fleeting variable; " + - "you can also store functions and thus build up a library).\n" + - "Think of the code being executed as the body of a function: " + - "The tool call returns whatever you return in the applicable `return` statement, if any. " + - "You can return arbitrary JS objects; no need to apply JSON.stringify.\n" + - "If an exception occurs, the exception's message will be returned to you.\n" + - "Any output that you generate via the `console` object will be returned to you separately; so you may use it " + - "to track what your code is doing, but you should *only* do so only if there is an ACTUAL NEED for this! " + - "VERY IMPORTANT: Don't use logging prematurely! NEVER log the data you are returning, as you will otherwise receive it twice!\n" + - "VERY IMPORTANT: In general, try a simple approach first, and only if it fails, try more complex code that involves " + - "handling different cases (in particular error cases) and that applies logging." - ); - } - - protected async executeCore(args: ExecuteCodeArgs): Promise { - const taskParams: ExecuteCodeTaskParams = { code: args.code }; - const task = new ExecuteCodePluginTask(taskParams); - const result = await this.mcpServer.pluginBridge.executePluginTask(task); - - if (result.data !== undefined) { - return new TextResponse(JSON.stringify(result.data, null, 2)); - } else { - return new TextResponse("Code executed successfully with no return value."); - } - } -} diff --git a/mcp/packages/server/src/tools/ExportShapeTool.ts b/mcp/packages/server/src/tools/ExportShapeTool.ts deleted file mode 100644 index 0d84df9..0000000 --- a/mcp/packages/server/src/tools/ExportShapeTool.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { z } from "zod"; -import { Tool } from "../Tool"; -import { ImageContent, PNGResponse, TextContent, TextResponse, ToolResponse } from "../ToolResponse"; -import "reflect-metadata"; -import { PenpotMcpServer } from "../PenpotMcpServer"; -import { ExecuteCodePluginTask } from "../tasks/ExecuteCodePluginTask"; -import { createLogger } from "../logger"; -import { FileUtils } from "../utils/FileUtils"; -import { Semaphore } from "../utils/Semaphore"; -import sharp from "sharp"; - -/** - * Arguments class for ExportShapeTool - */ -export class ExportShapeArgs { - static schema = { - shapeId: z - .string() - .min(1, "shapeId cannot be empty") - .describe( - "Identifier of the shape to export. " + - "Special identifiers you can use: 'selection' (first shape currently selected by the user), 'page' (entire current page)" - ), - format: z.enum(["svg", "png"]).default("png").describe("The output format, either 'png' (default) or 'svg'."), - mode: z - .enum(["shape", "fill"]) - .default("shape") - .describe( - "The export mode: either 'shape' (full shape as it appears in the design, including descendants; the default) or " + - "'fill' (export the raw image that is used as a fill for the shape; PNG format only)" - ), - filePath: z - .string() - .optional() - .describe( - "Optional file path to save the exported image to. If not provided, " + - "the image data is returned directly for you to see." - ), - }; - - shapeId!: string; - - format: "svg" | "png" = "png"; - - mode: "shape" | "fill" = "shape"; - - filePath?: string; -} - -/** - * Tool for executing JavaScript code in the Penpot plugin context - */ -export class ExportShapeTool extends Tool { - /** - * Maximum number of image-export operations that may run concurrently in multi-user mode. - * Configurable via the PENPOT_MCP_EXPORT_SHAPE_MAX_PARALLEL_REQUESTS environment variable; - * defaults to 0, meaning no limit. - * - * When set to a positive value (and combined with the plugin-side per-response cap - * MAX_TASK_RESPONSE_SIZE_REMOTE_MCP, ~15 MB JSON), this caps the in-flight memory - * footprint of image exports at roughly N x cap on the centrally hosted MCP server. - */ - private static readonly MAX_PARALLEL_EXPORTS = parseInt( - process.env.PENPOT_MCP_EXPORT_SHAPE_MAX_PARALLEL_REQUESTS ?? "0", - 10 - ); - - /** - * Gates concurrent export operations across all tool instances (one per session in - * multi-user mode). Static because instances are per-session, but the bound has to - * apply across the whole process. Permits beyond the maximum queue in FIFO order. - * Undefined when MAX_PARALLEL_EXPORTS is non-positive, indicating no limit. - */ - private static readonly parallelismSemaphore: Semaphore | undefined = - ExportShapeTool.MAX_PARALLEL_EXPORTS > 0 - ? new Semaphore("ExportShapeTool", ExportShapeTool.MAX_PARALLEL_EXPORTS) - : undefined; - - static { - createLogger("ExportShapeTool").info( - "Max parallel exports (multi-user mode): %d (0 = unbounded)", - ExportShapeTool.MAX_PARALLEL_EXPORTS - ); - } - - /** - * Creates a new ExecuteCode tool instance. - * - * @param mcpServer - The MCP server instance - */ - constructor(mcpServer: PenpotMcpServer) { - let schema: any = ExportShapeArgs.schema; - if (!mcpServer.isFileSystemAccessEnabled()) { - // remove filePath key from schema - schema = { ...schema }; - delete schema.filePath; - } - super(mcpServer, schema); - } - - public getToolName(): string { - return "export_shape"; - } - - public getToolDescription(): string { - let description = - "Exports a shape (or a shape's image fill) from the Penpot design to a PNG or SVG image, " + - "such that you can get an impression of what it looks like."; - if (this.mcpServer.isFileSystemAccessEnabled()) { - description += "\nAlternatively, you can save it to a file."; - } - return description; - } - - protected async executeCore(args: ExportShapeArgs): Promise { - // bound concurrent exports in multi-user mode to keep peak server memory under control; - // in single-user mode (or when no limit is configured) the gate is irrelevant - // and the export runs directly - if (this.mcpServer.isMultiUserMode() && ExportShapeTool.parallelismSemaphore) { - return ExportShapeTool.parallelismSemaphore.withPermit(() => this.exportImage(args)); - } else { - return this.exportImage(args); - } - } - - /** - * Performs the actual image export: requests the image via the plugin and either - * returns it as a tool response or saves it to the requested file path. The bulk - * of the memory pressure (parsed plugin response, decoded image buffer, optional - * re-encoding via sharp) lives here, which is why executeCore gates the call. - * - * @param args - the validated tool arguments - */ - private async exportImage(args: ExportShapeArgs): Promise { - // check arguments - if (args.filePath) { - FileUtils.checkPathIsAbsolute(args.filePath); - } - - // create code for exporting the shape - let shapeCode: string; - if (args.shapeId === "selection") { - shapeCode = `penpot.selection[0]`; - } else if (args.shapeId === "page") { - shapeCode = `penpot.root`; - } else { - shapeCode = `penpotUtils.findShapeById("${args.shapeId}")`; - } - const asSvg = args.format === "svg"; - const code = `return penpotUtils.exportImage(${shapeCode}, "${args.mode}", ${asSvg});`; - - // execute the code and obtain the image data - const task = new ExecuteCodePluginTask({ code: code }); - const result = await this.mcpServer.pluginBridge.executePluginTask(task); - const imageData = result.data!.result; - - // handle output and return response - if (!args.filePath) { - // return image data directly (for the LLM to "see" it) - if (args.format === "png") { - return new PNGResponse(await this.toPngImageBytes(imageData)); - } else { - return TextResponse.fromData(imageData); - } - } else { - // save to file requested: make sure file system access is enabled - if (!this.mcpServer.isFileSystemAccessEnabled()) { - throw new Error("File system access is not enabled on the MCP server!"); - } - // save to file - if (args.format === "png") { - FileUtils.writeBinaryFile(args.filePath, await this.toPngImageBytes(imageData)); - } else { - FileUtils.writeTextFile(args.filePath, TextContent.textData(imageData)); - } - return new TextResponse(`The shape has been exported to ${args.filePath}`); - } - } - - /** - * Converts image data to PNG format if necessary. - * - * @param data - The original image data as Uint8Array or as object (from JSON conversion of Uint8Array) - * @return The image data as PNG bytes - */ - private async toPngImageBytes(data: Uint8Array | object): Promise { - const originalBytes = ImageContent.byteData(data); - - // use sharp to detect format and convert to PNG if necessary - const image = sharp(originalBytes); - const metadata = await image.metadata(); - - // if already PNG, return as-is to avoid unnecessary re-encoding - if (metadata.format === "png") { - return originalBytes; - } - - // convert to PNG - const pngBuffer = await image.png().toBuffer(); - return new Uint8Array(pngBuffer); - } -} diff --git a/mcp/packages/server/src/tools/HighLevelOverviewTool.ts b/mcp/packages/server/src/tools/HighLevelOverviewTool.ts deleted file mode 100644 index b16edcb..0000000 --- a/mcp/packages/server/src/tools/HighLevelOverviewTool.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { EmptyToolArgs, Tool } from "../Tool"; -import "reflect-metadata"; -import type { ToolResponse } from "../ToolResponse"; -import { TextResponse } from "../ToolResponse"; -import { PenpotMcpServer } from "../PenpotMcpServer"; - -export class HighLevelOverviewTool extends Tool { - constructor(mcpServer: PenpotMcpServer) { - super(mcpServer, EmptyToolArgs.schema); - } - - public getToolName(): string { - return "high_level_overview"; - } - - public getToolDescription(): string { - return ( - "Returns basic high-level instructions on the usage of Penpot-related tools and the Penpot API. " + - "If you have already read the 'Penpot High-Level Overview', you must not call this tool." - ); - } - - protected async executeCore(args: EmptyToolArgs): Promise { - return new TextResponse(this.mcpServer.getHighLevelOverviewInstructions()); - } -} diff --git a/mcp/packages/server/src/tools/ImportImageTool.ts b/mcp/packages/server/src/tools/ImportImageTool.ts deleted file mode 100644 index 7581aaf..0000000 --- a/mcp/packages/server/src/tools/ImportImageTool.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { z } from "zod"; -import { Tool } from "../Tool"; -import { TextResponse, ToolResponse } from "../ToolResponse"; -import "reflect-metadata"; -import { PenpotMcpServer } from "../PenpotMcpServer"; -import { ExecuteCodePluginTask } from "../tasks/ExecuteCodePluginTask"; -import { FileUtils } from "../utils/FileUtils"; -import * as fs from "fs"; -import * as path from "path"; - -/** - * Arguments class for ImportImageTool - */ -export class ImportImageArgs { - static schema = { - filePath: z.string().min(1, "filePath cannot be empty").describe("Absolute path to the image file to import."), - x: z.number().optional().describe("Optional X coordinate for the rectangle's position."), - y: z.number().optional().describe("Optional Y coordinate for the rectangle's position."), - width: z - .number() - .positive("width must be positive") - .optional() - .describe( - "Optional width for the rectangle. If only width is provided, height is calculated to maintain aspect ratio." - ), - height: z - .number() - .positive("height must be positive") - .optional() - .describe( - "Optional height for the rectangle. If only height is provided, width is calculated to maintain aspect ratio." - ), - }; - - filePath!: string; - - x?: number; - - y?: number; - - width?: number; - - height?: number; -} - -/** - * Tool for importing a raster image from the local file system into Penpot - */ -export class ImportImageTool extends Tool { - /** - * Maps file extensions to MIME types. - */ - protected static readonly MIME_TYPES: { [key: string]: string } = { - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".png": "image/png", - ".gif": "image/gif", - ".webp": "image/webp", - }; - - /** - * Creates a new ImportImage tool instance. - * - * @param mcpServer - The MCP server instance - */ - constructor(mcpServer: PenpotMcpServer) { - super(mcpServer, ImportImageArgs.schema); - } - - public getToolName(): string { - return "import_image"; - } - - public getToolDescription(): string { - return ( - "Imports a pixel image from the local file system into Penpot by creating a Rectangle instance " + - "that uses the image as a fill. The rectangle has the image's original proportions by default. " + - "Optionally accepts position (x, y) and dimensions (width, height) parameters. " + - "If only one dimension is provided, the other is calculated to maintain the image's aspect ratio. " + - "Supported formats: JPEG, PNG, GIF, WEBP." - ); - } - - protected async executeCore(args: ImportImageArgs): Promise { - // check that file path is absolute - FileUtils.checkPathIsAbsolute(args.filePath); - - // check that file exists - if (!fs.existsSync(args.filePath)) { - throw new Error(`File not found: ${args.filePath}`); - } - - // read the file as binary data - const fileData = fs.readFileSync(args.filePath); - const base64Data = fileData.toString("base64"); - - // determine mime type from file extension - const ext = path.extname(args.filePath).toLowerCase(); - const mimeType = ImportImageTool.MIME_TYPES[ext]; - if (!mimeType) { - const supportedExtensions = Object.keys(ImportImageTool.MIME_TYPES).join(", "); - throw new Error( - `Unsupported image format: ${ext}. Supported formats (file extensions): ${supportedExtensions}` - ); - } - - // generate and execute JavaScript code to import the image - const fileName = path.basename(args.filePath); - const escapedBase64 = base64Data.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); - const escapedFileName = fileName.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); - const code = ` - const rectangle = await penpotUtils.importImage( - '${escapedBase64}', '${mimeType}', '${escapedFileName}', - ${args.x ?? "undefined"}, ${args.y ?? "undefined"}, - ${args.width ?? "undefined"}, ${args.height ?? "undefined"}); - return { shapeId: rectangle.id }; - `; - const task = new ExecuteCodePluginTask({ code: code }); - const executionResult = await this.mcpServer.pluginBridge.executePluginTask(task); - - return new TextResponse(JSON.stringify(executionResult.data?.result, null, 2)); - } -} diff --git a/mcp/packages/server/src/tools/ImportPenpotFileTool.ts b/mcp/packages/server/src/tools/ImportPenpotFileTool.ts deleted file mode 100644 index f55ae19..0000000 --- a/mcp/packages/server/src/tools/ImportPenpotFileTool.ts +++ /dev/null @@ -1,370 +0,0 @@ -import { z } from "zod"; -import { Tool } from "../Tool"; -import { TextResponse, ToolResponse } from "../ToolResponse"; -import "reflect-metadata"; -import { PenpotMcpServer } from "../PenpotMcpServer"; -import { NreplClient } from "../NreplClient"; -import { createLogger } from "../logger"; -import * as crypto from "crypto"; -import * as fs from "fs"; -import * as path from "path"; -import * as https from "https"; -import * as http from "http"; - -/** - * Arguments for ImportPenpotFileTool. - */ -export class ImportPenpotFileArgs { - static schema = { - url: z.url().describe("URL of the .penpot file to import."), - }; - - /** URL of the .penpot file to import */ - url!: string; -} - -/** - * Tool for importing a .penpot file into the running Penpot instance. - * - * Downloads the file from the given URL to a temporary location in the frontend's - * static directory, then triggers the import via the Penpot frontend's web worker - * using the ClojureScript REPL. The temporary file is cleaned up after the import - * completes (or fails). - * - * Only available in devenv mode, as it requires the ClojureScript nREPL connection. - */ -export class ImportPenpotFileTool extends Tool { - private static readonly POLL_INTERVAL_MS = 1_000; - private static readonly IMPORT_TIMEOUT_MS = 120_000; - - // assumes cwd is the server package root (same assumption as ConfigurationLoader) - private static readonly PUBLIC_DIR = path.resolve("../../../frontend/resources/public"); - - private static readonly NAVIGATION_HINT = - "To open an imported file in the workspace, use cljs_repl with:\n" + - "(do (require '[app.main.data.common :as dcm])\n" + - " (app.main.store/emit! (dcm/go-to-workspace\n" + - ' :team-id (parse-uuid "")\n' + - ' :file-id (parse-uuid "")\n' + - ' :page-id (parse-uuid ""))))'; - - private readonly log = createLogger("ImportPenpotFileTool"); - private readonly nreplClient: NreplClient; - - /** - * Creates a new ImportPenpotFileTool instance. - * - * @param mcpServer - the MCP server instance - * @param nreplClient - the nREPL client for communicating with shadow-cljs - */ - constructor(mcpServer: PenpotMcpServer, nreplClient: NreplClient) { - super(mcpServer, ImportPenpotFileArgs.schema); - this.nreplClient = nreplClient; - } - - public getToolName(): string { - return "import_penpot_file"; - } - - public getToolDescription(): string { - return ( - "Imports a .penpot file into the running Penpot instance from a given URL. " + - "The file is imported into the user's Drafts project. " + - "Returns the name(s) of the imported file(s)." - ); - } - - protected async executeCore(args: ImportPenpotFileArgs): Promise { - // generate a random filename for the temporary file - const randomName = `_import_${crypto.randomUUID()}.penpot`; - const tempFilePath = path.join(ImportPenpotFileTool.PUBLIC_DIR, randomName); - const servePath = `/${randomName}`; - - try { - // download the file - this.log.info("Downloading .penpot file from %s", args.url); - await this.downloadFile(args.url, tempFilePath); - const fileSize = fs.statSync(tempFilePath).size; - this.log.info("Downloaded %d bytes to %s", fileSize, tempFilePath); - - // set up the import via CLJS REPL - const atomName = `import-result-${crypto.randomUUID().slice(0, 8)}`; - const setupCode = this.buildImportCode(atomName, servePath); - - this.log.info("Initiating import via CLJS REPL"); - const setupResult = await this.nreplClient.evalCljs(setupCode); - this.log.debug("CLJS setup result: %s", JSON.stringify(setupResult)); - - // check for immediate errors in the setup - if (setupResult.err) { - throw new Error(`CLJS evaluation error: ${setupResult.err}`); - } - - // poll for the import result - const result = await this.pollForResult(atomName); - return new TextResponse(result); - } finally { - // clean up the temporary file - this.cleanupTempFile(tempFilePath); - } - } - - /** - * Builds the ClojureScript code that fetches the file from the static directory, - * creates a blob URL, and triggers the import via the web worker. - * - * @param atomName - unique name for the result atom - * @param servePath - the URL path to fetch the file from (same-origin) - * @returns the ClojureScript code string - */ - private buildImportCode(atomName: string, servePath: string): string { - // escape for embedding in a CLJS string - const escapedPath = servePath.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); - const escapedAtom = atomName.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); - - return ` - (do - (require '[app.main.store :as st]) - (require '[app.main.worker :as mw]) - (require '[app.common.uuid :as uuid]) - (require '[beicon.v2.core :as rx]) - - (def ${escapedAtom} (atom {:status :pending})) - - (let [project-id (->> @st/state :projects vals (filter :is-default) first :id) - file-ids-before (set (keys (:files @st/state)))] - (-> (js/fetch "${escapedPath}") - (.then (fn [resp] - (when-not (.-ok resp) - (reset! ${escapedAtom} {:status :error :error (str "Fetch failed: " (.-status resp))}) - (throw (js/Error. (str "Fetch failed: " (.-status resp))))) - (.blob resp))) - (.then (fn [blob] - (let [uri (js/URL.createObjectURL blob) - file-id (uuid/next) - entries [{:file-id file-id - :name "import" - :type :binfile-v3 - :uri uri}]] - (->> (mw/ask-many! - {:cmd :import-files - :project-id project-id - :files entries - :features (get @st/state :features)}) - (rx/subs! - (fn [msg] - (when (= :finish (:status msg)) - (reset! ${escapedAtom} - {:status :success - :file-ids-before file-ids-before}))) - (fn [err] - (reset! ${escapedAtom} {:status :error :error (str err)})) - (fn [] - (when (= :pending (:status @${escapedAtom})) - (reset! ${escapedAtom} {:status :error :error "Stream completed without success message"})))))))) - (.catch (fn [err] - (when (= :pending (:status @${escapedAtom})) - (reset! ${escapedAtom} {:status :error :error (str err)})))))) - - :initiated) - `; - } - - /** - * Builds the ClojureScript code that resolves the imported file details. - * - * Refreshes the dashboard, diffs the file list against the pre-import snapshot, - * and for each new file fetches the first page-id via the backend API. - * - * @param atomName - the atom holding the import result (including :file-ids-before) - * @param resultAtomName - the atom to store the final file details in - * @returns the ClojureScript code string - */ - private buildResolveCode(atomName: string, resultAtomName: string): string { - return ` - (do - (require '[app.main.store :as st]) - (require '[app.main.repo :as rp]) - (require '[app.main.data.dashboard :as dd]) - (require '[beicon.v2.core :as rx]) - - (def ${resultAtomName} (atom {:status :pending})) - - (let [file-ids-before (:file-ids-before @${atomName}) - team-id (:current-team-id @st/state)] - ;; refresh dashboard files - (st/emit! (dd/fetch-recent-files)) - ;; wait a moment for the state to update, then resolve - (js/setTimeout - (fn [] - (let [all-files (vals (:files @st/state)) - new-files (remove #(contains? file-ids-before (:id %)) all-files) - file-count (count new-files)] - (if (zero? file-count) - (reset! ${resultAtomName} {:status :success :files []}) - ;; fetch page-ids for each new file - (let [remaining (atom file-count) - results (atom [])] - (doseq [f new-files] - (->> (rp/cmd! :get-file {:id (:id f) :features (get @st/state :features)}) - (rx/subs! - (fn [file-data] - (swap! results conj - {:file-id (str (:id f)) - :name (:name f) - :team-id (str team-id) - :page-id (str (first (get-in file-data [:data :pages])))}) - (when (zero? (swap! remaining dec)) - (reset! ${resultAtomName} {:status :success :files @results}))) - (fn [err] - (swap! results conj - {:file-id (str (:id f)) - :name (:name f) - :team-id (str team-id) - :error (str err)}) - (when (zero? (swap! remaining dec)) - (reset! ${resultAtomName} {:status :success :files @results})))))))))) - 500)) - - :initiated) - `; - } - - /** - * Polls the CLJS atom for the import result until it succeeds, fails, or times out. - * On success, resolves the imported file details (server-side IDs, names, page-ids). - * - * @param atomName - the name of the atom to poll - * @returns a JSON string with the imported file details - */ - private async pollForResult(atomName: string): Promise { - const startTime = Date.now(); - - // phase 1: wait for the import to complete - while (Date.now() - startTime < ImportPenpotFileTool.IMPORT_TIMEOUT_MS) { - await this.sleep(ImportPenpotFileTool.POLL_INTERVAL_MS); - - const pollResult = await this.nreplClient.evalCljs(`(pr-str @${atomName})`); - const resultStr = pollResult.values.join(""); - this.log.debug(`Poll result: ${resultStr}`); - - if (resultStr.includes(":success")) { - this.log.info("Import succeeded, resolving file details..."); - return await this.resolveImportedFiles(atomName); - } else if (resultStr.includes(":error")) { - this.log.error(`Import failed: ${resultStr}`); - throw new Error(`Import failed: ${resultStr}`); - } - } - - throw new Error(`Import timed out after ${ImportPenpotFileTool.IMPORT_TIMEOUT_MS / 1000} seconds`); - } - - /** - * After a successful import, resolves the actual server-side file details - * by diffing the dashboard file list and fetching page IDs. - * - * @param atomName - the atom holding the import result with :file-ids-before - * @returns a JSON string with the imported file details - */ - private async resolveImportedFiles(atomName: string): Promise { - const resultAtomName = `import-details-${crypto.randomUUID().slice(0, 8)}`; - const resolveCode = this.buildResolveCode(atomName, resultAtomName); - - await this.nreplClient.evalCljs(resolveCode); - - // poll the result atom - const startTime = Date.now(); - const resolveTimeoutMs = 15_000; - - while (Date.now() - startTime < resolveTimeoutMs) { - await this.sleep(ImportPenpotFileTool.POLL_INTERVAL_MS); - - const pollResult = await this.nreplClient.evalCljs(`(pr-str @${resultAtomName})`); - const resultStr = pollResult.values.join(""); - - if (resultStr.includes(":success")) { - this.log.info("File details resolved"); - return resultStr + "\n\n" + ImportPenpotFileTool.NAVIGATION_HINT; - } - } - - this.log.warn("Timed out resolving file details, returning basic success"); - return "Import succeeded but could not resolve file details."; - } - - /** - * Downloads a file from a URL to a local path. - * - * @param url - the URL to download from - * @param destPath - the local file path to write to - */ - private downloadFile(url: string, destPath: string): Promise { - return new Promise((resolve, reject) => { - const client = url.startsWith("https") ? https : http; - const file = fs.createWriteStream(destPath); - - const request = client.get(url, (response) => { - // handle redirects - if ( - response.statusCode && - response.statusCode >= 300 && - response.statusCode < 400 && - response.headers.location - ) { - file.close(); - fs.unlinkSync(destPath); - this.downloadFile(response.headers.location, destPath).then(resolve, reject); - return; - } - - if (response.statusCode && response.statusCode !== 200) { - file.close(); - fs.unlinkSync(destPath); - reject(new Error(`Download failed with status ${response.statusCode}`)); - return; - } - - response.pipe(file); - file.on("finish", () => { - file.close(); - resolve(); - }); - }); - - request.on("error", (err) => { - file.close(); - if (fs.existsSync(destPath)) { - fs.unlinkSync(destPath); - } - reject(new Error(`Download error: ${err.message}`)); - }); - - file.on("error", (err) => { - file.close(); - if (fs.existsSync(destPath)) { - fs.unlinkSync(destPath); - } - reject(new Error(`File write error: ${err.message}`)); - }); - }); - } - - /** - * Removes the temporary file, logging but not throwing on failure. - */ - private cleanupTempFile(filePath: string): void { - try { - if (fs.existsSync(filePath)) { - fs.unlinkSync(filePath); - this.log.info("Cleaned up temporary file: %s", filePath); - } - } catch (err) { - this.log.warn("Failed to clean up temporary file %s: %s", filePath, err); - } - } - - private sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); - } -} diff --git a/mcp/packages/server/src/tools/PenpotApiInfoTool.ts b/mcp/packages/server/src/tools/PenpotApiInfoTool.ts deleted file mode 100644 index 935f4c3..0000000 --- a/mcp/packages/server/src/tools/PenpotApiInfoTool.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { z } from "zod"; -import { Tool } from "../Tool"; -import "reflect-metadata"; -import type { ToolResponse } from "../ToolResponse"; -import { TextResponse } from "../ToolResponse"; -import { PenpotMcpServer } from "../PenpotMcpServer"; -import { ApiDocs } from "../ApiDocs"; - -/** - * Arguments class for the PenpotApiInfoTool - */ -export class PenpotApiInfoArgs { - static schema = { - type: z.string().min(1, "Type name cannot be empty"), - member: z.string().optional(), - }; - - /** - * The API type name to retrieve information for. - */ - type!: string; - - /** - * The specific member name to retrieve (optional). - */ - member?: string; -} - -/** - * Tool for retrieving Penpot API documentation information. - * - * This tool provides access to API type documentation loaded from YAML files, - * allowing retrieval of either full type documentation or specific member details. - */ -export class PenpotApiInfoTool extends Tool { - private static readonly MAX_FULL_TEXT_CHARS = 2000; - private readonly apiDocs: ApiDocs; - - /** - * Creates a new PenpotApiInfo tool instance. - * - * @param mcpServer - The MCP server instance - */ - constructor(mcpServer: PenpotMcpServer, apiDocs: ApiDocs) { - super(mcpServer, PenpotApiInfoArgs.schema); - this.apiDocs = apiDocs; - } - - public getToolName(): string { - return "penpot_api_info"; - } - - public getToolDescription(): string { - return ( - "Retrieves Penpot API documentation for types and their members." + - "Be sure to read the 'Penpot High-Level Overview' first." - ); - } - - protected async executeCore(args: PenpotApiInfoArgs): Promise { - const apiType = this.apiDocs.getType(args.type); - - if (!apiType) { - throw new Error(`API type "${args.type}" not found`); - } - - if (args.member) { - // return specific member documentation - const memberDoc = apiType.getMember(args.member); - if (!memberDoc) { - throw new Error(`Member "${args.member}" not found in type "${args.type}"`); - } - return new TextResponse(memberDoc); - } else { - // return full text or overview based on length - const fullText = apiType.getFullText(); - if (fullText.length <= PenpotApiInfoTool.MAX_FULL_TEXT_CHARS) { - return new TextResponse(fullText); - } else { - return new TextResponse( - apiType.getOverviewText() + - "\n\nMember details not provided (too long). " + - "Call this tool with a member name for more information." - ); - } - } - } -} diff --git a/mcp/packages/server/src/tools/ReadTaigaIssueTool.ts b/mcp/packages/server/src/tools/ReadTaigaIssueTool.ts deleted file mode 100644 index 29e8bea..0000000 --- a/mcp/packages/server/src/tools/ReadTaigaIssueTool.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { z } from "zod"; -import { Tool } from "../Tool"; -import "reflect-metadata"; -import type { ToolResponse } from "../ToolResponse"; -import { TextResponse } from "../ToolResponse"; -import { PenpotMcpServer } from "../PenpotMcpServer"; - -/** - * Arguments for the {@link ReadTaigaIssueTool}. - */ -export class ReadTaigaIssueArgs { - static schema = { - issueNumber: z - .number() - .int() - .positive() - .describe( - "The Penpot issue number as it appears in Taiga URLs, " + - "e.g. 14177 for https://tree.taiga.io/project/penpot/issue/14177" - ), - }; - - /** - * The Penpot issue number as it appears in Taiga issue URLs - * (e.g. 14177 for https://tree.taiga.io/project/penpot/issue/14177). - */ - issueNumber!: number; -} - -/** - * Represents a file attachment on a Taiga issue. - */ -interface TaigaAttachment { - filename: string; - size: number; - url: string; -} - -/** - * Represents a comment on a Taiga issue. - */ -interface TaigaComment { - username: string; - comment: string; -} - -/** - * The resolved issue data returned by the tool. - */ -interface TaigaIssueData { - subject: string; - description: string; - status: string; - attachments: TaigaAttachment[]; - comments: TaigaComment[]; -} - -/** - * Tool for reading Penpot issues from the Taiga project tracker. - * - * Resolves a Penpot issue number to its internal Taiga ID and retrieves the issue's - * subject, description, status, attachments, and comments via the Taiga REST API. - */ -export class ReadTaigaIssueTool extends Tool { - private static readonly TAIGA_API_BASE = "https://api.taiga.io/api/v1"; - - constructor(mcpServer: PenpotMcpServer) { - super(mcpServer, ReadTaigaIssueArgs.schema); - } - - public getToolName(): string { - return "read_taiga_issue"; - } - - public getToolDescription(): string { - return "Reads a Penpot issue from the Taiga project tracker, returning its subject, description, status, attachments, and comments."; - } - - protected async executeCore(args: ReadTaigaIssueArgs): Promise { - const { projectId, issueId } = await this.resolveIssue(args.issueNumber); - const issueData = await this.fetchIssueData(projectId, issueId); - return new TextResponse(JSON.stringify(issueData, null, 2)); - } - - /** - * Resolves a Penpot issue number to the internal Taiga project and issue IDs. - */ - private async resolveIssue(issueNumber: number): Promise<{ projectId: number; issueId: number }> { - const url = `${ReadTaigaIssueTool.TAIGA_API_BASE}/resolver?project=penpot&issue=${issueNumber}`; - const data = await this.fetchJson(url); - return { projectId: data.project, issueId: data.issue }; - } - - /** - * Fetches the full issue data including details, attachments, and comments. - */ - private async fetchIssueData(projectId: number, issueId: number): Promise { - // fetch issue details, attachments, and history in parallel - const [details, attachments, comments] = await Promise.all([ - this.fetchIssueDetails(issueId), - this.fetchAttachments(projectId, issueId), - this.fetchComments(issueId), - ]); - - return { - subject: details.subject, - description: details.description ?? "", - status: details.status_extra_info?.name ?? "Unknown", - attachments, - comments, - }; - } - - /** - * Fetches the core issue details from the Taiga API. - */ - private async fetchIssueDetails(issueId: number): Promise { - const url = `${ReadTaigaIssueTool.TAIGA_API_BASE}/issues/${issueId}`; - return this.fetchJson(url); - } - - /** - * Fetches the attachments for an issue. - */ - private async fetchAttachments(projectId: number, issueId: number): Promise { - const url = `${ReadTaigaIssueTool.TAIGA_API_BASE}/issues/attachments?project=${projectId}&object_id=${issueId}`; - const data: any[] = await this.fetchJson(url); - return data.map((a) => ({ - filename: a.name, - size: a.size, - url: a.url, - })); - } - - /** - * Fetches comments from the issue history. - * - * History entries that have a non-empty `comment` field are treated as comments. - */ - private async fetchComments(issueId: number): Promise { - const url = `${ReadTaigaIssueTool.TAIGA_API_BASE}/history/issue/${issueId}`; - const history: any[] = await this.fetchJson(url); - return history - .filter((entry) => entry.comment && entry.comment.trim().length > 0) - .map((entry) => ({ - username: entry.user?.username ?? "unknown", - comment: entry.comment, - })); - } - - /** - * Performs a GET request and returns the parsed JSON response. - * - * @throws Error if the HTTP response status is not OK - */ - private async fetchJson(url: string): Promise { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Taiga API request failed: ${response.status} ${response.statusText} (${url})`); - } - return response.json(); - } -} diff --git a/mcp/packages/server/src/types/nrepl-client.d.ts b/mcp/packages/server/src/types/nrepl-client.d.ts deleted file mode 100644 index 21ed53f..0000000 --- a/mcp/packages/server/src/types/nrepl-client.d.ts +++ /dev/null @@ -1,53 +0,0 @@ -declare module "nrepl-client" { - import type { Socket } from "net"; - - interface NreplConnection extends Socket { - /** - * Evaluates the given Clojure expression on the nREPL server. - * - * @param code - the Clojure expression to evaluate - * @param callback - called with an error or array of response messages - */ - eval(code: string, callback: (err: Error | null, result: NreplMessage[]) => void): void; - - /** - * Sends a raw nREPL message to the server. - */ - send(message: Record, callback: (err: Error | null, result: NreplMessage[]) => void): void; - - /** - * Clones the current session, creating a new session that inherits the current state. - */ - clone(callback: (err: Error | null, result: NreplMessage[]) => void): void; - - /** - * Closes the current session. - */ - close(callback: (err: Error | null, result: NreplMessage[]) => void): void; - } - - interface NreplMessage { - id?: string; - session?: string; - "new-session"?: string; - ns?: string; - value?: string; - out?: string; - err?: string; - ex?: string; - status?: string[]; - } - - interface ConnectOptions { - port: number; - host?: string; - } - - /** - * Creates a connection to an nREPL server. - */ - function connect(options: ConnectOptions): NreplConnection; - - export default { connect }; - export type { NreplConnection, NreplMessage, ConnectOptions }; -} diff --git a/mcp/packages/server/src/utils/FileUtils.ts b/mcp/packages/server/src/utils/FileUtils.ts deleted file mode 100644 index 8da7c7d..0000000 --- a/mcp/packages/server/src/utils/FileUtils.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as path from "path"; -import * as fs from "fs"; - -export class FileUtils { - /** - * Checks whether the given file path is absolute and raises an error if not. - * - * @param filePath - The file path to check - */ - public static checkPathIsAbsolute(filePath: string): void { - if (!path.isAbsolute(filePath)) { - throw new Error(`The specified file path must be absolute: ${filePath}`); - } - } - - public static createParentDirectories(filePath: string): void { - const dir = path.dirname(filePath); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - } - - /** - * Writes binary data to a file at the specified path, creating the parent directories if necessary. - * - * @param filePath - The absolute path to the file where data should be written - * @param bytes - The binary data to write to the file - */ - public static writeBinaryFile(filePath: string, bytes: Uint8Array): void { - this.createParentDirectories(filePath); - fs.writeFileSync(filePath, Buffer.from(bytes)); - } - - /** - * Writes text data to a file at the specified path, creating the parent directories if necessary. - * - * @param filePath - The absolute path to the file where data should be written - * @param text - The text data to write to the file - */ - public static writeTextFile(filePath: string, text: string): void { - this.createParentDirectories(filePath); - fs.writeFileSync(filePath, text, { encoding: "utf-8" }); - } -} diff --git a/mcp/packages/server/src/utils/Semaphore.ts b/mcp/packages/server/src/utils/Semaphore.ts deleted file mode 100644 index e88a63d..0000000 --- a/mcp/packages/server/src/utils/Semaphore.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { createLogger } from "../logger"; - -/** - * Counting semaphore for bounding the parallelism of asynchronous operations. - * - * Calls in excess of the configured maximum are queued in FIFO order and - * proceed once an earlier holder has released its permit. - */ -export class Semaphore { - private static readonly logger = createLogger("Semaphore"); - - private available: number; - private readonly waiters: Array<() => void> = []; - - /** - * @param name - identifier used in log messages so the source of contention is recognisable - * @param maxConcurrent - the maximum number of permits that may be held simultaneously - */ - constructor( - private readonly name: string, - maxConcurrent: number - ) { - if (maxConcurrent < 1) { - throw new Error(`maxConcurrent must be at least 1; got ${maxConcurrent}`); - } - this.available = maxConcurrent; - } - - /** - * Acquires a permit, runs the given function, and releases the permit when it - * settles - including on rejection. Queues if no permit is currently available. - * - * @param fn - the function to run while holding the permit - * @returns the value returned by fn - */ - public async withPermit(fn: () => Promise): Promise { - await this.acquire(); - try { - return await fn(); - } finally { - this.release(); - } - } - - private acquire(): Promise { - if (this.available > 0) { - this.available--; - return Promise.resolve(); - } - return new Promise((resolve) => { - this.waiters.push(resolve); - Semaphore.logger.info( - "Semaphore '%s' saturated; request queued (%d waiting)", - this.name, - this.waiters.length - ); - }); - } - - private release(): void { - const next = this.waiters.shift(); - if (next !== undefined) { - // pass the permit directly to the next waiter without touching `available` - next(); - } else { - this.available++; - } - } -} diff --git a/mcp/packages/server/tsconfig.json b/mcp/packages/server/tsconfig.json deleted file mode 100644 index 5362935..0000000 --- a/mcp/packages/server/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "lib": ["ES2022"], - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "resolveJsonModule": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/mcp/pnpm-lock.yaml b/mcp/pnpm-lock.yaml deleted file mode 100644 index 13321c8..0000000 --- a/mcp/pnpm-lock.yaml +++ /dev/null @@ -1,2826 +0,0 @@ ---- -lockfileVersion: '9.0' - -importers: - - .: - configDependencies: {} - packageManagerDependencies: - pnpm: - specifier: 12.5.1 - version: 12.5.1 - -packages: - - '@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.5.1': - resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==} - cpu: [x64] - os: [darwin] - - '@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.5.1': - resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@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.5.1': - resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@pnpm/exe.win32-arm64@12.5.1': - resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==} - cpu: [arm64] - os: [win32] - - '@pnpm/exe.win32-x64@12.5.1': - resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==} - cpu: [x64] - os: [win32] - - pnpm@12.5.1: - resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==} - engines: {node: '>=18.*'} - hasBin: true - -snapshots: - - '@pnpm/exe.android-arm64@12.5.1': - optional: true - - '@pnpm/exe.android-x64@12.5.1': - optional: true - - '@pnpm/exe.darwin-arm64@12.5.1': - optional: true - - '@pnpm/exe.darwin-x64@12.5.1': - optional: true - - '@pnpm/exe.freebsd-x64@12.5.1': - optional: true - - '@pnpm/exe.linux-arm64-musl@12.5.1': - optional: true - - '@pnpm/exe.linux-arm64@12.5.1': - optional: true - - '@pnpm/exe.linux-ppc64@12.5.1': - optional: true - - '@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.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' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -overrides: - '@hono/node-server@<2.0.5': ^2.0.5 - -importers: - - .: - devDependencies: - concurrently: - specifier: ^10.0.5 - version: 10.0.5 - prettier: - specifier: ^3.9.6 - version: 3.9.6 - - packages/common: - devDependencies: - typescript: - specifier: ^6.0.3 - version: 6.0.3 - - packages/plugin: - dependencies: - '@penpot/plugin-styles': - specifier: 1.5.0 - version: 1.5.0 - '@penpot/plugin-types': - specifier: 1.5.0 - version: 1.5.0 - devDependencies: - cross-env: - specifier: ^10.1.0 - version: 10.1.0 - typescript: - specifier: ^6.0.3 - version: 6.0.3 - vite: - specifier: ^8.2.0 - version: 8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(tsx@4.23.12) - vite-live-preview: - specifier: ^0.4.0 - version: 0.4.0(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(tsx@4.23.12)) - - packages/server: - dependencies: - '@modelcontextprotocol/sdk': - specifier: ^1.29.0 - version: 1.30.0(supports-color@10.2.2)(zod@4.4.3) - class-transformer: - specifier: ^0.5.1 - version: 0.5.1 - class-validator: - specifier: ^0.15.1 - version: 0.15.1 - express: - specifier: ^5.2.1 - version: 5.2.1(supports-color@10.2.2) - ioredis: - specifier: ^6.0.0 - version: 6.0.0(supports-color@10.2.2) - js-yaml: - specifier: ^5.2.3 - version: 5.4.1 - nrepl-client: - specifier: ^0.3.0 - version: 0.3.0 - pino: - specifier: ^10.3.1 - version: 10.3.1 - pino-loki: - specifier: ^3.0.0 - version: 3.0.0 - pino-pretty: - specifier: ^13.1.3 - version: 13.1.3 - reflect-metadata: - specifier: ^0.2.2 - version: 0.2.2 - sharp: - specifier: ^0.35.3 - version: 0.35.4(@types/node@26.4.0) - ws: - specifier: ^8.21.1 - version: 8.21.3 - zod: - specifier: ^4.4.3 - version: 4.4.3 - devDependencies: - '@penpot/mcp-common': - specifier: workspace:../common - version: link:../common - '@types/express': - specifier: ^5.0.6 - version: 5.0.6 - '@types/js-yaml': - specifier: ^4.0.9 - version: 4.0.9 - '@types/node': - specifier: ^26.1.2 - version: 26.4.0 - '@types/ws': - specifier: ^8.18.1 - version: 8.18.1 - cross-env: - specifier: ^10.1.0 - version: 10.1.0 - esbuild: - specifier: ^0.28.1 - version: 0.28.2 - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@types/node@26.4.0)(typescript@6.0.3) - tsx: - specifier: ^4.23.5 - version: 4.23.12 - typescript: - specifier: ^6.0.3 - version: 6.0.3 - -packages: - - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - - '@emnapi/runtime@1.11.3': - resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} - - '@epic-web/invariant@1.0.0': - resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} - - '@esbuild/aix-ppc64@0.28.2': - resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.28.2': - resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.28.2': - resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.28.2': - resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.28.2': - resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.28.2': - resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.28.2': - resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.28.2': - resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.28.2': - resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.28.2': - resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.28.2': - resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.28.2': - resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.28.2': - resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.28.2': - resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.28.2': - resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.28.2': - resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.28.2': - resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.28.2': - resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.28.2': - resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.28.2': - resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.28.2': - resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.28.2': - resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.28.2': - resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.28.2': - resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.28.2': - resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.28.2': - resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@hono/node-server@2.1.1': - resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==} - engines: {node: '>=20'} - peerDependencies: - hono: ^4 - - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} - - '@img/sharp-darwin-arm64@0.35.4': - resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.35.4': - resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [darwin] - - '@img/sharp-freebsd-wasm32@0.35.4': - resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==} - engines: {node: '>=20.9.0'} - os: [freebsd] - - '@img/sharp-libvips-darwin-arm64@1.3.3': - resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.3.3': - resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.3.3': - resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-arm@1.3.3': - resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-ppc64@1.3.3': - resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-riscv64@1.3.3': - resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-s390x@1.3.3': - resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-x64@1.3.3': - resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linuxmusl-arm64@1.3.3': - resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-libvips-linuxmusl-x64@1.3.3': - resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-linux-arm64@0.35.4': - resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-arm@0.35.4': - resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==} - engines: {node: '>=20.9.0'} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-ppc64@0.35.4': - resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==} - engines: {node: '>=20.9.0'} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-riscv64@0.35.4': - resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==} - engines: {node: '>=20.9.0'} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-s390x@0.35.4': - resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==} - engines: {node: '>=20.9.0'} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-x64@0.35.4': - resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-linuxmusl-arm64@0.35.4': - resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-linuxmusl-x64@0.35.4': - resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-wasm32@0.35.4': - resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==} - engines: {node: '>=20.9.0'} - - '@img/sharp-webcontainers-wasm32@0.35.4': - resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==} - engines: {node: '>=20.9.0'} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.35.4': - resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.35.4': - resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==} - engines: {node: ^20.9.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.35.4': - resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [win32] - - '@ioredis/commands@2.0.0': - resolution: {integrity: sha512-vrx0AE/T0h7cRZwfo1M39Cr+ZhZrkf0V8mQN75wucKCxCLD9l/VX6no3gFvrLqD1IlG/1LtzWovqEw3t0Vr9zg==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - - '@modelcontextprotocol/sdk@1.30.0': - resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} - engines: {node: '>=18'} - peerDependencies: - '@cfworker/json-schema': ^4.1.1 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - '@cfworker/json-schema': - optional: true - - '@oxc-project/types@0.147.0': - resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==} - - '@penpot/plugin-styles@1.5.0': - resolution: {integrity: sha512-rwXFBRPd0IZs3ens+eRT8oA5WqkQgF6xlW6fCfH7U1NHRjej9HH7vTGXmfjoIVml6Xx1sbyYHZVxkQC8Ub8+zA==} - - '@penpot/plugin-types@1.5.0': - resolution: {integrity: sha512-hFk4Rx66rLaSUWoN6bhxXkDI2ymwKg9xokdslvZvXM5MSmEmfhsJKUMwtuzgYWljbbfaQhK+DjK8g8yVbbavtA==} - - '@pinojs/redact@0.4.0': - resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} - - '@rolldown/binding-android-arm-eabi@1.2.6': - resolution: {integrity: sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - - '@rolldown/binding-android-arm64@1.2.6': - resolution: {integrity: sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.2.6': - resolution: {integrity: sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.2.6': - resolution: {integrity: sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.2.6': - resolution: {integrity: sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.2.6': - resolution: {integrity: sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.2.6': - resolution: {integrity: sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.2.6': - resolution: {integrity: sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.2.6': - resolution: {integrity: sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.2.6': - resolution: {integrity: sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.2.6': - resolution: {integrity: sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.2.6': - resolution: {integrity: sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.2.6': - resolution: {integrity: sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-win32-arm64-msvc@1.2.6': - resolution: {integrity: sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.2.6': - resolution: {integrity: sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@seahax/deep-copy@0.1.0': - resolution: {integrity: sha512-Ux88qw9ypPbqszDGwj0JvP8FP017MV3ck7jnkit+0i3mecTkqhYiCUqy3exbxhGFBu07zrUS6yPRUWVct1eSMQ==} - - '@seahax/semaphore@0.5.1': - resolution: {integrity: sha512-q6SXYYbE6X+LDcq2h2yCgE+pCWJumNP3XCZkztdG4S4tiig9akMZGp8TsfU/EIRcHWPdnQ3BA8/NAvdDYdF/NQ==} - - '@tsconfig/node10@1.0.13': - resolution: {integrity: sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - - '@types/body-parser@1.19.6': - resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} - - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - - '@types/express-serve-static-core@5.1.3': - resolution: {integrity: sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==} - - '@types/express@5.0.6': - resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} - - '@types/http-errors@2.0.5': - resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} - - '@types/js-yaml@4.0.9': - resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} - - '@types/node@26.4.0': - resolution: {integrity: sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==} - - '@types/qs@6.15.1': - resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} - - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - - '@types/send@1.2.1': - resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} - - '@types/serve-static@2.2.0': - resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - - '@types/validator@13.15.10': - resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} - - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - - acorn-walk@8.3.5: - resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} - engines: {node: '>=0.4.0'} - - acorn@8.18.0: - resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - - bencode@2.0.3: - resolution: {integrity: sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==} - - body-parser@2.3.0: - resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} - engines: {node: '>=18'} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - class-transformer@0.5.1: - resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - - class-validator@0.15.1: - resolution: {integrity: sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==} - - cliui@9.0.1: - resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} - engines: {node: '>=20'} - - cluster-key-slot@1.1.1: - resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} - engines: {node: '>=0.10.0'} - - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - - concurrently@10.0.5: - resolution: {integrity: sha512-JaP/CoftUrCcAFW/g//RbgEGwlelnEae6cfBLgH6ZdO6s8jPkn6p9SB9u6pdVxYXoiSnFqseOlHfrEfF82TVOg==} - engines: {node: '>=22'} - hasBin: true - - content-disposition@1.1.0: - resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} - engines: {node: '>=18'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - content-type@2.1.0: - resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} - engines: {node: '>=18'} - - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - - cors@2.8.6: - resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} - engines: {node: '>= 0.10'} - - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - - cross-env@10.1.0: - resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} - engines: {node: '>=20'} - hasBin: true - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - dateformat@4.6.3: - resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - diff@4.0.4: - resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} - engines: {node: '>=0.3.1'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.2: - resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} - engines: {node: '>= 0.4'} - - esbuild@0.28.2: - resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-goat@4.0.0: - resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==} - engines: {node: '>=12'} - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - eventsource-parser@3.1.1: - resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} - engines: {node: '>=18.0.0'} - - eventsource@3.0.7: - resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} - engines: {node: '>=18.0.0'} - - express-rate-limit@8.6.2: - resolution: {integrity: sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==} - engines: {node: '>= 16'} - peerDependencies: - express: '>= 4.11' - - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} - - fast-copy@4.0.4: - resolution: {integrity: sha512-eVAiWVNPSEGIzDl5yPuLrx8fNMogScXvD9xp1Kzd41FjRIz2I3sSIcxsFeM5EzFfHAfobdvs8ZySffUopljvIA==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - - fast-uri@3.1.6: - resolution: {integrity: sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-east-asian-width@1.6.0: - resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} - engines: {node: '>=18'} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - hasown@2.0.4: - resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} - engines: {node: '>= 0.4'} - - help-me@5.0.0: - resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} - - hono@4.13.5: - resolution: {integrity: sha512-O6+/eCYRkzzzy0rPWwKLiGBR1nFuUPZynnwjxN1MBA62NNqbT0wQEzQyK2gSO5yDIDB336sXQleAhOHrzlYyKw==} - engines: {node: '>=16.9.0'} - - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - - iconv-lite@0.7.3: - resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} - engines: {node: '>=0.10.0'} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ioredis@6.0.0: - resolution: {integrity: sha512-f+Dtubxfpf6KYFq7WVXJoOLn0bk4TJrMrN9SzeE+jrWrCWj7XX3fA6vkryafhADX+GMymRxgDJDOI33COkJc0w==} - engines: {node: '>=20.0.0'} - - ip-address@10.5.0: - resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==} - engines: {node: '>= 12'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jose@6.2.10: - resolution: {integrity: sha512-iiW7J9qRFlGxvCOIBDBDxFePQSn7ZMAnrYGhrrOo6siO/MIqwfyilLR27pkfDgUk+raLuzADS8A3S/KLBisc0g==} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - - js-yaml@5.4.1: - resolution: {integrity: sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==} - hasBin: true - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-schema-typed@8.0.2: - resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - - libphonenumber-js@1.13.11: - resolution: {integrity: sha512-ETER2kMaIFTI/Nh1a8Gk03dUF/SL0VZqtI+CcVHZxp5WIHYwNS7S+uiYZDYCvLy3lOR4/DAD5jf0h5WkePPpqg==} - - lightningcss-android-arm64@1.33.0: - resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.33.0: - resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.33.0: - resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.33.0: - resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.33.0: - resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.33.0: - resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.33.0: - resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.33.0: - resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.33.0: - resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.33.0: - resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.33.0: - resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.33.0: - resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} - engines: {node: '>= 12.0.0'} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - media-typer@1.1.1: - resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} - engines: {node: '>= 0.8'} - - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.18: - resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - negotiator@1.1.0: - resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} - engines: {node: '>=18'} - - nrepl-client@0.3.0: - resolution: {integrity: sha512-EcROXUrzlGHKOdu/E/5WB0OESCI0iGHhdXeYk9cULYtd72eFJrM/Q1umvjTBfKWlT62y76cnyLG/3CmSCqT12w==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - on-exit-leak-free@2.1.2: - resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} - engines: {node: '>=14.0.0'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-to-regexp@8.4.2: - resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.7: - resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} - engines: {node: '>=12'} - - pino-abstract-transport@3.0.0: - resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} - - pino-loki@3.0.0: - resolution: {integrity: sha512-9TyUW5syTjp2nT70QcijJtIWUzdYUj+olQ7+fWNfm1/HrDGEWt86Q4ACzClH6DM6GBwtQimRDgneNczP+p4ypA==} - engines: {node: '>=20'} - hasBin: true - - pino-pretty@13.1.3: - resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} - hasBin: true - - pino-std-serializers@7.1.0: - resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} - - pino@10.3.1: - resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} - hasBin: true - - pkce-challenge@5.0.1: - resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} - engines: {node: '>=16.20.0'} - - postcss@8.5.26: - resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} - engines: {node: ^10 || ^12 || >=14} - - prettier@3.9.6: - resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} - engines: {node: '>=14'} - hasBin: true - - process-warning@5.1.0: - resolution: {integrity: sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - pump@3.0.4: - resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} - - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} - engines: {node: '>=0.6'} - - quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - - range-parser@1.3.0: - resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} - engines: {node: '>= 0.6'} - - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} - - real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - - real-require@1.0.0: - resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} - - redis-errors@1.2.0: - resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} - engines: {node: '>=4'} - - reflect-metadata@0.2.2: - resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - rolldown@1.2.6: - resolution: {integrity: sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - secure-json-parse@4.1.0: - resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} - - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} - engines: {node: '>=10'} - hasBin: true - - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - sharp@0.35.4: - resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==} - engines: {node: '>=20.9.0'} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - shell-quote@1.9.0: - resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==} - engines: {node: '>= 0.4'} - - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.1: - resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} - engines: {node: '>= 0.4'} - - sonic-boom@4.2.1: - resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - standard-as-callback@2.1.0: - resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - - strip-json-comments@5.0.3: - resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} - engines: {node: '>=14.16'} - - supports-color@10.2.2: - resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} - engines: {node: '>=18'} - - thread-stream@4.2.0: - resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} - engines: {node: '>=20'} - - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsx@4.23.12: - resolution: {integrity: sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==} - engines: {node: '>=18.0.0'} - hasBin: true - - type-is@2.1.0: - resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} - engines: {node: '>= 18'} - - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@8.3.0: - resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - - validator@13.15.35: - resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} - engines: {node: '>= 0.10'} - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - vite-live-preview@0.4.0: - resolution: {integrity: sha512-Qz8kr0kixXwnQl+zLPZX66OjajN4jnVnDwhNToJsO6TTboUtBo8pEmRuc0iBmkwW9lXR8mOeMu+QtxFkXBcHYg==} - peerDependencies: - vite: '>=5.4.0' - - vite@8.2.2: - resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.4.0 || ^0.5.0 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@8.21.3: - resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yargs-parser@22.0.0: - resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - - yargs@18.0.0: - resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - - zod-to-json-schema@3.25.2: - resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} - peerDependencies: - zod: ^3.25.28 || ^4 - - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - -snapshots: - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - - '@emnapi/runtime@1.11.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@epic-web/invariant@1.0.0': {} - - '@esbuild/aix-ppc64@0.28.2': - optional: true - - '@esbuild/android-arm64@0.28.2': - optional: true - - '@esbuild/android-arm@0.28.2': - optional: true - - '@esbuild/android-x64@0.28.2': - optional: true - - '@esbuild/darwin-arm64@0.28.2': - optional: true - - '@esbuild/darwin-x64@0.28.2': - optional: true - - '@esbuild/freebsd-arm64@0.28.2': - optional: true - - '@esbuild/freebsd-x64@0.28.2': - optional: true - - '@esbuild/linux-arm64@0.28.2': - optional: true - - '@esbuild/linux-arm@0.28.2': - optional: true - - '@esbuild/linux-ia32@0.28.2': - optional: true - - '@esbuild/linux-loong64@0.28.2': - optional: true - - '@esbuild/linux-mips64el@0.28.2': - optional: true - - '@esbuild/linux-ppc64@0.28.2': - optional: true - - '@esbuild/linux-riscv64@0.28.2': - optional: true - - '@esbuild/linux-s390x@0.28.2': - optional: true - - '@esbuild/linux-x64@0.28.2': - optional: true - - '@esbuild/netbsd-arm64@0.28.2': - optional: true - - '@esbuild/netbsd-x64@0.28.2': - optional: true - - '@esbuild/openbsd-arm64@0.28.2': - optional: true - - '@esbuild/openbsd-x64@0.28.2': - optional: true - - '@esbuild/openharmony-arm64@0.28.2': - optional: true - - '@esbuild/sunos-x64@0.28.2': - optional: true - - '@esbuild/win32-arm64@0.28.2': - optional: true - - '@esbuild/win32-ia32@0.28.2': - optional: true - - '@esbuild/win32-x64@0.28.2': - optional: true - - '@hono/node-server@2.1.1(hono@4.13.5)': - dependencies: - hono: 4.13.5 - - '@img/colour@1.1.0': {} - - '@img/sharp-darwin-arm64@0.35.4': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.3.3 - optional: true - - '@img/sharp-darwin-x64@0.35.4': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.3.3 - optional: true - - '@img/sharp-freebsd-wasm32@0.35.4': - dependencies: - '@img/sharp-wasm32': 0.35.4 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.3.3': - optional: true - - '@img/sharp-libvips-darwin-x64@1.3.3': - optional: true - - '@img/sharp-libvips-linux-arm64@1.3.3': - optional: true - - '@img/sharp-libvips-linux-arm@1.3.3': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.3.3': - optional: true - - '@img/sharp-libvips-linux-riscv64@1.3.3': - optional: true - - '@img/sharp-libvips-linux-s390x@1.3.3': - optional: true - - '@img/sharp-libvips-linux-x64@1.3.3': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.3.3': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.3.3': - optional: true - - '@img/sharp-linux-arm64@0.35.4': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.3.3 - optional: true - - '@img/sharp-linux-arm@0.35.4': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.3.3 - optional: true - - '@img/sharp-linux-ppc64@0.35.4': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.3.3 - optional: true - - '@img/sharp-linux-riscv64@0.35.4': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.3.3 - optional: true - - '@img/sharp-linux-s390x@0.35.4': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.3.3 - optional: true - - '@img/sharp-linux-x64@0.35.4': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.3.3 - optional: true - - '@img/sharp-linuxmusl-arm64@0.35.4': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 - optional: true - - '@img/sharp-linuxmusl-x64@0.35.4': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.3.3 - optional: true - - '@img/sharp-wasm32@0.35.4': - dependencies: - '@emnapi/runtime': 1.11.3 - optional: true - - '@img/sharp-webcontainers-wasm32@0.35.4': - dependencies: - '@img/sharp-wasm32': 0.35.4 - optional: true - - '@img/sharp-win32-arm64@0.35.4': - optional: true - - '@img/sharp-win32-ia32@0.35.4': - optional: true - - '@img/sharp-win32-x64@0.35.4': - optional: true - - '@ioredis/commands@2.0.0': {} - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@modelcontextprotocol/sdk@1.30.0(supports-color@10.2.2)(zod@4.4.3)': - dependencies: - '@hono/node-server': 2.1.1(hono@4.13.5) - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - content-type: 1.0.5 - cors: 2.8.6 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.1.1 - express: 5.2.1(supports-color@10.2.2) - express-rate-limit: 8.6.2(express@5.2.1(supports-color@10.2.2))(supports-color@10.2.2) - hono: 4.13.5 - jose: 6.2.10 - json-schema-typed: 8.0.2 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 - zod: 4.4.3 - zod-to-json-schema: 3.25.2(zod@4.4.3) - transitivePeerDependencies: - - supports-color - - '@oxc-project/types@0.147.0': {} - - '@penpot/plugin-styles@1.5.0': {} - - '@penpot/plugin-types@1.5.0': {} - - '@pinojs/redact@0.4.0': {} - - '@rolldown/binding-android-arm-eabi@1.2.6': - optional: true - - '@rolldown/binding-android-arm64@1.2.6': - optional: true - - '@rolldown/binding-darwin-arm64@1.2.6': - optional: true - - '@rolldown/binding-darwin-x64@1.2.6': - optional: true - - '@rolldown/binding-freebsd-x64@1.2.6': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.2.6': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.2.6': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.2.6': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.2.6': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.2.6': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.2.6': - optional: true - - '@rolldown/binding-linux-x64-musl@1.2.6': - optional: true - - '@rolldown/binding-openharmony-arm64@1.2.6': - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.2.6': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.2.6': - optional: true - - '@rolldown/pluginutils@1.0.1': {} - - '@seahax/deep-copy@0.1.0': {} - - '@seahax/semaphore@0.5.1': {} - - '@tsconfig/node10@1.0.13': {} - - '@tsconfig/node12@1.0.11': {} - - '@tsconfig/node14@1.0.3': {} - - '@tsconfig/node16@1.0.4': {} - - '@types/body-parser@1.19.6': - dependencies: - '@types/connect': 3.4.38 - '@types/node': 26.4.0 - - '@types/connect@3.4.38': - dependencies: - '@types/node': 26.4.0 - - '@types/express-serve-static-core@5.1.3': - dependencies: - '@types/node': 26.4.0 - '@types/qs': 6.15.1 - '@types/range-parser': 1.2.7 - '@types/send': 1.2.1 - - '@types/express@5.0.6': - dependencies: - '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 5.1.3 - '@types/serve-static': 2.2.0 - - '@types/http-errors@2.0.5': {} - - '@types/js-yaml@4.0.9': {} - - '@types/node@26.4.0': - dependencies: - undici-types: 8.3.0 - - '@types/qs@6.15.1': {} - - '@types/range-parser@1.2.7': {} - - '@types/send@1.2.1': - dependencies: - '@types/node': 26.4.0 - - '@types/serve-static@2.2.0': - dependencies: - '@types/http-errors': 2.0.5 - '@types/node': 26.4.0 - - '@types/validator@13.15.10': {} - - '@types/ws@8.18.1': - dependencies: - '@types/node': 26.4.0 - - accepts@2.0.0: - dependencies: - mime-types: 3.0.2 - negotiator: 1.1.0 - - acorn-walk@8.3.5: - dependencies: - acorn: 8.18.0 - - acorn@8.18.0: {} - - ajv-formats@3.0.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - - ajv@8.20.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.6 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - ansi-regex@6.2.2: {} - - ansi-styles@6.2.3: {} - - arg@4.1.3: {} - - argparse@2.0.1: {} - - atomic-sleep@1.0.0: {} - - bencode@2.0.3: {} - - body-parser@2.3.0(supports-color@10.2.2): - dependencies: - bytes: 3.1.2 - content-type: 2.1.0 - debug: 4.4.3(supports-color@10.2.2) - http-errors: 2.0.1 - iconv-lite: 0.7.3 - on-finished: 2.4.1 - qs: 6.15.3 - raw-body: 3.0.2 - type-is: 2.1.0 - transitivePeerDependencies: - - supports-color - - bytes@3.1.2: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - chalk@5.6.2: {} - - class-transformer@0.5.1: {} - - class-validator@0.15.1: - dependencies: - '@types/validator': 13.15.10 - libphonenumber-js: 1.13.11 - validator: 13.15.35 - - cliui@9.0.1: - dependencies: - string-width: 7.2.0 - strip-ansi: 7.2.0 - wrap-ansi: 9.0.2 - - cluster-key-slot@1.1.1: {} - - colorette@2.0.20: {} - - concurrently@10.0.5: - dependencies: - chalk: 5.6.2 - rxjs: 7.8.2 - shell-quote: 1.9.0 - supports-color: 10.2.2 - tree-kill: 1.2.2 - yargs: 18.0.0 - - content-disposition@1.1.0: {} - - content-type@1.0.5: {} - - content-type@2.1.0: {} - - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - - cors@2.8.6: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - - create-require@1.1.1: {} - - cross-env@10.1.0: - dependencies: - '@epic-web/invariant': 1.0.0 - cross-spawn: 7.0.6 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - dateformat@4.6.3: {} - - debug@4.4.3(supports-color@10.2.2): - dependencies: - ms: 2.1.3 - optionalDependencies: - supports-color: 10.2.2 - - denque@2.1.0: {} - - depd@2.0.0: {} - - detect-libc@2.1.2: {} - - diff@4.0.4: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - ee-first@1.1.1: {} - - emoji-regex@10.6.0: {} - - encodeurl@2.0.0: {} - - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.2: - dependencies: - es-errors: 1.3.0 - - esbuild@0.28.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.2 - '@esbuild/android-arm': 0.28.2 - '@esbuild/android-arm64': 0.28.2 - '@esbuild/android-x64': 0.28.2 - '@esbuild/darwin-arm64': 0.28.2 - '@esbuild/darwin-x64': 0.28.2 - '@esbuild/freebsd-arm64': 0.28.2 - '@esbuild/freebsd-x64': 0.28.2 - '@esbuild/linux-arm': 0.28.2 - '@esbuild/linux-arm64': 0.28.2 - '@esbuild/linux-ia32': 0.28.2 - '@esbuild/linux-loong64': 0.28.2 - '@esbuild/linux-mips64el': 0.28.2 - '@esbuild/linux-ppc64': 0.28.2 - '@esbuild/linux-riscv64': 0.28.2 - '@esbuild/linux-s390x': 0.28.2 - '@esbuild/linux-x64': 0.28.2 - '@esbuild/netbsd-arm64': 0.28.2 - '@esbuild/netbsd-x64': 0.28.2 - '@esbuild/openbsd-arm64': 0.28.2 - '@esbuild/openbsd-x64': 0.28.2 - '@esbuild/openharmony-arm64': 0.28.2 - '@esbuild/sunos-x64': 0.28.2 - '@esbuild/win32-arm64': 0.28.2 - '@esbuild/win32-ia32': 0.28.2 - '@esbuild/win32-x64': 0.28.2 - - escalade@3.2.0: {} - - escape-goat@4.0.0: {} - - escape-html@1.0.3: {} - - etag@1.8.1: {} - - eventsource-parser@3.1.1: {} - - eventsource@3.0.7: - dependencies: - eventsource-parser: 3.1.1 - - express-rate-limit@8.6.2(express@5.2.1(supports-color@10.2.2))(supports-color@10.2.2): - dependencies: - debug: 4.4.3(supports-color@10.2.2) - express: 5.2.1(supports-color@10.2.2) - ip-address: 10.5.0 - transitivePeerDependencies: - - supports-color - - express@5.2.1(supports-color@10.2.2): - dependencies: - accepts: 2.0.0 - body-parser: 2.3.0(supports-color@10.2.2) - content-disposition: 1.1.0 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3(supports-color@10.2.2) - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.1(supports-color@10.2.2) - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.15.3 - range-parser: 1.3.0 - router: 2.2.0(supports-color@10.2.2) - send: 1.2.1(supports-color@10.2.2) - serve-static: 2.2.1(supports-color@10.2.2) - statuses: 2.0.2 - type-is: 2.1.0 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - fast-copy@4.0.4: {} - - fast-deep-equal@3.1.3: {} - - fast-safe-stringify@2.1.1: {} - - fast-uri@3.1.6: {} - - fdir@6.5.0(picomatch@4.0.7): - optionalDependencies: - picomatch: 4.0.7 - - finalhandler@2.1.1(supports-color@10.2.2): - dependencies: - debug: 4.4.3(supports-color@10.2.2) - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - forwarded@0.2.0: {} - - fresh@2.0.0: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - get-caller-file@2.0.5: {} - - get-east-asian-width@1.6.0: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.4 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.2 - - gopd@1.2.0: {} - - has-symbols@1.1.0: {} - - hasown@2.0.4: - dependencies: - function-bind: 1.1.2 - - help-me@5.0.0: {} - - hono@4.13.5: {} - - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - - iconv-lite@0.7.3: - dependencies: - safer-buffer: 2.1.2 - - inherits@2.0.4: {} - - ioredis@6.0.0(supports-color@10.2.2): - dependencies: - '@ioredis/commands': 2.0.0 - cluster-key-slot: 1.1.1 - debug: 4.4.3(supports-color@10.2.2) - denque: 2.1.0 - redis-errors: 1.2.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - - ip-address@10.5.0: {} - - ipaddr.js@1.9.1: {} - - is-promise@4.0.0: {} - - isexe@2.0.0: {} - - jose@6.2.10: {} - - joycon@3.1.1: {} - - js-yaml@5.4.1: - dependencies: - argparse: 2.0.1 - - json-schema-traverse@1.0.0: {} - - json-schema-typed@8.0.2: {} - - libphonenumber-js@1.13.11: {} - - lightningcss-android-arm64@1.33.0: - optional: true - - lightningcss-darwin-arm64@1.33.0: - optional: true - - lightningcss-darwin-x64@1.33.0: - optional: true - - lightningcss-freebsd-x64@1.33.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.33.0: - optional: true - - lightningcss-linux-arm64-gnu@1.33.0: - optional: true - - lightningcss-linux-arm64-musl@1.33.0: - optional: true - - lightningcss-linux-x64-gnu@1.33.0: - optional: true - - lightningcss-linux-x64-musl@1.33.0: - optional: true - - lightningcss-win32-arm64-msvc@1.33.0: - optional: true - - lightningcss-win32-x64-msvc@1.33.0: - optional: true - - lightningcss@1.33.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.33.0 - lightningcss-darwin-arm64: 1.33.0 - lightningcss-darwin-x64: 1.33.0 - lightningcss-freebsd-x64: 1.33.0 - lightningcss-linux-arm-gnueabihf: 1.33.0 - lightningcss-linux-arm64-gnu: 1.33.0 - lightningcss-linux-arm64-musl: 1.33.0 - lightningcss-linux-x64-gnu: 1.33.0 - lightningcss-linux-x64-musl: 1.33.0 - lightningcss-win32-arm64-msvc: 1.33.0 - lightningcss-win32-x64-msvc: 1.33.0 - - make-error@1.3.6: {} - - math-intrinsics@1.1.0: {} - - media-typer@1.1.1: {} - - merge-descriptors@2.0.0: {} - - mime-db@1.54.0: {} - - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 - - minimist@1.2.8: {} - - ms@2.1.3: {} - - nanoid@3.3.18: {} - - negotiator@1.1.0: - dependencies: - content-type: 2.1.0 - - nrepl-client@0.3.0: - dependencies: - bencode: 2.0.3 - tree-kill: 1.2.2 - - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - on-exit-leak-free@2.1.2: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - parseurl@1.3.3: {} - - path-key@3.1.1: {} - - path-to-regexp@8.4.2: {} - - picocolors@1.1.1: {} - - picomatch@4.0.7: {} - - pino-abstract-transport@3.0.0: - dependencies: - split2: 4.2.0 - - pino-loki@3.0.0: - dependencies: - pino-abstract-transport: 3.0.0 - pump: 3.0.4 - - pino-pretty@13.1.3: - dependencies: - colorette: 2.0.20 - dateformat: 4.6.3 - fast-copy: 4.0.4 - fast-safe-stringify: 2.1.1 - help-me: 5.0.0 - joycon: 3.1.1 - minimist: 1.2.8 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 3.0.0 - pump: 3.0.4 - secure-json-parse: 4.1.0 - sonic-boom: 4.2.1 - strip-json-comments: 5.0.3 - - pino-std-serializers@7.1.0: {} - - pino@10.3.1: - dependencies: - '@pinojs/redact': 0.4.0 - atomic-sleep: 1.0.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 3.0.0 - pino-std-serializers: 7.1.0 - process-warning: 5.1.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.1 - thread-stream: 4.2.0 - - pkce-challenge@5.0.1: {} - - postcss@8.5.26: - dependencies: - nanoid: 3.3.18 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prettier@3.9.6: {} - - process-warning@5.1.0: {} - - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - - pump@3.0.4: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - - qs@6.15.3: - dependencies: - es-define-property: 1.0.1 - side-channel: 1.1.1 - - quick-format-unescaped@4.0.4: {} - - range-parser@1.3.0: {} - - raw-body@3.0.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.3 - unpipe: 1.0.0 - - real-require@0.2.0: {} - - real-require@1.0.0: {} - - redis-errors@1.2.0: {} - - reflect-metadata@0.2.2: {} - - require-from-string@2.0.2: {} - - rolldown@1.2.6: - dependencies: - '@oxc-project/types': 0.147.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.6 - '@rolldown/binding-android-arm64': 1.2.6 - '@rolldown/binding-darwin-arm64': 1.2.6 - '@rolldown/binding-darwin-x64': 1.2.6 - '@rolldown/binding-freebsd-x64': 1.2.6 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.6 - '@rolldown/binding-linux-arm64-gnu': 1.2.6 - '@rolldown/binding-linux-arm64-musl': 1.2.6 - '@rolldown/binding-linux-ppc64-gnu': 1.2.6 - '@rolldown/binding-linux-s390x-gnu': 1.2.6 - '@rolldown/binding-linux-x64-gnu': 1.2.6 - '@rolldown/binding-linux-x64-musl': 1.2.6 - '@rolldown/binding-openharmony-arm64': 1.2.6 - '@rolldown/binding-win32-arm64-msvc': 1.2.6 - '@rolldown/binding-win32-x64-msvc': 1.2.6 - - router@2.2.0(supports-color@10.2.2): - dependencies: - debug: 4.4.3(supports-color@10.2.2) - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.4.2 - transitivePeerDependencies: - - supports-color - - rxjs@7.8.2: - dependencies: - tslib: 2.8.1 - - safe-stable-stringify@2.5.0: {} - - safer-buffer@2.1.2: {} - - secure-json-parse@4.1.0: {} - - semver@7.8.5: {} - - send@1.2.1(supports-color@10.2.2): - dependencies: - debug: 4.4.3(supports-color@10.2.2) - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.3.0 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serve-static@2.2.1(supports-color@10.2.2): - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.1(supports-color@10.2.2) - transitivePeerDependencies: - - supports-color - - setprototypeof@1.2.0: {} - - sharp@0.35.4(@types/node@26.4.0): - dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.8.5 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.35.4 - '@img/sharp-darwin-x64': 0.35.4 - '@img/sharp-freebsd-wasm32': 0.35.4 - '@img/sharp-libvips-darwin-arm64': 1.3.3 - '@img/sharp-libvips-darwin-x64': 1.3.3 - '@img/sharp-libvips-linux-arm': 1.3.3 - '@img/sharp-libvips-linux-arm64': 1.3.3 - '@img/sharp-libvips-linux-ppc64': 1.3.3 - '@img/sharp-libvips-linux-riscv64': 1.3.3 - '@img/sharp-libvips-linux-s390x': 1.3.3 - '@img/sharp-libvips-linux-x64': 1.3.3 - '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 - '@img/sharp-libvips-linuxmusl-x64': 1.3.3 - '@img/sharp-linux-arm': 0.35.4 - '@img/sharp-linux-arm64': 0.35.4 - '@img/sharp-linux-ppc64': 0.35.4 - '@img/sharp-linux-riscv64': 0.35.4 - '@img/sharp-linux-s390x': 0.35.4 - '@img/sharp-linux-x64': 0.35.4 - '@img/sharp-linuxmusl-arm64': 0.35.4 - '@img/sharp-linuxmusl-x64': 0.35.4 - '@img/sharp-webcontainers-wasm32': 0.35.4 - '@img/sharp-win32-arm64': 0.35.4 - '@img/sharp-win32-ia32': 0.35.4 - '@img/sharp-win32-x64': 0.35.4 - '@types/node': 26.4.0 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - shell-quote@1.9.0: {} - - side-channel-list@1.0.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - sonic-boom@4.2.1: - dependencies: - atomic-sleep: 1.0.0 - - source-map-js@1.2.1: {} - - split2@4.2.0: {} - - standard-as-callback@2.1.0: {} - - statuses@2.0.2: {} - - string-width@7.2.0: - dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 - - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.2.2 - - strip-json-comments@5.0.3: {} - - supports-color@10.2.2: {} - - thread-stream@4.2.0: - dependencies: - real-require: 1.0.0 - - tinyglobby@0.2.17: - dependencies: - fdir: 6.5.0(picomatch@4.0.7) - picomatch: 4.0.7 - - toidentifier@1.0.1: {} - - tree-kill@1.2.2: {} - - ts-node@10.9.2(@types/node@26.4.0)(typescript@6.0.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.13 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 26.4.0 - acorn: 8.18.0 - acorn-walk: 8.3.5 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.4 - make-error: 1.3.6 - typescript: 6.0.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - - tslib@2.8.1: {} - - tsx@4.23.12: - dependencies: - esbuild: 0.28.2 - optionalDependencies: - fsevents: 2.3.3 - - type-is@2.1.0: - dependencies: - content-type: 2.1.0 - media-typer: 1.1.1 - mime-types: 3.0.2 - - typescript@6.0.3: {} - - undici-types@8.3.0: {} - - unpipe@1.0.0: {} - - v8-compile-cache-lib@3.0.1: {} - - validator@13.15.35: {} - - vary@1.1.2: {} - - vite-live-preview@0.4.0(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(tsx@4.23.12)): - dependencies: - '@seahax/deep-copy': 0.1.0 - '@seahax/semaphore': 0.5.1 - '@types/ws': 8.18.1 - escape-goat: 4.0.0 - strip-ansi: 7.2.0 - vite: 8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(tsx@4.23.12) - ws: 8.21.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(tsx@4.23.12): - dependencies: - lightningcss: 1.33.0 - picomatch: 4.0.7 - postcss: 8.5.26 - rolldown: 1.2.6 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 26.4.0 - esbuild: 0.28.2 - fsevents: 2.3.3 - tsx: 4.23.12 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wrap-ansi@9.0.2: - dependencies: - ansi-styles: 6.2.3 - string-width: 7.2.0 - strip-ansi: 7.2.0 - - wrappy@1.0.2: {} - - ws@8.21.3: {} - - y18n@5.0.8: {} - - yargs-parser@22.0.0: {} - - yargs@18.0.0: - dependencies: - cliui: 9.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - string-width: 7.2.0 - y18n: 5.0.8 - yargs-parser: 22.0.0 - - yn@3.1.1: {} - - zod-to-json-schema@3.25.2(zod@4.4.3): - dependencies: - zod: 4.4.3 - - zod@4.4.3: {} diff --git a/mcp/pnpm-workspace.yaml b/mcp/pnpm-workspace.yaml deleted file mode 100644 index 13e111b..0000000 --- a/mcp/pnpm-workspace.yaml +++ /dev/null @@ -1,24 +0,0 @@ -storeDir: ../.pnpm-store - -allowBuilds: - esbuild: true - sharp: false - -linkWorkspacePackages: true - -minimumReleaseAgeExclude: - - qs@6.14.2 || 6.15.2 - - path-to-regexp@8.4.0 - - body-parser@2.3.0 - - '@hono/node-server@2.0.5' - - fast-uri@3.1.3 || 3.1.4 || 3.1.5 - - ip-address@10.2.1 || 10.2.2 || 10.3.1 - - hono@4.12.34 - -packages: - - "packages/common" - - "packages/server" - - "packages/plugin" - -overrides: - '@hono/node-server@<2.0.5': ^2.0.5 diff --git a/mcp/resources/architecture.png b/mcp/resources/architecture.png deleted file mode 100644 index f8305fd..0000000 Binary files a/mcp/resources/architecture.png and /dev/null differ diff --git a/mcp/scripts/build b/mcp/scripts/build deleted file mode 100755 index c7e2b1f..0000000 --- a/mcp/scripts/build +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -# NOTE: this script should be called from the parent directory to -# properly work - -SCRIPT_DIR=$(dirname $0); -URL=${1:-http://localhost:9090} - -echo "Preparing bundle for types from $URL" - -set -ex - -# Ensure clean working directory -rm -rf dist; -rm -rf node_modules; -rm -rf packages/server/dist; -rm -rf packages/server/node_modules; - -pushd $SCRIPT_DIR; -set +e -./build-types $URL; -set -e -popd - -pnpm -r --filter "!mcp-plugin" install; -pnpm -r --filter "mcp-server" run build; - -rsync -avr packages/server/dist/ ./dist/; - -cp packages/server/package.json ./dist/; -cp pnpm-lock.yaml ./dist/; - -touch ./dist/pnpm-workspace.yaml; - -cat </dev/null || { echo "error: pnpm not found in PATH" >&2; exit 1; }; -pnpm install -P -EOF - -chmod +x ./dist/setup; diff --git a/mcp/scripts/build-types b/mcp/scripts/build-types deleted file mode 100755 index f1c0130..0000000 --- a/mcp/scripts/build-types +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -SCRIPT_DIR=$(dirname $0); -URL=${PENPOT_PLUGINS_API_DOC_URL:-http://localhost:9090} - -pushd $SCRIPT_DIR; - -if [[ "$URL" = "http://localhost:9090" ]]; then - pushd ../../plugins - rm -rf node_modules; - pnpm install - pnpm run build:doc - popd -fi - -if [[ "$URL" = "http://localhost:9090" ]]; then - pnpx concurrently --kill-others-on-fail -s last -k \ - "caddy file-server --root ../../plugins/dist/doc/ --listen :9090" \ - "bash ../types-generator/build $URL"; -else - bash ../types-generator/build $URL; -fi - -popd diff --git a/mcp/scripts/check b/mcp/scripts/check deleted file mode 100755 index 391ebd2..0000000 --- a/mcp/scripts/check +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -ex; - -pnpm run fmt:check; -pnpm -r run build; -pnpm -r run types:check; diff --git a/mcp/scripts/fmt b/mcp/scripts/fmt deleted file mode 100755 index 7ad3f24..0000000 --- a/mcp/scripts/fmt +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -set -ex; -pnpx prettier --write packages/ diff --git a/mcp/scripts/pack b/mcp/scripts/pack deleted file mode 100644 index 16a869c..0000000 --- a/mcp/scripts/pack +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -cd "$(dirname "$0")/.." - -# pnpm-lock.yaml is hard-excluded by npm, but we need it; ship it under a neutral name -cp pnpm-lock.yaml pnpm-lock.dist.yaml -trap 'rm -f pnpm-lock.dist.yaml' EXIT - -npm pack diff --git a/mcp/scripts/set-version b/mcp/scripts/set-version deleted file mode 100755 index 170aa62..0000000 --- a/mcp/scripts/set-version +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -# -# Derives a valid npm semver version from the Git tag produced by -# `git describe` and writes it into the root package.json. -# -# Examples of the conversion: -# 2.14.0 -> 2.14.0 -# 2.14.0-RC1 -> 2.14.0-rc.1 -# 2.14.0-RC1-140-g9f2ca9965 -> 2.14.0-rc.1.140 -# 2.14.0-140-g9f2ca9965 -> 2.14.1-dev.140 -# -# The last case (commits after a release tag) bumps the patch level so -# the resulting semver sorts higher than the release. - -set -euo pipefail - -raw=$(git describe --tags --match "*.*.*") - -# Parse: ..[-