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

This commit is contained in:
Andrey Antukh 2026-10-17 14:44:44 +02:00
commit 8c5113b815
83 changed files with 1877 additions and 937 deletions

View File

@ -0,0 +1,38 @@
---
name: rewrite-commit-message
description: Rewrite HEAD's commit message (typically a squash that concatenated old messages) into one proper title plus description following Penpot commit conventions, preserving every distinct AI-assisted-by attribution. Amends in place, never pushes.
slash: true
---
# Skill: rewrite-commit-message
HEAD's message is a concatenation of older messages (typical after a
squash): several subjects, bodies and repeated trailers stacked in a
single message. Replace it with one proper message and amend HEAD.
## When to Use
- The user asks to rewrite, retitle or fix the last commit message
- After a squash left HEAD with stacked messages and trailer repeats
## Steps
1. Read the full current message (`git log -1 --format=%B HEAD`) and
the branch diff (`git diff develop..HEAD --stat`, plus the code
when the summary needs grounding).
2. Collect every DISTINCT `AI-assisted-by:` value in the current
message. All of them survive into the new message, one line each:
attributions are never dropped in a rewrite, even when most of
the old text is.
3. Draft one message per `mem:workflow/creating-commits` (read it
end-to-end first): single subject (imperative, capitalized, no
trailing period, ≤80 chars), blank line, body wrapped at ≤80
chars covering the whole diff (not the old per-commit slices),
then the preserved trailers.
4. Write it to a file (a heredoc keeps real newlines; inline `-m`
with `\n` does not) and `git commit --amend -F <file>`.
5. Run `./scripts/check-commit` and require exit code 0. A non-zero
exit is a hard blocker: fix the message, amend again.
6. Report the new hash. Never push: the user pushes from their own
shell. Warn when local history was rewritten over pushed commits
(the push will need force — their call, not yours).

View File

@ -51,7 +51,7 @@ jobs:
uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0
with:
working-directory: plugins
runtime: node@24.21.0
runtime: node@24.22.0
install: false
- name: Install deps

View File

@ -49,7 +49,7 @@ jobs:
uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0
with:
working-directory: plugins
runtime: node@24.21.0
runtime: node@24.22.0
install: false
- name: Install deps

View File

@ -16,6 +16,7 @@ You are working on the GitHub project `penpot/penpot`, a monorepo.
- Before `gh issue create` → `mem:workflow/creating-issues` (title derivation, body template, labels, Issue Type)
- Before `gh pr create` / `gh pr edit` → `mem:workflow/creating-prs` (title format, body structure, "Note:" line)
- Before a repo-wide pnpm version update → `mem:workflow/updating-pnpm` (workspace layout, `scripts/sync-pnpm-version` flow, the stamp-missing-field and ignored-builds gotchas, verification steps)
- Before a Node version update → `mem:workflow/updating-node` (the six pin sites in `.nvmrc`, the devenv and image Dockerfiles and the plugins-deploy workflows, the DHI tag and digest rules, what to verify)
- **Never `git push`, force-push, or modify `git origin`** (or any other remote). The user pushes from their own shell; if a push is required, say so and wait. Never amend a commit that the user has already pushed unless explicitly asked.
- **Never edit `CHANGES.md` by hand.** The changelog is generated from GitHub milestones during the release process; update it only via the `update-changelog` skill flow or on explicit user request.
- You have access to the GitHub CLI `gh` or corresponding MCP tools.
@ -59,6 +60,7 @@ The memory is structured in a way that you can get the critical information abou
- `scripts/changelog.py` — Changelog checks for the `update-changelog` skill: `check-merged` (PR merge status), `cross-ref` (milestone PRs vs changelog section), `report` (anomaly report to CHANGES-ISSUES.md). Calls `scripts/gh.py` via subprocess. See `mem:scripts/changelog`.
- `scripts/error-reports.mjs` — Query error reports via RPC API with token authentication. Supports list/get operations with filtering and pagination. See `mem:scripts/error-reports`.
- `scripts/clean-node-modules` — Remove stale `node_modules` from all pnpm workspaces (root, modules, member packages). Keeps the shared pnpm store at `<repo>/.pnpm-store` unless `--store`; ignores `external/` and `.opencode/`. Usage and reinstall steps: `mem:workflow/updating-pnpm`.
- `scripts/setup-opencode` — Install the opencode CLI in-container as a global pnpm package in `$PNPM_HOME`. Not part of the devenv image; see `mem:devenv/core`.
- `scripts/ci` — CI orchestration script: runs lint, tests, and format checks per module (`frontend backend common render-wasm exporter mcp plugins library`). Logs go to `.ci-logs/`; read the log file on failure. It is the preferred way to run the backend tests, and it exports `PENPOT_FLAGS` with `enable-backend-asserts`, which is what makes `clojure.core/assert` exist in the compiled test code. See `mem:scripts/ci`.
# Dependency graph

View File

@ -24,6 +24,8 @@ Compose-based dev environment under `docker/devenv/`, driven by `manage.sh`. Par
- Network aliases (`- main`, `- redis`) are not declared in main.yml. Compose's auto-service-alias still registers `redis` on the shared network, so DNS for `redis` is non-deterministic with multiple instances. Backend uses `PENPOT_REDIS_URI=redis://penpot-devenv-wsN-valkey/0` (container_name) instead.
- No cross-project `depends_on`. `manage.sh ensure-infra-up` uses Compose `--wait`; PostgreSQL, RustFS, and Mailpit expose healthchecks. PostgreSQL checks TCP so its temporary init server cannot report ready.
- `JAVA_OPTS` in `manage.sh` is shadowed inside the container by `_env`. The `-e JAVA_OPTS=...` flag only matters for processes that don't source `_env`.
- opencode is not part of the devenv image. `scripts/setup-opencode` installs the `@opencode/cli` global pnpm package into the penpot user's `$PNPM_HOME` (`/home/penpot/.local/share/pnpm`, inside the user-data volume, so it survives a container re-creation); nothing pins the version in the repo, it tracks the `latest` dist-tag at install time. pnpm 12 puts the global packages in `$PNPM_HOME/global` and the bin dir in `$PNPM_HOME/bin`, and aborts with `ERR_PNPM_GLOBAL_BIN_DIR_NOT_IN_PATH` unless that bin dir is on `PATH`; `files/bashrc` and the script both add it. The script exports `PNPM_HOME` itself because non-interactive shells never read `~/.bashrc`.
- `files/entrypoint.sh` chowns penpot's whole `~/.local` and `~/.cache` on every start, because `EXTERNAL_UID` remaps penpot's UID to the host user and build-time ownership then stops matching. That covers pnpm's global dir, store, `package-manager-store` and metadata cache, plus opencode's own state; without it, global installs and pnpm's `packageManager` auto-download fail on permissions. The dotfiles copied from `/root` (`.bashrc`, `.vimrc`, `.tmux.conf`) are chowned in the same pass. `~/.config` stays out on purpose (the opencode overlay bind-mounts a host directory inside it) and so does `/home/penpot` itself, which holds the repo bind mount.
## Worker policy

View File

@ -0,0 +1,25 @@
# Updating Node Across the Toolchain
No single source of truth: nothing reads `.nvmrc` at build time, and the version is repeated in a different shape per file. Six sites move together, and the image ones carry a digest beside the tag.
## Pin sites
- `.nvmrc` → `v<X.Y.Z>`, with the leading `v`.
- `docker/devenv/Dockerfile`, `setup-node` stage → `ENV NODE_VERSION=v<X.Y.Z>`; downloads `https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-{arm64,x64}.tar.gz`, one tarball per arch, and verifies each against a `NODE_SHA256` right below its `BINARY_URL`. Both hashes come from `SHASUMS256.txt` in that same dist directory, so a bump must recompute the two.
- `docker/images/Dockerfile.mcp` → two stages: `FROM dhi.io/node:<X.Y.Z>-debian13-dev AS build` and `FROM dhi.io/node:<X.Y.Z>-debian13 AS image`. The release stage carries no `-dev`.
- `docker/images/Dockerfile.media-processor` and `docker/images/Dockerfile.exporter` → single `FROM dhi.io/node:<X.Y.Z>-debian13-dev`.
- `.github/workflows/plugins-deploy-api-doc.yml` and `.github/workflows/plugins-deploy-styles-doc.yml` → `runtime: node@<X.Y.Z>` in the `pnpm/setup` step. Its `working-directory: plugins` makes pnpm's own version come from the manifest, so this pin is the Node one; keep it equal to `.nvmrc` minus the `v`.
- DHI tags take the bare version; only the devenv `NODE_VERSION` carries the `v`.
- Every `dhi.io` base image in the repo (the node ones above, plus `debian-base` and `nginx`) is pinned by digest: `FROM dhi.io/<image>:<tag>@sha256:<digest>`. Tag and digest are a pair; Renovate's `Update DHI base images` commits rewrite both in one go, so a hand-made tag bump that leaves the digest behind keeps the old image.
## Gotchas
- DHI tags are exact-version and lag the upstream release, and dhi.io answers 401 to anonymous requests, so a tag that does not exist yet can only be caught by a real build. `build-docker.yml` and `build-docker-devenv.yml` log into dhi.io, so the failure surfaces in CI, not locally.
- Node 24.x is the Krypton LTS line: take the newest patch of that major unless a major bump is the point.
- Renovate also opens these bumps, on `renovate-*` branches and with its config living outside this repo; a manual bump and Renovate's must not diverge on any of the six sites.
- Both workflows must stay valid YAML. The `python3` in the devenv has no `yaml` module, so validate with `uv run --with pyyaml python3 -c ...` instead of a bare import.
## Verification
- `rg '<old version>'` finds nothing outside `CHANGES.md` and untracked scratch files (a repo-wide grep without path filters hits multi-megabyte ClojureScript analyzer caches: always scope it). Grep `dhi\.io/node` as well: a line whose tag moved but whose digest did not still pulls the old image.
- After a devenv rebuild, `node --version` matches `.nvmrc` and `pnpm --version` matches `PNPM_VERSION` in `docker/devenv/Dockerfile`.

View File

@ -33,7 +33,9 @@ file (never pipe tool output through filters).
images). Nothing may call corepack: it is gone from Node 25+. pnpm
auto-downloads the `packageManager` version on mismatch
(`pmOnFail: download`, the default), so drift self-heals; aligned pins
just skip the download.
just skip the download. Inside the devenv that download lands in the
penpot user's `package-manager-store`, which `files/entrypoint.sh` chowns
to penpot on every start (see `mem:devenv/core`).
- The pnpm version pin lives in three places that move together:
`PNPM_VERSION` (+ arch SHAs) in `docker/devenv/Dockerfile` and in
`docker/images/Dockerfile.{media-processor,exporter,mcp}`, and the
@ -49,14 +51,23 @@ file (never pipe tool output through filters).
`pnpm view pnpm dist-tags --json` for `latest-12` (latest 12.x). The
tag moves over time; always re-check.
2. Bump `PNPM_VERSION` (+ arch SHAs) in `docker/devenv/Dockerfile` and in
`docker/images/Dockerfile.{media-processor,exporter,mcp}`.
`docker/images/Dockerfile.{media-processor,exporter,mcp}`. Those SHAs are
of the **GitHub release tarballs**, one per arch: download
`pnpm-linux-{arm64,x64}.tar.gz` from the `v<version>` release and
`sha256sum` each. The npm `dist.integrity` that the script stamps in step
3 is a different hash of a different artifact — never reuse it for the
Dockerfiles.
3. From the repo root, on a host whose system pnpm is the target version
(rebuilt devenv), run `scripts/sync-pnpm-version`. It resolves the
integrity hash via `pnpm view` + node (no npm, no corepack, no
python3) and stamps the identical field into all 35 files, replacing
only the value line (each file keeps its own indent) and inserting
the key after `"name"`/`"version"` where missing. Explicit version
instead: `scripts/sync-pnpm-version <version>`.
instead: `scripts/sync-pnpm-version <version>`. The explicit form works
from a host whose system pnpm is older than the target (the registry
lookup resolves the hash, and the first later `pnpm` call auto-downloads
the target into `package-manager-store`), so a rebuild is only needed for
the in-container `pnpm --version` to match.
4. Run `scripts/sync-pnpm-version --install` (plain `pnpm install` in
each of the 11 workspace roots) to refresh lockfile metadata, then
fix any failing workspace (see gotchas) and re-run that directory.
@ -76,6 +87,10 @@ file (never pipe tool output through filters).
gone on purpose: pnpm picks the nearest `pnpm-workspace.yaml` walking up,
so a nested one silently forks install and lockfile behavior. Do not
reintroduce it.
- `media-processor/target/package.json` is a generated, gitignored copy that
the script also stamps, so stamping and `--check` report 36 files while
only 35 are tracked. A stale copy there makes `--check` fail with nothing
wrong in the repo: delete `media-processor/target` and re-run.
- Expect metadata-only lockfile diffs when only the pnpm version moves:
the pnpm self-reference entries, plus a new `packageManagerDependencies`
section in lockfiles last written by older pnpm. Large diffs mean

View File

@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC Sucursal en España SL",
"private": true,
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"

122
backend/pnpm-lock.yaml generated
View File

@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.8.1
version: 12.8.1
specifier: 12.14.0
version: 12.14.0
packages:
'@pnpm/exe.android-arm64@12.8.1':
resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==}
'@pnpm/exe.android-arm64@12.14.0':
resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.8.1':
resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==}
'@pnpm/exe.android-x64@12.14.0':
resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.8.1':
resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==}
'@pnpm/exe.darwin-arm64@12.14.0':
resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.8.1':
resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==}
'@pnpm/exe.darwin-x64@12.14.0':
resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.freebsd-x64@12.8.1':
resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==}
'@pnpm/exe.freebsd-x64@12.14.0':
resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.8.1':
resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==}
'@pnpm/exe.linux-arm64-musl@12.14.0':
resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.8.1':
resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==}
'@pnpm/exe.linux-arm64@12.14.0':
resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-ppc64@12.8.1':
resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==}
'@pnpm/exe.linux-ppc64@12.14.0':
resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.8.1':
resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==}
'@pnpm/exe.linux-riscv64@12.14.0':
resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.8.1':
resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==}
'@pnpm/exe.linux-s390x@12.14.0':
resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.8.1':
resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==}
'@pnpm/exe.linux-x64-musl@12.14.0':
resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.8.1':
resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==}
'@pnpm/exe.linux-x64@12.14.0':
resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.8.1':
resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==}
'@pnpm/exe.win32-arm64@12.14.0':
resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.8.1':
resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==}
'@pnpm/exe.win32-x64@12.14.0':
resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==}
cpu: [x64]
os: [win32]
pnpm@12.8.1:
resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==}
pnpm@12.14.0:
resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.android-arm64@12.8.1':
'@pnpm/exe.android-arm64@12.14.0':
optional: true
'@pnpm/exe.android-x64@12.8.1':
'@pnpm/exe.android-x64@12.14.0':
optional: true
'@pnpm/exe.darwin-arm64@12.8.1':
'@pnpm/exe.darwin-arm64@12.14.0':
optional: true
'@pnpm/exe.darwin-x64@12.8.1':
'@pnpm/exe.darwin-x64@12.14.0':
optional: true
'@pnpm/exe.freebsd-x64@12.8.1':
'@pnpm/exe.freebsd-x64@12.14.0':
optional: true
'@pnpm/exe.linux-arm64-musl@12.8.1':
'@pnpm/exe.linux-arm64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-arm64@12.8.1':
'@pnpm/exe.linux-arm64@12.14.0':
optional: true
'@pnpm/exe.linux-ppc64@12.8.1':
'@pnpm/exe.linux-ppc64@12.14.0':
optional: true
'@pnpm/exe.linux-riscv64@12.8.1':
'@pnpm/exe.linux-riscv64@12.14.0':
optional: true
'@pnpm/exe.linux-s390x@12.8.1':
'@pnpm/exe.linux-s390x@12.14.0':
optional: true
'@pnpm/exe.linux-x64-musl@12.8.1':
'@pnpm/exe.linux-x64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-x64@12.8.1':
'@pnpm/exe.linux-x64@12.14.0':
optional: true
'@pnpm/exe.win32-arm64@12.8.1':
'@pnpm/exe.win32-arm64@12.14.0':
optional: true
'@pnpm/exe.win32-x64@12.8.1':
'@pnpm/exe.win32-x64@12.14.0':
optional: true
pnpm@12.8.1:
pnpm@12.14.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.8.1
'@pnpm/exe.android-x64': 12.8.1
'@pnpm/exe.darwin-arm64': 12.8.1
'@pnpm/exe.darwin-x64': 12.8.1
'@pnpm/exe.freebsd-x64': 12.8.1
'@pnpm/exe.linux-arm64': 12.8.1
'@pnpm/exe.linux-arm64-musl': 12.8.1
'@pnpm/exe.linux-ppc64': 12.8.1
'@pnpm/exe.linux-riscv64': 12.8.1
'@pnpm/exe.linux-s390x': 12.8.1
'@pnpm/exe.linux-x64': 12.8.1
'@pnpm/exe.linux-x64-musl': 12.8.1
'@pnpm/exe.win32-arm64': 12.8.1
'@pnpm/exe.win32-x64': 12.8.1
'@pnpm/exe.android-arm64': 12.14.0
'@pnpm/exe.android-x64': 12.14.0
'@pnpm/exe.darwin-arm64': 12.14.0
'@pnpm/exe.darwin-x64': 12.14.0
'@pnpm/exe.freebsd-x64': 12.14.0
'@pnpm/exe.linux-arm64': 12.14.0
'@pnpm/exe.linux-arm64-musl': 12.14.0
'@pnpm/exe.linux-ppc64': 12.14.0
'@pnpm/exe.linux-riscv64': 12.14.0
'@pnpm/exe.linux-s390x': 12.14.0
'@pnpm/exe.linux-x64': 12.14.0
'@pnpm/exe.linux-x64-musl': 12.14.0
'@pnpm/exe.win32-arm64': 12.14.0
'@pnpm/exe.win32-x64': 12.14.0
---
lockfileVersion: '9.0'

View File

