Compare commits

...
515 Commits
Author SHA1 Message Date
Andrey Antukh 06e5825c8a 🐛 Add proper input checking to font related RCP method 2026-02-10 10:36:57 +01:00
Andrey Antukh d30387eb77 Backport docker images changes from develop 2026-02-09 19:21:30 +01:00
Andrey Antukh 33fd672c21 Backport MCP related changes from develop (#8306) 2026-02-09 18:00:43 +01:00
Andrey Antukh a7b2e98b8e ⬆️ Use latest imagemagick version on docker images 2026-02-09 13:19:26 +01:00
Andrey Antukh d979894872 Add libxml2 dep to imagemagick dockerfile 2026-02-09 12:27:44 +01:00
Alejandro Alonso 3d20fc508d 🐛 Fix image magick info call (#8300) 2026-02-09 12:26:42 +01:00
Alejandro Alonso ccfee34e76 Merge pull request #8289 from penpot/niwinz-staging-exporter-fix
🐛 Fix issue with pdf render on exporter
2026-02-06 11:40:18 +01:00
Andrey Antukh 6f3f2f9a71 🐛 Fix issue with pdf render on exporter
When paired with release build penpot app
2026-02-06 11:19:56 +01:00
Alejandro Alonso ad5e8ccdb3 🐛 Fix pdf sizing issue on export (#8274) 2026-02-05 09:23:14 +01:00
Andrey Antukh 44c7d3fbd6 Backport .github workflows from develop 2026-02-04 16:21:19 +01:00
Andrey Antukh 3d50aa6cb2 ⬆️ Update imagemagick version 2026-02-04 16:21:19 +01:00
Andrey Antukh 06afd94a74 ⬆️ Update backend dependencies (mainly bugfixes) 2026-02-04 16:21:19 +01:00
Andrey Antukh e7d9dca55e ⬆️ Update jdk and node on devenv and other images 2026-02-04 16:21:19 +01:00
Andrey Antukh c14ccc18b8 Import mcp from develop 2026-02-04 16:21:19 +01:00
Andrey Antukh 7d09d930fe 📚 Update changelog 2026-02-03 11:13:46 +01:00
Andrey Antukh 0d9b7ca696 Merge tag '2.13.0-RC11' 2026-02-03 08:23:27 +01:00
Andrey Antukh d215a5c402 Merge tag '2.13.0-RC10' 2026-02-03 08:22:50 +01:00
Andrey Antukh f65292a13c 📎 Mark as skip two text editor v2 tests (flaky) 2026-01-29 10:43:29 +01:00
Andrey Antukh 94722fdec2 Ensure .hidePopover fn exist before call 2026-01-29 10:43:29 +01:00
Andrey Antukh 28509e0418 Ensure .stopPropagation fn exists before calling
Also for .stopImmediatePropagation and .preventDefault on
the event instances.
2026-01-29 10:43:29 +01:00
Eva Marco 9569fa2bcb 🐛 Fix error when creating a token with an invalid name (#8216) 2026-01-29 10:41:52 +01:00
Eva Marco 852b31c3a0 🐛 Fix allow spaces on token description (#8234) 2026-01-29 10:40:32 +01:00
Andrés Moya 84b3f5d7c6 🐛 Fix import of shadow tokens 2026-01-29 10:25:22 +01:00
David Barragán Merino 76bd31fe7d 🔧 Fix CORS error 2026-01-28 13:40:45 +01:00
David Barragán Merino cc81e56d82 🔧 Fix CORS error 2026-01-28 13:40:26 +01:00
Andrey Antukh a9e2fc8d94 Backport linter fixes and config from develop 2026-01-28 12:58:54 +01:00
David Barragán Merino 77bbf30ae4 🔧 Fix file name 2026-01-27 21:16:44 +01:00
David Barragán Merino 693b52bf45 📚 Fix links related to penpot plugins 2026-01-27 21:16:44 +01:00
David Barragán Merino 0f51b23ce7 🔧 Deploy plugin styles documentation 2026-01-27 21:16:44 +01:00
David Barragán Merino ec61aa6b6d 🔧 Add custom domain 2026-01-27 21:16:41 +01:00
David Barragán Merino 18aca16f98 🔧 Fix file name 2026-01-27 21:12:32 +01:00
David Barragán Merino c6465e27e3 📚 Fix links related to penpot plugins 2026-01-27 21:12:32 +01:00
David Barragán Merino 1834a18263 🔧 Deploy plugin styles documentation 2026-01-27 21:12:32 +01:00
David Barragán Merino d220d07875 🔧 Add custom domain 2026-01-27 21:12:32 +01:00
Eva Marco 9ca76c745f 🐛 Fix app freeze on token name change (#8214) 2026-01-27 17:31:50 +01:00
Andrey Antukh 3112b240a0 📎 Add missing entry on changelog 2026-01-27 09:28:41 +01:00
Andrey Antukh 56fd66b91a 🐛 Fix several issues related to path edition (#8187)
*  Improve save-path-content event consistency

Mainly removing possible race conditions from the event
implementation.

*  Ensure path content snapshot on start-path-edit event

*  Reuse already available shape-id on split-segments
2026-01-27 09:27:42 +01:00
Andrey Antukh abc1773f65 Merge tag '2.13.0-RC9' 2026-01-26 18:12:16 +01:00
David Barragán Merino 93f5e74bb0 🔧 Run all the jobs if the workflow is launched manually 2026-01-26 17:14:15 +01:00
David Barragán Merino 1ce0b60e3d 🔧 Run all the jobs if the workflow is launched manually 2026-01-26 17:13:54 +01:00
David Barragán Merino 38179ba11e 🔧 Enable secret inheritance 2026-01-26 14:01:22 +01:00
David Barragán Merino ef80901400 🔧 Enable secret inheritance 2026-01-26 14:00:55 +01:00
David Barragán Merino 719a95246a 🔧 Define deploy plugin packages workflows 2026-01-26 13:48:33 +01:00
David Barragán Merino e590cd852d 🔧 Rename wrangle to wrangler 2026-01-26 13:48:33 +01:00
David Barragán Merino a9741073e5 🔧 Add deploy plugin packages workflow placeholder and wrangle config files 2026-01-26 13:48:33 +01:00
David Barragán Merino 5306bed548 🔧 Define deploy plugin packages workflows 2026-01-26 13:47:57 +01:00
David Barragán Merino 92a319ddd1 🔧 Rename wrangle to wrangler 2026-01-26 13:47:57 +01:00
David Barragán Merino 68a6d4c9a8 🔧 Add deploy plugin packages workflow placeholder and wrangle config files 2026-01-26 13:47:57 +01:00
Andrey Antukh f07495ae95 🐛 Fix incorrect handling of numeric values layout padding and gap
Fixes https://github.com/penpot/penpot/issues/8113
2026-01-26 11:01:25 +01:00
Andrey Antukh 23d5fc7408 🐛 Prevent exception on open-new-window when no window is returned
Fixes https://github.com/penpot/penpot/issues/7787
2026-01-26 11:01:25 +01:00
Andrey Antukh 8632b18eec 🐛 Avoid json decoder liner limit exception by chunking
Happens only when we send large binary data serialized with transit
(mainly used for upload fonts data).
2026-01-26 11:01:25 +01:00
Andrey Antukh 33e650242c Add slugify to the filename on assets exportation
Fixes https://github.com/penpot/penpot/issues/8017
2026-01-26 11:01:25 +01:00
Andrey Antukh e03ad25118 🔧 Backport CI workflow from develop 2026-01-26 10:18:24 +01:00
David Barragán Merino 599656c31e 🔧 Fix a typo in an interpolation 2026-01-23 19:52:47 +01:00
David Barragán Merino 5d7e6afd76 🔧 Fix a typo in an interpolation 2026-01-23 19:52:20 +01:00
Alonso Torres 15d369493b 🐛 Fix problem with z-index modal in dashboard (#8178) 2026-01-23 12:48:01 +01:00
Eva Marco 9c9b672e3e 🐛 Fix spanish translations on import export token modal (#8172) 2026-01-23 10:05:20 +01:00
Eva Marco 5146221513 🐛 Fix allow negative spread values on shadow token creation (#8167)
* 🐛 Fix allow negative spread values on shadow token creation

* 🎉 Add test
2026-01-23 09:50:36 +01:00
Eva Marco e53f335204 🐛 Fix unhandled error on tokens modal (#8165) 2026-01-23 09:35:53 +01:00
David Barragán Merino 16f22a7b5c 🔧 Fixes to the API documentation deployer 2026-01-22 12:10:27 +01:00
David Barragán Merino a1460115e8 🔧 Deploy penpot api documentation 2026-01-22 12:10:27 +01:00
David Barragán Merino 2574ad3315 🔧 Fixes to the API documentation deployer 2026-01-22 12:09:38 +01:00
David Barragán Merino e6b5364a84 🔧 Deploy penpot api documentation 2026-01-22 12:09:38 +01:00
Alonso Torres 656f81f89f ⬆️ Update plugins to 1.4.2 (#8157) 2026-01-21 17:36:58 +01:00
Alonso Torres 01a4ffeb8b ⬆️ Updated plugins release to 1.4.0 (#8148) 2026-01-21 15:41:00 +01:00
Andrey Antukh b8c70be9a2 Make frontend build and watch process more resilent to errors 2026-01-21 13:44:35 +01:00
Andrey Antukh 525adcfcbe Add wasm build on watch app script (devenv) 2026-01-21 13:44:35 +01:00
Eva Marco 7cce4c6532 🐛 Fix unhandled exception tokens creation dialog (#8136) 2026-01-21 13:09:22 +01:00
Alejandro Alonso a3fdd8b691 Merge pull request #8147 from penpot/niwinz-staging-file-menu-issue
 Use correct team-id on file-menu on dashboard
2026-01-21 12:54:28 +01:00
Andrey Antukh b6a9579c98 Use correct team-id on file-menu on dashboard
Before the changes on this commit, the team object is used for
retrieve the id, where we already have team-id. Additionally, the
team object resolution is async operation and is not available on
the first render which causes strange issues on automated flows
(playwright) where an option is clicked when the async flow is
still pending and we have no team object loaded.
2026-01-21 12:23:44 +01:00
Eva Marco 8c7fd0af4b 🐛 Fix shadow reference validation (#8132) 2026-01-21 09:17:03 +01:00
Andrey Antukh cf46051f56 🔥 Remove .traivis.yml file from the repository 2026-01-20 19:40:31 +01:00
Andrey Antukh 6393330ee1 Merge remote-tracking branch 'origin/staging' 2026-01-20 16:25:10 +01:00
Andrey Antukh 8252bc485e 📚 Fix oidc callback related documentation issue 2026-01-20 16:24:12 +01:00
Alonso Torres 47775a9e2c Merge pull request #8134 from penpot/alotor-fix-plugins-export
🐛 Fix problem with export in plugins
2026-01-20 15:03:04 +01:00
Andrey Antukh 8191d04114 Use non-legacy config example on docker compose file 2026-01-20 13:25:55 +01:00
Andrey Antukh 88f0f75174 Merge pull request #8129 from penpot/niwinz-staging-bugfix-1
 Several improvements for build process
2026-01-20 12:26:55 +01:00
Andrey Antukh 1ffa956251 Include timestamp on version tag 2026-01-20 12:26:39 +01:00
Andrey Antukh 31054099ff Use pseudo-names on release builds of frontend (#8105) 2026-01-20 12:26:39 +01:00
Eva Marco 983487d73c 🐛 Fix shadow token reference validation (#8128) 2026-01-20 10:56:27 +01:00
Andrey Antukh 3ecf509f3b 🔧 Add missing gif files handling on defaut nginx config 2026-01-19 13:32:01 +01:00
Dalai FelintoandDalai Felinto 7f395b2642 🐛 Fix import tokens dialog default option (#8051)
This was intended to be changed on 13fcf3a9bb. However only the menu
order changed, not the default option.

Signed-off-by: Dalai Felinto <dalai@blender.org>
Co-authored-by: Dalai Felinto <dalai@blender.org>
2026-01-19 11:14:34 +01:00
Juan de la Cruz 2a62bd2586 🎉 Add 2.13 release slides (#8116) 2026-01-19 11:09:58 +01:00
Andrey Antukh 1ae7515189 🐛 Set correct defaults for storage configuration on compose (#8060)
* 🔥 Remove mention to legacy and never released admin on compose

* 🐛 Set correct defaults for storage configuration on compose
2026-01-16 08:11:34 +01:00
andrés gonzález 8c401f5346 📚 Add Files and Projects section to user guide (#8095) 2026-01-15 17:01:05 +01:00
andrés gonzález b72de2dc8f 📚 Add shadow token documentation (#8082) 2026-01-15 09:09:08 +01:00
Alejandro Alonso 51635770ce Merge pull request #8065 from penpot/eva-fix-lost-translation
🐛 Fix translation on rename token
2026-01-14 09:44:41 +01:00
Eva Marco 18a4e63da0 🐛 Fix translation on rename token 2026-01-13 15:57:09 +01:00
Andrey Antukh d71f811dbe Update help center build script 2026-01-13 10:35:15 +01:00
Andrey Antukh 1beb3b86aa 🐛 Add missing msgid_plural on several translations 2026-01-12 11:52:07 +01:00
Andrey Antukh fe20bdd00e 🐛 Fix multiple selection options on dashboard deleted page (#8055)
* 🐛 Fix multiple selection options on dashboard deleted page

* 📎 Fix translations
2026-01-12 11:41:37 +01:00
María Valderrama 5420897b92 🐛 Fix empty state message in trash page (#8045) 2026-01-12 11:17:01 +01:00
Andrey Antukh e430a4c9f3 Revert " Backport translations from develop"
This reverts commit ec6d72bd91.
2026-01-12 10:40:17 +01:00
Andrey Antukh ec6d72bd91 Backport translations from develop 2026-01-12 09:39:33 +01:00
Eva Marco 6ec451b46d 🐛 Fix resolved value on line height (#8047) 2026-01-09 12:54:24 +01:00
Andrey Antukh 2b836f10cb 🐛 Do not show deleted files on search (#8036)
* 🐛 Do not show deleted files on search

* 💄 Add cosmetic changes to dashboard deleted files page
2026-01-09 11:11:29 +01:00
Andrey Antukh 1ae0f3fc87 Merge pull request #8037 from penpot/niwinz-staging-project-name-fix
🐛 Fix long project name visual problem on dashboard
2026-01-08 17:41:37 +01:00
Eva Marco e13c203b8d ♻️ Refactor scss file 2026-01-08 16:35:56 +01:00
Andrey Antukh 90efb665b5 Add several additional renames for make translation string consistent 2026-01-08 14:37:58 +01:00
Pablo Alba 47ee490158 🐛 Fix typos on download modal 2026-01-08 14:37:58 +01:00
Andrey Antukh f0f89599bc 🌐 Backport translations from develop 2026-01-08 14:08:02 +01:00
Andrey Antukh b0dc7d6ffb 🔧 Change default jmx port on deps.edn 2026-01-08 13:56:22 +01:00
Andrey Antukh b7cd315872 🐛 Fix wasm-playground on devenv 2026-01-08 13:48:09 +01:00
Eva Marco 743d4e5c8d 🐛 Fix error on shadow token creation (#8029) 2026-01-08 13:26:01 +01:00
Andrey Antukh 97e4f4c424 🐛 Fix long project name visual problem on dashboard 2026-01-08 11:40:55 +01:00
Belén Albeza fb9560c315 🐛 Fix guides dropdown width (#8031)
* 🐛 Fix width of guides column dropdown

* ♻️ Remove deprecated tokens in css

* 🔧 Update changelog
2026-01-08 10:47:11 +01:00
Alejandro Alonso d53c090900 Merge pull request #8028 from penpot/elenatorro-12956-fix-text-color-tokens
🐛 Fix missing text color token from selected shapes in selected colors list
2026-01-07 16:49:41 +01:00
Elena Torro 621e030095 🐛 Fix missing text color token from selected shapes in selected colors list 2026-01-07 16:41:25 +01:00
Alejandro Alonso 157e4aa2d0 Merge pull request #8025 from penpot/elenatorro-12951-fix-inner-text-shadow-token
🐛 Fix inner shadow selector on shadow token
2026-01-07 16:37:19 +01:00
Elena Torro 7cd2308f3b 🐛 Fix inner shadow selector on shadow token 2026-01-07 16:36:51 +01:00
Alejandro Alonso c315a15b48 Merge pull request #8026 from penpot/elenatorro-12997-fix-clojure-on-css-box-shadow
🐛 Fix CSS generated box-shadow property
2026-01-07 16:32:12 +01:00
Elena Torro 8a3e6d026e 🐛 Fix CSS generated box-shadow property 2026-01-07 16:28:05 +01:00
Florian Schrödl 0dd062d011 🐛 Fix line-height throwing for int (#7927) 2026-01-07 16:13:10 +01:00
Alejandro Alonso bfbb546699 Merge pull request #8027 from penpot/superalex-fix-colors-assets-from-shared-libraries
🐛 Fix color assets from shared libraries
2026-01-07 14:16:57 +01:00
Alejandro Alonso 083e77e9c5 🐛 Fix color assets from shared libraries 2026-01-07 14:02:28 +01:00
Alejandro Alonso 919f78daeb Merge pull request #7965 from penpot/eva-fix-styles-on-viewer
🐛 Fix inspect tab styles on viewer
2026-01-07 11:54:33 +01:00
Eva Marco b5c30f8c41 🐛 Fix inspect tab styles on viewer 2026-01-07 11:41:49 +01:00
Alejandro Alonso 60aa426753 Merge pull request #8022 from penpot/alotor-fix-drag-handlers
🐛 Fix problem with dragging handlers
2026-01-07 11:36:26 +01:00
Alejandro Alonso 86f7d6b26b Sanitizing error values (#8020) 2026-01-07 11:23:19 +01:00
Andrey Antukh 36732a4bd3 Make the devenv runtine initialization yarn independent (#8023) 2026-01-07 11:21:58 +01:00
Andrey Antukh eff572d3bb Revert "💄 Group tokens by name path (#7775)"
This reverts commit 0956b66281.
2026-01-07 11:20:44 +01:00
alonso.torres d470d96833 🐛 Fix problem with dragging handlers 2026-01-07 11:00:02 +01:00
Aitor Moreno cab70773d2 Merge pull request #7667 from penpot/azazeln28-doc-add-more-info-text-editor-v2-readme
📚 Add more info about text editor v2
2026-01-07 09:40:06 +01:00
Alejandro Alonso 32ca42a093 Merge remote-tracking branch 'origin/staging-render' into staging 2026-01-05 13:21:58 +01:00
Alejandro Alonso 523a97a4ec Merge pull request #8016 from penpot/alotor-fix-refresh-thumbnails
🐛 Fix problem with thumbnail regeneration
2026-01-05 13:21:34 +01:00
Alejandro Alonso 260f6861a3 Merge pull request #8015 from penpot/alotor-fix-grid-component-auto-sizing
🐛 Fix problem with grid layout components and auto sizing
2026-01-05 13:18:59 +01:00
alonso.torres edd53b419a 🐛 Fix problem with thumbnail regeneration 2026-01-05 13:09:40 +01:00
alonso.torres 078a3d5a5c 🐛 Fix problem with grid layout components and auto sizing 2026-01-05 10:54:36 +01:00
Alejandro Alonso c4e57427ac Merge branch 'staging-render' into staging 2026-01-05 10:30:06 +01:00
Alejandro Alonso 7a6405481c Merge remote-tracking branch 'origin/develop' into staging 2026-01-05 08:37:19 +01:00
Alejandro Alonso 218f34380a Merge pull request #8012 from penpot/azazeln28-refactor-minor-changes
♻️ Minor naming changes and event handling
2026-01-02 14:16:55 +01:00
Alejandro Alonso 47aaa2b5fa Merge pull request #8011 from penpot/alotor-fix-trash-bar
🐛 Fix problems with trash bar in dashboard
2026-01-02 13:46:53 +01:00
Aitor Moreno 6c6b3db87e ♻️ Minor naming changes and event handling 2026-01-02 13:41:48 +01:00
Alejandro Alonso 6eb32cfb79 Merge pull request #8008 from penpot/alotor-fix-path-editor
🐛 Fix problem with path editor and right click
2026-01-02 13:29:44 +01:00
alonso.torres dbba3496af 🐛 Fix problems with trash bar in dashboard 2026-01-02 12:00:16 +01:00
alonso.torres 55752d361f 🐛 Fix problem with path editor and right click 2026-01-02 10:37:52 +01:00
Alejandro Alonso fe94ee4526 Merge pull request #8009 from penpot/alotor-fix-style-font-input
🐛 Fix problem with style in fonts input
2026-01-02 10:23:02 +01:00
Aitor Moreno e39f292499 📚 Add more info about text editor v2 2026-01-02 10:13:34 +01:00
Andrey Antukh 5c71c57dd9 Merge tag '2.12.1' 2025-12-30 15:37:30 +01:00
Andrey Antukh 52b8560b70 Merge branch 'staging-render' into develop 2025-12-30 15:30:56 +01:00
Andrey Antukh 75860afe57 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-30 15:29:58 +01:00
Andrey Antukh 824ca1bbca 🔧 Make devenv init yarn indpendent 2025-12-30 15:28:19 +01:00
Andrey Antukh 5b6f9c1741 📎 Disable legacy cache on plugins nx config 2025-12-30 14:56:15 +01:00
Andrey Antukh 19853b832b 📚 Update documentation 2025-12-30 14:56:15 +01:00
Andrey Antukh d20c011db2 Migrate plugins to pnpm 2025-12-30 14:56:15 +01:00
Andrey Antukh 9431ae6858 📎 Update docs 2025-12-30 14:56:15 +01:00
Andrey Antukh 96356c1b89 ⬆️ Update storybook and fix compatibility issues 2025-12-30 14:56:15 +01:00
Andrey Antukh b7b68eeb47 🔥 Remove npx prefix on package.json scripts 2025-12-30 14:56:15 +01:00
Andrey Antukh 9bbeb657f8 🔧 Add plugins runtime ci job 2025-12-30 14:56:15 +01:00
Andrey Antukh ec1af4ad96 🎉 Import penpot-plugins repository
As commit 819a549e4928d2b1fa98e52bee82d59aec0f70d8
2025-12-30 14:56:15 +01:00
alonso.torres 23e7116b24 🐛 Fix problem with style in fonts input 2025-12-30 14:28:10 +01:00
Alejandro Alonso 48e3f35bb3 🐛 Fix setting a portion of text as bold or underline messes things up 2025-12-30 11:34:24 +01:00
Andrey Antukh 6b794c9d12 Merge branch 'staging' into staging-render 2025-12-30 11:13:15 +01:00
Yamila Moreno d3ee50daf5 🔧 Add ci for branch staging-render 2025-12-30 11:13:00 +01:00
Yamila Moreno 22a36d59d8 🔧 Add ci for branch staging-render 2025-12-30 10:57:51 +01:00
Alejandro Alonso a948e49e51 🐛 Fix using cache on first zoom after pan 2025-12-30 10:03:24 +01:00
Alejandro Alonso d635f5a8dc 🐛 Detecting situations where WebGL context is lost or no WebGL support 2025-12-30 10:03:24 +01:00
Alejandro Alonso ab3a3ef43b 🎉 Resize cache only when required 2025-12-30 10:03:24 +01:00
Alejandro Alonso 9c21fd3359 🐛 Fix resize cache memory leak 2025-12-30 10:03:24 +01:00
Andrey Antukh 7b5817f407 ♻️ Make several adjustments to the dashboard deleted page (#7999)
* ♻️ Make several sustantial adjustments to the dashboard deleted page

* 📎 Add PR feedback changes
2025-12-30 09:52:29 +01:00
Yamila Moreno e3405eacca 🔧 Improve mattermost notification 2025-12-29 19:06:26 +01:00
Alejandro Alonso 44b70cf1d4 Merge pull request #7998 from penpot/alotor-fix-problem-with-create-grid
🐛 Fix problem creating grid from elements
2025-12-29 14:31:15 +01:00
Alejandro Alonso a8bd74b392 Merge pull request #8001 from penpot/alotor-fix-gfonts-references
🐛 Fix problem with some fonts
2025-12-29 14:25:54 +01:00
alonso.torres 3d3e3582d6 🐛 Fix problem with some fonts 2025-12-29 12:35:19 +01:00
Andrey Antukh de052b5161 📎 Update changelog 2025-12-29 11:10:04 +01:00
Andrey Antukh e01654ba43 Merge branch 'staging-render' into develop 2025-12-29 10:43:00 +01:00
Andrey Antukh 6ebd48b94c Merge branch 'staging' into staging-render 2025-12-29 10:41:08 +01:00
Andrey Antukh 8a3b33797f 🐛 Fix error handling on password change form
Fixes https://github.com/penpot/penpot/issues/7978
2025-12-29 10:27:27 +01:00
Andrey Antukh 13fd20f76f Backport form error management improvements from develop 2025-12-29 10:27:27 +01:00
alonso.torres 417cd80564 🐛 Fix problem creating grid from elements 2025-12-23 14:49:21 +01:00
Alejandro Alonso a57011ec7b Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-23 13:35:27 +01:00
Andrey Antukh 69c880d00e 🐛 Fix importmap usage on firefox 2025-12-23 13:10:58 +01:00
Andrey Antukh 9eebc467ef Preload default translations 2025-12-23 13:10:58 +01:00
Andrey Antukh b77712ce73 Move frontend/vendor to frontend/packages 2025-12-23 13:10:58 +01:00
Andrey Antukh 3d3e81f314 Replace tubax with more modern tooling 2025-12-23 13:10:58 +01:00
Andrey Antukh fe6441bb24 Replace hightlight.js internal bundle with direct npm use 2025-12-23 13:10:58 +01:00
Andrey Antukh e15f0baf30 Replace direct draft-js usage with internal module 2025-12-23 13:10:58 +01:00
Andrey Antukh c040cbb784 🔥 Remove old gulp related dependencies 2025-12-23 13:10:58 +01:00
Andrey Antukh 7f674b78a9 📎 Move all deps to dev-dependencies on frontend package.json
All they only needed for build process.
2025-12-23 13:10:58 +01:00
Andrey Antukh 099b78affd 📎 Update frontend yarn.lock file 2025-12-23 13:10:58 +01:00
Andrey Antukh 78cc3f0aa4 📎 Add immutable dependency to vendor/draft-js 2025-12-23 13:10:58 +01:00
Andrey Antukh 76f5f12808 ⬆️ Update dependencies on exporter 2025-12-23 13:10:58 +01:00
Alejandro Alonso cb325282ec Merge pull request #7994 from penpot/alotor-fix-font-style
🐛 Fix problem when changing colors with multiple fonts
2025-12-23 07:34:41 +01:00
Andrey Antukh 01ecde3bfa Add the ability to add relations on penpot sdk (#7987)
*  Add the ability to add relations on penpot sdk

* 📎 Remove debug console log
2025-12-22 20:55:31 +01:00
Andrey Antukh 047483a70a 🐛 Fix deleted files thumbnails generation 2025-12-22 20:20:43 +01:00
Alonso Torres 4000ec8762 🐛 Fix problem resizing auto size layouts (#7995) 2025-12-22 20:17:11 +01:00
Andrey Antukh 8cb2f27de8 ♻️ Move file permissions to binfile common ns 2025-12-22 20:16:41 +01:00
Andrey Antukh 0433336fc9 📎 Use correct criterium version on frontend deps 2025-12-22 20:16:41 +01:00
Andrey Antukh ce234fbeda Allow get thumbnails for deleted files 2025-12-22 20:16:41 +01:00
Andrey Antukh fc4d31eed7 Add minor efficiency improvements to deleted dashboard page 2025-12-22 20:16:41 +01:00
María Valderrama c670aac339 🎉 Added deleted files to dashboard 2025-12-22 20:16:41 +01:00
Andrés Moya 1d3fb5434f Enable shadow tokens by default (#7996) 2025-12-22 18:17:29 +01:00
Andrey Antukh f478399ae0 Merge remote-tracking branch 'origin/staging-render' into develop 2025-12-22 17:28:18 +01:00
Andrey Antukh 6a1854f180 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-22 17:28:01 +01:00
Andrés Moya 0858e297e5 🎉 Add composite tokens to plugins API (#7992) 2025-12-22 17:14:54 +01:00
alonso.torres bd580ab159 🐛 Fix problem when changing colors with multiple fonts 2025-12-22 17:14:37 +01:00
Alejandro Alonso 5780a43fe0 🐛 Fix object added in different page (#7988) 2025-12-22 16:59:47 +01:00
Alejandro Alonso 737eceda3a 🐛 Fix unmasking shapes (#7989) 2025-12-22 16:59:04 +01:00
Alonso Torres 923c3c2dbd 🐛 Fix font weight token (#7991) 2025-12-22 16:58:26 +01:00
Alejandro Alonso a14b4561e7 🐛 Fix comment bubbles (#7990) 2025-12-22 16:57:45 +01:00
Andrey Antukh bb5568e15a 🎉 Enable hindi translations on the application 2025-12-22 16:57:00 +01:00
Pablo Alba 5cbcec3db6 🐛 Fix "maximum call stack size exceeded" crash on variant 2025-12-22 16:57:00 +01:00
Anonymous 105e1fe86c 🌐 Add translations for: Spanish
Currently translated at 97.1% (1940 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2025-12-22 16:34:43 +01:00
Yaron Shahrabani 3e0a916883 🌐 Add translations for: Hebrew
Currently translated at 99.3% (1985 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2025-12-22 16:34:42 +01:00
Ahmad HosseinBor 4f80238bc2 🌐 Add translations for: Persian
Currently translated at 39.2% (783 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2025-12-22 16:34:42 +01:00
Alejandro Alonso 5156cc5d9a 🌐 Add translations for: Yoruba
Currently translated at 58.8% (1176 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/yo/
2025-12-22 16:34:42 +01:00
Yessenia Villarte Vaca 42c46b6cfc 🌐 Add translations for: Spanish (Latin America)
Currently translated at 6.5% (131 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es_419/
2025-12-22 16:34:41 +01:00
VKing9 8b3c40b35e 🌐 Add translations for: Hindi
Currently translated at 99.6% (1991 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2025-12-22 16:34:41 +01:00
Andy Li d3996e5fb1 🌐 Add translations for: Chinese (Traditional Han script)
Currently translated at 80.1% (1601 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2025-12-22 16:34:40 +01:00
Anonymous 0c42bca866 🌐 Add translations for: German
Currently translated at 95.5% (1908 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-12-22 16:34:40 +01:00
Marius e5685c1f1c 🌐 Add translations for: German
Currently translated at 95.5% (1908 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-12-22 16:34:40 +01:00
Anonymous 2784209bde 🌐 Add translations for: Turkish
Currently translated at 99.5% (1989 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2025-12-22 16:34:40 +01:00
Alejandro Alonso 024f460e99 🌐 Add translations for: Igbo
Currently translated at 25.6% (512 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ig/
2025-12-22 16:34:39 +01:00
Anonymous 1d9b76b62a 🌐 Add translations for: Romanian
Currently translated at 97.2% (1942 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/
2025-12-22 16:34:39 +01:00
Shuaib Zahda 7e17a75b7d 🌐 Add translations for: Arabic
Currently translated at 56.4% (1127 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/
2025-12-22 16:34:39 +01:00
Anonymous ca093d6fae 🌐 Add translations for: French
Currently translated at 96.8% (1934 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-12-22 16:34:38 +01:00
Alexandre Pawlak 0f0b7562b5 🌐 Add translations for: French
Currently translated at 96.8% (1934 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-12-22 16:34:38 +01:00
Anonymous 9cdc694697 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 90.3% (1804 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-12-22 16:34:37 +01:00
Dário b972a4033b 🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 90.3% (1804 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2025-12-22 16:34:37 +01:00
Anonymous cbe9f4da51 🌐 Add translations for: Swedish
Currently translated at 99.3% (1985 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2025-12-22 16:34:37 +01:00
Anonymous c583bde9e3 🌐 Add translations for: Russian
Currently translated at 76.8% (1534 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2025-12-22 16:34:37 +01:00
Vint Prox 3911ebdc4e 🌐 Add translations for: Russian
Currently translated at 76.8% (1534 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2025-12-22 16:34:36 +01:00
Anonymous 3e3b18667b 🌐 Add translations for: Portuguese (Brazil)
Currently translated at 69.9% (1396 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2025-12-22 16:34:36 +01:00
Anonymous ed81c9b8df 🌐 Add translations for: Dutch
Currently translated at 99.5% (1988 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-12-22 16:34:36 +01:00
Sebastiaan Pasma fbdf98d29c 🌐 Add translations for: Dutch
Currently translated at 99.5% (1988 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-12-22 16:34:36 +01:00
Anonymous e603825a55 🌐 Add translations for: Italian
Currently translated at 99.5% (1988 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-12-22 16:34:35 +01:00
Valentina Chapellu 1d724783e6 🌐 Add translations for: Italian
Currently translated at 99.5% (1988 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-12-22 16:34:35 +01:00
Hosted Weblate e0abe7dcb5 🌐 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2025-12-22 16:33:05 +01:00
Andrey Antukh 5c1bbf5be8 Merge remote-tracking branch 'weblate/develop' into develop 2025-12-22 16:32:30 +01:00
Pablo Alba bbb0d58190 🐛 Fix "maximum call stack size exceeded" crash on variant 2025-12-22 16:27:10 +01:00
Andrey Antukh 88dcf9d1fe 🐛 Mark rpc calls as authenticated when shared key is used (#7901) 2025-12-22 12:18:36 +01:00
Alejandro Alonso fe44c14bac Merge pull request #7982 from penpot/niwinz-staging-import-bucket
🐛 Prefill storage object bucket if it comes nil on import binfile
2025-12-22 12:17:16 +01:00
Belén Albeza 20061067ad 🐛 Fix text editor not getting focus back after font variant change 2025-12-22 11:18:25 +01:00
Andrey Antukh 336173645e 🐛 Fix regression on export shape on plungins API 2025-12-22 10:41:42 +01:00
Andrey Antukh 2acf15958b Merge branch 'staging-render' into develop 2025-12-22 09:24:04 +01:00
Andrey Antukh 08267de242 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-22 09:23:48 +01:00
Pablo Alba 35fb376a78 Add proxypass to caddyfile on devenv (#7985) 2025-12-22 09:21:22 +01:00
Dalai FelintoandDalai Felinto 13fcf3a9bb 💄 Set import Tokens default option to be Single JSON value (#7918)
This patches makes the default Tokens importing option to match the
current default Tokens exporting option (single JSON value). This way it
is more obvious and quick to export the tokens from a file and import
in new one,

---

While testing our design system we are often re-exporting and
re-importing the Tokens to the files using the design system components.

I'm aware that this may be addressed in the future so the Tokens are
brought in together with the library. Meanwhile (and even in the future)
I think it is sensible to have a symmetry between the export and import
defeault options.

Co-authored-by: Dalai Felinto <dalai@blender.org>
2025-12-19 10:44:05 +01:00
Andrey Antukh 83bb4bf221 🐛 Prefill storage object bucket if it comes nil on import binfile 2025-12-19 09:32:51 +01:00
Henrik Steffens dba6ae2820 🌐 Add translations for: German
Currently translated at 95.8% (1911 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-12-17 13:00:25 +01:00
Marius ada101c236 🌐 Add translations for: German
Currently translated at 95.8% (1911 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-12-17 13:00:24 +01:00
Marius ea48fb5825 🌐 Add translations for: German
Currently translated at 90.4% (1804 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2025-12-16 11:00:25 +01:00
Alejandro Alonso 15ed25ca79 Merge pull request #7966 from penpot/niwinz-staging-abrreviate
🐛 Fix incorrect string truncation with abbreviate template filter
2025-12-12 13:53:33 +01:00
Andrey Antukh 9aa387a473 🐛 Fix incorrect string truncation with abbreviate template filter 2025-12-12 13:50:46 +01:00
Alejandro Alonso 67ba91b4b9 Merge pull request #7971 from penpot/niwinz-staging-bugfix-6
🐛 Fix tokens-lib encoding when value is nilable
2025-12-12 13:46:06 +01:00
Alejandro Alonso f67f1a6a0e Merge pull request #7972 from penpot/niwinz-staging-bugfix-7
🐛 Fix exception on assinging gradient to shadow on multiple selection
2025-12-12 13:42:39 +01:00
Alejandro Alonso 82d3e2024e Merge pull request #7973 from penpot/niwinz-staging-worker-scheduler
🐛 Fix incorrect redis connection error handling
2025-12-12 13:23:49 +01:00
Alejandro Alonso 4bd846c16d Merge pull request #7969 from penpot/niwinz-staging-fix-ratelimit
🐛 Fix issue on reading rlimit config
2025-12-12 13:22:53 +01:00
alonso.torres 8fde6b28ed 🐛 Fix problems with alignments and margins 2025-12-12 13:21:04 +01:00
alonso.torres 63325ec796 🐛 Fix problem with flex fill size distribution 2025-12-12 13:21:04 +01:00
alonso.torres 84415476d0 🐛 Fix problem with reflow layout 2025-12-12 13:21:04 +01:00
Andrey Antukh 94f95ca6b8 🐛 Fix incorrect redis connection error handling 2025-12-12 12:33:38 +01:00
Andrey Antukh 33c786498d Merge remote-tracking branch 'origin/staging-render' into develop 2025-12-12 12:19:49 +01:00
Andrey Antukh 5abc1aafb4 Merge tag '2.12.0-RC3' 2025-12-12 12:19:29 +01:00
Andrey Antukh 1f886b1f88 Merge remote-tracking branch 'origin/staging' into develop 2025-12-12 12:16:41 +01:00
Aitor Moreno 5a922c6bd6 Merge pull request #7960 from penpot/superalex-fix-too-many-active-webgl-contexts
🐛 Fix too many active WEBGL contexts
2025-12-12 12:03:46 +01:00
Andrey Antukh 507bf7445b 🐛 Fix tokens-lib encoding when value is nilable 2025-12-12 11:42:15 +01:00
Andrey Antukh 81b72c5acd 🐛 Fix exception on assinging gradient to shadow on multiple selection 2025-12-12 11:24:53 +01:00
Alejandro Alonso 1388865cfc 🐛 Fix too many active WEBGL contexts 2025-12-12 11:16:47 +01:00
Andrey Antukh 1738847694 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-12 10:20:21 +01:00
Aitor Moreno ca1c3c799d Merge pull request #7968 from penpot/alotor-fix-border-radius
🐛 Fix problem with border radius to path
2025-12-12 10:18:07 +01:00
Andrey Antukh 974495e08f Reduce log level for profile picture download error
Because it is not blocking operation and does not provents user
to proceed.
2025-12-12 08:17:13 +01:00
Andrey Antukh 2ed39e43c3 🐛 Fix issue on reading rlimit config 2025-12-11 23:50:01 +01:00
alonso.torres ce5006ae84 🐛 Fix problem with border radius to path 2025-12-11 22:40:44 +01:00
Eva Marco 50dbe6ab12 🐛 Fix horizontal scroll on layer panel (#7956) 2025-12-11 21:34:18 +01:00
Belén Albeza 0a7a65af5d ♻️ Make SerializableResult to depend on From traits 2025-12-11 16:00:03 +01:00
alonso.torres ea4d0e1238 Calculate position data in wasm 2025-12-11 16:00:03 +01:00
Elena Torro b705cf953a 🐛 Set layout data from set-object 2025-12-11 14:52:32 +01:00
Alejandro Alonso 90ce1f56e7 Merge pull request #7958 from penpot/superalex-fix-svg-extract-ids
🐛 Fix svg extract ids
2025-12-11 14:02:05 +01:00
Alejandro Alonso ab0438cc6f 🐛 Fix svg extract ids 2025-12-11 13:47:00 +01:00
Aitor Moreno c6aa9cc4b7 Merge pull request #7950 from penpot/ladybenko-12851-fix-text-selection
🐛 Fix text selection when editor regains focus
2025-12-11 13:45:29 +01:00
Andrey Antukh 5779adef33 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-11 13:30:59 +01:00
Andrey Antukh 2f46cbc0d4 Make render wasm import on worker http cache aware 2025-12-11 13:27:20 +01:00
Elena Torró ebf1758958 Merge pull request #7935 from penpot/superalex-improve-svg-import
🎉 Improve svg import
2025-12-11 13:21:29 +01:00
Elena Torró e94c56bfa7 Merge pull request #7954 from penpot/azazeln28-fix-font-weight-mixed-value
🐛 Fix font weight mixed value
2025-12-11 12:43:53 +01:00
Andrey Antukh 53be6f996b 🐛 Fix issues on build processs related to render-wasm 2025-12-11 12:41:19 +01:00
Alejandro Alonso 89d9591011 🎉 Improve svg import 2025-12-11 12:02:34 +01:00
Andrey Antukh 5a260294a1 🔧 Update build-tag.yml github workflow 2025-12-11 12:00:42 +01:00
Andrey Antukh 3becfcd723 🔧 Update build-tag.yml github workflow 2025-12-11 11:59:16 +01:00
Andrey Antukh 3f6e44316e 🐛 Add missing node depes install on render-wasm 2025-12-11 11:51:47 +01:00
Aitor Moreno 5501a2815f 🐛 Fix font-variant-id mixed value 2025-12-11 11:32:27 +01:00
Eva Marco 77ef8e6fe6 🐛 Fix scroll on move library modal (#7952) 2025-12-11 10:46:54 +01:00
Alejandro Alonso 1066438b02 Merge pull request #7922 from penpot/elenatorro-12855-improve-pan-rendering
🔧 Improve pan rendering
2025-12-10 15:58:59 +01:00
Alejandro Alonso 3b23a3ad19 Merge pull request #7947 from penpot/elenatorro-12880-fix-variant-ui
🔧 Support variants interactivity on the new render's UI
2025-12-10 15:27:48 +01:00
Andrey Antukh 7396f4bfb6 Merge remote-tracking branch 'origin/staging' into develop 2025-12-10 15:17:50 +01:00
Alejandro Alonso 916b7709dc Update Pencil Penpot Design System System template in carousel (#7948) 2025-12-10 15:09:28 +01:00
Belén Albeza 5cf51f3d26 🐛 Fix text selection not being restore if it was only 1 word 2025-12-10 15:05:13 +01:00
Belén Albeza 25acad5154 🔧 Add formatting rules to the TextEditor 2025-12-10 15:04:34 +01:00
Elena Torro 0a212b6291 🔧 Support variants interactivity on the new render's UI 2025-12-10 14:39:59 +01:00
Eva Marco 443e41fea4 🐛 Fix multiple selection with color tokens (#7941) 2025-12-10 14:36:08 +01:00
Alejandro Alonso c7c9b04095 Merge pull request #7944 from penpot/niwinz-staging-exporter-fix
🐛 Fix incorrect resource lifetime handling on exporter
2025-12-10 14:35:20 +01:00
Eva Marco c61a0c0332 📚 Add line to changelog (#7945) 2025-12-10 13:58:18 +01:00
Andrey Antukh eb1eeb4750 Merge remote-tracking branch 'origin/staging-render' into niwinz-develop-merge 2025-12-10 13:53:15 +01:00
Andrey Antukh a78477592b Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-10 13:36:23 +01:00
Eva Marco 8707ff6511 🎉 Add spanish translation 2025-12-10 13:12:30 +01:00
Florian Schroedl 3d8a251741 🐛 Disallow font-family referencing composite token 2025-12-10 13:12:30 +01:00
Andrey Antukh 34e84ee3c8 🐛 Fix incorrect resource lifetime handling on exporter 2025-12-10 13:02:31 +01:00
Xaviju 0956b66281 💄 Group tokens by name path (#7775)
* 💄 Group tokens by name path
2025-12-10 12:34:19 +01:00
Luis de Dios 007b3f11f9 🐛 Fix pass new icons to radio buttons (#7939) 2025-12-10 12:28:27 +01:00
Alejandro Alonso e8201402a7 Merge pull request #7938 from penpot/niwinz-staging-bugfix-5
🐛 Fix several issues
2025-12-10 12:05:42 +01:00
Aitor Moreno 8a22477b96 Merge pull request #7932 from penpot/niwinz-staging-worker-wasm-load
🐛 Fix WASM loading strategy on worker
2025-12-10 11:47:31 +01:00
Elena Torro a661b2564f 🐛 Fix default case on vertical align 2025-12-10 10:59:27 +01:00
Elena Torro 2c3732f3f4 🔧 Fix line height calculation 2025-12-10 10:59:27 +01:00
Andrey Antukh e16645227b Merge branch 'staging-render' into develop 2025-12-10 10:10:44 +01:00
Andrey Antukh 45665a3c21 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-10 10:09:58 +01:00
Alejandro Alonso 3e684ea54f ⬆️ Update svgo dependency on frontend (#7936) 2025-12-10 10:07:02 +01:00
Eva Marco 179e6a195d 🎉 Add test for token creation (#7915) 2025-12-10 09:56:21 +01:00
Andrey Antukh 98039f13d8 🐛 Fix main toolbar z-index 2025-12-10 09:47:40 +01:00
Alejandro Alonso 40c27591f6 🐛 Fix svg import (#7925) 2025-12-10 08:36:54 +01:00
Andrey Antukh 91d20a46d1 💄 Add cosmetic changes to exports assets progress component 2025-12-10 08:23:05 +01:00
Andrey Antukh 50bead7c56 🐛 Fix react warning on having p inside p on assets export progress 2025-12-10 08:22:41 +01:00
Andrey Antukh b75b999903 📎 Fix devenv jvm warning 2025-12-10 08:22:05 +01:00
Andrey Antukh 810f1721c8 🐛 Fix recursion render on subscription modal 2025-12-10 07:54:52 +01:00
Stephan Paternotte b45bdd723f 🌐 Add translations for: Dutch
Currently translated at 99.8% (1991 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-12-09 22:00:21 +00:00
Ingrid Pigueron 8696044620 🌐 Add translations for: French
Currently translated at 97.1% (1937 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-12-09 22:00:19 +00:00
Andrey Antukh 8a8f360c7f Merge remote-tracking branch 'origin/staging' into develop 2025-12-09 19:53:38 +01:00
Andrey Antukh a4646373cf ♻️ Refactor wasm loading strategy on worker 2025-12-09 19:41:19 +01:00
Andrey Antukh f111cbb2a4 Add better cache config on devenv nginx 2025-12-09 19:38:30 +01:00
Luis de Dios e35fc85c3d 🎉 Create new empty-state component (#7903) 2025-12-09 16:48:12 +01:00
Aitor Moreno a614207f7e 🐛 Fix exporter failing with HTTPS 2025-12-09 16:08:20 +01:00
Elena Torro 81bc1bb0af 🔧 Log performance when building using profile-macros 2025-12-09 15:25:13 +01:00
Yamila Moreno 1798461d21 🐳 Add override for assets (#7926) 2025-12-09 14:55:21 +01:00
Luis de Dios 6ce3249c6d 🐛 Fix color format does not switch in the view mode (#7923)
* 🐛 Fix color format does not switch in the inspect mode of the view mode

* ♻️ Update components
2025-12-09 14:38:15 +01:00
Yamila Moreno dde0fddd6f 🐳 Add missing override to Dockerfile.frontend (#7920) 2025-12-09 12:08:46 +01:00
Aitor Moreno 7d36bc4025 Merge pull request #7907 from penpot/alotor-fix-export-text
🐛 Fix problem when exporting texts
2025-12-09 11:28:47 +01:00
Elena Torro b8feb6374d 🔧 Rebuild indices on zoom change, not pan 2025-12-09 11:26:03 +01:00
Elena Torro 0889df8e08 🔧 Skip slow operations on fast render 2025-12-09 11:26:03 +01:00
Andrey Antukh 4637aced8c Add support auto decoding and validation syntax for obj/reify 2025-12-09 11:13:06 +01:00
Andrey Antukh 9dfe5b0865 🐛 Fix inconsistencies on using obj/reify on plugins 2025-12-09 11:13:06 +01:00
Andrey Antukh 33bcc9544a Update frontend repl script 2025-12-09 11:13:06 +01:00
Andrey Antukh babd481b7f Make sm/coercer lazy 2025-12-09 11:13:06 +01:00
Andrey Antukh a9733c792d Make check-fn completly lazy 2025-12-09 11:13:06 +01:00
Belén Albeza 7be8ac3fd7 🐛 Fix internal error while importing a library 2025-12-09 11:10:32 +01:00
Pablo Alba b0351be724 🐛 Fix switch variants with paths 2025-12-09 11:08:55 +01:00
Elena Torro 9216d965ef 🔧 Update rendering settings to smooth render 2025-12-09 10:43:33 +01:00
Andrey Antukh d04fdb5fbd Make the dist bundle use consistent and cache-aware uris (#7911) 2025-12-09 08:05:28 +01:00
Ingrid Pigueron 4f3ca6422c 🌐 Add translations for: French
Currently translated at 96.8% (1931 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2025-12-08 19:40:14 +01:00
Nicola Bortoletto 1c03457fda 🌐 Add translations for: Italian
Currently translated at 99.8% (1991 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-12-06 07:00:19 +00:00
Eva Marco 81e0e4f222 ♻️ Replace token form files (#7896)
* ♻️ Replace shadow form

* ♻️ Rename files and components

* ♻️ Replace offsetx and offsety names

* ♻️ Replace form file for new form component using new form system

* ♻️ Rename files and props
2025-12-05 17:04:07 +01:00
Andrey Antukh b8392b3731 🐛 Fix regression on sending team invitations (#7912) 2025-12-05 12:36:06 +01:00
Andrey Antukh 935728aa39 🔧 Backport build-tag github workflow from develop 2025-12-05 10:26:01 +01:00
Andrey Antukh 77dba477ca 🔧 Backport build-tag github workflow from develop 2025-12-05 10:25:03 +01:00
Eva Marco b6598d1f07 🐛 Fix scrollbar on color modal (#7906) 2025-12-05 09:55:41 +01:00
Yamila Moreno f13b3c8737 🔧 Fix bug in Github Actions (#7908) 2025-12-04 20:24:33 +01:00
alonso.torres 520e979363 🐛 Fix problem when exporting texts 2025-12-04 17:32:54 +01:00
Yamila Moreno a0f8559ffc 🔧 Add ci/cd for nitrate-module (#7905) 2025-12-04 16:02:29 +01:00
Xaviju bf1dc21c75 💄 Hide themes & sets panels when none active (#7902) 2025-12-04 14:11:57 +01:00
Alejandro Alonso 46c20a993f Merge pull request #7904 from penpot/niwinz-staging-fix-invitation-resend
🐛 Fix exception on resending invitation
2025-12-04 11:56:07 +01:00
Andrey Antukh 0e0106f69a 🐛 Add correct assertion on create-invitation fn 2025-12-04 11:38:32 +01:00
Andrey Antukh 19bb69cc60 Improve invalid schema error report 2025-12-04 11:38:16 +01:00
Alejandro Alonso 504eb70988 Merge pull request #7885 from penpot/niwinz-staging-bugfix-2
🐛 Make workspace palette reposition on left sidebar collapse
2025-12-04 11:19:20 +01:00
Andrey Antukh a38f425dd3 Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-04 11:06:48 +01:00
Xaviju 75a2331edf 💄 Set low-emphasis color for both light/dark modes (#7884) 2025-12-04 11:04:07 +01:00
VKing9 74d4b9b045 🌐 Add translations for: Hindi
Currently translated at 100.0% (1994 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2025-12-04 11:00:32 +01:00
Alejandro Alonso c2b4c9907d Merge pull request #7886 from penpot/niwinz-staging-bugfix-3
🐛 Fix casing on a translation of export files modal option
2025-12-04 10:59:51 +01:00
Alejandro Alonso bd5bbcae26 Merge pull request #7894 from penpot/niwinz-staging-bugfix-4
🐛 Fix incorrect interaction betwen hower and scroll on assets sidebar
2025-12-04 10:58:54 +01:00
Andrey Antukh 84273508ad 🐛 Fix incorrect interaction betwen hower and scroll on assets sidebar 2025-12-04 10:56:29 +01:00
Andrey Antukh 9245ba6bc2 💄 Adapt component style for assets-local-library on sidebar assets 2025-12-04 10:55:57 +01:00
Andrey Antukh 4be046406d Pass direct args instead of a vector to toggle-values on sidebar assets 2025-12-04 10:55:57 +01:00
Alejandro Alonso 84c747cd31 Merge pull request #7883 from penpot/niwinz-staging-bugfix
🐛 Fix exception on paste text on comments input
2025-12-04 10:32:07 +01:00
Alejandro Alonso 0036a9a0cd Merge pull request #7865 from penpot/niwinz-staging-audit
 Add minor improvements to the audit module
2025-12-04 10:04:00 +01:00
Alejandro Alonso 2105c3a68c Merge pull request #7866 from penpot/niwinz-staging-fix-emails
🐛 Change internal ordering on how email parts are assembled
2025-12-04 09:56:22 +01:00
Belén Albeza 38efa88460 🐛 Fix unpublish library modal not scrolling file list (#7892)
* 🐛 Fix unpublish library modal not scrolling when the linked files list is too long

* 💄 Remove deprecated tokens in unpublish library modal

* 🔧 Update CHANGELOG
2025-12-03 22:41:20 +01:00
Pablo Alba 6e254c2cf4 🐛 Fix change of library on swap (#7898) 2025-12-03 22:40:23 +01:00
Andrey Antukh 416980f063 🐛 Fix issue on render template on dist bundle (#7899) 2025-12-03 20:48:02 +01:00
Andrey Antukh f76710296c Merge remote-tracking branch 'origin/staging' into develop 2025-12-03 18:52:28 +01:00
Andrey Antukh 6251fa6b22 🐛 Close other open context menus on open a context menu (#7895) 2025-12-03 18:50:00 +01:00
alonso.torres aedd8cc11e 🐛 Fix problem when renaming variants in plugins 2025-12-03 17:42:17 +01:00
Andrey Antukh d1379c55f6 Make i18n translation files load on demand 2025-12-03 16:44:37 +01:00
Andrey Antukh b125c7b5a3 Merge remote-tracking branch 'origin/staging' into develop 2025-12-03 13:55:01 +01:00
Andrey Antukh 496d37795b Adapt docker images nginx config template to latest changes (#7891) 2025-12-03 13:45:18 +01:00
Alonso Torres 2f0853f5cc 🐛 Fix problem with variant plugins api (#7890) 2025-12-03 13:27:32 +01:00
Juan de la CruzandAndrey Antukh 648e660bcf 🎉 Add new content and images for the slides of 2.12 (#7874)
* 🎉 Add new slide's content

* 🎉 Add new slides images

* 📎 Fix clj fmt

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2025-12-03 13:26:55 +01:00
Andrey Antukh 9f6899007a Merge remote-tracking branch 'origin/staging' into develop 2025-12-03 13:10:30 +01:00
Slava "nerfur" Voronzoff bee2f70bfa 🐛 Add missing amd64 option on arch detection code on mange.sh script
Signed-off-by: Slava "nerfur" Voronzoff <nerfur@gmail.com>
2025-12-03 13:09:49 +01:00
Pablo Alba 00f8eac8fa 🐛 Fix can't delete unsaved variant prop (#7878) 2025-12-03 13:03:17 +01:00
Dalai FelintoandDalai Felinto df7caacb45 🐛 Fix crash in token grid view due to tooltip validation (#7887)
The color tokens in grid view have a tooltip which is a map.
This is done so the frontend can render:

```
Name: foo
Resolved value: #000000
```

However the validation scheme for tooltips was only accepting functions
and strings.

---

How to reproduce the original (unreported) crash:
* Create a color token
* Create a shape, add a fill
* Pick a color, chose the Token options
* Click on the Grid View

Crash: `{:hint "invalid props on component tooltip*\n\n  -> 'content'
    should be a string\n"}`

Signed-off-by: Dalai Felinto <dalai@blender.org>
Co-authored-by: Dalai Felinto <dalai@blender.org>
2025-12-03 13:01:36 +01:00
Marina López 641df77834 🐛 Fix wrong board size presets in Android (#7888) 2025-12-03 12:52:47 +01:00
Marina López 49bbdfb257 🐛 Fix U and E icon displayed in project list (#7875)
* 🐛 Fix U and E icon displayed in project lis

* 🐛 Fix U and E icon displayed in project list
2025-12-03 12:50:51 +01:00
Aitor Moreno 4e84deca44 Merge pull request #7879 from penpot/elenatorro-12797-fix-update-spans
🐛 Fix paragraph with text spans with multiple styles
2025-12-03 11:30:17 +01:00
Aitor Moreno 0d21e52068 🐛 Fix applyStylesTo entire selection 2025-12-03 11:07:33 +01:00
alonso.torres 1b29e9a50f 🐛 Fix race condition with fix fonts patch 2025-12-03 10:39:05 +01:00
Andrey Antukh 94af978be8 🐛 Fix casing on a translation of export files modal option 2025-12-03 10:22:45 +01:00
Andrey Antukh feababe2a8 🐛 Make workspace palette reposition on left sidebar collapse 2025-12-03 09:56:14 +01:00
Andrey Antukh 5ef06685fc 💄 Add cosmetic improvements to workspace palette component 2025-12-03 09:38:23 +01:00
Elena Torro 9f567c3bf4 🐛 Fix italic variant 2025-12-03 08:59:25 +01:00
Elena Torro 1ba15e5d10 🐛 Do not merge fill styles 2025-12-03 08:55:11 +01:00
Andrey Antukh 57fcec5afc 🐛 Make from-synthetic-clipboard-event function return always a stream
Causes an execption on steam processing when it returns nil
2025-12-03 08:32:38 +01:00
Andrey Antukh 58f82da61e 🐛 Fix exception on paste text on comments input 2025-12-03 08:20:58 +01:00
Andrey Antukh a28c5b61ca 💄 Adapt viewport paste code codestyle
And remove some not necessary constructions
2025-12-03 08:09:13 +01:00
Yaron Shahrabani 60df56caa3 🌐 Add translations for: Hebrew
Currently translated at 99.6% (1988 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2025-12-02 21:00:32 +00:00
Andrey Antukh 53aad7bc15 Merge remote-tracking branch 'origin/staging' into develop 2025-12-02 17:43:34 +01:00
Andrey Antukh 9123d199b7 🐛 Fix scripts/fmt 2025-12-02 17:43:21 +01:00
alonso.torres 37e45a8bbf 🐛 Fix race condition with text and type 2025-12-02 17:28:20 +01:00
alonso.torres 3471d40f46 🐛 Fix problem with boolean shapes updates 2025-12-02 17:28:20 +01:00
Elena Torro c6b64a8e39 🐛 Fix selectAll on mixed span styles 2025-12-02 16:50:48 +01:00
Elena Torro 511e80c948 🐛 Fix merge fill styles when there are multiple fills 2025-12-02 16:50:04 +01:00
Elena Torró f5a640d104 Merge pull request #7876 from penpot/ladybenko-12805-slow-loading
🐛 Fix viewport not being fully drawn on first load until a mouse …
2025-12-02 15:31:43 +01:00
Belén Albeza 3ae7c514e4 🐛 Fix viewport not being fully drawn on first load until a mouse hover 2025-12-02 15:06:28 +01:00
Andrey Antukh 57297741f5 Merge remote-tracking branch 'origin/staging' into develop 2025-12-02 13:28:50 +01:00
Andrey Antukh eeaf28bb25 📎 Disable caddy logging 2025-12-02 13:27:09 +01:00
Dalai Felinto d63d692d34 🐛 Fix mask issues with component swap #7675
The logic to swap a component would delete the swapped out component
first before bringing in the new one.

In the process of doing so, the sanitization code would unmask the
group, now orphan of its mask shape component, when it was the first
element of the group.

The fix  was to pass an optional argument to the generate-delete-shapes
function to ignore mask in special cases like this.

Signed-off-by: Dalai Felinto <dalai@blender.org>
2025-12-02 12:31:44 +01:00
alonso.torres fad9ed1c48 🐛 Fix problem with reordering layers 2025-12-02 12:27:00 +01:00
alonso.torres 0caaefefea 🐛 Fix outline with single click text creation 2025-12-02 11:08:58 +01:00
Elena Torro b179aa79b1 🐛 Fix create empty text on click regression 2025-12-02 11:08:58 +01:00
Andrey Antukh 6b8091bb90 Make devenv https and http2 capable (#7871)
Making it more similar on how it runs on production
environments and improves large amount of files loading
thanks to http2.
2025-12-02 10:49:37 +01:00
Andrey Antukh fe72d0af82 Add self-signed cert to caddy (#7872) 2025-12-02 10:45:26 +01:00
Aitor Moreno 405ddb60d8 🐛 Fix letter spacing applied to paragraph 2025-12-02 10:45:19 +01:00
Luis de Dios ef68081d1d 🎉 Add prototype tab UI tweaks (#7832)
* 🎉 Add prototype tab UI tweaks

* 📎 PR changes
2025-12-02 10:44:16 +01:00
Madalena MeloandAndres Gonzalez bba02473d5 📚 Update subtitles in the new user guide cards (#7823)
Co-authored-by: Andres Gonzalez <andres.gonzalez79@gmail.com>
2025-12-02 09:21:05 +01:00
Andrey Antukh 4ed49cdc5d Make devenv https and http2 capable (#7871)
Making it more similar on how it runs on production
environments and improves large amount of files loading
thanks to http2.
2025-12-01 20:43:23 +01:00
Elena Torró 95c0d42d5b Merge pull request #7868 from penpot/alotor-fix-flex-tools
🐛 Fix visual feedback on padding/margin/gaps modified
2025-12-01 17:51:44 +01:00
alonso.torres 721b337511 🐛 Fix visual feedback on padding/margin/gaps modified 2025-12-01 16:31:15 +01:00
Elena Torró 359379be09 Merge pull request #7867 from penpot/azazeln28-add-text-editor-v2-tests-to-staging
 Add text editor v2 integration tests
2025-12-01 16:11:25 +01:00
Aitor Moreno 876d5783cf Add text editor v2 integration tests 2025-12-01 15:56:52 +01:00
Elena Torro 786f73767b 🔧 Normalize font attributes to support old formats 2025-12-01 14:59:24 +01:00
Andrey Antukh 50f9eedcdf Merge remote-tracking branch 'origin/staging' into develop 2025-12-01 14:33:38 +01:00
Andrey Antukh 77c9d8a2c8 🐛 Revert exporter dockerfile changes 2025-12-01 14:32:00 +01:00
Andrey Antukh 95b7784a42 🐛 Change internal ordering on how email parts are assembled
This fixes the html email rendering on gmail. Other clients (like proton,
emailcatcher) properly renders html independently of the order of parts
on the multipart email structure but gmail requires that html should be
the last one.
2025-12-01 14:27:21 +01:00
Andrey Antukh 4690f740b9 Add minor improvements to the audit module 2025-12-01 13:57:55 +01:00
Xaviju 529c4eb38a 💄 Avoid code tab overflow (#7854) 2025-12-01 11:37:37 +01:00
Andrey Antukh c3a9919c4d 🐛 Fix typo on exporter dockerfile 2025-12-01 11:19:41 +01:00
Eva Marco efe74e62e8 🎉 Replace font family form (#7825) 2025-12-01 11:17:25 +01:00
Juanfran 10a2732a55 Merge pull request #7863 from penpot/niwinz-staging-improve-yarn-independency
 Use setup script on exporter instead of direct commands
2025-12-01 10:13:58 +01:00
Eva Marco 456afe46de 🎉 Replace font family form (#7784) 2025-12-01 10:11:29 +01:00
Andrey Antukh 4282cdcd2c Merge remote-tracking branch 'origin/staging' into staging-render 2025-12-01 10:11:06 +01:00
Andrey Antukh 964ef799c2 🔥 Remove core.spec usage on common and frontend 2025-12-01 09:30:21 +01:00
Andrey Antukh d34b6b88b6 Remove malli dev stuff from cljs build
It only used on backend.
2025-12-01 09:30:21 +01:00
Andrey Antukh 9a58f0e954 🔧 Disable code motion on shadow config 2025-12-01 09:30:21 +01:00
Andrey Antukh adaf8be56d Use sm/coercer on app.render entry point 2025-12-01 09:30:21 +01:00
Andrey Antukh 2f1b99fa53 ♻️ Use ESM target for build frontend 2025-12-01 09:30:21 +01:00
Andrey Antukh 5080fcc594 🔥 Remove unused require of edn reader on loggin ns 2025-12-01 09:30:21 +01:00
Andrey Antukh ea2d3758f0 Merge remote-tracking branch 'origin/staging' into develop 2025-12-01 09:28:49 +01:00
Andrey Antukh 40e3617138 Use setup script on exporter instead of direct commands 2025-12-01 09:23:11 +01:00
Alejandro Alonso e889413f26 🐛 Fix nested shadows clipping 2025-12-01 09:22:23 +01:00
Andrey Antukh b18c421415 📎 Update .gitignore 2025-12-01 09:20:33 +01:00
Andrey Antukh e7029f2182 Make automatic workflows not dependent on yarn 2025-12-01 08:17:52 +01:00
Elena Torró 115273b478 Merge pull request #7852 from penpot/alotor-flex-issues
🐛 Fix flex problems in new render
2025-11-28 14:10:42 +01:00
Elena Torró fdddd3284a Merge pull request #7859 from penpot/ladybenko-12801-fix-mismatched-fonts
🐛 Fix mismatch between fonts for rendered and selected text when no fallback fonts apply
2025-11-28 14:10:17 +01:00
Belén Albeza 51385a04a0 🐛 Fix mismatch between fonts for rendered and selected text when no fallback fonts apply 2025-11-28 13:54:17 +01:00
Alonso Torres 2c3becb408 🐛 Fix problem with plugins content attribute (#7835) 2025-11-28 13:41:27 +01:00
Belén Albeza f96ed8ccd6 Fix playwright tests 2025-11-28 13:25:13 +01:00
Belén Albeza bda5de5c1b 🔧 Update google fonts list 2025-11-28 13:25:13 +01:00
Juanfran 94c15916e2 Merge pull request #7857 from penpot/niwinz-develop-prepare-for-pnpm
 Make automatic workflows not dependent on yarn
2025-11-28 13:07:30 +01:00
Andrey Antukh ed0f3c3595 Make automatic workflows not dependent on yarn 2025-11-28 12:26:56 +01:00
alonso.torres 59f3b4db4c 🐛 Fix problem with auto-size and element margins 2025-11-28 12:12:19 +01:00
alonso.torres 7ee03ad911 🐛 Fix problem with grid layout editor 2025-11-28 12:12:09 +01:00
alonso.torres 130b8c8214 🐛 Fix problems with flex layout in new render 2025-11-28 10:49:55 +01:00
alonso.torres 0198d41757 🐛 Fix crash when cleanup 2025-11-28 10:44:54 +01:00
alonso.torres 567a955151 🐛 Fix problem with change gap/margin/padding 2025-11-28 10:44:38 +01:00
Xaviju a4e6aa0588 💄 Limit inspect layer info message to avoid overflow (#7847) 2025-11-28 10:19:02 +01:00
Nicola Bortoletto 34da754357 🌐 Add translations for: Italian
Currently translated at 98.9% (1973 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2025-11-28 05:00:28 +00:00
alonso.torres c2014a37b4 🐛 Fix problem when pasting elements in reverse flex layout 2025-11-27 18:02:34 +01:00
alonso.torres 6611fbd13b 🐛 Fix problem when drag+duplicate a full grid 2025-11-27 18:02:34 +01:00
Andrey Antukh b5a6867058 Merge remote-tracking branch 'origin/staging' into develop 2025-11-27 18:01:08 +01:00
Andrey Antukh 7fe20b65dc 🔧 Add more cache efficient configuration for devenv nginx 2025-11-27 17:59:12 +01:00
Andrey Antukh e5638cd769 ⬆️ Update clojure tools version on devenv 2025-11-27 17:58:56 +01:00
Eva Marco 8e79dfcb82 🐛 Fix input variant 2025-11-27 17:54:11 +01:00
Eva Marco 508db99a57 🐛 Restore empty field error on dimension, text-case and color forms 2025-11-27 17:54:11 +01:00
Andrey Antukh 3c6c9894da 🐛 Restore empty value error on border radius token form 2025-11-27 17:54:11 +01:00
Andrey Antukh 972b23e6c0 🐛 Fix incorect pred build on ::sm/text schema 2025-11-27 17:54:11 +01:00
Andrey Antukh 28f550d533 🔥 Remove commented code 2025-11-27 17:54:11 +01:00
Elena Torró 2b20f75fd4 Merge pull request #7837 from penpot/ladybenko-12719-fix-editor-unicode-fonts
🐛 Fix editor not using fallback fonts for selected text
2025-11-27 17:37:00 +01:00
Belén Albeza 4d6d7a6a3d 🐛 Fix emoji font not being used as fallback in text editor dom 2025-11-27 17:23:20 +01:00
Andrey Antukh 0f88253dd5 Merge remote-tracking branch 'origin/staging' into develop 2025-11-27 16:11:36 +01:00
Andrey Antukh db1ab7be69 📎 Run worker bundling serially on devenv 2025-11-27 16:09:15 +01:00
Andrey Antukh fcbe9d92dc 🐛 Fix unexpected exception on rendering feedback email
Looks like a bug on selmer library
2025-11-27 16:09:15 +01:00
Andrey Antukh 9998ce0bb4 🔥 Remove fipps direct dependency 2025-11-27 16:09:15 +01:00
Andrey Antukh 6061391c89 Don't require cljs.analyzer api under cljs on data.macros
Reduces the final production bundle size
2025-11-27 16:09:15 +01:00
Andrey Antukh eabf6e36ed Remove a level of indentation on subscriptions-dashboard tests 2025-11-27 16:09:15 +01:00
Andrey Antukh 04274e53fa 📎 Fix advanced compilation warnings related to jsdoc 2025-11-27 16:09:15 +01:00
Andrey Antukh 52dd9271a9 🐛 Encode header values as strings on audit archive task 2025-11-27 16:09:15 +01:00
andrés gonzález 8f5a81e179 📚 Add info about boolean variants (#7828) 2025-11-27 16:03:11 +01:00
Alonso Torres a940c08da9 🐛 Fix problem with worker bundling in development (#7844) 2025-11-27 14:13:48 +01:00
Alejandro Alonso 3de4473251 Merge pull request #7845 from penpot/elenatorro-fix-case
🐛 Fix editor vertical align default case
2025-11-27 14:00:12 +01:00
Andrey Antukh 0735140f07 🔧 Change concurrency rules on tests github workflow 2025-11-27 13:46:48 +01:00
Elena Torro dc8a07099d 🐛 Fix vertical align default case 2025-11-27 13:38:51 +01:00
Andrey Antukh 8e3996fbb0 🔧 Change concirrency rules on tests github workflow 2025-11-27 13:16:08 +01:00
Alonso Torres 67762d9450 🐛 Fix problem with worker bundling in development (#7844) 2025-11-27 13:02:47 +01:00
Elena Torró 90dcf04fb0 Merge pull request #7841 from penpot/superalex-fix-boolean-operators-no-selection
🐛 Fix boolean operators no selection
2025-11-27 12:50:16 +01:00
Belén Albeza f84c236e02 🐛 Fix text editor v2 not using fallback fonts for selected text 2025-11-27 12:26:39 +01:00
Alejandro Alonso 63959a22cc 🐛 Fix svg attrs 2025-11-27 12:23:46 +01:00
Alejandro Alonso 8840246425 🐛 Fix bleeding masks 2025-11-27 12:23:46 +01:00
Alejandro Alonso 62ec66cd15 🔧 Adding more e2e tests for nested frames with clipping 2025-11-27 12:23:46 +01:00
Alejandro Alonso e3b87390f6 🐛 Fix nested shadows clipping 2025-11-27 12:23:46 +01:00
Alejandro Alonso d9ab28e6ed 🐛 Fix nested clipping 2025-11-27 12:23:46 +01:00
Belén Albeza 9183dbbc43 🔧 Fix lint error (rust) 2025-11-27 11:51:05 +01:00
Andrey Antukh 74d00473e9 Add missing render-wasm to the ci workflow 2025-11-27 11:51:05 +01:00
Alejandro Alonso 1c70f5a36b 🐛 Fix boolean operatos shown when there is no selection 2025-11-27 11:22:15 +01:00
Andrey Antukh b23e0c0642 Add tempfile storage bucket handler test case (#7839) 2025-11-27 10:27:57 +01:00
Andrey Antukh 7f62652870 Merge remote-tracking branch 'origin/staging' into develop 2025-11-27 09:24:40 +01:00
Marina López db0cbbbc2e 🐛 Fix logic preventing incorrect trial flow in subscription modal (#7831) 2025-11-26 12:08:02 +01:00
alonso.torres 48304bd26f 🐛 Fix issue when exporting files 2025-11-26 12:04:34 +01:00
Elena Torro 60e32bbc71 🐛 Fix text editor vertical align 2025-11-26 11:46:47 +01:00
André Carvalhais 54451608dc 💄 Fix spelling of 'smtp' in email configuration section
Corrected the spelling of 'smtp' in the documentation.

Signed-off-by: André Carvalhais <carvalhais@live.com>
2025-11-26 08:11:27 +01:00
Andrey Antukh 78d31ab11a 🐳 Update devenv docker and compose files
Reuse the already builded imagemagick instead of building
it again on the devenv.
2025-11-26 07:44:56 +01:00
Andrey Antukh 0a80c47901 Merge remote-tracking branch 'origin/staging' into develop 2025-11-26 07:30:42 +01:00
Alejandro Alonso b7727122d5 Merge pull request #7829 from penpot/alotor-fixes
🐛 Fix problem with thumbnails in parallel
2025-11-26 07:21:49 +01:00
alonso.torres 8880f07a6a 🐛 Fix problem with thumbnails in parallel 2025-11-25 17:56:00 +01:00
Oğuz Ersen 39eafae251 🌐 Add translations for: Turkish
Currently translated at 99.8% (1991 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2025-11-25 16:51:25 +00:00
Edgars Andersons e1e09b7f96 🌐 Add translations for: Latvian
Currently translated at 94.0% (1876 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2025-11-25 16:51:24 +00:00
Stephan Paternotte 3b39980f2f 🌐 Add translations for: Dutch
Currently translated at 99.8% (1991 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2025-11-25 16:51:23 +00:00
Anton Palmqvist 223b12d2c7 🌐 Add translations for: Swedish
Currently translated at 99.6% (1987 of 1994 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2025-11-25 16:51:21 +00:00
andrés gonzález aaca2c41d8 📚 Add metadescriptions to some help center pages (#7821) 2025-11-25 17:00:14 +01:00
Yamila Moreno 77f1046fc8 🔧 Add MT notification when a docker image with final tag is built (#7824) 2025-11-25 16:39:42 +01:00
Belén Albeza 33417a4b20 🐛 Fix svg attrs stroke-linecap stroke-linejoin fill-rule 2025-11-25 12:43:40 +01:00
Andrés Moya 2640889dc8 🐛 Fix backwards compatibility importing files with token themes 2025-11-25 10:56:33 +01:00
alonso.torres dd5f3396d1 🐛 Fix problem with layout z-index 2025-11-24 17:48:58 +01:00
Andrey Antukh dedeae8641 🐛 Fix incorrect subscription fetching after profile registration 2025-11-24 14:36:46 +01:00
Andrey Antukh a7552d412a Add explicit network asingation and alias on devenv compose 2025-11-24 14:36:46 +01:00
Aitor Moreno f58475a7c9 🐛 Fix pasting application/transit+json (#7812) 2025-11-24 14:36:24 +01:00
Marina López 00bbb0bfb6 ♻️ Add format and refactor payments 2025-11-24 11:41:03 +01:00
Andrey Antukh d93fe89c12 📎 Backport CI github workflog from develop 2025-11-24 10:48:51 +01:00
Andrey Antukh 553b73a83c ♻️ Replace CircleCI with Github Actions (#7789)
* ♻️ Replace circleci with github actions

* 📎 Add integration test sharding

* 📎 Reuse single build for integration tests shards
2025-11-24 10:44:04 +01:00
Andrey Antukh 00a45cb274 📎 Bump new version on changelog 2025-11-24 09:47:00 +01:00
945 changed files with 145561 additions and 23484 deletions

No files matched your search

-305
View File
@@ -1,305 +0,0 @@
version: 2.1
jobs:
lint:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
steps:
- checkout
- run:
name: "fmt check"
working_directory: "."
command: |
yarn install
yarn run fmt:clj:check
- run:
name: "lint clj common"
working_directory: "."
command: |
yarn run lint:clj:common
- run:
name: "lint clj frontend"
working_directory: "."
command: |
yarn run lint:clj:frontend
- run:
name: "lint clj backend"
working_directory: "."
command: |
yarn run lint:clj:backend
- run:
name: "lint clj exporter"
working_directory: "."
command: |
yarn run lint:clj:exporter
- run:
name: "lint clj library"
working_directory: "."
command: |
yarn run lint:clj:library
test-common:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
environment:
JAVA_OPTS: -Xmx4g -Xms100m -XX:+UseSerialGC
NODE_OPTIONS: --max-old-space-size=4096
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "common/deps.edn"}}-{{ checksum "common/yarn.lock" }}
- run:
name: "JVM tests"
working_directory: "./common"
command: |
clojure -M:dev:test
- run:
name: "NODE tests"
working_directory: "./common"
command: |
yarn install
yarn run test
- save_cache:
paths:
- ~/.m2
- ~/.yarn
- ~/.gitlibs
- ~/.cache/ms-playwright
key: v1-dependencies-{{ checksum "common/deps.edn"}}-{{ checksum "common/yarn.lock" }}
test-frontend:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
environment:
JAVA_OPTS: -Xmx4g -Xms100m -XX:+UseSerialGC
NODE_OPTIONS: --max-old-space-size=4096
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
- run:
name: "install dependencies"
working_directory: "./frontend"
# We install playwright here because the dependent tasks
# uses the same cache as this task so we prepopulate it
command: |
yarn install
yarn run playwright install chromium --with-deps
- run:
name: "lint scss on frontend"
working_directory: "./frontend"
command: |
yarn run lint:scss
- run:
name: "unit tests"
working_directory: "./frontend"
command: |
yarn run test
- save_cache:
paths:
- ~/.m2
- ~/.yarn
- ~/.gitlibs
- ~/.cache/ms-playwright
key: v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
test-library:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
environment:
JAVA_OPTS: -Xmx6g
NODE_OPTIONS: --max-old-space-size=4096
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
- run:
name: Install dependencies and build
working_directory: "./library"
command: |
yarn install
- run:
name: Build and Test
working_directory: "./library"
command: |
./scripts/build
yarn run test
test-components:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
environment:
JAVA_OPTS: -Xmx6g -Xms2g
NODE_OPTIONS: --max-old-space-size=4096
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
- run:
name: Install dependencies
working_directory: "./frontend"
command: |
yarn install
yarn run playwright install chromium
- run:
name: Build Storybook
working_directory: "./frontend"
command: yarn run build:storybook
- run:
name: Serve Storybook and run tests
working_directory: "./frontend"
command: |
npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
"npx http-server storybook-static --port 6006 --silent" \
"npx wait-on tcp:6006 && yarn test:storybook"
test-backend:
docker:
- image: penpotapp/devenv:latest
- image: cimg/postgres:14.5
environment:
POSTGRES_USER: penpot_test
POSTGRES_PASSWORD: penpot_test
POSTGRES_DB: penpot_test
- image: cimg/redis:7.0.5
working_directory: ~/repo
resource_class: medium+
environment:
JAVA_OPTS: -Xmx4g -Xms100m -XX:+UseSerialGC
NODE_OPTIONS: --max-old-space-size=4096
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "backend/deps.edn" }}
- run:
name: "tests"
working_directory: "./backend"
command: |
clojure -M:dev:test --reporter kaocha.report/documentation
environment:
PENPOT_TEST_DATABASE_URI: "postgresql://localhost/penpot_test"
PENPOT_TEST_DATABASE_USERNAME: penpot_test
PENPOT_TEST_DATABASE_PASSWORD: penpot_test
PENPOT_TEST_REDIS_URI: "redis://localhost/1"
- save_cache:
paths:
- ~/.m2
- ~/.gitlibs
key: v1-dependencies-{{ checksum "backend/deps.edn" }}
test-render-wasm:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
environment:
steps:
- checkout
- run:
name: "fmt check"
working_directory: "./render-wasm"
command: |
cargo fmt --check
- run:
name: "lint"
working_directory: "./render-wasm"
command: |
./lint
- run:
name: "cargo tests"
working_directory: "./render-wasm"
command: |
./test
workflows:
penpot:
jobs:
- test-frontend:
requires:
- lint: success
- test-library:
requires:
- lint: success
- test-components:
requires:
- lint: success
- test-backend:
requires:
- lint: success
- test-common:
requires:
- lint: success
- lint
- test-render-wasm
+9
View File
@@ -45,6 +45,15 @@
:potok/reify-type :potok/reify-type
{:level :error} {:level :error}
:redundant-primitive-coercion
{:level :off}
:unused-excluded-var
{:level :off}
:unresolved-excluded-var
{:level :off}
:missing-protocol-method :missing-protocol-method
{:level :off} {:level :off}
@@ -0,0 +1,38 @@
---
name: New Render Bug Report
about: Create a report about the bugs you have found in the new render
title: ''
labels: new render
assignees: claragvinola
---
**Describe the bug**
A clear and concise description of what the bug is.
**Steps to Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots or screen recordings**
If applicable, add screenshots or screen recording to help illustrate your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
+1 -1
View File
@@ -40,7 +40,7 @@ on:
jobs: jobs:
build-bundle: build-bundle:
name: Build and Upload Penpot Bundle name: Build and Upload Penpot Bundle
runs-on: ubuntu-24.04 runs-on: penpot-runner-01
env: env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+6 -1
View File
@@ -7,9 +7,14 @@ jobs:
build-and-push: build-and-push:
name: Build and push DevEnv Docker image name: Build and push DevEnv Docker image
environment: release-admins environment: release-admins
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
steps: steps:
- name: Set common environment variables
run: |
# Each job execution will use its own docker configuration.
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
+32 -1
View File
@@ -19,9 +19,14 @@ on:
jobs: jobs:
build-and-push: build-and-push:
name: Build and Push Penpot Docker Images name: Build and Push Penpot Docker Images
runs-on: ubuntu-24.04-arm runs-on: penpot-runner-02
steps: steps:
- name: Set common environment variables
run: |
# Each job execution will use its own docker configuration.
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
@@ -54,6 +59,7 @@ jobs:
mv penpot/frontend bundle-frontend mv penpot/frontend bundle-frontend
mv penpot/exporter bundle-exporter mv penpot/exporter bundle-exporter
mv penpot/storybook bundle-storybook mv penpot/storybook bundle-storybook
mv penpot/mcp bundle-mcp
popd popd
- name: Set up Docker Buildx - name: Set up Docker Buildx
@@ -66,6 +72,15 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
# To avoid the “429 Too Many Requests” error when downloading
# images from DockerHub for unregistered users.
# https://docs.docker.com/docker-hub/usage/
- name: Login to DockerHub Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.PUB_DOCKER_USERNAME }}
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) - name: Extract metadata (tags, labels)
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -75,6 +90,7 @@ jobs:
backend backend
exporter exporter
storybook storybook
mcp
labels: | labels: |
bundle_version=${{ steps.bundles.outputs.bundle_version }} bundle_version=${{ steps.bundles.outputs.bundle_version }}
@@ -138,6 +154,21 @@ jobs:
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
- name: Build and push MCP Docker image
uses: docker/build-push-action@v6
env:
DOCKER_IMAGE: 'mcp'
BUNDLE_PATH: './bundle-mcp'
with:
context: ./docker/images/
file: ./docker/images/Dockerfile.mcp
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.gh_ref }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
- name: Notify Mattermost - name: Notify Mattermost
if: failure() if: failure()
uses: mattermost/action-mattermost-notify@master uses: mattermost/action-mattermost-notify@master
@@ -0,0 +1,21 @@
name: _NITRATE MODULE
on:
schedule:
- cron: '36 5-20 * * 1-5'
jobs:
build-bundle:
uses: ./.github/workflows/build-bundle.yml
secrets: inherit
with:
gh_ref: "nitrate-module"
build_wasm: "yes"
build_storybook: "yes"
build-docker:
needs: build-bundle
uses: ./.github/workflows/build-docker.yml
secrets: inherit
with:
gh_ref: "nitrate-module"
@@ -0,0 +1,14 @@
name: _STAGING RENDER
on:
schedule:
- cron: '36 5-20 * * 1-5'
jobs:
build-bundle:
uses: ./.github/workflows/build-bundle.yml
secrets: inherit
with:
gh_ref: "staging-render"
build_wasm: "yes"
build_storybook: "yes"
+17 -1
View File
@@ -11,7 +11,7 @@ jobs:
secrets: inherit secrets: inherit
with: with:
gh_ref: ${{ github.ref_name }} gh_ref: ${{ github.ref_name }}
build_wasm: "no" build_wasm: "yes"
build_storybook: "yes" build_storybook: "yes"
build-docker: build-docker:
@@ -21,6 +21,22 @@ jobs:
with: with:
gh_ref: ${{ github.ref_name }} gh_ref: ${{ github.ref_name }}
notify:
name: Notifications
runs-on: ubuntu-24.04
needs: build-docker
steps:
- name: Notify Mattermost
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
🐳 *[PENPOT] Docker image available: ${{ github.ref_name }}*
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra
publish-final-tag: publish-final-tag:
if: ${{ !contains(github.ref_name, '-RC') && !contains(github.ref_name, '-alpha') && !contains(github.ref_name, '-beta') && contains(github.ref_name, '.') }} if: ${{ !contains(github.ref_name, '-RC') && !contains(github.ref_name, '-alpha') && !contains(github.ref_name, '-beta') && contains(github.ref_name, '.') }}
needs: build-docker needs: build-docker
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
- name: Check Commit Type - name: Check Commit Type
uses: gsactions/commit-message-checker@v2 uses: gsactions/commit-message-checker@v2
with: with:
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert).+[^.])$' pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert|Reapply).+[^.])$'
flags: 'gm' flags: 'gm'
error: 'Commit should match CONTRIBUTING.md guideline' error: 'Commit should match CONTRIBUTING.md guideline'
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request
@@ -0,0 +1,125 @@
name: Plugins/api-doc deployer
on:
push:
branches:
- develop
- staging
- main
paths:
- 'plugins/libs/plugin-types/index.d.ts'
- 'plugins/libs/plugin-types/REAME.md'
- 'plugins/tools/typedoc.css'
- 'plugins/CHANGELOG.md'
- 'plugins/wrangler-penpot-plugins-api-doc.toml'
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch'
type: choice
required: true
default: 'develop'
options:
- develop
- staging
- main
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Extract some useful variables
id: vars
run: |
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.vars.outputs.gh_ref }}
# START: Setup Node and PNPM enabling cache
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Enable PNPM
working-directory: ./plugins
shell: bash
run: |
corepack enable;
corepack install;
- name: Get pnpm store path
id: pnpm-store
working-directory: ./plugins
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
# END: Setup Node and PNPM enabling cache
- name: Install deps
working-directory: ./plugins
shell: bash
run: |
pnpm install --no-frozen-lockfile;
pnpm add -D -w wrangler@latest;
- name: Build docs
working-directory: plugins
shell: bash
run: pnpm run build:doc
- name: Select Worker name
run: |
REF="${{ steps.vars.outputs.gh_ref }}"
case "$REF" in
main)
echo "WORKER_NAME=penpot-plugins-api-doc-pro" >> $GITHUB_ENV
echo "WORKER_URI=doc.plugins.penpot.app" >> $GITHUB_ENV ;;
staging)
echo "WORKER_NAME=penpot-plugins-api-doc-pre" >> $GITHUB_ENV
echo "WORKER_URI=doc.plugins.penpot.dev" >> $GITHUB_ENV ;;
develop)
echo "WORKER_NAME=penpot-plugins-api-doc-hourly" >> $GITHUB_ENV
echo "WORKER_URI=doc.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
*) echo "Unsupported branch ${REF}" && exit 1 ;;
esac
- name: Set the custom url
working-directory: plugins
shell: bash
run: |
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" wrangler-penpot-plugins-api-doc.toml
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
workingDirectory: plugins
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config wrangler-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }}
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
❌ 🧩📚 *[PENPOT PLUGINS] Error deploying API documentation.*
📄 Triggered from ref: `${{ inputs.gh_ref }}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra
@@ -0,0 +1,127 @@
name: Plugins/package deployer
on:
# Deploy package from manual action
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch'
type: choice
required: true
default: 'develop'
options:
- develop
- staging
- main
plugin_name:
description: 'Pluging name (like plugins/apps/<plugin_name>-plugin)'
type: string
required: true
workflow_call:
inputs:
gh_ref:
description: 'Name of the branch'
type: string
required: true
default: 'develop'
plugin_name:
description: 'Publig name (from plugins/apps/<plugin_name>-plugin)'
type: string
required: true
permissions:
contents: read
jobs:
deploy:
runs-on: penpot-runner-01
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.gh_ref }}
# START: Setup Node and PNPM enabling cache
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Enable PNPM
working-directory: ./plugins
shell: bash
run: |
corepack enable;
corepack install;
- name: Get pnpm store path
id: pnpm-store
working-directory: ./plugins
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
# END: Setup Node and PNPM enabling cache
- name: Install deps
working-directory: ./plugins
shell: bash
run: |
pnpm install --no-frozen-lockfile;
pnpm add -D -w wrangler@latest;
- name: "Build package for ${{ inputs.plugin_name }}-plugin"
working-directory: plugins
shell: bash
run: npx nx build ${{ inputs.plugin_name }}-plugin
- name: Select Worker name
run: |
REF="${{ inputs.gh_ref }}"
case "$REF" in
main)
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pro" >> $GITHUB_ENV
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.app" >> $GITHUB_ENV ;;
staging)
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pre" >> $GITHUB_ENV
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.dev" >> $GITHUB_ENV ;;
develop)
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-hourly" >> $GITHUB_ENV
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
*) echo "Unsupported branch ${REF}" && exit 1 ;;
esac
- name: Set the custom url
working-directory: plugins
shell: bash
run: |
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" apps/${{ inputs.plugin_name }}-plugin/wrangler.toml
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
workingDirectory: plugins
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config apps/${{ inputs.plugin_name }}-plugin/wrangler.toml --name ${{ env.WORKER_NAME }}
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
❌ 🧩📦 *[PENPOT PLUGINS] Error deploying ${{ env.WORKER_NAME }}.*
📄 Triggered from ref: `${{ inputs.gh_ref }}`
Plugin name: `${{ inputs.plugin_name }}-plugin`
Cloudflare worker name: `${{ env.WORKER_NAME }}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra
@@ -0,0 +1,143 @@
name: Plugins/packages deployer
on:
push:
branches:
- develop
- staging
- main
paths:
- 'plugins/apps/*-plugin/**'
- 'libs/plugins-styles/**'
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch'
type: choice
required: true
default: 'develop'
options:
- develop
- staging
- main
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
colors_to_tokens: ${{ steps.filter.outputs.colors_to_tokens }}
create_palette: ${{ steps.filter.outputs.create_palette }}
lorem_ipsum: ${{ steps.filter.outputs.lorem_ipsum }}
rename_layers: ${{ steps.filter.outputs.rename_layers }}
contrast: ${{ steps.filter.outputs.contrast }}
icons: ${{ steps.filter.outputs.icons }}
poc_state: ${{ steps.filter.outputs.poc_state }}
table: ${{ steps.filter.outputs.table }}
# [For new plugins]
# Add more outputs here
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
colors_to_tokens:
- 'plugins/apps/colors-to-tokens-plugin/**'
- 'libs/plugins-styles/**'
contrast:
- 'plugins/apps/contrast-plugin/**'
- 'libs/plugins-styles/**'
create_palette:
- 'plugins/apps/create-palette-plugin/**'
- 'libs/plugins-styles/**'
icons:
- 'plugins/apps/icons-plugin/**'
- 'libs/plugins-styles/**'
lorem_ipsum:
- 'plugins/apps/lorem-ipsum-plugin/**'
- 'libs/plugins-styles/**'
rename_layers:
- 'plugins/apps/rename-layers-plugin/**'
- 'libs/plugins-styles/**'
table:
- 'plugins/apps/table-plugin/**'
- 'libs/plugins-styles/**'
# [For new plugins]
# Add more plugin filters here
# another_plugin:
# - 'plugins/apps/another-plugin/**'
# - 'libs/plugins-styles/**'
colors-to-tokens-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.colors_to_tokens == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: colors-to-tokens
contrast-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.contrast == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: contrast
create-palette-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.create_palette == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: create-palette
icons-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.icons == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: icons
lorem-ipsum-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.lorem_ipsum == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: lorem-ipsum
rename-layers-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.rename_layers == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: rename-layers
table-plugin:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.table == 'true'
uses: ./.github/workflows/plugins-deploy-package.yml
secrets: inherit
with:
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
plugin_name: table
# [For new plugins]
# Add more jobs for other plugins below, following the same pattern
# another-plugin:
# needs: detect-changes
# if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.another_plugin == 'true'
# uses: ./.github/workflows/plugins-deploy-package.yml
# secrets: inherit
# with:
# gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
# plugin_name: another
@@ -0,0 +1,123 @@
name: Plugins/styles-doc deployer
on:
push:
branches:
- develop
- staging
- main
paths:
- 'plugins/apps/example-styles/**'
- 'plugins/libs/plugins-styles/**'
- 'plugins/wrangler-penpot-plugins-styles-doc.toml'
workflow_dispatch:
inputs:
gh_ref:
description: 'Name of the branch'
type: choice
required: true
default: 'develop'
options:
- develop
- staging
- main
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Extract some useful variables
id: vars
run: |
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.vars.outputs.gh_ref }}
# START: Setup Node and PNPM enabling cache
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Enable PNPM
working-directory: ./plugins
shell: bash
run: |
corepack enable;
corepack install;
- name: Get pnpm store path
id: pnpm-store
working-directory: ./plugins
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
# END: Setup Node and PNPM enabling cache
- name: Install deps
working-directory: ./plugins
shell: bash
run: |
pnpm install --no-frozen-lockfile;
pnpm add -D -w wrangler@latest;
- name: Build styles
working-directory: plugins
shell: bash
run: npx nx run example-styles:build
- name: Select Worker name
run: |
REF="${{ steps.vars.outputs.gh_ref }}"
case "$REF" in
main)
echo "WORKER_NAME=penpot-plugins-styles-doc-pro" >> $GITHUB_ENV
echo "WORKER_URI=styles-doc.plugins.penpot.app" >> $GITHUB_ENV ;;
staging)
echo "WORKER_NAME=penpot-plugins-styles-doc-pre" >> $GITHUB_ENV
echo "WORKER_URI=styles-doc.plugins.penpot.dev" >> $GITHUB_ENV ;;
develop)
echo "WORKER_NAME=penpot-plugins-styles-doc-hourly" >> $GITHUB_ENV
echo "WORKER_URI=styles-doc.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
*) echo "Unsupported branch ${REF}" && exit 1 ;;
esac
- name: Set the custom url
working-directory: plugins
shell: bash
run: |
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" wrangler-penpot-plugins-styles-doc.toml
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
workingDirectory: plugins
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config wrangler-penpot-plugins-styles-doc.toml --name ${{ env.WORKER_NAME }}
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
❌ 🧩💅 *[PENPOT PLUGINS] Error deploying Styles documentation.*
📄 Triggered from ref: `${{ inputs.gh_ref }}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra
+45
View File
@@ -0,0 +1,45 @@
name: "MCP CI"
on:
pull_request:
branches:
- develop
- staging
- main
types:
- opened
- synchronize
paths:
- 'mcp/**'
push:
branches:
- develop
- staging
- main
paths:
- 'mcp/**'
jobs:
test:
name: "Test"
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup
working-directory: ./mcp
run: ./scripts/setup
- name: Check
working-directory: ./mcp
run: |
pnpm run fmt:check;
pnpm -r run build;
pnpm -r run types:check;
+278 -114
View File
@@ -1,4 +1,4 @@
name: "CI: Tests" name: "CI"
defaults: defaults:
run: run:
@@ -8,134 +8,196 @@ on:
pull_request: pull_request:
types: types:
- opened - opened
- edited
- reopened
- synchronize - synchronize
push: push:
branches: branches:
- develop - develop
- staging - staging
concurrency:
group: ${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
# lint: lint:
# name: "Code Linter" name: "Linter"
# runs-on: ubuntu-24.04 runs-on: penpot-runner-02
# container: penpotapp/devenv:latest container: penpotapp/devenv:latest
# steps: steps:
# - name: Checkout repository - name: Checkout repository
# uses: actions/checkout@v4 uses: actions/checkout@v4
# - name: Check clojure code format - name: Check clojure code format
# run: | run: |
# corepack enable; ./scripts/lint
# corepack install;
# yarn install
# yarn run fmt:clj:check
# test-common: test-common:
# name: "Common Tests" name: "Common Tests"
# runs-on: ubuntu-24.04 runs-on: penpot-runner-02
# container: penpotapp/devenv:latest container: penpotapp/devenv:latest
# steps: steps:
# - name: Checkout repository - name: Checkout repository
# uses: actions/checkout@v4 uses: actions/checkout@v4
# - name: Run tests on JVM - name: Run tests on JVM
# working-directory: ./common working-directory: ./common
# run: | run: |
# clojure -M:dev:test clojure -M:dev:test
# - name: Run tests on NODE - name: Run tests on NODE
# working-directory: ./common working-directory: ./common
# run: | run: |
# corepack enable; ./scripts/test
# corepack install;
# yarn install;
# yarn run test;
# test-frontend: test-plugins:
# name: "Frontend Tests" name: Plugins Runtime Linter & Tests
# runs-on: ubuntu-24.04 runs-on: penpot-runner-02
# container: penpotapp/devenv:latest container: penpotapp/devenv:latest
# steps: steps:
# - name: Checkout repository - uses: actions/checkout@v4
# uses: actions/checkout@v4
# - name: Unit Tests - name: Setup Node
# working-directory: ./frontend id: setup-node
# run: | uses: actions/setup-node@v6
# corepack enable; with:
# corepack install; node-version-file: .nvmrc
# yarn install;
# yarn run test;
# - name: Component Tests - name: Install deps
# working-directory: ./frontend working-directory: ./plugins
# run: | shell: bash
# yarn run playwright install chromium --with-deps; run: |
# yarn run build:storybook corepack enable;
corepack install;
pnpm install;
# npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \ - name: Run Lint
# "npx http-server storybook-static --port 6006 --silent" \ working-directory: ./plugins
# "npx wait-on tcp:6006 && yarn test:storybook" run: pnpm run lint
# - name: Check SCSS Format - name: Run Format Check
# working-directory: ./frontend working-directory: ./plugins
# run: | run: pnpm run format:check
# yarn run lint:scss;
# test-backend: - name: Run Test
# name: "Backend Tests" working-directory: ./plugins
# runs-on: ubuntu-24.04 run: pnpm run test
# container: penpotapp/devenv:latest
# services: - name: Build runtime
# postgres: working-directory: ./plugins
# image: postgres:17 run: pnpm run build
# # Provide the password for postgres
# env:
# POSTGRES_USER: penpot_test
# POSTGRES_PASSWORD: penpot_test
# POSTGRES_DB: penpot_test
# # Set health checks to wait until postgres has started - name: Build plugins
# options: >- working-directory: ./plugins
# --health-cmd pg_isready run: pnpm run build:plugins
# --health-interval 10s
# --health-timeout 5s
# --health-retries 5
# redis: - name: Build styles
# image: valkey/valkey:9 working-directory: ./plugins
run: pnpm run build:styles-example
# steps: test-frontend:
# - name: Checkout repository name: "Frontend Tests"
# uses: actions/checkout@v4 runs-on: penpot-runner-02
container: penpotapp/devenv:latest
# test-library: steps:
# name: "Library Tests" - name: Checkout repository
# runs-on: ubuntu-24.04 uses: actions/checkout@v4
# container: penpotapp/devenv:latest
# steps: - name: Unit Tests
# - name: Checkout repository working-directory: ./frontend
# uses: actions/checkout@v4 run: |
./scripts/test
# - name: Run tests - name: Component Tests
# working-directory: ./library working-directory: ./frontend
# run: | env:
# corepack enable; VITEST_BROWSER_TIMEOUT: 120000
# corepack install; run: |
# yarn install; ./scripts/test-components
# yarn run build:bundle;
# yarn run test;
test-integration: test-render-wasm:
name: "Integration Tests" name: "Render WASM Tests"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Format
working-directory: ./render-wasm
run: |
cargo fmt --check
- name: Lint
working-directory: ./render-wasm
run: |
./lint
- name: Test
working-directory: ./render-wasm
run: |
./test
test-backend:
name: "Backend Tests"
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
services:
postgres:
image: postgres:17
# Provide the password for postgres
env:
POSTGRES_USER: penpot_test
POSTGRES_PASSWORD: penpot_test
POSTGRES_DB: penpot_test
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: valkey/valkey:9
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run tests
working-directory: ./backend
env:
PENPOT_TEST_DATABASE_URI: "postgresql://postgres/penpot_test"
PENPOT_TEST_DATABASE_USERNAME: penpot_test
PENPOT_TEST_DATABASE_PASSWORD: penpot_test
PENPOT_TEST_REDIS_URI: "redis://redis/1"
run: |
clojure -M:dev:test --reporter kaocha.report/documentation
test-library:
name: "Library Tests"
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run tests
working-directory: ./library
run: |
./scripts/test
build-integration:
name: "Build Integration Bundle"
runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
steps: steps:
@@ -145,29 +207,131 @@ jobs:
- name: Build Bundle - name: Build Bundle
working-directory: ./frontend working-directory: ./frontend
run: | run: |
corepack enable; ./scripts/build 0.0.0
corepack install;
yarn install
yarn run build:app:assets
yarn run build:app
yarn run build:app:libs
- name: Build WASM - name: Store Bundle Cache
working-directory: "./render-wasm" uses: actions/cache@v4
run: | with:
./build release key: "integration-bundle-${{ github.sha }}"
path: frontend/resources/public
test-integration-1:
name: "Integration Tests 1/4"
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
needs: build-integration
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Restore Cache
uses: actions/cache/restore@v4
with:
key: "integration-bundle-${{ github.sha }}"
path: frontend/resources/public
- name: Run Tests - name: Run Tests
working-directory: ./frontend working-directory: ./frontend
run: | run: |
yarn run playwright install chromium --with-deps ./scripts/test-e2e --shard="1/4";
yarn run test:e2e -x --workers=1 --reporter=line
- name: Upload test result - name: Upload test result
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
name: integration-tests-result name: integration-tests-result-1
path: frontend/test-results/
overwrite: true
retention-days: 3
test-integration-2:
name: "Integration Tests 2/4"
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
needs: build-integration
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Restore Cache
uses: actions/cache/restore@v4
with:
key: "integration-bundle-${{ github.sha }}"
path: frontend/resources/public
- name: Run Tests
working-directory: ./frontend
run: |
./scripts/test-e2e --shard="2/4";
- name: Upload test result
uses: actions/upload-artifact@v4
if: always()
with:
name: integration-tests-result-2
path: frontend/test-results/
overwrite: true
retention-days: 3
test-integration-3:
name: "Integration Tests 3/4"
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
needs: build-integration
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Restore Cache
uses: actions/cache/restore@v4
with:
key: "integration-bundle-${{ github.sha }}"
path: frontend/resources/public
- name: Run Tests
working-directory: ./frontend
run: |
./scripts/test-e2e --shard="3/4";
- name: Upload test result
uses: actions/upload-artifact@v4
if: always()
with:
name: integration-tests-result-3
path: frontend/test-results/
overwrite: true
retention-days: 3
test-integration-4:
name: "Integration Tests 4/4"
runs-on: penpot-runner-02
container: penpotapp/devenv:latest
needs: build-integration
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Restore Cache
uses: actions/cache/restore@v4
with:
key: "integration-bundle-${{ github.sha }}"
path: frontend/resources/public
- name: Run Tests
working-directory: ./frontend
run: |
./scripts/test-e2e --shard="4/4";
- name: Upload test result
uses: actions/upload-artifact@v4
if: always()
with:
name: integration-tests-result-4
path: frontend/test-results/ path: frontend/test-results/
overwrite: true overwrite: true
retention-days: 3 retention-days: 3
+2
View File
@@ -5,6 +5,7 @@
!.yarn/releases !.yarn/releases
!.yarn/sdks !.yarn/sdks
!.yarn/versions !.yarn/versions
.pnpm-store
*-init.clj *-init.clj
*.css.json *.css.json
*.jar *.jar
@@ -80,3 +81,4 @@ node_modules
/playwright/.cache/ /playwright/.cache/
/render-wasm/target/ /render-wasm/target/
/**/.yarn/* /**/.yarn/*
/.pnpm-store
+1 -1
View File
@@ -1 +1 @@
v22.19.0 v22.22.0
-40
View File
@@ -1,40 +0,0 @@
dist: xenial
language: generic
sudo: required
cache:
directories:
- $HOME/.m2
services:
- docker
branches:
only:
- master
- develop
install:
- curl -O https://download.clojure.org/install/linux-install-1.10.1.447.sh
- chmod +x linux-install-1.10.1.447.sh
- sudo ./linux-install-1.10.1.447.sh
before_script:
- env | sort
script:
- ./manage.sh build-devenv
- ./manage.sh run-frontend-tests
- ./manage.sh run-backend-tests
- ./manage.sh build-images
- ./manage.sh run
after_script:
- docker images
notifications:
email: false
env:
- NODE_VERSION=10.16.0
+66 -1
View File
@@ -1,6 +1,59 @@
# CHANGELOG # CHANGELOG
## 2.12.0 (Unreleased) ## 2.13.1
### :bug: Bugs fixed
- Fix PDF Exporter outputs empty page when board has A4 format [Taiga #13181](https://tree.taiga.io/project/penpot/issue/13181)
## 2.13.0
### :heart: Community contributions (Thank you!)
- Fix mask issues with component swap (by @dfelinto) [Github #7675](https://github.com/penpot/penpot/issues/7675)
### :sparkles: New features & Enhancements
- Add new Box Shadow Tokens [Taiga #10201](https://tree.taiga.io/project/penpot/us/10201)
- Make i18n translation files load on-demand [Taiga #11474](https://tree.taiga.io/project/penpot/us/11474)
- Add deleted files to dashboard [Taiga #8149](https://tree.taiga.io/project/penpot/us/8149)
### :bug: Bugs fixed
- Fix problem when drag+duplicate a full grid [Taiga #12565](https://tree.taiga.io/project/penpot/issue/12565)
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
- Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797)
- Fix some alignments on inspect tab [Taiga #12915](https://tree.taiga.io/project/penpot/issue/12915)
- Fix color assets from shared libraries not appearing as assets in Selected colors panel [Taiga #12957](https://tree.taiga.io/project/penpot/issue/12957)
- Fix CSS generated box-shadow property [Taiga #12997](https://tree.taiga.io/project/penpot/issue/12997)
- Fix inner shadow selector on shadow token [Taiga #12951](https://tree.taiga.io/project/penpot/issue/12951)
- Fix missing text color token from selected shapes in selected colors list [Taiga #12956](https://tree.taiga.io/project/penpot/issue/12956)
- Fix dropdown option width in Guides columns dropdown [Taiga #12959](https://tree.taiga.io/project/penpot/issue/12959)
- Fix typos on download modal [Taiga #12865](https://tree.taiga.io/project/penpot/issue/12865)
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
- Fix allow negative spread values on shadow token creation [Taiga #13167](https://tree.taiga.io/project/penpot/issue/13167)
- Fix spanish translations on import export token modal [Taiga #13171](https://tree.taiga.io/project/penpot/issue/13171)
- Remove whitespaces from asset export filename [Github #8133](https://github.com/penpot/penpot/pull/8133)
- Fix exception on uploading large fonts [Github #8135](https://github.com/penpot/penpot/pull/8135)
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
- Fix incorrect handling of input values on layout gap and padding inputs [Github #8113](https://github.com/penpot/penpot/issues/8113)
- Fix several race conditions on path editor [Github #8187](https://github.com/penpot/penpot/pull/8187)
- Fix app freeze when introducing an error on a very long token name [Taiga #13214](https://tree.taiga.io/project/penpot/issue/13214)
- Fix import a file with shadow tokens [Taiga #13229](https://tree.taiga.io/project/penpot/issue/13229)
- Fix allow spaces on token description [Taiga #13184](https://tree.taiga.io/project/penpot/issue/13184)
- Fix error when creating a token with an invalid name [Taiga #13219](https://tree.taiga.io/project/penpot/issue/13219)
## 2.12.1
### :bug: Bugs fixed
- Fix setting a portion of text as bold or underline messes things up [Github #7980](https://github.com/penpot/penpot/issues/7980)
- Fix problem with style in fonts input [Taiga #12935](https://tree.taiga.io/project/penpot/issue/12935)
- Fix problem with path editor and right click [Github #7917](https://github.com/penpot/penpot/issues/7917)
## 2.12.0
### :boom: Breaking changes & Deprecations ### :boom: Breaking changes & Deprecations
@@ -61,6 +114,8 @@ example. It's still usable as before, we just removed the example.
### :heart: Community contributions (Thank you!) ### :heart: Community contributions (Thank you!)
- Ensure consistent snap behavior across all zoom levels [Github #7774](https://github.com/penpot/penpot/pull/7774) by [@Tokytome](https://github.com/Tokytome) - Ensure consistent snap behavior across all zoom levels [Github #7774](https://github.com/penpot/penpot/pull/7774) by [@Tokytome](https://github.com/Tokytome)
- Fix crash in token grid view due to tooltip validation (by @dfelinto) [Github #7887](https://github.com/penpot/penpot/pull/7887)
- Enable Hindi translations on the application
### :sparkles: New features & Enhancements ### :sparkles: New features & Enhancements
@@ -71,6 +126,7 @@ example. It's still usable as before, we just removed the example.
- Add new shape validation mechanism for shapes [Github #7696](https://github.com/penpot/penpot/pull/7696) - Add new shape validation mechanism for shapes [Github #7696](https://github.com/penpot/penpot/pull/7696)
- Apply color tokens from sidebar [Taiga #11353](https://tree.taiga.io/project/penpot/us/11353) - Apply color tokens from sidebar [Taiga #11353](https://tree.taiga.io/project/penpot/us/11353)
- Display tokens in the inspect tab [Taiga #9313](https://tree.taiga.io/project/penpot/us/9313) - Display tokens in the inspect tab [Taiga #9313](https://tree.taiga.io/project/penpot/us/9313)
- Refactor clipboard behavior to assess some minor inconsistencies and make pasting binary data faster. [Taiga #12571](https://tree.taiga.io/project/penpot/task/12571)
### :bug: Bugs fixed ### :bug: Bugs fixed
@@ -85,6 +141,15 @@ example. It's still usable as before, we just removed the example.
- Fix shortcut conflict in text editor (increase/decrease font size vs word selection) - Fix shortcut conflict in text editor (increase/decrease font size vs word selection)
- Fix problem with plugins generating code for pages different than current one [Taiga #12312](https://tree.taiga.io/project/penpot/issue/12312) - Fix problem with plugins generating code for pages different than current one [Taiga #12312](https://tree.taiga.io/project/penpot/issue/12312)
- Fix input confirmation behavior is not uniform [Taiga #12294](https://tree.taiga.io/project/penpot/issue/12294) - Fix input confirmation behavior is not uniform [Taiga #12294](https://tree.taiga.io/project/penpot/issue/12294)
- Fix copy/pasting application/transit+json [Taiga #12721](https://tree.taiga.io/project/penpot/issue/12721)
- Fix problem with plugins content attribute [Plugins #209](https://github.com/penpot/penpot-plugins/issues/209)
- Fix U and E icon displayed in project list [Taiga #12806](https://tree.taiga.io/project/penpot/issue/12806)
- Fix unpublish library modal not scrolling a long file list [Taiga #12285](https://tree.taiga.io/project/penpot/issue/12285)
- Fix incorrect interaction betwen hower and scroll on assets sidebar [Taiga #12389](https://tree.taiga.io/project/penpot/issue/12389)
- Fix switch variants with paths [Taiga #12841](https://tree.taiga.io/project/penpot/issue/12841)
- Fix referencing typography tokens on font-family tokens [Taiga #12492](https://tree.taiga.io/project/penpot/issue/12492)
- Fix horizontal scroll on layer panel [Taiga #12843](https://tree.taiga.io/project/penpot/issue/12843)
- Fix unicode handling on email template abbreviation filter [Github #7966](https://github.com/penpot/penpot/pull/7966)
## 2.11.1 ## 2.11.1
+8 -8
View File
@@ -3,7 +3,7 @@
:deps :deps
{penpot/common {:local/root "../common"} {penpot/common {:local/root "../common"}
org.clojure/clojure {:mvn/version "1.12.2"} org.clojure/clojure {:mvn/version "1.12.4"}
org.clojure/tools.namespace {:mvn/version "1.5.0"} org.clojure/tools.namespace {:mvn/version "1.5.0"}
com.github.luben/zstd-jni {:mvn/version "1.5.7-4"} com.github.luben/zstd-jni {:mvn/version "1.5.7-4"}
@@ -28,8 +28,8 @@
com.google.guava/guava {:mvn/version "33.4.8-jre"} com.google.guava/guava {:mvn/version "33.4.8-jre"}
funcool/yetti funcool/yetti
{:git/tag "v11.8" {:git/tag "v11.9"
:git/sha "1d1b33f" :git/sha "5fad7a9"
:git/url "https://github.com/funcool/yetti.git" :git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]} :exclusions [org.slf4j/slf4j-api]}
@@ -39,7 +39,7 @@
metosin/reitit-core {:mvn/version "0.9.1"} metosin/reitit-core {:mvn/version "0.9.1"}
nrepl/nrepl {:mvn/version "1.4.0"} nrepl/nrepl {:mvn/version "1.4.0"}
org.postgresql/postgresql {:mvn/version "42.7.7"} org.postgresql/postgresql {:mvn/version "42.7.9"}
org.xerial/sqlite-jdbc {:mvn/version "3.50.3.0"} org.xerial/sqlite-jdbc {:mvn/version "3.50.3.0"}
com.zaxxer/HikariCP {:mvn/version "7.0.2"} com.zaxxer/HikariCP {:mvn/version "7.0.2"}
@@ -49,7 +49,7 @@
buddy/buddy-hashers {:mvn/version "2.0.167"} buddy/buddy-hashers {:mvn/version "2.0.167"}
buddy/buddy-sign {:mvn/version "3.6.1-359"} buddy/buddy-sign {:mvn/version "3.6.1-359"}
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.2"} com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.3"}
org.jsoup/jsoup {:mvn/version "1.21.2"} org.jsoup/jsoup {:mvn/version "1.21.2"}
org.im4java/im4java org.im4java/im4java
@@ -66,7 +66,7 @@
;; Pretty Print specs ;; Pretty Print specs
pretty-spec/pretty-spec {:mvn/version "0.1.4"} pretty-spec/pretty-spec {:mvn/version "0.1.4"}
software.amazon.awssdk/s3 {:mvn/version "2.33.10"}} software.amazon.awssdk/s3 {:mvn/version "2.41.21"}}
:paths ["src" "resources" "target/classes"] :paths ["src" "resources" "target/classes"]
:aliases :aliases
@@ -97,8 +97,8 @@
:jmx-remote :jmx-remote
{:jvm-opts ["-Dcom.sun.management.jmxremote" {:jvm-opts ["-Dcom.sun.management.jmxremote"
"-Dcom.sun.management.jmxremote.port=9090" "-Dcom.sun.management.jmxremote.port=9000"
"-Dcom.sun.management.jmxremote.rmi.port=9090" "-Dcom.sun.management.jmxremote.rmi.port=9000"
"-Dcom.sun.management.jmxremote.local.only=false" "-Dcom.sun.management.jmxremote.local.only=false"
"-Dcom.sun.management.jmxremote.authenticate=false" "-Dcom.sun.management.jmxremote.authenticate=false"
"-Dcom.sun.management.jmxremote.ssl=false" "-Dcom.sun.management.jmxremote.ssl=false"
+2 -1
View File
@@ -1,7 +1,8 @@
From: {{profile.fullname}} <{{profile.email}}> / {{profile.id}} From: {{profile.fullname}} <{{profile.email}}> / {{profile.id}}
Subject: {{feedback-subject}} Subject: {{feedback-subject}}
Type: {{feedback-type}} Type: {{feedback-type}}
{%- if feedback-error-href %}
{% if feedback-error-href %}
HREF: {{feedback-error-href}} HREF: {{feedback-error-href}}
{% endif -%} {% endif -%}
@@ -240,4 +240,4 @@
</div> </div>
</body> </body>
</html> </html>
+1 -1
View File
@@ -3,7 +3,7 @@
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Tokens%20starter%20kit.penpot"} :file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Tokens%20starter%20kit.penpot"}
{:id "penpot-design-system" {:id "penpot-design-system"
:name "Penpot Design System | Pencil" :name "Penpot Design System | Pencil"
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/penpot-app.penpot"} :file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Pencil-Penpot-Design-System.penpot"}
{:id "wireframing-kit" {:id "wireframing-kit"
:name "Wireframe library" :name "Wireframe library"
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Wireframing%20kit%20v1.1.penpot"} :file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Wireframing%20kit%20v1.1.penpot"}
+1
View File
@@ -3,6 +3,7 @@
export PENPOT_MANAGEMENT_API_KEY=super-secret-management-api-key export PENPOT_MANAGEMENT_API_KEY=super-secret-management-api-key
export PENPOT_SECRET_KEY=super-secret-devenv-key export PENPOT_SECRET_KEY=super-secret-devenv-key
export PENPOT_HOST=devenv export PENPOT_HOST=devenv
export PENPOT_PUBLIC_URI=https://localhost:3449
export PENPOT_FLAGS="\ export PENPOT_FLAGS="\
$PENPOT_FLAGS \ $PENPOT_FLAGS \
+7 -18
View File
@@ -36,17 +36,6 @@
[integrant.core :as ig] [integrant.core :as ig]
[yetti.response :as-alias yres])) [yetti.response :as-alias yres]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn obfuscate-string
[s]
(if (< (count s) 10)
(apply str (take (count s) (repeat "*")))
(str (subs s 0 5)
(apply str (take (- (count s) 5) (repeat "*"))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; OIDC PROVIDER (GENERIC) ;; OIDC PROVIDER (GENERIC)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -177,7 +166,7 @@
(l/inf :hint "provider initialized" (l/inf :hint "provider initialized"
:provider (:id provider) :provider (:id provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))) :client-secret (d/obfuscate-string (:client-secret provider)))
provider) provider)
(catch Throwable cause (catch Throwable cause
@@ -222,7 +211,7 @@
(l/inf :hint "provider initialized" (l/inf :hint "provider initialized"
:provider (:id provider) :provider (:id provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))) :client-secret (d/obfuscate-string (:client-secret provider)))
provider) provider)
(catch Throwable cause (catch Throwable cause
@@ -299,7 +288,7 @@
(l/inf :hint "provider initialized" (l/inf :hint "provider initialized"
:provider (:id provider) :provider (:id provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))) :client-secret (d/obfuscate-string (:client-secret provider)))
provider) provider)
(catch Throwable cause (catch Throwable cause
@@ -341,7 +330,7 @@
:provider "gitlab" :provider "gitlab"
:base-uri (:base-uri provider) :base-uri (:base-uri provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))) :client-secret (d/obfuscate-string (:client-secret provider)))
provider) provider)
(catch Throwable cause (catch Throwable cause
(ex/raise :type ::internal (ex/raise :type ::internal
@@ -361,7 +350,7 @@
(l/inf :hint "provider initialized" (l/inf :hint "provider initialized"
:provider (:id provider) :provider (:id provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))) :client-secret (d/obfuscate-string (:client-secret provider)))
provider) provider)
(catch Throwable cause (catch Throwable cause
@@ -459,7 +448,7 @@
(l/trc :hint "fetch access token" (l/trc :hint "fetch access token"
:provider (:id provider) :provider (:id provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)) :client-secret (d/obfuscate-string (:client-secret provider))
:grant-type (:grant_type params) :grant-type (:grant_type params)
:redirect-uri (:redirect_uri params)) :redirect-uri (:redirect_uri params))
@@ -512,7 +501,7 @@
[cfg provider tdata] [cfg provider tdata]
(l/trc :hint "fetch user info" (l/trc :hint "fetch user info"
:uri (:user-uri provider) :uri (:user-uri provider)
:token (obfuscate-string (:token/access tdata))) :token (d/obfuscate-string (:token/access tdata)))
(let [params {:uri (:user-uri provider) (let [params {:uri (:user-uri provider)
:headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))} :headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))}
+75
View File
@@ -331,6 +331,81 @@
(set/difference cfeat/backend-only-features)) (set/difference cfeat/backend-only-features))
#{})))) #{}))))
(defn check-file-exists
[cfg id & {:keys [include-deleted?]
:or {include-deleted? false}
:as options}]
(db/get-with-sql cfg [sql:get-minimal-file id]
{:db/remove-deleted (not include-deleted?)}))
(def ^:private sql:file-permissions
"select fpr.is_owner,
fpr.is_admin,
fpr.can_edit
from file_profile_rel as fpr
inner join file as f on (f.id = fpr.file_id)
where fpr.file_id = ?
and fpr.profile_id = ?
union all
select tpr.is_owner,
tpr.is_admin,
tpr.can_edit
from team_profile_rel as tpr
inner join project as p on (p.team_id = tpr.team_id)
inner join file as f on (p.id = f.project_id)
where f.id = ?
and tpr.profile_id = ?
union all
select ppr.is_owner,
ppr.is_admin,
ppr.can_edit
from project_profile_rel as ppr
inner join file as f on (f.project_id = ppr.project_id)
where f.id = ?
and ppr.profile_id = ?")
(defn- get-file-permissions*
[conn profile-id file-id]
(when (and profile-id file-id)
(db/exec! conn [sql:file-permissions
file-id profile-id
file-id profile-id
file-id profile-id])))
(defn get-file-permissions
([conn profile-id file-id]
(let [rows (get-file-permissions* conn profile-id file-id)
is-owner (boolean (some :is-owner rows))
is-admin (boolean (some :is-admin rows))
can-edit (boolean (some :can-edit rows))]
(when (seq rows)
{:type :membership
:is-owner is-owner
:is-admin (or is-owner is-admin)
:can-edit (or is-owner is-admin can-edit)
:can-read true
:is-logged (some? profile-id)})))
([conn profile-id file-id share-id]
(let [perms (get-file-permissions conn profile-id file-id)
ldata (some-> (db/get* conn :share-link {:id share-id :file-id file-id})
(dissoc :flags)
(update :pages db/decode-pgarray #{}))]
;; NOTE: in a future when share-link becomes more powerful and
;; will allow us specify which parts of the app is available, we
;; will probably need to tweak this function in order to expose
;; this flags to the frontend.
(cond
(some? perms) perms
(some? ldata) {:type :share-link
:can-read true
:pages (:pages ldata)
:is-logged (some? profile-id)
:who-comment (:who-comment ldata)
:who-inspect (:who-inspect ldata)}))))
(defn get-project (defn get-project
[cfg project-id] [cfg project-id]
(db/get cfg :project {:id project-id})) (db/get cfg :project {:id project-id}))
+5 -7
View File
@@ -821,9 +821,10 @@
entries (keep (match-storage-entry-fn) entries)] entries (keep (match-storage-entry-fn) entries)]
(doseq [{:keys [id entry]} entries] (doseq [{:keys [id entry]} entries]
(let [object (->> (read-entry input entry) (let [object (-> (read-entry input entry)
(decode-storage-object) (decode-storage-object)
(validate-storage-object)) (update :bucket d/nilv sto/default-bucket)
(validate-storage-object))
ext (cmedia/mtype->extension (:content-type object)) ext (cmedia/mtype->extension (:content-type object))
path (str "objects/" id ext) path (str "objects/" id ext)
@@ -872,11 +873,8 @@
(import-storage-objects cfg) (import-storage-objects cfg)
(let [files (get manifest :files) (let [files (get manifest :files)
result (reduce (fn [result {:keys [id] :as file}] result (reduce (fn [result file]
(let [name' (get file :name) (let [name' (get file :name)
name' (if (map? name)
(get name id)
name')
file (assoc file :name name')] file (assoc file :name name')]
(conj result (import-file cfg file)))) (conj result (import-file cfg file))))
[] []
+5 -7
View File
@@ -106,17 +106,17 @@
(let [content-part (MimeBodyPart.) (let [content-part (MimeBodyPart.)
alternative-mpart (MimeMultipart. "alternative")] alternative-mpart (MimeMultipart. "alternative")]
(when-let [content (get body "text/plain")]
(let [text-part (MimeBodyPart.)]
(.setText text-part ^String content ^String charset)
(.addBodyPart alternative-mpart text-part)))
(when-let [content (get body "text/html")] (when-let [content (get body "text/html")]
(let [html-part (MimeBodyPart.)] (let [html-part (MimeBodyPart.)]
(.setContent html-part ^String content (.setContent html-part ^String content
(str "text/html; charset=" charset)) (str "text/html; charset=" charset))
(.addBodyPart alternative-mpart html-part))) (.addBodyPart alternative-mpart html-part)))
(when-let [content (get body "text/plain")]
(let [text-part (MimeBodyPart.)]
(.setText text-part ^String content ^String charset)
(.addBodyPart alternative-mpart text-part)))
(.setContent content-part alternative-mpart) (.setContent content-part alternative-mpart)
(.addBodyPart mixed-mpart content-part)) (.addBodyPart mixed-mpart content-part))
@@ -124,8 +124,6 @@
(throw (IllegalArgumentException. "invalid email body provided"))) (throw (IllegalArgumentException. "invalid email body provided")))
(doseq [[name content] attachments] (doseq [[name content] attachments]
(prn "attachment" name)
(let [attachment-part (MimeBodyPart.)] (let [attachment-part (MimeBodyPart.)]
(.setFileName attachment-part ^String name) (.setFileName attachment-part ^String name)
(.setContent attachment-part ^String content (str "text/plain; charset=" charset)) (.setContent attachment-part ^String content (str "text/plain; charset=" charset))
+1 -1
View File
@@ -30,7 +30,7 @@
(defn- get-file-media-object (defn- get-file-media-object
[pool id] [pool id]
(db/get pool :file-media-object {:id id})) (db/get pool :file-media-object {:id id} {::db/remove-deleted false}))
(defn- serve-object-from-s3 (defn- serve-object-from-s3
[{:keys [::sto/storage] :as cfg} obj] [{:keys [::sto/storage] :as cfg} obj]
+1 -1
View File
@@ -309,7 +309,7 @@
(fn [request] (fn [request]
(let [key (yreq/get-header request "x-shared-key")] (let [key (yreq/get-header request "x-shared-key")]
(if (= key shared-key) (if (= key shared-key)
(handler request) (handler (assoc request ::http/auth-with-shared-key true))
{::yres/status 403})))) {::yres/status 403}))))
(fn [_ _] (fn [_ _]
{::yres/status 403}))) {::yres/status 403})))
+44 -26
View File
@@ -79,18 +79,6 @@
(remove #(contains? reserved-props (key %)))) (remove #(contains? reserved-props (key %))))
props)) props))
(defn event-from-rpc-params
"Create a base event skeleton with pre-filled some important
data that can be extracted from RPC params object"
[params]
(let [context {:external-session-id (::rpc/external-session-id params)
:external-event-origin (::rpc/external-event-origin params)
:triggered-by (::rpc/handler-name params)}]
{::type "action"
::profile-id (::rpc/profile-id params)
::ip-addr (::rpc/ip-addr params)
::context (d/without-nils context)}))
(defn get-external-session-id (defn get-external-session-id
[request] [request]
(when-let [session-id (yreq/get-header request "x-external-session-id")] (when-let [session-id (yreq/get-header request "x-external-session-id")]
@@ -99,13 +87,24 @@
(str/blank? session-id)) (str/blank? session-id))
session-id))) session-id)))
(defn- get-external-event-origin (defn- get-client-event-origin
[request] [request]
(when-let [origin (yreq/get-header request "x-event-origin")] (when-let [origin (yreq/get-header request "x-event-origin")]
(when-not (or (> (count origin) 256) (when-not (or (= origin "null")
(= origin "null")
(str/blank? origin)) (str/blank? origin))
origin))) (str/prune origin 200))))
(defn get-client-user-agent
[request]
(when-let [user-agent (yreq/get-header request "user-agent")]
(str/prune user-agent 500)))
(defn- get-client-version
[request]
(when-let [origin (yreq/get-header request "x-frontend-version")]
(when-not (or (= origin "null")
(str/blank? origin))
(str/prune origin 100))))
;; --- SPECS ;; --- SPECS
@@ -134,6 +133,33 @@
(def ^:private check-event (def ^:private check-event
(sm/check-fn schema:event)) (sm/check-fn schema:event))
(defn- prepare-context-from-request
[request]
(let [client-event-origin (get-client-event-origin request)
client-version (get-client-version request)
client-user-agent (get-client-user-agent request)
session-id (get-external-session-id request)
token-id (::actoken/id request)]
(d/without-nils
{:external-session-id session-id
:access-token-id (some-> token-id str)
:client-event-origin client-event-origin
:client-user-agent client-user-agent
:client-version client-version
:version (:full cf/version)})))
(defn event-from-rpc-params
"Create a base event skeleton with pre-filled some important
data that can be extracted from RPC params object"
[params]
(let [context (some-> params meta ::http/request prepare-context-from-request)
event {::type "action"
::profile-id (or (::rpc/profile-id params) uuid/zero)
::ip-addr (::rpc/ip-addr params)}]
(cond-> event
(some? context)
(assoc ::context context))))
(defn prepare-event (defn prepare-event
[cfg mdata params result] [cfg mdata params result]
(let [resultm (meta result) (let [resultm (meta result)
@@ -148,18 +174,10 @@
(merge (::props resultm)) (merge (::props resultm))
(dissoc :profile-id) (dissoc :profile-id)
(dissoc :type))) (dissoc :type)))
(clean-props)) (clean-props))
token-id (::actoken/id request) context (merge (::context resultm)
context (-> (::context resultm) (prepare-context-from-request request))
(assoc :external-session-id
(get-external-session-id request))
(assoc :external-event-origin
(get-external-event-origin request))
(assoc :access-token-id (some-> token-id str))
(d/without-nils))
ip-addr (inet/parse-request request)] ip-addr (inet/parse-request request)]
{::type (or (::type resultm) {::type (or (::type resultm)
@@ -57,7 +57,7 @@
:uid uuid/zero}) :uid uuid/zero})
body (t/encode {:events events}) body (t/encode {:events events})
headers {"content-type" "application/transit+json" headers {"content-type" "application/transit+json"
"origin" (cf/get :public-uri) "origin" (str (cf/get :public-uri))
"cookie" (u/map->query-string {:auth-token token})} "cookie" (u/map->query-string {:auth-token token})}
params {:uri uri params {:uri uri
:timeout 12000 :timeout 12000
+35 -24
View File
@@ -35,8 +35,7 @@
javax.xml.parsers.SAXParserFactory javax.xml.parsers.SAXParserFactory
org.apache.commons.io.IOUtils org.apache.commons.io.IOUtils
org.im4java.core.ConvertCmd org.im4java.core.ConvertCmd
org.im4java.core.IMOperation org.im4java.core.IMOperation))
org.im4java.core.Info))
(def default-max-file-size (def default-max-file-size
(* 1024 1024 10)) ; 10 MiB (* 1024 1024 10)) ; 10 MiB
@@ -224,17 +223,18 @@
;; If we are processing an animated gif we use the first frame with -scene 0 ;; If we are processing an animated gif we use the first frame with -scene 0
(let [dim-result (sh/sh "identify" "-format" "%w %h\n" path) (let [dim-result (sh/sh "identify" "-format" "%w %h\n" path)
orient-result (sh/sh "identify" "-format" "%[EXIF:Orientation]\n" path)] orient-result (sh/sh "identify" "-format" "%[EXIF:Orientation]\n" path)]
(if (and (= 0 (:exit dim-result)) (when (= 0 (:exit dim-result))
(= 0 (:exit orient-result)))
(let [[w h] (-> (:out dim-result) (let [[w h] (-> (:out dim-result)
str/trim str/trim
(clojure.string/split #"\s+") (clojure.string/split #"\s+")
(->> (mapv #(Integer/parseInt %)))) (->> (mapv #(Integer/parseInt %))))
orientation (-> orient-result :out str/trim)] orientation-exit (:exit orient-result)
(case orientation orientation (-> orient-result :out str/trim)]
("6" "8") {:width h :height w} ; Rotated 90 or 270 degrees (if (= 0 orientation-exit)
{:width w :height h})) ; Normal or unknown orientation (case orientation
nil))) ("6" "8") {:width h :height w} ; Rotated 90 or 270 degrees
{:width w :height h}) ; Normal or unknown orientation
{:width w :height h}))))) ; If orientation can't be read, use dimensions as-is
(defmethod process :info (defmethod process :info
[{:keys [input] :as params}] [{:keys [input] :as params}]
@@ -247,26 +247,37 @@
:hint "uploaded svg does not provides dimensions")) :hint "uploaded svg does not provides dimensions"))
(merge input info {:ts (ct/now) :size (fs/size path)})) (merge input info {:ts (ct/now) :size (fs/size path)}))
(let [instance (Info. (str path)) (let [path-str (str path)
mtype' (.getProperty instance "Mime type")] identify-res (sh/sh "identify" "-format" "image/%[magick]\n" path-str)
;; identify prints one line per frame (animated GIFs, etc.); we take the first one
mtype' (if (zero? (:exit identify-res))
(-> identify-res
:out
str/trim
(str/split #"\s+" 2)
first
str/lower)
(ex/raise :type :validation
:code :invalid-image
:hint "invalid image"))
{:keys [width height]}
(or (get-dimensions-with-orientation path-str)
(do
(l/warn "Failed to read image dimensions with orientation" {:path path})
(ex/raise :type :validation
:code :invalid-image
:hint "invalid image")))]
(when (and (string? mtype) (when (and (string? mtype)
(not= mtype mtype')) (not= (str/lower mtype) mtype'))
(ex/raise :type :validation (ex/raise :type :validation
:code :media-type-mismatch :code :media-type-mismatch
:hint (str "Seems like you are uploading a file whose content does not match the extension." :hint (str "Seems like you are uploading a file whose content does not match the extension."
"Expected: " mtype ". Got: " mtype'))) "Expected: " mtype ". Got: " mtype')))
(let [{:keys [width height]} (assoc input
(or (get-dimensions-with-orientation (str path)) :width width
(do :height height
(l/warn "Failed to read image dimensions with orientation; falling back to im4java" :size (fs/size path)
{:path path}) :ts (ct/now))))))
{:width (.getPageWidth instance)
:height (.getPageHeight instance)}))]
(assoc input
:width width
:height height
:size (fs/size path)
:ts (ct/now)))))))
(defmethod process-error org.im4java.core.InfoException (defmethod process-error org.im4java.core.InfoException
[error] [error]
+6 -1
View File
@@ -14,6 +14,7 @@
[app.common.spec :as us] [app.common.spec :as us]
[app.common.time :as ct] [app.common.time :as ct]
[app.common.uri :as u] [app.common.uri :as u]
[app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
[app.db :as db] [app.db :as db]
[app.http :as-alias http] [app.http :as-alias http]
@@ -92,7 +93,11 @@
(let [handler-name (:type path-params) (let [handler-name (:type path-params)
etag (yreq/get-header request "if-none-match") etag (yreq/get-header request "if-none-match")
profile-id (or (::session/profile-id request) profile-id (or (::session/profile-id request)
(::actoken/profile-id request)) (::actoken/profile-id request)
(if (::http/auth-with-shared-key request)
uuid/zero
nil))
ip-addr (inet/parse-request request) ip-addr (inet/parse-request request)
data (-> params data (-> params
+2 -1
View File
@@ -307,7 +307,8 @@
:content-type (:mtype input)})] :content-type (:mtype input)})]
(:id sobject)) (:id sobject))
(catch Throwable cause (catch Throwable cause
(l/err :hint "unable to import profile picture" (l/wrn :hint "unable to import profile picture"
:uri uri
:cause cause) :cause cause)
nil))) nil)))
+62 -126
View File
@@ -79,85 +79,14 @@
;; --- FILE PERMISSIONS ;; --- FILE PERMISSIONS
(def ^:private sql:file-permissions
"select fpr.is_owner,
fpr.is_admin,
fpr.can_edit
from file_profile_rel as fpr
inner join file as f on (f.id = fpr.file_id)
where fpr.file_id = ?
and fpr.profile_id = ?
and f.deleted_at is null
union all
select tpr.is_owner,
tpr.is_admin,
tpr.can_edit
from team_profile_rel as tpr
inner join project as p on (p.team_id = tpr.team_id)
inner join file as f on (p.id = f.project_id)
where f.id = ?
and tpr.profile_id = ?
and f.deleted_at is null
union all
select ppr.is_owner,
ppr.is_admin,
ppr.can_edit
from project_profile_rel as ppr
inner join file as f on (f.project_id = ppr.project_id)
where f.id = ?
and ppr.profile_id = ?
and f.deleted_at is null")
(defn get-file-permissions
[conn profile-id file-id]
(when (and profile-id file-id)
(db/exec! conn [sql:file-permissions
file-id profile-id
file-id profile-id
file-id profile-id])))
(defn get-permissions
([conn profile-id file-id]
(let [rows (get-file-permissions conn profile-id file-id)
is-owner (boolean (some :is-owner rows))
is-admin (boolean (some :is-admin rows))
can-edit (boolean (some :can-edit rows))]
(when (seq rows)
{:type :membership
:is-owner is-owner
:is-admin (or is-owner is-admin)
:can-edit (or is-owner is-admin can-edit)
:can-read true
:is-logged (some? profile-id)})))
([conn profile-id file-id share-id]
(let [perms (get-permissions conn profile-id file-id)
ldata (some-> (db/get* conn :share-link {:id share-id :file-id file-id})
(dissoc :flags)
(update :pages db/decode-pgarray #{}))]
;; NOTE: in a future when share-link becomes more powerful and
;; will allow us specify which parts of the app is available, we
;; will probably need to tweak this function in order to expose
;; this flags to the frontend.
(cond
(some? perms) perms
(some? ldata) {:type :share-link
:can-read true
:pages (:pages ldata)
:is-logged (some? profile-id)
:who-comment (:who-comment ldata)
:who-inspect (:who-inspect ldata)}))))
(def has-edit-permissions? (def has-edit-permissions?
(perms/make-edition-predicate-fn get-permissions)) (perms/make-edition-predicate-fn bfc/get-file-permissions))
(def has-read-permissions? (def has-read-permissions?
(perms/make-read-predicate-fn get-permissions)) (perms/make-read-predicate-fn bfc/get-file-permissions))
(def has-comment-permissions? (def has-comment-permissions?
(perms/make-comment-predicate-fn get-permissions)) (perms/make-comment-predicate-fn bfc/get-file-permissions))
(def check-edition-permissions! (def check-edition-permissions!
(perms/make-check-fn has-edit-permissions?)) (perms/make-check-fn has-edit-permissions?))
@@ -170,7 +99,7 @@
(defn check-comment-permissions! (defn check-comment-permissions!
[conn profile-id file-id share-id] [conn profile-id file-id share-id]
(let [perms (get-permissions conn profile-id file-id share-id) (let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
can-read (has-read-permissions? perms) can-read (has-read-permissions? perms)
can-comment (has-comment-permissions? perms)] can-comment (has-comment-permissions? perms)]
(when-not (or can-read can-comment) (when-not (or can-read can-comment)
@@ -222,7 +151,7 @@
(defn- get-minimal-file-with-perms (defn- get-minimal-file-with-perms
[cfg {:keys [:id ::rpc/profile-id]}] [cfg {:keys [:id ::rpc/profile-id]}]
(let [mfile (get-minimal-file cfg id) (let [mfile (get-minimal-file cfg id)
perms (get-permissions cfg profile-id id)] perms (bfc/get-file-permissions cfg profile-id id)]
(assoc mfile :permissions perms))) (assoc mfile :permissions perms)))
(defn get-file-etag (defn get-file-etag
@@ -248,7 +177,7 @@
;; will be already prefetched and we just reuse them instead ;; will be already prefetched and we just reuse them instead
;; of making an additional database queries. ;; of making an additional database queries.
(let [perms (or (:permissions (::cond/object params)) (let [perms (or (:permissions (::cond/object params))
(get-permissions conn profile-id id))] (bfc/get-file-permissions conn profile-id id))]
(check-read-permissions! perms) (check-read-permissions! perms)
(let [team (teams/get-team conn (let [team (teams/get-team conn
@@ -311,7 +240,7 @@
::sm/result schema:file-fragment} ::sm/result schema:file-fragment}
[cfg {:keys [::rpc/profile-id file-id fragment-id share-id]}] [cfg {:keys [::rpc/profile-id file-id fragment-id share-id]}]
(db/run! cfg (fn [cfg] (db/run! cfg (fn [cfg]
(let [perms (get-permissions cfg profile-id file-id share-id)] (let [perms (bfc/get-file-permissions cfg profile-id file-id share-id)]
(check-read-permissions! perms) (check-read-permissions! perms)
(-> (get-file-fragment cfg file-id fragment-id) (-> (get-file-fragment cfg file-id fragment-id)
(rph/with-http-cache long-cache-duration)))))) (rph/with-http-cache long-cache-duration))))))
@@ -456,8 +385,7 @@
:code :params-validation :code :params-validation
:hint "page-id is required when object-id is provided")) :hint "page-id is required when object-id is provided"))
(let [perms (get-permissions conn profile-id file-id share-id) (let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
file (bfc/get-file cfg file-id :read-only? true) file (bfc/get-file cfg file-id :read-only? true)
proj (db/get conn :project {:id (:project-id file)}) proj (db/get conn :project {:id (:project-id file)})
@@ -688,11 +616,10 @@
"Get libraries used by the specified file." "Get libraries used by the specified file."
{::doc/added "1.17" {::doc/added "1.17"
::sm/params schema:get-file-libraries} ::sm/params schema:get-file-libraries}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id]}] [cfg {:keys [::rpc/profile-id file-id]}]
(dm/with-open [conn (db/open pool)] (bfc/check-file-exists cfg file-id)
(check-read-permissions! conn profile-id file-id) (check-read-permissions! cfg profile-id file-id)
(bfc/get-file-libraries conn file-id))) (bfc/get-file-libraries cfg file-id))
;; --- COMMAND QUERY: Files that use this File library ;; --- COMMAND QUERY: Files that use this File library
@@ -777,7 +704,6 @@
f.created_at, f.created_at,
f.modified_at, f.modified_at,
f.name, f.name,
f.is_shared,
f.deleted_at AS will_be_deleted_at, f.deleted_at AS will_be_deleted_at,
ft.media_id AS thumbnail_id, ft.media_id AS thumbnail_id,
row_number() OVER w AS row_num, row_number() OVER w AS row_num,
@@ -785,8 +711,7 @@
FROM file AS f FROM file AS f
INNER JOIN project AS p ON (p.id = f.project_id) INNER JOIN project AS p ON (p.id = f.project_id)
LEFT JOIN file_thumbnail AS ft on (ft.file_id = f.id LEFT JOIN file_thumbnail AS ft on (ft.file_id = f.id
AND ft.revn = f.revn AND ft.revn = f.revn)
AND ft.deleted_at is null)
WHERE p.team_id = ? WHERE p.team_id = ?
AND (p.deleted_at > ?::timestamptz OR AND (p.deleted_at > ?::timestamptz OR
f.deleted_at > ?::timestamptz) f.deleted_at > ?::timestamptz)
@@ -888,7 +813,7 @@
AND (f.deleted_at IS NULL OR f.deleted_at > now()) AND (f.deleted_at IS NULL OR f.deleted_at > now())
ORDER BY f.created_at ASC;") ORDER BY f.created_at ASC;")
(defn- absorb-library-by-file! (defn- absorb-library-by-file
[cfg ldata file-id] [cfg ldata file-id]
(assert (db/connection-map? cfg) (assert (db/connection-map? cfg)
@@ -912,7 +837,7 @@
:modified-at (ct/now) :modified-at (ct/now)
:has-media-trimmed false})))) :has-media-trimmed false}))))
(defn- absorb-library (defn- absorb-library*
"Find all files using a shared library, and absorb all library assets "Find all files using a shared library, and absorb all library assets
into the file local libraries" into the file local libraries"
[cfg {:keys [id data] :as library}] [cfg {:keys [id data] :as library}]
@@ -927,10 +852,10 @@
:library-id (str id) :library-id (str id)
:files (str/join "," (map str ids))) :files (str/join "," (map str ids)))
(run! (partial absorb-library-by-file! cfg data) ids) (run! (partial absorb-library-by-file cfg data) ids)
library)) library))
(defn absorb-library! (defn absorb-library
[{:keys [::db/conn] :as cfg} id] [{:keys [::db/conn] :as cfg} id]
(let [file (-> (bfc/get-file cfg id (let [file (-> (bfc/get-file cfg id
:realize? true :realize? true
@@ -947,7 +872,7 @@
(-> (cfeat/get-team-enabled-features cf/flags team) (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-file-features! (:features file))) (cfeat/check-file-features! (:features file)))
(absorb-library cfg file))) (absorb-library* cfg file)))
(defn- set-file-shared (defn- set-file-shared
[{:keys [::db/conn] :as cfg} {:keys [profile-id id] :as params}] [{:keys [::db/conn] :as cfg} {:keys [profile-id id] :as params}]
@@ -960,14 +885,14 @@
;; file, we need to perform more complex operation, ;; file, we need to perform more complex operation,
;; so in this case we retrieve the complete file and ;; so in this case we retrieve the complete file and
;; perform all required validations. ;; perform all required validations.
(let [file (-> (absorb-library! cfg id) (let [file (-> (absorb-library cfg id)
(assoc :is-shared false))] (assoc :is-shared false))]
(db/delete! conn :file-library-rel {:library-file-id id}) (db/delete! conn :file-library-rel {:library-file-id id})
(db/update! conn :file (db/update! conn :file
{:is-shared false {:is-shared false
:modified-at (ct/now)} :modified-at (ct/now)}
{:id id}) {:id id})
(select-keys file [:id :name :is-shared])) file)
(and (false? (:is-shared file)) (and (false? (:is-shared file))
(true? (:is-shared params))) (true? (:is-shared params)))
@@ -1014,6 +939,11 @@
{:id file-id} {:id file-id}
{::db/return-keys [:id :name :is-shared :deleted-at {::db/return-keys [:id :name :is-shared :deleted-at
:project-id :created-at :modified-at]})] :project-id :created-at :modified-at]})]
;; Remove all possible relations for that file
(db/delete! conn :file-library-rel
{:library-file-id file-id})
(wrk/submit! {::db/conn conn (wrk/submit! {::db/conn conn
::wrk/task :delete-object ::wrk/task :delete-object
::wrk/params {:object :file ::wrk/params {:object :file
@@ -1164,47 +1094,53 @@
;; --- MUTATION COMMAND: delete-files-immediatelly ;; --- MUTATION COMMAND: delete-files-immediatelly
(def ^:private sql:delete-team-files (def ^:private sql:get-delete-team-files-candidates
"UPDATE file AS uf SET deleted_at = ?::timestamptz "SELECT f.id
FROM ( FROM file AS f
SELECT f.id JOIN project AS p ON (p.id = f.project_id)
FROM file AS f JOIN team AS t ON (t.id = p.team_id)
JOIN project AS p ON (p.id = f.project_id) WHERE t.deleted_at IS NULL
JOIN team AS t ON (t.id = p.team_id) AND t.id = ?
WHERE t.deleted_at IS NULL AND f.id = ANY(?::uuid[])")
AND t.id = ?
AND f.id = ANY(?::uuid[])
) AS subquery
WHERE uf.id = subquery.id
RETURNING uf.id, uf.deleted_at;")
(def ^:private schema:permanently-delete-team-files (def ^:private schema:permanently-delete-team-files
[:map {:title "permanently-delete-team-files"} [:map {:title "permanently-delete-team-files"}
[:team-id ::sm/uuid] [:team-id ::sm/uuid]
[:ids [::sm/set ::sm/uuid]]]) [:ids [::sm/set ::sm/uuid]]])
(defn- permanently-delete-team-files
[{:keys [::db/conn]} {:keys [::rpc/request-at team-id ids]}]
(let [ids (into #{}
d/xf:map-id
(db/exec! conn [sql:get-delete-team-files-candidates team-id
(db/create-array conn "uuid" ids)]))]
(reduce (fn [acc id]
(events/tap :progress {:file-id id :index (inc (count acc)) :total (count ids)})
(db/update! conn :file
{:deleted-at request-at}
{:id id}
{::db/return-keys false})
(wrk/submit! {::db/conn conn
::wrk/task :delete-object
::wrk/params {:object :file
:deleted-at request-at
:id id}})
(conj acc id))
#{}
ids)))
(sv/defmethod ::permanently-delete-team-files (sv/defmethod ::permanently-delete-team-files
"Mark the specified files to be deleted immediatelly on the "Mark the specified files to be deleted immediatelly on the
specified team. The team-id on params will be used to filter and specified team. The team-id on params will be used to filter and
check writable permissons on team." check writable permissons on team."
{::doc/added "2.12" {::doc/added "2.13"
::sm/params schema:permanently-delete-team-files ::sm/params schema:permanently-delete-team-files}
::db/transaction true}
[{:keys [::db/conn]} {:keys [::rpc/profile-id ::rpc/request-at team-id ids]}] [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id] :as params}]
(teams/check-edition-permissions! conn profile-id team-id) (teams/check-edition-permissions! pool profile-id team-id)
(sse/response #(db/tx-run! cfg permanently-delete-team-files params)))
(reduce (fn [acc {:keys [id deleted-at]}]
(wrk/submit! {::db/conn conn
::wrk/task :delete-object
::wrk/params {:object :file
:deleted-at deleted-at
:id id}})
(conj acc id))
#{}
(db/plan conn [sql:delete-team-files request-at team-id
(db/create-array conn "uuid" ids)])))
;; --- MUTATION COMMAND: restore-files-immediatelly ;; --- MUTATION COMMAND: restore-files-immediatelly
@@ -1268,7 +1204,7 @@
{:keys [files projects]} {:keys [files projects]}
(reduce (fn [result {:keys [id project-id]}] (reduce (fn [result {:keys [id project-id]}]
(let [index (-> result :files count)] (let [index (-> result :files count)]
(events/tap :progress {:file-id id :index index :total total-files}) (events/tap :progress {:file-id id :index (inc index) :total total-files})
(restore-file conn id) (restore-file conn id)
(-> result (-> result
@@ -1291,7 +1227,7 @@
(sv/defmethod ::restore-deleted-team-files (sv/defmethod ::restore-deleted-team-files
"Removes the deletion mark from the specified files (and respective "Removes the deletion mark from the specified files (and respective
projects) on the specified team." projects) on the specified team."
{::doc/added "2.12" {::doc/added "2.13"
::sse/stream? true ::sse/stream? true
::sm/params schema:restore-deleted-team-files} ::sm/params schema:restore-deleted-team-files}
[cfg params] [cfg params]
@@ -199,15 +199,13 @@
[cfg {:keys [::rpc/profile-id file-id strip-frames-with-thumbnails] :as params}] [cfg {:keys [::rpc/profile-id file-id strip-frames-with-thumbnails] :as params}]
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}] (db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
(files/check-read-permissions! conn profile-id file-id) (files/check-read-permissions! conn profile-id file-id)
(let [team (teams/get-team conn (let [team (teams/get-team conn
:profile-id profile-id :profile-id profile-id
:file-id file-id) :file-id file-id)
file (bfc/get-file cfg file-id file (bfc/get-file cfg file-id
:include-deleted? true
:realize? true :realize? true
:read-only? true) :read-only? true)
strip-frames-with-thumbnails strip-frames-with-thumbnails
(or (nil? strip-frames-with-thumbnails) ;; if not present, default to true (or (nil? strip-frames-with-thumbnails) ;; if not present, default to true
(true? strip-frames-with-thumbnails))] (true? strip-frames-with-thumbnails))]
@@ -333,12 +331,16 @@
;; --- MUTATION COMMAND: create-file-thumbnail ;; --- MUTATION COMMAND: create-file-thumbnail
(defn- create-file-thumbnail! (defn- create-file-thumbnail
[{:keys [::db/conn ::sto/storage]} {:keys [file-id revn props media] :as params}] [{:keys [::db/conn ::sto/storage] :as cfg} {:keys [file-id revn props media] :as params}]
(media/validate-media-type! media) (media/validate-media-type! media)
(media/validate-media-size! media) (media/validate-media-size! media)
(let [props (db/tjson (or props {})) (let [file (bfc/get-file cfg file-id
:include-deleted? true
:load-data? false)
props (db/tjson (or props {}))
path (:path media) path (:path media)
mtype (:mtype media) mtype (:mtype media)
hash (sto/calculate-hash path) hash (sto/calculate-hash path)
@@ -367,7 +369,7 @@
(db/update! conn :file-thumbnail (db/update! conn :file-thumbnail
{:media-id (:id media) {:media-id (:id media)
:deleted-at nil :deleted-at (:deleted-at file)
:updated-at tnow :updated-at tnow
:props props} :props props}
{:file-id file-id {:file-id file-id
@@ -378,6 +380,7 @@
:revn revn :revn revn
:created-at tnow :created-at tnow
:updated-at tnow :updated-at tnow
:deleted-at (:deleted-at file)
:props props :props props
:media-id (:id media)})) :media-id (:id media)}))
@@ -402,6 +405,8 @@
::rtry/when rtry/conflict-exception? ::rtry/when rtry/conflict-exception?
::sm/params schema:create-file-thumbnail} ::sm/params schema:create-file-thumbnail}
;; FIXME: do not run the thumbnail upload inside a transaction
[cfg {:keys [::rpc/profile-id file-id] :as params}] [cfg {:keys [::rpc/profile-id file-id] :as params}]
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}] (db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
;; TODO For now we check read permissions instead of write, ;; TODO For now we check read permissions instead of write,
@@ -409,6 +414,6 @@
;; review this approach on the future. ;; review this approach on the future.
(files/check-read-permissions! conn profile-id file-id) (files/check-read-permissions! conn profile-id file-id)
(when-not (db/read-only? conn) (when-not (db/read-only? conn)
(let [media (create-file-thumbnail! cfg params)] (let [media (create-file-thumbnail cfg params)]
{:uri (files/resolve-public-uri (:id media)) {:uri (files/resolve-public-uri (:id media))
:id (:id media)}))))) :id (:id media)})))))
+36 -5
View File
@@ -6,6 +6,7 @@
(ns app.rpc.commands.fonts (ns app.rpc.commands.fonts
(:require (:require
[app.binfile.common :as bfc]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.schema :as sm] [app.common.schema :as sm]
@@ -26,7 +27,17 @@
[app.rpc.helpers :as rph] [app.rpc.helpers :as rph]
[app.rpc.quotes :as quotes] [app.rpc.quotes :as quotes]
[app.storage :as sto] [app.storage :as sto]
[app.util.services :as sv])) [app.storage.tmp :as tmp]
[app.util.services :as sv]
[datoteka.io :as io])
(:import
java.io.InputStream
java.io.OutputStream
java.io.SequenceInputStream
java.util.Collections))
(set! *warn-on-reflection* true)
(def valid-weight #{100 200 300 400 500 600 700 800 900 950}) (def valid-weight #{100 200 300 400 500 600 700 800 900 950})
(def valid-style #{"normal" "italic"}) (def valid-style #{"normal" "italic"})
@@ -66,7 +77,7 @@
(uuid? file-id) (uuid? file-id)
(let [file (db/get-by-id conn :file file-id {:columns [:id :project-id]}) (let [file (db/get-by-id conn :file file-id {:columns [:id :project-id]})
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]}) project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})
perms (files/get-permissions conn profile-id file-id share-id)] perms (bfc/get-file-permissions conn profile-id file-id share-id)]
(files/check-read-permissions! perms) (files/check-read-permissions! perms)
(db/query conn :team-font-variant (db/query conn :team-font-variant
{:team-id (:team-id project) {:team-id (:team-id project)
@@ -78,7 +89,8 @@
(def ^:private schema:create-font-variant (def ^:private schema:create-font-variant
[:map {:title "create-font-variant"} [:map {:title "create-font-variant"}
[:team-id ::sm/uuid] [:team-id ::sm/uuid]
[:data [:map-of ::sm/text ::sm/any]] [:data [:map-of ::sm/text [:or ::sm/bytes
[::sm/vec ::sm/bytes]]]]
[:font-id ::sm/uuid] [:font-id ::sm/uuid]
[:font-family ::sm/text] [:font-family ::sm/text]
[:font-weight [::sm/one-of {:format "number"} valid-weight]] [:font-weight [::sm/one-of {:format "number"} valid-weight]]
@@ -104,7 +116,7 @@
(defn create-font-variant (defn create-font-variant
[{:keys [::sto/storage ::db/conn]} {:keys [data] :as params}] [{:keys [::sto/storage ::db/conn]} {:keys [data] :as params}]
(letfn [(generate-missing! [data] (letfn [(generate-missing [data]
(let [data (media/run {:cmd :generate-fonts :input data})] (let [data (media/run {:cmd :generate-fonts :input data})]
(when (and (not (contains? data "font/otf")) (when (and (not (contains? data "font/otf"))
(not (contains? data "font/ttf")) (not (contains? data "font/ttf"))
@@ -115,8 +127,26 @@
:hint "invalid font upload, unable to generate missing font assets")) :hint "invalid font upload, unable to generate missing font assets"))
data)) data))
(process-chunks [chunks]
(let [tmp (tmp/tempfile :prefix "penpot.tempfont." :suffix "")
streams (map io/input-stream chunks)
streams (Collections/enumeration streams)]
(with-open [^OutputStream output (io/output-stream tmp)
^InputStream input (SequenceInputStream. streams)]
(io/copy input output))
tmp))
(join-chunks [data]
(reduce-kv (fn [data mtype content]
(if (vector? content)
(assoc data mtype (process-chunks content))
data))
data
data))
(prepare-font [data mtype] (prepare-font [data mtype]
(when-let [resource (get data mtype)] (when-let [resource (get data mtype)]
(let [hash (sto/calculate-hash resource) (let [hash (sto/calculate-hash resource)
content (-> (sto/content resource) content (-> (sto/content resource)
(sto/wrap-with-hash hash))] (sto/wrap-with-hash hash))]
@@ -155,7 +185,8 @@
:otf-file-id (:id otf) :otf-file-id (:id otf)
:ttf-file-id (:id ttf)}))] :ttf-file-id (:id ttf)}))]
(let [data (generate-missing! data) (let [data (join-chunks data)
data (generate-missing data)
assets (persist-fonts-files! data) assets (persist-fonts-files! data)
result (insert-font-variant! assets)] result (insert-font-variant! assets)]
(vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys)))))) (vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys))))))
+3 -3
View File
@@ -19,7 +19,7 @@
inner join team_profile_rel as tpr on (tpr.team_id = p.team_id) inner join team_profile_rel as tpr on (tpr.team_id = p.team_id)
where tpr.profile_id = ? where tpr.profile_id = ?
and p.team_id = ? and p.team_id = ?
and (p.deleted_at is null or p.deleted_at > now()) and (p.deleted_at is null)
and (tpr.is_admin = true or and (tpr.is_admin = true or
tpr.is_owner = true or tpr.is_owner = true or
tpr.can_edit = true) tpr.can_edit = true)
@@ -29,7 +29,7 @@
inner join project_profile_rel as ppr on (ppr.project_id = p.id) inner join project_profile_rel as ppr on (ppr.project_id = p.id)
where ppr.profile_id = ? where ppr.profile_id = ?
and p.team_id = ? and p.team_id = ?
and (p.deleted_at is null or p.deleted_at > now()) and (p.deleted_at is null)
and (ppr.is_admin = true or and (ppr.is_admin = true or
ppr.is_owner = true or ppr.is_owner = true or
ppr.can_edit = true) ppr.can_edit = true)
@@ -47,7 +47,7 @@
left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn) left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn)
inner join projects as pr on (f.project_id = pr.id) inner join projects as pr on (f.project_id = pr.id)
where f.name ilike ('%' || ? || '%') where f.name ilike ('%' || ? || '%')
and (f.deleted_at is null or f.deleted_at > now()) and (f.deleted_at is null)
order by f.created_at asc") order by f.created_at asc")
(defn search-files (defn search-files
+1 -2
View File
@@ -13,7 +13,6 @@
[app.config :as cf] [app.config :as cf]
[app.db :as db] [app.db :as db]
[app.rpc :as-alias rpc] [app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.rpc.commands.teams :as teams] [app.rpc.commands.teams :as teams]
[app.rpc.cond :as-alias cond] [app.rpc.cond :as-alias cond]
[app.rpc.doc :as-alias doc] [app.rpc.doc :as-alias doc]
@@ -121,7 +120,7 @@
[system {:keys [::rpc/profile-id file-id share-id] :as params}] [system {:keys [::rpc/profile-id file-id share-id] :as params}]
(db/run! system (db/run! system
(fn [{:keys [::db/conn] :as system}] (fn [{:keys [::db/conn] :as system}]
(let [perms (files/get-permissions conn profile-id file-id share-id) (let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
params (-> params params (-> params
(assoc ::perms perms) (assoc ::perms perms)
(assoc :profile-id profile-id))] (assoc :profile-id profile-id))]
+31 -33
View File
@@ -104,28 +104,29 @@
(def ^:private schema:limit (def ^:private schema:limit
[:and [:and
[:map [:map
[::name :any] [::name :keyword]
[::strategy schema:strategy] [::strategy schema:strategy]
[::key :string] [::key :string]
[::opts :string]] [::opts :string]
[:or [::capacity {:optional true} ::sm/int]
[:map [::rate {:optional true} ::sm/int]
[::capacity ::sm/int] [::interval {:optional true} ::ct/duration]
[::rate ::sm/int] [::params {:optional true} [::sm/vec :any]]
[::internal ::ct/duration] [::permits {:optional true} ::sm/int]
[::params [::sm/vec :any]]] [::unit {:optional true} [:enum :days :hours :minutes :seconds :weeks]]]
[:map [:fn (fn [attrs]
[::nreq ::sm/int] (let [contains-fn (partial contains? attrs)]
[::unit [:enum :days :hours :minutes :seconds :weeks]]]]]) (or (every? contains-fn [::capacity ::rate ::interval])
(every? contains-fn [::permits ::unit]))))]])
(def ^:private schema:limits (def ^:private schema:limits
[:map-of :keyword [::sm/vec schema:limit]]) [:map-of :keyword [::sm/vec schema:limit]])
(def ^:private valid-limit-tuple? (def ^:private valid-limit-tuple?
(sm/lazy-validator schema:limit-tuple)) (sm/validator schema:limit-tuple))
(def ^:private valid-rlimit-instance? (def ^:private valid-rlimit-instance?
(sm/lazy-validator ::rpc/rlimit)) (sm/validator ::rpc/rlimit))
(defmethod parse-limit :window (defmethod parse-limit :window
[[name strategy opts :as vlimit]] [[name strategy opts :as vlimit]]
@@ -134,16 +135,16 @@
(merge (merge
{::name name {::name name
::strategy strategy} ::strategy strategy}
(if-let [[_ nreq unit] (re-find window-opts-re opts)] (if-let [[_ permits unit] (re-find window-opts-re opts)]
(let [nreq (parse-long nreq)] (let [permits (parse-long permits)]
{::nreq nreq {::permits permits
::unit (case unit ::unit (case unit
"d" :days "d" :days
"h" :hours "h" :hours
"m" :minutes "m" :minutes
"s" :seconds "s" :seconds
"w" :weeks) "w" :weeks)
::key (str "ratelimit.window." (d/name name)) ::key (str "penpot.rlimit." (cf/get :tenant) ".window." (d/name name))
::opts opts}) ::opts opts})
(ex/raise :type :validation (ex/raise :type :validation
:code :invalid-window-limit-opts :code :invalid-window-limit-opts
@@ -164,15 +165,15 @@
::interval interval ::interval interval
::opts opts ::opts opts
::params [(->seconds interval) rate capacity] ::params [(->seconds interval) rate capacity]
::key (str "ratelimit.bucket." (d/name name))}) ::key (str "penpot.rlimit." (cf/get :tenant) ".bucket." (d/name name))})
(ex/raise :type :validation (ex/raise :type :validation
:code :invalid-bucket-limit-opts :code :invalid-bucket-limit-opts
:hint (str/ffmt "looks like '%' does not have a valid format" opts)))) :hint (str/ffmt "looks like '%' does not have a valid format" opts))))
(defmethod process-limit :bucket (defmethod process-limit :bucket
[rconn user-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}] [rconn profile-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}]
(let [script (-> bucket-rate-limit-script (let [script (-> bucket-rate-limit-script
(assoc ::rscript/keys [(str key "." service "." user-id)]) (assoc ::rscript/keys [(str key "." service "." profile-id)])
(assoc ::rscript/vals (conj params (->seconds now)))) (assoc ::rscript/vals (conj params (->seconds now))))
result (rds/eval rconn script) result (rds/eval rconn script)
allowed? (boolean (nth result 0)) allowed? (boolean (nth result 0))
@@ -192,18 +193,18 @@
(assoc ::lresult/remaining remaining)))) (assoc ::lresult/remaining remaining))))
(defmethod process-limit :window (defmethod process-limit :window
[rconn user-id now {:keys [::nreq ::unit ::key ::service] :as limit}] [rconn profile-id now {:keys [::permits ::unit ::key ::service] :as limit}]
(let [ts (ct/truncate now unit) (let [ts (ct/truncate now unit)
ttl (ct/diff now (ct/plus ts {unit 1})) ttl (ct/diff now (ct/plus ts {unit 1}))
script (-> window-rate-limit-script script (-> window-rate-limit-script
(assoc ::rscript/keys [(str key "." service "." user-id "." (ct/format-inst ts))]) (assoc ::rscript/keys [(str key "." service "." profile-id "." (ct/format-inst ts))])
(assoc ::rscript/vals [nreq (->seconds ttl)])) (assoc ::rscript/vals [permits (->seconds ttl)]))
result (rds/eval rconn script) result (rds/eval rconn script)
allowed? (boolean (nth result 0)) allowed? (boolean (nth result 0))
remaining (nth result 1)] remaining (nth result 1)]
(l/trace :hint "limit processed" (l/trace :hint "limit processed"
:service service :service service
:limit (name (::name limit)) :name (name (::name limit))
:strategy (name (::strategy limit)) :strategy (name (::strategy limit))
:opts (::opts limit) :opts (::opts limit)
:allowed allowed? :allowed allowed?
@@ -214,8 +215,8 @@
(assoc ::lresult/reset (ct/plus ts {unit 1}))))) (assoc ::lresult/reset (ct/plus ts {unit 1})))))
(defn- process-limits (defn- process-limits
[rconn user-id limits now] [rconn profile-id limits now]
(let [results (into [] (map (partial process-limit rconn user-id now)) limits) (let [results (into [] (map (partial process-limit rconn profile-id now)) limits)
remaining (->> results remaining (->> results
(d/index-by ::name ::lresult/remaining) (d/index-by ::name ::lresult/remaining)
(uri/map->query-string)) (uri/map->query-string))
@@ -227,7 +228,7 @@
(when rejected (when rejected
(l/warn :hint "rejected rate limit" (l/warn :hint "rejected rate limit"
:user-id (str user-id) :profile-id (str profile-id)
:limit-service (-> rejected ::service name) :limit-service (-> rejected ::service name)
:limit-name (-> rejected ::name name) :limit-name (-> rejected ::name name)
:limit-strategy (-> rejected ::strategy name))) :limit-strategy (-> rejected ::strategy name)))
@@ -371,12 +372,9 @@
(defn- on-refresh-error (defn- on-refresh-error
[_ cause] [_ cause]
(when-not (instance? java.util.concurrent.RejectedExecutionException cause) (when-not (instance? java.util.concurrent.RejectedExecutionException cause)
(if-let [explain (-> cause ex-data ex/explain)] (l/warn :hint "unexpected exception on loading config"
(l/warn ::l/raw (str "unable to refresh config, invalid format:\n" explain) :cause cause
::l/sync? true) ::l/sync? true)))
(l/warn :hint "unexpected exception on loading config"
:cause cause
::l/sync? true))))
(defn- get-config-path (defn- get-config-path
[] []
+1 -1
View File
@@ -25,9 +25,9 @@ local allowed = filled >= requested
local newTokens = filled local newTokens = filled
if allowed then if allowed then
newTokens = filled - requested newTokens = filled - requested
redis.call("hset", tokensKey, "tokens", newTokens, "timestamp", timestamp)
end end
redis.call("hset", tokensKey, "tokens", newTokens, "timestamp", timestamp)
redis.call("expire", tokensKey, ttl) redis.call("expire", tokensKey, ttl)
return { allowed, newTokens } return { allowed, newTokens }
+3
View File
@@ -35,6 +35,9 @@
:assets-s3 :s3 :assets-s3 :s3
nil))) nil)))
(def default-bucket
"file-media-object")
(def valid-buckets (def valid-buckets
#{"file-media-object" #{"file-media-object"
"team-font-variant" "team-font-variant"
+2 -2
View File
@@ -25,7 +25,7 @@
[app.common.time :as ct] [app.common.time :as ct]
[app.config :as cf] [app.config :as cf]
[app.db :as db] [app.db :as db]
[app.storage :as-alias sto] [app.storage :as sto]
[app.storage.impl :as impl] [app.storage.impl :as impl]
[integrant.core :as ig])) [integrant.core :as ig]))
@@ -130,7 +130,7 @@
[{:keys [metadata]}] [{:keys [metadata]}]
(or (some-> metadata :bucket) (or (some-> metadata :bucket)
(some-> metadata :reference d/name) (some-> metadata :reference d/name)
"file-media-object")) sto/default-bucket))
(defn- process-objects! (defn- process-objects!
[conn has-refs? bucket objects] [conn has-refs? bucket objects]
+3 -2
View File
@@ -45,7 +45,8 @@
:deleted-at (ct/format-inst deleted-at)) :deleted-at (ct/format-inst deleted-at))
(db/update! conn :file (db/update! conn :file
{:deleted-at deleted-at} {:deleted-at deleted-at
:is-shared false}
{:id id} {:id id}
{::db/return-keys false}) {::db/return-keys false})
@@ -53,7 +54,7 @@
(not *team-deletion*)) (not *team-deletion*))
;; NOTE: we don't prevent file deletion on absorb operation failure ;; NOTE: we don't prevent file deletion on absorb operation failure
(try (try
(db/tx-run! cfg files/absorb-library! id) (db/tx-run! cfg files/absorb-library id)
(catch Throwable cause (catch Throwable cause
(l/warn :hint "error on absorbing library" (l/warn :hint "error on absorbing library"
:file-id id :file-id id
+9 -1
View File
@@ -7,9 +7,17 @@
(ns app.util.template (ns app.util.template
(:require (:require
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[cuerdas.core :as str]
[selmer.filters :as sf]
[selmer.parser :as sp])) [selmer.parser :as sp]))
(sp/cache-off!) ;; (sp/cache-off!)
(sf/add-filter! :abbreviate
(fn [s n]
(let [n (parse-long n)]
(str/abbreviate s n))))
(defn render (defn render
[path context] [path context]
+25 -24
View File
@@ -137,33 +137,34 @@ RETURNING task.id, task.queue")
::wait))) ::wait)))
(run-batch [] (run-batch []
(let [rconn (rds/connect cfg)] (try
(try (let [rconn (rds/connect cfg)]
(-> cfg (try
(assoc ::rds/conn rconn) (-> cfg
(db/tx-run! run-batch')) (assoc ::rds/conn rconn)
(db/tx-run! run-batch'))
(finally
(.close ^AutoCloseable rconn))))
(catch InterruptedException cause (catch InterruptedException cause
(throw cause)) (throw cause))
(catch Exception cause
(cond
(rds/exception? cause)
(do
(l/wrn :hint "redis exception (will retry in an instant)" :cause cause)
(px/sleep timeout))
(db/sql-exception? cause) (catch Exception cause
(do (cond
(l/wrn :hint "database exception (will retry in an instant)" :cause cause) (rds/exception? cause)
(px/sleep timeout)) (do
(l/wrn :hint "redis exception (will retry in an instant)" :cause cause)
(px/sleep timeout))
:else (db/sql-exception? cause)
(do (do
(l/err :hint "unhandled exception (will retry in an instant)" :cause cause) (l/wrn :hint "database exception (will retry in an instant)" :cause cause)
(px/sleep timeout)))) (px/sleep timeout))
(finally :else
(.close ^AutoCloseable rconn))))) (do
(l/err :hint "unhandled exception (will retry in an instant)" :cause cause)
(px/sleep timeout))))))
(dispatcher [] (dispatcher []
(l/inf :hint "started") (l/inf :hint "started")
@@ -176,7 +177,7 @@ RETURNING task.id, task.queue")
(catch InterruptedException _ (catch InterruptedException _
(l/trc :hint "interrupted")) (l/trc :hint "interrupted"))
(catch Throwable cause (catch Throwable cause
(l/err :hint " unexpected exception" :cause cause)) (l/err :hint "unexpected exception" :cause cause))
(finally (finally
(l/inf :hint "terminated"))))] (l/inf :hint "terminated"))))]
+2 -2
View File
@@ -595,8 +595,8 @@
(px/exec! :virtual #(rcp/write-body-to-stream body nil output)) (px/exec! :virtual #(rcp/write-body-to-stream body nil output))
(into [] (into []
(map (fn [{:keys [event data]}] (map (fn [{:keys [event data]}]
[(keyword event) (d/vec2 (keyword event)
(tr/decode-str data)])) (tr/decode-str data))))
(parse-sse (slurp' input))) (parse-sse (slurp' input)))
(finally (finally
(.close input))))) (.close input)))))
+5 -1
View File
@@ -1921,7 +1921,11 @@
;; (th/print-result! out) ;; (th/print-result! out)
(t/is (nil? (:error out))) (t/is (nil? (:error out)))
(let [result (:result out)] (let [result (:result out)]
(t/is (= (:ids data) result))) (t/is (fn? result))
(let [[ev1 ev2 :as events] (th/consume-sse result)]
(t/is (= 2 (count events)))
(t/is (= (:ids data) (val ev2)))))
(let [row (th/db-exec-one! ["select * from file where id = ?" file-id])] (let [row (th/db-exec-one! ["select * from file where id = ?" file-id])]
(t/is (= (:deleted-at row) now))))))) (t/is (= (:deleted-at row) now)))))))
@@ -275,3 +275,30 @@
(let [res (th/run-task! :storage-gc-touched {})] (let [res (th/run-task! :storage-gc-touched {})]
(t/is (= 0 (:freeze res))) (t/is (= 0 (:freeze res)))
(t/is (= 3 (:delete res))))))) (t/is (= 3 (:delete res)))))))
(t/deftest input-sanitization-1
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
(let [prof (th/create-profile* 1 {:is-active true})
team-id (:default-team-id prof)
proj-id (:default-project-id prof)
font-id (uuid/custom 10 1)
ttfdata (-> (io/resource "backend_tests/test_files/font-1.ttf")
(io/read*))
params {::th/type :create-font-variant
::rpc/profile-id (:id prof)
:team-id team-id
:font-id font-id
:font-family "somefont"
:font-weight 400
:font-style "normal"
:data {"font/ttf" "/etc/passwd"}}
out (th/command! params)]
(t/is (= 0 (:call-count @mock)))
;; (th/print-result! out)
(let [error (:error out)
error-data (ex-data error)]
(t/is (th/ex-info? error))))))
@@ -318,3 +318,35 @@
;; check that we have all no objects ;; check that we have all no objects
(let [rows (th/db-exec! ["select * from storage_object where deleted_at is null"])] (let [rows (th/db-exec! ["select * from storage_object where deleted_at is null"])]
(t/is (= 0 (count rows)))))) (t/is (= 0 (count rows))))))
(t/deftest tempfile-bucket-test
(let [storage (-> (:app.storage/storage th/*system*)
(configure-storage-backend))
content1 (sto/content "content1")
now (ct/now)
object1 (sto/put-object! storage {::sto/content content1
::sto/touched-at (ct/plus now {:minutes 1})
:bucket "tempfile"
:content-type "text/plain"})]
(binding [ct/*clock* (clock/fixed now)]
(let [res (th/run-task! :storage-gc-touched {})]
(t/is (= 0 (:freeze res)))
(t/is (= 0 (:delete res)))))
(binding [ct/*clock* (clock/fixed (ct/plus now {:minutes 1}))]
(let [res (th/run-task! :storage-gc-touched {})]
(t/is (= 0 (:freeze res)))
(t/is (= 1 (:delete res)))))
(binding [ct/*clock* (clock/fixed (ct/plus now {:hours 1}))]
(let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 0 (:deleted res)))))
(binding [ct/*clock* (clock/fixed (ct/plus now {:hours 2}))]
(let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 0 (:deleted res)))))))
+10 -15
View File
@@ -1,5 +1,5 @@
{:deps {:deps
{org.clojure/clojure {:mvn/version "1.12.2"} {org.clojure/clojure {:mvn/version "1.12.4"}
org.clojure/data.json {:mvn/version "2.5.1"} org.clojure/data.json {:mvn/version "2.5.1"}
org.clojure/tools.cli {:mvn/version "1.1.230"} org.clojure/tools.cli {:mvn/version "1.1.230"}
org.clojure/test.check {:mvn/version "1.1.1"} org.clojure/test.check {:mvn/version "1.1.1"}
@@ -9,15 +9,15 @@
org.apache.commons/commons-pool2 {:mvn/version "2.12.1"} org.apache.commons/commons-pool2 {:mvn/version "2.12.1"}
;; Logging ;; Logging
org.apache.logging.log4j/log4j-api {:mvn/version "2.25.1"} org.apache.logging.log4j/log4j-api {:mvn/version "2.25.3"}
org.apache.logging.log4j/log4j-core {:mvn/version "2.25.1"} org.apache.logging.log4j/log4j-core {:mvn/version "2.25.3"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.25.1"} org.apache.logging.log4j/log4j-web {:mvn/version "2.25.3"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.25.1"} org.apache.logging.log4j/log4j-jul {:mvn/version "2.25.3"}
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.25.1"} org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.25.3"}
org.slf4j/slf4j-api {:mvn/version "2.0.17"} org.slf4j/slf4j-api {:mvn/version "2.0.17"}
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.40"} pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.41"}
selmer/selmer {:mvn/version "1.12.62"} selmer/selmer {:mvn/version "1.12.70"}
criterium/criterium {:mvn/version "0.4.6"} criterium/criterium {:mvn/version "0.4.6"}
metosin/jsonista {:mvn/version "0.3.13"} metosin/jsonista {:mvn/version "0.3.13"}
@@ -27,10 +27,9 @@
com.cognitect/transit-clj {:mvn/version "1.0.333"} com.cognitect/transit-clj {:mvn/version "1.0.333"}
com.cognitect/transit-cljs {:mvn/version "0.8.280"} com.cognitect/transit-cljs {:mvn/version "0.8.280"}
java-http-clj/java-http-clj {:mvn/version "0.4.3"} java-http-clj/java-http-clj {:mvn/version "0.4.3"}
integrant/integrant {:mvn/version "1.0.0"} integrant/integrant {:mvn/version "1.0.1"}
funcool/tubax {:mvn/version "2021.05.20-0"} funcool/cuerdas {:mvn/version "2026.415"}
funcool/cuerdas {:mvn/version "2025.06.16-414"}
funcool/promesa funcool/promesa
{:git/sha "46048fc0d4bf5466a2a4121f5d52aefa6337f2e8" {:git/sha "46048fc0d4bf5466a2a4121f5d52aefa6337f2e8"
:git/url "https://github.com/funcool/promesa"} :git/url "https://github.com/funcool/promesa"}
@@ -48,12 +47,8 @@
com.sun.mail/jakarta.mail {:mvn/version "2.0.2"} com.sun.mail/jakarta.mail {:mvn/version "2.0.2"}
org.la4j/la4j {:mvn/version "0.6.0"} org.la4j/la4j {:mvn/version "0.6.0"}
;; exception printing
fipp/fipp {:mvn/version "0.6.29"}
me.flowthing/pp {:mvn/version "2024-11-13.77"} me.flowthing/pp {:mvn/version "2024-11-13.77"}
io.aviso/pretty {:mvn/version "1.4.4"} io.aviso/pretty {:mvn/version "1.4.4"}
environ/environ {:mvn/version "1.2.0"}} environ/environ {:mvn/version "1.2.0"}}
:paths ["src" "vendor" "target/classes"] :paths ["src" "vendor" "target/classes"]
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -ex
corepack enable;
corepack install;
yarn install;
yarn run test;
+20
View File
@@ -1024,6 +1024,26 @@
:clj :clj
(sort comp-fn items)))) (sort comp-fn items))))
(defn obfuscate-string
"Obfuscates potentially sensitive values.
- One-arg arity:
* For strings shorter than 10 characters, all characters are replaced by `*`.
* For longer strings, the first 5 characters are preserved and the rest obfuscated.
- Two-arg arity accepts a boolean `full?` that, when true, replaces the whole value
by `*`, preserving only the length."
([v]
(obfuscate-string v false))
([v full?]
(let [s (str v)
n (count s)]
(cond
(zero? n) s
full? (apply str (repeat n "*"))
(< n 10) (apply str (repeat n "*"))
:else (str (subs s 0 5)
(apply str (repeat (- n 5) "*")))))))
(defn reorder (defn reorder
"Reorder a vector by moving one of their items from some position to some space between positions. "Reorder a vector by moving one of their items from some position to some space between positions.
It clamps the position numbers to a valid range." It clamps the position numbers to a valid range."
+36 -35
View File
@@ -9,10 +9,10 @@
(:refer-clojure :exclude [get-in select-keys str with-open max]) (:refer-clojure :exclude [get-in select-keys str with-open max])
#?(:cljs (:require-macros [app.common.data.macros])) #?(:cljs (:require-macros [app.common.data.macros]))
(:require (:require
#?(:clj [cljs.analyzer.api :as aapi])
#?(:clj [clojure.core :as c] #?(:clj [clojure.core :as c]
:cljs [cljs.core :as c]) :cljs [cljs.core :as c])
[app.common.data :as d] [app.common.data :as d]
[cljs.analyzer.api :as aapi]
[cuerdas.core :as str])) [cuerdas.core :as str]))
(defmacro select-keys (defmacro select-keys
@@ -44,42 +44,43 @@
[& params] [& params]
`(str/concat ~@params)) `(str/concat ~@params))
(defmacro export #?(:clj
"A helper macro that allows reexport a var in a current namespace." (defmacro export
[v] "A helper macro that allows reexport a var in a current namespace."
(if (boolean (:ns &env)) [v]
(if (boolean (:ns &env))
;; Code for ClojureScript ;; Code for ClojureScript
(let [mdata (aapi/resolve &env v) (let [mdata (aapi/resolve &env v)
arglists (second (get-in mdata [:meta :arglists])) arglists (second (get-in mdata [:meta :arglists]))
sym (symbol (c/name v)) sym (symbol (c/name v))
andsym (symbol "&") andsym (symbol "&")
procarg #(if (= % andsym) % (gensym "param"))] procarg #(if (= % andsym) % (gensym "param"))]
(if (pos? (count arglists)) (if (pos? (count arglists))
`(def `(def
~(with-meta sym (:meta mdata)) ~(with-meta sym (:meta mdata))
(fn ~@(for [args arglists] (fn ~@(for [args arglists]
(let [args (map procarg args)] (let [args (map procarg args)]
(if (some #(= andsym %) args) (if (some #(= andsym %) args)
(let [[sargs dargs] (split-with #(not= andsym %) args)] (let [[sargs dargs] (split-with #(not= andsym %) args)]
`([~@sargs ~@dargs] (apply ~v ~@sargs ~@(rest dargs)))) `([~@sargs ~@dargs] (apply ~v ~@sargs ~@(rest dargs))))
`([~@args] (~v ~@args))))))) `([~@args] (~v ~@args)))))))
`(def ~(with-meta sym (:meta mdata)) ~v))) `(def ~(with-meta sym (:meta mdata)) ~v)))
;; Code for Clojure ;; Code for Clojure
(let [vr (resolve v) (let [vr (resolve v)
m (meta vr) m (meta vr)
n (:name m) n (:name m)
n (with-meta n n (with-meta n
(cond-> {} (cond-> {}
(:dynamic m) (assoc :dynamic true) (:dynamic m) (assoc :dynamic true)
(:protocol m) (assoc :protocol (:protocol m))))] (:protocol m) (assoc :protocol (:protocol m))))]
`(let [m# (meta ~vr)] `(let [m# (meta ~vr)]
(def ~n (deref ~vr)) (def ~n (deref ~vr))
(alter-meta! (var ~n) merge (dissoc m# :name)) (alter-meta! (var ~n) merge (dissoc m# :name))
;; (when (:macro m#) ;; (when (:macro m#)
;; (.setMacro (var ~n))) ;; (.setMacro (var ~n)))
~vr)))) ~vr)))))
(defmacro fmt (defmacro fmt
"String interpolation helper. Can only be used with strings known at "String interpolation helper. Can only be used with strings known at
+25 -10
View File
@@ -10,16 +10,20 @@
(:refer-clojure :exclude [instance?]) (:refer-clojure :exclude [instance?])
(:require (:require
#?(:clj [clojure.stacktrace :as strace]) #?(:clj [clojure.stacktrace :as strace])
[app.common.data :refer [obfuscate-string]]
[app.common.pprint :as pp] [app.common.pprint :as pp]
[app.common.schema :as sm] [app.common.schema :as sm]
[clojure.core :as c] [clojure.core :as c]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[cuerdas.core :as str] [cuerdas.core :as str])
[expound.alpha :as expound])
#?(:clj #?(:clj
(:import (:import
clojure.lang.IPersistentMap))) clojure.lang.IPersistentMap)))
(def ^:private sensitive-fields
"Keys whose values must be obfuscated in validation explains."
#{:password :old-password :token :invitation-token})
#?(:clj (set! *warn-on-reflection* true)) #?(:clj (set! *warn-on-reflection* true))
(def ^:dynamic *data-length* 8) (def ^:dynamic *data-length* 8)
@@ -110,15 +114,26 @@
(contains? data :explain)) (contains? data :explain))
(explain (:explain data) opts) (explain (:explain data) opts)
(and (contains? data ::s/problems)
(contains? data ::s/value)
(contains? data ::s/spec))
(binding [s/*explain-out* expound/printer]
(with-out-str
(s/explain-out (update data ::s/problems #(take (:length opts 10) %)))))
(contains? data ::sm/explain) (contains? data ::sm/explain)
(sm/humanize-explain (::sm/explain data) opts))) (let [exp (::sm/explain data)
sanitize-map (fn sanitize-map [m]
(reduce-kv
(fn [acc k v]
(let [k* (if (string? k) (keyword k) k)]
(cond
(contains? sensitive-fields k*)
(assoc acc k (if (map? v)
(sanitize-map v)
(obfuscate-string v true)))
(map? v) (assoc acc k (sanitize-map v))
:else (assoc acc k v))))
{}
m))
sanitize-explain (fn [exp]
(cond-> exp
(:value exp) (update :value sanitize-map)))]
(sm/humanize-explain (sanitize-explain exp) opts))))
#?(:clj #?(:clj
(defn format-throwable (defn format-throwable
+152 -9
View File
@@ -82,6 +82,113 @@
(declare create-svg-children) (declare create-svg-children)
(declare parse-svg-element) (declare parse-svg-element)
(defn- process-gradient-stops
"Processes gradient stops to extract stop-color and stop-opacity from style attributes
and convert them to direct attributes. This ensures stops with style='stop-color:#...;stop-opacity:1'
are properly converted to stop-color and stop-opacity attributes."
[stops]
(mapv (fn [stop]
(let [stop-attrs (:attrs stop)
stop-style (get stop-attrs :style)
;; Parse style if it's a string using csvg/parse-style utility
parsed-style (when (and (string? stop-style) (seq stop-style))
(csvg/parse-style stop-style))
;; Extract stop-color and stop-opacity from style
style-stop-color (when parsed-style (:stop-color parsed-style))
style-stop-opacity (when parsed-style (:stop-opacity parsed-style))
;; Merge: use direct attributes first, then style values as fallback
final-attrs (cond-> stop-attrs
(and style-stop-color (not (contains? stop-attrs :stop-color)))
(assoc :stop-color style-stop-color)
(and style-stop-opacity (not (contains? stop-attrs :stop-opacity)))
(assoc :stop-opacity style-stop-opacity)
;; Remove style attribute if we've extracted its values
(or style-stop-color style-stop-opacity)
(dissoc :style))]
(assoc stop :attrs final-attrs)))
stops))
(defn- resolve-gradient-href
"Resolves xlink:href references in gradients by merging the referenced gradient's
stops and attributes with the referencing gradient. This ensures gradients that
reference other gradients (like linearGradient3550 referencing linearGradient3536)
inherit the stops from the base gradient.
According to SVG spec, when a gradient has xlink:href:
- It inherits all attributes from the referenced gradient
- It inherits all stops from the referenced gradient
- The referencing gradient's attributes override the base ones
- If the referencing gradient has stops, they replace the base stops
Returns the defs map with all gradient href references resolved."
[defs]
(letfn [(resolve-gradient [gradient-id gradient-node defs visited]
(if (contains? visited gradient-id)
(do
#?(:cljs (js/console.warn "[resolve-gradient] Circular reference detected for" gradient-id)
:clj nil)
gradient-node) ;; Avoid circular references
(let [attrs (:attrs gradient-node)
href-id (or (:href attrs) (:xlink:href attrs))
href-id (when (and (string? href-id) (pos? (count href-id)))
(subs href-id 1)) ;; Remove leading #
base-gradient (when (and href-id (contains? defs href-id))
(get defs href-id))
resolved-base (when base-gradient (resolve-gradient href-id base-gradient defs (conj visited gradient-id)))]
(if resolved-base
;; Merge: base gradient attributes + referencing gradient attributes
;; Use referencing gradient's stops if present, otherwise use base stops
(let [base-attrs (:attrs resolved-base)
ref-attrs (:attrs gradient-node)
;; Start with base attributes (without id), then merge with ref attributes
;; This ensures ref attributes override base ones
base-attrs-clean (dissoc base-attrs :id)
ref-attrs-clean (dissoc ref-attrs :href :xlink:href :id)
;; Special handling for gradientTransform: if both have it, combine them
base-transform (get base-attrs :gradientTransform)
ref-transform (get ref-attrs :gradientTransform)
combined-transform (cond
(and base-transform ref-transform)
(str base-transform " " ref-transform) ;; Apply base first, then ref
:else (or ref-transform base-transform))
;; Merge attributes: base first, then ref (ref overrides)
merged-attrs (-> (d/deep-merge base-attrs-clean ref-attrs-clean)
(cond-> combined-transform
(assoc :gradientTransform combined-transform)))
;; If referencing gradient has content (stops), use it; otherwise use base content
final-content (if (seq (:content gradient-node))
(:content gradient-node)
(:content resolved-base))
;; Process stops to extract stop-color and stop-opacity from style attributes
processed-content (process-gradient-stops final-content)
result {:tag (:tag gradient-node)
:attrs (assoc merged-attrs :id gradient-id)
:content processed-content}]
result)
;; Process stops even for gradients without references to extract style attributes
(let [processed-content (process-gradient-stops (:content gradient-node))]
(assoc gradient-node :content processed-content))))))]
(let [gradient-tags #{:linearGradient :radialGradient}
result (reduce-kv
(fn [acc id node]
(if (contains? gradient-tags (:tag node))
(assoc acc id (resolve-gradient id node defs #{}))
(assoc acc id node)))
{}
defs)]
result)))
(defn create-svg-shapes (defn create-svg-shapes
([svg-data pos objects frame-id parent-id selected center?] ([svg-data pos objects frame-id parent-id selected center?]
(create-svg-shapes (uuid/next) svg-data pos objects frame-id parent-id selected center?)) (create-svg-shapes (uuid/next) svg-data pos objects frame-id parent-id selected center?))
@@ -112,6 +219,9 @@
(csvg/fix-percents) (csvg/fix-percents)
(csvg/extract-defs)) (csvg/extract-defs))
;; Resolve gradient href references in all defs before processing shapes
def-nodes (resolve-gradient-href def-nodes)
;; In penpot groups have the size of their children. To ;; In penpot groups have the size of their children. To
;; respect the imported svg size and empty space let's create ;; respect the imported svg size and empty space let's create
;; a transparent shape as background to respect the imported ;; a transparent shape as background to respect the imported
@@ -142,12 +252,23 @@
(reduce (partial create-svg-children objects selected frame-id root-id svg-data) (reduce (partial create-svg-children objects selected frame-id root-id svg-data)
[unames []] [unames []]
(d/enumerate (->> (:content svg-data) (d/enumerate (->> (:content svg-data)
(mapv #(csvg/inherit-attributes root-attrs %)))))] (mapv #(csvg/inherit-attributes root-attrs %)))))
[root-shape children]))) ;; Collect all defs from children and merge into root shape
all-defs-from-children (reduce (fn [acc child]
(if-let [child-defs (:svg-defs child)]
(merge acc child-defs)
acc))
{}
children)
;; Merge defs from svg-data and children into root shape
root-shape-with-defs (assoc root-shape :svg-defs (merge def-nodes all-defs-from-children))]
[root-shape-with-defs children])))
(defn create-raw-svg (defn create-raw-svg
[name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}] [name frame-id {:keys [x y width height offset-x offset-y defs] :as svg-data} {:keys [attrs] :as data}]
(let [props (csvg/attrs->props attrs) (let [props (csvg/attrs->props attrs)
vbox (grc/make-rect offset-x offset-y width height)] vbox (grc/make-rect offset-x offset-y width height)]
(cts/setup-shape (cts/setup-shape
@@ -160,10 +281,11 @@
:y y :y y
:content data :content data
:svg-attrs props :svg-attrs props
:svg-viewbox vbox}))) :svg-viewbox vbox
:svg-defs defs})))
(defn create-svg-root (defn create-svg-root
[id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}] [id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs defs] :as svg-data}]
(let [props (-> (dissoc attrs :viewBox :view-box :xmlns) (let [props (-> (dissoc attrs :viewBox :view-box :xmlns)
(d/without-keys csvg/inheritable-props) (d/without-keys csvg/inheritable-props)
(csvg/attrs->props))] (csvg/attrs->props))]
@@ -177,7 +299,8 @@
:height height :height height
:x (+ x offset-x) :x (+ x offset-x)
:y (+ y offset-y) :y (+ y offset-y)
:svg-attrs props}))) :svg-attrs props
:svg-defs defs})))
(defn create-svg-children (defn create-svg-children
[objects selected frame-id parent-id svg-data [unames children] [_index svg-element]] [objects selected frame-id parent-id svg-data [unames children] [_index svg-element]]
@@ -198,7 +321,7 @@
(defn create-group (defn create-group
[name frame-id {:keys [x y width height offset-x offset-y] :as svg-data} {:keys [attrs]}] [name frame-id {:keys [x y width height offset-x offset-y defs] :as svg-data} {:keys [attrs]}]
(let [transform (csvg/parse-transform (:transform attrs)) (let [transform (csvg/parse-transform (:transform attrs))
attrs (-> attrs attrs (-> attrs
(d/without-keys csvg/inheritable-props) (d/without-keys csvg/inheritable-props)
@@ -214,7 +337,8 @@
:height height :height height
:svg-transform transform :svg-transform transform
:svg-attrs attrs :svg-attrs attrs
:svg-viewbox vbox}))) :svg-viewbox vbox
:svg-defs defs})))
(defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}] (defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}]
(when (and (contains? attrs :d) (seq (:d attrs))) (when (and (contains? attrs :d) (seq (:d attrs)))
@@ -523,6 +647,21 @@
:else (dm/str tag))] :else (dm/str tag))]
(dm/str "svg-" suffix))) (dm/str "svg-" suffix)))
(defn- filter-valid-def-references
"Filters out false positive references that are not valid def IDs.
Filters out:
- Colors in style attributes (hex colors like #f9dd67)
- Style fragments that contain CSS keywords (like stop-opacity)
- References that don't exist in defs"
[ref-ids defs]
(let [is-style-fragment? (fn [ref-id]
(or (clr/hex-color-string? (str "#" ref-id))
(str/includes? ref-id ";") ;; Contains CSS separator
(str/includes? ref-id "stop-opacity") ;; CSS keyword
(str/includes? ref-id "stop-color")))] ;; CSS keyword
(->> ref-ids
(remove is-style-fragment?) ;; Filter style fragments and hex colors
(filter #(contains? defs %))))) ;; Only existing defs
(defn parse-svg-element (defn parse-svg-element
[frame-id svg-data {:keys [tag attrs hidden] :as element} unames] [frame-id svg-data {:keys [tag attrs hidden] :as element} unames]
@@ -534,7 +673,11 @@
(let [name (or (:id attrs) (tag->name tag)) (let [name (or (:id attrs) (tag->name tag))
att-refs (csvg/find-attr-references attrs) att-refs (csvg/find-attr-references attrs)
defs (get svg-data :defs) defs (get svg-data :defs)
references (csvg/find-def-references defs att-refs) valid-refs (filter-valid-def-references att-refs defs)
all-refs (csvg/find-def-references defs valid-refs)
;; Filter the final result to ensure all references are valid defs
;; This prevents false positives from style attributes in gradient stops
references (filter-valid-def-references all-refs defs)
href-id (or (:href attrs) (:xlink:href attrs) " ") href-id (or (:href attrs) (:xlink:href attrs) " ")
href-id (if (and (string? href-id) href-id (if (and (string? href-id)
+1
View File
@@ -169,6 +169,7 @@
:enable-component-thumbnails :enable-component-thumbnails
:enable-render-wasm-dpr :enable-render-wasm-dpr
:enable-token-color :enable-token-color
:enable-token-shadow
:enable-inspect-styles :enable-inspect-styles
:enable-feature-fdata-objects-map]) :enable-feature-fdata-objects-map])
-2
View File
@@ -43,8 +43,6 @@
" "
#?(:cljs (:require-macros [app.common.logging :as l])) #?(:cljs (:require-macros [app.common.logging :as l]))
(:require (:require
#?(:clj [clojure.edn :as edn]
:cljs [cljs.reader :as edn])
[app.common.data :as d] [app.common.data :as d]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.pprint :as pp] [app.common.pprint :as pp]
+71 -17
View File
@@ -12,13 +12,15 @@
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.variant :as cfv] [app.common.files.variant :as cfv]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.common :as gco]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.logic.shapes :as cls] [app.common.logic.shapes :as cls]
[app.common.logic.variant-properties :as clvp] [app.common.logic.variant-properties :as clvp]
[app.common.path-names :as cpn] [app.common.path-names :as cpn]
[app.common.spec :as us]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
@@ -26,6 +28,7 @@
[app.common.types.library :as ctl] [app.common.types.library :as ctl]
[app.common.types.page :as ctp] [app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl] [app.common.types.pages-list :as ctpl]
[app.common.types.path.segment :as segment]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi] [app.common.types.shape.interactions :as ctsi]
@@ -35,8 +38,7 @@
[app.common.types.typography :as cty] [app.common.types.typography :as cty]
[app.common.types.variant :as ctv] [app.common.types.variant :as ctv]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[clojure.set :as set] [clojure.set :as set]))
[clojure.spec.alpha :as s]))
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default ;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
(log/set-level! :warn) (log/set-level! :warn)
@@ -473,10 +475,10 @@
If an asset id is given, only shapes linked to this particular asset will If an asset id is given, only shapes linked to this particular asset will
be synchronized." be synchronized."
[changes file-id asset-type asset-id library-id libraries current-file-id] [changes file-id asset-type asset-id library-id libraries current-file-id]
(s/assert #{:colors :components :typographies} asset-type) (assert (contains? #{:colors :components :typographies} asset-type))
(s/assert (s/nilable ::us/uuid) asset-id) (assert (or (nil? asset-id) (uuid? asset-id)))
(s/assert ::us/uuid file-id) (assert (uuid? file-id))
(s/assert ::us/uuid library-id) (assert (uuid? library-id))
(container-log :info asset-id (container-log :info asset-id
:msg "Sync file with library" :msg "Sync file with library"
@@ -510,10 +512,10 @@
If an asset id is given, only shapes linked to this particular asset will If an asset id is given, only shapes linked to this particular asset will
be synchronized." be synchronized."
[changes file-id asset-type asset-id library-id libraries current-file-id] [changes file-id asset-type asset-id library-id libraries current-file-id]
(s/assert #{:colors :components :typographies} asset-type) (assert (contains? #{:colors :components :typographies} asset-type))
(s/assert (s/nilable ::us/uuid) asset-id) (assert (or (nil? asset-id) (uuid? asset-id)))
(s/assert ::us/uuid file-id) (assert (uuid? file-id))
(s/assert ::us/uuid library-id) (assert (uuid? library-id))
(container-log :info asset-id (container-log :info asset-id
:msg "Sync local components with library" :msg "Sync local components with library"
@@ -1876,6 +1878,44 @@
roperations' roperations'
uoperations'))))))) uoperations')))))))
(defn- set-path-new-values
[current-shape prev-shape transform]
(let [new-content (segment/transform-content
(:content current-shape)
(gmt/transform-in (gpt/point 0 0) transform))
new-points (-> (segment/content->selrect new-content)
(grc/rect->points))
points-center (gco/points->center new-points)
new-selrect (gsh/calculate-selrect new-points points-center)
shape (assoc current-shape
:content new-content
:points new-points
:selrect new-selrect)
prev-center (segment/content-center (:content prev-shape))
delta (gpt/subtract points-center (first new-points))
new-pos (gpt/subtract prev-center delta)]
(gsh/absolute-move shape new-pos)))
(defn- switch-path-change-value
[prev-shape ;; The shape before the switch
current-shape ;; The shape after the switch (a clean copy)
ref-shape ;; The referenced shape on the main component
;; before the switch
attr]
(let [old-width (-> ref-shape :selrect :width)
new-width (-> prev-shape :selrect :width)
old-height (-> ref-shape :selrect :height)
new-height (-> prev-shape :selrect :height)
transform (-> (gpt/point (/ new-width old-width)
(/ new-height old-height))
(gmt/scale-matrix))
shape (set-path-new-values current-shape prev-shape transform)]
(get shape attr)))
(defn- switch-text-change-value (defn- switch-text-change-value
[prev-content ;; The :content of the text before the switch [prev-content ;; The :content of the text before the switch
@@ -2027,6 +2067,10 @@
(= :content attr) (= :content attr)
(touched attr-group)) (touched attr-group))
path-change?
(and (= :path (:type current-shape))
(contains? #{:points :selrect :content} attr))
;; position-data is a special case because can be affected by :geometry-group and :content-group ;; position-data is a special case because can be affected by :geometry-group and :content-group
;; so, if the position-data changes but the geometry is touched we need to reset the position-data ;; so, if the position-data changes but the geometry is touched we need to reset the position-data
;; so it's calculated again ;; so it's calculated again
@@ -2055,6 +2099,12 @@
(:content origin-ref-shape) (:content origin-ref-shape)
touched) touched)
path-change?
(switch-path-change-value previous-shape
current-shape
origin-ref-shape
attr)
:else :else
(get previous-shape attr))) (get previous-shape attr)))
@@ -2441,11 +2491,13 @@
(ctk/get-swap-slot)) (ctk/get-swap-slot))
(constantly false)) (constantly false))
;; In the cases where the swapped shape was the first element of the masked group it would make the group to loose the
;; mask property as part of the sanitization check on generate-delete-shapes, passing "ignore-mask" to prevent this
[all-parents changes] [all-parents changes]
(-> changes (-> changes
(cls/generate-delete-shapes (cls/generate-delete-shapes
file page objects (d/ordered-set (:id shape)) file page objects (d/ordered-set (:id shape))
{:allow-altering-copies true :ignore-children-fn ignore-swapped-fn})) {:allow-altering-copies true :ignore-children-fn ignore-swapped-fn :ignore-mask true}))
[new-shape changes] [new-shape changes]
(-> changes (-> changes
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))] (generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]
@@ -2815,13 +2867,15 @@
ids-map (into {} (map #(vector % (uuid/next))) all-ids) ids-map (into {} (map #(vector % (uuid/next))) all-ids)
;; If there is an alt-duplication of a variant, change its parent to root ;; If there is an alt-duplication we change to root
;; so the copy is made as a child of root ;; For variants so the copy is made as a child of root
;; This is because inside a variant-container can't be a copy ;; This is because inside a variant-container can't be a copy
;; For other shape this way the layout won't be changed when duplicated
;; and if you move outside the layout will not change
shapes (map (fn [shape] shapes (map (fn [shape]
(if (and alt-duplication? (ctk/is-variant? shape)) (cond-> shape
(assoc shape :parent-id uuid/zero :frame-id nil) alt-duplication?
shape)) (assoc :parent-id uuid/zero :frame-id uuid/zero)))
shapes) shapes)
+18 -14
View File
@@ -123,8 +123,10 @@
;; ignore-children-fn is used to ignore some descendants ;; ignore-children-fn is used to ignore some descendants
;; on the deletion process. It should receive a shape and ;; on the deletion process. It should receive a shape and
;; return a boolean ;; return a boolean
ignore-children-fn] ignore-children-fn
:or {ignore-children-fn (constantly false)}}] ignore-mask]
:or {ignore-children-fn (constantly false)
ignore-mask false}}]
(let [objects (pcb/get-objects changes) (let [objects (pcb/get-objects changes)
data (pcb/get-library-data changes) data (pcb/get-library-data changes)
page-id (pcb/get-page-id changes) page-id (pcb/get-page-id changes)
@@ -162,18 +164,20 @@
lookup (d/getf objects) lookup (d/getf objects)
groups-to-unmask groups-to-unmask
(reduce (fn [group-ids id] (when-not ignore-mask
;; When the shape to delete is the mask of a masked group, (reduce (fn [group-ids id]
;; the mask condition must be removed, and it must be ;; When the shape to delete is the mask of a masked group,
;; converted to a normal group. ;; the mask condition must be removed, and it must be
(let [obj (lookup id) ;; converted to a normal group.
parent (lookup (:parent-id obj))] (let [obj (lookup id)
(if (and (:masked-group parent) parent (lookup (:parent-id obj))]
(= id (first (:shapes parent)))) (if (and (:masked-group parent)
(conj group-ids (:id parent)) (= id (first (:shapes parent))))
group-ids))) (conj group-ids (:id parent))
#{} group-ids)))
ids-to-delete) #{}
ids-to-delete)
[])
interacting-shapes interacting-shapes
(filter (fn [shape] (filter (fn [shape]
+58 -55
View File
@@ -8,6 +8,8 @@
(:refer-clojure :exclude [deref merge parse-uuid parse-long parse-double parse-boolean type keys]) (:refer-clojure :exclude [deref merge parse-uuid parse-long parse-double parse-boolean type keys])
#?(:cljs (:require-macros [app.common.schema :refer [ignoring]])) #?(:cljs (:require-macros [app.common.schema :refer [ignoring]]))
(:require (:require
#?(:clj [malli.dev.pretty :as mdp])
#?(:clj [malli.dev.virhe :as v])
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.pprint :as pp] [app.common.pprint :as pp]
@@ -19,8 +21,6 @@
[clojure.core :as c] [clojure.core :as c]
[cuerdas.core :as str] [cuerdas.core :as str]
[malli.core :as m] [malli.core :as m]
[malli.dev.pretty :as mdp]
[malli.dev.virhe :as v]
[malli.error :as me] [malli.error :as me]
[malli.generator :as mg] [malli.generator :as mg]
[malli.registry :as mr] [malli.registry :as mr]
@@ -245,27 +245,30 @@
:level (d/nilv level 8) :level (d/nilv level 8)
:length (d/nilv length 12)}))))) :length (d/nilv length 12)})))))
(defmethod v/-format ::schemaless-explain #?(:clj
[_ explanation printer] (defmethod v/-format ::schemaless-explain
{:body [:group [_ explanation printer]
(v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break {:body [:group
(v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer)]}) (v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break
(v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer)]}))
(defmethod v/-format ::explain #?(:clj
[_ {:keys [schema] :as explanation} printer] (defmethod v/-format ::explain
{:body [:group [_ {:keys [schema] :as explanation} printer]
(v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break {:body [:group
(v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer) :break :break (v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break
(v/-block "Schema" (v/-visit schema printer) printer)]}) (v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer) :break :break
(v/-block "Schema" (v/-visit schema printer) printer)]}))
(defn pretty-explain #?(:clj
"A helper that allows print a console-friendly output for the (defn pretty-explain
explain; should not be used for other purposes" "A helper that allows print a console-friendly output for the explain;
[explain & {:keys [variant message] should not be used for other purposes"
:or {variant ::explain [explain & {:keys [variant message]
message "Validation Error"}}] :or {variant ::explain
(let [explain (fn [] (me/with-error-messages explain))] message "Validation Error"}}]
((mdp/prettifier variant message explain default-options)))) (let [explain (fn [] (me/with-error-messages explain))]
((mdp/prettifier variant message explain default-options)))))
(defmacro ignoring (defmacro ignoring
[expr] [expr]
@@ -281,7 +284,20 @@
(defn check-fn (defn check-fn
"Create a predefined check function" "Create a predefined check function"
[s & {:keys [hint type code]}] [s & {:keys [hint type code]}]
(let [s (schema s) (let [s #?(:clj
(schema s)
:cljs
(try
(schema s)
(catch :default cause
(let [data (ex-data cause)]
(if (= :malli.core/invalid-schema (:type data))
(throw (ex-info
(str "Invalid schema\n"
(pp/pprint-str (:data data)))
{}))
(throw cause))))))
validator* (delay (m/validator s)) validator* (delay (m/validator s))
explainer* (delay (m/explainer s)) explainer* (delay (m/explainer s))
hint (or ^boolean hint "check error") hint (or ^boolean hint "check error")
@@ -299,6 +315,13 @@
::explain explain})))) ::explain explain}))))
value)))) value))))
(defn coercer
[schema & {:as opts}]
(let [decode-fn (lazy-decoder schema json-transformer)
check-fn (check-fn schema opts)]
(fn [data]
(-> data decode-fn check-fn))))
(defn check (defn check
"A helper intended to be used on assertions for validate/check the "A helper intended to be used on assertions for validate/check the
schema over provided data. Raises an assertion exception. schema over provided data. Raises an assertion exception.
@@ -842,38 +865,6 @@
choices))] choices))]
{:pred pred}))}) {:pred pred}))})
;; (register!
;; {:type ::inst
;; :pred tm/instant?
;; :type-properties
;; {:title "inst"
;; :description "Satisfies Inst protocol"
;; :error/message "should be an instant"
;; :gen/gen (->> (sg/small-int :min 0 :max 100000)
;; (sg/fmap (fn [v] (tm/parse-inst v))))
;; :decode/string tm/parse-inst
;; :encode/string tm/format-inst
;; :decode/json tm/parse-inst
;; :encode/json tm/format-inst
;; ::oapi/type "string"
;; ::oapi/format "iso"}})
;; (register!
;; {:type ::timestamp
;; :pred tm/instant?
;; :type-properties
;; {:title "inst"
;; :description "Satisfies Inst protocol, the same as ::inst but encodes to epoch"
;; :error/message "should be an instant"
;; :gen/gen (->> (sg/small-int)
;; (sg/fmap (fn [v] (tm/parse-inst v))))
;; :decode/string tm/parse-inst
;; :encode/string inst-ms
;; :decode/json tm/parse-inst
;; :encode/json inst-ms
;; ::oapi/type "string"
;; ::oapi/format "number"}})
#?(:clj #?(:clj
(register! (register!
@@ -951,7 +942,7 @@
:pred #(and (string? %) (not (str/blank? %))) :pred #(and (string? %) (not (str/blank? %)))
:property-pred :property-pred
(fn [{:keys [min max] :as props}] (fn [{:keys [min max] :as props}]
(if (seq props) (if (or min max)
(fn [value] (fn [value]
(let [size (count value)] (let [size (count value)]
(cond (cond
@@ -1018,6 +1009,15 @@
{:title "agent" {:title "agent"
:description "instance of clojure agent"}})) :description "instance of clojure agent"}}))
#?(:clj
(register!
{:type ::bytes
:pred bytes?
:type-properties
{:title "bytes"
:description "bytes array"}}))
(register! ::any (mu/update-properties :any assoc :gen/gen sg/any)) (register! ::any (mu/update-properties :any assoc :gen/gen sg/any))
;; ---- PREDICATES ;; ---- PREDICATES
@@ -1025,6 +1025,9 @@
(def valid-safe-number? (def valid-safe-number?
(lazy-validator ::safe-number)) (lazy-validator ::safe-number))
(def valid-safe-int?
(lazy-validator ::safe-int))
(def valid-text? (def valid-text?
(validator ::text)) (validator ::text))
+12 -2
View File
@@ -546,9 +546,19 @@
filter-values))) filter-values)))
(defn extract-ids [val] (defn extract-ids [val]
(when (some? val) ;; Extract referenced ids from string values like "url(#myId)".
;; Non-string values (maps, numbers, nil, etc.) return an empty seq
;; to avoid re-seq type errors when attributes carry nested structures.
(cond
(string? val)
(->> (re-seq xml-id-regex val) (->> (re-seq xml-id-regex val)
(mapv second)))) (mapv second))
(sequential? val)
(mapcat extract-ids val)
:else
[]))
(defn fix-dot-number (defn fix-dot-number
"Fixes decimal numbers starting in dot but without leading 0" "Fixes decimal numbers starting in dot but without leading 0"
+1 -1
View File
@@ -340,7 +340,7 @@
(dfn-diff t2 t1))) (dfn-diff t2 t1)))
#?(:cljs #?(:cljs
(defn set-default-locale! (defn set-default-locale
[locale] [locale]
(when-let [locale (unchecked-get locales locale)] (when-let [locale (unchecked-get locales locale)]
(dfn-set-default-options #js {:locale locale})))) (dfn-set-default-options #js {:locale locale}))))
+2 -2
View File
@@ -269,8 +269,8 @@
"Remove flex children properties except the fit-content for flex layouts. These are properties "Remove flex children properties except the fit-content for flex layouts. These are properties
that we don't have to propagate to copies but will be respected when swapping components" that we don't have to propagate to copies but will be respected when swapping components"
[shape] [shape]
(let [layout-item-h-sizing (when (and (ctl/flex-layout? shape) (ctl/auto-width? shape)) :auto) (let [layout-item-h-sizing (when (and (ctl/any-layout? shape) (ctl/auto-width? shape)) :auto)
layout-item-v-sizing (when (and (ctl/flex-layout? shape) (ctl/auto-height? shape)) :auto)] layout-item-v-sizing (when (and (ctl/any-layout? shape) (ctl/auto-height? shape)) :auto)]
(-> shape (-> shape
(d/without-keys ctk/swap-keep-attrs) (d/without-keys ctk/swap-keep-attrs)
(cond-> (some? layout-item-h-sizing) (cond-> (some? layout-item-h-sizing)
+10 -10
View File
@@ -362,24 +362,24 @@
component (ctkl/get-component component-file (:component-id top-instance) true) component (ctkl/get-component component-file (:component-id top-instance) true)
remote-shape (get-ref-shape component-file component shape) remote-shape (get-ref-shape component-file component shape)
component-container (get-component-container component-file component) component-container (get-component-container component-file component)
[remote-shape component-container] [remote-shape component-container component-file]
(if (some? remote-shape) (if (some? remote-shape)
[remote-shape component-container] [remote-shape component-container component-file]
;; If not found, try the case of this being a fostered or swapped children ;; If not found, try the case of this being a fostered or swapped children
(let [head-instance (ctn/get-head-shape (:objects container) shape) (let [head-instance (ctn/get-head-shape (:objects container) shape)
component-file (get-in libraries [(:component-file head-instance) :data]) component-file (get-in libraries [(:component-file head-instance) :data])
head-component (ctkl/get-component component-file (:component-id head-instance) true) head-component (ctkl/get-component component-file (:component-id head-instance) true)
remote-shape' (get-ref-shape component-file head-component shape) remote-shape' (get-ref-shape component-file head-component shape)
component-container (get-component-container component-file component)] component-container' (get-component-container component-file head-component)]
[remote-shape' component-container]))] [remote-shape' component-container' component-file]))]
(if (nil? remote-shape) (if (nil? remote-shape)
nil nil
(if (nil? (:shape-ref remote-shape)) (if (nil? (:shape-ref remote-shape))
(cond-> remote-shape (cond-> remote-shape
(and remote-shape with-context?) (and remote-shape with-context?)
(with-meta {:file {:id (:id file-data) (with-meta {:file {:id (:id component-file)
:data file-data} :data component-file}
:container component-container})) :container component-container}))
(find-remote-shape component-container libraries remote-shape :with-context? with-context?))))) (find-remote-shape component-container libraries remote-shape :with-context? with-context?)))))
+9 -7
View File
@@ -112,8 +112,10 @@
(:c2y params) (update-in [index :params :c2y] + (:c2y params))) (:c2y params) (update-in [index :params :c2y] + (:c2y params)))
content))] content))]
(impl/path-data (if (some? modifiers)
(reduce apply-to-index (vec content) modifiers)))) (impl/path-data
(reduce apply-to-index (vec content) modifiers))
content)))
(defn transform-content (defn transform-content
"Applies a transformation matrix over content and returns a new "Applies a transformation matrix over content and returns a new
@@ -234,16 +236,15 @@
"Calculate the boolean content from shape and objects. Returns a "Calculate the boolean content from shape and objects. Returns a
packed PathData instance" packed PathData instance"
[shape objects] [shape objects]
(let [content (if (fn? wasm:calc-bool-content) (let [content (calc-bool-content* shape objects)]
(wasm:calc-bool-content (get shape :bool-type)
(get shape :shapes))
(calc-bool-content* shape objects))]
(impl/path-data content))) (impl/path-data content)))
(defn update-bool-shape (defn update-bool-shape
"Calculates the selrect+points for the boolean shape" "Calculates the selrect+points for the boolean shape"
[shape objects] [shape objects]
(let [content (calc-bool-content shape objects) (let [content (if (fn? wasm:calc-bool-content)
(wasm:calc-bool-content shape objects)
(calc-bool-content shape objects))
shape (assoc shape :content content)] shape (assoc shape :content content)]
(update-geometry shape))) (update-geometry shape)))
@@ -267,3 +268,4 @@
(-> (stp/convert-to-path shape objects) (-> (stp/convert-to-path shape objects)
(update :content impl/path-data)))) (update :content impl/path-data))))
(dm/export impl/decode-segments)
@@ -565,6 +565,9 @@
(def check-content (def check-content
(sm/check-fn schema:content)) (sm/check-fn schema:content))
(def decode-segments
(sm/lazy-decoder schema:segments sm/json-transformer))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CONSTRUCTORS & PREDICATES ;; CONSTRUCTORS & PREDICATES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+29
View File
@@ -0,0 +1,29 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.types.project
(:require
[app.common.schema :as sm]
[app.common.time :as cm]))
(def schema:project
[:map {:title "Profile"}
[:id ::sm/uuid]
[:created-at {:optional true} ::cm/inst]
[:modified-at {:optional true} ::cm/inst]
[:name :string]
[:is-default {:optional true} ::sm/boolean]
[:is-pinned {:optional true} ::sm/boolean]
[:count {:optional true} ::sm/int]
[:total-count {:optional true} ::sm/int]
[:team-id ::sm/uuid]])
(def valid-project?
(sm/lazy-validator schema:project))
(def check-project
(sm/check-fn schema:project))
+5 -2
View File
@@ -59,6 +59,7 @@
:dimensions "dimension" :dimensions "dimension"
:font-family "fontFamilies" :font-family "fontFamilies"
:font-size "fontSizes" :font-size "fontSizes"
:font-weight "fontWeights"
:letter-spacing "letterSpacing" :letter-spacing "letterSpacing"
:number "number" :number "number"
:opacity "opacity" :opacity "opacity"
@@ -70,7 +71,6 @@
:stroke-width "borderWidth" :stroke-width "borderWidth"
:text-case "textCase" :text-case "textCase"
:text-decoration "textDecoration" :text-decoration "textDecoration"
:font-weight "fontWeights"
:typography "typography"}) :typography "typography"})
(def dtcg-token-type->token-type (def dtcg-token-type->token-type
@@ -97,9 +97,12 @@
(def token-types (def token-types
(into #{} (keys token-type->dtcg-token-type))) (into #{} (keys token-type->dtcg-token-type)))
(def token-name-validation-regex
#"^[a-zA-Z0-9_-][a-zA-Z0-9$_-]*(\.[a-zA-Z0-9$_-]+)*$")
(def token-name-ref (def token-name-ref
[:re {:title "TokenNameRef" :gen/gen sg/text} [:re {:title "TokenNameRef" :gen/gen sg/text}
#"^(?!\$)([a-zA-Z0-9-$_]+\.?)*(?<!\.)$"]) token-name-validation-regex])
(def ^:private schema:color (def ^:private schema:color
[:map [:map
+15 -14
View File
@@ -1410,8 +1410,8 @@ Will return a value that matches this schema:
;; NOTE: we can't assign statically at eval time the value of a ;; NOTE: we can't assign statically at eval time the value of a
;; function that is declared but not defined; so we need to pass ;; function that is declared but not defined; so we need to pass
;; an anonymous function and delegate the resolution to runtime ;; an anonymous function and delegate the resolution to runtime
{:encode/json #(export-dtcg-json %) {:encode/json #(some-> % export-dtcg-json)
:decode/json #(read-multi-set-dtcg %) :decode/json #(some-> % read-multi-set-dtcg)
;; FIXME: add better, more reallistic generator ;; FIXME: add better, more reallistic generator
:gen/gen (->> (sg/small-int) :gen/gen (->> (sg/small-int)
(sg/fmap (fn [_] (sg/fmap (fn [_]
@@ -1462,11 +1462,12 @@ Will return a value that matches this schema:
(def ^:private schema:dtcg-node (def ^:private schema:dtcg-node
[:schema {:registry [:schema {:registry
{::simple-value {::simple-value
[:or :string :int :double] [:or :string :int :double ::sm/boolean]
::value ::value
[:or [:or
[:ref ::simple-value] [:ref ::simple-value]
[:vector ::simple-value] [:vector ::simple-value]
[:vector [:map-of :string ::simple-value]]
[:map-of :string [:or [:map-of :string [:or
[:ref ::simple-value] [:ref ::simple-value]
[:vector ::simple-value]]]]}} [:vector ::simple-value]]]]}}
@@ -1545,7 +1546,7 @@ Will return a value that matches this schema:
(and (not (contains? decoded-json "$metadata")) (and (not (contains? decoded-json "$metadata"))
(not (contains? decoded-json "$themes")))) (not (contains? decoded-json "$themes"))))
(defn- convert-dtcg-font-family (defn convert-dtcg-font-family
"Convert font-family token value from DTCG format to internal format. "Convert font-family token value from DTCG format to internal format.
- If value is a string, split it into a collection of font families - If value is a string, split it into a collection of font families
- If value is already an array, keep it as is - If value is already an array, keep it as is
@@ -1556,7 +1557,7 @@ Will return a value that matches this schema:
(sequential? value) value (sequential? value) value
:else value)) :else value))
(defn- convert-dtcg-typography-composite (defn convert-dtcg-typography-composite
"Convert typography token value keys from DTCG format to internal format." "Convert typography token value keys from DTCG format to internal format."
[value] [value]
(if (map? value) (if (map? value)
@@ -1568,17 +1569,17 @@ Will return a value that matches this schema:
;; Reference value ;; Reference value
value)) value))
(defn- convert-dtcg-shadow-composite (defn convert-dtcg-shadow-composite
"Convert shadow token value from DTCG format to internal format." "Convert shadow token value from DTCG format to internal format."
[value] [value]
(let [process-shadow (fn [shadow] (let [process-shadow (fn [shadow]
(if (map? shadow) (if (map? shadow)
(let [legacy-shadow-type (get "type" shadow)] (let [legacy-shadow-type (get "type" shadow)]
(-> shadow (-> shadow
(set/rename-keys {"x" :offsetX (set/rename-keys {"x" :offset-x
"offsetX" :offsetX "offsetX" :offset-x
"y" :offsetY "y" :offset-y
"offsetY" :offsetY "offsetY" :offset-y
"blur" :blur "blur" :blur
"spread" :spread "spread" :spread
"color" :color "color" :color
@@ -1589,7 +1590,7 @@ Will return a value that matches this schema:
(= "false" %) false (= "false" %) false
(= legacy-shadow-type "innerShadow") true (= legacy-shadow-type "innerShadow") true
:else false)) :else false))
(select-keys [:offsetX :offsetY :blur :spread :color :inset]))) (select-keys [:offset-x :offset-y :blur :spread :color :inset])))
shadow))] shadow))]
(cond (cond
;; Reference value - keep as string ;; Reference value - keep as string
@@ -1679,7 +1680,7 @@ Will return a value that matches this schema:
["id" {:optional true} :string] ["id" {:optional true} :string]
["name" :string] ["name" :string]
["description" :string] ["description" :string]
["isSource" :boolean] ["isSource" {:optional true} :boolean]
["selectedTokenSets" ["selectedTokenSets"
[:map-of :string [:enum "enabled" "disabled"]]]]]] [:map-of :string [:enum "enabled" "disabled"]]]]]]
["$metadata" {:optional true} ["$metadata" {:optional true}
@@ -1860,8 +1861,8 @@ Will return a value that matches this schema:
(mapv (fn [shadow] (mapv (fn [shadow]
(if (map? shadow) (if (map? shadow)
(-> shadow (-> shadow
(set/rename-keys {:offsetX "offsetX" (set/rename-keys {:offset-x "offsetX"
:offsetY "offsetY" :offset-y "offsetY"
:blur "blur" :blur "blur"
:spread "spread" :spread "spread"
:color "color" :color "color"
+2 -1
View File
@@ -14,7 +14,8 @@
(defn parse (defn parse
[data] [data]
(cond (cond
(str/starts-with? data "%") (or (str/starts-with? data "%")
(= data "develop"))
{:full "develop" {:full "develop"
:branch "develop" :branch "develop"
:base "0.0.0" :base "0.0.0"
@@ -1897,15 +1897,15 @@
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-single")] (let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-single")]
(t/is (some? token)) (t/is (some? token))
(t/is (= :shadow (:type token))) (t/is (= :shadow (:type token)))
(t/is (= [{:offsetX "0", :offsetY "2px", :blur "4px", :spread "0", :color "#000", :inset false}] (t/is (= [{:offset-x "0", :offset-y "2px", :blur "4px", :spread "0", :color "#000", :inset false}]
(:value token))))) (:value token)))))
(t/testing "multiple shadow token" (t/testing "multiple shadow token"
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-multiple")] (let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-multiple")]
(t/is (some? token)) (t/is (some? token))
(t/is (= :shadow (:type token))) (t/is (= :shadow (:type token)))
(t/is (= [{:offsetX "0", :offsetY "2px", :blur "4px", :spread "0", :color "#000", :inset true} (t/is (= [{:offset-x "0", :offset-y "2px", :blur "4px", :spread "0", :color "#000", :inset true}
{:offsetX "0", :offsetY "8px", :blur "16px", :spread "0", :color "#000", :inset true}] {:offset-x "0", :offset-y "8px", :blur "16px", :spread "0", :color "#000", :inset true}]
(:value token))))) (:value token)))))
(t/testing "shadow token with reference" (t/testing "shadow token with reference"
@@ -1918,7 +1918,7 @@
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-with-type")] (let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-with-type")]
(t/is (some? token)) (t/is (some? token))
(t/is (= :shadow (:type token))) (t/is (= :shadow (:type token)))
(t/is (= [{:offsetX "0", :offsetY "4px", :blur "8px", :spread "0", :color "rgba(0,0,0,0.2)", :inset false}] (t/is (= [{:offset-x "0", :offset-y "4px", :blur "8px", :spread "0", :color "rgba(0,0,0,0.2)", :inset false}]
(:value token))))) (:value token)))))
(t/testing "shadow token with description" (t/testing "shadow token with description"
@@ -1937,14 +1937,14 @@
(ctob/make-token (ctob/make-token
{:name "shadow.single" {:name "shadow.single"
:type :shadow :type :shadow
:value [{:offsetX "0" :offsetY "2px" :blur "4px" :spread "0" :color "#0000001A"}] :value [{:offset-x "0" :offset-y "2px" :blur "4px" :spread "0" :color "#0000001A"}]
:description "A single shadow"}) :description "A single shadow"})
"shadow.multiple" "shadow.multiple"
(ctob/make-token (ctob/make-token
{:name "shadow.multiple" {:name "shadow.multiple"
:type :shadow :type :shadow
:value [{:offsetX "0" :offsetY "2px" :blur "4px" :spread "0" :color "#0000001A"} :value [{:offset-x "0" :offset-y "2px" :blur "4px" :spread "0" :color "#0000001A"}
{:offsetX "0" :offsetY "8px" :blur "16px" :spread "0" :color "#0000001A"}]}) {:offset-x "0" :offset-y "8px" :blur "16px" :spread "0" :color "#0000001A"}]})
"shadow.ref" "shadow.ref"
(ctob/make-token (ctob/make-token
{:name "shadow.ref" {:name "shadow.ref"
@@ -1991,7 +1991,7 @@
(ctob/make-token (ctob/make-token
{:name "shadow.test" {:name "shadow.test"
:type :shadow :type :shadow
:value [{:offsetX "1" :offsetY "1" :blur "1" :spread "1" :color "red" :inset true}] :value [{:offset-x "1" :offset-y "1" :blur "1" :spread "1" :color "red" :inset true}]
:description "Round trip test"}) :description "Round trip test"})
"shadow.ref" "shadow.ref"
(ctob/make-token (ctob/make-token
+67 -52
View File
@@ -25,55 +25,13 @@ RUN set -ex; \
binutils \ binutils \
build-essential autoconf libtool pkg-config build-essential autoconf libtool pkg-config
################################################################################
## IMAGE MAGICK
################################################################################
FROM base AS build-imagemagick
ENV IMAGEMAGICK_VERSION=7.1.1-47 \
DEBIAN_FRONTEND=noninteractive
RUN set -ex; \
apt-get -qq update; \
apt-get -qq upgrade; \
apt-get -qqy --no-install-recommends install \
libltdl-dev \
libpng-dev \
libjpeg-dev \
libtiff-dev \
libwebp-dev \
libopenexr-dev \
libfftw3-dev \
libzip-dev \
liblcms2-dev \
liblzma-dev \
libzstd-dev \
libheif-dev \
librsvg2-dev \
; \
rm -rf /var/lib/apt/lists/*
RUN set -eux; \
curl -LfsSo /tmp/magick.tar.gz https://github.com/ImageMagick/ImageMagick/archive/refs/tags/${IMAGEMAGICK_VERSION}.tar.gz; \
mkdir -p /tmp/magick; \
cd /tmp/magick; \
tar -xf /tmp/magick.tar.gz --strip-components=1; \
./configure --prefix=/opt/imagick; \
make -j 2; \
make install; \
rm -rf /opt/imagick/lib/libMagick++*; \
rm -rf /opt/imagick/include; \
rm -rf /opt/imagick/share;
################################################################################ ################################################################################
## NODE SETUP ## NODE SETUP
################################################################################ ################################################################################
FROM base AS setup-node FROM base AS setup-node
ENV NODE_VERSION=v22.21.1 \ ENV NODE_VERSION=v22.22.0 \
PATH=/opt/node/bin:$PATH PATH=/opt/node/bin:$PATH
RUN set -eux; \ RUN set -eux; \
@@ -101,24 +59,57 @@ RUN set -eux; \
corepack enable; \ corepack enable; \
rm -rf /tmp/nodejs.tar.gz; rm -rf /tmp/nodejs.tar.gz;
################################################################################
## CADDYSERVER SETUP
################################################################################
FROM base AS setup-caddy
ENV CADDY_VERSION=2.10.2
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://github.com/caddyserver/caddy/releases/download/v${CADDY_VERSION}/caddy_${CADDY_VERSION}_linux_arm64.tar.gz"; \
;; \
amd64|x86_64) \
BINARY_URL="https://github.com/caddyserver/caddy/releases/download/v${CADDY_VERSION}/caddy_${CADDY_VERSION}_linux_amd64.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
curl -LfsSo /tmp/caddy.tar.gz ${BINARY_URL}; \
mkdir -p /tmp/caddy; \
cd /tmp/caddy; \
tar -xf /tmp/caddy.tar.gz; \
chown -R root /tmp/caddy; \
mv /tmp/caddy/caddy /usr/bin/; \
rm -rf /tmp/caddy.tar.gz; \
rm -rf /tmp/caddy;
################################################################################ ################################################################################
## JVM SETUP ## JVM SETUP
################################################################################ ################################################################################
FROM base AS setup-jvm FROM base AS setup-jvm
ENV CLOJURE_VERSION=1.12.2.1565 # https://clojure.org/releases/tools
ENV CLOJURE_VERSION=1.12.4.1602
RUN set -eux; \ RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \ ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \ case "${ARCH}" in \
aarch64|arm64) \ aarch64|arm64) \
ESUM='8c5321f16d9f1d8149f83e4e9ff8ca5d9e94320b92d205e6db42a604de3d1140'; \ ESUM='9903c6b19183a33725ca1dfdae5b72400c9d00995c76fafc4a0d31c5152f33f7'; \
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.30.17-ca-jdk25.0.1-linux_aarch64.tar.gz'; \ BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_aarch64.tar.gz'; \
;; \ ;; \
amd64|x86_64) \ amd64|x86_64) \
ESUM='471b3e62bdffaed27e37005d842d8639f10d244ccce1c7cdebf7abce06c8313e'; \ ESUM='946ad9766d98fc6ab495a1a120072197db54997f6925fb96680f1ecd5591db4e'; \
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.30.17-ca-jdk25.0.1-linux_x64.tar.gz'; \ BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_x64.tar.gz'; \
;; \ ;; \
*) \ *) \
echo "Unsupported arch: ${ARCH}"; \ echo "Unsupported arch: ${ARCH}"; \
@@ -189,9 +180,10 @@ RUN set -eux; \
FROM base AS setup-utils FROM base AS setup-utils
ENV CLJKONDO_VERSION=2025.07.28 \ ENV CLJKONDO_VERSION=2026.01.19 \
BABASHKA_VERSION=1.12.208 \ BABASHKA_VERSION=1.12.208 \
CLJFMT_VERSION=0.13.1 CLJFMT_VERSION=0.15.6 \
PIXI_VERSION=0.63.2
RUN set -ex; \ RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \ ARCH="$(dpkg --print-architecture)"; \
@@ -234,6 +226,26 @@ RUN set -ex; \
tar -xf /tmp/babashka.tar.gz; \ tar -xf /tmp/babashka.tar.gz; \
rm -rf /tmp/babashka.tar.gz; rm -rf /tmp/babashka.tar.gz;
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://github.com/prefix-dev/pixi/releases/download/v$PIXI_VERSION/pixi-aarch64-unknown-linux-musl.tar.gz"; \
;; \
amd64|x86_64) \
BINARY_URL="https://github.com/prefix-dev/pixi/releases/download/v$PIXI_VERSION/pixi-x86_64-unknown-linux-musl.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
cd /tmp; \
curl -LfsSo /tmp/pixi.tar.gz ${BINARY_URL}; \
cd /opt/utils/bin; \
tar -xf /tmp/pixi.tar.gz; \
rm -rf /tmp/pixi.tar.gz;
RUN set -ex; \ RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \ ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \ case "${ARCH}" in \
@@ -385,7 +397,7 @@ ENV LANG='C.UTF-8' \
RUSTUP_HOME="/opt/rustup" \ RUSTUP_HOME="/opt/rustup" \
PATH="/opt/jdk/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH" PATH="/opt/jdk/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH"
COPY --from=build-imagemagick /opt/imagick /opt/imagick COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick
COPY --from=setup-jvm /opt/jdk /opt/jdk COPY --from=setup-jvm /opt/jdk /opt/jdk
COPY --from=setup-jvm /opt/clojure /opt/clojure COPY --from=setup-jvm /opt/clojure /opt/clojure
COPY --from=setup-node /opt/node /opt/node COPY --from=setup-node /opt/node /opt/node
@@ -393,6 +405,7 @@ COPY --from=setup-utils /opt/utils /opt/utils
COPY --from=setup-rust /opt/cargo /opt/cargo COPY --from=setup-rust /opt/cargo /opt/cargo
COPY --from=setup-rust /opt/rustup /opt/rustup COPY --from=setup-rust /opt/rustup /opt/rustup
COPY --from=setup-rust /opt/emsdk /opt/emsdk COPY --from=setup-rust /opt/emsdk /opt/emsdk
COPY --from=setup-caddy /usr/bin/caddy /usr/bin/caddy
COPY files/nginx.conf /etc/nginx/nginx.conf COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/nginx-mime.types /etc/nginx/mime.types COPY files/nginx-mime.types /etc/nginx/mime.types
@@ -403,8 +416,10 @@ COPY files/vimrc /root/.vimrc
COPY files/tmux.conf /root/.tmux.conf COPY files/tmux.conf /root/.tmux.conf
COPY files/sudoers /etc/sudoers COPY files/sudoers /etc/sudoers
COPY files/Caddyfile /home/
COPY files/selfsigned.crt /home/
COPY files/selfsigned.key /home/
COPY files/start-tmux.sh /home/start-tmux.sh COPY files/start-tmux.sh /home/start-tmux.sh
COPY files/start-tmux-back.sh /home/start-tmux-back.sh
COPY files/entrypoint.sh /home/entrypoint.sh COPY files/entrypoint.sh /home/entrypoint.sh
COPY files/init.sh /home/init.sh COPY files/init.sh /home/init.sh
+32 -4
View File
@@ -33,13 +33,23 @@ services:
- 3447:3447 - 3447:3447
- 3448:3448 - 3448:3448
- 3449:3449 - 3449:3449
- 3449:3449/udp
- 3450:3450
- 6006:6006 - 6006:6006
- 6060:6060 - 6060:6060
- 6061:6061 - 6061:6061
- 6062:6062 - 6062:6062
- 6063:6063 - 6063:6063
- 6064:6064 - 6064:6064
- 9000:9000
- 9001:9001
- 9090:9090 - 9090:9090
- 9091:9091
# MCP
- 4400:4400
- 4401:4401
- 4402:4402
environment: environment:
- EXTERNAL_UID=${CURRENT_USER_ID} - EXTERNAL_UID=${CURRENT_USER_ID}
@@ -67,6 +77,11 @@ services:
- PENPOT_LDAP_ATTRS_FULLNAME=cn - PENPOT_LDAP_ATTRS_FULLNAME=cn
- PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto - PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto
networks:
default:
aliases:
- main
minio: minio:
image: "minio/minio:RELEASE.2025-04-03T14-56-28Z" image: "minio/minio:RELEASE.2025-04-03T14-56-28Z"
command: minio server /mnt/data --console-address ":9001" command: minio server /mnt/data --console-address ":9001"
@@ -78,10 +93,6 @@ services:
- MINIO_ROOT_USER=minioadmin - MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin - MINIO_ROOT_PASSWORD=minioadmin
ports:
- 9000:9000
- 9001:9001
networks: networks:
default: default:
aliases: aliases:
@@ -115,6 +126,11 @@ services:
volumes: volumes:
- "valkey_data:/data" - "valkey_data:/data"
networks:
default:
aliases:
- redis
mailer: mailer:
image: sj26/mailcatcher:latest image: sj26/mailcatcher:latest
restart: always restart: always
@@ -123,6 +139,12 @@ services:
ports: ports:
- "1080:1080" - "1080:1080"
networks:
default:
aliases:
- mailer
# https://github.com/rroemhild/docker-test-openldap # https://github.com/rroemhild/docker-test-openldap
ldap: ldap:
image: rroemhild/test-openldap:2.1 image: rroemhild/test-openldap:2.1
@@ -136,3 +158,9 @@ services:
nofile: nofile:
soft: 1024 soft: 1024
hard: 1024 hard: 1024
networks:
default:
aliases:
- ldap
+17
View File
@@ -0,0 +1,17 @@
{
auto_https off
}
localhost:3449 {
reverse_proxy localhost:4449
tls /home/selfsigned.crt /home/selfsigned.key
header -Strict-Transport-Security
}
http://localhost:3450 {
reverse_proxy localhost:4449
}
http://penpot-devenv-main:3450 {
reverse_proxy localhost:4449
}
+2 -1
View File
@@ -6,7 +6,8 @@ export PATH="/home/penpot/.cargo/bin:/opt/jdk/bin:/opt/utils/bin:/opt/clojure/bi
export CARGO_HOME="/home/penpot/.cargo" export CARGO_HOME="/home/penpot/.cargo"
alias l='ls --color -GFlh' alias l='ls --color -GFlh'
alias rm='rm -r' alias ll='ls --color -GFlh'
alias rm='rm -rf'
alias ls='ls --color -F' alias ls='ls --color -F'
alias lsd='ls -d *(/)' alias lsd='ls -d *(/)'
alias lsf='ls -h *(.)' alias lsf='ls -h *(.)'
+2 -1
View File
@@ -2,4 +2,5 @@
set -e set -e
nginx nginx
tail -f /dev/null caddy start -c /home/Caddyfile
tail -f /dev/null;
+41 -20
View File
@@ -12,7 +12,7 @@ http {
sendfile on; sendfile on;
tcp_nopush on; tcp_nopush on;
tcp_nodelay on; tcp_nodelay on;
keepalive_timeout 65; keepalive_timeout 100;
types_hash_max_size 2048; types_hash_max_size 2048;
server_tokens off; server_tokens off;
@@ -38,11 +38,11 @@ http {
gzip_vary on; gzip_vary on;
gzip_proxied any; gzip_proxied any;
gzip_comp_level 3; gzip_comp_level 6;
gzip_buffers 16 8k; gzip_buffers 16 8k;
gzip_http_version 1.1; gzip_http_version 1.1;
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json image/svg+xml; gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json image/svg+xml application/wasm;
map $http_upgrade $connection_upgrade { map $http_upgrade $connection_upgrade {
default upgrade; default upgrade;
@@ -55,7 +55,7 @@ http {
proxy_cache_key "$host$request_uri"; proxy_cache_key "$host$request_uri";
server { server {
listen 3449 default_server; listen 4449 default_server;
server_name _; server_name _;
client_max_body_size 300M; client_max_body_size 300M;
@@ -121,6 +121,28 @@ http {
proxy_http_version 1.1; proxy_http_version 1.1;
} }
location /mcp {
alias /home/penpot/penpot/mcp/packages/plugin/dist;
proxy_http_version 1.1;
}
location /mcp/ws {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://127.0.0.1:4402;
proxy_http_version 1.1;
}
location /mcp/stream {
proxy_pass http://127.0.0.1:4401/mcp;
proxy_http_version 1.1;
}
location /mcp/sse {
proxy_pass http://127.0.0.1:4401/sse;
proxy_http_version 1.1;
}
location /admin { location /admin {
proxy_pass http://127.0.0.1:6063/admin; proxy_pass http://127.0.0.1:6063/admin;
} }
@@ -141,12 +163,18 @@ http {
proxy_pass http://127.0.0.1:5000; proxy_pass http://127.0.0.1:5000;
} }
location /nitrate/ { location /control-center {
proxy_pass http://127.0.0.1:3000/; proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
} }
location /playground { location /wasm-playground {
alias /home/penpot/penpot/experiments/; alias /home/penpot/penpot/frontend/resources/public/wasm-playground/;
add_header Cache-Control "no-cache, max-age=0"; add_header Cache-Control "no-cache, max-age=0";
autoindex on; autoindex on;
} }
@@ -223,26 +251,19 @@ http {
add_header X-Cache-Status $upstream_cache_status; add_header X-Cache-Status $upstream_cache_status;
} }
location ~ ^/js/config.js$ { location ~* \.(jpg|png|svg|ttf|woff|woff2|gif)$ {
add_header Cache-Control "no-store, no-cache, max-age=0" always; add_header Cache-Control "public, max-age=604800" always; # 7 days
} }
location ~* \.(js|css|jpg|svg|png|mjs|map)$ { location ~* \.(js|css|wasm)$ {
# We set no cache only on devenv add_header Cache-Control "no-store" always;
add_header Cache-Control "no-store, no-cache, max-age=0" always;
# add_header Cache-Control "max-age=604800" always; # 7 days
}
location ~ ^/(/|css|fonts|images|js|wasm|mjs|map) {
} }
location ~ ^/[^/]+/(.*)$ { location ~ ^/[^/]+/(.*)$ {
return 301 " /404"; return 301 " /404";
} }
add_header Last-Modified $date_gmt; add_header Cache-Control "no-store" always;
add_header Cache-Control "no-store, no-cache, max-age=0" always;
if_modified_since off;
try_files $uri /index.html$is_args$args /index.html =404; try_files $uri /index.html$is_args$args /index.html =404;
} }
} }
+22
View File
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIUa3THJQSn1+ErK65g1jDL0tjUkBYwDQYJKoZIhvcNAQEL
BQAwXzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBUxvY2FsMQ4wDAYDVQQHDAVMb2Nh
bDEOMAwGA1UECgwFTG9jYWwxDDAKBgNVBAsMA0RldjESMBAGA1UEAwwJbG9jYWxo
b3N0MB4XDTI1MTIwMjA4MjUyM1oXDTI2MTIwMjA4MjUyM1owXzELMAkGA1UEBhMC
VVMxDjAMBgNVBAgMBUxvY2FsMQ4wDAYDVQQHDAVMb2NhbDEOMAwGA1UECgwFTG9j
YWwxDDAKBgNVBAsMA0RldjESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyVIlfpIPE+QyL/q7IQOilEA7wEOZ6wbsh2Fr
59H1gSLFvgoCxI6RVUkQ/MFRnw/r1ZbAqRpc2xAl5a9Ml14q20Zlj6dAHsWX6O2J
EwNsD18dQmX3BncnjV3yCZM2iQcMFKuXG4KQNdIQNNvdIgtlrHYp0ohS9s3XC7cj
KxNrm/pW9EAXfn9AYDd/qER090L2E4ipP9m/5l3MjinNc4l2kpH9rLOgb79H0RLt
PK3/KP8ErZhAvzdmDBAdM5Z5K37b+TfB/kSVNUKL6qyw5CCjlShERLhBNprlnRfz
tHNIQ1RHq3qJJN19ZnJrLqICuQ5ztvj7hBDiOSV0LnmyKgXr6wIDAQABo28wbTAd
BgNVHQ4EFgQUPL8WGf6z/wB8TimJBx1zybsIeikwHwYDVR0jBBgwFoAUPL8WGf6z
/wB8TimJBx1zybsIeikwDwYDVR0TAQH/BAUwAwEB/zAaBgNVHREEEzARgglsb2Nh
bGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggEBACMMVyR3kbNxnzuUc2lahKH4
cPXVWOsvCvnDtjzm41XmKjUJTbtjn3p5d/ZmLbZ4zzIQULfWXO3XG/HevkvVo0g6
6pJXTXc6C6ZhFG0rIYMcPPzmGmalDV5n+lUaCVx5XbFFxvRQ7893auwhRATdwGs+
xiMyYbE2w9otKqyDItmJZJ5nW6vmXJ42YHxlXF18u9U88xqtOSMd5xZahbsmw7Gg
A4/o4TPoAX5QfA306sL443WaczsF7bmsTf9qcYa/3xxQkP5Seyqx8ePWpS22qysE
jG6XPpymxb6sb2mVaFBAzhEMb/eBvE9nRAopxmB7uV4TbqC51K/U3uo6jFX4Jbw=
-----END CERTIFICATE-----
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJUiV+kg8T5DIv
+rshA6KUQDvAQ5nrBuyHYWvn0fWBIsW+CgLEjpFVSRD8wVGfD+vVlsCpGlzbECXl
r0yXXirbRmWPp0AexZfo7YkTA2wPXx1CZfcGdyeNXfIJkzaJBwwUq5cbgpA10hA0
290iC2WsdinSiFL2zdcLtyMrE2ub+lb0QBd+f0BgN3+oRHT3QvYTiKk/2b/mXcyO
Kc1ziXaSkf2ss6Bvv0fREu08rf8o/wStmEC/N2YMEB0zlnkrftv5N8H+RJU1Qovq
rLDkIKOVKEREuEE2muWdF/O0c0hDVEereokk3X1mcmsuogK5DnO2+PuEEOI5JXQu
ebIqBevrAgMBAAECggEABqtE+LNn8nW9v98jcc2IBjc2g4D5yVJaZYWxqGVJJ7T6
Lfhw7Qf4AoZAHM9en9FMM7Ahw7hO2SboynoLJHyHGOp1FNQqiJptFNdBkjKr0rqI
4pk0HK+3zLQO/4gz50gne0vP3qZtlorV5Jpf8e/Et3jWm9XOQcTB2e6AKL4k827B
dv4Tld+/7PoZVXjahfrUWuIZr5mzyF1eUkD8sPOpdr3HJxSueqsOMjbG8XMRqCQ+
5eCWWSW5yPQlMr7M7cXM+a0k73Xn1sKl7fP3/9byji25zxGUaMu5RA1kw0Oqseid
RXuRxGphGZgnx1aFxDAPg3FtmGch7/Cc6WfqboOL0QKBgQD4GZO1gGaE8cg4lvuo
ZUX2YJu6UJuNOmuhfvG3ui4WO9PHy3btc2q+3kutSuBcyIjhi+qbXasBcX/QOOJF
udyTZc5PopNkJojS4JdXAZCiu5sKI3lp4DIt9qNISlXGgrJgdxGUO+DzarBctXdn
BSwXFw5hcjJjl7wsPGQl1tBTQwKBgQDPuz5MEM5ZeUe9CT5sQDq/ld0u4aL5AHmx
aaA2gzDgd9l2R5wHX6wLzjoVWXOmeqaYzJopt2JN4iXrtbjWkyePgZeZMyWoyJ/v
clW9bi8HM9f9EpPr7czSj9sLUnsjd9cuTD+JuXK//jRGbRpw7r7nWtLHImjj6d2v
APZRq0v2OQKBgBcESG/OObSbubeGSlKVEqiIzem7ELNJeDLDVCl3XE8zvbILbj0Z
OA39EYhCKg5xjEFgeaNwTS0VGoZ2wIc3dv81sq4wpvvjl035CBFKU+DFBt0p7Vml
MwKQnxVV0B9agLHyWe8mnvf2LeZr72ffUvfRa8QelA4pRYvVDnV0OF+BAoGAW6rM
+tQPuvwB5DFIEozlX9XKHP4E5MyI5vktceDCmMtKcx92gup9CVif2Pv4ROaqzZK8
FNyPzL6W7UTrpASb2H/fXgNsAudFbGyP2V/d8Ne34D1qeRoe4GwKxRxIqoYftpZ/
E096i66pcsqCeINiSsWRbb6JesmgwbEzAScOBkECgYEA6O/Dibc9PaqRpaiE6Qut
S3W/Rr1Pd1jbN4rOVI2TFCgMJQmc6jOdq2fCntR9acsa8HPx+djOlXTUBPKBZ/Ae
p8umRdXVWcNMnwWVWHt7tsEuR/gYkxQ5xjXeS1VDPnEre9+EaevMBuVs8HdRsKQO
uzvNGeAFEfqwIqn7CFQ+ndU=
-----END PRIVATE KEY-----
+9 -18
View File
@@ -8,14 +8,10 @@ source ~/.bashrc
echo "[start-tmux.sh] Installing node dependencies" echo "[start-tmux.sh] Installing node dependencies"
pushd ~/penpot/frontend/ pushd ~/penpot/frontend/
corepack install; ./scripts/setup;
yarn install;
yarn playwright install chromium
popd popd
pushd ~/penpot/exporter/ pushd ~/penpot/exporter/
corepack install; ./scripts/setup;
yarn install
yarn playwright install chromium
popd popd
tmux -2 new-session -d -s penpot tmux -2 new-session -d -s penpot
@@ -23,30 +19,25 @@ tmux -2 new-session -d -s penpot
tmux rename-window -t penpot:0 'frontend watch' tmux rename-window -t penpot:0 'frontend watch'
tmux select-window -t penpot:0 tmux select-window -t penpot:0
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
tmux send-keys -t penpot 'yarn run watch' enter tmux send-keys -t penpot './scripts/watch app' enter
tmux new-window -t penpot:1 -n 'frontend shadow' tmux new-window -t penpot:1 -n 'frontend storybook'
tmux select-window -t penpot:1 tmux select-window -t penpot:1
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
tmux send-keys -t penpot 'yarn run watch:app' enter tmux send-keys -t penpot './scripts/watch storybook' enter
tmux new-window -t penpot:2 -n 'frontend storybook' tmux new-window -t penpot:2 -n 'exporter'
tmux select-window -t penpot:2 tmux select-window -t penpot:2
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
tmux send-keys -t penpot 'yarn run watch:storybook' enter
tmux new-window -t penpot:3 -n 'exporter'
tmux select-window -t penpot:3
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l
tmux send-keys -t penpot 'yarn run watch' enter tmux send-keys -t penpot './scripts/watch' enter
tmux split-window -v tmux split-window -v
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
tmux send-keys -t penpot './scripts/wait-and-start.sh' enter tmux send-keys -t penpot './scripts/wait-and-start.sh' enter
tmux new-window -t penpot:4 -n 'backend' tmux new-window -t penpot:3 -n 'backend'
tmux select-window -t penpot:4 tmux select-window -t penpot:3
tmux send-keys -t penpot 'cd penpot/backend' enter C-l tmux send-keys -t penpot 'cd penpot/backend' enter C-l
tmux send-keys -t penpot './scripts/start-dev' enter tmux send-keys -t penpot './scripts/start-dev' enter
+1
View File
@@ -1,3 +1,4 @@
set -g default-command "${SHELL}"
set -g mouse off set -g mouse off
set -g history-limit 50000 set -g history-limit 50000
setw -g mode-keys emacs setw -g mode-keys emacs
+4 -1
View File
@@ -6,7 +6,7 @@ ENV LANG='C.UTF-8' \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive \
TZ=Etc/UTC TZ=Etc/UTC
ARG IMAGEMAGICK_VERSION=7.1.1-47 ARG IMAGEMAGICK_VERSION=7.1.2-13
RUN set -e; \ RUN set -e; \
apt-get -qq update; \ apt-get -qq update; \
@@ -24,6 +24,7 @@ RUN set -e; \
libltdl-dev \ libltdl-dev \
liblzma-dev \ liblzma-dev \
libopenexr-dev \ libopenexr-dev \
libxml2-dev \
libpng-dev \ libpng-dev \
librsvg2-dev \ librsvg2-dev \
libtiff-dev \ libtiff-dev \
@@ -52,6 +53,7 @@ RUN set -e; \
libfftw3-dev \ libfftw3-dev \
libheif-dev \ libheif-dev \
libjpeg-dev \ libjpeg-dev \
libxml2-dev \
liblcms2-dev \ liblcms2-dev \
libltdl-dev \ libltdl-dev \
liblzma-dev \ liblzma-dev \
@@ -77,6 +79,7 @@ RUN set -e; \
libopenjp2-7 \ libopenjp2-7 \
libpng16-16 \ libpng16-16 \
librsvg2-2 \ librsvg2-2 \
libxml2 \
libtiff6 \ libtiff6 \
libwebp7 \ libwebp7 \
libwebpdemux2 \ libwebpdemux2 \
+6 -6
View File
@@ -5,7 +5,7 @@ ENV LANG='C.UTF-8' \
LC_ALL='C.UTF-8' \ LC_ALL='C.UTF-8' \
JAVA_HOME="/opt/jdk" \ JAVA_HOME="/opt/jdk" \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive \
NODE_VERSION=v22.21.1 \ NODE_VERSION=v22.22.0 \
TZ=Etc/UTC TZ=Etc/UTC
RUN set -ex; \ RUN set -ex; \
@@ -46,12 +46,12 @@ RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \ ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \ case "${ARCH}" in \
aarch64|arm64) \ aarch64|arm64) \
ESUM='8c5321f16d9f1d8149f83e4e9ff8ca5d9e94320b92d205e6db42a604de3d1140'; \ ESUM='9903c6b19183a33725ca1dfdae5b72400c9d00995c76fafc4a0d31c5152f33f7'; \
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.30.17-ca-jdk25.0.1-linux_aarch64.tar.gz'; \ BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_aarch64.tar.gz'; \
;; \ ;; \
amd64|x86_64) \ amd64|x86_64) \
ESUM='471b3e62bdffaed27e37005d842d8639f10d244ccce1c7cdebf7abce06c8313e'; \ ESUM='946ad9766d98fc6ab495a1a120072197db54997f6925fb96680f1ecd5591db4e'; \
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.30.17-ca-jdk25.0.1-linux_x64.tar.gz'; \ BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_x64.tar.gz'; \
;; \ ;; \
*) \ *) \
echo "Unsupported arch: ${ARCH}"; \ echo "Unsupported arch: ${ARCH}"; \
@@ -125,7 +125,7 @@ RUN set -ex; \
COPY --from=build /opt/jre /opt/jre COPY --from=build /opt/jre /opt/jre
COPY --from=build /opt/node /opt/node COPY --from=build /opt/node /opt/node
COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick
ARG BUNDLE_PATH="./bundle-backend/" ARG BUNDLE_PATH="./bundle-backend/"
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/backend/ COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/backend/
+2 -2
View File
@@ -3,7 +3,7 @@ LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG=en_US.UTF-8 \ ENV LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 \
NODE_VERSION=v22.21.1 \ NODE_VERSION=v22.22.0 \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive \
PATH=/opt/node/bin:/opt/imagick/bin:$PATH PATH=/opt/node/bin:/opt/imagick/bin:$PATH
@@ -107,7 +107,7 @@ RUN set -eux; \
ARG BUNDLE_PATH="./bundle-exporter/" ARG BUNDLE_PATH="./bundle-exporter/"
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/exporter/ COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/exporter/
COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick
WORKDIR /opt/penpot/exporter WORKDIR /opt/penpot/exporter
USER penpot:penpot USER penpot:penpot
+2
View File
@@ -7,8 +7,10 @@ RUN set -ex; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \ useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
mkdir -p /opt/data/assets; \ mkdir -p /opt/data/assets; \
chown -R penpot:penpot /opt/data; \ chown -R penpot:penpot /opt/data; \
mkdir -p /etc/nginx/overrides/main.d/; \
mkdir -p /etc/nginx/overrides/http.d/; \ mkdir -p /etc/nginx/overrides/http.d/; \
mkdir -p /etc/nginx/overrides/server.d/; \ mkdir -p /etc/nginx/overrides/server.d/; \
mkdir -p /etc/nginx/overrides/assets.d/; \
mkdir -p /etc/nginx/overrides/location.d/; mkdir -p /etc/nginx/overrides/location.d/;
ARG BUNDLE_PATH="./bundle-frontend/" ARG BUNDLE_PATH="./bundle-frontend/"
+58
View File
@@ -0,0 +1,58 @@
FROM ubuntu:24.04
LABEL maintainer="Penpot <docker@penpot.app>"
ENV LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8 \
NODE_VERSION=v22.21.1 \
DEBIAN_FRONTEND=noninteractive \
PATH=/opt/node/bin:$PATH
RUN set -ex; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
mkdir -p /etc/resolvconf/resolv.conf.d; \
echo "nameserver 127.0.0.11" > /etc/resolvconf/resolv.conf.d/tail; \
apt-get -qq update; \
apt-get -qqy --no-install-recommends install \
curl \
tzdata \
locales \
ca-certificates \
; \
rm -rf /var/lib/apt/lists/*; \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
locale-gen; \
find /usr/share/i18n/locales/ -type f ! -name "en_US" ! -name "POSIX" ! -name "C" -delete;
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
;; \
amd64|x86_64) \
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
curl -LfsSo /tmp/nodejs.tar.gz ${BINARY_URL}; \
mkdir -p /opt/node; \
cd /opt/node; \
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
chown -R root /opt/node; \
rm -rf /tmp/nodejs.tar.gz; \
corepack enable; \
mkdir -p /opt/penpot; \
chown -R penpot:penpot /opt/penpot;
ARG BUNDLE_PATH="./bundle-mcp/"
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/mcp/
WORKDIR /opt/penpot/mcp
USER penpot:penpot
RUN ./setup
CMD ["node", "index.js", "--multi-user"]
+5 -11
View File
@@ -130,12 +130,6 @@ services:
environment: environment:
<< : [*penpot-flags, *penpot-public-uri, *penpot-http-body-size, *penpot-secret-key] << : [*penpot-flags, *penpot-public-uri, *penpot-http-body-size, *penpot-secret-key]
## The PREPL host. Mainly used for external programatic access to penpot backend
## (example: admin). By default it will listen on `localhost` but if you are going to use
## the `admin`, you will need to uncomment this and set the host to `0.0.0.0`.
# PENPOT_PREPL_HOST: 0.0.0.0
## Database connection parameters. Don't touch them unless you are using custom ## Database connection parameters. Don't touch them unless you are using custom
## postgresql connection parameters. ## postgresql connection parameters.
@@ -151,16 +145,16 @@ services:
## Default configuration for assets storage: using filesystem based with all files ## Default configuration for assets storage: using filesystem based with all files
## stored in a docker volume. ## stored in a docker volume.
PENPOT_ASSETS_STORAGE_BACKEND: assets-fs PENPOT_OBJECTS_STORAGE_BACKEND: fs
PENPOT_STORAGE_ASSETS_FS_DIRECTORY: /opt/data/assets PENPOT_OBJECTS_STORAGE_FS_DIRECTORY: /opt/data/assets
## Also can be configured to to use a S3 compatible storage. ## Also can be configured to to use a S3 compatible storage.
# AWS_ACCESS_KEY_ID: <KEY_ID> # AWS_ACCESS_KEY_ID: <KEY_ID>
# AWS_SECRET_ACCESS_KEY: <ACCESS_KEY> # AWS_SECRET_ACCESS_KEY: <ACCESS_KEY>
# PENPOT_ASSETS_STORAGE_BACKEND: assets-s3 # PENPOT_OBJECTS_STORAGE_BACKEND: s3
# PENPOT_STORAGE_ASSETS_S3_ENDPOINT: <ENDPOINT> # PENPOT_OBJECTS_STORAGE_S3_ENDPOINT: <ENDPOINT>
# PENPOT_STORAGE_ASSETS_S3_BUCKET: <BUKET_NAME> # PENPOT_OBJECTS_STORAGE_S3_BUCKET: <BUKET_NAME>
## Telemetry. When enabled, a periodical process will send anonymous data about this ## Telemetry. When enabled, a periodical process will send anonymous data about this
## instance. Telemetry data will enable us to learn how the application is used, ## instance. Telemetry data will enable us to learn how the application is used,
+9 -13
View File
@@ -42,11 +42,11 @@ http {
gzip_vary on; gzip_vary on;
gzip_proxied any; gzip_proxied any;
gzip_static on; gzip_static on;
gzip_comp_level 4; gzip_comp_level 6;
gzip_buffers 16 8k; gzip_buffers 16 8k;
gzip_http_version 1.1; gzip_http_version 1.1;
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json image/svg+xml; gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json image/svg+xml application/wasm;
proxy_buffer_size 16k; proxy_buffer_size 16k;
proxy_busy_buffers_size 24k; # essentially, proxy_buffer_size + 2 small buffers of 4k proxy_busy_buffers_size 24k; # essentially, proxy_buffer_size + 2 small buffers of 4k
@@ -110,6 +110,8 @@ http {
recursive_error_pages on; recursive_error_pages on;
proxy_intercept_errors on; proxy_intercept_errors on;
error_page 301 302 307 = @handle_redirect; error_page 301 302 307 = @handle_redirect;
include /etc/nginx/overrides/assets.d/*.conf;
} }
location /internal/assets { location /internal/assets {
@@ -128,6 +130,7 @@ http {
} }
location /readyz { location /readyz {
access_log off;
proxy_pass $PENPOT_BACKEND_URI$request_uri; proxy_pass $PENPOT_BACKEND_URI$request_uri;
} }
@@ -142,25 +145,18 @@ http {
location / { location / {
include /etc/nginx/overrides/location.d/*.conf; include /etc/nginx/overrides/location.d/*.conf;
location ~ ^/js/config.js$ { location ~* \.(js|css|jpg|png|svg|gif|ttf|woff|woff2|wasm|map)$ {
add_header Cache-Control "no-store, no-cache, max-age=0" always; add_header Cache-Control "public, max-age=604800" always; # 7 days
}
location ~* \.(js|css|jpg|svg|png|mjs|map)$ {
add_header Cache-Control "max-age=604800" always; # 7 days
}
location ~ ^/(/|css|fonts|images|js|wasm|mjs|map) {
} }
location ~ ^/[^/]+/(.*)$ { location ~ ^/[^/]+/(.*)$ {
return 301 " /404"; return 301 " /404";
} }
add_header Last-Modified $date_gmt; add_header X-Frame-Options SAMEORIGIN always;
add_header Cache-Control "no-store, no-cache, max-age=0" always; add_header Cache-Control "no-store, no-cache, max-age=0" always;
if_modified_since off;
try_files $uri /index.html$is_args$args /index.html =404; try_files $uri /index.html$is_args$args /index.html =404;
} }
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Loaded 100 of 945 files, more files were not shown because too many files have changed in this diff. Show more