Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06e5825c8a | ||
|
|
d30387eb77 | ||
|
|
33fd672c21 | ||
|
|
a7b2e98b8e | ||
|
|
d979894872 | ||
|
|
3d20fc508d | ||
|
|
ccfee34e76 | ||
|
|
6f3f2f9a71 | ||
|
|
ad5e8ccdb3 | ||
|
|
44c7d3fbd6 | ||
|
|
3d50aa6cb2 | ||
|
|
06afd94a74 | ||
|
|
e7d9dca55e | ||
|
|
c14ccc18b8 | ||
|
|
7d09d930fe | ||
|
|
0d9b7ca696 | ||
|
|
d215a5c402 | ||
|
|
f65292a13c | ||
|
|
94722fdec2 | ||
|
|
28509e0418 | ||
|
|
9569fa2bcb | ||
|
|
852b31c3a0 | ||
|
|
84b3f5d7c6 | ||
|
|
76bd31fe7d | ||
|
|
cc81e56d82 | ||
|
|
a9e2fc8d94 | ||
|
|
77bbf30ae4 | ||
|
|
693b52bf45 | ||
|
|
0f51b23ce7 | ||
|
|
ec61aa6b6d | ||
|
|
18aca16f98 | ||
|
|
c6465e27e3 | ||
|
|
1834a18263 | ||
|
|
d220d07875 | ||
|
|
9ca76c745f | ||
|
|
3112b240a0 | ||
|
|
56fd66b91a | ||
|
|
abc1773f65 | ||
|
|
93f5e74bb0 | ||
|
|
1ce0b60e3d | ||
|
|
38179ba11e | ||
|
|
ef80901400 | ||
|
|
719a95246a | ||
|
|
e590cd852d | ||
|
|
a9741073e5 | ||
|
|
5306bed548 | ||
|
|
92a319ddd1 | ||
|
|
68a6d4c9a8 | ||
|
|
f07495ae95 | ||
|
|
23d5fc7408 | ||
|
|
8632b18eec | ||
|
|
33e650242c | ||
|
|
e03ad25118 | ||
|
|
599656c31e | ||
|
|
5d7e6afd76 | ||
|
|
15d369493b | ||
|
|
9c9b672e3e | ||
|
|
5146221513 | ||
|
|
e53f335204 | ||
|
|
16f22a7b5c | ||
|
|
a1460115e8 | ||
|
|
2574ad3315 | ||
|
|
e6b5364a84 | ||
|
|
656f81f89f | ||
|
|
01a4ffeb8b | ||
|
|
b8c70be9a2 | ||
|
|
525adcfcbe | ||
|
|
7cce4c6532 | ||
|
|
a3fdd8b691 | ||
|
|
b6a9579c98 | ||
|
|
8c7fd0af4b | ||
|
|
cf46051f56 | ||
|
|
6393330ee1 | ||
|
|
8252bc485e | ||
|
|
47775a9e2c | ||
|
|
8191d04114 | ||
|
|
88f0f75174 | ||
|
|
1ffa956251 | ||
|
|
31054099ff | ||
|
|
983487d73c | ||
|
|
3ecf509f3b | ||
|
|
7f395b2642 | ||
|
|
2a62bd2586 | ||
|
|
1ae7515189 | ||
|
|
8c401f5346 | ||
|
|
b72de2dc8f | ||
|
|
51635770ce | ||
|
|
18a4e63da0 | ||
|
|
d71f811dbe | ||
|
|
1beb3b86aa | ||
|
|
fe20bdd00e | ||
|
|
5420897b92 | ||
|
|
e430a4c9f3 | ||
|
|
ec6d72bd91 | ||
|
|
6ec451b46d | ||
|
|
2b836f10cb | ||
|
|
1ae0f3fc87 | ||
|
|
e13c203b8d | ||
|
|
90efb665b5 | ||
|
|
47ee490158 | ||
|
|
f0f89599bc | ||
|
|
b0dc7d6ffb | ||
|
|
b7cd315872 | ||
|
|
743d4e5c8d | ||
|
|
97e4f4c424 | ||
|
|
fb9560c315 | ||
|
|
d53c090900 | ||
|
|
621e030095 | ||
|
|
157e4aa2d0 | ||
|
|
7cd2308f3b | ||
|
|
c315a15b48 | ||
|
|
8a3e6d026e | ||
|
|
0dd062d011 | ||
|
|
bfbb546699 | ||
|
|
083e77e9c5 | ||
|
|
919f78daeb | ||
|
|
b5c30f8c41 | ||
|
|
60aa426753 | ||
|
|
86f7d6b26b | ||
|
|
36732a4bd3 | ||
|
|
eff572d3bb | ||
|
|
d470d96833 | ||
|
|
cab70773d2 | ||
|
|
32ca42a093 | ||
|
|
523a97a4ec | ||
|
|
260f6861a3 | ||
|
|
edd53b419a | ||
|
|
078a3d5a5c | ||
|
|
c4e57427ac | ||
|
|
7a6405481c | ||
|
|
218f34380a | ||
|
|
47aaa2b5fa | ||
|
|
6c6b3db87e | ||
|
|
6eb32cfb79 | ||
|
|
dbba3496af | ||
|
|
55752d361f | ||
|
|
fe94ee4526 | ||
|
|
e39f292499 | ||
|
|
5c71c57dd9 | ||
|
|
52b8560b70 | ||
|
|
75860afe57 | ||
|
|
824ca1bbca | ||
|
|
5b6f9c1741 | ||
|
|
19853b832b | ||
|
|
d20c011db2 | ||
|
|
9431ae6858 | ||
|
|
96356c1b89 | ||
|
|
b7b68eeb47 | ||
|
|
9bbeb657f8 | ||
|
|
ec1af4ad96 | ||
|
|
23e7116b24 | ||
|
|
48e3f35bb3 | ||
|
|
6b794c9d12 | ||
|
|
d3ee50daf5 | ||
|
|
22a36d59d8 | ||
|
|
a948e49e51 | ||
|
|
d635f5a8dc | ||
|
|
ab3a3ef43b | ||
|
|
9c21fd3359 | ||
|
|
7b5817f407 | ||
|
|
e3405eacca | ||
|
|
44b70cf1d4 | ||
|
|
a8bd74b392 | ||
|
|
3d3e3582d6 | ||
|
|
de052b5161 | ||
|
|
e01654ba43 | ||
|
|
6ebd48b94c | ||
|
|
8a3b33797f | ||
|
|
13fd20f76f | ||
|
|
417cd80564 | ||
|
|
a57011ec7b | ||
|
|
69c880d00e | ||
|
|
9eebc467ef | ||
|
|
b77712ce73 | ||
|
|
3d3e81f314 | ||
|
|
fe6441bb24 | ||
|
|
e15f0baf30 | ||
|
|
c040cbb784 | ||
|
|
7f674b78a9 | ||
|
|
099b78affd | ||
|
|
78cc3f0aa4 | ||
|
|
76f5f12808 | ||
|
|
cb325282ec | ||
|
|
01ecde3bfa | ||
|
|
047483a70a | ||
|
|
4000ec8762 | ||
|
|
8cb2f27de8 | ||
|
|
0433336fc9 | ||
|
|
ce234fbeda | ||
|
|
fc4d31eed7 | ||
|
|
c670aac339 | ||
|
|
1d3fb5434f | ||
|
|
f478399ae0 | ||
|
|
6a1854f180 | ||
|
|
0858e297e5 | ||
|
|
bd580ab159 | ||
|
|
5780a43fe0 | ||
|
|
737eceda3a | ||
|
|
923c3c2dbd | ||
|
|
a14b4561e7 | ||
|
|
bb5568e15a | ||
|
|
5cbcec3db6 | ||
|
|
105e1fe86c | ||
|
|
3e0a916883 | ||
|
|
4f80238bc2 | ||
|
|
5156cc5d9a | ||
|
|
42c46b6cfc | ||
|
|
8b3c40b35e | ||
|
|
d3996e5fb1 | ||
|
|
0c42bca866 | ||
|
|
e5685c1f1c | ||
|
|
2784209bde | ||
|
|
024f460e99 | ||
|
|
1d9b76b62a | ||
|
|
7e17a75b7d | ||
|
|
ca093d6fae | ||
|
|
0f0b7562b5 | ||
|
|
9cdc694697 | ||
|
|
b972a4033b | ||
|
|
cbe9f4da51 | ||
|
|
c583bde9e3 | ||
|
|
3911ebdc4e | ||
|
|
3e3b18667b | ||
|
|
ed81c9b8df | ||
|
|
fbdf98d29c | ||
|
|
e603825a55 | ||
|
|
1d724783e6 | ||
|
|
e0abe7dcb5 | ||
|
|
5c1bbf5be8 | ||
|
|
bbb0d58190 | ||
|
|
88dcf9d1fe | ||
|
|
fe44c14bac | ||
|
|
20061067ad | ||
|
|
336173645e | ||
|
|
2acf15958b | ||
|
|
08267de242 | ||
|
|
35fb376a78 | ||
|
|
13fcf3a9bb | ||
|
|
83bb4bf221 | ||
|
|
dba6ae2820 | ||
|
|
ada101c236 | ||
|
|
ea48fb5825 | ||
|
|
15ed25ca79 | ||
|
|
9aa387a473 | ||
|
|
67ba91b4b9 | ||
|
|
f67f1a6a0e | ||
|
|
82d3e2024e | ||
|
|
4bd846c16d | ||
|
|
8fde6b28ed | ||
|
|
63325ec796 | ||
|
|
84415476d0 | ||
|
|
94f95ca6b8 | ||
|
|
33c786498d | ||
|
|
5abc1aafb4 | ||
|
|
1f886b1f88 | ||
|
|
5a922c6bd6 | ||
|
|
507bf7445b | ||
|
|
81b72c5acd | ||
|
|
1388865cfc | ||
|
|
1738847694 | ||
|
|
ca1c3c799d | ||
|
|
974495e08f | ||
|
|
2ed39e43c3 | ||
|
|
ce5006ae84 | ||
|
|
50dbe6ab12 | ||
|
|
0a7a65af5d | ||
|
|
ea4d0e1238 | ||
|
|
b705cf953a | ||
|
|
90ce1f56e7 | ||
|
|
ab0438cc6f | ||
|
|
c6aa9cc4b7 | ||
|
|
5779adef33 | ||
|
|
2f46cbc0d4 | ||
|
|
ebf1758958 | ||
|
|
e94c56bfa7 | ||
|
|
53be6f996b | ||
|
|
89d9591011 | ||
|
|
5a260294a1 | ||
|
|
3becfcd723 | ||
|
|
3f6e44316e | ||
|
|
5501a2815f | ||
|
|
77ef8e6fe6 | ||
|
|
1066438b02 | ||
|
|
3b23a3ad19 | ||
|
|
7396f4bfb6 | ||
|
|
916b7709dc | ||
|
|
5cf51f3d26 | ||
|
|
25acad5154 | ||
|
|
0a212b6291 | ||
|
|
443e41fea4 | ||
|
|
c7c9b04095 | ||
|
|
c61a0c0332 | ||
|
|
eb1eeb4750 | ||
|
|
a78477592b | ||
|
|
8707ff6511 | ||
|
|
3d8a251741 | ||
|
|
34e84ee3c8 | ||
|
|
0956b66281 | ||
|
|
007b3f11f9 | ||
|
|
e8201402a7 | ||
|
|
8a22477b96 | ||
|
|
a661b2564f | ||
|
|
2c3732f3f4 | ||
|
|
e16645227b | ||
|
|
45665a3c21 | ||
|
|
3e684ea54f | ||
|
|
179e6a195d | ||
|
|
98039f13d8 | ||
|
|
40c27591f6 | ||
|
|
91d20a46d1 | ||
|
|
50bead7c56 | ||
|
|
b75b999903 | ||
|
|
810f1721c8 | ||
|
|
b45bdd723f | ||
|
|
8696044620 | ||
|
|
8a8f360c7f | ||
|
|
a4646373cf | ||
|
|
f111cbb2a4 | ||
|
|
e35fc85c3d | ||
|
|
a614207f7e | ||
|
|
81bc1bb0af | ||
|
|
1798461d21 | ||
|
|
6ce3249c6d | ||
|
|
dde0fddd6f | ||
|
|
7d36bc4025 | ||
|
|
b8feb6374d | ||
|
|
0889df8e08 | ||
|
|
4637aced8c | ||
|
|
9dfe5b0865 | ||
|
|
33bcc9544a | ||
|
|
babd481b7f | ||
|
|
a9733c792d | ||
|
|
7be8ac3fd7 | ||
|
|
b0351be724 | ||
|
|
9216d965ef | ||
|
|
d04fdb5fbd | ||
|
|
4f3ca6422c | ||
|
|
1c03457fda | ||
|
|
81e0e4f222 | ||
|
|
b8392b3731 | ||
|
|
935728aa39 | ||
|
|
77dba477ca | ||
|
|
b6598d1f07 | ||
|
|
f13b3c8737 | ||
|
|
520e979363 | ||
|
|
a0f8559ffc | ||
|
|
bf1dc21c75 | ||
|
|
46c20a993f | ||
|
|
0e0106f69a | ||
|
|
19bb69cc60 | ||
|
|
504eb70988 | ||
|
|
a38f425dd3 | ||
|
|
75a2331edf | ||
|
|
74d4b9b045 | ||
|
|
c2b4c9907d | ||
|
|
bd5bbcae26 | ||
|
|
84273508ad | ||
|
|
9245ba6bc2 | ||
|
|
4be046406d | ||
|
|
84c747cd31 | ||
|
|
0036a9a0cd | ||
|
|
2105c3a68c | ||
|
|
38efa88460 | ||
|
|
6e254c2cf4 | ||
|
|
416980f063 | ||
|
|
f76710296c | ||
|
|
6251fa6b22 | ||
|
|
aedd8cc11e | ||
|
|
d1379c55f6 | ||
|
|
b125c7b5a3 | ||
|
|
496d37795b | ||
|
|
2f0853f5cc | ||
|
|
648e660bcf | ||
|
|
9f6899007a | ||
|
|
bee2f70bfa | ||
|
|
00f8eac8fa | ||
|
|
df7caacb45 | ||
|
|
641df77834 | ||
|
|
49bbdfb257 | ||
|
|
4e84deca44 | ||
|
|
0d21e52068 | ||
|
|
1b29e9a50f | ||
|
|
94af978be8 | ||
|
|
feababe2a8 | ||
|
|
5ef06685fc | ||
|
|
9f567c3bf4 | ||
|
|
1ba15e5d10 | ||
|
|
57fcec5afc | ||
|
|
58f82da61e | ||
|
|
a28c5b61ca | ||
|
|
60df56caa3 | ||
|
|
53aad7bc15 | ||
|
|
9123d199b7 | ||
|
|
37e45a8bbf | ||
|
|
3471d40f46 | ||
|
|
c6b64a8e39 | ||
|
|
511e80c948 | ||
|
|
f5a640d104 | ||
|
|
3ae7c514e4 | ||
|
|
57297741f5 | ||
|
|
eeaf28bb25 | ||
|
|
d63d692d34 | ||
|
|
fad9ed1c48 | ||
|
|
0caaefefea | ||
|
|
b179aa79b1 | ||
|
|
6b8091bb90 | ||
|
|
fe72d0af82 | ||
|
|
405ddb60d8 | ||
|
|
ef68081d1d | ||
|
|
bba02473d5 | ||
|
|
4ed49cdc5d | ||
|
|
95c0d42d5b | ||
|
|
721b337511 | ||
|
|
359379be09 | ||
|
|
876d5783cf | ||
|
|
786f73767b | ||
|
|
50f9eedcdf | ||
|
|
77c9d8a2c8 | ||
|
|
95b7784a42 | ||
|
|
4690f740b9 | ||
|
|
529c4eb38a | ||
|
|
c3a9919c4d | ||
|
|
efe74e62e8 | ||
|
|
10a2732a55 | ||
|
|
456afe46de | ||
|
|
4282cdcd2c | ||
|
|
964ef799c2 | ||
|
|
d34b6b88b6 | ||
|
|
9a58f0e954 | ||
|
|
adaf8be56d | ||
|
|
2f1b99fa53 | ||
|
|
5080fcc594 | ||
|
|
ea2d3758f0 | ||
|
|
40e3617138 | ||
|
|
e889413f26 | ||
|
|
b18c421415 | ||
|
|
e7029f2182 | ||
|
|
115273b478 | ||
|
|
fdddd3284a | ||
|
|
51385a04a0 | ||
|
|
2c3becb408 | ||
|
|
f96ed8ccd6 | ||
|
|
bda5de5c1b | ||
|
|
94c15916e2 | ||
|
|
ed0f3c3595 | ||
|
|
59f3b4db4c | ||
|
|
7ee03ad911 | ||
|
|
130b8c8214 | ||
|
|
0198d41757 | ||
|
|
567a955151 | ||
|
|
a4e6aa0588 | ||
|
|
34da754357 | ||
|
|
c2014a37b4 | ||
|
|
6611fbd13b | ||
|
|
b5a6867058 | ||
|
|
7fe20b65dc | ||
|
|
e5638cd769 | ||
|
|
8e79dfcb82 | ||
|
|
508db99a57 | ||
|
|
3c6c9894da | ||
|
|
972b23e6c0 | ||
|
|
28f550d533 | ||
|
|
2b20f75fd4 | ||
|
|
4d6d7a6a3d | ||
|
|
0f88253dd5 | ||
|
|
db1ab7be69 | ||
|
|
fcbe9d92dc | ||
|
|
9998ce0bb4 | ||
|
|
6061391c89 | ||
|
|
eabf6e36ed | ||
|
|
04274e53fa | ||
|
|
52dd9271a9 | ||
|
|
8f5a81e179 | ||
|
|
a940c08da9 | ||
|
|
3de4473251 | ||
|
|
0735140f07 | ||
|
|
dc8a07099d | ||
|
|
8e3996fbb0 | ||
|
|
67762d9450 | ||
|
|
90dcf04fb0 | ||
|
|
f84c236e02 | ||
|
|
63959a22cc | ||
|
|
8840246425 | ||
|
|
62ec66cd15 | ||
|
|
e3b87390f6 | ||
|
|
d9ab28e6ed | ||
|
|
9183dbbc43 | ||
|
|
74d00473e9 | ||
|
|
1c70f5a36b | ||
|
|
b23e0c0642 | ||
|
|
7f62652870 | ||
|
|
db0cbbbc2e | ||
|
|
48304bd26f | ||
|
|
60e32bbc71 | ||
|
|
54451608dc | ||
|
|
78d31ab11a | ||
|
|
0a80c47901 | ||
|
|
b7727122d5 | ||
|
|
8880f07a6a | ||
|
|
39eafae251 | ||
|
|
e1e09b7f96 | ||
|
|
3b39980f2f | ||
|
|
223b12d2c7 | ||
|
|
aaca2c41d8 | ||
|
|
77f1046fc8 | ||
|
|
33417a4b20 | ||
|
|
2640889dc8 | ||
|
|
dd5f3396d1 | ||
|
|
dedeae8641 | ||
|
|
a7552d412a | ||
|
|
f58475a7c9 | ||
|
|
00bbb0bfb6 | ||
|
|
d93fe89c12 | ||
|
|
553b73a83c | ||
|
|
00a45cb274 |
No files matched your search
@@ -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
|
|
||||||
@@ -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.
|
||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
@@ -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,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
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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"}
|
||||||
|
|||||||
@@ -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 \
|
||||||
|
|||||||
@@ -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))}
|
||||||
|
|||||||
@@ -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}))
|
||||||
|
|||||||
@@ -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))))
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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})))
|
||||||
|
|||||||
@@ -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,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]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)))
|
||||||
|
|
||||||
|
|||||||
@@ -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)})))))
|
||||||
@@ -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))))))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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))]
|
||||||
|
|||||||
@@ -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
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -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 }
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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"))))]
|
||||||
|
|
||||||
|
|||||||
@@ -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)))))
|
||||||
@@ -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)))))))
|
||||||
@@ -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"]
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
corepack enable;
|
||||||
|
corepack install;
|
||||||
|
yarn install;
|
||||||
|
yarn run test;
|
||||||
@@ -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."
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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}))))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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?)))))
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|||||||
@@ -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))
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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,4 +2,5 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
nginx
|
nginx
|
||||||
tail -f /dev/null
|
caddy start -c /home/Caddyfile
|
||||||
|
tail -f /dev/null;
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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 \
|
||||||
|
|||||||
@@ -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/
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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/"
|
||||||
|
|||||||
@@ -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"]
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 16 KiB |