@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC Sucursal en España SL",
"private": true,
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"type": "module",
"repository": {
"type": "git",

122
common/pnpm-lock.yaml generated
View File

@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.8.1
version: 12.8.1
specifier: 12.14.0
version: 12.14.0
packages:
'@pnpm/exe.android-arm64@12.8.1':
resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==}
'@pnpm/exe.android-arm64@12.14.0':
resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.8.1':
resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==}
'@pnpm/exe.android-x64@12.14.0':
resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.8.1':
resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==}
'@pnpm/exe.darwin-arm64@12.14.0':
resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.8.1':
resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==}
'@pnpm/exe.darwin-x64@12.14.0':
resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.freebsd-x64@12.8.1':
resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==}
'@pnpm/exe.freebsd-x64@12.14.0':
resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.8.1':
resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==}
'@pnpm/exe.linux-arm64-musl@12.14.0':
resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.8.1':
resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==}
'@pnpm/exe.linux-arm64@12.14.0':
resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-ppc64@12.8.1':
resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==}
'@pnpm/exe.linux-ppc64@12.14.0':
resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.8.1':
resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==}
'@pnpm/exe.linux-riscv64@12.14.0':
resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.8.1':
resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==}
'@pnpm/exe.linux-s390x@12.14.0':
resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.8.1':
resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==}
'@pnpm/exe.linux-x64-musl@12.14.0':
resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.8.1':
resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==}
'@pnpm/exe.linux-x64@12.14.0':
resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.8.1':
resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==}
'@pnpm/exe.win32-arm64@12.14.0':
resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.8.1':
resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==}
'@pnpm/exe.win32-x64@12.14.0':
resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==}
cpu: [x64]
os: [win32]
pnpm@12.8.1:
resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==}
pnpm@12.14.0:
resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.android-arm64@12.8.1':
'@pnpm/exe.android-arm64@12.14.0':
optional: true
'@pnpm/exe.android-x64@12.8.1':
'@pnpm/exe.android-x64@12.14.0':
optional: true
'@pnpm/exe.darwin-arm64@12.8.1':
'@pnpm/exe.darwin-arm64@12.14.0':
optional: true
'@pnpm/exe.darwin-x64@12.8.1':
'@pnpm/exe.darwin-x64@12.14.0':
optional: true
'@pnpm/exe.freebsd-x64@12.8.1':
'@pnpm/exe.freebsd-x64@12.14.0':
optional: true
'@pnpm/exe.linux-arm64-musl@12.8.1':
'@pnpm/exe.linux-arm64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-arm64@12.8.1':
'@pnpm/exe.linux-arm64@12.14.0':
optional: true
'@pnpm/exe.linux-ppc64@12.8.1':
'@pnpm/exe.linux-ppc64@12.14.0':
optional: true
'@pnpm/exe.linux-riscv64@12.8.1':
'@pnpm/exe.linux-riscv64@12.14.0':
optional: true
'@pnpm/exe.linux-s390x@12.8.1':
'@pnpm/exe.linux-s390x@12.14.0':
optional: true
'@pnpm/exe.linux-x64-musl@12.8.1':
'@pnpm/exe.linux-x64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-x64@12.8.1':
'@pnpm/exe.linux-x64@12.14.0':
optional: true
'@pnpm/exe.win32-arm64@12.8.1':
'@pnpm/exe.win32-arm64@12.14.0':
optional: true
'@pnpm/exe.win32-x64@12.8.1':
'@pnpm/exe.win32-x64@12.14.0':
optional: true
pnpm@12.8.1:
pnpm@12.14.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.8.1
'@pnpm/exe.android-x64': 12.8.1
'@pnpm/exe.darwin-arm64': 12.8.1
'@pnpm/exe.darwin-x64': 12.8.1
'@pnpm/exe.freebsd-x64': 12.8.1
'@pnpm/exe.linux-arm64': 12.8.1
'@pnpm/exe.linux-arm64-musl': 12.8.1
'@pnpm/exe.linux-ppc64': 12.8.1
'@pnpm/exe.linux-riscv64': 12.8.1
'@pnpm/exe.linux-s390x': 12.8.1
'@pnpm/exe.linux-x64': 12.8.1
'@pnpm/exe.linux-x64-musl': 12.8.1
'@pnpm/exe.win32-arm64': 12.8.1
'@pnpm/exe.win32-x64': 12.8.1
'@pnpm/exe.android-arm64': 12.14.0
'@pnpm/exe.android-x64': 12.14.0
'@pnpm/exe.darwin-arm64': 12.14.0
'@pnpm/exe.darwin-x64': 12.14.0
'@pnpm/exe.freebsd-x64': 12.14.0
'@pnpm/exe.linux-arm64': 12.14.0
'@pnpm/exe.linux-arm64-musl': 12.14.0
'@pnpm/exe.linux-ppc64': 12.14.0
'@pnpm/exe.linux-riscv64': 12.14.0
'@pnpm/exe.linux-s390x': 12.14.0
'@pnpm/exe.linux-x64': 12.14.0
'@pnpm/exe.linux-x64-musl': 12.14.0
'@pnpm/exe.win32-arm64': 12.14.0
'@pnpm/exe.win32-x64': 12.14.0
---
lockfileVersion: '9.0'

View File

@ -523,7 +523,9 @@
(effective-tokens-source? file-data (:id file-data))))
(defn editable-tokens?
"Returns true if the file-data is its own tokens source."
"Returns true if the file-data is its own tokens source.
Call only with the current local file's data. This predicate does not check
which file is open; callers must reject connected libraries first."
[file-data]
(effective-tokens-source? file-data (:id file-data)))
@ -810,4 +812,4 @@
{:active-theme-ids active-theme-ids
:active-set-ids active-set-ids})
{:active-theme-ids #{}
:active-set-ids #{}}))
:active-set-ids #{}}))

View File

@ -15,15 +15,34 @@
;; Tokens source
(defn- own-tokens-status
"The current tokens status of `file-data`, keeping only the themes and sets
that exist in its own tokens lib."
[file-data]
(let [tokens-status (cfo/get-tokens-status file-data)
tokens-lib (cfo/get-tokens-lib file-data)]
(if (and tokens-status tokens-lib)
(cfo/sync-tokens-status-with-lib tokens-status tokens-lib)
(ctos/make-tokens-status))))
(defn generate-set-tokens-source
"Create changes for setting the tokens source of a file to `library`,
copying the library's tokens status. If the library is nil, the tokens source
is removed and the tokens status is cleared."
copying the library's tokens status. If the library is the file itself,
the themes and sets that don't exist in its own tokens lib are deactivated.
If the library is nil, the tokens source is removed and the tokens status
is cleared."
[changes library]
(let [library-id (:id library)
library-tokens-status (if library
(-> library ctf/file-data cfo/get-tokens-status)
(ctos/make-tokens-status))]
file-data (pcb/get-library-data changes)
library-tokens-status (cond
(nil? library)
(ctos/make-tokens-status)
(= library-id (:id file-data))
(own-tokens-status file-data)
:else
(-> library ctf/file-data cfo/get-tokens-status))]
(-> changes
(pcb/set-tokens-source library-id)
(pcb/set-tokens-status library-tokens-status))))

View File

@ -99,26 +99,34 @@
(str/replace filename image-extensions-re "")))
(defn parse-font-weight
"Parses a font variant/subfamily string into a numeric weight.
A nil or non-string variant means the font carries no subfamily
metadata, so it defaults to 400 (regular), the same as a blank string."
[variant]
(cond
(re-seq #"(?i)(?:^|[-_\s])(hairline|thin)(?=(?:[-_\s]|$|italic\b))" variant) 100
(re-seq #"(?i)(?:^|[-_\s])(extra\s*light|ultra\s*light)(?=(?:[-_\s]|$|italic\b))" variant) 200
(re-seq #"(?i)(?:^|[-_\s])(light)(?=(?:[-_\s]|$|italic\b))" variant) 300
(re-seq #"(?i)(?:^|[-_\s])(normal|regular)(?=(?:[-_\s]|$|italic\b))" variant) 400
(re-seq #"(?i)(?:^|[-_\s])(medium)(?=(?:[-_\s]|$|italic\b))" variant) 500
(re-seq #"(?i)(?:^|[-_\s])(semi\s*bold|demi\s*bold)(?=(?:[-_\s]|$|italic\b))" variant) 600
(re-seq #"(?i)(?:^|[-_\s])(extra\s*bold|ultra\s*bold)(?=(?:[-_\s]|$|italic\b))" variant) 800
(re-seq #"(?i)(?:^|[-_\s])(bold)(?=(?:[-_\s]|$|italic\b))" variant) 700
(re-seq #"(?i)(?:^|[-_\s])(extra\s*black|ultra\s*black)(?=(?:[-_\s]|$|italic\b))" variant) 950
(re-seq #"(?i)(?:^|[-_\s])(black|heavy|solid)(?=(?:[-_\s]|$|italic\b))" variant) 900
:else 400))
(let [variant (if (string? variant) variant "")]
(cond
(re-seq #"(?i)(?:^|[-_\s])(hairline|thin)(?=(?:[-_\s]|$|italic\b))" variant) 100
(re-seq #"(?i)(?:^|[-_\s])(extra\s*light|ultra\s*light)(?=(?:[-_\s]|$|italic\b))" variant) 200
(re-seq #"(?i)(?:^|[-_\s])(light)(?=(?:[-_\s]|$|italic\b))" variant) 300
(re-seq #"(?i)(?:^|[-_\s])(normal|regular)(?=(?:[-_\s]|$|italic\b))" variant) 400
(re-seq #"(?i)(?:^|[-_\s])(medium)(?=(?:[-_\s]|$|italic\b))" variant) 500
(re-seq #"(?i)(?:^|[-_\s])(semi\s*bold|demi\s*bold)(?=(?:[-_\s]|$|italic\b))" variant) 600
(re-seq #"(?i)(?:^|[-_\s])(extra\s*bold|ultra\s*bold)(?=(?:[-_\s]|$|italic\b))" variant) 800
(re-seq #"(?i)(?:^|[-_\s])(bold)(?=(?:[-_\s]|$|italic\b))" variant) 700
(re-seq #"(?i)(?:^|[-_\s])(extra\s*black|ultra\s*black)(?=(?:[-_\s]|$|italic\b))" variant) 950
(re-seq #"(?i)(?:^|[-_\s])(black|heavy|solid)(?=(?:[-_\s]|$|italic\b))" variant) 900
:else 400)))
(defn parse-font-style
"Parses a font variant/subfamily string into \"italic\" or \"normal\".
A nil or non-string variant means the font carries no subfamily
metadata, so it defaults to \"normal\"."
[variant]
(if (or (re-seq #"(?i)(?:^|[-_\s])(italic)(?:[-_\s]|$)" variant)
(re-seq #"(?i)italic$" variant))
"italic"
"normal"))
(let [variant (if (string? variant) variant "")]
(if (or (re-seq #"(?i)(?:^|[-_\s])(italic)(?:[-_\s]|$)" variant)
(re-seq #"(?i)italic$" variant))
"italic"
"normal")))
(defn font-weight->name
[weight]

View File

@ -177,7 +177,60 @@
(t/is (= (ctos/get-active-theme-ids file-tokens-status)
(ctos/get-active-theme-ids undo-tokens-status)))
(t/is (= (ctos/get-active-set-ids file-tokens-status)
(ctos/get-active-set-ids undo-tokens-status))))))
(ctos/get-active-set-ids undo-tokens-status)))))
(t/testing "if the library is the same file, themes and sets of the previous source are deactivated"
(let [set-id (uuid/next)
external-set-id (uuid/next)
external-theme-id (uuid/next)
library-id (uuid/next)
file (-> (tht/sample-file-with-tokens
:file-id :file1
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id set-id
:name "one set")))
:status-fn #(ctos/set-tokens-status % #{external-theme-id}
#{set-id external-set-id}))
(assoc-in [:data :tokens-source] library-id))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (ctf/file-data file))
(clt/generate-set-tokens-source file))
redo (thf/apply-changes file changes)
undo (thf/apply-undo-changes redo changes)
file-tokens-status (tht/get-tokens-status file)
redo-tokens-source (tht/get-tokens-source redo)
redo-tokens-status (tht/get-tokens-status redo)
undo-tokens-source (tht/get-tokens-source undo)
undo-tokens-status (tht/get-tokens-status undo)]
;; Redo: only the themes and sets of the file's own lib stay active
(t/is (nil? redo-tokens-source))
(t/is (= #{} (ctos/get-active-theme-ids redo-tokens-status)))
(t/is (= #{set-id} (ctos/get-active-set-ids redo-tokens-status)))
;; Undo: source and status restored to the original state
(t/is (= library-id undo-tokens-source))
(t/is (= (ctos/get-active-theme-ids file-tokens-status)
(ctos/get-active-theme-ids undo-tokens-status)))
(t/is (= (ctos/get-active-set-ids file-tokens-status)
(ctos/get-active-set-ids undo-tokens-status)))))
(t/testing "if the library is the same file and it has no own tokens lib, the tokens status is cleared"
(let [set-id (uuid/next)
file (-> (thf/sample-file :file1)
(update :data dissoc :tokens-lib)
(assoc-in [:data :tokens-source] (uuid/next))
(assoc-in [:data :tokens-status] (ctos/set-tokens-status (ctos/make-tokens-status)
#{} #{set-id})))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (ctf/file-data file))
(clt/generate-set-tokens-source file))
redo (thf/apply-changes file changes)
redo-tokens-status (tht/get-tokens-status redo)]
(t/is (nil? (tht/get-tokens-source redo)))
(t/is (= #{} (ctos/get-active-theme-ids redo-tokens-status)))
(t/is (= #{} (ctos/get-active-set-ids redo-tokens-status))))))
;; Tokens lib

View File

@ -35,7 +35,12 @@
(t/is (= 400 (media/parse-font-weight "Lighthaus")))
(t/is (= 400 (media/parse-font-weight "Blackwood")))
(t/is (= 400 (media/parse-font-weight "Thinker")))
(t/is (= 400 (media/parse-font-weight "Mediaeval")))))
(t/is (= 400 (media/parse-font-weight "Mediaeval"))))
(t/testing "defaults to regular when the variant metadata is missing"
(t/is (= 400 (media/parse-font-weight nil)))
(t/is (= 400 (media/parse-font-weight "")))
(t/is (= 400 (media/parse-font-weight " ")))))
(t/deftest test-parse-font-style
(t/testing "matches italic with proper boundaries"
@ -47,7 +52,12 @@
(t/testing "does not match italic embedded in words"
(t/is (= "normal" (media/parse-font-style "Italica")))
(t/is (= "normal" (media/parse-font-style "Roboto-Regular")))))
(t/is (= "normal" (media/parse-font-style "Roboto-Regular"))))
(t/testing "defaults to normal when the variant metadata is missing"
(t/is (= "normal" (media/parse-font-style nil)))
(t/is (= "normal" (media/parse-font-style "")))
(t/is (= "normal" (media/parse-font-style " ")))))
(t/deftest test-strip-image-extension
(t/testing "removes extension from supported image files"

View File

@ -67,7 +67,7 @@ RUN set -ex; \
FROM base AS setup-node
ENV NODE_VERSION=v24.22.0 \
PNPM_VERSION=12.9.1 \
PNPM_VERSION=12.14.0 \
PATH=/opt/node/bin:$PATH
RUN set -eux; \
@ -78,14 +78,14 @@ RUN set -eux; \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
NODE_SHA256='b7e7991d5407815adfa74a2a9cd4db683552c8ce32ff4a295c22e972849c3f5d'; \
PNPM_ARCH='arm64'; \
PNPM_SHA256='468294b77633d29889c6e4a79cefb1277eba30cd62e7d6a754218cb85a1d730d'; \
PNPM_SHA256='cfc780b9e690549e6633e1045fb1fb6ef84e390711e2099e6ccbae91c32456ea'; \
;; \
amd64|x86_64) \
OPENSSL_ARCH='linux-x86_64'; \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
NODE_SHA256='28ea0a438de9a13bd7891fd758eee6c7819ee8af5442dfd8d45bd65e56d4228c'; \
PNPM_ARCH='x64'; \
PNPM_SHA256='b4f58449ac02d9d24023ac89e17e05e8331f8ddc12ca1f489cf231bc2bb2bce1'; \
PNPM_SHA256='3910c3852136bed5bc1511b9137a45fd4357ed6b3970d9d08893641e9005867e'; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
@ -106,37 +106,6 @@ RUN set -eux; \
find /opt/node/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; ; \
rm -f /tmp/nodejs.tar.gz /tmp/pnpm.tar.gz;
################################################################################
## OPENCODE SETUP
################################################################################
FROM base AS setup-opencode
ENV OPENCODE_VERSION=2.0.29
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-arm64/-/cli-linux-arm64-${OPENCODE_VERSION}.tgz"; \
ESUM='0af39e75dbdfacc20f8f531ef35fb4dcc0b44c08084203518614272f232283ae'; \
;; \
amd64|x86_64) \
BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-${OPENCODE_VERSION}.tgz"; \
ESUM='14b5a6638a1a093ac9d484599c844b10c29b445d04844b682ccc668ce26530a2'; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; exit 1; \
;; \
esac; \
curl -LfsSo /tmp/opencode.tgz "${BINARY_URL}"; \
echo "${ESUM} */tmp/opencode.tgz" | sha256sum -c -; \
mkdir -p /tmp/opencode; \
tar -xzf /tmp/opencode.tgz -C /tmp/opencode --strip-components=2 package/bin; \
chmod +x /tmp/opencode/opencode; \
rm -f /tmp/opencode.tgz;
################################################################################
## CADDYSERVER SETUP
################################################################################
@ -561,7 +530,6 @@ COPY --from=setup-rust /opt/cargo /opt/cargo
COPY --from=setup-rust /opt/rustup /opt/rustup
COPY --from=setup-rust /opt/emsdk /opt/emsdk
COPY --from=setup-caddy /usr/bin/caddy /usr/bin/caddy
COPY --from=setup-opencode /tmp/opencode/opencode /opt/utils/bin/opencode
COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/nginx-mime.types /etc/nginx/mime.types

View File

@ -27,7 +27,7 @@ fi
# pnpm
export PNPM_HOME="/home/penpot/.local/share/pnpm"
case ":$PATH:" in
*":$PNPM_HOME:"*) ;;
*) export PATH="$PNPM_HOME:$PATH" ;;
*":$PNPM_HOME/bin:"*) ;;
*) export PATH="$PNPM_HOME/bin:$PATH" ;;
esac
# pnpm end

View File

@ -10,6 +10,10 @@ cp /root/.bashrc /home/penpot/.bashrc
cp /root/.vimrc /home/penpot/.vimrc
cp /root/.tmux.conf /home/penpot/.tmux.conf
chown -R penpot:penpot /home/penpot/.bashrc
chown -R penpot:penpot /home/penpot/.vimrc
chown -R penpot:penpot /home/penpot/.tmux.conf
# Seed SERENA_HOME with default config on first run
mkdir -p ${SERENA_HOME}
if [ ! -f "${SERENA_HOME}/serena_config.yml" ]; then
@ -21,6 +25,16 @@ chown penpot:users /home/penpot
# we need to be able to install rust-analyzer and possibly other dependencies with rustup
chown -R penpot:penpot /opt/rustup
# pnpm keeps its global dir (bin, global, store, package-manager-store)
# under ~/.local and its metadata cache under ~/.cache; opencode and other
# user-space tools write there too. Hand both trees back to penpot so
# packages, and pnpm itself, can be updated without root. Needed because the
# UID remap above can leave everything the image created owned by the old
# UID. ~/.config is deliberately excluded: the opencode overlay bind-mounts
# a host directory inside it.
mkdir -p /home/penpot/.local /home/penpot/.cache
chown -R penpot:penpot /home/penpot/.local /home/penpot/.cache
rsync -ar --chown=penpot:users /opt/cargo/ /home/penpot/.cargo/
export JAVA_OPTS="-Djava.net.preferIPv4Stack=true"

View File

@ -95,17 +95,17 @@ WORKDIR /opt/penpot/exporter
# pnpm ships as a system binary (same tarball + SHA pin as
# docker/devenv/Dockerfile); the generated ./setup expects it on PATH.
# Corepack is gone from Node 25+, so nothing here may use it.
ARG PNPM_VERSION=12.8.1
ARG PNPM_VERSION=12.14.0
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
PNPM_ARCH='arm64'; \
PNPM_SHA256='75c5e34d3164fe3d3549580b1b1b59c9dd16543e558dec722b686193cdb7dfa1'; \
PNPM_SHA256='cfc780b9e690549e6633e1045fb1fb6ef84e390711e2099e6ccbae91c32456ea'; \
;; \
amd64|x86_64) \
PNPM_ARCH='x64'; \
PNPM_SHA256='db3906881f63e41b655e3b97d473603dc26326c2470d0b1b8083689e1bcbd1e8'; \
PNPM_SHA256='3910c3852136bed5bc1511b9137a45fd4357ed6b3970d9d08893641e9005867e'; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \

View File

@ -11,7 +11,7 @@ WORKDIR /opt/penpot/mcp
# pnpm ships as a system binary (same tarball + SHA pin as
# docker/devenv/Dockerfile); ./setup expects it on PATH.
# Corepack is gone from Node 25+, so nothing here may use it.
ARG PNPM_VERSION=12.8.1
ARG PNPM_VERSION=12.14.0
RUN set -eux; \
apt-get -qq update; \
apt-get -qqy --no-install-recommends install curl ca-certificates gzip; \
@ -20,11 +20,11 @@ RUN set -eux; \
case "${ARCH}" in \
aarch64|arm64) \
PNPM_ARCH='arm64'; \
PNPM_SHA256='75c5e34d3164fe3d3549580b1b1b59c9dd16543e558dec722b686193cdb7dfa1'; \
PNPM_SHA256='cfc780b9e690549e6633e1045fb1fb6ef84e390711e2099e6ccbae91c32456ea'; \
;; \
amd64|x86_64) \
PNPM_ARCH='x64'; \
PNPM_SHA256='db3906881f63e41b655e3b97d473603dc26326c2470d0b1b8083689e1bcbd1e8'; \
PNPM_SHA256='3910c3852136bed5bc1511b9137a45fd4357ed6b3970d9d08893641e9005867e'; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \

View File

@ -58,7 +58,7 @@ WORKDIR /opt/penpot/media-processor
# pnpm ships as a system binary (same tarball + SHA pin as
# docker/devenv/Dockerfile); ./setup expects it on PATH.
# Corepack is gone from Node 25+, so nothing here may use it.
ARG PNPM_VERSION=12.8.1
ARG PNPM_VERSION=12.14.0
RUN set -eux; \
apt-get -qq update; \
apt-get -qqy --no-install-recommends install gzip; \
@ -67,11 +67,11 @@ RUN set -eux; \
case "${ARCH}" in \
aarch64|arm64) \
PNPM_ARCH='arm64'; \
PNPM_SHA256='75c5e34d3164fe3d3549580b1b1b59c9dd16543e558dec722b686193cdb7dfa1'; \
PNPM_SHA256='cfc780b9e690549e6633e1045fb1fb6ef84e390711e2099e6ccbae91c32456ea'; \
;; \
amd64|x86_64) \
PNPM_ARCH='x64'; \
PNPM_SHA256='db3906881f63e41b655e3b97d473603dc26326c2470d0b1b8083689e1bcbd1e8'; \
PNPM_SHA256='3910c3852136bed5bc1511b9137a45fd4357ed6b3970d9d08893641e9005867e'; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \

View File

@ -39,5 +39,5 @@
"markdown-it-anchor": "^10.0.0",
"markdown-it-plantuml": "^1.4.1"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

122
docs/pnpm-lock.yaml generated
View File

@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.8.1
version: 12.8.1
specifier: 12.14.0
version: 12.14.0
packages:
'@pnpm/exe.android-arm64@12.8.1':
resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==}
'@pnpm/exe.android-arm64@12.14.0':
resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.8.1':
resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==}
'@pnpm/exe.android-x64@12.14.0':
resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.8.1':
resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==}
'@pnpm/exe.darwin-arm64@12.14.0':
resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.8.1':
resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==}
'@pnpm/exe.darwin-x64@12.14.0':
resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.freebsd-x64@12.8.1':
resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==}
'@pnpm/exe.freebsd-x64@12.14.0':
resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.8.1':
resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==}
'@pnpm/exe.linux-arm64-musl@12.14.0':
resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.8.1':
resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==}
'@pnpm/exe.linux-arm64@12.14.0':
resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-ppc64@12.8.1':
resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==}
'@pnpm/exe.linux-ppc64@12.14.0':
resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.8.1':
resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==}
'@pnpm/exe.linux-riscv64@12.14.0':
resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.8.1':
resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==}
'@pnpm/exe.linux-s390x@12.14.0':
resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.8.1':
resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==}
'@pnpm/exe.linux-x64-musl@12.14.0':
resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.8.1':
resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==}
'@pnpm/exe.linux-x64@12.14.0':
resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.8.1':
resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==}
'@pnpm/exe.win32-arm64@12.14.0':
resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.8.1':
resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==}
'@pnpm/exe.win32-x64@12.14.0':
resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==}
cpu: [x64]
os: [win32]
pnpm@12.8.1:
resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==}
pnpm@12.14.0:
resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.android-arm64@12.8.1':
'@pnpm/exe.android-arm64@12.14.0':
optional: true
'@pnpm/exe.android-x64@12.8.1':
'@pnpm/exe.android-x64@12.14.0':
optional: true
'@pnpm/exe.darwin-arm64@12.8.1':
'@pnpm/exe.darwin-arm64@12.14.0':
optional: true
'@pnpm/exe.darwin-x64@12.8.1':
'@pnpm/exe.darwin-x64@12.14.0':
optional: true
'@pnpm/exe.freebsd-x64@12.8.1':
'@pnpm/exe.freebsd-x64@12.14.0':
optional: true
'@pnpm/exe.linux-arm64-musl@12.8.1':
'@pnpm/exe.linux-arm64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-arm64@12.8.1':
'@pnpm/exe.linux-arm64@12.14.0':
optional: true
'@pnpm/exe.linux-ppc64@12.8.1':
'@pnpm/exe.linux-ppc64@12.14.0':
optional: true
'@pnpm/exe.linux-riscv64@12.8.1':
'@pnpm/exe.linux-riscv64@12.14.0':
optional: true
'@pnpm/exe.linux-s390x@12.8.1':
'@pnpm/exe.linux-s390x@12.14.0':
optional: true
'@pnpm/exe.linux-x64-musl@12.8.1':
'@pnpm/exe.linux-x64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-x64@12.8.1':
'@pnpm/exe.linux-x64@12.14.0':
optional: true
'@pnpm/exe.win32-arm64@12.8.1':
'@pnpm/exe.win32-arm64@12.14.0':
optional: true
'@pnpm/exe.win32-x64@12.8.1':
'@pnpm/exe.win32-x64@12.14.0':
optional: true
pnpm@12.8.1:
pnpm@12.14.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.8.1
'@pnpm/exe.android-x64': 12.8.1
'@pnpm/exe.darwin-arm64': 12.8.1
'@pnpm/exe.darwin-x64': 12.8.1
'@pnpm/exe.freebsd-x64': 12.8.1
'@pnpm/exe.linux-arm64': 12.8.1
'@pnpm/exe.linux-arm64-musl': 12.8.1
'@pnpm/exe.linux-ppc64': 12.8.1
'@pnpm/exe.linux-riscv64': 12.8.1
'@pnpm/exe.linux-s390x': 12.8.1
'@pnpm/exe.linux-x64': 12.8.1
'@pnpm/exe.linux-x64-musl': 12.8.1
'@pnpm/exe.win32-arm64': 12.8.1
'@pnpm/exe.win32-x64': 12.8.1
'@pnpm/exe.android-arm64': 12.14.0
'@pnpm/exe.android-x64': 12.14.0
'@pnpm/exe.darwin-arm64': 12.14.0
'@pnpm/exe.darwin-x64': 12.14.0
'@pnpm/exe.freebsd-x64': 12.14.0
'@pnpm/exe.linux-arm64': 12.14.0
'@pnpm/exe.linux-arm64-musl': 12.14.0
'@pnpm/exe.linux-ppc64': 12.14.0
'@pnpm/exe.linux-riscv64': 12.14.0
'@pnpm/exe.linux-s390x': 12.14.0
'@pnpm/exe.linux-x64': 12.14.0
'@pnpm/exe.linux-x64-musl': 12.14.0
'@pnpm/exe.win32-arm64': 12.14.0
'@pnpm/exe.win32-x64': 12.14.0
---
lockfileVersion: '9.0'

View File

@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC Sucursal en España SL",
"private": true,
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"

122
exporter/pnpm-lock.yaml generated
View File

@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.8.1
version: 12.8.1
specifier: 12.14.0
version: 12.14.0
packages:
'@pnpm/exe.android-arm64@12.8.1':
resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==}
'@pnpm/exe.android-arm64@12.14.0':
resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.8.1':
resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==}
'@pnpm/exe.android-x64@12.14.0':
resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.8.1':
resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==}
'@pnpm/exe.darwin-arm64@12.14.0':
resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.8.1':
resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==}
'@pnpm/exe.darwin-x64@12.14.0':
resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.freebsd-x64@12.8.1':
resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==}
'@pnpm/exe.freebsd-x64@12.14.0':
resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.8.1':
resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==}
'@pnpm/exe.linux-arm64-musl@12.14.0':
resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.8.1':
resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==}
'@pnpm/exe.linux-arm64@12.14.0':
resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-ppc64@12.8.1':
resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==}
'@pnpm/exe.linux-ppc64@12.14.0':
resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.8.1':
resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==}
'@pnpm/exe.linux-riscv64@12.14.0':
resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.8.1':
resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==}
'@pnpm/exe.linux-s390x@12.14.0':
resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.8.1':
resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==}
'@pnpm/exe.linux-x64-musl@12.14.0':
resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.8.1':
resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==}
'@pnpm/exe.linux-x64@12.14.0':
resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.8.1':
resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==}
'@pnpm/exe.win32-arm64@12.14.0':
resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.8.1':
resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==}
'@pnpm/exe.win32-x64@12.14.0':
resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==}
cpu: [x64]
os: [win32]
pnpm@12.8.1:
resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==}
pnpm@12.14.0:
resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.android-arm64@12.8.1':
'@pnpm/exe.android-arm64@12.14.0':
optional: true
'@pnpm/exe.android-x64@12.8.1':
'@pnpm/exe.android-x64@12.14.0':
optional: true
'@pnpm/exe.darwin-arm64@12.8.1':
'@pnpm/exe.darwin-arm64@12.14.0':
optional: true
'@pnpm/exe.darwin-x64@12.8.1':
'@pnpm/exe.darwin-x64@12.14.0':
optional: true
'@pnpm/exe.freebsd-x64@12.8.1':
'@pnpm/exe.freebsd-x64@12.14.0':
optional: true
'@pnpm/exe.linux-arm64-musl@12.8.1':
'@pnpm/exe.linux-arm64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-arm64@12.8.1':
'@pnpm/exe.linux-arm64@12.14.0':
optional: true
'@pnpm/exe.linux-ppc64@12.8.1':
'@pnpm/exe.linux-ppc64@12.14.0':
optional: true
'@pnpm/exe.linux-riscv64@12.8.1':
'@pnpm/exe.linux-riscv64@12.14.0':
optional: true
'@pnpm/exe.linux-s390x@12.8.1':
'@pnpm/exe.linux-s390x@12.14.0':
optional: true
'@pnpm/exe.linux-x64-musl@12.8.1':
'@pnpm/exe.linux-x64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-x64@12.8.1':
'@pnpm/exe.linux-x64@12.14.0':
optional: true
'@pnpm/exe.win32-arm64@12.8.1':
'@pnpm/exe.win32-arm64@12.14.0':
optional: true
'@pnpm/exe.win32-x64@12.8.1':
'@pnpm/exe.win32-x64@12.14.0':
optional: true
pnpm@12.8.1:
pnpm@12.14.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.8.1
'@pnpm/exe.android-x64': 12.8.1
'@pnpm/exe.darwin-arm64': 12.8.1
'@pnpm/exe.darwin-x64': 12.8.1
'@pnpm/exe.freebsd-x64': 12.8.1
'@pnpm/exe.linux-arm64': 12.8.1
'@pnpm/exe.linux-arm64-musl': 12.8.1
'@pnpm/exe.linux-ppc64': 12.8.1
'@pnpm/exe.linux-riscv64': 12.8.1
'@pnpm/exe.linux-s390x': 12.8.1
'@pnpm/exe.linux-x64': 12.8.1
'@pnpm/exe.linux-x64-musl': 12.8.1
'@pnpm/exe.win32-arm64': 12.8.1
'@pnpm/exe.win32-x64': 12.8.1
'@pnpm/exe.android-arm64': 12.14.0
'@pnpm/exe.android-x64': 12.14.0
'@pnpm/exe.darwin-arm64': 12.14.0
'@pnpm/exe.darwin-x64': 12.14.0
'@pnpm/exe.freebsd-x64': 12.14.0
'@pnpm/exe.linux-arm64': 12.14.0
'@pnpm/exe.linux-arm64-musl': 12.14.0
'@pnpm/exe.linux-ppc64': 12.14.0
'@pnpm/exe.linux-riscv64': 12.14.0
'@pnpm/exe.linux-s390x': 12.14.0
'@pnpm/exe.linux-x64': 12.14.0
'@pnpm/exe.linux-x64-musl': 12.14.0
'@pnpm/exe.win32-arm64': 12.14.0
'@pnpm/exe.win32-x64': 12.14.0
---
lockfileVersion: '9.0'

View File

@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC Sucursal en España SL",
"private": true,
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"browserslist": [
"defaults"
],

View File

@ -4,7 +4,7 @@
"description": "Penpot Draft-JS Wrapper",
"main": "index.js",
"type": "module",
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"author": "Andrey Antukh",
"license": "MPL-2.0",
"dependencies": {

View File

@ -4,7 +4,7 @@
"description": "Simple library for handling keyboard shortcuts",
"main": "index.js",
"type": "module",
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"author": "Craig Campbell",
"license": "Apache-2.0 WITH LLVM-exception"
}

View File

@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"type": "module",
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"author": "Andrey Antukh",
"license": "MPL-2.0",
"dependencies": {

View File

@ -3,7 +3,7 @@
"version": "0.0.1",
"types": "./dist/index.d.ts",
"type": "module",
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"exports": {
".": {
"import": "./dist/index.js"

122
frontend/pnpm-lock.yaml generated
View File

@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.8.1
version: 12.8.1
specifier: 12.14.0
version: 12.14.0
packages:
'@pnpm/exe.android-arm64@12.8.1':
resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==}
'@pnpm/exe.android-arm64@12.14.0':
resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.8.1':
resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==}
'@pnpm/exe.android-x64@12.14.0':
resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.8.1':
resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==}
'@pnpm/exe.darwin-arm64@12.14.0':
resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.8.1':
resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==}
'@pnpm/exe.darwin-x64@12.14.0':
resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.freebsd-x64@12.8.1':
resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==}
'@pnpm/exe.freebsd-x64@12.14.0':
resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.8.1':
resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==}
'@pnpm/exe.linux-arm64-musl@12.14.0':
resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.8.1':
resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==}
'@pnpm/exe.linux-arm64@12.14.0':
resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-ppc64@12.8.1':
resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==}
'@pnpm/exe.linux-ppc64@12.14.0':
resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.8.1':
resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==}
'@pnpm/exe.linux-riscv64@12.14.0':
resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.8.1':
resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==}
'@pnpm/exe.linux-s390x@12.14.0':
resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.8.1':
resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==}
'@pnpm/exe.linux-x64-musl@12.14.0':
resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.8.1':
resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==}
'@pnpm/exe.linux-x64@12.14.0':
resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.8.1':
resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==}
'@pnpm/exe.win32-arm64@12.14.0':
resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.8.1':
resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==}
'@pnpm/exe.win32-x64@12.14.0':
resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==}
cpu: [x64]
os: [win32]
pnpm@12.8.1:
resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==}
pnpm@12.14.0:
resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.android-arm64@12.8.1':
'@pnpm/exe.android-arm64@12.14.0':
optional: true
'@pnpm/exe.android-x64@12.8.1':
'@pnpm/exe.android-x64@12.14.0':
optional: true
'@pnpm/exe.darwin-arm64@12.8.1':
'@pnpm/exe.darwin-arm64@12.14.0':
optional: true
'@pnpm/exe.darwin-x64@12.8.1':
'@pnpm/exe.darwin-x64@12.14.0':
optional: true
'@pnpm/exe.freebsd-x64@12.8.1':
'@pnpm/exe.freebsd-x64@12.14.0':
optional: true
'@pnpm/exe.linux-arm64-musl@12.8.1':
'@pnpm/exe.linux-arm64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-arm64@12.8.1':
'@pnpm/exe.linux-arm64@12.14.0':
optional: true
'@pnpm/exe.linux-ppc64@12.8.1':
'@pnpm/exe.linux-ppc64@12.14.0':
optional: true
'@pnpm/exe.linux-riscv64@12.8.1':
'@pnpm/exe.linux-riscv64@12.14.0':
optional: true
'@pnpm/exe.linux-s390x@12.8.1':
'@pnpm/exe.linux-s390x@12.14.0':
optional: true
'@pnpm/exe.linux-x64-musl@12.8.1':
'@pnpm/exe.linux-x64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-x64@12.8.1':
'@pnpm/exe.linux-x64@12.14.0':
optional: true
'@pnpm/exe.win32-arm64@12.8.1':
'@pnpm/exe.win32-arm64@12.14.0':
optional: true
'@pnpm/exe.win32-x64@12.8.1':
'@pnpm/exe.win32-x64@12.14.0':
optional: true
pnpm@12.8.1:
pnpm@12.14.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.8.1
'@pnpm/exe.android-x64': 12.8.1
'@pnpm/exe.darwin-arm64': 12.8.1
'@pnpm/exe.darwin-x64': 12.8.1
'@pnpm/exe.freebsd-x64': 12.8.1
'@pnpm/exe.linux-arm64': 12.8.1
'@pnpm/exe.linux-arm64-musl': 12.8.1
'@pnpm/exe.linux-ppc64': 12.8.1
'@pnpm/exe.linux-riscv64': 12.8.1
'@pnpm/exe.linux-s390x': 12.8.1
'@pnpm/exe.linux-x64': 12.8.1
'@pnpm/exe.linux-x64-musl': 12.8.1
'@pnpm/exe.win32-arm64': 12.8.1
'@pnpm/exe.win32-x64': 12.8.1
'@pnpm/exe.android-arm64': 12.14.0
'@pnpm/exe.android-x64': 12.14.0
'@pnpm/exe.darwin-arm64': 12.14.0
'@pnpm/exe.darwin-x64': 12.14.0
'@pnpm/exe.freebsd-x64': 12.14.0
'@pnpm/exe.linux-arm64': 12.14.0
'@pnpm/exe.linux-arm64-musl': 12.14.0
'@pnpm/exe.linux-ppc64': 12.14.0
'@pnpm/exe.linux-riscv64': 12.14.0
'@pnpm/exe.linux-s390x': 12.14.0
'@pnpm/exe.linux-x64': 12.14.0
'@pnpm/exe.linux-x64-musl': 12.14.0
'@pnpm/exe.win32-arm64': 12.14.0
'@pnpm/exe.win32-x64': 12.14.0
---
lockfileVersion: '9.0'

View File

@ -115,6 +115,128 @@
:font-style font-style
:uploads uploads})))))
(defn- parse-font-mtype
"Detects a font mime-type from the first four bytes of an ArrayBuffer.
Returns the `font/*` mime-type string, or nil when the buffer is
missing, too short, or carries an unknown signature. Unknown
signatures are treated as errors by `read-blob`, never silently
dropped."
[ba]
(when (and (some? ba)
(>= (.-byteLength ^js ba) 4))
(let [u8 (js/Uint8Array. ba 0 4)
sg (areduce u8 i ret "" (str ret (if (zero? i) "" " ") (.toString (aget u8 i) 8)))]
(case sg
"117 124 124 117" "font/otf"
"0 1 0 0" "font/ttf"
"167 117 106 106" "font/woff"
"167 117 106 62" "font/woff2"
nil))))
(defn- parse-font
"Associates the opentype.js font object for TTF/OTF/WOFF blobs.
Takes a `read-blob` result map. Passes `{:error ...}` maps through
untouched. WOFF2 blobs cannot be parsed by opentype.js, so they keep
`:font nil` and metadata is derived from the filename later. Any other
blob is parsed with opentype.js; when parsing fails an
`{:error filename}` map is returned so the caller can notify the user
instead of silently dropping the file."
[{:keys [error data type name] :as params}]
(cond
(some? error)
params
(= type "font/woff2")
;; woff2 cannot be parsed by opentype.js, extract metadata from filename
(assoc params :font nil)
:else
(try
(assoc params :font (ot/parse data))
(catch :default _e
(log/warn :msg (str/fmt "skipping file %s, unsupported format" name))
{:error name}))))
(defn- prepare-font
"Builds a ready-to-upload font entry from a parsed blob.
Never throws for missing font metadata: a nil subfamily (opentype.js
returns nil when the font carries no English preferred/subfamily
names, as seen with some Fontshare families) defaults to regular
weight/style, and missing vertical-metric tables default to no
height warning."
[{:keys [font type name data] :as _params}]
(let [name (or name "")]
(if (some? font)
;; Font was parsed with opentype.js (ttf, otf, woff)
(let [family (or (.getEnglishName ^js font "preferredFamily")
(.getEnglishName ^js font "fontFamily")
"")
variant (or (.getEnglishName ^js font "preferredSubfamily")
(.getEnglishName ^js font "fontSubfamily")
"")
;; Vertical metrics determine the baseline in a text and the space between lines of
;; text. For historical reasons, there are three pairs of ascender/descender
;; values, known as hhea, OS/2 and uSWin metrics. Depending on the font, operating
;; system and application a different set will be used to render text on the
;; screen. On Mac, Safari and Chrome use the hhea values to render text. Firefox
;; will respect the useTypoMetrics setting and will use the OS/2 if it is set. If
;; the useTypoMetrics is not set, Firefox will also use metrics from the hhea
;; table. On Windows, all browsers use the usWin metrics, but respect the
;; useTypoMetrics setting and if set will use the OS/2 values.
hhea (some-> ^js font .-tables .-hhea)
os2 (some-> ^js font .-tables .-os2)
hhea-ascender (some-> ^js hhea .-ascender abs)
hhea-descender (some-> ^js hhea .-descender abs)
win-ascent (some-> ^js os2 .-usWinAscent abs)
win-descent (some-> ^js os2 .-usWinDescent abs)
os2-ascent (some-> ^js os2 .-sTypoAscender abs)
os2-descent (some-> ^js os2 .-sTypoDescender abs)
;; useTypoMetrics can be read from the 7th bit
f-selection (some-> ^js os2 .-fsSelection (bit-test 7))
height-warning? (boolean
(and hhea-ascender hhea-descender win-ascent win-descent
(or (not= hhea-ascender win-ascent)
(not= hhea-descender win-descent)
(and f-selection os2-ascent os2-descent
(or (not= hhea-ascender os2-ascent)
(not= hhea-descender os2-descent))))))
data (js/Uint8Array. data)]
{:content {:data data
:name name
:type type}
:font-family (or family "")
:font-weight (cm/parse-font-weight variant)
:font-style (cm/parse-font-style variant)
:variant-name variant
:height-warning? height-warning?})
;; Font could not be parsed (woff2), extract metadata from filename
(let [base-name (str/replace name #"\.[^.]+$" "")
;; Strip known weight/style tokens and separators to derive family name
;; Use word boundaries to avoid matching substrings (e.g. "Boldini" should not match "bold")
raw-family-name (-> base-name
(str/replace #"(?i)(^|[-_\s])(extra\s*black|ultra\s*black|extra\s*bold|ultra\s*bold|semi\s*bold|demi\s*bold|extra\s*light|ultra\s*light|hairline|thin|light|normal|regular|medium|bold|black|heavy|solid|italic)([-_\s]|$)" "$1$3")
(str/replace #"[-_\s]+" " ")
(str/trim))
family-name (if (str/blank? raw-family-name) base-name raw-family-name)
data (js/Uint8Array. data)]
{:content {:data data
:name name
:type type}
:font-family family-name
:font-weight (cm/parse-font-weight base-name)
:font-style (cm/parse-font-style base-name)
:height-warning? false}))))
(defn process-upload
"Given a seq of blobs and the team id, creates a ready-to-use fonts
map with temporal ID's associated to each font entry.
@ -122,71 +244,16 @@
Each font entry's `:data` is a map of `{mtype -> ArrayBuffer}`. The
raw `ArrayBuffer` is kept as-is so that `upload-font-variant` can
wrap it in a `Blob` and hand it directly to `upload-blob-chunked`
without any intermediate client-side chunking."
without any intermediate client-side chunking.
Files that cannot be read, carry an unknown signature, fail
opentype.js parsing, or fail entry preparation are collected and
reported with a `bad-font` notification instead of silently dropped,
so the user always gets feedback. The returned observable never
errors for per-file failures; it emits the map of successfully
processed fonts (possibly empty)."
[blobs team-id]
(letfn [(prepare [{:keys [font type name data] :as params}]
(if font
;; Font was parsed with opentype.js (ttf, otf, woff)
(let [family (or (.getEnglishName ^js font "preferredFamily")
(.getEnglishName ^js font "fontFamily"))
variant (or (.getEnglishName ^js font "preferredSubfamily")
(.getEnglishName ^js font "fontSubfamily"))
;; Vertical metrics determine the baseline in a text and the space between lines of
;; text. For historical reasons, there are three pairs of ascender/descender
;; values, known as hhea, OS/2 and uSWin metrics. Depending on the font, operating
;; system and application a different set will be used to render text on the
;; screen. On Mac, Safari and Chrome use the hhea values to render text. Firefox
;; will respect the useTypoMetrics setting and will use the OS/2 if it is set. If
;; the useTypoMetrics is not set, Firefox will also use metrics from the hhea
;; table. On Windows, all browsers use the usWin metrics, but respect the
;; useTypoMetrics setting and if set will use the OS/2 values.
hhea-ascender (abs (-> ^js font .-tables .-hhea .-ascender))
hhea-descender (abs (-> ^js font .-tables .-hhea .-descender))
win-ascent (abs (-> ^js font .-tables .-os2 .-usWinAscent))
win-descent (abs (-> ^js font .-tables .-os2 .-usWinDescent))
os2-ascent (abs (-> ^js font .-tables .-os2 .-sTypoAscender))
os2-descent (abs (-> ^js font .-tables .-os2 .-sTypoDescender))
;; useTypoMetrics can be read from the 7th bit
f-selection (-> ^js font .-tables .-os2 .-fsSelection (bit-test 7))
height-warning? (or (not= hhea-ascender win-ascent)
(not= hhea-descender win-descent)
(and f-selection (or
(not= hhea-ascender os2-ascent)
(not= hhea-descender os2-descent))))
data (js/Uint8Array. data)]
{:content {:data data
:name name
:type type}
:font-family (or family "")
:font-weight (cm/parse-font-weight variant)
:font-style (cm/parse-font-style variant)
:variant-name variant
:height-warning? height-warning?})
;; Font could not be parsed (woff2), extract metadata from filename
(let [base-name (str/replace name #"\.[^.]+$" "")
;; Strip known weight/style tokens and separators to derive family name
;; Use word boundaries to avoid matching substrings (e.g. "Boldini" should not match "bold")
raw-family-name (-> base-name
(str/replace #"(?i)(^|[-_\s])(extra\s*black|ultra\s*black|extra\s*bold|ultra\s*bold|semi\s*bold|demi\s*bold|extra\s*light|ultra\s*light|hairline|thin|light|normal|regular|medium|bold|black|heavy|solid|italic)([-_\s]|$)" "$1$3")
(str/replace #"[-_\s]+" " ")
(str/trim))
family-name (if (str/blank? raw-family-name) base-name raw-family-name)
data (js/Uint8Array. data)]
{:content {:data data
:name name
:type type}
:font-family family-name
:font-weight (cm/parse-font-weight base-name)
:font-style (cm/parse-font-style base-name)
:height-warning? false})))
(join [res {:keys [content] :as font}]
(letfn [(join [res {:keys [content] :as font}]
(let [key-fn (juxt :font-family :font-weight :font-style)
existing (d/seek #(= (key-fn font) (key-fn %)) (vals res))]
(if existing
@ -206,60 +273,42 @@
(:data content)})
(dissoc :content)))))))
(parse-mtype [ba]
(let [u8 (js/Uint8Array. ba 0 4)
sg (areduce u8 i ret "" (str ret (if (zero? i) "" " ") (.toString (aget u8 i) 8)))]
(case sg
"117 124 124 117" "font/otf"
"0 1 0 0" "font/ttf"
"167 117 106 106" "font/woff"
"167 117 106 62" "font/woff2")))
(parse-font [{:keys [data type name] :as params}]
(if (= type "font/woff2")
;; woff2 cannot be parsed by opentype.js, extract metadata from filename
(assoc params :font nil)
(try
(assoc params :font (ot/parse data))
(catch :default _e
(log/warn :msg (str/fmt "skipping file %s, unsupported format" name))
nil))))
(read-blob [blob]
(->> (wa/read-file-as-array-buffer blob)
(rx/map (fn [data]
{:data data
:name (.-name blob)
:type (parse-mtype data)}))
(rx/catch (fn []
(rx/of {:error (.-name blob)})))
(rx/mapcat (fn [{:keys [type, error] :as font}]
(if (or type error)
(rx/of font)
(rx/empty))))))]
(let [blob-name (.-name blob)]
(->> (wa/read-file-as-array-buffer blob)
(rx/map (fn [data]
{:data data
:name blob-name
:type (parse-font-mtype data)}))
(rx/catch (fn []
(rx/of {:error blob-name})))
(rx/mapcat (fn [{:keys [type error] :as font}]
(if (or type error)
(rx/of font)
;; Unknown signature: report it instead of silently dropping it.
(rx/of {:error blob-name})))))))]
(let [fonts (->> (rx/from blobs)
(rx/mapcat read-blob))
errors (->> fonts
(rx/filter #(some? (:error %)))
(rx/reduce (fn [acc font]
(conj acc (str "'" (:error font) "'")))
[]))]
(rx/subscribe errors
#(when
(not-empty %)
(st/emit!
(ntf/error
(if (> (count %) 1)
(tr "errors.bad-font-plural" (str/join ", " %))
(tr "errors.bad-font" (first %)))))))
(->> fonts
(rx/filter #(nil? (:error %)))
(rx/map parse-font)
(rx/filter some?)
(rx/map prepare)
(rx/reduce join {})))))
(->> (rx/from blobs)
(rx/mapcat read-blob)
(rx/map parse-font)
(rx/filter some?)
(rx/reduce (fn [acc item]
(if (:error item)
(update acc :errors conj (str "'" (:error item) "'"))
(try
(update acc :fonts #(join % (prepare-font item)))
(catch :default cause
(log/warn :msg "skipping font file" :cause cause :name (:name item))
(update acc :errors conj (str "'" (:name item) "'"))))))
{:fonts {} :errors []})
(rx/mapcat (fn [{:keys [fonts errors]}]
(when (seq errors)
(st/emit!
(ntf/error
(if (> (count errors) 1)
(tr "errors.bad-font-plural" (str/join ", " errors))
(tr "errors.bad-font" (first errors))))))
(rx/of fonts))))))
(defn- calculate-family-to-id-mapping
[existing]

View File

@ -7,6 +7,7 @@
(ns app.main.data.workspace.tokens.library-edit
(:require
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
@ -48,6 +49,28 @@
;; Helpers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn editable-tokens-status?
"True when file-id is the current file. Its activation status is editable
even when its tokens come from an external source."
[state file-id]
(= file-id (:current-file-id state)))
(defn editable-tokens?
"True when file-id is the current file and uses its own tokens source.
Use this predicate when the file may be a connected library."
[state file-id]
(and (editable-tokens-status? state file-id)
(cfo/editable-tokens? (dsh/lookup-file-data state file-id))))
(defn- check-editable-tokens!
"Throws when the tokens of the current file come from an external library,
so they can't be modified from this file."
[state]
(when-not (editable-tokens? state (:current-file-id state))
(ex/raise :type :assertion
:code :tokens-not-editable
:hint "cannot modify the tokens of an external library")))
;; TODO HYMA: Copied over from workspace.cljs
(defn update-shape
[id attrs]
@ -268,6 +291,7 @@
(ptk/reify ::create-token-theme
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [data (dsh/lookup-file-data state)
tokens-lib (dsh/lookup-tokens-lib state)]
(if (and tokens-lib (ctob/get-theme tokens-lib (ctob/get-id token-theme)))
@ -286,6 +310,7 @@
(ptk/reify ::update-token-theme
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [data (dsh/lookup-file-data state)
tokens-lib (dsh/lookup-tokens-lib state)]
(if (and (not= id (ctob/get-id token-theme))
@ -322,7 +347,7 @@
(ptk/reify ::toggle-token-theme-active
ptk/WatchEvent
(watch [it state _]
(let [data (dsh/lookup-tokens-source-data state)
(let [data (dsh/lookup-file-data state)
tokens-status (dsh/lookup-tokens-status state)
tokens-lib (dsh/lookup-tokens-lib state)
changes (-> (pcb/empty-changes it)
@ -338,6 +363,7 @@
(ptk/reify ::delete-token-theme
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
@ -352,6 +378,7 @@
(ptk/reify ::create-token-set
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
@ -366,6 +393,7 @@
(ptk/reify ::update-token-set
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
@ -377,7 +405,8 @@
[set-group-path set-group-fname]
(ptk/reify ::rename-token-set-group
ptk/WatchEvent
(watch [it _state _]
(watch [it state _]
(check-editable-tokens! state)
(let [changes (-> (pcb/empty-changes it)
(pcb/rename-token-set-group set-group-path set-group-fname))]
(rx/of
@ -390,6 +419,7 @@
(ptk/reify ::duplicate-token-set
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [data (dsh/lookup-file-data state)
tokens-lib (dsh/lookup-tokens-lib state)
suffix (tr "workspace.tokens.duplicate-suffix")]
@ -458,6 +488,7 @@
(ptk/reify ::import-tokens-lib
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [data (dsh/lookup-file-data state)
status (cfo/make-tokens-status-from-lib lib)
changes (-> (pcb/empty-changes it)
@ -486,6 +517,7 @@
(ptk/reify ::delete-token-set
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
@ -498,6 +530,7 @@
(ptk/reify ::delete-token-set-group
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
@ -528,6 +561,7 @@
(ptk/reify ::drop-token-set-group
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(try
(when-let [changes (clo/generate-move-token-set-group (pcb/empty-changes it) (dsh/lookup-tokens-lib state) drop-opts)]
(rx/of
@ -544,6 +578,7 @@
(ptk/reify ::drop-token-set
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(try
(let [tokens-lib (dsh/lookup-tokens-lib state)
changes (-> (pcb/empty-changes it)
@ -559,6 +594,7 @@
(ptk/reify ::create-token-and-set
ptk/WatchEvent
(watch [_ state _]
(check-editable-tokens! state)
(let [data
(dsh/lookup-file-data state)
@ -594,6 +630,7 @@
(ptk/reify ::create-token
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(if-let [token-set (if set-id
(lookup-token-set state set-id)
(lookup-token-set state))]
@ -621,6 +658,7 @@
(ptk/reify ::bulk-create-tokens
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [token-set (lookup-token-set state set-id)
data (dsh/lookup-file-data state)
changes (reduce (fn [changes token-id]
@ -650,6 +688,7 @@
(ptk/reify ::update-token
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [token-set (if set-id
(lookup-token-set state set-id)
(lookup-token-set state))
@ -678,6 +717,7 @@
(ptk/reify ::bulk-update-tokens
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [token-set (if set-id
(lookup-token-set state set-id)
(lookup-token-set state))
@ -708,6 +748,7 @@
(ptk/reify ::delete-token
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [data (dsh/lookup-file-data state)
tokens-lib (dsh/lookup-tokens-lib state)
token-set (if set-id
@ -731,6 +772,7 @@
(ptk/reify ::bulk-delete-tokens
ptk/WatchEvent
(watch [it state _]
(check-editable-tokens! state)
(let [data (dsh/lookup-file-data state)
changes (reduce (fn [changes token-id]
(pcb/set-token changes set-id token-id nil))
@ -746,6 +788,7 @@
(ptk/reify ::duplicate-token
ptk/WatchEvent
(watch [_ state _]
(check-editable-tokens! state)
(when-let [token-set (lookup-token-set state)]
(when-let [tokens-lib (dsh/lookup-tokens-lib state)]
(when-let [token (ctob/get-token tokens-lib

View File

@ -114,6 +114,7 @@
(rx/subs! (fn [result]
(swap! fonts* df/merge-and-group-fonts installed-fonts result))
(fn [error]
(st/emit! (ntf/error (tr "errors.generic")))
(js/console.error "error" error))))))
on-upload

View File

@ -489,9 +489,10 @@
(tr "common.publish")])]
(for [{:keys [id name data connected-to connected-to-names] :as library} linked-libraries]
(let [disabled? (some #(contains? linked-libraries-ids %) connected-to)
has-tokens? (and (has-tokens? library)
(contains? cf/flags :token-import-from-library))]
(let [disabled? (some #(contains? linked-libraries-ids %) connected-to)
can-import-tokens? (and (has-tokens? library)
(cfo/editable-tokens? local-library)
(contains? cf/flags :token-import-from-library))]
[:div {:class (stl/css :section-list-item-legacy)
:key (dm/str id)
:data-testid "library-item"}
@ -508,7 +509,7 @@
[:span ")"]])])]]
[:div {:class (stl/css :library-actions)}
(when ^boolean has-tokens?
(when ^boolean can-import-tokens?
[:> icon-button*
{:type "button"
:aria-label (tr "workspace.tokens.import-tokens")
@ -865,9 +866,10 @@
:title-class (stl/css :connected-libraries-title)}]
(if (seq linked-libraries)
(for [{:keys [id name data connected-to nested? parent-name] :as library} linked-libraries]
(let [disabled? (some #(contains? linked-libraries-ids %) connected-to)
has-tokens? (and (has-tokens? library)
(contains? cf/flags :token-import-from-library))]
(let [disabled? (some #(contains? linked-libraries-ids %) connected-to)
can-import-tokens? (and (has-tokens? library)
(cfo/editable-tokens? local-library)
(contains? cf/flags :token-import-from-library))]
[:div {:class (stl/css-case :section-list-item true
:section-list-item-nested nested?)
:key (dm/str id)
@ -893,7 +895,7 @@
:data-library-id (dm/str id)
:on-click set-as-tokens-source}
(tr "workspace.libraries.set-as-tokens-source")])
(when ^boolean has-tokens?
(when ^boolean can-import-tokens?
[:> icon-button*
{:type "button"
:aria-label (tr "workspace.tokens.import-tokens")

View File

@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.files.variant :as cfv]
[app.common.geom.point :as gpt]
[app.common.schema :as sm]
@ -22,6 +23,7 @@
[app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.data.workspace.variants :as dwv]
[app.main.fonts :as fonts]
[app.main.repo :as rp]
@ -1318,6 +1320,37 @@
(fn []
(apply array (map (partial library-proxy plugin-id) (loaded-file-ids @st/state))))}
:tokensSource
{:get
(fn []
(let [file-id (:current-file-id @st/state)
file-data (ctf/file-data (u/locate-file file-id))]
(library-proxy plugin-id (cfo/get-effective-tokens-source file-data))))
:set
(fn [value]
(let [file-id (:current-file-id @st/state)
library-id (when (library-proxy? value) (obj/get value "$id"))
library (some-> library-id u/locate-file)
library-data (some-> library ctf/file-data)
file-data (ctf/file-data (u/locate-file file-id))]
(cond
(not (r/check-permission plugin-id "content:write"))
(u/not-valid plugin-id :tokensSource "Plugin doesn't have 'content:write' permission")
(not (library-proxy? value))
(u/not-valid plugin-id :tokensSource value)
(nil? library)
(u/not-valid plugin-id :tokensSource "Library is not connected")
(and (not= library-id file-id)
(not (cfo/tokens-provider? library-data)))
(u/not-valid plugin-id :tokensSource "Library must have its own tokens")
(not (cfo/effective-tokens-source? file-data library-id))
(st/emit! (-> (dwtl/set-tokens-source library-id)
(se/add-event plugin-id))))))}
:availableLibraries
(fn []
(let [team-id (:current-team-id @st/state)]

View File

@ -11,7 +11,6 @@
[app.common.files.tokens :as cfo]
[app.common.json :as json]
[app.common.schema :as sm]
[app.common.types.file :as ctf]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
@ -509,8 +508,11 @@
(not (r/check-permission plugin-id "content:write"))
(u/not-valid plugin-id :active "Plugin doesn't have 'content:write' permission")
(not (dwtl/editable-tokens-status? @st/state file-id))
(u/not-valid plugin-id :active "Cannot change activation through a connected library; use penpot.library.local.tokens")
:else
(st/emit! (dwtl/set-enabled-token-set id value))))} ;; This can be done even with tokens in an external library
(st/emit! (dwtl/set-enabled-token-set id value))))}
:toggleActive
(fn []
@ -518,6 +520,9 @@
(not (r/check-permission plugin-id "content:write"))
(u/not-valid plugin-id :toggleActive "Plugin doesn't have 'content:write' permission")
(not (dwtl/editable-tokens-status? @st/state file-id))
(u/not-valid plugin-id :toggleActive "Cannot change activation through a connected library; use penpot.library.local.tokens")
:else
(st/emit! (dwtl/toggle-token-set id))))
@ -734,6 +739,9 @@
(not (r/check-permission plugin-id "content:write"))
(u/not-valid plugin-id :active "Plugin doesn't have 'content:write' permission")
(not (dwtl/editable-tokens-status? @st/state file-id))
(u/not-valid plugin-id :active "Cannot change activation through a connected library; use penpot.library.local.tokens")
:else
(st/emit! (dwtl/set-token-theme-active id value))))}
@ -743,6 +751,9 @@
(not (r/check-permission plugin-id "content:write"))
(u/not-valid plugin-id :toggleActive "Plugin doesn't have 'content:write' permission")
(not (dwtl/editable-tokens-status? @st/state file-id))
(u/not-valid plugin-id :toggleActive "Cannot change activation through a connected library; use penpot.library.local.tokens")
:else
(st/emit! (dwtl/toggle-token-theme-active id))))
@ -836,8 +847,7 @@
:enumerable false
:get
(fn []
(let [file (u/locate-file file-id)]
(cfo/editable-tokens? (ctf/file-data file))))}
(dwtl/editable-tokens? @st/state file-id))}
:themes
{:this true

View File

@ -18,6 +18,7 @@
[app.common.types.file :as ctf]
[app.common.types.tokens-lib :as ctob]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.store :as st]
[app.util.object :as obj]
[cuerdas.core :as str]))
@ -288,9 +289,8 @@
(defn check-editable-tokens
[file-id]
(let [file (locate-file file-id)]
(when-not (cfo/editable-tokens? (ctf/file-data file))
(throw (js/Error. (dm/str "[PENPOT PLUGIN] Cannot modify tokens in an external library"))))))
(when-not (dwtl/editable-tokens? @st/state file-id)
(throw (js/Error. (dm/str "[PENPOT PLUGIN] Cannot modify tokens in an external library")))))
(defn throw-validation-errors?
[plugin-id]

View File

@ -6,13 +6,18 @@
(ns frontend-tests.fonts-test
(:require
[app.common.uuid :as uuid]
[app.main.data.fonts :as df]
[app.main.fonts :as fonts]
[app.main.store :as st]
[app.util.globals :as globals]
[app.util.http :as http]
[app.util.webapi :as wa]
[beicon.v2.core :as rx]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.async :as async]
[frontend-tests.helpers.mock :as mock]))
[frontend-tests.helpers.mock :as mock]
[potok.v2.core :as ptk]))
(def sample-font
{:id "sourcesanspro"
@ -244,3 +249,105 @@
;; Trailing settle: the body is synchronous, but `with-mocks*`
;; evaluates to a promise, so the body must settle one.
(await (async/settle))))))
;; --- custom font upload ------------------------------------------------------
;;
;; Regression tests for https://github.com/penpot/penpot/issues/12304:
;; uploading a font whose opentype.js names are missing (nil subfamily,
;; as seen with some Fontshare families) crashed `process-upload` with
;; `re-seq must match against a string` and left the user with no
;; feedback. Unknown signatures and unparseable files were silently
;; dropped as well.
(defn- bytes->buffer
[bytes]
(.-buffer (js/Uint8Array. (clj->js bytes))))
(defn- fake-ot-font
[{:keys [family variant hhea os2]}]
(let [names {"preferredFamily" family
"fontFamily" family
"preferredSubfamily" variant
"fontSubfamily" variant}]
#js {:getEnglishName (fn [k] (get names k))
:tables #js {:hhea hhea :os2 os2}}))
(t/deftest parse-font-mtype-detects-font-signatures
(t/is (= "font/ttf" (#'df/parse-font-mtype (bytes->buffer [0 1 0 0]))))
(t/is (= "font/otf" (#'df/parse-font-mtype (bytes->buffer [79 84 84 79]))))
(t/is (= "font/woff" (#'df/parse-font-mtype (bytes->buffer [119 79 70 70]))))
(t/is (= "font/woff2" (#'df/parse-font-mtype (bytes->buffer [119 79 70 50]))))
(t/is (nil? (#'df/parse-font-mtype (bytes->buffer [1 2 3 4]))))
(t/is (nil? (#'df/parse-font-mtype nil)))
(t/is (nil? (#'df/parse-font-mtype (bytes->buffer [0 1])))))
(t/deftest prepare-font-defaults-missing-subfamily
(let [font (fake-ot-font {:family "Satoshi"
:variant nil
:hhea #js {:ascender 1000 :descender 300}
:os2 #js {:usWinAscent 1000 :usWinDescent 300
:sTypoAscender 800 :sTypoDescender 200
:fsSelection 0}})
entry (#'df/prepare-font {:font font
:type "font/otf"
:name "Satoshi-Regular.otf"
:data (bytes->buffer [79 84 84 79 0 0 0 0])})]
(t/is (= "Satoshi" (:font-family entry)))
(t/is (= 400 (:font-weight entry)))
(t/is (= "normal" (:font-style entry)))
(t/is (false? (:height-warning? entry)))))
(t/deftest prepare-font-tolerates-missing-tables
(let [font (fake-ot-font {:family "Satoshi" :variant "Regular" :hhea nil :os2 nil})
entry (#'df/prepare-font {:font font
:type "font/ttf"
:name "Satoshi-Regular.ttf"
:data (bytes->buffer [0 1 0 0 0 0 0 0])})]
(t/is (= "Satoshi" (:font-family entry)))
(t/is (false? (:height-warning? entry)))))
(t/deftest parse-font-returns-error-on-unparseable
(t/testing "read errors pass through"
(t/is (= {:error "x.ttf"} (#'df/parse-font {:error "x.ttf"}))))
(t/testing "woff2 keeps a nil font for filename-based metadata"
(let [result (#'df/parse-font {:data (bytes->buffer [119 79 70 50])
:type "font/woff2"
:name "Satoshi-Bold.woff2"})]
(t/is (nil? (:font result)))
(t/is (nil? (:error result)))))
(t/testing "opentype.js failures become errors instead of silent drops"
(let [garbage (bytes->buffer (into [0 1 0 0] (repeat 96 0)))
result (#'df/parse-font {:data garbage :type "font/ttf" :name "bad.ttf"})]
(t/is (= "bad.ttf" (:error result))))))
(t/deftest ^:async process-upload-reports-unknown-signature
(let [team-id (uuid/next)
blob #js {:name "mystery.bin"}
emitted (atom [])
result (atom nil)]
(await (mock/with-mocks*
{wa/read-file-as-array-buffer (mock/stub (fn [_] (rx/of (bytes->buffer [1 2 3 4]))))
st/emit! (mock/stub (fn [& evts] (swap! emitted into evts) nil))}
(await (async/observe (df/process-upload [blob] team-id)
:on-next #(reset! result %)))))
(t/is (= {} @result))
(t/is (= 1 (count @emitted)))
(t/is (= :app.main.data.notifications/show (ptk/type (first @emitted))))))
(t/deftest ^:async process-upload-woff2-succeeds-from-filename
(let [team-id (uuid/next)
blob #js {:name "Satoshi-Bold.woff2"}
emitted (atom [])
result (atom nil)]
(await (mock/with-mocks*
{wa/read-file-as-array-buffer (mock/stub (fn [_] (rx/of (bytes->buffer [119 79 70 50 0 0 0 0]))))
st/emit! (mock/stub (fn [& evts] (swap! emitted into evts) nil))}
(await (async/observe (df/process-upload [blob] team-id)
:on-next #(reset! result %)))
(await (async/settle))))
(t/is (= 1 (count (vals @result))))
(let [entry (first (vals @result))]
(t/is (= "Satoshi" (:font-family entry)))
(t/is (= 700 (:font-weight entry)))
(t/is (= "normal" (:font-style entry))))
(t/is (empty? @emitted))))

View File

@ -25,7 +25,7 @@
;; profile renderer preference instead of the global feature flag alone.
:profile {:props {:renderer :wasm}}})
(defn- on-error
(defn- report-error
[cause]
(js/console.error "STORE ERROR" (.-stack cause))
(t/do-report {:type :error :message "Store error" :actual cause})
@ -34,7 +34,7 @@
(defn setup-store
([file] (setup-store file nil))
([file {:keys [renderer libraries] :as _opts}]
([file {:keys [renderer libraries on-error] :or {on-error report-error} :as _opts}]
(let [state (-> initial-state
(assoc :current-file-id (:id file)
:current-page-id (cthf/current-page-id file)

View File

@ -18,17 +18,21 @@
[app.main.data.tokenscript :as ts]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.data.workspace.undo :as dwu]
[app.main.store :as st]
[app.plugins.api :as api]
[app.plugins.library :as library]
[app.plugins.register :as r]
[app.plugins.tokens :as ptok]
[app.plugins.utils :as u]
[app.util.object :as obj]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.async :as tha]
[clojure.datafy :refer [datafy]]
[frontend-tests.helpers.async :as async]
[frontend-tests.helpers.mock :as mock]
[frontend-tests.helpers.state :as ths]
[frontend-tests.helpers.wasm :as thw]
[frontend-tests.tokens.helpers.state :as tohs]
[potok.v2.core :as ptk]))
(t/use-fixtures :each
@ -71,7 +75,7 @@
#js {:strokeColor "#000000" :strokeWidth 1}])
(ptk/emit! store (dwsh/update-shapes [id] #(assoc % :applied-tokens tokens)))
(obj/set! (aget (.-strokes rect) index) property value)
(await (tha/settle))
(await (async/settle))
(t/is (= (apply dissoc tokens removed) (get-in @store path)))
(t/is (= value (obj/get (aget (.-strokes rect) index) property))))))))
@ -97,15 +101,344 @@
(ptk/emit! store (dwsh/update-shapes [id] #(assoc % :applied-tokens tokens)))
(let [^js gradient (obj/get (aget (.-strokes rect) 0) "strokeColorGradient")]
(set! (.-startX gradient) 0.5)
(await (tha/settle))
(await (async/settle))
(t/is (= (dissoc tokens :stroke-color) (get-in @store path)))
(t/is (= 0.5 (obj/get (obj/get (aget (.-strokes rect) 0) "strokeColorGradient") "startX")))
(ptk/emit! store (dwsh/update-shapes [id] #(assoc % :applied-tokens tokens)))
(obj/set! (aget (.-stops gradient) 0) "color" "#ff0000")
(await (tha/settle))
(await (async/settle))
(t/is (= (dissoc tokens :stroke-color) (get-in @store path)))
(t/is (= "#ff0000" (obj/get (aget (obj/get (obj/get (aget (.-strokes rect) 0) "strokeColorGradient") "stops") 0) "color"))))))
(defn- token-source-file
[file-label]
(let [set-id (uuid/next)]
(ctht/sample-file-with-tokens
:file-id file-label
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id set-id :name "Core"))
(ctob/add-theme (ctob/make-token-theme :name "Light"
:group "Mode"
:sets #{"Core"}))
(ctob/add-token set-id (ctob/make-token :name "color.primary"
:type :color
:value "#FF0000")))
:status-fn #(ctos/set-tokens-status % #{} #{set-id}))))
(defn- with-active-theme
"The tokens status of `file` with its first visible theme activated."
[file]
(let [tokens-lib (ctht/get-tokens-lib file)
theme-id (:id (first (remove ctob/hidden-theme? (ctob/get-themes tokens-lib))))]
(cfo/activate-theme (ctht/get-tokens-status file) tokens-lib theme-id)))
(defn- tokens-unchanged?
"True when the token attributes of `file` in the store are the original ones."
[store file]
(let [attrs [:tokens-lib :tokens-status :tokens-source]]
(= (select-keys (:data file) attrs)
(select-keys (get-in @store [:files (:id file) :data]) attrs))))
(defn- record-not-valid
"A `u/not-valid` stub that collects each call into `errors`."
[errors]
(mock/stub (fn [plugin-id code value] (swap! errors conj [plugin-id code value]))))
(t/deftest ^:async library-tokens-source-switches-to-connected-library
(let [file (token-source-file :local)
source (token-source-file :source)
store (ths/setup-store file {:libraries [source]})
^js context (library/library-subcontext (str uuid/zero))
source-proxy (library/library-proxy (str uuid/zero) (:id source))]
(await
(mock/with-mocks* {st/state store st/stream (ptk/input-stream store)}
(t/is (= (str (:id file)) (.. context -tokensSource -id)))
(set! (.-tokensSource context) source-proxy)
(await (async/wait-for #(= (:id source) (get-in @store [:files (:id file) :data :tokens-source]))
"library token source"))
(t/is (= (str (:id source)) (.. context -tokensSource -id)))
(t/is (= (datafy (ctht/get-tokens-status source))
(datafy (get-in @store [:files (:id file) :data :tokens-status]))))
(t/is (= (ctht/get-tokens-lib source) (u/locate-tokens-lib (:id file))))
(t/is (false? (.. context -local -tokens -isEditableTokens)))
(t/is (= (ctht/get-tokens-lib file)
(get-in @store [:files (:id file) :data :tokens-lib])))
(t/is (tokens-unchanged? store source))))))
(t/deftest ^:async library-tokens-source-restores-local-tokens
(let [source (token-source-file :source)
file (-> (token-source-file :local)
(assoc-in [:data :tokens-source] (:id source))
(assoc-in [:data :tokens-status] (with-active-theme source)))
store (ths/setup-store file {:libraries [source]})
^js context (library/library-subcontext (str uuid/zero))]
(await
(mock/with-mocks* {st/state store st/stream (ptk/input-stream store)}
(set! (.-tokensSource context) (.-local context))
(await (async/wait-for #(nil? (get-in @store [:files (:id file) :data :tokens-source]))
"local token source"))
(let [^js catalog (.. context -local -tokens)]
(t/is (= (str (:id file)) (.. context -tokensSource -id)))
(t/is (true? (.-isEditableTokens catalog)))
(t/is (= (ctht/get-tokens-lib file) (u/locate-tokens-lib (:id file))))
(t/is (= #{} (ctos/get-active-set-ids (get-in @store [:files (:id file) :data :tokens-status]))))
(t/is (not-any? #(.-active ^js %) (.-themes catalog))))))))
(t/deftest ^:async library-tokens-source-supports-undo-and-redo
(let [file (token-source-file :local)
source (token-source-file :source)
store (ths/setup-store file {:libraries [source]})
^js context (library/library-subcontext (str uuid/zero))
source-proxy (library/library-proxy (str uuid/zero) (:id source))]
(await
(mock/with-mocks* {st/state store st/stream (ptk/input-stream store)}
(st/emit! (tohs/watch-undo-stack))
(set! (.-tokensSource context) source-proxy)
(await (async/wait-for #(seq (get-in @store [:workspace-undo :items]))
"token source undo entry"))
(st/emit! dwu/undo)
(await (async/wait-for #(nil? (get-in @store [:files (:id file) :data :tokens-source]))
"undo token source"))
(t/is (= (datafy (ctht/get-tokens-status file))
(datafy (get-in @store [:files (:id file) :data :tokens-status]))))
(st/emit! dwu/redo)
(await (async/wait-for #(= (:id source) (get-in @store [:files (:id file) :data :tokens-source]))
"redo token source"))
(t/is (= (datafy (ctht/get-tokens-status source))
(datafy (get-in @store [:files (:id file) :data :tokens-status]))))))))
(t/deftest ^:async library-tokens-source-keeps-status-when-source-is-unchanged
(let [source (token-source-file :source)
file (-> (token-source-file :local)
(assoc-in [:data :tokens-source] (:id source)))
store (ths/setup-store file {:libraries [source]})
^js context (library/library-subcontext (str uuid/zero))
source-proxy (library/library-proxy (str uuid/zero) (:id source))]
(await
(mock/with-mocks* {st/state store st/stream (ptk/input-stream store)}
(set! (.-tokensSource context) source-proxy)
(await (async/settle))
(t/is (tokens-unchanged? store file))))))
(t/deftest ^:async library-tokens-source-rejects-invalid-libraries
(let [file (token-source-file :local)
empty-lib (ctht/sample-file-with-tokens :file-id :empty)
indirect (-> (token-source-file :indirect)
(assoc-in [:data :tokens-source] (uuid/next)))
store (ths/setup-store file {:libraries [empty-lib indirect]})
plugin-id (str uuid/zero)
^js context (library/library-subcontext plugin-id)
errors (atom [])]
(await
(mock/with-mocks* {st/state store
st/stream (ptk/input-stream store)
u/not-valid (record-not-valid errors)}
(doseq [[label value message]
[["not a library" "invalid" "invalid"]
["not connected" (library/library-proxy plugin-id (uuid/next)) "Library is not connected"]
["without own tokens" (library/library-proxy plugin-id (:id empty-lib)) "Library must have its own tokens"]
["with another tokens source" (library/library-proxy plugin-id (:id indirect)) "Library must have its own tokens"]]]
(t/testing label
(reset! errors [])
(set! (.-tokensSource context) value)
(t/is (= [[plugin-id :tokensSource message]] @errors))))
(await (async/settle))
(t/is (tokens-unchanged? store file))))))
(t/deftest ^:async external-catalogs-reject-tokens-changes
(let [source (token-source-file :source)
other (token-source-file :other)
file (-> (token-source-file :local)
(assoc-in [:data :tokens-source] (:id source)))
store (ths/setup-store file {:libraries [source other]})
plugin-id (str uuid/zero)]
(swap! store assoc-in [:plugins :flags plugin-id :throw-validation-errors] true)
(await
(mock/with-mocks* {st/state store st/stream (ptk/input-stream store)}
(doseq [[catalog-label ^js catalog]
[["local catalog" (.. (library/library-subcontext plugin-id) -local -tokens)]
["source catalog" (.. (library/library-subcontext plugin-id) -tokensSource -tokens)]
["other connected catalog" (.-tokens (library/library-proxy plugin-id (:id other)))]]]
(t/testing catalog-label
(let [^js token-set (aget (.-sets catalog) 0)
^js token (aget (.-tokens token-set) 0)
^js theme (aget (.-themes catalog) 0)]
(t/is (false? (.-isEditableTokens catalog)))
(doseq [[label mutate]
[["create set" #(.addSet catalog #js {:name "New"})]
["rename set" #(set! (.-name token-set) "Renamed")]
["duplicate set" #(.duplicate token-set)]
["remove set" #(.remove token-set)]
["create token" #(.addToken token-set #js {:name "color.new" :type "color" :value "#00FF00"})]
["rename token" #(set! (.-name token) "color.renamed")]
["change token value" #(set! (.-value token) "#00FF00")]
["change token description" #(set! (.-description token) "New description")]
["duplicate token" #(.duplicate token)]
["remove token" #(.remove token)]
["create theme" #(.addTheme catalog #js {:name "Dark" :group "Mode"})]
["rename theme" #(set! (.-name theme) "Dark")]
["change theme group" #(set! (.-group theme) "Other")]
["add set to theme" #(.addSet theme token-set)]
["remove set from theme" #(.removeSet theme token-set)]
["duplicate theme" #(.duplicate theme)]
["remove theme" #(.remove theme)]]]
(t/testing label
(t/is (thrown-with-msg? js/Error #"Cannot modify tokens in an external library"
(mutate)))
(await (async/settle))
(t/is (tokens-unchanged? store file))
(t/is (tokens-unchanged? store source))
(t/is (tokens-unchanged? store other)))))))))))
(t/deftest ^:async external-catalogs-reject-status-changes
(let [source (token-source-file :source)
other (token-source-file :other)
file (-> (token-source-file :local)
(assoc-in [:data :tokens-source] (:id source))
(assoc-in [:data :tokens-status] (with-active-theme source)))
store (ths/setup-store file {:libraries [source other]})
plugin-id (str uuid/zero)
message "Cannot change activation through a connected library; use penpot.library.local.tokens"
errors (atom [])]
(await
(mock/with-mocks* {st/state store
st/stream (ptk/input-stream store)
u/not-valid (record-not-valid errors)}
(let [^js context (library/library-subcontext plugin-id)]
(t/is (true? (.-active ^js (aget (.. context -local -tokens -themes) 0))))
(doseq [[catalog-label ^js catalog]
[["source catalog" (.. context -tokensSource -tokens)]
["other connected catalog" (.-tokens (library/library-proxy plugin-id (:id other)))]]]
(t/testing catalog-label
(let [^js token-set (aget (.-sets catalog) 0)
^js theme (aget (.-themes catalog) 0)]
(t/testing "reports the status of its own file"
(t/is (true? (.-active token-set)))
(t/is (false? (.-active theme))))
(reset! errors [])
(set! (.-active token-set) false)
(.toggleActive token-set)
(set! (.-active theme) true)
(.toggleActive theme)
(await (async/settle))
(t/is (= [[plugin-id :active message]
[plugin-id :toggleActive message]
[plugin-id :active message]
[plugin-id :toggleActive message]]
@errors))
(t/is (tokens-unchanged? store file))
(t/is (tokens-unchanged? store source))
(t/is (tokens-unchanged? store other))))))))))
(t/deftest ^:async external-token-source-allows-set-activation
(let [source (token-source-file :source)
file (-> (token-source-file :local)
(assoc-in [:data :tokens-source] (:id source))
(assoc-in [:data :tokens-status] (ctht/get-tokens-status source)))
store (ths/setup-store file {:libraries [source]})]
(await
(mock/with-mocks* {st/state store st/stream (ptk/input-stream store)}
(let [^js catalog (.. (library/library-subcontext (str uuid/zero)) -local -tokens)
^js token-set (aget (.-sets catalog) 0)]
(t/is (true? (.-active token-set)))
(set! (.-active token-set) false)
(await (async/wait-for #(false? (.-active token-set)) "deactivate set"))
(.toggleActive token-set)
(await (async/wait-for #(true? (.-active token-set)) "toggle set"))
(t/is (= (ctht/get-tokens-lib file) (get-in @store [:files (:id file) :data :tokens-lib])))
(t/is (tokens-unchanged? store source)))))))
(t/deftest ^:async external-token-source-allows-theme-activation
(let [source (token-source-file :source)
file (-> (token-source-file :local)
(assoc-in [:data :tokens-source] (:id source))
(assoc-in [:data :tokens-status] (ctht/get-tokens-status source)))
store (ths/setup-store file {:libraries [source]})]
(await
(mock/with-mocks* {st/state store st/stream (ptk/input-stream store)}
(let [^js catalog (.. (library/library-subcontext (str uuid/zero)) -local -tokens)
^js theme (aget (.-themes catalog) 0)]
(t/is (false? (.-active theme)))
(set! (.-active theme) true)
(await (async/wait-for #(true? (.-active theme)) "activate theme"))
(.toggleActive theme)
(await (async/wait-for #(false? (.-active theme)) "toggle theme"))
(t/is (= (ctht/get-tokens-lib file) (get-in @store [:files (:id file) :data :tokens-lib])))
(t/is (tokens-unchanged? store source)))))))
(t/deftest ^:async external-token-source-theme-toggle-supports-undo
(let [source (token-source-file :source)
status (with-active-theme source)
file (-> (token-source-file :local)
(assoc-in [:data :tokens-source] (:id source))
(assoc-in [:data :tokens-status] status))
store (ths/setup-store file {:libraries [source]})]
(await
(mock/with-mocks* {st/state store st/stream (ptk/input-stream store)}
(let [^js catalog (.. (library/library-subcontext (str uuid/zero)) -local -tokens)
^js theme (aget (.-themes catalog) 0)]
(st/emit! (tohs/watch-undo-stack))
(.toggleActive theme)
(await (async/wait-for #(seq (get-in @store [:workspace-undo :items]))
"theme toggle undo entry"))
(t/is (false? (.-active theme)))
(st/emit! dwu/undo)
(await (async/wait-for #(= (datafy status)
(datafy (get-in @store [:files (:id file) :data :tokens-status])))
"undo external theme toggle"))
(t/is (tokens-unchanged? store source)))))))
(t/deftest ^:async external-token-source-allows-applying-tokens-to-shapes
(let [source (token-source-file :source)
file (-> (token-source-file :local)
(assoc-in [:data :tokens-source] (:id source))
(assoc-in [:data :tokens-status] (ctht/get-tokens-status source))
(ctho/add-rect :rect1)
(ctho/add-rect :rect2)
(ctho/add-rect :rect3))
store (ths/setup-store file {:libraries [source]})
page-id (cthf/current-page-id file)]
(swap! store assoc-in [:workspace-local :selected] #{(cthi/id :rect3)})
(await
(mock/with-mocks* {st/state store st/stream (ptk/input-stream store)}
(let [^js context (api/create-context (str uuid/zero))
^js page (.-currentPage context)
^js catalog (.. context -library -local -tokens)
^js token-set (aget (.-sets catalog) 0)
^js token (aget (.-tokens token-set) 0)
^js rect1 (.getShapeById page (str (cthi/id :rect1)))
^js rect2 (.getShapeById page (str (cthi/id :rect2)))]
(.applyToken rect1 token #js ["fill"])
(.applyToShapes token #js [rect2] #js ["fill"])
(.applyToSelected token #js ["fill"])
(doseq [label [:rect1 :rect2 :rect3]]
(let [shape-path [:files (:id file) :data :pages-index page-id :objects (cthi/id label)]]
(await (async/wait-for #(= "color.primary" (get-in @store (conj shape-path :applied-tokens :fill)))
(str "apply external token to " label)))
(await (async/wait-for #(= "#ff0000" (get-in @store (conj shape-path :fills 0 :fill-color)))
(str "resolve external token on " label)))))
(t/is (= (ctht/get-tokens-lib file) (get-in @store [:files (:id file) :data :tokens-lib])))
(t/is (= (datafy (ctht/get-tokens-status file))
(datafy (get-in @store [:files (:id file) :data :tokens-status]))))
(t/is (tokens-unchanged? store source)))))))
(t/deftest ^:async library-tokens-source-requires-content-write-permission
(let [file (token-source-file :local)
source (token-source-file :source)
plugin-id "read-only-plugin"
store (ths/setup-store file {:libraries [source]})
^js context (library/library-subcontext plugin-id)
source-proxy (library/library-proxy plugin-id (:id source))
errors (atom [])]
(await
(mock/with-mocks* {st/state store
st/stream (ptk/input-stream store)
u/not-valid (record-not-valid errors)}
(set! (.-tokensSource context) source-proxy)
(t/is (= [[plugin-id :tokensSource "Plugin doesn't have 'content:write' permission"]] @errors))
(await (async/settle))
(t/is (tokens-unchanged? store file))))))
;; Regression coverage for issue #9162.
;;
;; Plugin code calling `shape.applyToken(token, ["fill"])` or

View File

@ -15,10 +15,12 @@
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.data.workspace.undo :as dwu]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.async :as async]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]
[frontend-tests.tokens.helpers.state :as tohs]
[frontend-tests.tokens.helpers.tokens :as toht]))
[frontend-tests.tokens.helpers.tokens :as toht]
[potok.v2.core :as ptk]))
(t/use-fixtures :each
{:before thp/reset-idmap!})
@ -895,3 +897,64 @@
(t/testing "Tokens lib has been imported into file without existing lib"
(t/is (some? tokens-lib'))
(t/is (some? (ctob/get-set-by-name tokens-lib' "Imported Set"))))))))))
(defn- setup-tokens-source-library
[]
(ctho/sample-file-with-tokens
:file-id :library
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :source-set)
:name "Source"))
(ctob/add-token (cthi/id :source-set)
(ctob/make-token :id (cthi/new-id! :source-token)
:name "color.source"
:type :color
:value "#FF0000"))
(ctob/add-theme (ctob/make-token-theme :id (cthi/new-id! :source-theme)
:name "Light"
:group "Mode"
:sets #{"Source"})))
:status-fn #(ctos/set-tokens-status % #{} #{(cthi/id :source-set)})))
(t/deftest ^:async external-tokens-source-rejects-tokens-lib-changes
(let [source (setup-tokens-source-library)
file (-> (setup-file-with-token-lib)
(assoc-in [:data :tokens-source] (:id source)))
errors (atom [])
store (ths/setup-store file {:libraries [source]
:on-error #(swap! errors conj %)})
tokens-lib (ctho/get-tokens-lib source)
set-id (cthi/id :source-set)
token-id (cthi/id :source-token)
theme-id (cthi/id :source-theme)]
(doseq [[label event]
[["create theme" (dwtl/create-token-theme (ctob/make-token-theme :name "Dark" :group "Mode"))]
["update theme" (dwtl/update-token-theme theme-id (ctob/rename (ctob/get-theme tokens-lib theme-id) "Dark"))]
["delete theme" (dwtl/delete-token-theme theme-id)]
["create set" (dwtl/create-token-set (ctob/make-token-set :name "New"))]
["rename set" (dwtl/rename-token-set (ctob/get-set tokens-lib set-id) "Renamed")]
["rename set group" (dwtl/rename-token-set-group ["Source"] "Renamed")]
["duplicate set" (dwtl/duplicate-token-set set-id)]
["delete set" (dwtl/delete-token-set set-id)]
["delete set group" (dwtl/delete-token-set-group ["Source"])]
["drop set group" (dwtl/drop-token-set-group {:from-index 0 :to-index 0 :position :top})]
["drop set" (dwtl/drop-token-set {:from-index 0 :to-index 0 :position :top})]
["import tokens lib" (dwtl/import-tokens-lib (ctob/make-tokens-lib))]
["create token" (dwtl/create-token set-id (ctob/make-token :name "color.new"
:type :color
:value "#00FF00"))]
["bulk create tokens" (dwtl/bulk-create-tokens set-id [token-id] :color {:depth 0} "brand")]
["update token" (dwtl/update-token set-id token-id {:value "#00FF00"})]
["bulk update tokens" (dwtl/bulk-update-tokens set-id [token-id] :color "color" "brand")]
["delete token" (dwtl/delete-token set-id token-id)]
["bulk delete tokens" (dwtl/bulk-delete-tokens set-id [token-id])]
["duplicate token" (dwtl/duplicate-token token-id)]]]
(t/testing label
(reset! errors [])
(ptk/emit! store event)
(await (async/wait-for #(seq @errors) label))
(t/is (= [{:type :assertion :code :tokens-not-editable}]
(map #(select-keys (ex-data %) [:type :code]) @errors)))))
(await (async/settle))
(t/is (= file (get-in @store [:files (:id file)])))
(t/is (= source (get-in @store [:files (:id source)])))))

View File

@ -28,5 +28,5 @@
"vite": "^8.2.0",
"vitest": "^5.0.2"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -3,7 +3,7 @@
"version": "1.2.0",
"license": "MPL-2.0",
"author": "KALEIDOS SUBSIDIARY SL",
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"type": "module",
"repository": {
"type": "git",

122
library/pnpm-lock.yaml generated
View File

@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.8.1
version: 12.8.1
specifier: 12.14.0
version: 12.14.0
packages:
'@pnpm/exe.android-arm64@12.8.1':
resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==}
'@pnpm/exe.android-arm64@12.14.0':
resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.8.1':
resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==}
'@pnpm/exe.android-x64@12.14.0':
resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.8.1':
resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==}
'@pnpm/exe.darwin-arm64@12.14.0':
resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.8.1':
resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==}
'@pnpm/exe.darwin-x64@12.14.0':
resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.freebsd-x64@12.8.1':
resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==}
'@pnpm/exe.freebsd-x64@12.14.0':
resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.8.1':
resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==}
'@pnpm/exe.linux-arm64-musl@12.14.0':
resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.8.1':
resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==}
'@pnpm/exe.linux-arm64@12.14.0':
resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-ppc64@12.8.1':
resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==}
'@pnpm/exe.linux-ppc64@12.14.0':
resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.8.1':
resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==}
'@pnpm/exe.linux-riscv64@12.14.0':
resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.8.1':
resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==}
'@pnpm/exe.linux-s390x@12.14.0':
resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.8.1':
resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==}
'@pnpm/exe.linux-x64-musl@12.14.0':
resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.8.1':
resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==}
'@pnpm/exe.linux-x64@12.14.0':
resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.8.1':
resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==}
'@pnpm/exe.win32-arm64@12.14.0':
resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.8.1':
resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==}
'@pnpm/exe.win32-x64@12.14.0':
resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==}
cpu: [x64]
os: [win32]
pnpm@12.8.1:
resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==}
pnpm@12.14.0:
resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.android-arm64@12.8.1':
'@pnpm/exe.android-arm64@12.14.0':
optional: true
'@pnpm/exe.android-x64@12.8.1':
'@pnpm/exe.android-x64@12.14.0':
optional: true
'@pnpm/exe.darwin-arm64@12.8.1':
'@pnpm/exe.darwin-arm64@12.14.0':
optional: true
'@pnpm/exe.darwin-x64@12.8.1':
'@pnpm/exe.darwin-x64@12.14.0':
optional: true
'@pnpm/exe.freebsd-x64@12.8.1':
'@pnpm/exe.freebsd-x64@12.14.0':
optional: true
'@pnpm/exe.linux-arm64-musl@12.8.1':
'@pnpm/exe.linux-arm64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-arm64@12.8.1':
'@pnpm/exe.linux-arm64@12.14.0':
optional: true
'@pnpm/exe.linux-ppc64@12.8.1':
'@pnpm/exe.linux-ppc64@12.14.0':
optional: true
'@pnpm/exe.linux-riscv64@12.8.1':
'@pnpm/exe.linux-riscv64@12.14.0':
optional: true
'@pnpm/exe.linux-s390x@12.8.1':
'@pnpm/exe.linux-s390x@12.14.0':
optional: true
'@pnpm/exe.linux-x64-musl@12.8.1':
'@pnpm/exe.linux-x64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-x64@12.8.1':
'@pnpm/exe.linux-x64@12.14.0':
optional: true
'@pnpm/exe.win32-arm64@12.8.1':
'@pnpm/exe.win32-arm64@12.14.0':
optional: true
'@pnpm/exe.win32-x64@12.8.1':
'@pnpm/exe.win32-x64@12.14.0':
optional: true
pnpm@12.8.1:
pnpm@12.14.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.8.1
'@pnpm/exe.android-x64': 12.8.1
'@pnpm/exe.darwin-arm64': 12.8.1
'@pnpm/exe.darwin-x64': 12.8.1
'@pnpm/exe.freebsd-x64': 12.8.1
'@pnpm/exe.linux-arm64': 12.8.1
'@pnpm/exe.linux-arm64-musl': 12.8.1
'@pnpm/exe.linux-ppc64': 12.8.1
'@pnpm/exe.linux-riscv64': 12.8.1
'@pnpm/exe.linux-s390x': 12.8.1
'@pnpm/exe.linux-x64': 12.8.1
'@pnpm/exe.linux-x64-musl': 12.8.1
'@pnpm/exe.win32-arm64': 12.8.1
'@pnpm/exe.win32-x64': 12.8.1
'@pnpm/exe.android-arm64': 12.14.0
'@pnpm/exe.android-x64': 12.14.0
'@pnpm/exe.darwin-arm64': 12.14.0
'@pnpm/exe.darwin-x64': 12.14.0
'@pnpm/exe.freebsd-x64': 12.14.0
'@pnpm/exe.linux-arm64': 12.14.0
'@pnpm/exe.linux-arm64-musl': 12.14.0
'@pnpm/exe.linux-ppc64': 12.14.0
'@pnpm/exe.linux-riscv64': 12.14.0
'@pnpm/exe.linux-s390x': 12.14.0
'@pnpm/exe.linux-x64': 12.14.0
'@pnpm/exe.linux-x64-musl': 12.14.0
'@pnpm/exe.win32-arm64': 12.14.0
'@pnpm/exe.win32-x64': 12.14.0
---
lockfileVersion: '9.0'

View File

@ -23,7 +23,7 @@
"type": "git",
"url": "https://github.com/penpot/penpot.git"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"devDependencies": {
"concurrently": "^10.0.5",
"prettier": "^3.9.9"

View File

@ -4,7 +4,7 @@
"description": "Shared type definitions and interfaces for Penpot MCP",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"scripts": {
"build": "tsc --build --clean && tsc --build",
"watch": "tsc --watch",

View File

@ -3,7 +3,7 @@
"private": true,
"version": "1.0.0",
"type": "module",
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"scripts": {
"start": "vite build --watch --config vite.config.ts",
"start:multi-user": "pnpm run start",

View File

@ -23033,10 +23033,12 @@ TokenCatalog:
readonly isEditableTokens: boolean
```
Returns true if the tokens can be edited (it can't
if the tokens of the file are in an external library).
Note that even in this case, Themes and Sets status still
can be changed and tokens can be applied to shapes.
Returns true if the tokens in this catalog can be edited. Only the
catalog of the current file (`penpot.library.local.tokens`) can be
edited, and only when the file is its own tokens source.
When the tokens come from an external library, the local catalog
can still change the active themes and sets, and its tokens can
still be applied to shapes.
themes: |-
```
readonly themes: TokenTheme[]
@ -23171,7 +23173,8 @@ TokenSet:
active: boolean
```
Indicates if the set is currently active.
Indicates if the set is active in the file that owns this catalog.
Only sets from `penpot.library.local.tokens` can be changed.
tokens: |-
```
readonly tokens: Token[]
@ -23191,6 +23194,7 @@ TokenSet:
```
Toggles the active status of this set.
Only sets from `penpot.library.local.tokens` can be changed.
Returns void
getTokenById: |-
@ -23314,7 +23318,8 @@ TokenTheme:
active: boolean
```
Indicates if the theme is currently active.
Indicates if the theme is active in the file that owns this catalog.
Only themes from `penpot.library.local.tokens` can be changed.
activeSets: |-
```
readonly activeSets: TokenSet[]
@ -23328,6 +23333,7 @@ TokenTheme:
```
Toggles the active status of this theme.
Only themes from `penpot.library.local.tokens` can be changed.
Returns void
addSet: |-
@ -23904,6 +23910,7 @@ LibraryContext:
type LibraryContext = {
local: Library;
connected: Library[];
tokensSource: Library;
availableLibraries(): Promise<LibrarySummary[]>;
connectLibrary(libraryId: string): Promise<Library>;
}
@ -23934,6 +23941,33 @@ LibraryContext:
```
const connectedLibraries = libraryContext.connected;
```
tokensSource: |-
```
tokensSource: Library
```
The library that supplies tokens to the current file.
Defaults to the local library.
Assign a connected library that has its own tokens to use it as the
source. This copies its active themes and sets to the current file.
Assign `local` to use the file's own tokens again. Themes and sets
that don't exist in the local tokens are deactivated.
Setting the source requires `content:write` permission and supports undo.
Assigning the current source again keeps the active themes and sets.
The catalog of an external `tokensSource` is read-only and reports the
library's own active themes and sets. To change which ones are active
in the current file, use `penpot.library.local.tokens`, which lists the
tokens of the source.
Example
```
const source = await penpot.library.connectLibrary('library-id');
penpot.library.tokensSource = source;
// Restore local tokens:
penpot.library.tokensSource = penpot.library.local;
```
Methods:
availableLibraries: |-
```

View File

@ -27,7 +27,7 @@
"engines": {
"node": ">=20"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"dependencies": {
"@modelcontextprotocol/node": "^2.1.1",
"@modelcontextprotocol/server": "^2.3.1",

122
mcp/pnpm-lock.yaml generated
View File

@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.8.1
version: 12.8.1
specifier: 12.14.0
version: 12.14.0
packages:
'@pnpm/exe.android-arm64@12.8.1':
resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==}
'@pnpm/exe.android-arm64@12.14.0':
resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.8.1':
resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==}
'@pnpm/exe.android-x64@12.14.0':
resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.8.1':
resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==}
'@pnpm/exe.darwin-arm64@12.14.0':
resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.8.1':
resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==}
'@pnpm/exe.darwin-x64@12.14.0':
resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.freebsd-x64@12.8.1':
resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==}
'@pnpm/exe.freebsd-x64@12.14.0':
resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.8.1':
resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==}
'@pnpm/exe.linux-arm64-musl@12.14.0':
resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.8.1':
resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==}
'@pnpm/exe.linux-arm64@12.14.0':
resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-ppc64@12.8.1':
resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==}
'@pnpm/exe.linux-ppc64@12.14.0':
resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.8.1':
resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==}
'@pnpm/exe.linux-riscv64@12.14.0':
resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.8.1':
resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==}
'@pnpm/exe.linux-s390x@12.14.0':
resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.8.1':
resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==}
'@pnpm/exe.linux-x64-musl@12.14.0':
resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.8.1':
resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==}
'@pnpm/exe.linux-x64@12.14.0':
resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.8.1':
resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==}
'@pnpm/exe.win32-arm64@12.14.0':
resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.8.1':
resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==}
'@pnpm/exe.win32-x64@12.14.0':
resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==}
cpu: [x64]
os: [win32]
pnpm@12.8.1:
resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==}
pnpm@12.14.0:
resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.android-arm64@12.8.1':
'@pnpm/exe.android-arm64@12.14.0':
optional: true
'@pnpm/exe.android-x64@12.8.1':
'@pnpm/exe.android-x64@12.14.0':
optional: true
'@pnpm/exe.darwin-arm64@12.8.1':
'@pnpm/exe.darwin-arm64@12.14.0':
optional: true
'@pnpm/exe.darwin-x64@12.8.1':
'@pnpm/exe.darwin-x64@12.14.0':
optional: true
'@pnpm/exe.freebsd-x64@12.8.1':
'@pnpm/exe.freebsd-x64@12.14.0':
optional: true
'@pnpm/exe.linux-arm64-musl@12.8.1':
'@pnpm/exe.linux-arm64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-arm64@12.8.1':
'@pnpm/exe.linux-arm64@12.14.0':
optional: true
'@pnpm/exe.linux-ppc64@12.8.1':
'@pnpm/exe.linux-ppc64@12.14.0':
optional: true
'@pnpm/exe.linux-riscv64@12.8.1':
'@pnpm/exe.linux-riscv64@12.14.0':
optional: true
'@pnpm/exe.linux-s390x@12.8.1':
'@pnpm/exe.linux-s390x@12.14.0':
optional: true
'@pnpm/exe.linux-x64-musl@12.8.1':
'@pnpm/exe.linux-x64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-x64@12.8.1':
'@pnpm/exe.linux-x64@12.14.0':
optional: true
'@pnpm/exe.win32-arm64@12.8.1':
'@pnpm/exe.win32-arm64@12.14.0':
optional: true
'@pnpm/exe.win32-x64@12.8.1':
'@pnpm/exe.win32-x64@12.14.0':
optional: true
pnpm@12.8.1:
pnpm@12.14.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.8.1
'@pnpm/exe.android-x64': 12.8.1
'@pnpm/exe.darwin-arm64': 12.8.1
'@pnpm/exe.darwin-x64': 12.8.1
'@pnpm/exe.freebsd-x64': 12.8.1
'@pnpm/exe.linux-arm64': 12.8.1
'@pnpm/exe.linux-arm64-musl': 12.8.1
'@pnpm/exe.linux-ppc64': 12.8.1
'@pnpm/exe.linux-riscv64': 12.8.1
'@pnpm/exe.linux-s390x': 12.8.1
'@pnpm/exe.linux-x64': 12.8.1
'@pnpm/exe.linux-x64-musl': 12.8.1
'@pnpm/exe.win32-arm64': 12.8.1
'@pnpm/exe.win32-x64': 12.8.1
'@pnpm/exe.android-arm64': 12.14.0
'@pnpm/exe.android-x64': 12.14.0
'@pnpm/exe.darwin-arm64': 12.14.0
'@pnpm/exe.darwin-x64': 12.14.0
'@pnpm/exe.freebsd-x64': 12.14.0
'@pnpm/exe.linux-arm64': 12.14.0
'@pnpm/exe.linux-arm64-musl': 12.14.0
'@pnpm/exe.linux-ppc64': 12.14.0
'@pnpm/exe.linux-riscv64': 12.14.0
'@pnpm/exe.linux-s390x': 12.14.0
'@pnpm/exe.linux-x64': 12.14.0
'@pnpm/exe.linux-x64-musl': 12.14.0
'@pnpm/exe.win32-arm64': 12.14.0
'@pnpm/exe.win32-x64': 12.14.0
---
lockfileVersion: '9.0'

View File

@ -63,6 +63,8 @@ class PenpotAPIContentMarkdownConverter(MarkdownConverter):
if node.name == "pre":
for button in soup.find_all("button"):
button.decompose()
for br in soup.find_all("br"):
br.replace_with("\n")
return f"\n```\n{soup.get_text().strip()}\n```\n\n"
# convert tsd-signature elements to code blocks, converting <br> to newlines

View File

@ -0,0 +1,31 @@
import unittest
from prepare_api_docs import PenpotAPIContentMarkdownConverter
class PenpotAPIContentMarkdownConverterTest(unittest.TestCase):
def test_preserves_line_breaks_in_code_examples(self):
html = (
'<pre><code>'
'const source = await penpot.library.connectLibrary("library-id");<br>'
'penpot.library.tokensSource = source;<br>'
'// Restore local tokens:<br>'
'penpot.library.tokensSource = penpot.library.local;'
'</code><button>Copy</button></pre>'
)
result = PenpotAPIContentMarkdownConverter().convert(html).strip()
self.assertEqual(
result,
'```\n'
'const source = await penpot.library.connectLibrary("library-id");\n'
'penpot.library.tokensSource = source;\n'
'// Restore local tokens:\n'
'penpot.library.tokensSource = penpot.library.local;\n'
'```',
)
if __name__ == '__main__':
unittest.main()

View File

@ -14,7 +14,7 @@
"fmt:check": "prettier --check src/ test/",
"clean": "rm -rf dist/"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"dependencies": {
"express": "^5.2.1",
"multer": "^2.4.0",

View File

@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.8.1
version: 12.8.1
specifier: 12.14.0
version: 12.14.0
packages:
'@pnpm/exe.android-arm64@12.8.1':
resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==}
'@pnpm/exe.android-arm64@12.14.0':
resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.8.1':
resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==}
'@pnpm/exe.android-x64@12.14.0':
resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.8.1':
resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==}
'@pnpm/exe.darwin-arm64@12.14.0':
resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.8.1':
resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==}
'@pnpm/exe.darwin-x64@12.14.0':
resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.freebsd-x64@12.8.1':
resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==}
'@pnpm/exe.freebsd-x64@12.14.0':
resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.8.1':
resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==}
'@pnpm/exe.linux-arm64-musl@12.14.0':
resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.8.1':
resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==}
'@pnpm/exe.linux-arm64@12.14.0':
resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-ppc64@12.8.1':
resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==}
'@pnpm/exe.linux-ppc64@12.14.0':
resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.8.1':
resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==}
'@pnpm/exe.linux-riscv64@12.14.0':
resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.8.1':
resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==}
'@pnpm/exe.linux-s390x@12.14.0':
resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.8.1':
resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==}
'@pnpm/exe.linux-x64-musl@12.14.0':
resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.8.1':
resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==}
'@pnpm/exe.linux-x64@12.14.0':
resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.8.1':
resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==}
'@pnpm/exe.win32-arm64@12.14.0':
resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.8.1':
resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==}
'@pnpm/exe.win32-x64@12.14.0':
resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==}
cpu: [x64]
os: [win32]
pnpm@12.8.1:
resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==}
pnpm@12.14.0:
resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.android-arm64@12.8.1':
'@pnpm/exe.android-arm64@12.14.0':
optional: true
'@pnpm/exe.android-x64@12.8.1':
'@pnpm/exe.android-x64@12.14.0':
optional: true
'@pnpm/exe.darwin-arm64@12.8.1':
'@pnpm/exe.darwin-arm64@12.14.0':
optional: true
'@pnpm/exe.darwin-x64@12.8.1':
'@pnpm/exe.darwin-x64@12.14.0':
optional: true
'@pnpm/exe.freebsd-x64@12.8.1':
'@pnpm/exe.freebsd-x64@12.14.0':
optional: true
'@pnpm/exe.linux-arm64-musl@12.8.1':
'@pnpm/exe.linux-arm64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-arm64@12.8.1':
'@pnpm/exe.linux-arm64@12.14.0':
optional: true
'@pnpm/exe.linux-ppc64@12.8.1':
'@pnpm/exe.linux-ppc64@12.14.0':
optional: true
'@pnpm/exe.linux-riscv64@12.8.1':
'@pnpm/exe.linux-riscv64@12.14.0':
optional: true
'@pnpm/exe.linux-s390x@12.8.1':
'@pnpm/exe.linux-s390x@12.14.0':
optional: true
'@pnpm/exe.linux-x64-musl@12.8.1':
'@pnpm/exe.linux-x64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-x64@12.8.1':
'@pnpm/exe.linux-x64@12.14.0':
optional: true
'@pnpm/exe.win32-arm64@12.8.1':
'@pnpm/exe.win32-arm64@12.14.0':
optional: true
'@pnpm/exe.win32-x64@12.8.1':
'@pnpm/exe.win32-x64@12.14.0':
optional: true
pnpm@12.8.1:
pnpm@12.14.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.8.1
'@pnpm/exe.android-x64': 12.8.1
'@pnpm/exe.darwin-arm64': 12.8.1
'@pnpm/exe.darwin-x64': 12.8.1
'@pnpm/exe.freebsd-x64': 12.8.1
'@pnpm/exe.linux-arm64': 12.8.1
'@pnpm/exe.linux-arm64-musl': 12.8.1
'@pnpm/exe.linux-ppc64': 12.8.1
'@pnpm/exe.linux-riscv64': 12.8.1
'@pnpm/exe.linux-s390x': 12.8.1
'@pnpm/exe.linux-x64': 12.8.1
'@pnpm/exe.linux-x64-musl': 12.8.1
'@pnpm/exe.win32-arm64': 12.8.1
'@pnpm/exe.win32-x64': 12.8.1
'@pnpm/exe.android-arm64': 12.14.0
'@pnpm/exe.android-x64': 12.14.0
'@pnpm/exe.darwin-arm64': 12.14.0
'@pnpm/exe.darwin-x64': 12.14.0
'@pnpm/exe.freebsd-x64': 12.14.0
'@pnpm/exe.linux-arm64': 12.14.0
'@pnpm/exe.linux-arm64-musl': 12.14.0
'@pnpm/exe.linux-ppc64': 12.14.0
'@pnpm/exe.linux-riscv64': 12.14.0
'@pnpm/exe.linux-s390x': 12.14.0
'@pnpm/exe.linux-x64': 12.14.0
'@pnpm/exe.linux-x64-musl': 12.14.0
'@pnpm/exe.win32-arm64': 12.14.0
'@pnpm/exe.win32-x64': 12.14.0
---
lockfileVersion: '9.0'

View File

@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC Sucursal en España SL",
"private": true,
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"

View File

@ -6,6 +6,7 @@
- **plugins-runtime, plugin-types**: Added global plugins (`scope: "global"`) that remain active across dashboard and workspace navigation until closed or logged out, with `penpotMgmt` for project and file discovery, file navigation and workspace events. Global scope adds the `allow:global` permission, which users must accept when installing the plugin.
- **plugin-types:** Added `LibraryComponent.restore()` to restore a deleted component and its main instance without changing the current page, selection, zoom, or viewport. Requires `library:write` permission.
- **plugin-types:** Added `Shape.updateMain()`, `LibraryComponent.duplicate()`, writable component `annotation`, and `Library.deletedComponents`. Declared the existing `LibraryComponent.remove()` method.
- **plugin-types:** Added `LibraryContext.tokensSource` to read or set the current file's token source to a connected library, or restore local tokens.
- **plugin-types:** Added `paddingType` (`'simple' | 'multiple'`) to flex and grid layouts and `marginType` (`'simple' | 'multiple'`) to layout children, exposing whether the four padding/margin sides are mirrored or honoured independently.
- **plugin-types**: Added `waitForLayoutUpdate` to wait until pending layout updates have finished. It rejects when the optional timeout elapses, defaulting to 30 seconds so a wait never hangs.
- **plugin-types**: Added `waitForLayoutUpdate` to the `Shape` interface to wait until the pending layout updates of a shape and its children have finished

View File

@ -13,5 +13,5 @@
"lint": "eslint .",
"test": "vitest"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -27,5 +27,5 @@
"vite": "^8.3.2",
"vite-live-preview": "^0.4.0"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -13,5 +13,5 @@
"lint": "eslint .",
"test": "vitest"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -12,5 +12,5 @@
"lint": "eslint .",
"test": "vitest"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -7,5 +7,5 @@
"test": "vitest",
"lint": "eslint ."
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -11,5 +11,5 @@
"serve": "vite preview",
"lint": "eslint ."
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -13,5 +13,5 @@
"lint": "eslint .",
"test": "vitest"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -13,5 +13,5 @@
"lint": "eslint .",
"test": "vitest"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -20,5 +20,5 @@
"devDependencies": {
"playwright": "^1.63.0"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -303,7 +303,8 @@
"availableLibraries",
"connectLibrary",
"connected",
"local"
"local",
"tokensSource"
],
"LibraryElement": ["id", "libraryId", "name", "path"],
"LibrarySummary": [
@ -5383,6 +5384,12 @@
"type": "Library",
"array": true
},
"tokensSource": {
"decl": "LibraryContext",
"kind": "getset",
"type": "Library",
"array": false
},
"availableLibraries": {
"decl": "LibraryContext",
"kind": "method",

View File

@ -13,5 +13,5 @@
"lint": "eslint .",
"test": "vitest"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -13,5 +13,5 @@
"lint": "eslint .",
"test": "exit 0"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -13,5 +13,5 @@
"lint": "eslint .",
"test": "vitest"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -13,5 +13,5 @@
"lint": "eslint .",
"test": "vitest"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -3161,6 +3161,32 @@ export type LibraryContext = {
*/
readonly connected: Library[];
/**
* The library that supplies tokens to the current file.
* Defaults to the local library.
*
* Assign a connected library that has its own tokens to use it as the
* source. This copies its active themes and sets to the current file.
* Assign `local` to use the file's own tokens again. Themes and sets
* that don't exist in the local tokens are deactivated.
* Setting the source requires `content:write` permission and supports undo.
* Assigning the current source again keeps the active themes and sets.
*
* The catalog of an external `tokensSource` is read-only and reports the
* library's own active themes and sets. To change which ones are active
* in the current file, use `penpot.library.local.tokens`, which lists the
* tokens of the source.
*
* @example
* ```js
* const source = await penpot.library.connectLibrary('library-id');
* penpot.library.tokensSource = source;
* // Restore local tokens:
* penpot.library.tokensSource = penpot.library.local;
* ```
*/
tokensSource: Library;
/**
* Retrieves a summary of available libraries that can be connected to.
* @return Returns a promise that resolves to an array of `LibrarySummary` objects representing available libraries.
@ -5562,10 +5588,12 @@ export type Token =
*/
export interface TokenCatalog {
/**
* Returns true if the tokens can be edited (it can't
* if the tokens of the file are in an external library).
* Note that even in this case, Themes and Sets status still
* can be changed and tokens can be applied to shapes.
* Returns true if the tokens in this catalog can be edited. Only the
* catalog of the current file (`penpot.library.local.tokens`) can be
* edited, and only when the file is its own tokens source.
* When the tokens come from an external library, the local catalog
* can still change the active themes and sets, and its tokens can
* still be applied to shapes.
*/
readonly isEditableTokens: boolean;
@ -5639,7 +5667,8 @@ export interface TokenSet {
name: string;
/**
* Indicates if the set is currently active.
* Indicates if the set is active in the file that owns this catalog.
* Only sets from `penpot.library.local.tokens` can be changed.
*/
active: boolean;
@ -5655,6 +5684,7 @@ export interface TokenSet {
/**
* Toggles the active status of this set.
* Only sets from `penpot.library.local.tokens` can be changed.
*/
toggleActive(): void;
@ -5739,12 +5769,14 @@ export interface TokenTheme {
name: string;
/**
* Indicates if the theme is currently active.
* Indicates if the theme is active in the file that owns this catalog.
* Only themes from `penpot.library.local.tokens` can be changed.
*/
active: boolean;
/**
* Toggles the active status of this theme.
* Only themes from `penpot.library.local.tokens` can be changed.
*/
toggleActive(): void;

View File

@ -7,5 +7,5 @@
"build": "node ../../tools/scripts/build-types.mjs",
"lint": "tsc -p . --noEmit"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -16,5 +16,5 @@
"lint": "eslint .",
"test": "vitest"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -6,5 +6,5 @@
"build": "node ../../tools/scripts/build-css.mjs",
"lint": "echo 0"
},
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45"
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6"
}

View File

@ -3,7 +3,7 @@
"version": "0.6.0",
"type": "module",
"license": "MIT",
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"scripts": {
"start": "pnpm run start:app:runtime",
"start:app:runtime": "concurrently --kill-others --names build,server \"pnpm --filter @penpot/plugins-runtime run build:watch\" \"pnpm --filter @penpot/plugins-runtime run preview\"",

122
plugins/pnpm-lock.yaml generated
View File

@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.8.1
version: 12.8.1
specifier: 12.14.0
version: 12.14.0
packages:
'@pnpm/exe.android-arm64@12.8.1':
resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==}
'@pnpm/exe.android-arm64@12.14.0':
resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.8.1':
resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==}
'@pnpm/exe.android-x64@12.14.0':
resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.8.1':
resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==}
'@pnpm/exe.darwin-arm64@12.14.0':
resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.8.1':
resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==}
'@pnpm/exe.darwin-x64@12.14.0':
resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.freebsd-x64@12.8.1':
resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==}
'@pnpm/exe.freebsd-x64@12.14.0':
resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.8.1':
resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==}
'@pnpm/exe.linux-arm64-musl@12.14.0':
resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.8.1':
resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==}
'@pnpm/exe.linux-arm64@12.14.0':
resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-ppc64@12.8.1':
resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==}
'@pnpm/exe.linux-ppc64@12.14.0':
resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.8.1':
resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==}
'@pnpm/exe.linux-riscv64@12.14.0':
resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.8.1':
resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==}
'@pnpm/exe.linux-s390x@12.14.0':
resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.8.1':
resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==}
'@pnpm/exe.linux-x64-musl@12.14.0':
resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.8.1':
resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==}
'@pnpm/exe.linux-x64@12.14.0':
resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.8.1':
resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==}
'@pnpm/exe.win32-arm64@12.14.0':
resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.8.1':
resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==}
'@pnpm/exe.win32-x64@12.14.0':
resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==}
cpu: [x64]
os: [win32]
pnpm@12.8.1:
resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==}
pnpm@12.14.0:
resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.android-arm64@12.8.1':
'@pnpm/exe.android-arm64@12.14.0':
optional: true
'@pnpm/exe.android-x64@12.8.1':
'@pnpm/exe.android-x64@12.14.0':
optional: true
'@pnpm/exe.darwin-arm64@12.8.1':
'@pnpm/exe.darwin-arm64@12.14.0':
optional: true
'@pnpm/exe.darwin-x64@12.8.1':
'@pnpm/exe.darwin-x64@12.14.0':
optional: true
'@pnpm/exe.freebsd-x64@12.8.1':
'@pnpm/exe.freebsd-x64@12.14.0':
optional: true
'@pnpm/exe.linux-arm64-musl@12.8.1':
'@pnpm/exe.linux-arm64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-arm64@12.8.1':
'@pnpm/exe.linux-arm64@12.14.0':
optional: true
'@pnpm/exe.linux-ppc64@12.8.1':
'@pnpm/exe.linux-ppc64@12.14.0':
optional: true
'@pnpm/exe.linux-riscv64@12.8.1':
'@pnpm/exe.linux-riscv64@12.14.0':
optional: true
'@pnpm/exe.linux-s390x@12.8.1':
'@pnpm/exe.linux-s390x@12.14.0':
optional: true
'@pnpm/exe.linux-x64-musl@12.8.1':
'@pnpm/exe.linux-x64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-x64@12.8.1':
'@pnpm/exe.linux-x64@12.14.0':
optional: true
'@pnpm/exe.win32-arm64@12.8.1':
'@pnpm/exe.win32-arm64@12.14.0':
optional: true
'@pnpm/exe.win32-x64@12.8.1':
'@pnpm/exe.win32-x64@12.14.0':
optional: true
pnpm@12.8.1:
pnpm@12.14.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.8.1
'@pnpm/exe.android-x64': 12.8.1
'@pnpm/exe.darwin-arm64': 12.8.1
'@pnpm/exe.darwin-x64': 12.8.1
'@pnpm/exe.freebsd-x64': 12.8.1
'@pnpm/exe.linux-arm64': 12.8.1
'@pnpm/exe.linux-arm64-musl': 12.8.1
'@pnpm/exe.linux-ppc64': 12.8.1
'@pnpm/exe.linux-riscv64': 12.8.1
'@pnpm/exe.linux-s390x': 12.8.1
'@pnpm/exe.linux-x64': 12.8.1
'@pnpm/exe.linux-x64-musl': 12.8.1
'@pnpm/exe.win32-arm64': 12.8.1
'@pnpm/exe.win32-x64': 12.8.1
'@pnpm/exe.android-arm64': 12.14.0
'@pnpm/exe.android-x64': 12.14.0
'@pnpm/exe.darwin-arm64': 12.14.0
'@pnpm/exe.darwin-x64': 12.14.0
'@pnpm/exe.freebsd-x64': 12.14.0
'@pnpm/exe.linux-arm64': 12.14.0
'@pnpm/exe.linux-arm64-musl': 12.14.0
'@pnpm/exe.linux-ppc64': 12.14.0
'@pnpm/exe.linux-riscv64': 12.14.0
'@pnpm/exe.linux-s390x': 12.14.0
'@pnpm/exe.linux-x64': 12.14.0
'@pnpm/exe.linux-x64-musl': 12.14.0
'@pnpm/exe.win32-arm64': 12.14.0
'@pnpm/exe.win32-x64': 12.14.0
---
lockfileVersion: '9.0'

122
pnpm-lock.yaml generated
View File

@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.8.1
version: 12.8.1
specifier: 12.14.0
version: 12.14.0
packages:
'@pnpm/exe.android-arm64@12.8.1':
resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==}
'@pnpm/exe.android-arm64@12.14.0':
resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.8.1':
resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==}
'@pnpm/exe.android-x64@12.14.0':
resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.8.1':
resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==}
'@pnpm/exe.darwin-arm64@12.14.0':
resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.8.1':
resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==}
'@pnpm/exe.darwin-x64@12.14.0':
resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.freebsd-x64@12.8.1':
resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==}
'@pnpm/exe.freebsd-x64@12.14.0':
resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.8.1':
resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==}
'@pnpm/exe.linux-arm64-musl@12.14.0':
resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.8.1':
resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==}
'@pnpm/exe.linux-arm64@12.14.0':
resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-ppc64@12.8.1':
resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==}
'@pnpm/exe.linux-ppc64@12.14.0':
resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.8.1':
resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==}
'@pnpm/exe.linux-riscv64@12.14.0':
resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.8.1':
resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==}
'@pnpm/exe.linux-s390x@12.14.0':
resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.8.1':
resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==}
'@pnpm/exe.linux-x64-musl@12.14.0':
resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.8.1':
resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==}
'@pnpm/exe.linux-x64@12.14.0':
resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.8.1':
resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==}
'@pnpm/exe.win32-arm64@12.14.0':
resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.8.1':
resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==}
'@pnpm/exe.win32-x64@12.14.0':
resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==}
cpu: [x64]
os: [win32]
pnpm@12.8.1:
resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==}
pnpm@12.14.0:
resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.android-arm64@12.8.1':
'@pnpm/exe.android-arm64@12.14.0':
optional: true
'@pnpm/exe.android-x64@12.8.1':
'@pnpm/exe.android-x64@12.14.0':
optional: true
'@pnpm/exe.darwin-arm64@12.8.1':
'@pnpm/exe.darwin-arm64@12.14.0':
optional: true
'@pnpm/exe.darwin-x64@12.8.1':
'@pnpm/exe.darwin-x64@12.14.0':
optional: true
'@pnpm/exe.freebsd-x64@12.8.1':
'@pnpm/exe.freebsd-x64@12.14.0':
optional: true
'@pnpm/exe.linux-arm64-musl@12.8.1':
'@pnpm/exe.linux-arm64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-arm64@12.8.1':
'@pnpm/exe.linux-arm64@12.14.0':
optional: true
'@pnpm/exe.linux-ppc64@12.8.1':
'@pnpm/exe.linux-ppc64@12.14.0':
optional: true
'@pnpm/exe.linux-riscv64@12.8.1':
'@pnpm/exe.linux-riscv64@12.14.0':
optional: true
'@pnpm/exe.linux-s390x@12.8.1':
'@pnpm/exe.linux-s390x@12.14.0':
optional: true
'@pnpm/exe.linux-x64-musl@12.8.1':
'@pnpm/exe.linux-x64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-x64@12.8.1':
'@pnpm/exe.linux-x64@12.14.0':
optional: true
'@pnpm/exe.win32-arm64@12.8.1':
'@pnpm/exe.win32-arm64@12.14.0':
optional: true
'@pnpm/exe.win32-x64@12.8.1':
'@pnpm/exe.win32-x64@12.14.0':
optional: true
pnpm@12.8.1:
pnpm@12.14.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.8.1
'@pnpm/exe.android-x64': 12.8.1
'@pnpm/exe.darwin-arm64': 12.8.1
'@pnpm/exe.darwin-x64': 12.8.1
'@pnpm/exe.freebsd-x64': 12.8.1
'@pnpm/exe.linux-arm64': 12.8.1
'@pnpm/exe.linux-arm64-musl': 12.8.1
'@pnpm/exe.linux-ppc64': 12.8.1
'@pnpm/exe.linux-riscv64': 12.8.1
'@pnpm/exe.linux-s390x': 12.8.1
'@pnpm/exe.linux-x64': 12.8.1
'@pnpm/exe.linux-x64-musl': 12.8.1
'@pnpm/exe.win32-arm64': 12.8.1
'@pnpm/exe.win32-x64': 12.8.1
'@pnpm/exe.android-arm64': 12.14.0
'@pnpm/exe.android-x64': 12.14.0
'@pnpm/exe.darwin-arm64': 12.14.0
'@pnpm/exe.darwin-x64': 12.14.0
'@pnpm/exe.freebsd-x64': 12.14.0
'@pnpm/exe.linux-arm64': 12.14.0
'@pnpm/exe.linux-arm64-musl': 12.14.0
'@pnpm/exe.linux-ppc64': 12.14.0
'@pnpm/exe.linux-riscv64': 12.14.0
'@pnpm/exe.linux-s390x': 12.14.0
'@pnpm/exe.linux-x64': 12.14.0
'@pnpm/exe.linux-x64-musl': 12.14.0
'@pnpm/exe.win32-arm64': 12.14.0
'@pnpm/exe.win32-x64': 12.14.0
---
lockfileVersion: '9.0'

View File

@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC Sucursal en España SL",
"private": true,
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"

View File

@ -7,153 +7,153 @@ importers:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.8.1
version: 12.8.1
specifier: 12.14.0
version: 12.14.0
packages:
'@pnpm/exe.android-arm64@12.8.1':
resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==}
'@pnpm/exe.android-arm64@12.14.0':
resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==}
cpu: [arm64]
os: [android]
'@pnpm/exe.android-x64@12.8.1':
resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==}
'@pnpm/exe.android-x64@12.14.0':
resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==}
cpu: [x64]
os: [android]
'@pnpm/exe.darwin-arm64@12.8.1':
resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==}
'@pnpm/exe.darwin-arm64@12.14.0':
resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==}
cpu: [arm64]
os: [darwin]
'@pnpm/exe.darwin-x64@12.8.1':
resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==}
'@pnpm/exe.darwin-x64@12.14.0':
resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==}
cpu: [x64]
os: [darwin]
'@pnpm/exe.freebsd-x64@12.8.1':
resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==}
'@pnpm/exe.freebsd-x64@12.14.0':
resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==}
cpu: [x64]
os: [freebsd]
'@pnpm/exe.linux-arm64-musl@12.8.1':
resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==}
'@pnpm/exe.linux-arm64-musl@12.14.0':
resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-arm64@12.8.1':
resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==}
'@pnpm/exe.linux-arm64@12.14.0':
resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-ppc64@12.8.1':
resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==}
'@pnpm/exe.linux-ppc64@12.14.0':
resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-riscv64@12.8.1':
resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==}
'@pnpm/exe.linux-riscv64@12.14.0':
resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-s390x@12.8.1':
resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==}
'@pnpm/exe.linux-s390x@12.14.0':
resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/exe.linux-x64-musl@12.8.1':
resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==}
'@pnpm/exe.linux-x64-musl@12.14.0':
resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/exe.linux-x64@12.8.1':
resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==}
'@pnpm/exe.linux-x64@12.14.0':
resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/exe.win32-arm64@12.8.1':
resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==}
'@pnpm/exe.win32-arm64@12.14.0':
resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==}
cpu: [arm64]
os: [win32]
'@pnpm/exe.win32-x64@12.8.1':
resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==}
'@pnpm/exe.win32-x64@12.14.0':
resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==}
cpu: [x64]
os: [win32]
pnpm@12.8.1:
resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==}
pnpm@12.14.0:
resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/exe.android-arm64@12.8.1':
'@pnpm/exe.android-arm64@12.14.0':
optional: true
'@pnpm/exe.android-x64@12.8.1':
'@pnpm/exe.android-x64@12.14.0':
optional: true
'@pnpm/exe.darwin-arm64@12.8.1':
'@pnpm/exe.darwin-arm64@12.14.0':
optional: true
'@pnpm/exe.darwin-x64@12.8.1':
'@pnpm/exe.darwin-x64@12.14.0':
optional: true
'@pnpm/exe.freebsd-x64@12.8.1':
'@pnpm/exe.freebsd-x64@12.14.0':
optional: true
'@pnpm/exe.linux-arm64-musl@12.8.1':
'@pnpm/exe.linux-arm64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-arm64@12.8.1':
'@pnpm/exe.linux-arm64@12.14.0':
optional: true
'@pnpm/exe.linux-ppc64@12.8.1':
'@pnpm/exe.linux-ppc64@12.14.0':
optional: true
'@pnpm/exe.linux-riscv64@12.8.1':
'@pnpm/exe.linux-riscv64@12.14.0':
optional: true
'@pnpm/exe.linux-s390x@12.8.1':
'@pnpm/exe.linux-s390x@12.14.0':
optional: true
'@pnpm/exe.linux-x64-musl@12.8.1':
'@pnpm/exe.linux-x64-musl@12.14.0':
optional: true
'@pnpm/exe.linux-x64@12.8.1':
'@pnpm/exe.linux-x64@12.14.0':
optional: true
'@pnpm/exe.win32-arm64@12.8.1':
'@pnpm/exe.win32-arm64@12.14.0':
optional: true
'@pnpm/exe.win32-x64@12.8.1':
'@pnpm/exe.win32-x64@12.14.0':
optional: true
pnpm@12.8.1:
pnpm@12.14.0:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.8.1
'@pnpm/exe.android-x64': 12.8.1
'@pnpm/exe.darwin-arm64': 12.8.1
'@pnpm/exe.darwin-x64': 12.8.1
'@pnpm/exe.freebsd-x64': 12.8.1
'@pnpm/exe.linux-arm64': 12.8.1
'@pnpm/exe.linux-arm64-musl': 12.8.1
'@pnpm/exe.linux-ppc64': 12.8.1
'@pnpm/exe.linux-riscv64': 12.8.1
'@pnpm/exe.linux-s390x': 12.8.1
'@pnpm/exe.linux-x64': 12.8.1
'@pnpm/exe.linux-x64-musl': 12.8.1
'@pnpm/exe.win32-arm64': 12.8.1
'@pnpm/exe.win32-x64': 12.8.1
'@pnpm/exe.android-arm64': 12.14.0
'@pnpm/exe.android-x64': 12.14.0
'@pnpm/exe.darwin-arm64': 12.14.0
'@pnpm/exe.darwin-x64': 12.14.0
'@pnpm/exe.freebsd-x64': 12.14.0
'@pnpm/exe.linux-arm64': 12.14.0
'@pnpm/exe.linux-arm64-musl': 12.14.0
'@pnpm/exe.linux-ppc64': 12.14.0
'@pnpm/exe.linux-riscv64': 12.14.0
'@pnpm/exe.linux-s390x': 12.14.0
'@pnpm/exe.linux-x64': 12.14.0
'@pnpm/exe.linux-x64-musl': 12.14.0
'@pnpm/exe.win32-arm64': 12.14.0
'@pnpm/exe.win32-x64': 12.14.0
---
lockfileVersion: '9.0'

View File

@ -416,6 +416,7 @@ impl Shape {
pub fn set_clip(&mut self, value: bool) {
self.clip_content = value;
self.invalidate_extrect();
}
pub fn set_rotation(&mut self, angle: f32) {

35
scripts/setup-opencode Executable file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Install the opencode CLI as a global pnpm package.
#
# opencode is not part of the devenv image: run this when you want the
# `opencode` and `opencode2` commands. The install lives in $PNPM_HOME, which
# inside the devenv is the penpot home (the user-data volume), so it survives
# a container re-creation.
#
# Usage: scripts/setup-opencode
set -euo pipefail
if ! command -v pnpm >/dev/null 2>&1; then
echo "setup-opencode: pnpm is not on PATH." >&2
exit 1
fi
# pnpm installs global packages under $PNPM_HOME and refuses to run when its
# bin dir is not on PATH: without this the install aborts with
# ERR_PNPM_GLOBAL_BIN_DIR_NOT_IN_PATH. Non-interactive shells do not read
# ~/.bashrc, so both are set here. The default is the directory pnpm itself
# uses on Linux.
export PNPM_HOME="${PNPM_HOME:-$HOME/.local/share/pnpm}"
path_hint=0
case ":$PATH:" in
*":$PNPM_HOME/bin:"*) ;;
*) path_hint=1; export PATH="$PNPM_HOME/bin:$PATH" ;;
esac
pnpm add -g --allow-build=@opencode/cli @opencode/cli
opencode --version
if [[ $path_hint -eq 1 ]]; then
echo "note: $PNPM_HOME/bin is not on your PATH; add it to run 'opencode' directly." >&2
fi