mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b14ddfb37 | ||
|
|
597cc460aa | ||
|
|
f88f1db7e7 | ||
|
|
e523190f39 | ||
|
|
f9d2f36ab7 | ||
|
|
5df466266a | ||
|
|
cc1931fa75 | ||
|
|
00a9174939 | ||
|
|
d135e25d02 | ||
|
|
ba72a2a623 | ||
|
|
94e517df3c | ||
|
|
c8bc57d31c | ||
|
|
e5150df8a5 | ||
|
|
a9faa2aa22 | ||
|
|
fb37ee8038 | ||
|
|
8555fe3fb5 | ||
|
|
3195f3f2ba | ||
|
|
47e0c9cb51 | ||
|
|
a23f0ec53d | ||
|
|
bf57b1f33b | ||
|
|
dc6daaad0d | ||
|
|
62d2819213 | ||
|
|
6f217066a1 | ||
|
|
691fb80554 | ||
|
|
d4fda73e01 | ||
|
|
0f5a0e694a | ||
|
|
00136f9e20 | ||
|
|
a844c7d09e | ||
|
|
0128cad683 | ||
|
|
fc5acc9d92 | ||
|
|
d89b96088b | ||
|
|
9cec2d14cb | ||
|
|
ffd1abbdb3 | ||
|
|
79bc62a5d2 | ||
|
|
1c43bd363a | ||
|
|
4f404a1ccf | ||
|
|
2ae413c5ea | ||
|
|
36358fc139 | ||
|
|
d39a7ddda7 | ||
|
|
789eaa263f | ||
|
|
4575d7178b | ||
|
|
3cdf363eab | ||
|
|
d990d4181d | ||
|
|
a46dc141d0 | ||
|
|
6fee27739c | ||
|
|
1cc562a6b1 | ||
|
|
d90e51233d | ||
|
|
1cd3e2846e | ||
|
|
080faf9353 | ||
|
|
d486bb4939 | ||
|
|
41f9929fd2 | ||
|
|
0b0db1793e | ||
|
|
f3cf747cfe | ||
|
|
e3982ed4c5 | ||
|
|
5c7d675960 | ||
|
|
7fc6ec5fe1 | ||
|
|
7722001463 | ||
|
|
43d4b46b31 | ||
|
|
eee17d4d19 |
@@ -59,8 +59,8 @@ def find_duplicate_keys(file_path, keys=None, prefix=""):
|
||||
return duplicates
|
||||
|
||||
|
||||
# Maximum size for TOML files (e.g., 570 KB)
|
||||
MAX_FILE_SIZE = 570 * 1024
|
||||
# Maximum size for TOML files (e.g., 1 MB)
|
||||
MAX_FILE_SIZE = 1000 * 1024
|
||||
|
||||
|
||||
def parse_toml_file(file_path):
|
||||
@@ -259,10 +259,27 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
||||
report.append(
|
||||
f" - **_Extra keys in `{locale_dir}/{basename_current_file}`_**: `{missing_keys_str}` that are not present in **_`{basename_reference_file}`_**."
|
||||
)
|
||||
report.append("")
|
||||
report.append(" Use the following command to remove them:")
|
||||
report.append(
|
||||
f" `python scripts/translations/translation_merger.py {locale_dir} remove-unused`"
|
||||
)
|
||||
report.append("")
|
||||
if extra_keys_list:
|
||||
report.append(
|
||||
f" - **_Missing keys in `{locale_dir}/{basename_current_file}`_**: `{extra_keys_str}` that are not present in **_`{basename_reference_file}`_**."
|
||||
)
|
||||
report.append("")
|
||||
report.append(" Use the following command to add them:")
|
||||
report.append(
|
||||
f" `python scripts/translations/translation_merger.py {locale_dir} add-missing`"
|
||||
)
|
||||
report.append("")
|
||||
|
||||
if missing_keys_list or extra_keys_list:
|
||||
report.append(
|
||||
" See: https://github.com/Stirling-Tools/Stirling-PDF/tree/main/scripts/translations#2-translation_mergerpy"
|
||||
)
|
||||
else:
|
||||
report.append("2. **Test Status:** ✅ **_Passed_**")
|
||||
|
||||
|
||||
@@ -97,9 +97,9 @@ cffi==2.0.0 \
|
||||
--hash=sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453 \
|
||||
--hash=sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf
|
||||
# via weasyprint
|
||||
cfgv==3.4.0 \
|
||||
--hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \
|
||||
--hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560
|
||||
cfgv==3.5.0 \
|
||||
--hash=sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 \
|
||||
--hash=sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132
|
||||
# via pre-commit
|
||||
cssselect2==0.8.0 \
|
||||
--hash=sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e \
|
||||
@@ -109,259 +109,269 @@ distlib==0.4.0 \
|
||||
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
||||
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
||||
# via virtualenv
|
||||
filelock==3.20.0 \
|
||||
--hash=sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 \
|
||||
--hash=sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4
|
||||
filelock==3.20.3 \
|
||||
--hash=sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1 \
|
||||
--hash=sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1
|
||||
# via virtualenv
|
||||
fonttools==4.60.1 \
|
||||
--hash=sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c \
|
||||
--hash=sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc \
|
||||
--hash=sha256:0b0835ed15dd5b40d726bb61c846a688f5b4ce2208ec68779bc81860adb5851a \
|
||||
--hash=sha256:0eae96373e4b7c9e45d099d7a523444e3554360927225c1cdae221a58a45b856 \
|
||||
--hash=sha256:122e1a8ada290423c493491d002f622b1992b1ab0b488c68e31c413390dc7eb2 \
|
||||
--hash=sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259 \
|
||||
--hash=sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c \
|
||||
--hash=sha256:1525796c3ffe27bb6268ed2a1bb0dcf214d561dfaf04728abf01489eb5339dce \
|
||||
--hash=sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003 \
|
||||
--hash=sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272 \
|
||||
--hash=sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77 \
|
||||
--hash=sha256:268ecda8ca6cb5c4f044b1fb9b3b376e8cd1b361cef275082429dc4174907038 \
|
||||
--hash=sha256:282dafa55f9659e8999110bd8ed422ebe1c8aecd0dc396550b038e6c9a08b8ea \
|
||||
--hash=sha256:2ee06fc57512144d8b0445194c2da9f190f61ad51e230f14836286470c99f854 \
|
||||
--hash=sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2 \
|
||||
--hash=sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258 \
|
||||
--hash=sha256:4ba4bd646e86de16160f0fb72e31c3b9b7d0721c3e5b26b9fa2fc931dfdb2652 \
|
||||
--hash=sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08 \
|
||||
--hash=sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99 \
|
||||
--hash=sha256:596ecaca36367027d525b3b426d8a8208169d09edcf8c7506aceb3a38bfb55c7 \
|
||||
--hash=sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914 \
|
||||
--hash=sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6 \
|
||||
--hash=sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed \
|
||||
--hash=sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb \
|
||||
--hash=sha256:7473a8ed9ed09aeaa191301244a5a9dbe46fe0bf54f9d6cd21d83044c3321217 \
|
||||
--hash=sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc \
|
||||
--hash=sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f \
|
||||
--hash=sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c \
|
||||
--hash=sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877 \
|
||||
--hash=sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801 \
|
||||
--hash=sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85 \
|
||||
--hash=sha256:8b4eb332f9501cb1cd3d4d099374a1e1306783ff95489a1026bde9eb02ccc34a \
|
||||
--hash=sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb \
|
||||
--hash=sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383 \
|
||||
--hash=sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401 \
|
||||
--hash=sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28 \
|
||||
--hash=sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01 \
|
||||
--hash=sha256:a140761c4ff63d0cb9256ac752f230460ee225ccef4ad8f68affc723c88e2036 \
|
||||
--hash=sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc \
|
||||
--hash=sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac \
|
||||
--hash=sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903 \
|
||||
--hash=sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3 \
|
||||
--hash=sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6 \
|
||||
--hash=sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c \
|
||||
--hash=sha256:b42d86938e8dda1cd9a1a87a6d82f1818eaf933348429653559a458d027446da \
|
||||
--hash=sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299 \
|
||||
--hash=sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15 \
|
||||
--hash=sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199 \
|
||||
--hash=sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf \
|
||||
--hash=sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1 \
|
||||
--hash=sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537 \
|
||||
--hash=sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d \
|
||||
--hash=sha256:ee0c0b3b35b34f782afc673d503167157094a16f442ace7c6c5e0ca80b08f50c \
|
||||
--hash=sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4 \
|
||||
--hash=sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9 \
|
||||
--hash=sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed \
|
||||
--hash=sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987 \
|
||||
--hash=sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa
|
||||
fonttools==4.61.1 \
|
||||
--hash=sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87 \
|
||||
--hash=sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796 \
|
||||
--hash=sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75 \
|
||||
--hash=sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d \
|
||||
--hash=sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371 \
|
||||
--hash=sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b \
|
||||
--hash=sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b \
|
||||
--hash=sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2 \
|
||||
--hash=sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3 \
|
||||
--hash=sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9 \
|
||||
--hash=sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd \
|
||||
--hash=sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c \
|
||||
--hash=sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c \
|
||||
--hash=sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56 \
|
||||
--hash=sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37 \
|
||||
--hash=sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0 \
|
||||
--hash=sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958 \
|
||||
--hash=sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5 \
|
||||
--hash=sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118 \
|
||||
--hash=sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69 \
|
||||
--hash=sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9 \
|
||||
--hash=sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261 \
|
||||
--hash=sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb \
|
||||
--hash=sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47 \
|
||||
--hash=sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24 \
|
||||
--hash=sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c \
|
||||
--hash=sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba \
|
||||
--hash=sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c \
|
||||
--hash=sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91 \
|
||||
--hash=sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1 \
|
||||
--hash=sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19 \
|
||||
--hash=sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6 \
|
||||
--hash=sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5 \
|
||||
--hash=sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2 \
|
||||
--hash=sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d \
|
||||
--hash=sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881 \
|
||||
--hash=sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063 \
|
||||
--hash=sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7 \
|
||||
--hash=sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09 \
|
||||
--hash=sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da \
|
||||
--hash=sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e \
|
||||
--hash=sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e \
|
||||
--hash=sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8 \
|
||||
--hash=sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa \
|
||||
--hash=sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6 \
|
||||
--hash=sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e \
|
||||
--hash=sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a \
|
||||
--hash=sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c \
|
||||
--hash=sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7 \
|
||||
--hash=sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd
|
||||
# via weasyprint
|
||||
identify==2.6.15 \
|
||||
--hash=sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 \
|
||||
--hash=sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf
|
||||
identify==2.6.16 \
|
||||
--hash=sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0 \
|
||||
--hash=sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980
|
||||
# via pre-commit
|
||||
nodeenv==1.9.1 \
|
||||
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
||||
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
|
||||
nodeenv==1.10.0 \
|
||||
--hash=sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 \
|
||||
--hash=sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb
|
||||
# via pre-commit
|
||||
numpy==2.2.6 \
|
||||
--hash=sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff \
|
||||
--hash=sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47 \
|
||||
--hash=sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84 \
|
||||
--hash=sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d \
|
||||
--hash=sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6 \
|
||||
--hash=sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f \
|
||||
--hash=sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b \
|
||||
--hash=sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49 \
|
||||
--hash=sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163 \
|
||||
--hash=sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571 \
|
||||
--hash=sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42 \
|
||||
--hash=sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff \
|
||||
--hash=sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491 \
|
||||
--hash=sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4 \
|
||||
--hash=sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566 \
|
||||
--hash=sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf \
|
||||
--hash=sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40 \
|
||||
--hash=sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd \
|
||||
--hash=sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06 \
|
||||
--hash=sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282 \
|
||||
--hash=sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680 \
|
||||
--hash=sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db \
|
||||
--hash=sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3 \
|
||||
--hash=sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90 \
|
||||
--hash=sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1 \
|
||||
--hash=sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289 \
|
||||
--hash=sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab \
|
||||
--hash=sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c \
|
||||
--hash=sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d \
|
||||
--hash=sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb \
|
||||
--hash=sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d \
|
||||
--hash=sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a \
|
||||
--hash=sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf \
|
||||
--hash=sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1 \
|
||||
--hash=sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2 \
|
||||
--hash=sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a \
|
||||
--hash=sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543 \
|
||||
--hash=sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00 \
|
||||
--hash=sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c \
|
||||
--hash=sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f \
|
||||
--hash=sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd \
|
||||
--hash=sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868 \
|
||||
--hash=sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303 \
|
||||
--hash=sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83 \
|
||||
--hash=sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3 \
|
||||
--hash=sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d \
|
||||
--hash=sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87 \
|
||||
--hash=sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa \
|
||||
--hash=sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f \
|
||||
--hash=sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae \
|
||||
--hash=sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda \
|
||||
--hash=sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915 \
|
||||
--hash=sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249 \
|
||||
--hash=sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de \
|
||||
--hash=sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8
|
||||
numpy==2.4.1 \
|
||||
--hash=sha256:0093e85df2960d7e4049664b26afc58b03236e967fb942354deef3208857a04c \
|
||||
--hash=sha256:09aa8a87e45b55a1c2c205d42e2808849ece5c484b2aab11fecabec3841cafba \
|
||||
--hash=sha256:0cce2a669e3c8ba02ee563c7835f92c153cf02edff1ae05e1823f1dde21b16a5 \
|
||||
--hash=sha256:0e6e8f9d9ecf95399982019c01223dc130542960a12edfa8edd1122dfa66a8a8 \
|
||||
--hash=sha256:0f118ce6b972080ba0758c6087c3617b5ba243d806268623dc34216d69099ba0 \
|
||||
--hash=sha256:178de8f87948163d98a4c9ab5bee4ce6519ca918926ec8df195af582de28544d \
|
||||
--hash=sha256:18e14c4d09d55eef39a6ab5b08406e84bc6869c1e34eef45564804f90b7e0574 \
|
||||
--hash=sha256:2023ef86243690c2791fd6353e5b4848eedaa88ca8a2d129f462049f6d484696 \
|
||||
--hash=sha256:20d4649c773f66cc2fc36f663e091f57c3b7655f936a4c681b4250855d1da8f5 \
|
||||
--hash=sha256:2302dc0224c1cbc49bb94f7064f3f923a971bfae45c33870dcbff63a2a550505 \
|
||||
--hash=sha256:26f0bcd9c79a00e339565b303badc74d3ea2bd6d52191eeca5f95936cad107d0 \
|
||||
--hash=sha256:297c72b1b98100c2e8f873d5d35fb551fce7040ade83d67dd51d38c8d42a2162 \
|
||||
--hash=sha256:2f44de05659b67d20499cbc96d49f2650769afcb398b79b324bb6e297bfe3844 \
|
||||
--hash=sha256:2ffd257026eb1b34352e749d7cc1678b5eeec3e329ad8c9965a797e08ccba205 \
|
||||
--hash=sha256:382ad67d99ef49024f11d1ce5dcb5ad8432446e4246a4b014418ba3a1175a1f4 \
|
||||
--hash=sha256:3869ea1ee1a1edc16c29bbe3a2f2a4e515cc3a44d43903ad41e0cacdbaf733dc \
|
||||
--hash=sha256:3d1a100e48cb266090a031397863ff8a30050ceefd798f686ff92c67a486753d \
|
||||
--hash=sha256:423797bdab2eeefbe608d7c1ec7b2b4fd3c58d51460f1ee26c7500a1d9c9ee93 \
|
||||
--hash=sha256:42d7dd5fa36d16d52a84f821eb96031836fd405ee6955dd732f2023724d0aa01 \
|
||||
--hash=sha256:49e792ec351315e16da54b543db06ca8a86985ab682602d90c60ef4ff4db2a9c \
|
||||
--hash=sha256:4e53170557d37ae404bf8d542ca5b7c629d6efa1117dac6a83e394142ea0a43f \
|
||||
--hash=sha256:4f1b68ff47680c2925f8063402a693ede215f0257f02596b1318ecdfb1d79e33 \
|
||||
--hash=sha256:4f9c360ecef085e5841c539a9a12b883dff005fbd7ce46722f5e9cef52634d82 \
|
||||
--hash=sha256:529050522e983e00a6c1c6b67411083630de8b57f65e853d7b03d9281b8694d2 \
|
||||
--hash=sha256:52b5f61bdb323b566b528899cc7db2ba5d1015bda7ea811a8bcf3c89c331fa42 \
|
||||
--hash=sha256:538bf4ec353709c765ff75ae616c34d3c3dca1a68312727e8f2676ea644f8509 \
|
||||
--hash=sha256:5adf01965456a664fc727ed69cc71848f28d063217c63e1a0e200a118d5eec9a \
|
||||
--hash=sha256:5b55aa56165b17aaf15520beb9cbd33c9039810e0d9643dd4379e44294c7303e \
|
||||
--hash=sha256:5d558123217a83b2d1ba316b986e9248a1ed1971ad495963d555ccd75dcb1556 \
|
||||
--hash=sha256:5de60946f14ebe15e713a6f22850c2372fa72f4ff9a432ab44aa90edcadaa65a \
|
||||
--hash=sha256:62fea415f83ad8fdb6c20840578e5fbaf5ddd65e0ec6c3c47eda0f69da172510 \
|
||||
--hash=sha256:6436cffb4f2bf26c974344439439c95e152c9a527013f26b3577be6c2ca64295 \
|
||||
--hash=sha256:6461de5113088b399d655d45c3897fa188766415d0f568f175ab071c8873bd73 \
|
||||
--hash=sha256:69e7419c9012c4aaf695109564e3387f1259f001b4326dfa55907b098af082d3 \
|
||||
--hash=sha256:71abbea030f2cfc3092a0ff9f8c8fdefdc5e0bf7d9d9c99663538bb0ecdac0b9 \
|
||||
--hash=sha256:7211b95ca365519d3596a1d8688a95874cc94219d417504d9ecb2df99fa7bfa8 \
|
||||
--hash=sha256:727c6c3275ddefa0dc078524a85e064c057b4f4e71ca5ca29a19163c607be745 \
|
||||
--hash=sha256:79e9e06c4c2379db47f3f6fc7a8652e7498251789bf8ff5bd43bf478ef314ca2 \
|
||||
--hash=sha256:7ad270f438cbdd402c364980317fb6b117d9ec5e226fff5b4148dd9aa9fc6e02 \
|
||||
--hash=sha256:7d5d7999df434a038d75a748275cd6c0094b0ecdb0837342b332a82defc4dc4d \
|
||||
--hash=sha256:8097529164c0f3e32bb89412a0905d9100bf434d9692d9fc275e18dcf53c9344 \
|
||||
--hash=sha256:82c55962006156aeef1629b953fd359064aa47e4d82cfc8e67f0918f7da3344f \
|
||||
--hash=sha256:8361ea4220d763e54cff2fbe7d8c93526b744f7cd9ddab47afeff7e14e8503be \
|
||||
--hash=sha256:899d2c18024984814ac7e83f8f49d8e8180e2fbe1b2e252f2e7f1d06bea92425 \
|
||||
--hash=sha256:8ad35f20be147a204e28b6a0575fbf3540c5e5f802634d4258d55b1ff5facce1 \
|
||||
--hash=sha256:8f085da926c0d491ffff3096f91078cc97ea67e7e6b65e490bc8dcda65663be2 \
|
||||
--hash=sha256:9171a42fcad32dcf3fa86f0a4faa5e9f8facefdb276f54b8b390d90447cff4e2 \
|
||||
--hash=sha256:92a0e65272fd60bfa0d9278e0484c2f52fe03b97aedc02b357f33fe752c52ffb \
|
||||
--hash=sha256:941c2a93313d030f219f3a71fd3d91a728b82979a5e8034eb2e60d394a2b83f9 \
|
||||
--hash=sha256:98b35775e03ab7f868908b524fc0a84d38932d8daf7b7e1c3c3a1b6c7a2c9f15 \
|
||||
--hash=sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690 \
|
||||
--hash=sha256:a73044b752f5d34d4232f25f18160a1cc418ea4507f5f11e299d8ac36875f8a0 \
|
||||
--hash=sha256:a7870e8c5fc11aef57d6fea4b4085e537a3a60ad2cdd14322ed531fdca68d261 \
|
||||
--hash=sha256:a92f227dbcdc9e4c3e193add1a189a9909947d4f8504c576f4a732fd0b54240a \
|
||||
--hash=sha256:ac08c63cb7779b85e9d5318e6c3518b424bc1f364ac4cb2c6136f12e5ff2dccc \
|
||||
--hash=sha256:b6bcf39112e956594b3331316d90c90c90fb961e39696bda97b89462f5f3943f \
|
||||
--hash=sha256:c0faba4a331195bfa96f93dd9dfaa10b2c7aa8cda3a02b7fd635e588fe821bf5 \
|
||||
--hash=sha256:ce9ce141a505053b3c7bce3216071f3bf5c182b8b28930f14cd24d43932cd2df \
|
||||
--hash=sha256:cf6470d91d34bf669f61d515499859fa7a4c2f7c36434afb70e82df7217933f9 \
|
||||
--hash=sha256:d3703409aac693fa82c0aee023a1ae06a6e9d065dba10f5e8e80f642f1e9d0a2 \
|
||||
--hash=sha256:d3e3087f53e2b4428766b54932644d148613c5a595150533ae7f00dab2f319a8 \
|
||||
--hash=sha256:d3f8f0df9f4b8be57b3bf74a1d087fec68f927a2fab68231fdb442bf2c12e426 \
|
||||
--hash=sha256:d797454e37570cfd61143b73b8debd623c3c0952959adb817dd310a483d58a1b \
|
||||
--hash=sha256:e1a27bb1b2dee45a2a53f5ca6ff2d1a7f135287883a1689e930d44d1ff296c87 \
|
||||
--hash=sha256:e3bd2cb07841166420d2fa7146c96ce00cb3410664cbc1a6be028e456c4ee220 \
|
||||
--hash=sha256:e7b6b5e28bbd47b7532698e5db2fe1db693d84b58c254e4389d99a27bb9b8f6b \
|
||||
--hash=sha256:e867df947d427cdd7a60e3e271729090b0f0df80f5f10ab7dd436f40811699c3 \
|
||||
--hash=sha256:ea66d2b41ca4a1630aae5507ee0a71647d3124d1741980138aa8f28f44dac36e \
|
||||
--hash=sha256:edee228f76ee2dab4579fad6f51f6a305de09d444280109e0f75df247ff21501 \
|
||||
--hash=sha256:f0a90aba7d521e6954670550e561a4cb925713bd944445dbe9e729b71f6cabee \
|
||||
--hash=sha256:f93bc6892fe7b0663e5ffa83b61aab510aacffd58c16e012bb9352d489d90cb7 \
|
||||
--hash=sha256:fb1461c99de4d040666ca0444057b06541e5642f800b71c56e6ea92d6a853a0c
|
||||
# via opencv-python-headless
|
||||
opencv-python-headless==4.12.0.88 \
|
||||
--hash=sha256:1e58d664809b3350c1123484dd441e1667cd7bed3086db1b9ea1b6f6cb20b50e \
|
||||
--hash=sha256:236c8df54a90f4d02076e6f9c1cc763d794542e886c576a6fee46ec8ff75a7a9 \
|
||||
--hash=sha256:365bb2e486b50feffc2d07a405b953a8f3e8eaa63865bc650034e5c71e7a5154 \
|
||||
--hash=sha256:86b413bdd6c6bf497832e346cd5371995de148e579b9774f8eba686dee3f5528 \
|
||||
--hash=sha256:aeb4b13ecb8b4a0beb2668ea07928160ea7c2cd2d9b5ef571bbee6bafe9cc8d0 \
|
||||
--hash=sha256:cfdc017ddf2e59b6c2f53bc12d74b6b0be7ded4ec59083ea70763921af2b6c09 \
|
||||
--hash=sha256:fde2cf5c51e4def5f2132d78e0c08f9c14783cd67356922182c6845b9af87dbd
|
||||
opencv-python-headless==4.13.0.90 \
|
||||
--hash=sha256:0e0c8c9f620802fddc4fa7f471a1d263c7b0dca16cd9e7e2f996bb8bd2128c0c \
|
||||
--hash=sha256:12a28674f215542c9bf93338de1b5bffd76996d32da9acb9e739fdb9c8bbd738 \
|
||||
--hash=sha256:32255203040dc98803be96362e13f9e4bce20146898222d2e5c242f80de50da5 \
|
||||
--hash=sha256:96060fc57a1abb1144b0b8129e2ff3bfcdd0ccd8e8bd05bd85256ff4ed587d3b \
|
||||
--hash=sha256:dbc1f4625e5af3a80ebdbd84380227c0f445228588f2521b11af47710caca1ba \
|
||||
--hash=sha256:e13790342591557050157713af17a7435ac1b50c65282715093c9297fa045d8f \
|
||||
--hash=sha256:eba38bc255d0b7d1969c5bcc90a060ca2b61a3403b613872c750bfa5dfe9e03b \
|
||||
--hash=sha256:f46b17ea0aa7e4124ca6ad71143f89233ae9557f61d2326bcdb34329a1ddf9bd
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
pdf2image==1.17.0 \
|
||||
--hash=sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57 \
|
||||
--hash=sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
pillow==12.0.0 \
|
||||
--hash=sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643 \
|
||||
--hash=sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e \
|
||||
--hash=sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e \
|
||||
--hash=sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc \
|
||||
--hash=sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642 \
|
||||
--hash=sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6 \
|
||||
--hash=sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1 \
|
||||
--hash=sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b \
|
||||
--hash=sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399 \
|
||||
--hash=sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba \
|
||||
--hash=sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad \
|
||||
--hash=sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47 \
|
||||
--hash=sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739 \
|
||||
--hash=sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b \
|
||||
--hash=sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f \
|
||||
--hash=sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10 \
|
||||
--hash=sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52 \
|
||||
--hash=sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d \
|
||||
--hash=sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b \
|
||||
--hash=sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a \
|
||||
--hash=sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9 \
|
||||
--hash=sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d \
|
||||
--hash=sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098 \
|
||||
--hash=sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905 \
|
||||
--hash=sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b \
|
||||
--hash=sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3 \
|
||||
--hash=sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371 \
|
||||
--hash=sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953 \
|
||||
--hash=sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01 \
|
||||
--hash=sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca \
|
||||
--hash=sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e \
|
||||
--hash=sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7 \
|
||||
--hash=sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27 \
|
||||
--hash=sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082 \
|
||||
--hash=sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e \
|
||||
--hash=sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d \
|
||||
--hash=sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8 \
|
||||
--hash=sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a \
|
||||
--hash=sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad \
|
||||
--hash=sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3 \
|
||||
--hash=sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a \
|
||||
--hash=sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d \
|
||||
--hash=sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353 \
|
||||
--hash=sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee \
|
||||
--hash=sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b \
|
||||
--hash=sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b \
|
||||
--hash=sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a \
|
||||
--hash=sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7 \
|
||||
--hash=sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef \
|
||||
--hash=sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a \
|
||||
--hash=sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a \
|
||||
--hash=sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257 \
|
||||
--hash=sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07 \
|
||||
--hash=sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4 \
|
||||
--hash=sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c \
|
||||
--hash=sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c \
|
||||
--hash=sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4 \
|
||||
--hash=sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe \
|
||||
--hash=sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8 \
|
||||
--hash=sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5 \
|
||||
--hash=sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6 \
|
||||
--hash=sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e \
|
||||
--hash=sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8 \
|
||||
--hash=sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e \
|
||||
--hash=sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275 \
|
||||
--hash=sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3 \
|
||||
--hash=sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76 \
|
||||
--hash=sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227 \
|
||||
--hash=sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9 \
|
||||
--hash=sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5 \
|
||||
--hash=sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79 \
|
||||
--hash=sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca \
|
||||
--hash=sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa \
|
||||
--hash=sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b \
|
||||
--hash=sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e \
|
||||
--hash=sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197 \
|
||||
--hash=sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab \
|
||||
--hash=sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79 \
|
||||
--hash=sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2 \
|
||||
--hash=sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363 \
|
||||
--hash=sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0 \
|
||||
--hash=sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e \
|
||||
--hash=sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782 \
|
||||
--hash=sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925 \
|
||||
--hash=sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0 \
|
||||
--hash=sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b \
|
||||
--hash=sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced \
|
||||
--hash=sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c \
|
||||
--hash=sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344 \
|
||||
--hash=sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9 \
|
||||
--hash=sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1
|
||||
pillow==12.1.0 \
|
||||
--hash=sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d \
|
||||
--hash=sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc \
|
||||
--hash=sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84 \
|
||||
--hash=sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de \
|
||||
--hash=sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0 \
|
||||
--hash=sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef \
|
||||
--hash=sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4 \
|
||||
--hash=sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82 \
|
||||
--hash=sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9 \
|
||||
--hash=sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030 \
|
||||
--hash=sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0 \
|
||||
--hash=sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18 \
|
||||
--hash=sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a \
|
||||
--hash=sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef \
|
||||
--hash=sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b \
|
||||
--hash=sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6 \
|
||||
--hash=sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179 \
|
||||
--hash=sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e \
|
||||
--hash=sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72 \
|
||||
--hash=sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64 \
|
||||
--hash=sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451 \
|
||||
--hash=sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd \
|
||||
--hash=sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924 \
|
||||
--hash=sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616 \
|
||||
--hash=sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a \
|
||||
--hash=sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94 \
|
||||
--hash=sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc \
|
||||
--hash=sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8 \
|
||||
--hash=sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9 \
|
||||
--hash=sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91 \
|
||||
--hash=sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a \
|
||||
--hash=sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c \
|
||||
--hash=sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670 \
|
||||
--hash=sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea \
|
||||
--hash=sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91 \
|
||||
--hash=sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c \
|
||||
--hash=sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc \
|
||||
--hash=sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0 \
|
||||
--hash=sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b \
|
||||
--hash=sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65 \
|
||||
--hash=sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661 \
|
||||
--hash=sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19 \
|
||||
--hash=sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1 \
|
||||
--hash=sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0 \
|
||||
--hash=sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e \
|
||||
--hash=sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75 \
|
||||
--hash=sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4 \
|
||||
--hash=sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8 \
|
||||
--hash=sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd \
|
||||
--hash=sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7 \
|
||||
--hash=sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61 \
|
||||
--hash=sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51 \
|
||||
--hash=sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551 \
|
||||
--hash=sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45 \
|
||||
--hash=sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1 \
|
||||
--hash=sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644 \
|
||||
--hash=sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796 \
|
||||
--hash=sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587 \
|
||||
--hash=sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304 \
|
||||
--hash=sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b \
|
||||
--hash=sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8 \
|
||||
--hash=sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17 \
|
||||
--hash=sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171 \
|
||||
--hash=sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3 \
|
||||
--hash=sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7 \
|
||||
--hash=sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988 \
|
||||
--hash=sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a \
|
||||
--hash=sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0 \
|
||||
--hash=sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c \
|
||||
--hash=sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2 \
|
||||
--hash=sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14 \
|
||||
--hash=sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5 \
|
||||
--hash=sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a \
|
||||
--hash=sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377 \
|
||||
--hash=sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0 \
|
||||
--hash=sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5 \
|
||||
--hash=sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b \
|
||||
--hash=sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d \
|
||||
--hash=sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac \
|
||||
--hash=sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c \
|
||||
--hash=sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554 \
|
||||
--hash=sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643 \
|
||||
--hash=sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13 \
|
||||
--hash=sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09 \
|
||||
--hash=sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208 \
|
||||
--hash=sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda \
|
||||
--hash=sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea \
|
||||
--hash=sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e \
|
||||
--hash=sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0 \
|
||||
--hash=sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831 \
|
||||
--hash=sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd
|
||||
# via
|
||||
# -r .github/scripts/requirements_dev.in
|
||||
# pdf2image
|
||||
# weasyprint
|
||||
platformdirs==4.5.0 \
|
||||
--hash=sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312 \
|
||||
--hash=sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3
|
||||
platformdirs==4.5.1 \
|
||||
--hash=sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda \
|
||||
--hash=sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31
|
||||
# via virtualenv
|
||||
pre-commit==4.3.0 \
|
||||
--hash=sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8 \
|
||||
--hash=sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16
|
||||
pre-commit==4.5.1 \
|
||||
--hash=sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77 \
|
||||
--hash=sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
pycparser==2.23 \
|
||||
--hash=sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2 \
|
||||
--hash=sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934
|
||||
pycparser==3.0 \
|
||||
--hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
|
||||
--hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992
|
||||
# via cffi
|
||||
pydyf==0.11.0 \
|
||||
--hash=sha256:0aaf9e2ebbe786ec7a78ec3fbffa4cdcecde53fd6f563221d53c6bc1328848a3 \
|
||||
--hash=sha256:394dddf619cca9d0c55715e3c55ea121a9bf9cbc780cdc1201a2427917b86b64
|
||||
pydyf==0.12.1 \
|
||||
--hash=sha256:ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc \
|
||||
--hash=sha256:fbd7e759541ac725c29c506612003de393249b94310ea78ae44cb1d04b220095
|
||||
# via weasyprint
|
||||
pyphen==0.17.2 \
|
||||
--hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \
|
||||
@@ -442,9 +452,9 @@ pyyaml==6.0.3 \
|
||||
--hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \
|
||||
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
|
||||
# via pre-commit
|
||||
tinycss2==1.4.0 \
|
||||
--hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \
|
||||
--hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289
|
||||
tinycss2==1.5.1 \
|
||||
--hash=sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661 \
|
||||
--hash=sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957
|
||||
# via
|
||||
# cssselect2
|
||||
# weasyprint
|
||||
@@ -452,17 +462,17 @@ tinyhtml5==2.0.0 \
|
||||
--hash=sha256:086f998833da24c300c414d9fe81d9b368fd04cb9d2596a008421cbc705fcfcc \
|
||||
--hash=sha256:13683277c5b176d070f82d099d977194b7a1e26815b016114f581a74bbfbf47e
|
||||
# via weasyprint
|
||||
unoserver==3.4 \
|
||||
--hash=sha256:3dcf2204013def1d1ddd3671f38b11346bdf349fef9728277462666a8a634419 \
|
||||
--hash=sha256:64c24d33d4f65d680a2d9f676518cb28e7fd6c1f9d9a745c33e4a4cb59afdfcd
|
||||
unoserver==3.6 \
|
||||
--hash=sha256:25c360fa194396a89cb79b4edd2735f8e4f0fd8531e59db3952114585bd7df05 \
|
||||
--hash=sha256:e446bcb3638c51880f002aaeecab1cf74dfa9df81035f027f7ff2e081b6d7015
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
virtualenv==20.35.4 \
|
||||
--hash=sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c \
|
||||
--hash=sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b
|
||||
virtualenv==20.36.1 \
|
||||
--hash=sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f \
|
||||
--hash=sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba
|
||||
# via pre-commit
|
||||
weasyprint==66.0 \
|
||||
--hash=sha256:82b0783b726fcd318e2c977dcdddca76515b30044bc7a830cc4fbe717582a6d0 \
|
||||
--hash=sha256:da71dc87dc129ac9cffdc65e5477e90365ab9dbae45c744014ec1d06303dde40
|
||||
weasyprint==68.0 \
|
||||
--hash=sha256:447f40898b747cb44ac31a5d493d512e7441fd56e13f63744c099383bbf9cda9 \
|
||||
--hash=sha256:c2cb40c71b50837c5971f00171c9e4078e8c9912dd7c217f3e90e068f11e8aa1
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
webencodings==0.5.1 \
|
||||
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
|
||||
@@ -471,86 +481,27 @@ webencodings==0.5.1 \
|
||||
# cssselect2
|
||||
# tinycss2
|
||||
# tinyhtml5
|
||||
zopfli==0.2.3.post1 \
|
||||
--hash=sha256:0aa5f90d6298bda02a95bc8dc8c3c19004d5a4e44bda00b67ca7431d857b4b54 \
|
||||
--hash=sha256:0cc20b02a9531559945324c38302fd4ba763311632d0ec8a1a0aa9c10ea363e6 \
|
||||
--hash=sha256:1d8cc06605519e82b16df090e17cb3990d1158861b2872c3117f1168777b81e4 \
|
||||
--hash=sha256:1f990634fd5c5c8ced8edddd8bd45fab565123b4194d6841e01811292650acae \
|
||||
--hash=sha256:2345e713260a350bea0b01a816a469ea356bc2d63d009a0d777691ecbbcf7493 \
|
||||
--hash=sha256:2768c877f76c8a0e7519b1c86c93757f3c01492ddde55751e9988afb7eff64e1 \
|
||||
--hash=sha256:29ea74e72ffa6e291b8c6f2504ce6c146b4fe990c724c1450eb8e4c27fd31431 \
|
||||
--hash=sha256:34a99592f3d9eb6f737616b5bd74b48a589fdb3cb59a01a50d636ea81d6af272 \
|
||||
--hash=sha256:3654bfc927bc478b1c3f3ff5056ed7b20a1a37fa108ca503256d0a699c03bbb1 \
|
||||
--hash=sha256:3657e416ffb8f31d9d3424af12122bb251befae109f2e271d87d825c92fc5b7b \
|
||||
--hash=sha256:37d011e92f7b9622742c905fdbed9920a1d0361df84142807ea2a528419dea7f \
|
||||
--hash=sha256:3827170de28faf144992d3d4dcf8f3998fe3c8a6a6f4a08f1d42c2ec6119d2bb \
|
||||
--hash=sha256:39e576f93576c5c223b41d9c780bbb91fd6db4babf3223d2a4fe7bf568e2b5a8 \
|
||||
--hash=sha256:3a89277ed5f8c0fb2d0b46d669aa0633123aa7381f1f6118c12f15e0fb48f8ca \
|
||||
--hash=sha256:3c163911f8bad94b3e1db0a572e7c28ba681a0c91d0002ea1e4fa9264c21ef17 \
|
||||
--hash=sha256:3f0197b6aa6eb3086ae9e66d6dd86c4d502b6c68b0ec490496348ae8c05ecaef \
|
||||
--hash=sha256:48dba9251060289101343110ab47c0756f66f809bb4d1ddbb6d5c7e7752115c5 \
|
||||
--hash=sha256:4915a41375bdee4db749ecd07d985a0486eb688a6619f713b7bf6fbfd145e960 \
|
||||
--hash=sha256:4c1226a7e2c7105ac31503a9bb97454743f55d88164d6d46bc138051b77f609b \
|
||||
--hash=sha256:4e50ffac74842c1c1018b9b73875a0d0a877c066ab06bf7cccbaa84af97e754f \
|
||||
--hash=sha256:518f1f4ed35dd69ce06b552f84e6d081f07c552b4c661c5312d950a0b764a58a \
|
||||
--hash=sha256:5aad740b4d4fcbaaae4887823925166ffd062db3b248b3f432198fc287381d1a \
|
||||
--hash=sha256:5f272186e03ad55e7af09ab78055535c201b1a0bcc2944edb1768298d9c483a4 \
|
||||
--hash=sha256:5fcfc0dc2761e4fcc15ad5d273b4d58c2e8e059d3214a7390d4d3c8e2aee644e \
|
||||
--hash=sha256:60db20f06c3d4c5934b16cfa62a2cc5c3f0686bffe0071ed7804d3c31ab1a04e \
|
||||
--hash=sha256:615a8ac9dda265e9cc38b2a76c3142e4a9f30fea4a79c85f670850783bc6feb4 \
|
||||
--hash=sha256:6482db9876c68faac2d20a96b566ffbf65ddaadd97b222e4e73641f4f8722fc4 \
|
||||
--hash=sha256:6617fb10f9e4393b331941861d73afb119cd847e88e4974bdbe8068ceef3f73f \
|
||||
--hash=sha256:676919fba7311125244eb0c4393679ac5fe856e5864a15d122bd815205369fa0 \
|
||||
--hash=sha256:6c2d2bc8129707e34c51f9352c4636ca313b52350bbb7e04637c46c1818a2a70 \
|
||||
--hash=sha256:71390dbd3fbf6ebea9a5d85ffed8c26ee1453ee09248e9b88486e30e0397b775 \
|
||||
--hash=sha256:716cdbfc57bfd3d3e31a58e6246e8190e6849b7dbb7c4ce39ef8bbf0edb8f6d5 \
|
||||
--hash=sha256:75a26a2307b10745a83b660c404416e984ee6fca515ec7f0765f69af3ce08072 \
|
||||
--hash=sha256:7be5cc6732eb7b4df17305d8a7b293223f934a31783a874a01164703bc1be6cd \
|
||||
--hash=sha256:7cce242b5df12b2b172489daf19c32e5577dd2fac659eb4b17f6a6efb446fd5c \
|
||||
--hash=sha256:81c341d9bb87a6dbbb0d45d6e272aca80c7c97b4b210f9b6e233bf8b87242f29 \
|
||||
--hash=sha256:89899641d4de97dbad8e0cde690040d078b6aea04066dacaab98e0b5a23573f2 \
|
||||
--hash=sha256:8d5ab297d660b75c159190ce6d73035502310e40fd35170aed7d1a1aea7ddd65 \
|
||||
--hash=sha256:8fbe5bcf10d01aab3513550f284c09fef32f342b36f56bfae2120a9c4d12c130 \
|
||||
--hash=sha256:91a2327a4d7e77471fa4fbb26991c6de4a738c6fc6a33e09bb25f56a870a4b7b \
|
||||
--hash=sha256:95a260cafd56b8fffa679918937401c80bb38e1681c448b988022e4c3610965d \
|
||||
--hash=sha256:96484dc0f48be1c5d7ae9f38ed1ce41e3675fd506b27c11a6607f14b49101e99 \
|
||||
--hash=sha256:9a6aec38a989bad7ddd1ef53f1265699e49e294d08231b5313d61293f3cd6237 \
|
||||
--hash=sha256:9ba214f4f45bec195ee8559651154d3ac2932470b9d91c5715fc29c013349f8c \
|
||||
--hash=sha256:9f4a7ec2770e6af05f5a02733fd3900f30a9cd58e5d6d3727e14c5bcd6e7d587 \
|
||||
--hash=sha256:a1cf720896d2ce998bc8e051d4b4ce0d8bec007aab6243102e8e1d22a0b2fb3f \
|
||||
--hash=sha256:a241a68581d34d67b40c425cce3d1fd211c092f99d9250947824ccba9f491949 \
|
||||
--hash=sha256:a53b18797cdef27e019db595d66c4b077325afe2fd62145953275f53d84ce40c \
|
||||
--hash=sha256:a82fc2dbebe6eb908b9c665e71496f8525c1bc4d2e3a7a7722ef2b128b6227c8 \
|
||||
--hash=sha256:a86eb88e06bd87e1fff31dac878965c26b0c26db59ddcf78bb0379a954b120de \
|
||||
--hash=sha256:aa588b21044f8a74e423d8c8a4c7fc9988501878aacced793467010039c50734 \
|
||||
--hash=sha256:b05296e8bc88c92e2b21e0a9bae4740c1551ee613c1d93a51fd28a7a0b2b6fbb \
|
||||
--hash=sha256:b0ec13f352ea5ae0fc91f98a48540512eed0767d0ec4f7f3cb92d92797983d18 \
|
||||
--hash=sha256:b3df42f52502438ee973042cc551877d24619fa1cd38ef7b7e9ac74200daca8b \
|
||||
--hash=sha256:b78008a69300d929ca2efeffec951b64a312e9a811e265ea4a907ab546d79fa6 \
|
||||
--hash=sha256:b9026a21b6d41eb0e2e63f5bc1242c3fcc43ecb770963cda99a4307863dac12e \
|
||||
--hash=sha256:bbe429fc50686bb2a2608a30843e36fbaa123462a5284f136c7d9e0145220bfd \
|
||||
--hash=sha256:bfa1eb759e07d8b7aa7a310a2bc535e127ee70addf90dc8d4b946b593c3e51a8 \
|
||||
--hash=sha256:c1e0ed5d84ffa2d677cc9582fc01e61dab2e7ef8b8996e055f0a76167b1b94df \
|
||||
--hash=sha256:c4278d1873ce6e803e5d4f8d702fd3026bd67fca744aa98881324d1157ddf748 \
|
||||
--hash=sha256:cac2b37ab21c2b36a10b685b1893ebd6b0f83ae26004838ac817680881576567 \
|
||||
--hash=sha256:cbe6df25807227519debd1a57ab236f5f6bad441500e85b13903e51f93a43214 \
|
||||
--hash=sha256:cd2c002f160502608dcc822ed2441a0f4509c52e86fcfd1a09e937278ed1ca14 \
|
||||
--hash=sha256:e0137dd64a493ba6a4be37405cfd6febe650a98cc1e9dca8f6b8c63b1db11b41 \
|
||||
--hash=sha256:e63d558847166543c2c9789e6f985400a520b7eacc4b99181668b2c3aeadd352 \
|
||||
--hash=sha256:eb45a34f23da4f8bc712b6376ca5396914b0b7c09adbb001dad964eb7f3132f8 \
|
||||
--hash=sha256:ecb7572df5372abce8073df078207d9d1749f20b8b136089916a4a0868d56051 \
|
||||
--hash=sha256:f12000a6accdd4bf0a3fa6eaa1b1c7a7bc80af0a2edf3f89d770d3dcce1d0e22 \
|
||||
--hash=sha256:f7d69c1a7168ad0e9cb864e8663acb232986a0c9c9cb9801f56bf6214f53a54d \
|
||||
--hash=sha256:f815fcc2b2a457977724bad97fb4854022980f51ce7b136925e336b530545ae1 \
|
||||
--hash=sha256:fc39f5c27f962ec8660d8d20c24762431131b5d8c672b44b0a54cf2b5bcde9b9
|
||||
zopfli==0.4.0 \
|
||||
--hash=sha256:03181d48e719fcb6cf8340189c61e8f9883d8bbbdf76bf5212a74457f7d083c1 \
|
||||
--hash=sha256:18b5f1570f64d4988482e4466f10ef5f2a30f687c19ad62a64560f2152dc89eb \
|
||||
--hash=sha256:25e4863b8dc30e5d5309f87c106b0b7d3da4ed0e340b8a52b36d4471e797589f \
|
||||
--hash=sha256:7d66337be6d5613dec55213e9ac28f378c41e2cc04fbad4a10748e4df774ca85 \
|
||||
--hash=sha256:9097e8e1dfdb7f5aea5464e469946857e80502b6d29ba1b232450916bd4a74d1 \
|
||||
--hash=sha256:a8ee992b2549e090cd3f0178bf606dd41a29e0613a04cdf5054224662c72dce6 \
|
||||
--hash=sha256:b72a010d205d00b2855acc2302772067362f9ab5a012e3550662aec60d28e6b3 \
|
||||
--hash=sha256:b8bdb41fbfdc4738b7bdc09ed7c1e951579fae192391a5e694d59bb186cdbec7 \
|
||||
--hash=sha256:c3ba02a9a6ca90481d2b2f68bab038b310d63a1e3b5ae305e95a6599787ed941 \
|
||||
--hash=sha256:d1b98ad47c434ef213444a03ef2f826eeec100144d64f6a57504b9893d3931ce \
|
||||
--hash=sha256:f67d04280065e24cb9a4174cb6b3d1f763687f8cb2963aa135ad8f57c6995f5a \
|
||||
--hash=sha256:f94e4dd7d76b4fe9f5d9229372be20d7f786164eea5152d1af1c34298c3d5975
|
||||
# via fonttools
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
pip==25.3 \
|
||||
--hash=sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343 \
|
||||
--hash=sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd
|
||||
pip==26.0 \
|
||||
--hash=sha256:3ce220a0a17915972fbf1ab451baae1521c4539e778b28127efa79b974aff0fa \
|
||||
--hash=sha256:98436feffb9e31bc9339cf369fd55d3331b1580b6a6f1173bacacddcf9c34754
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
setuptools==80.9.0 \
|
||||
--hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \
|
||||
--hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c
|
||||
setuptools==80.10.2 \
|
||||
--hash=sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70 \
|
||||
--hash=sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
|
||||
@@ -12,25 +12,25 @@ distlib==0.4.0 \
|
||||
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
||||
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
||||
# via virtualenv
|
||||
filelock==3.20.0 \
|
||||
--hash=sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 \
|
||||
--hash=sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4
|
||||
filelock==3.20.3 \
|
||||
--hash=sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1 \
|
||||
--hash=sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1
|
||||
# via virtualenv
|
||||
identify==2.6.15 \
|
||||
--hash=sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 \
|
||||
--hash=sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf
|
||||
identify==2.6.16 \
|
||||
--hash=sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0 \
|
||||
--hash=sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980
|
||||
# via pre-commit
|
||||
nodeenv==1.9.1 \
|
||||
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
||||
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
|
||||
nodeenv==1.10.0 \
|
||||
--hash=sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 \
|
||||
--hash=sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb
|
||||
# via pre-commit
|
||||
platformdirs==4.5.0 \
|
||||
--hash=sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312 \
|
||||
--hash=sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3
|
||||
platformdirs==4.5.1 \
|
||||
--hash=sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda \
|
||||
--hash=sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31
|
||||
# via virtualenv
|
||||
pre-commit==4.5.0 \
|
||||
--hash=sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1 \
|
||||
--hash=sha256:dc5a065e932b19fc1d4c653c6939068fe54325af8e741e74e88db4d28a4dd66b
|
||||
pre-commit==4.5.1 \
|
||||
--hash=sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77 \
|
||||
--hash=sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61
|
||||
# via -r .github/scripts/requirements_pre_commit.in
|
||||
pyyaml==6.0.3 \
|
||||
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
|
||||
@@ -107,7 +107,7 @@ pyyaml==6.0.3 \
|
||||
--hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \
|
||||
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
|
||||
# via pre-commit
|
||||
virtualenv==20.35.4 \
|
||||
--hash=sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c \
|
||||
--hash=sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b
|
||||
virtualenv==20.36.1 \
|
||||
--hash=sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f \
|
||||
--hash=sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba
|
||||
# via pre-commit
|
||||
|
||||
@@ -8,7 +8,7 @@ tomli-w==1.2.0 \
|
||||
--hash=sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 \
|
||||
--hash=sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021
|
||||
# via -r .github/scripts/requirements_sync_readme.in
|
||||
tomlkit==0.13.3 \
|
||||
--hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \
|
||||
--hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0
|
||||
tomlkit==0.14.0 \
|
||||
--hash=sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680 \
|
||||
--hash=sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064
|
||||
# via -r .github/scripts/requirements_sync_readme.in
|
||||
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout main repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
ref: main
|
||||
@@ -172,7 +172,7 @@ jobs:
|
||||
return newComment.id;
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ needs.check-pr.outputs.pr_repository }}
|
||||
ref: ${{ needs.check-pr.outputs.pr_ref }}
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
@@ -362,7 +362,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
@@ -133,7 +133,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
@@ -145,19 +145,19 @@ jobs:
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: refs/pull/${{ needs.check-comment.outputs.pr_number }}/merge
|
||||
token: ${{ steps.setup-bot.outputs.token }}
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -179,7 +179,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
@@ -375,7 +375,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
- name: AI PR Title Analysis
|
||||
if: steps.actor.outputs.is_repo_dev == 'true'
|
||||
id: ai-title-analysis
|
||||
uses: actions/ai-inference@334892bb203895caaed82ec52d23c1ed9385151e # v2.0.4
|
||||
uses: actions/ai-inference@a6101c89c6feaecc585efdd8d461f18bb7896f20 # v2.0.5
|
||||
with:
|
||||
model: openai/gpt-4o
|
||||
system-prompt-file: ".github/config/system-prompt.txt"
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
|
||||
+23
-23
@@ -36,7 +36,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Check for file changes
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
@@ -60,16 +60,16 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up JDK ${{ matrix.jdk-version }}
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: ${{ matrix.jdk-version }}
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
|
||||
- name: Upload Test Reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: test-reports-jdk-${{ matrix.jdk-version }}-spring-security-${{ matrix.spring-security }}
|
||||
path: |
|
||||
@@ -135,16 +135,16 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
DISABLE_ADDITIONAL_FEATURES: true
|
||||
|
||||
- name: Upload OpenAPI Documentation
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: openapi-docs
|
||||
path: ./SwaggerDoc.json
|
||||
@@ -172,9 +172,9 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: "npm"
|
||||
@@ -190,7 +190,7 @@ jobs:
|
||||
- name: Run frontend tests
|
||||
run: cd frontend && npm run test -- --run
|
||||
- name: Upload frontend build artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: frontend-build
|
||||
path: frontend/dist/
|
||||
@@ -207,16 +207,16 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -229,7 +229,7 @@ jobs:
|
||||
|
||||
- name: FAILED - check the licenses for compatibility
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: dependencies-without-allowed-license.json
|
||||
path: build/reports/dependency-license/dependencies-without-allowed-license.json
|
||||
@@ -261,16 +261,16 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -325,7 +325,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Free disk space on runner
|
||||
run: |
|
||||
@@ -335,13 +335,13 @@ jobs:
|
||||
echo "Disk space after cleanup:" && df -h
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -376,7 +376,7 @@ jobs:
|
||||
|
||||
- name: Upload Reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: reports-docker-${{ matrix.artifact-suffix }}
|
||||
path: |
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout main branch first
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: "Dependency Review"
|
||||
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
|
||||
with:
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Check for file changes
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR head (default)
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Checkout BASE branch (safe script)
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
path: base
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: "npm"
|
||||
@@ -273,7 +273,7 @@ jobs:
|
||||
- name: Create Pull Request (Push only)
|
||||
id: cpr
|
||||
if: github.event_name == 'push' && env.CHANGES_DETECTED == 'true'
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ steps.setup-bot.outputs.token }}
|
||||
commit-message: "Update Frontend 3rd Party Licenses"
|
||||
@@ -317,7 +317,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
@@ -331,13 +331,13 @@ jobs:
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -364,7 +364,7 @@ jobs:
|
||||
|
||||
- name: Upload artifact on license issues
|
||||
if: env.LICENSE_WARNINGS_EXIST == 'true'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: backend-dependencies-without-allowed-license.json
|
||||
path: build/reports/dependency-license/dependencies-without-allowed-license.json
|
||||
@@ -490,7 +490,7 @@ jobs:
|
||||
- name: Create Pull Request (push only)
|
||||
if: github.event_name == 'push' && env.CHANGES_DETECTED == 'true'
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ steps.setup-bot.outputs.token }}
|
||||
commit-message: "Update Backend 3rd Party Licenses"
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Run Labeler
|
||||
uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916 # v5.3.0
|
||||
|
||||
@@ -42,16 +42,16 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -115,22 +115,22 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
- name: Setup Node.js
|
||||
if: matrix.variant.build_frontend == true
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
cp app/core/build/libs/stirling-pdf-${{ needs.determine-matrix.outputs.version }}.jar ./jar-dist/Stirling-PDF${{ matrix.variant.file_suffix }}.jar
|
||||
|
||||
- name: Upload JAR artifacts
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: jar${{ matrix.variant.file_suffix }}
|
||||
path: ./jar-dist/*.jar
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-22.04'
|
||||
@@ -185,26 +185,26 @@ jobs:
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libjavascriptcoregtk-4.0-dev libsoup2.4-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: ${{ (matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -291,7 +291,7 @@ jobs:
|
||||
- name: Setup DigiCert KeyLocker
|
||||
id: digicert-setup
|
||||
if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && (github.event_name == 'release' || github.ref == 'refs/heads/V2-master') }}
|
||||
uses: digicert/ssm-code-signing@v1.1.0
|
||||
uses: digicert/ssm-code-signing@af7f4b07694120e514fe5b19f0598af3c20256e3 # v1.2.0
|
||||
env:
|
||||
SM_API_KEY: ${{ secrets.SM_API_KEY }}
|
||||
SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }}
|
||||
@@ -399,7 +399,7 @@ jobs:
|
||||
echo "Certificate imported successfully."
|
||||
|
||||
- name: Build Tauri app
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
uses: tauri-apps/tauri-action@51a9f1156b33df106d827c3a78f8f894946c5faa # v0.5.25
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
@@ -533,7 +533,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Stirling-PDF-${{ matrix.name }}
|
||||
path: ./dist/*
|
||||
|
||||
@@ -26,9 +26,10 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
@@ -48,17 +49,24 @@ jobs:
|
||||
run: |
|
||||
pip install --require-hashes --only-binary=:all: -r ./.github/scripts/requirements_pre_commit.txt
|
||||
|
||||
- run: pre-commit run --all-files -c .pre-commit-config.yaml
|
||||
- name: Run Pre-Commit
|
||||
run: |
|
||||
pre-commit run ruff --all-files -c .pre-commit-config.yaml
|
||||
pre-commit run ruff-format --all-files -c .pre-commit-config.yaml
|
||||
pre-commit run codespell --all-files -c .pre-commit-config.yaml
|
||||
pre-commit run gitleaks --all-files -c .pre-commit-config.yaml
|
||||
pre-commit run end-of-file-fixer --all-files -c .pre-commit-config.yaml
|
||||
pre-commit run trailing-whitespace --all-files -c .pre-commit-config.yaml
|
||||
continue-on-error: true
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: 21
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -76,7 +84,7 @@ jobs:
|
||||
|
||||
- name: Create Pull Request
|
||||
if: env.CHANGES_DETECTED == 'true'
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ steps.setup-bot.outputs.token }}
|
||||
commit-message: ":file_folder: pre-commit"
|
||||
|
||||
@@ -37,16 +37,16 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -75,13 +75,13 @@ jobs:
|
||||
cosign-release: "v2.4.1"
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
|
||||
@@ -31,16 +31,16 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
@@ -55,12 +57,16 @@ jobs:
|
||||
cache: "pip" # caching pip dependencies
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt
|
||||
run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt -r ./.github/scripts/requirements_pre_commit.txt
|
||||
|
||||
- name: Sync translation TOML files
|
||||
run: |
|
||||
python .github/scripts/check_language_toml.py --reference-file "frontend/public/locales/en-GB/translation.toml" --branch main
|
||||
|
||||
- name: pre-commit run
|
||||
run: |
|
||||
pre-commit run toml-sort-fix --all-files
|
||||
|
||||
- name: Commit translation files
|
||||
run: |
|
||||
git add frontend/public/locales/*/translation.toml
|
||||
@@ -77,7 +83,7 @@ jobs:
|
||||
|
||||
- name: Create Pull Request
|
||||
if: always()
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ steps.setup-bot.outputs.token }}
|
||||
commit-message: Update files
|
||||
|
||||
@@ -25,6 +25,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
determine-matrix:
|
||||
@@ -33,6 +34,11 @@ jobs:
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Determine build matrix
|
||||
id: set-matrix
|
||||
run: |
|
||||
@@ -72,7 +78,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-22.04'
|
||||
@@ -81,26 +87,26 @@ jobs:
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libjavascriptcoregtk-4.0-dev libsoup2.4-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: ${{ (matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -188,7 +194,7 @@ jobs:
|
||||
- name: Setup DigiCert KeyLocker
|
||||
id: digicert-setup
|
||||
if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }}
|
||||
uses: digicert/ssm-code-signing@v1.1.0
|
||||
uses: digicert/ssm-code-signing@af7f4b07694120e514fe5b19f0598af3c20256e3 # v1.2.0
|
||||
env:
|
||||
SM_API_KEY: ${{ secrets.SM_API_KEY }}
|
||||
SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }}
|
||||
@@ -307,7 +313,7 @@ jobs:
|
||||
ls -la /usr/bin/hd* || echo "No hd* tools found"
|
||||
|
||||
- name: Build Tauri app
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
uses: tauri-apps/tauri-action@51a9f1156b33df106d827c3a78f8f894946c5faa # v0.5.25
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
@@ -581,7 +587,7 @@ jobs:
|
||||
}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Stirling-PDF-${{ matrix.name }}
|
||||
path: ./dist/*
|
||||
@@ -634,11 +640,110 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
pr-comment:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request' && needs.build.result == 'success'
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Post/Update PR Comment with Download Links
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const prNumber = context.issue.number;
|
||||
const runId = context.runId;
|
||||
|
||||
// Fetch artifacts for this workflow run
|
||||
const { data: artifactsList } = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner,
|
||||
repo,
|
||||
run_id: runId
|
||||
});
|
||||
|
||||
// Map of expected artifact names to display info
|
||||
const artifactMap = {
|
||||
'Stirling-PDF-windows-x86_64': { icon: '🪟', platform: 'Windows x64', files: '.exe, .msi' },
|
||||
'Stirling-PDF-macos-aarch64': { icon: '🍎', platform: 'macOS ARM64', files: '.dmg' },
|
||||
'Stirling-PDF-macos-x86_64': { icon: '🍎', platform: 'macOS Intel', files: '.dmg' },
|
||||
'Stirling-PDF-linux-x86_64': { icon: '🐧', platform: 'Linux x64', files: '.deb, .AppImage' }
|
||||
};
|
||||
|
||||
let commentBody = `## 📦 Tauri Desktop Builds Ready!\n\n`;
|
||||
commentBody += `The desktop applications have been built and are ready for testing.\n\n`;
|
||||
commentBody += `### Download Artifacts:\n\n`;
|
||||
|
||||
// Add links for each found artifact
|
||||
let foundArtifacts = 0;
|
||||
for (const artifact of artifactsList.artifacts) {
|
||||
const info = artifactMap[artifact.name];
|
||||
if (info) {
|
||||
foundArtifacts++;
|
||||
// GitHub doesn't provide direct download URLs via API, but we can link to the artifact on the Actions page
|
||||
const artifactUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${artifact.id}`;
|
||||
commentBody += `${info.icon} **${info.platform}**: [Download ${artifact.name}](${artifactUrl}) `;
|
||||
commentBody += `(${info.files}) - ${(artifact.size_in_bytes / 1024 / 1024).toFixed(1)} MB\n`;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundArtifacts === 0) {
|
||||
commentBody += `⚠️ **Warning**: No artifacts found in workflow run.\n`;
|
||||
commentBody += `[View workflow run](https://github.com/${owner}/${repo}/actions/runs/${runId})\n`;
|
||||
}
|
||||
|
||||
commentBody += `\n---\n`;
|
||||
commentBody += `_Built from commit ${context.sha.substring(0, 7)}_\n`;
|
||||
commentBody += `_Artifacts expire in 7 days_`;
|
||||
|
||||
// Find existing comment
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const botComment = comments.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body.includes('📦 Tauri Desktop Builds Ready!')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
// Update existing comment
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: botComment.id,
|
||||
body: commentBody
|
||||
});
|
||||
console.log('Updated existing comment');
|
||||
} else {
|
||||
// Create new comment
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: commentBody
|
||||
});
|
||||
console.log('Created new comment');
|
||||
}
|
||||
|
||||
report:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Report build results
|
||||
run: |
|
||||
if [ "${{ needs.build.result }}" = "success" ]; then
|
||||
|
||||
@@ -30,16 +30,16 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
@@ -128,7 +128,12 @@ jobs:
|
||||
outputs:
|
||||
frontend: ${{ steps.changes.outputs.frontend }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Check for file changes
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
@@ -147,10 +152,10 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
+1
-1
@@ -149,6 +149,7 @@ app/proprietary/build
|
||||
common/build
|
||||
proprietary/build
|
||||
stirling-pdf/build
|
||||
frontend/src-tauri/provisioner/target
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
@@ -243,4 +244,3 @@ docs/type3/signatures/
|
||||
|
||||
# Type3 sample PDFs (development only)
|
||||
**/type3/samples/
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.8
|
||||
rev: v0.14.14
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
@@ -16,11 +16,11 @@ repos:
|
||||
hooks:
|
||||
- id: codespell
|
||||
args:
|
||||
- --ignore-words-list=thirdParty,tabEl,tabEls
|
||||
- --ignore-words-list=thirdParty,tabEl,tabEls,Sie,ist
|
||||
- --skip="./.*,*.csv,*.json,*.ambr"
|
||||
- --quiet-level=2
|
||||
files: \.(html|css|js|py|md)$
|
||||
exclude: (.vscode|.devcontainer|app/core/src/main/resources|app/proprietary/src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
|
||||
exclude: (.vscode|.devcontainer|app/core/src/main/resources|app/proprietary/src/main/resources|frontend/public/vendor|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.30.0
|
||||
hooks:
|
||||
@@ -34,6 +34,12 @@ repos:
|
||||
- id: trailing-whitespace
|
||||
files: ^.*(\.js|\.java|\.py|\.yml)$
|
||||
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js|\.github/workflows/.*$)
|
||||
- repo: https://github.com/pappasam/toml-sort
|
||||
rev: v0.24.3
|
||||
hooks:
|
||||
- id: toml-sort-fix
|
||||
files: frontend/public/locales/.*\.toml$
|
||||
args: ['--in-place', '--all', '--ignore-case']
|
||||
# - repo: https://github.com/thibaudcolas/pre-commit-stylelint
|
||||
# rev: v16.21.1
|
||||
# hooks:
|
||||
|
||||
Vendored
+1
-1
@@ -13,7 +13,7 @@
|
||||
"vscjava.vscode-spring-boot-dashboard", // Spring Boot dashboard for managing and visualizing Spring Boot applications
|
||||
"EditorConfig.EditorConfig", // EditorConfig support for maintaining consistent coding styles
|
||||
"ms-azuretools.vscode-docker", // Docker extension for Visual Studio Code
|
||||
"GitHub.copilot", // GitHub Copilot AI pair programmer for Visual Studio Code
|
||||
"GitHub.copilot-chat", // GitHub Copilot AI pair programmer for Visual Studio Code
|
||||
"GitHub.vscode-pull-request-github", // GitHub Pull Requests extension for Visual Studio Code
|
||||
"charliermarsh.ruff", // Ruff code formatter for Python to follow the Ruff Style Guide
|
||||
"yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing
|
||||
|
||||
@@ -303,22 +303,23 @@ public class EndpointConfiguration {
|
||||
// Adding endpoints to "PageOps" group
|
||||
addEndpointToGroup("PageOps", "remove-pages");
|
||||
addEndpointToGroup("PageOps", "merge-pdfs");
|
||||
addEndpointToGroup("PageOps", "split-pdfs");
|
||||
addEndpointToGroup("PageOps", "pdf-organizer");
|
||||
addEndpointToGroup("PageOps", "split-pages");
|
||||
addEndpointToGroup("PageOps", "rearrange-pages");
|
||||
addEndpointToGroup("PageOps", "rotate-pdf");
|
||||
addEndpointToGroup("PageOps", "multi-page-layout");
|
||||
addEndpointToGroup("PageOps", "booklet-imposition");
|
||||
addEndpointToGroup("PageOps", "scale-pages");
|
||||
addEndpointToGroup("PageOps", "crop");
|
||||
addEndpointToGroup("PageOps", "extract-page");
|
||||
addEndpointToGroup("PageOps", "pdf-to-single-page");
|
||||
addEndpointToGroup("PageOps", "auto-split-pdf");
|
||||
addEndpointToGroup("PageOps", "split-by-size-or-count");
|
||||
addEndpointToGroup("PageOps", "overlay-pdf");
|
||||
addEndpointToGroup("PageOps", "split-pdf-by-sections");
|
||||
addEndpointToGroup("PageOps", "split-pdf-by-chapters");
|
||||
addEndpointToGroup("PageOps", "add-page-numbers");
|
||||
addEndpointToGroup("PageOps", "extract-pages");
|
||||
|
||||
// Adding endpoints to "Convert" group
|
||||
// Adding endpoints to "Convert" group (Frontend has 15 convert endpoints)
|
||||
addEndpointToGroup("Convert", "pdf-to-img");
|
||||
addEndpointToGroup("Convert", "img-to-pdf");
|
||||
addEndpointToGroup("Convert", "pdf-to-pdfa");
|
||||
@@ -334,6 +335,8 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Convert", "pdf-to-csv");
|
||||
addEndpointToGroup("Convert", "pdf-to-markdown");
|
||||
addEndpointToGroup("Convert", "eml-to-pdf");
|
||||
addEndpointToGroup("Convert", "pdf-to-epub");
|
||||
// Backend-only endpoints (not in frontend tool registry)
|
||||
addEndpointToGroup("Convert", "pdf-to-vector");
|
||||
addEndpointToGroup("Convert", "vector-to-pdf");
|
||||
addEndpointToGroup("Convert", "pdf-to-video");
|
||||
@@ -341,6 +344,7 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Convert", "pdf-to-cbz");
|
||||
addEndpointToGroup("Convert", "pdf-to-json");
|
||||
addEndpointToGroup("Convert", "json-to-pdf");
|
||||
addEndpointToGroup("Convert", "pdf-to-rtf");
|
||||
|
||||
// Adding endpoints to "Security" group
|
||||
addEndpointToGroup("Security", "add-password");
|
||||
@@ -351,51 +355,49 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Security", "remove-cert-sign");
|
||||
addEndpointToGroup("Security", "sanitize-pdf");
|
||||
addEndpointToGroup("Security", "auto-redact");
|
||||
addEndpointToGroup("Security", "redact");
|
||||
addEndpointToGroup("Security", "validate-signature");
|
||||
addEndpointToGroup("Security", "add-stamp");
|
||||
addEndpointToGroup("Security", "unlock-pdf-forms");
|
||||
// Backend-only endpoints (not in frontend tool registry endpoints)
|
||||
addEndpointToGroup("Security", "redact");
|
||||
addEndpointToGroup("Security", "verify-pdf");
|
||||
addEndpointToGroup("Security", "stamp");
|
||||
addEndpointToGroup("Security", "sign");
|
||||
|
||||
// Adding endpoints to "Other" group
|
||||
addEndpointToGroup("Other", "ocr-pdf");
|
||||
addEndpointToGroup("Other", "add-image");
|
||||
addEndpointToGroup("Other", "extract-images");
|
||||
addEndpointToGroup("Other", "change-metadata");
|
||||
addEndpointToGroup("Other", "update-metadata");
|
||||
addEndpointToGroup("Other", "flatten");
|
||||
addEndpointToGroup("Other", "unlock-pdf-forms");
|
||||
addEndpointToGroup("Other", REMOVE_BLANKS);
|
||||
addEndpointToGroup("Other", "remove-annotations");
|
||||
addEndpointToGroup("Other", "compare");
|
||||
addEndpointToGroup("Other", "add-page-numbers");
|
||||
addEndpointToGroup("Other", "get-info-on-pdf");
|
||||
addEndpointToGroup("Other", "remove-image-pdf");
|
||||
addEndpointToGroup("Other", "add-attachments");
|
||||
addEndpointToGroup("Other", "replace-invert-pdf");
|
||||
addEndpointToGroup("Other", "edit-table-of-contents");
|
||||
addEndpointToGroup("Other", "text-editor-pdf");
|
||||
// Backend-only endpoints (not in frontend tool registry endpoints)
|
||||
addEndpointToGroup("Other", "add-image");
|
||||
addEndpointToGroup("Other", "compare");
|
||||
addEndpointToGroup("Other", "view-pdf");
|
||||
addEndpointToGroup("Other", "replace-and-invert-color-pdf");
|
||||
addEndpointToGroup("Other", "multi-tool");
|
||||
|
||||
// Adding form-related endpoints to "Other" group
|
||||
addEndpointToGroup("Other", "fields");
|
||||
addEndpointToGroup("Other", "modify-fields");
|
||||
addEndpointToGroup("Other", "delete-fields");
|
||||
addEndpointToGroup("Other", "fill");
|
||||
|
||||
// Adding endpoints to "Advance" group
|
||||
addEndpointToGroup("Advance", "adjust-contrast");
|
||||
addEndpointToGroup("Advance", "compress-pdf");
|
||||
addEndpointToGroup("Advance", "extract-image-scans");
|
||||
addEndpointToGroup("Advance", "repair");
|
||||
addEndpointToGroup("Advance", "auto-rename");
|
||||
addEndpointToGroup("Advance", "pipeline");
|
||||
addEndpointToGroup("Advance", "handleData");
|
||||
addEndpointToGroup("Advance", "scanner-effect");
|
||||
addEndpointToGroup("Advance", "auto-split-pdf");
|
||||
addEndpointToGroup("Advance", "show-javascript");
|
||||
addEndpointToGroup("Advance", "split-by-size-or-count");
|
||||
addEndpointToGroup("Advance", "overlay-pdf");
|
||||
addEndpointToGroup("Advance", "split-pdf-by-sections");
|
||||
addEndpointToGroup("Advance", "edit-table-of-contents");
|
||||
addEndpointToGroup("Advance", "split-pdf-by-chapters");
|
||||
// Backend-only endpoints
|
||||
addEndpointToGroup("Advance", "adjust-contrast");
|
||||
addEndpointToGroup("Advance", "pipeline");
|
||||
|
||||
// CLI
|
||||
addEndpointToGroup("CLI", "compress-pdf");
|
||||
@@ -436,8 +438,8 @@ public class EndpointConfiguration {
|
||||
// Java
|
||||
addEndpointToGroup("Java", "merge-pdfs");
|
||||
addEndpointToGroup("Java", "remove-pages");
|
||||
addEndpointToGroup("Java", "split-pdfs");
|
||||
addEndpointToGroup("Java", "pdf-organizer");
|
||||
addEndpointToGroup("Java", "split-pages");
|
||||
addEndpointToGroup("Java", "rearrange-pages");
|
||||
addEndpointToGroup("Java", "rotate-pdf");
|
||||
addEndpointToGroup("Java", "pdf-to-img");
|
||||
addEndpointToGroup("Java", "img-to-pdf");
|
||||
@@ -445,9 +447,10 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Java", "remove-password");
|
||||
addEndpointToGroup("Java", "change-permissions");
|
||||
addEndpointToGroup("Java", "add-watermark");
|
||||
addEndpointToGroup("Java", "add-stamp");
|
||||
addEndpointToGroup("Java", "add-image");
|
||||
addEndpointToGroup("Java", "extract-images");
|
||||
addEndpointToGroup("Java", "change-metadata");
|
||||
addEndpointToGroup("Java", "update-metadata");
|
||||
addEndpointToGroup("Java", "cert-sign");
|
||||
addEndpointToGroup("Java", "remove-cert-sign");
|
||||
addEndpointToGroup("Java", "multi-page-layout");
|
||||
@@ -459,7 +462,6 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Java", "sanitize-pdf");
|
||||
addEndpointToGroup("Java", "crop");
|
||||
addEndpointToGroup("Java", "get-info-on-pdf");
|
||||
addEndpointToGroup("Java", "extract-page");
|
||||
addEndpointToGroup("Java", "pdf-to-single-page");
|
||||
addEndpointToGroup("Java", "markdown-to-pdf");
|
||||
addEndpointToGroup("Java", "show-javascript");
|
||||
@@ -469,7 +471,9 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Java", "split-by-size-or-count");
|
||||
addEndpointToGroup("Java", "overlay-pdf");
|
||||
addEndpointToGroup("Java", "split-pdf-by-sections");
|
||||
addEndpointToGroup("Java", "split-pdf-by-chapters");
|
||||
addEndpointToGroup("Java", REMOVE_BLANKS);
|
||||
addEndpointToGroup("Java", "remove-annotations");
|
||||
addEndpointToGroup("Java", "pdf-to-text");
|
||||
addEndpointToGroup("Java", "remove-image-pdf");
|
||||
addEndpointToGroup("Java", "pdf-to-markdown");
|
||||
@@ -479,15 +483,24 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Java", "pdf-to-cbz");
|
||||
addEndpointToGroup("Java", "pdf-to-json");
|
||||
addEndpointToGroup("Java", "json-to-pdf");
|
||||
addEndpointToGroup("rar", "pdf-to-cbr");
|
||||
addEndpointToGroup("Java", "pdf-to-video");
|
||||
addEndpointToGroup("Java", "verify-pdf");
|
||||
addEndpointToGroup("Java", "flatten");
|
||||
addEndpointToGroup("Java", "unlock-pdf-forms");
|
||||
addEndpointToGroup("Java", "validate-signature");
|
||||
addEndpointToGroup("Java", "text-editor-pdf");
|
||||
addEndpointToGroup("Java", "edit-table-of-contents");
|
||||
addEndpointToGroup("Java", "pdf-to-epub");
|
||||
addEndpointToGroup("Java", "eml-to-pdf");
|
||||
addEndpointToGroup("Java", "handleData");
|
||||
addEndpointToGroup("rar", "pdf-to-cbr");
|
||||
|
||||
// Javascript
|
||||
addEndpointToGroup("Javascript", "pdf-organizer");
|
||||
addEndpointToGroup("Javascript", "rearrange-pages");
|
||||
addEndpointToGroup("Javascript", "sign");
|
||||
addEndpointToGroup("Javascript", "compare");
|
||||
addEndpointToGroup("Javascript", "adjust-contrast");
|
||||
addEndpointToGroup("Javascript", "text-editor-pdf");
|
||||
|
||||
/* qpdf */
|
||||
addEndpointToGroup("qpdf", "repair");
|
||||
@@ -498,6 +511,7 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Ghostscript", "compress-pdf");
|
||||
addEndpointToGroup("Ghostscript", "crop");
|
||||
addEndpointToGroup("Ghostscript", "replace-invert-pdf");
|
||||
addEndpointToGroup("Ghostscript", "scanner-effect");
|
||||
addEndpointToGroup("Ghostscript", "pdf-to-vector");
|
||||
addEndpointToGroup("Ghostscript", "vector-to-pdf");
|
||||
|
||||
@@ -516,6 +530,8 @@ public class EndpointConfiguration {
|
||||
addEndpointAlternative("compress-pdf", "qpdf");
|
||||
addEndpointAlternative("compress-pdf", "Ghostscript");
|
||||
addEndpointAlternative("compress-pdf", "Java");
|
||||
addEndpointAlternative("crop", "Ghostscript");
|
||||
addEndpointAlternative("crop", "Java");
|
||||
addEndpointAlternative("ocr-pdf", "tesseract");
|
||||
addEndpointAlternative("ocr-pdf", "OCRmyPDF");
|
||||
|
||||
@@ -544,6 +560,9 @@ public class EndpointConfiguration {
|
||||
// Pdftohtml dependent endpoints
|
||||
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
||||
addEndpointToGroup("Pdftohtml", "pdf-to-markdown");
|
||||
|
||||
// Calibre dependent endpoints
|
||||
addEndpointToGroup("Calibre", "pdf-to-epub");
|
||||
}
|
||||
|
||||
private void processEnvironmentConfigs() {
|
||||
|
||||
+155
-6
@@ -1,10 +1,14 @@
|
||||
package stirling.software.common.configuration;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -41,6 +45,7 @@ public class RuntimePathConfig {
|
||||
|
||||
// Pipeline paths
|
||||
private final String pipelineWatchedFoldersPath;
|
||||
private final List<String> pipelineWatchedFoldersPaths;
|
||||
private final String pipelineFinishedFoldersPath;
|
||||
private final String pipelineDefaultWebUiConfigs;
|
||||
private final String pipelinePath;
|
||||
@@ -49,20 +54,27 @@ public class RuntimePathConfig {
|
||||
this.properties = properties;
|
||||
this.basePath = InstallationPathConfig.getPath();
|
||||
|
||||
this.pipelinePath = Path.of(basePath, "pipeline").toString();
|
||||
String defaultWatchedFolders = Path.of(this.pipelinePath, "watchedFolders").toString();
|
||||
String defaultFinishedFolders = Path.of(this.pipelinePath, "finishedFolders").toString();
|
||||
String defaultWebUIConfigs = Path.of(this.pipelinePath, "defaultWebUIConfigs").toString();
|
||||
|
||||
System system = properties.getSystem();
|
||||
CustomPaths customPaths = system.getCustomPaths();
|
||||
|
||||
Pipeline pipeline = customPaths.getPipeline();
|
||||
|
||||
this.pipelineWatchedFoldersPath =
|
||||
this.pipelinePath =
|
||||
resolvePath(
|
||||
Path.of(basePath, "pipeline").toString(),
|
||||
pipeline != null ? pipeline.getPipelineDir() : null);
|
||||
String defaultWatchedFolders = Path.of(this.pipelinePath, "watchedFolders").toString();
|
||||
String defaultFinishedFolders = Path.of(this.pipelinePath, "finishedFolders").toString();
|
||||
String defaultWebUIConfigs = Path.of(this.pipelinePath, "defaultWebUIConfigs").toString();
|
||||
|
||||
List<String> watchedFoldersDirs =
|
||||
sanitizePathList(pipeline != null ? pipeline.getWatchedFoldersDirs() : null);
|
||||
this.pipelineWatchedFoldersPaths =
|
||||
resolveWatchedFolderPaths(
|
||||
defaultWatchedFolders,
|
||||
watchedFoldersDirs,
|
||||
pipeline != null ? pipeline.getWatchedFoldersDir() : null);
|
||||
this.pipelineWatchedFoldersPath = this.pipelineWatchedFoldersPaths.get(0);
|
||||
this.pipelineFinishedFoldersPath =
|
||||
resolvePath(
|
||||
defaultFinishedFolders,
|
||||
@@ -72,6 +84,9 @@ public class RuntimePathConfig {
|
||||
defaultWebUIConfigs,
|
||||
pipeline != null ? pipeline.getWebUIConfigsDir() : null);
|
||||
|
||||
// Validate path conflicts after all paths are resolved
|
||||
validatePipelinePaths();
|
||||
|
||||
boolean isDocker = isRunningInDocker();
|
||||
|
||||
// Initialize Operation paths
|
||||
@@ -129,6 +144,140 @@ public class RuntimePathConfig {
|
||||
return StringUtils.isNotBlank(customPath) ? customPath : defaultPath;
|
||||
}
|
||||
|
||||
private List<String> resolveWatchedFolderPaths(
|
||||
String defaultPath, List<String> watchedFoldersDirs, String legacyWatchedFolder) {
|
||||
List<String> rawPaths = new ArrayList<>();
|
||||
|
||||
// Collect paths from new config
|
||||
if (watchedFoldersDirs != null && !watchedFoldersDirs.isEmpty()) {
|
||||
rawPaths.addAll(watchedFoldersDirs);
|
||||
}
|
||||
// Fall back to legacy config
|
||||
else if (StringUtils.isNotBlank(legacyWatchedFolder)) {
|
||||
rawPaths.add(legacyWatchedFolder);
|
||||
}
|
||||
// Fall back to default
|
||||
else {
|
||||
rawPaths.add(defaultPath);
|
||||
}
|
||||
|
||||
// Validate, normalize, and deduplicate paths
|
||||
List<String> validatedPaths = validateAndNormalizePaths(rawPaths);
|
||||
|
||||
// Ensure we have at least one valid path (critical for system to function)
|
||||
if (validatedPaths.isEmpty()) {
|
||||
log.warn(
|
||||
"No valid watched folder paths configured, falling back to default: {}",
|
||||
defaultPath);
|
||||
validatedPaths.add(defaultPath);
|
||||
}
|
||||
|
||||
// Detect overlapping paths (warning only, not blocking)
|
||||
detectOverlappingPaths(validatedPaths);
|
||||
|
||||
return validatedPaths;
|
||||
}
|
||||
|
||||
private List<String> sanitizePathList(List<String> paths) {
|
||||
if (paths == null || paths.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> sanitized = new ArrayList<>();
|
||||
for (String path : paths) {
|
||||
if (StringUtils.isNotBlank(path)) {
|
||||
sanitized.add(path.trim());
|
||||
}
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
private List<String> validateAndNormalizePaths(List<String> paths) {
|
||||
Set<String> normalizedPaths = new LinkedHashSet<>(); // Preserves order, prevents duplicates
|
||||
|
||||
for (String pathStr : paths) {
|
||||
if (StringUtils.isBlank(pathStr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Normalize to absolute path
|
||||
Path path = Paths.get(pathStr.trim()).toAbsolutePath().normalize();
|
||||
String normalizedPath = path.toString();
|
||||
|
||||
// Check for duplicates
|
||||
if (normalizedPaths.contains(normalizedPath)) {
|
||||
log.debug("Skipping duplicate watched folder path: {}", pathStr);
|
||||
continue;
|
||||
}
|
||||
|
||||
normalizedPaths.add(normalizedPath);
|
||||
log.info("Registered watched folder path: {}", normalizedPath);
|
||||
|
||||
} catch (InvalidPathException e) {
|
||||
log.error(
|
||||
"Invalid watched folder path '{}' - skipping: {}", pathStr, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(normalizedPaths);
|
||||
}
|
||||
|
||||
private void detectOverlappingPaths(List<String> paths) {
|
||||
for (int i = 0; i < paths.size(); i++) {
|
||||
Path path1 = Paths.get(paths.get(i));
|
||||
for (int j = i + 1; j < paths.size(); j++) {
|
||||
Path path2 = Paths.get(paths.get(j));
|
||||
|
||||
// Check if one path is a parent of the other
|
||||
if (path1.startsWith(path2)) {
|
||||
log.warn(
|
||||
"Watched folder path '{}' is nested inside '{}' - this may cause duplicate processing",
|
||||
path1,
|
||||
path2);
|
||||
} else if (path2.startsWith(path1)) {
|
||||
log.warn(
|
||||
"Watched folder path '{}' is nested inside '{}' - this may cause duplicate processing",
|
||||
path2,
|
||||
path1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePipelinePaths() {
|
||||
try {
|
||||
Path finishedPath = Paths.get(pipelineFinishedFoldersPath).toAbsolutePath().normalize();
|
||||
|
||||
for (String watchedPathStr : pipelineWatchedFoldersPaths) {
|
||||
Path watchedPath = Paths.get(watchedPathStr).toAbsolutePath().normalize();
|
||||
|
||||
// Check if watched folder is same as finished folder
|
||||
if (watchedPath.equals(finishedPath)) {
|
||||
log.error(
|
||||
"CRITICAL: Watched folder '{}' is the same as finished folder '{}' - this will cause processing loops!",
|
||||
watchedPath,
|
||||
finishedPath);
|
||||
}
|
||||
// Check if watched folder contains finished folder
|
||||
else if (finishedPath.startsWith(watchedPath)) {
|
||||
log.warn(
|
||||
"Finished folder '{}' is nested inside watched folder '{}' - this may cause issues",
|
||||
finishedPath,
|
||||
watchedPath);
|
||||
}
|
||||
// Check if finished folder contains watched folder
|
||||
else if (watchedPath.startsWith(finishedPath)) {
|
||||
log.error(
|
||||
"CRITICAL: Watched folder '{}' is nested inside finished folder '{}' - this will cause processing loops!",
|
||||
watchedPath,
|
||||
finishedPath);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error validating pipeline paths: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRunningInDocker() {
|
||||
return Files.exists(Path.of("/.dockerenv"));
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ import org.springframework.stereotype.Component;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -98,6 +100,52 @@ public class ApplicationProperties {
|
||||
return propertySource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize fileUploadLimit from environment variables if not set in settings.yml. Supports
|
||||
* SYSTEMFILEUPLOADLIMIT (format: "100MB") and SYSTEM_MAXFILESIZE (format: "100" in MB).
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initializeFileUploadLimitFromEnv() {
|
||||
// Only override if fileUploadLimit is not already set in settings.yml
|
||||
if (system.getFileUploadLimit() == null || system.getFileUploadLimit().isEmpty()) {
|
||||
String fileUploadLimit = null;
|
||||
|
||||
// Check SYSTEMFILEUPLOADLIMIT first (format: "100MB", "1GB", etc.)
|
||||
String systemFileUploadLimit = java.lang.System.getenv("SYSTEMFILEUPLOADLIMIT");
|
||||
if (systemFileUploadLimit != null && !systemFileUploadLimit.trim().isEmpty()) {
|
||||
fileUploadLimit = systemFileUploadLimit.trim();
|
||||
log.info("Setting fileUploadLimit from SYSTEMFILEUPLOADLIMIT: {}", fileUploadLimit);
|
||||
} else {
|
||||
// Check SYSTEM_MAXFILESIZE (format: number in MB, e.g., "100")
|
||||
String systemMaxFileSize = java.lang.System.getenv("SYSTEM_MAXFILESIZE");
|
||||
if (systemMaxFileSize != null && !systemMaxFileSize.trim().isEmpty()) {
|
||||
try {
|
||||
// Validate it's a number
|
||||
long sizeInMB = Long.parseLong(systemMaxFileSize.trim());
|
||||
if (sizeInMB > 0 && sizeInMB <= 999) {
|
||||
fileUploadLimit = sizeInMB + "MB";
|
||||
log.info(
|
||||
"Setting fileUploadLimit from SYSTEM_MAXFILESIZE: {}MB",
|
||||
sizeInMB);
|
||||
} else {
|
||||
log.warn(
|
||||
"SYSTEM_MAXFILESIZE value {} is out of valid range (1-999), ignoring",
|
||||
sizeInMB);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn(
|
||||
"SYSTEM_MAXFILESIZE value '{}' is not a valid number, ignoring",
|
||||
systemMaxFileSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fileUploadLimit != null) {
|
||||
system.setFileUploadLimit(fileUploadLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class AutoPipeline {
|
||||
private String outputFolder;
|
||||
@@ -164,6 +212,7 @@ public class ApplicationProperties {
|
||||
private String customGlobalAPIKey;
|
||||
private Jwt jwt = new Jwt();
|
||||
private Validation validation = new Validation();
|
||||
private String xFrameOptions = "DENY";
|
||||
|
||||
public Boolean isAltLogin() {
|
||||
return saml2.getEnabled() || oauth2.getEnabled();
|
||||
@@ -458,7 +507,9 @@ public class ApplicationProperties {
|
||||
|
||||
@Data
|
||||
public static class Pipeline {
|
||||
private String pipelineDir;
|
||||
private String watchedFoldersDir;
|
||||
private List<String> watchedFoldersDirs = new ArrayList<>();
|
||||
private String finishedFoldersDir;
|
||||
private String webUIConfigsDir;
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package stirling.software.common.service;
|
||||
|
||||
/**
|
||||
* Interface for checking license status dynamically. Implementation provided by proprietary module
|
||||
* when available.
|
||||
*/
|
||||
public interface LicenseServiceInterface {
|
||||
|
||||
/**
|
||||
* Get the license type as a string.
|
||||
*
|
||||
* @return "NORMAL", "SERVER", or "ENTERPRISE"
|
||||
*/
|
||||
String getLicenseTypeName();
|
||||
|
||||
/**
|
||||
* Check if running Pro or higher (SERVER or ENTERPRISE license).
|
||||
*
|
||||
* @return true if SERVER or ENTERPRISE license is active
|
||||
*/
|
||||
boolean isRunningProOrHigher();
|
||||
|
||||
/**
|
||||
* Check if running Enterprise edition.
|
||||
*
|
||||
* @return true if ENTERPRISE license is active
|
||||
*/
|
||||
boolean isRunningEE();
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public class FileMonitor {
|
||||
private final ConcurrentHashMap.KeySetView<Path, Boolean> readyForProcessingFiles;
|
||||
private final WatchService watchService;
|
||||
private final Predicate<Path> pathFilter;
|
||||
private final Path rootDir;
|
||||
private final List<Path> rootDirs;
|
||||
private Set<Path> stagingFiles;
|
||||
|
||||
/**
|
||||
@@ -47,8 +47,28 @@ public class FileMonitor {
|
||||
this.pathFilter = pathFilter;
|
||||
this.readyForProcessingFiles = ConcurrentHashMap.newKeySet();
|
||||
this.watchService = FileSystems.getDefault().newWatchService();
|
||||
log.info("Monitoring directory: {}", runtimePathConfig.getPipelineWatchedFoldersPath());
|
||||
this.rootDir = Path.of(runtimePathConfig.getPipelineWatchedFoldersPath());
|
||||
|
||||
List<String> watchedFoldersDirs = runtimePathConfig.getPipelineWatchedFoldersPaths();
|
||||
List<Path> validRootDirs = new ArrayList<>();
|
||||
|
||||
for (String pathStr : watchedFoldersDirs) {
|
||||
try {
|
||||
Path path = Path.of(pathStr);
|
||||
validRootDirs.add(path);
|
||||
log.info("Monitoring directory: {}", path);
|
||||
} catch (Exception e) {
|
||||
log.error(
|
||||
"Failed to initialize monitoring for path '{}': {}",
|
||||
pathStr,
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
this.rootDirs = Collections.unmodifiableList(validRootDirs);
|
||||
|
||||
if (this.rootDirs.isEmpty()) {
|
||||
log.error("No valid directories to monitor - FileMonitor will not function");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldNotProcess(Path path) {
|
||||
@@ -85,13 +105,15 @@ public class FileMonitor {
|
||||
readyForProcessingFiles.clear();
|
||||
|
||||
if (path2KeyMapping.isEmpty()) {
|
||||
log.warn("not monitoring any directory, even the root directory itself: {}", rootDir);
|
||||
if (Files.exists(
|
||||
rootDir)) { // if the root directory exists, re-register the root directory
|
||||
try {
|
||||
recursivelyRegisterEntry(rootDir);
|
||||
} catch (IOException e) {
|
||||
log.error("unable to register monitoring", e);
|
||||
log.warn("Not monitoring any directories; attempting to re-register root paths.");
|
||||
for (Path rootDir : rootDirs) {
|
||||
if (Files.exists(
|
||||
rootDir)) { // if the root directory exists, re-register the root directory
|
||||
try {
|
||||
recursivelyRegisterEntry(rootDir);
|
||||
} catch (IOException e) {
|
||||
log.error("unable to register monitoring for {}", rootDir, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,56 +107,65 @@ public class PDFToFile {
|
||||
File[] outputFiles =
|
||||
Objects.requireNonNull(tempOutputDir.getPath().toFile().listFiles());
|
||||
List<File> markdownFiles = new ArrayList<>();
|
||||
List<File> imageFiles = new ArrayList<>();
|
||||
|
||||
// Convert HTML files to Markdown
|
||||
// Convert HTML files to Markdown and collect image files
|
||||
for (File outputFile : outputFiles) {
|
||||
if (outputFile.getName().endsWith(".html")) {
|
||||
String html = Files.readString(outputFile.toPath());
|
||||
String markdown = htmlToMarkdownConverter.convert(html);
|
||||
|
||||
// Update image references to point to images/ folder
|
||||
markdown = updateImageReferences(markdown);
|
||||
|
||||
String mdFileName = outputFile.getName().replace(".html", ".md");
|
||||
File mdFile = new File(tempOutputDir.getPath().toFile(), mdFileName);
|
||||
Files.writeString(mdFile.toPath(), markdown);
|
||||
markdownFiles.add(mdFile);
|
||||
} else if (!outputFile.getName().endsWith(".md")) {
|
||||
// Collect non-HTML, non-MD files as images/assets
|
||||
imageFiles.add(outputFile);
|
||||
}
|
||||
}
|
||||
|
||||
// If there's only one markdown file, return it directly
|
||||
if (markdownFiles.size() == 1) {
|
||||
fileName = pdfBaseName + ".md";
|
||||
fileBytes = Files.readAllBytes(markdownFiles.get(0).toPath());
|
||||
} else {
|
||||
// Multiple files - create a zip
|
||||
fileName = pdfBaseName + "ToMarkdown.zip";
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
// Always create a ZIP file
|
||||
fileName = pdfBaseName + "ToMarkdown.zip";
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
|
||||
// Add markdown files
|
||||
for (File mdFile : markdownFiles) {
|
||||
ZipEntry mdEntry = new ZipEntry(mdFile.getName());
|
||||
zipOutputStream.putNextEntry(mdEntry);
|
||||
Files.copy(mdFile.toPath(), zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
// Add images and other assets
|
||||
for (File file : outputFiles) {
|
||||
if (!file.getName().endsWith(".html") && !file.getName().endsWith(".md")) {
|
||||
ZipEntry assetEntry = new ZipEntry(file.getName());
|
||||
zipOutputStream.putNextEntry(assetEntry);
|
||||
Files.copy(file.toPath(), zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
|
||||
// Add markdown files to root of ZIP
|
||||
for (File mdFile : markdownFiles) {
|
||||
ZipEntry mdEntry = new ZipEntry(mdFile.getName());
|
||||
zipOutputStream.putNextEntry(mdEntry);
|
||||
Files.copy(mdFile.toPath(), zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
fileBytes = byteArrayOutputStream.toByteArray();
|
||||
// Add images and other assets to images/ folder
|
||||
for (File imageFile : imageFiles) {
|
||||
ZipEntry assetEntry = new ZipEntry("images/" + imageFile.getName());
|
||||
zipOutputStream.putNextEntry(assetEntry);
|
||||
Files.copy(imageFile.toPath(), zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
fileBytes = byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates image references in markdown to point to the images/ folder. Matches patterns like
|
||||
*  and converts to 
|
||||
*/
|
||||
private String updateImageReferences(String markdown) {
|
||||
// Match markdown image syntax: 
|
||||
// Only update if the path doesn't already start with images/
|
||||
return markdown.replaceAll("(!\\[.*?\\])\\((?!images/)([^/)][^)]*?)\\)", "$1(images/$2)");
|
||||
}
|
||||
|
||||
public ResponseEntity<byte[]> processPdfToHtml(MultipartFile inputFile)
|
||||
throws IOException, InterruptedException {
|
||||
if (!MediaType.APPLICATION_PDF_VALUE.equals(inputFile.getContentType())) {
|
||||
|
||||
@@ -574,34 +574,39 @@ public class PdfUtils {
|
||||
boolean everyPage)
|
||||
throws IOException {
|
||||
|
||||
PDDocument document = pdfDocumentFactory.load(pdfBytes);
|
||||
|
||||
// Get the first page of the PDF
|
||||
int pages = document.getNumberOfPages();
|
||||
for (int i = 0; i < pages; i++) {
|
||||
PDPage page = document.getPage(i);
|
||||
try (PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
|
||||
// Create an image object from the image bytes
|
||||
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "");
|
||||
// Draw the image onto the page at the specified x and y coordinates
|
||||
contentStream.drawImage(image, x, y);
|
||||
log.info("Image successfully overlaid onto PDF");
|
||||
if (!everyPage && i == 0) {
|
||||
break;
|
||||
try (PDDocument document = pdfDocumentFactory.load(pdfBytes)) {
|
||||
// Get the first page of the PDF
|
||||
int pages = document.getNumberOfPages();
|
||||
for (int i = 0; i < pages; i++) {
|
||||
PDPage page = document.getPage(i);
|
||||
try (PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document,
|
||||
page,
|
||||
PDPageContentStream.AppendMode.APPEND,
|
||||
true,
|
||||
true)) {
|
||||
// Create an image object from the image bytes
|
||||
PDImageXObject image =
|
||||
PDImageXObject.createFromByteArray(document, imageBytes, "");
|
||||
// Draw the image onto the page at the specified x and y coordinates
|
||||
contentStream.drawImage(image, x, y);
|
||||
log.info("Image successfully overlaid onto PDF");
|
||||
if (!everyPage && i == 0) {
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Log an error message if there is an issue overlaying the image onto the PDF
|
||||
log.error("Error overlaying image onto PDF", e);
|
||||
throw e;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Log an error message if there is an issue overlaying the image onto the PDF
|
||||
log.error("Error overlaying image onto PDF", e);
|
||||
throw e;
|
||||
}
|
||||
// Create a ByteArrayOutputStream to save the PDF to
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
log.info("PDF successfully saved to byte array");
|
||||
return baos.toByteArray();
|
||||
}
|
||||
// Create a ByteArrayOutputStream to save the PDF to
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
log.info("PDF successfully saved to byte array");
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck)
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -34,7 +35,8 @@ class FileMonitorTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
when(runtimePathConfig.getPipelineWatchedFoldersPath()).thenReturn(tempDir.toString());
|
||||
when(runtimePathConfig.getPipelineWatchedFoldersPaths())
|
||||
.thenReturn(List.of(tempDir.toString()));
|
||||
|
||||
// This mock is used in all tests except testPathFilter
|
||||
// We use lenient to avoid UnnecessaryStubbingException in that test
|
||||
|
||||
@@ -153,11 +153,12 @@ class PDFToFileTest {
|
||||
MediaType.APPLICATION_PDF_VALUE,
|
||||
"Fake PDF content".getBytes());
|
||||
|
||||
// Create a mock HTML output file
|
||||
// Create a mock HTML output file with image references
|
||||
Path htmlOutputFile = tempDir.resolve("test.html");
|
||||
Files.write(
|
||||
htmlOutputFile,
|
||||
"<html><body><h1>Test</h1><p>This is a test.</p></body></html>".getBytes());
|
||||
"<html><body><h1>Test</h1><p>This is a test.</p><img src=\"image1.png\" /></body></html>"
|
||||
.getBytes());
|
||||
|
||||
// Setup ProcessExecutor mock
|
||||
mockedStaticProcessExecutor
|
||||
@@ -174,18 +175,61 @@ class PDFToFileTest {
|
||||
Files.copy(
|
||||
htmlOutputFile, Path.of(outputDir.getPath(), "test.html"));
|
||||
|
||||
// Create a mock image file
|
||||
Files.write(
|
||||
Path.of(outputDir.getPath(), "image1.png"),
|
||||
"Fake image data".getBytes());
|
||||
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
// Execute the method
|
||||
ResponseEntity<byte[]> response = pdfToFile.processPdfToMarkdown(pdfFile);
|
||||
|
||||
// Verify
|
||||
// Verify - should now return a ZIP file instead of plain markdown
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
|
||||
// Verify content disposition indicates a ZIP file
|
||||
assertTrue(
|
||||
response.getHeaders().getContentDisposition().toString().contains("test.md"));
|
||||
response.getHeaders()
|
||||
.getContentDisposition()
|
||||
.toString()
|
||||
.contains("ToMarkdown.zip"));
|
||||
|
||||
// Verify the content by unzipping it
|
||||
try (ZipInputStream zipStream =
|
||||
ZipSecurity.createHardenedInputStream(
|
||||
new java.io.ByteArrayInputStream(response.getBody()))) {
|
||||
ZipEntry entry;
|
||||
boolean foundMdFile = false;
|
||||
boolean foundImageInFolder = false;
|
||||
String markdownContent = null;
|
||||
|
||||
while ((entry = zipStream.getNextEntry()) != null) {
|
||||
if (entry.getName().endsWith(".md")) {
|
||||
foundMdFile = true;
|
||||
// Read markdown content to verify image references
|
||||
markdownContent =
|
||||
new String(
|
||||
zipStream.readAllBytes(),
|
||||
java.nio.charset.StandardCharsets.UTF_8);
|
||||
} else if (entry.getName().startsWith("images/")
|
||||
&& entry.getName().endsWith(".png")) {
|
||||
foundImageInFolder = true;
|
||||
}
|
||||
zipStream.closeEntry();
|
||||
}
|
||||
|
||||
assertTrue(foundMdFile, "ZIP should contain Markdown file");
|
||||
assertTrue(foundImageInFolder, "ZIP should contain image in images/ folder");
|
||||
assertNotNull(markdownContent, "Markdown content should be present");
|
||||
// Verify markdown references images with images/ prefix
|
||||
assertTrue(
|
||||
markdownContent.contains("images/"),
|
||||
"Markdown should reference images with images/ prefix");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,14 +300,15 @@ class PDFToFileTest {
|
||||
while ((entry = zipStream.getNextEntry()) != null) {
|
||||
if (entry.getName().endsWith(".md")) {
|
||||
foundMdFiles = true;
|
||||
} else if (entry.getName().endsWith(".png")) {
|
||||
} else if (entry.getName().startsWith("images/")
|
||||
&& entry.getName().endsWith(".png")) {
|
||||
foundImage = true;
|
||||
}
|
||||
zipStream.closeEntry();
|
||||
}
|
||||
|
||||
assertTrue(foundMdFiles, "ZIP should contain Markdown files");
|
||||
assertTrue(foundImage, "ZIP should contain image files");
|
||||
assertTrue(foundImage, "ZIP should contain image files in images/ folder");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,7 @@ def generatedFrontendPaths = [
|
||||
]
|
||||
|
||||
tasks.register('npmInstall', Exec) {
|
||||
doNotTrackState("node_modules contains symlinks that Gradle cannot snapshot on Windows/WSL")
|
||||
enabled = buildWithFrontend
|
||||
group = 'frontend'
|
||||
description = 'Install frontend dependencies'
|
||||
@@ -214,6 +215,7 @@ tasks.register('npmInstall', Exec) {
|
||||
}
|
||||
|
||||
tasks.register('npmBuild', Exec) {
|
||||
doNotTrackState("Frontend build depends on untracked npmInstall task")
|
||||
enabled = buildWithFrontend
|
||||
group = 'frontend'
|
||||
description = 'Build frontend application'
|
||||
@@ -221,6 +223,7 @@ tasks.register('npmBuild', Exec) {
|
||||
commandLine = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c', 'npm', 'run', 'build'] : ['npm', 'run', 'build']
|
||||
dependsOn npmInstall
|
||||
inputs.dir(new File(frontendDir, 'src'))
|
||||
inputs.dir(new File(frontendDir, 'public'))
|
||||
inputs.file(new File(frontendDir, 'package.json'))
|
||||
outputs.dir(frontendDistDir)
|
||||
|
||||
|
||||
@@ -118,7 +118,8 @@ public class ExternalAppDepConfig {
|
||||
|
||||
for (String group : affectedGroups) {
|
||||
List<String> affectedFeatures = getAffectedFeatures(group);
|
||||
endpointConfiguration.disableGroup(group);
|
||||
endpointConfiguration.disableGroup(
|
||||
group, EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
log.warn(
|
||||
"Missing dependency: {} - Disabling group: {} (Affected features: {})",
|
||||
command,
|
||||
@@ -143,7 +144,8 @@ public class ExternalAppDepConfig {
|
||||
commandToGroupMapping.getOrDefault(
|
||||
command, List.of("Weasyprint"));
|
||||
for (String group : affectedGroups) {
|
||||
endpointConfiguration.disableGroup(group);
|
||||
endpointConfiguration.disableGroup(
|
||||
group, EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
}
|
||||
log.warn(
|
||||
"WeasyPrint version {} is below required {} - disabling"
|
||||
@@ -172,7 +174,8 @@ public class ExternalAppDepConfig {
|
||||
List<String> affectedGroups =
|
||||
commandToGroupMapping.getOrDefault(command, List.of("qpdf"));
|
||||
for (String group : affectedGroups) {
|
||||
endpointConfiguration.disableGroup(group);
|
||||
endpointConfiguration.disableGroup(
|
||||
group, EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
}
|
||||
log.warn(
|
||||
"qpdf version {} is below required {} - disabling group(s): {}",
|
||||
@@ -226,7 +229,8 @@ public class ExternalAppDepConfig {
|
||||
int ec = runAndWait(List.of(python, "-c", "import cv2"), DEFAULT_TIMEOUT).exitCode();
|
||||
if (ec != 0) {
|
||||
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
||||
endpointConfiguration.disableGroup("OpenCV");
|
||||
endpointConfiguration.disableGroup(
|
||||
"OpenCV", EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
log.warn(
|
||||
"OpenCV not available in Python - Disabling OpenCV features: {}",
|
||||
String.join(", ", openCVFeatures));
|
||||
@@ -236,8 +240,10 @@ public class ExternalAppDepConfig {
|
||||
private void disablePythonAndOpenCV(String reason) {
|
||||
List<String> pythonFeatures = getAffectedFeatures("Python");
|
||||
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
||||
endpointConfiguration.disableGroup("Python");
|
||||
endpointConfiguration.disableGroup("OpenCV");
|
||||
endpointConfiguration.disableGroup(
|
||||
"Python", EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
endpointConfiguration.disableGroup(
|
||||
"OpenCV", EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
log.warn(
|
||||
"Missing dependency: Python (reason: {}) - Disabling Python features: {} and OpenCV"
|
||||
+ " features: {}",
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.servlet.MultipartConfigFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import jakarta.servlet.MultipartConfigElement;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.controller.web.UploadLimitService;
|
||||
|
||||
/**
|
||||
* Configuration for Spring multipart file upload settings. Synchronizes multipart limits with
|
||||
* fileUploadLimit from settings.yml or environment variables (SYSTEMFILEUPLOADLIMIT or
|
||||
* SYSTEM_MAXFILESIZE).
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class MultipartConfiguration {
|
||||
|
||||
@Autowired private UploadLimitService uploadLimitService;
|
||||
|
||||
/**
|
||||
* Creates MultipartConfigElement that respects fileUploadLimit from settings.yml or environment
|
||||
* variables (SYSTEMFILEUPLOADLIMIT or SYSTEM_MAXFILESIZE). Depends on ApplicationProperties
|
||||
* being initialized so @PostConstruct has run.
|
||||
*/
|
||||
@Bean
|
||||
@DependsOn("applicationProperties")
|
||||
public MultipartConfigElement multipartConfigElement() {
|
||||
MultipartConfigFactory factory = new MultipartConfigFactory();
|
||||
|
||||
// First check if SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE is explicitly set
|
||||
String springMaxFileSize =
|
||||
java.lang.System.getenv("SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE");
|
||||
long uploadLimitBytes = 0;
|
||||
|
||||
if (springMaxFileSize != null && !springMaxFileSize.trim().isEmpty()) {
|
||||
// Parse the Spring property format (e.g., "2000MB")
|
||||
try {
|
||||
DataSize dataSize = DataSize.parse(springMaxFileSize.trim());
|
||||
uploadLimitBytes = dataSize.toBytes();
|
||||
log.info("Using SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE: {}", springMaxFileSize);
|
||||
} catch (Exception e) {
|
||||
log.warn(
|
||||
"Failed to parse SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE: {}",
|
||||
springMaxFileSize,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
// If not set via Spring property, use UploadLimitService which reads from
|
||||
// fileUploadLimit (set from SYSTEMFILEUPLOADLIMIT/SYSTEM_MAXFILESIZE or settings.yml)
|
||||
if (uploadLimitBytes == 0) {
|
||||
uploadLimitBytes = uploadLimitService.getUploadLimit();
|
||||
if (uploadLimitBytes > 0) {
|
||||
log.info(
|
||||
"Using fileUploadLimit setting: {}",
|
||||
uploadLimitService.getReadableUploadLimit());
|
||||
}
|
||||
}
|
||||
|
||||
// If still no limit, use default of 2000MB
|
||||
if (uploadLimitBytes == 0) {
|
||||
uploadLimitBytes = 2000L * 1024 * 1024; // 2000MB default
|
||||
log.info("Using default multipart file upload limit: 2000MB");
|
||||
}
|
||||
|
||||
// Set max file size and max request size to the same value
|
||||
factory.setMaxFileSize(DataSize.ofBytes(uploadLimitBytes));
|
||||
factory.setMaxRequestSize(DataSize.ofBytes(uploadLimitBytes));
|
||||
|
||||
return factory.createMultipartConfig();
|
||||
}
|
||||
}
|
||||
@@ -106,16 +106,29 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
} else if (hasConfiguredOrigins) {
|
||||
// Use user-configured origins
|
||||
// Use user-configured origins + always include Tauri origins for desktop app support
|
||||
logger.info(
|
||||
"Configuring CORS with allowed origins: {}",
|
||||
applicationProperties.getSystem().getCorsAllowedOrigins());
|
||||
|
||||
String[] allowedOrigins =
|
||||
applicationProperties
|
||||
.getSystem()
|
||||
.getCorsAllowedOrigins()
|
||||
.toArray(new String[0]);
|
||||
// Combine user-configured origins with Tauri origins
|
||||
java.util.List<String> allOrigins =
|
||||
new java.util.ArrayList<>(
|
||||
applicationProperties.getSystem().getCorsAllowedOrigins());
|
||||
|
||||
// Always include Tauri origins for desktop app compatibility
|
||||
// Tauri v1 uses tauri://localhost, v2 uses http(s)://tauri.localhost
|
||||
if (!allOrigins.contains("tauri://localhost")) {
|
||||
allOrigins.add("tauri://localhost");
|
||||
}
|
||||
if (!allOrigins.contains("http://tauri.localhost")) {
|
||||
allOrigins.add("http://tauri.localhost");
|
||||
}
|
||||
if (!allOrigins.contains("https://tauri.localhost")) {
|
||||
allOrigins.add("https://tauri.localhost");
|
||||
}
|
||||
|
||||
String[] allowedOrigins = allOrigins.toArray(new String[0]);
|
||||
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns(allowedOrigins)
|
||||
|
||||
+5
-5
@@ -46,18 +46,18 @@ public class EditTableOfContentsController {
|
||||
@Operation(
|
||||
summary = "Extract PDF Bookmarks",
|
||||
description = "Extracts bookmarks/table of contents from a PDF document as JSON.")
|
||||
@ResponseBody
|
||||
public List<Map<String, Object>> extractBookmarks(@RequestParam("file") MultipartFile file)
|
||||
throws Exception {
|
||||
public ResponseEntity<List<Map<String, Object>>> extractBookmarks(
|
||||
@RequestParam("file") MultipartFile file) throws Exception {
|
||||
try (PDDocument document = pdfDocumentFactory.load(file)) {
|
||||
PDDocumentOutline outline = document.getDocumentCatalog().getDocumentOutline();
|
||||
|
||||
if (outline == null) {
|
||||
log.info("No outline/bookmarks found in PDF");
|
||||
return new ArrayList<>();
|
||||
return ResponseEntity.ok(new ArrayList<>());
|
||||
}
|
||||
|
||||
return extractBookmarkItems(document, outline);
|
||||
List<Map<String, Object>> bookmarks = extractBookmarkItems(document, outline);
|
||||
return ResponseEntity.ok(bookmarks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+56
-54
@@ -101,66 +101,68 @@ public class SplitPdfBySectionsController {
|
||||
return WebResponseUtils.baosToWebResponse(baos, filename + ".pdf");
|
||||
}
|
||||
} else {
|
||||
TempFile zipTempFile = new TempFile(tempFileManager, ".zip");
|
||||
try (ZipOutputStream zipOut =
|
||||
new ZipOutputStream(Files.newOutputStream(zipTempFile.getPath()))) {
|
||||
for (int pageIndex = 0;
|
||||
pageIndex < sourceDocument.getNumberOfPages();
|
||||
pageIndex++) {
|
||||
int pageNum = pageIndex + 1;
|
||||
if (pagesToSplit.contains(pageIndex)) {
|
||||
for (int i = 0; i < horiz; i++) {
|
||||
for (int j = 0; j < verti; j++) {
|
||||
try (PDDocument subDoc =
|
||||
pdfDocumentFactory.createNewDocument()) {
|
||||
LayerUtility subLayerUtility = new LayerUtility(subDoc);
|
||||
addSingleSectionToTarget(
|
||||
sourceDocument,
|
||||
pageIndex,
|
||||
subDoc,
|
||||
subLayerUtility,
|
||||
i,
|
||||
j,
|
||||
horiz,
|
||||
verti);
|
||||
int sectionNum = i * verti + j + 1;
|
||||
String entryName =
|
||||
filename
|
||||
+ "_"
|
||||
+ pageNum
|
||||
+ "_"
|
||||
+ sectionNum
|
||||
+ ".pdf";
|
||||
saveDocToZip(subDoc, zipOut, entryName);
|
||||
} catch (IOException e) {
|
||||
log.error(
|
||||
"Error creating section {} for page {}",
|
||||
(i * verti + j + 1),
|
||||
pageNum,
|
||||
e);
|
||||
throw e;
|
||||
try (TempFile zipTempFile = new TempFile(tempFileManager, ".zip")) {
|
||||
try (ZipOutputStream zipOut =
|
||||
new ZipOutputStream(Files.newOutputStream(zipTempFile.getPath()))) {
|
||||
for (int pageIndex = 0;
|
||||
pageIndex < sourceDocument.getNumberOfPages();
|
||||
pageIndex++) {
|
||||
int pageNum = pageIndex + 1;
|
||||
if (pagesToSplit.contains(pageIndex)) {
|
||||
for (int i = 0; i < horiz; i++) {
|
||||
for (int j = 0; j < verti; j++) {
|
||||
try (PDDocument subDoc =
|
||||
pdfDocumentFactory.createNewDocument()) {
|
||||
LayerUtility subLayerUtility = new LayerUtility(subDoc);
|
||||
addSingleSectionToTarget(
|
||||
sourceDocument,
|
||||
pageIndex,
|
||||
subDoc,
|
||||
subLayerUtility,
|
||||
i,
|
||||
j,
|
||||
horiz,
|
||||
verti);
|
||||
int sectionNum = i * verti + j + 1;
|
||||
String entryName =
|
||||
filename
|
||||
+ "_"
|
||||
+ pageNum
|
||||
+ "_"
|
||||
+ sectionNum
|
||||
+ ".pdf";
|
||||
saveDocToZip(subDoc, zipOut, entryName);
|
||||
} catch (IOException e) {
|
||||
log.error(
|
||||
"Error creating section {} for page {}",
|
||||
(i * verti + j + 1),
|
||||
pageNum,
|
||||
e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try (PDDocument subDoc = pdfDocumentFactory.createNewDocument()) {
|
||||
LayerUtility subLayerUtility = new LayerUtility(subDoc);
|
||||
addPageToTarget(sourceDocument, pageIndex, subDoc, subLayerUtility);
|
||||
String entryName = filename + "_" + pageNum + "_1.pdf";
|
||||
saveDocToZip(subDoc, zipOut, entryName);
|
||||
} catch (IOException e) {
|
||||
log.error("Error processing unsplit page {}", pageNum, e);
|
||||
throw e;
|
||||
} else {
|
||||
try (PDDocument subDoc = pdfDocumentFactory.createNewDocument()) {
|
||||
LayerUtility subLayerUtility = new LayerUtility(subDoc);
|
||||
addPageToTarget(
|
||||
sourceDocument, pageIndex, subDoc, subLayerUtility);
|
||||
String entryName = filename + "_" + pageNum + "_1.pdf";
|
||||
saveDocToZip(subDoc, zipOut, entryName);
|
||||
} catch (IOException e) {
|
||||
log.error("Error processing unsplit page {}", pageNum, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error creating ZIP file with split PDF sections", e);
|
||||
throw e;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error creating ZIP file with split PDF sections", e);
|
||||
throw e;
|
||||
byte[] zipBytes = Files.readAllBytes(zipTempFile.getPath());
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
zipBytes, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
byte[] zipBytes = Files.readAllBytes(zipTempFile.getPath());
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
zipBytes, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error splitting PDF file: {}", file.getOriginalFilename(), e);
|
||||
|
||||
+164
-23
@@ -44,7 +44,7 @@ public class ConvertMarkdownToPdf {
|
||||
@Operation(
|
||||
summary = "Convert a Markdown file to PDF",
|
||||
description =
|
||||
"This endpoint takes a Markdown file input, converts it to HTML, and then to"
|
||||
"This endpoint takes a Markdown file or ZIP (containing Markdown + images) input, converts it to HTML, and then to"
|
||||
+ " PDF format. Input:MARKDOWN Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile generalFile)
|
||||
throws Exception {
|
||||
@@ -52,40 +52,181 @@ public class ConvertMarkdownToPdf {
|
||||
|
||||
if (fileInput == null) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.fileFormatRequired", "File must be in {0} format", "Markdown");
|
||||
"error.fileFormatRequired", "File must be in {0} format", "Markdown or ZIP");
|
||||
}
|
||||
|
||||
String originalFilename = Filenames.toSimpleFileName(fileInput.getOriginalFilename());
|
||||
if (originalFilename == null || !originalFilename.endsWith(".md")) {
|
||||
if (originalFilename == null) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.fileFormatRequired", "File must be in {0} format", ".md");
|
||||
"error.fileFormatRequired", "File must be in {0} format", ".md or .zip");
|
||||
}
|
||||
|
||||
// Convert Markdown to HTML using CommonMark
|
||||
List<Extension> extensions = List.of(TablesExtension.create());
|
||||
Parser parser = Parser.builder().extensions(extensions).build();
|
||||
boolean isZip = originalFilename.toLowerCase().endsWith(".zip");
|
||||
boolean isMarkdown = originalFilename.toLowerCase().endsWith(".md");
|
||||
|
||||
Node document = parser.parse(new String(fileInput.getBytes()));
|
||||
HtmlRenderer renderer =
|
||||
HtmlRenderer.builder()
|
||||
.attributeProviderFactory(context -> new TableAttributeProvider())
|
||||
.extensions(extensions)
|
||||
.build();
|
||||
if (!isZip && !isMarkdown) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.fileFormatRequired", "File must be in {0} format", ".md or .zip");
|
||||
}
|
||||
|
||||
String htmlContent = renderer.render(document);
|
||||
byte[] pdfBytes;
|
||||
String outputFilename;
|
||||
|
||||
if (isZip) {
|
||||
// Handle ZIP file containing markdown + images
|
||||
try (TempDirectory tempDir = new TempDirectory(tempFileManager)) {
|
||||
// Extract ZIP to temp directory
|
||||
java.nio.file.Path tempDirPath = tempDir.getPath();
|
||||
try (java.util.zip.ZipInputStream zipIn =
|
||||
io.github.pixee.security.ZipSecurity.createHardenedInputStream(
|
||||
new java.io.ByteArrayInputStream(fileInput.getBytes()))) {
|
||||
java.util.zip.ZipEntry entry;
|
||||
while ((entry = zipIn.getNextEntry()) != null) {
|
||||
if (!entry.isDirectory()) {
|
||||
java.nio.file.Path filePath = tempDirPath.resolve(entry.getName());
|
||||
java.nio.file.Files.createDirectories(filePath.getParent());
|
||||
java.nio.file.Files.copy(zipIn, filePath);
|
||||
}
|
||||
zipIn.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
// Find the markdown file (look for .md files, prefer index.md or first one)
|
||||
java.io.File markdownFile = findMarkdownFile(tempDirPath.toFile());
|
||||
if (markdownFile == null) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.fileFormatRequired",
|
||||
"ZIP must contain at least one {0} file",
|
||||
".md");
|
||||
}
|
||||
|
||||
// Read and convert markdown to HTML
|
||||
String markdownContent = java.nio.file.Files.readString(markdownFile.toPath());
|
||||
List<Extension> extensions = List.of(TablesExtension.create());
|
||||
Parser parser = Parser.builder().extensions(extensions).build();
|
||||
Node document = parser.parse(markdownContent);
|
||||
HtmlRenderer renderer =
|
||||
HtmlRenderer.builder()
|
||||
.attributeProviderFactory(context -> new TableAttributeProvider())
|
||||
.extensions(extensions)
|
||||
.build();
|
||||
String htmlContent = renderer.render(document);
|
||||
|
||||
// Create a new ZIP with HTML + images for WeasyPrint
|
||||
byte[] htmlZipBytes = createHtmlZip(htmlContent, tempDirPath.toFile());
|
||||
|
||||
// Use FileToPdf which already supports ZIP files with images
|
||||
pdfBytes =
|
||||
FileToPdf.convertHtmlToPdf(
|
||||
runtimePathConfig.getWeasyPrintPath(),
|
||||
null,
|
||||
htmlZipBytes,
|
||||
"package.zip",
|
||||
tempFileManager,
|
||||
customHtmlSanitizer);
|
||||
|
||||
outputFilename =
|
||||
GeneralUtils.generateFilename(
|
||||
originalFilename.substring(0, originalFilename.lastIndexOf('.')),
|
||||
".pdf");
|
||||
}
|
||||
} else {
|
||||
// Handle plain markdown file (no images)
|
||||
List<Extension> extensions = List.of(TablesExtension.create());
|
||||
Parser parser = Parser.builder().extensions(extensions).build();
|
||||
|
||||
Node document = parser.parse(new String(fileInput.getBytes()));
|
||||
HtmlRenderer renderer =
|
||||
HtmlRenderer.builder()
|
||||
.attributeProviderFactory(context -> new TableAttributeProvider())
|
||||
.extensions(extensions)
|
||||
.build();
|
||||
|
||||
String htmlContent = renderer.render(document);
|
||||
|
||||
pdfBytes =
|
||||
FileToPdf.convertHtmlToPdf(
|
||||
runtimePathConfig.getWeasyPrintPath(),
|
||||
null,
|
||||
htmlContent.getBytes(),
|
||||
"converted.html",
|
||||
tempFileManager,
|
||||
customHtmlSanitizer);
|
||||
|
||||
outputFilename = GeneralUtils.generateFilename(originalFilename, ".pdf");
|
||||
}
|
||||
|
||||
byte[] pdfBytes =
|
||||
FileToPdf.convertHtmlToPdf(
|
||||
runtimePathConfig.getWeasyPrintPath(),
|
||||
null,
|
||||
htmlContent.getBytes(),
|
||||
"converted.html",
|
||||
tempFileManager,
|
||||
customHtmlSanitizer);
|
||||
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||
String outputFilename = GeneralUtils.generateFilename(originalFilename, ".pdf");
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a markdown file in the directory. Prefers index.md, otherwise returns the first .md
|
||||
* file found.
|
||||
*/
|
||||
private java.io.File findMarkdownFile(java.io.File directory) throws java.io.IOException {
|
||||
java.io.File indexMd = new java.io.File(directory, "index.md");
|
||||
if (indexMd.exists()) {
|
||||
return indexMd;
|
||||
}
|
||||
|
||||
// Search for any .md file
|
||||
try (java.util.stream.Stream<java.nio.file.Path> paths =
|
||||
java.nio.file.Files.walk(directory.toPath())) {
|
||||
return paths.filter(p -> p.toString().toLowerCase().endsWith(".md"))
|
||||
.findFirst()
|
||||
.map(java.nio.file.Path::toFile)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ZIP file containing the HTML content and all other files (images) from the
|
||||
* directory.
|
||||
*/
|
||||
private byte[] createHtmlZip(String htmlContent, java.io.File sourceDir)
|
||||
throws java.io.IOException {
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
|
||||
try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(baos)) {
|
||||
// Add HTML file to root
|
||||
java.util.zip.ZipEntry htmlEntry = new java.util.zip.ZipEntry("index.html");
|
||||
zos.putNextEntry(htmlEntry);
|
||||
zos.write(htmlContent.getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||
zos.closeEntry();
|
||||
|
||||
// Add all other files (images, etc.)
|
||||
addDirectoryToZip(zos, sourceDir.toPath(), sourceDir.toPath());
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
/** Recursively adds files from a directory to a ZIP, excluding .md files. */
|
||||
private void addDirectoryToZip(
|
||||
java.util.zip.ZipOutputStream zos,
|
||||
java.nio.file.Path sourceDir,
|
||||
java.nio.file.Path rootDir)
|
||||
throws java.io.IOException {
|
||||
try (java.util.stream.Stream<java.nio.file.Path> paths =
|
||||
java.nio.file.Files.walk(sourceDir, 1)) {
|
||||
for (java.nio.file.Path path : paths.toList()) {
|
||||
if (java.nio.file.Files.isDirectory(path)) {
|
||||
if (!path.equals(sourceDir)) {
|
||||
addDirectoryToZip(zos, path, rootDir);
|
||||
}
|
||||
} else if (!path.toString().toLowerCase().endsWith(".md")) {
|
||||
// Add file to ZIP, maintaining relative path structure
|
||||
java.nio.file.Path relativePath = rootDir.relativize(path);
|
||||
java.util.zip.ZipEntry entry =
|
||||
new java.util.zip.ZipEntry(relativePath.toString());
|
||||
zos.putNextEntry(entry);
|
||||
java.nio.file.Files.copy(path, zos);
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TableAttributeProvider implements AttributeProvider {
|
||||
|
||||
+39
-3
@@ -71,10 +71,12 @@ import org.apache.xmpbox.schema.PDFAIdentificationSchema;
|
||||
import org.apache.xmpbox.schema.XMPBasicSchema;
|
||||
import org.apache.xmpbox.xml.DomXmpParser;
|
||||
import org.apache.xmpbox.xml.XmpSerializer;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -99,6 +101,7 @@ public class ConvertPDFToPDFA {
|
||||
|
||||
private static final Pattern NON_PRINTABLE_ASCII = Pattern.compile("[^\\x20-\\x7E]");
|
||||
private final RuntimePathConfig runtimePathConfig;
|
||||
private final stirling.software.SPDF.service.VeraPDFService veraPDFService;
|
||||
|
||||
private static final String ICC_RESOURCE_PATH = "/icc/sRGB2014.icc";
|
||||
private static final int PDFA_COMPATIBILITY_POLICY = 1;
|
||||
@@ -587,7 +590,8 @@ public class ConvertPDFToPDFA {
|
||||
if (isPdfX) {
|
||||
return handlePdfXConversion(inputFile, outputFormat);
|
||||
} else {
|
||||
return handlePdfAConversion(inputFile, outputFormat);
|
||||
return handlePdfAConversion(
|
||||
inputFile, outputFormat, request.getStrict() != null && request.getStrict());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1793,7 +1797,7 @@ public class ConvertPDFToPDFA {
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> handlePdfAConversion(
|
||||
MultipartFile inputFile, String outputFormat) throws Exception {
|
||||
MultipartFile inputFile, String outputFormat, boolean strict) throws Exception {
|
||||
PdfaProfile profile = PdfaProfile.fromRequest(outputFormat);
|
||||
|
||||
// Get the original filename without extension
|
||||
@@ -1822,6 +1826,10 @@ public class ConvertPDFToPDFA {
|
||||
|
||||
validateAndWarnPdfA(converted, profile, "Ghostscript");
|
||||
|
||||
if (strict) {
|
||||
verifyStrictCompliance(converted);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
converted, outputFilename, MediaType.APPLICATION_PDF);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
@@ -1839,14 +1847,42 @@ public class ConvertPDFToPDFA {
|
||||
// Validate with PDFBox preflight and warn if issues found
|
||||
validateAndWarnPdfA(converted, profile, "PDFBox/LibreOffice");
|
||||
|
||||
if (strict) {
|
||||
verifyStrictCompliance(converted);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
converted, outputFilename, MediaType.APPLICATION_PDF);
|
||||
|
||||
} finally {
|
||||
deleteQuietly(workingDir);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyStrictCompliance(byte[] pdfBytes) throws IOException {
|
||||
try (InputStream is = new ByteArrayInputStream(pdfBytes)) {
|
||||
List<stirling.software.SPDF.model.api.security.PDFVerificationResult> results =
|
||||
veraPDFService.validatePDF(is);
|
||||
boolean isCompliant = results.stream().anyMatch(result -> result.isCompliant());
|
||||
if (!isCompliant) {
|
||||
String details =
|
||||
results.stream()
|
||||
.map(r -> r.getStandard() + ": " + r.getComplianceSummary())
|
||||
.collect(Collectors.joining("; "));
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Strict PDF/A mode enabled: Conversion is not perfectly compliant. Details: "
|
||||
+ details);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof ResponseStatusException) {
|
||||
throw (ResponseStatusException) e;
|
||||
}
|
||||
log.error("Error during strict PDF/A verification", e);
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR, "Error during strict PDF/A verification");
|
||||
}
|
||||
}
|
||||
|
||||
private Path sanitizePdfWithPdfBox(Path inputPdf, boolean addWhiteBackground) {
|
||||
try {
|
||||
Path sanitizedPath =
|
||||
|
||||
+65
-9
@@ -35,6 +35,7 @@ public class ConfigController {
|
||||
private final EndpointConfiguration endpointConfiguration;
|
||||
private final ServerCertificateServiceInterface serverCertificateService;
|
||||
private final UserServiceInterface userService;
|
||||
private final stirling.software.common.service.LicenseServiceInterface licenseService;
|
||||
private final stirling.software.SPDF.config.ExternalAppDepConfig externalAppDepConfig;
|
||||
|
||||
public ConfigController(
|
||||
@@ -45,15 +46,66 @@ public class ConfigController {
|
||||
ServerCertificateServiceInterface serverCertificateService,
|
||||
@org.springframework.beans.factory.annotation.Autowired(required = false)
|
||||
UserServiceInterface userService,
|
||||
@org.springframework.beans.factory.annotation.Autowired(required = false)
|
||||
stirling.software.common.service.LicenseServiceInterface licenseService,
|
||||
stirling.software.SPDF.config.ExternalAppDepConfig externalAppDepConfig) {
|
||||
this.applicationProperties = applicationProperties;
|
||||
this.applicationContext = applicationContext;
|
||||
this.endpointConfiguration = endpointConfiguration;
|
||||
this.serverCertificateService = serverCertificateService;
|
||||
this.userService = userService;
|
||||
this.licenseService = licenseService;
|
||||
this.externalAppDepConfig = externalAppDepConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current license type dynamically instead of from cached bean. This ensures the frontend
|
||||
* sees updated license status after admin changes the license key.
|
||||
*/
|
||||
private String getCurrentLicenseType() {
|
||||
// Use LicenseService for fresh license status if available
|
||||
if (licenseService != null) {
|
||||
return licenseService.getLicenseTypeName();
|
||||
}
|
||||
|
||||
// Fallback to cached bean if service not available
|
||||
if (applicationContext.containsBean("license")) {
|
||||
return applicationContext.getBean("license", String.class);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Check if running Pro or higher (SERVER or ENTERPRISE license) dynamically. */
|
||||
private Boolean isRunningProOrHigher() {
|
||||
// Use LicenseService for fresh license status if available
|
||||
if (licenseService != null) {
|
||||
return licenseService.isRunningProOrHigher();
|
||||
}
|
||||
|
||||
// Fallback to cached bean
|
||||
if (applicationContext.containsBean("runningProOrHigher")) {
|
||||
return applicationContext.getBean("runningProOrHigher", Boolean.class);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Check if running Enterprise edition dynamically. */
|
||||
private Boolean isRunningEE() {
|
||||
// Use LicenseService for fresh license status if available
|
||||
if (licenseService != null) {
|
||||
return licenseService.isRunningEE();
|
||||
}
|
||||
|
||||
// Fallback to cached bean
|
||||
if (applicationContext.containsBean("runningEE")) {
|
||||
return applicationContext.getBean("runningEE", Boolean.class);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@GetMapping("/app-config")
|
||||
public ResponseEntity<Map<String, Object>> getAppConfig() {
|
||||
Map<String, Object> configData = new HashMap<>();
|
||||
@@ -185,19 +237,23 @@ public class ConfigController {
|
||||
applicationProperties.getLegal().getAccessibilityStatement());
|
||||
|
||||
// Try to get EEAppConfig values if available
|
||||
// Get these dynamically to reflect current license status (not cached at startup)
|
||||
try {
|
||||
if (applicationContext.containsBean("runningProOrHigher")) {
|
||||
configData.put(
|
||||
"runningProOrHigher",
|
||||
applicationContext.getBean("runningProOrHigher", Boolean.class));
|
||||
Boolean runningProOrHigher = isRunningProOrHigher();
|
||||
if (runningProOrHigher != null) {
|
||||
configData.put("runningProOrHigher", runningProOrHigher);
|
||||
}
|
||||
if (applicationContext.containsBean("runningEE")) {
|
||||
configData.put(
|
||||
"runningEE", applicationContext.getBean("runningEE", Boolean.class));
|
||||
|
||||
Boolean runningEE = isRunningEE();
|
||||
if (runningEE != null) {
|
||||
configData.put("runningEE", runningEE);
|
||||
}
|
||||
if (applicationContext.containsBean("license")) {
|
||||
configData.put("license", applicationContext.getBean("license", String.class));
|
||||
|
||||
String licenseType = getCurrentLicenseType();
|
||||
if (licenseType != null) {
|
||||
configData.put("license", licenseType);
|
||||
}
|
||||
|
||||
if (applicationContext.containsBean("SSOAutoLogin")) {
|
||||
configData.put(
|
||||
"SSOAutoLogin",
|
||||
|
||||
+32
-32
@@ -55,45 +55,45 @@ public class OverlayImageController {
|
||||
|
||||
boolean isSvg = SvgOverlayUtil.isSvgImage(imageBytes);
|
||||
|
||||
PDDocument document = pdfDocumentFactory.load(pdfBytes);
|
||||
try (PDDocument document = pdfDocumentFactory.load(pdfBytes)) {
|
||||
int pages = document.getNumberOfPages();
|
||||
for (int i = 0; i < pages; i++) {
|
||||
PDPage page = document.getPage(i);
|
||||
|
||||
int pages = document.getNumberOfPages();
|
||||
for (int i = 0; i < pages; i++) {
|
||||
PDPage page = document.getPage(i);
|
||||
if (isSvg) {
|
||||
SvgOverlayUtil.overlaySvgOnPage(document, page, imageBytes, x, y);
|
||||
} else {
|
||||
try (PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document,
|
||||
page,
|
||||
PDPageContentStream.AppendMode.APPEND,
|
||||
true,
|
||||
true)) {
|
||||
PDImageXObject image =
|
||||
PDImageXObject.createFromByteArray(document, imageBytes, "");
|
||||
contentStream.drawImage(image, x, y);
|
||||
log.info("Image successfully overlaid onto PDF page {}", i);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSvg) {
|
||||
SvgOverlayUtil.overlaySvgOnPage(document, page, imageBytes, x, y);
|
||||
} else {
|
||||
try (PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document,
|
||||
page,
|
||||
PDPageContentStream.AppendMode.APPEND,
|
||||
true,
|
||||
true)) {
|
||||
PDImageXObject image =
|
||||
PDImageXObject.createFromByteArray(document, imageBytes, "");
|
||||
contentStream.drawImage(image, x, y);
|
||||
log.info("Image successfully overlaid onto PDF page {}", i);
|
||||
if (!everyPage && i == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!everyPage && i == 0) {
|
||||
break;
|
||||
}
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
|
||||
byte[] result = baos.toByteArray();
|
||||
log.info("PDF with overlaid image successfully created");
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
result,
|
||||
GeneralUtils.generateFilename(
|
||||
pdfFile.getOriginalFilename(), "_overlayed.pdf"));
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
document.close();
|
||||
|
||||
byte[] result = baos.toByteArray();
|
||||
log.info("PDF with overlaid image successfully created");
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
result,
|
||||
GeneralUtils.generateFilename(pdfFile.getOriginalFilename(), "_overlayed.pdf"));
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to add image to PDF", e);
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
|
||||
+10
-1
@@ -52,9 +52,17 @@ public class PageNumbersController {
|
||||
int pageNumber = request.getStartingNumber();
|
||||
String pagesToNumber = request.getPagesToNumber();
|
||||
String customText = request.getCustomText();
|
||||
int zeroPad = request.getZeroPad();
|
||||
float fontSize = request.getFontSize();
|
||||
String fontType = request.getFontType();
|
||||
String fontColor = request.getFontColor();
|
||||
// compute padded number string where requested
|
||||
String formatN;
|
||||
if (zeroPad > 0) {
|
||||
formatN = String.format("%%0%dd", Math.max(0, zeroPad));
|
||||
} else {
|
||||
formatN = "%d";
|
||||
}
|
||||
|
||||
Color color = Color.BLACK;
|
||||
if (fontColor != null && !fontColor.trim().isEmpty()) {
|
||||
@@ -93,9 +101,10 @@ public class PageNumbersController {
|
||||
PDPage page = document.getPage(i);
|
||||
PDRectangle pageSize = page.getMediaBox();
|
||||
|
||||
String nFormatted = String.format(formatN, pageNumber);
|
||||
String text =
|
||||
customText
|
||||
.replace("{n}", String.valueOf(pageNumber))
|
||||
.replace("{n}", nFormatted)
|
||||
.replace("{total}", String.valueOf(document.getNumberOfPages()))
|
||||
.replace(
|
||||
"{filename}",
|
||||
|
||||
+196
-57
@@ -7,11 +7,13 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
@@ -58,6 +60,13 @@ public class StampController {
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
private static final int MAX_DATE_FORMAT_LENGTH = 50;
|
||||
private static final Pattern SAFE_DATE_FORMAT_PATTERN =
|
||||
Pattern.compile("^[yMdHhmsS/\\-:\\s.,'+EGuwWDFzZXa]+$");
|
||||
private static final Pattern CUSTOM_DATE_PATTERN = Pattern.compile("@date\\{([^}]{1,50})\\}");
|
||||
// Placeholder for escaped @ symbol (using Unicode private use area)
|
||||
private static final String ESCAPED_AT_PLACEHOLDER = "\uE000ESCAPED_AT\uE000";
|
||||
|
||||
/**
|
||||
* Initialize data binder for multipart file uploads. This method registers a custom editor for
|
||||
* MultipartFile to handle file uploads. It sets the MultipartFile to null if the uploaded file
|
||||
@@ -166,7 +175,9 @@ public class StampController {
|
||||
overrideX,
|
||||
overrideY,
|
||||
margin,
|
||||
customColor);
|
||||
customColor,
|
||||
pageIndex,
|
||||
pdfFileName);
|
||||
} else if ("image".equalsIgnoreCase(stampType)) {
|
||||
addImageStamp(
|
||||
contentStream,
|
||||
@@ -201,9 +212,11 @@ public class StampController {
|
||||
float fontSize,
|
||||
String alphabet,
|
||||
float overrideX, // X override
|
||||
float overrideY,
|
||||
float overrideY, // Y override
|
||||
float margin,
|
||||
String colorString) // Y override
|
||||
String colorString,
|
||||
int currentPageNumber,
|
||||
String filename)
|
||||
throws IOException {
|
||||
String resourceDir;
|
||||
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||
@@ -231,8 +244,6 @@ public class StampController {
|
||||
}
|
||||
}
|
||||
|
||||
contentStream.setFont(font, fontSize);
|
||||
|
||||
Color redactColor;
|
||||
try {
|
||||
if (!colorString.startsWith("#")) {
|
||||
@@ -240,47 +251,54 @@ public class StampController {
|
||||
}
|
||||
redactColor = Color.decode(colorString);
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
redactColor = Color.LIGHT_GRAY;
|
||||
}
|
||||
|
||||
contentStream.setNonStrokingColor(redactColor);
|
||||
|
||||
PDRectangle pageSize = page.getMediaBox();
|
||||
float x, y;
|
||||
|
||||
if (overrideX >= 0 && overrideY >= 0) {
|
||||
// Use override values if provided
|
||||
x = overrideX;
|
||||
y = overrideY;
|
||||
} else {
|
||||
x = calculatePositionX(pageSize, position, fontSize, font, fontSize, stampText, margin);
|
||||
y =
|
||||
calculatePositionY(
|
||||
pageSize, position, calculateTextCapHeight(font, fontSize), margin);
|
||||
}
|
||||
|
||||
String currentDate = LocalDate.now().toString();
|
||||
String currentTime = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
|
||||
int pageCount = document.getNumberOfPages();
|
||||
|
||||
String processedStampText =
|
||||
stampText
|
||||
.replace("@date", currentDate)
|
||||
.replace("@time", currentTime)
|
||||
.replace("@page_count", String.valueOf(pageCount));
|
||||
processStampText(stampText, currentPageNumber, pageCount, filename, document);
|
||||
|
||||
// Split the stampText into multiple lines
|
||||
String[] lines =
|
||||
String normalizedText =
|
||||
RegexPatternUtils.getInstance()
|
||||
.getEscapedNewlinePattern()
|
||||
.split(processedStampText);
|
||||
.matcher(processedStampText)
|
||||
.replaceAll("\n");
|
||||
String[] lines = normalizedText.split("\\r?\\n");
|
||||
|
||||
PDRectangle pageSize = page.getMediaBox();
|
||||
|
||||
// Use fontSize directly (default 40 if not specified)
|
||||
float effectiveFontSize = fontSize > 0 ? fontSize : 40f;
|
||||
|
||||
contentStream.setFont(font, effectiveFontSize);
|
||||
|
||||
// Calculate dynamic line height based on font ascent and descent
|
||||
float ascent = font.getFontDescriptor().getAscent();
|
||||
float descent = font.getFontDescriptor().getDescent();
|
||||
float lineHeight = ((ascent - descent) / 1000) * fontSize;
|
||||
float lineHeight = ((ascent - descent) / 1000) * effectiveFontSize;
|
||||
|
||||
float maxLineWidth = 0;
|
||||
for (String line : lines) {
|
||||
float lineWidth = calculateTextWidth(line, font, effectiveFontSize);
|
||||
if (lineWidth > maxLineWidth) {
|
||||
maxLineWidth = lineWidth;
|
||||
}
|
||||
}
|
||||
|
||||
float totalTextHeight = lines.length * lineHeight;
|
||||
|
||||
float x, y;
|
||||
|
||||
if (overrideX >= 0 && overrideY >= 0) {
|
||||
x = overrideX;
|
||||
y = overrideY;
|
||||
} else {
|
||||
x = calculatePositionX(pageSize, position, maxLineWidth, margin);
|
||||
y = calculatePositionY(pageSize, position, totalTextHeight, margin);
|
||||
}
|
||||
|
||||
contentStream.beginText();
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
@@ -293,6 +311,140 @@ public class StampController {
|
||||
contentStream.endText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process stamp text by replacing all @commands with their actual values. Supported commands:
|
||||
*
|
||||
* <p>Date & Time:
|
||||
*
|
||||
* <ul>
|
||||
* <li>@date - Current date (YYYY-MM-DD)
|
||||
* <li>@time - Current time (HH:mm:ss)
|
||||
* <li>@datetime - Current date and time (YYYY-MM-DD HH:mm:ss)
|
||||
* <li>@date{format} - Custom date/time format (e.g., @date{dd/MM/yyyy})
|
||||
* <li>@year - Current year (4 digits)
|
||||
* <li>@month - Current month (01-12)
|
||||
* <li>@day - Current day of month (01-31)
|
||||
* </ul>
|
||||
*
|
||||
* <p>Page Information:
|
||||
*
|
||||
* <ul>
|
||||
* <li>@page_number or @page - Current page number
|
||||
* <li>@total_pages or @page_count - Total number of pages
|
||||
* </ul>
|
||||
*
|
||||
* <p>File Information:
|
||||
*
|
||||
* <ul>
|
||||
* <li>@filename - Original filename (without extension)
|
||||
* <li>@filename_full - Original filename (with extension)
|
||||
* </ul>
|
||||
*
|
||||
* <p>Document Metadata:
|
||||
*
|
||||
* <ul>
|
||||
* <li>@author - Document author (from PDF metadata)
|
||||
* <li>@title - Document title (from PDF metadata)
|
||||
* <li>@subject - Document subject (from PDF metadata)
|
||||
* </ul>
|
||||
*
|
||||
* <p>Other:
|
||||
*
|
||||
* <ul>
|
||||
* <li>@uuid - Short unique identifier (8 characters)
|
||||
* </ul>
|
||||
*/
|
||||
private String processStampText(
|
||||
String stampText,
|
||||
int currentPageNumber,
|
||||
int totalPages,
|
||||
String filename,
|
||||
PDDocument document) {
|
||||
if (stampText == null || stampText.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Handle escaped @@ sequences first - replace with placeholder to preserve literal @
|
||||
String result = stampText.replace("@@", ESCAPED_AT_PLACEHOLDER);
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String currentDate = now.toLocalDate().toString();
|
||||
String currentTime = now.toLocalTime().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
String currentDateTime = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
String filenameWithoutExt = filename != null ? filename : "";
|
||||
if (filename != null && filename.contains(".")) {
|
||||
int lastDot = filename.lastIndexOf('.');
|
||||
if (lastDot > 0) { // Ensure there's actually a name before the dot
|
||||
filenameWithoutExt = filename.substring(0, lastDot);
|
||||
}
|
||||
}
|
||||
|
||||
String author = "";
|
||||
String title = "";
|
||||
String subject = "";
|
||||
if (document != null && document.getDocumentInformation() != null) {
|
||||
var info = document.getDocumentInformation();
|
||||
author = info.getAuthor() != null ? info.getAuthor() : "";
|
||||
title = info.getTitle() != null ? info.getTitle() : "";
|
||||
subject = info.getSubject() != null ? info.getSubject() : "";
|
||||
}
|
||||
|
||||
String uuid = UUID.randomUUID().toString().substring(0, 8);
|
||||
|
||||
// Process @date{format} with custom format first (must be before simple @date)
|
||||
Matcher matcher = CUSTOM_DATE_PATTERN.matcher(result);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
String format = matcher.group(1);
|
||||
String replacement = processCustomDateFormat(format, now);
|
||||
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
result = sb.toString();
|
||||
|
||||
result =
|
||||
result.replace("@datetime", currentDateTime)
|
||||
.replace("@date", currentDate)
|
||||
.replace("@time", currentTime)
|
||||
.replace("@year", String.valueOf(now.getYear()))
|
||||
.replace("@month", String.format("%02d", now.getMonthValue()))
|
||||
.replace("@day", String.format("%02d", now.getDayOfMonth()))
|
||||
.replace("@page_number", String.valueOf(currentPageNumber))
|
||||
.replace(
|
||||
"@page_count", String.valueOf(totalPages)) // Must come before @page
|
||||
.replace("@total_pages", String.valueOf(totalPages))
|
||||
.replace(
|
||||
"@page",
|
||||
String.valueOf(currentPageNumber)) // Must come after @page_count
|
||||
.replace("@filename_full", filename != null ? filename : "")
|
||||
.replace("@filename", filenameWithoutExt)
|
||||
.replace("@author", author)
|
||||
.replace("@title", title)
|
||||
.replace("@subject", subject)
|
||||
.replace("@uuid", uuid);
|
||||
|
||||
result = result.replace(ESCAPED_AT_PLACEHOLDER, "@");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String processCustomDateFormat(String format, LocalDateTime now) {
|
||||
if (format == null || format.length() > MAX_DATE_FORMAT_LENGTH) {
|
||||
return "[invalid format: too long]";
|
||||
}
|
||||
|
||||
if (!SAFE_DATE_FORMAT_PATTERN.matcher(format).matches()) {
|
||||
return "[invalid format]";
|
||||
}
|
||||
|
||||
try {
|
||||
return now.format(DateTimeFormatter.ofPattern(format));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return "[invalid format: " + format + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private void addImageStamp(
|
||||
PDPageContentStream contentStream,
|
||||
MultipartFile stampImage,
|
||||
@@ -329,8 +481,8 @@ public class StampController {
|
||||
x = overrideX;
|
||||
y = overrideY;
|
||||
} else {
|
||||
x = calculatePositionX(pageSize, position, desiredPhysicalWidth, null, 0, null, margin);
|
||||
y = calculatePositionY(pageSize, position, fontSize, margin);
|
||||
x = calculatePositionX(pageSize, position, desiredPhysicalWidth, margin);
|
||||
y = calculatePositionY(pageSize, position, desiredPhysicalHeight, margin);
|
||||
}
|
||||
|
||||
contentStream.saveGraphicsState();
|
||||
@@ -341,23 +493,14 @@ public class StampController {
|
||||
}
|
||||
|
||||
private float calculatePositionX(
|
||||
PDRectangle pageSize,
|
||||
int position,
|
||||
float contentWidth,
|
||||
PDFont font,
|
||||
float fontSize,
|
||||
String text,
|
||||
float margin)
|
||||
throws IOException {
|
||||
float actualWidth =
|
||||
(text != null) ? calculateTextWidth(text, font, fontSize) : contentWidth;
|
||||
PDRectangle pageSize, int position, float contentWidth, float margin) {
|
||||
return switch (position % 3) {
|
||||
case 1: // Left
|
||||
yield pageSize.getLowerLeftX() + margin;
|
||||
case 2: // Center
|
||||
yield (pageSize.getWidth() - actualWidth) / 2;
|
||||
yield (pageSize.getWidth() - contentWidth) / 2;
|
||||
case 0: // Right
|
||||
yield pageSize.getUpperRightX() - actualWidth - margin;
|
||||
yield pageSize.getUpperRightX() - contentWidth - margin;
|
||||
default:
|
||||
yield 0;
|
||||
};
|
||||
@@ -366,12 +509,12 @@ public class StampController {
|
||||
private float calculatePositionY(
|
||||
PDRectangle pageSize, int position, float height, float margin) {
|
||||
return switch ((position - 1) / 3) {
|
||||
case 0: // Top
|
||||
yield pageSize.getUpperRightY() - height - margin;
|
||||
case 1: // Middle
|
||||
yield (pageSize.getHeight() - height) / 2;
|
||||
case 2: // Bottom
|
||||
yield pageSize.getLowerLeftY() + margin;
|
||||
case 0: // Top - first line near the top
|
||||
yield pageSize.getUpperRightY() - margin;
|
||||
case 1: // Middle - center of text block at page center
|
||||
yield (pageSize.getHeight() + height) / 2;
|
||||
case 2: // Bottom - first line positioned so last line is at bottom margin
|
||||
yield pageSize.getLowerLeftY() + margin + height;
|
||||
default:
|
||||
yield 0;
|
||||
};
|
||||
@@ -380,8 +523,4 @@ public class StampController {
|
||||
private float calculateTextWidth(String text, PDFont font, float fontSize) throws IOException {
|
||||
return font.getStringWidth(text) / 1000 * fontSize;
|
||||
}
|
||||
|
||||
private float calculateTextCapHeight(PDFont font, float fontSize) {
|
||||
return font.getFontDescriptor().getCapHeight() / 1000 * fontSize;
|
||||
}
|
||||
}
|
||||
|
||||
+59
-6
@@ -3,6 +3,7 @@ package stirling.software.SPDF.controller.api.pipeline;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystemException;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -14,6 +15,7 @@ import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -41,14 +43,20 @@ import stirling.software.common.util.FileMonitor;
|
||||
@Slf4j
|
||||
public class PipelineDirectoryProcessor {
|
||||
|
||||
private static final int MAX_DIRECTORY_DEPTH = 50; // Prevent excessive recursion
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
private final ApiDocService apiDocService;
|
||||
private final PipelineProcessor processor;
|
||||
private final FileMonitor fileMonitor;
|
||||
private final PostHogService postHogService;
|
||||
private final String watchedFoldersDir;
|
||||
private final List<String> watchedFoldersDirs;
|
||||
private final String finishedFoldersDir;
|
||||
|
||||
// Track processed directories in current scan to prevent duplicates
|
||||
private final ThreadLocal<java.util.Set<Path>> processedDirsInScan =
|
||||
ThreadLocal.withInitial(java.util.HashSet::new);
|
||||
|
||||
public PipelineDirectoryProcessor(
|
||||
ObjectMapper objectMapper,
|
||||
ApiDocService apiDocService,
|
||||
@@ -61,13 +69,26 @@ public class PipelineDirectoryProcessor {
|
||||
this.processor = processor;
|
||||
this.fileMonitor = fileMonitor;
|
||||
this.postHogService = postHogService;
|
||||
this.watchedFoldersDir = runtimePathConfig.getPipelineWatchedFoldersPath();
|
||||
this.watchedFoldersDirs = runtimePathConfig.getPipelineWatchedFoldersPaths();
|
||||
this.finishedFoldersDir = runtimePathConfig.getPipelineFinishedFoldersPath();
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 60000)
|
||||
public void scanFolders() {
|
||||
Path watchedFolderPath = Paths.get(watchedFoldersDir).toAbsolutePath();
|
||||
// Clear the processed directories set for this scan cycle
|
||||
processedDirsInScan.get().clear();
|
||||
|
||||
try {
|
||||
for (String watchedFoldersDir : watchedFoldersDirs) {
|
||||
scanWatchedFolder(Paths.get(watchedFoldersDir).toAbsolutePath());
|
||||
}
|
||||
} finally {
|
||||
// Clean up ThreadLocal to prevent memory leaks
|
||||
processedDirsInScan.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private void scanWatchedFolder(Path watchedFolderPath) {
|
||||
if (!Files.exists(watchedFolderPath)) {
|
||||
try {
|
||||
Files.createDirectories(watchedFolderPath);
|
||||
@@ -78,16 +99,34 @@ public class PipelineDirectoryProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the path is a directory and readable
|
||||
if (!Files.isDirectory(watchedFolderPath)) {
|
||||
log.error("Path is not a directory: {}", watchedFolderPath);
|
||||
return;
|
||||
}
|
||||
if (!Files.isReadable(watchedFolderPath)) {
|
||||
log.error("Directory is not readable: {}", watchedFolderPath);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use FOLLOW_LINKS to follow symlinks, with max depth to prevent infinite loops
|
||||
Files.walkFileTree(
|
||||
watchedFolderPath,
|
||||
EnumSet.of(FileVisitOption.FOLLOW_LINKS),
|
||||
MAX_DIRECTORY_DEPTH,
|
||||
new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(
|
||||
Path dir, BasicFileAttributes attrs) {
|
||||
try {
|
||||
String dirName =
|
||||
dir.getFileName() != null
|
||||
? dir.getFileName().toString()
|
||||
: "";
|
||||
// Skip root directory and "processing" subdirectories
|
||||
if (!dir.equals(watchedFolderPath) && !dir.endsWith("processing")) {
|
||||
if (!dir.equals(watchedFolderPath)
|
||||
&& !"processing".equals(dirName)) {
|
||||
handleDirectory(dir);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -98,8 +137,11 @@ public class PipelineDirectoryProcessor {
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed(Path path, IOException exc) {
|
||||
// Handle broken symlinks or inaccessible directories
|
||||
log.error("Error accessing path: {}", path, exc);
|
||||
// Handle broken symlinks, permission issues, or inaccessible
|
||||
// directories
|
||||
if (exc != null) {
|
||||
log.debug("Cannot access path '{}': {}", path, exc.getMessage());
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
@@ -109,6 +151,17 @@ public class PipelineDirectoryProcessor {
|
||||
}
|
||||
|
||||
public void handleDirectory(Path dir) throws IOException {
|
||||
// Normalize path to absolute to prevent duplicate processing from different path
|
||||
// representations
|
||||
Path normalizedDir = dir.toAbsolutePath().normalize();
|
||||
|
||||
// Check if we've already processed this directory in this scan cycle
|
||||
java.util.Set<Path> processedDirs = processedDirsInScan.get();
|
||||
if (!processedDirs.add(normalizedDir)) {
|
||||
log.debug("Directory already processed in this scan cycle: {}", normalizedDir);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Handling directory: {}", dir);
|
||||
Path processingDir = createProcessingDirectory(dir);
|
||||
Optional<Path> jsonFileOptional = findJsonFile(dir);
|
||||
|
||||
+5
@@ -16,4 +16,9 @@ public class PdfToPdfARequest extends PDFFile {
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
allowableValues = {"pdfa", "pdfa-1", "pdfa-2", "pdfa-2b", "pdfa-3", "pdfa-3b", "pdfx"})
|
||||
private String outputFormat;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"If true, the conversion will fail if the output is not perfectly compliant")
|
||||
private Boolean strict = false;
|
||||
}
|
||||
|
||||
+8
@@ -39,6 +39,14 @@ public class AddPageNumbersRequest extends PDFWithPageNums {
|
||||
requiredMode = RequiredMode.NOT_REQUIRED)
|
||||
private String fontColor;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"Zero-padding width for page numbers (Bates Stamping). Set to 0 to disable padding",
|
||||
minimum = "0",
|
||||
defaultValue = "0",
|
||||
requiredMode = RequiredMode.NOT_REQUIRED)
|
||||
private int zeroPad = 0;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"Position: 1-9 representing positions on the page (1=top-left, 2=top-center,"
|
||||
|
||||
@@ -32,8 +32,8 @@ public class AddStampRequest extends PDFWithPageNums {
|
||||
private String alphabet = "roman";
|
||||
|
||||
@Schema(
|
||||
description = "The font size of the stamp text and image",
|
||||
defaultValue = "30",
|
||||
description = "The font size of the stamp text and image in points.",
|
||||
defaultValue = "40",
|
||||
requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private float fontSize;
|
||||
|
||||
|
||||
@@ -26,8 +26,11 @@ spring.mvc.problemdetails.enabled=true
|
||||
#logging.level.org.springframework=DEBUG
|
||||
#logging.level.org.springframework.security=DEBUG
|
||||
|
||||
spring.servlet.multipart.max-file-size=2000MB
|
||||
spring.servlet.multipart.max-request-size=2000MB
|
||||
# Multipart file size limits
|
||||
# Can be set via environment variables: SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE and SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE
|
||||
# Or via SYSTEMFILEUPLOADLIMIT/SYSTEM_MAXFILESIZE which will also set fileUploadLimit in settings.yml
|
||||
spring.servlet.multipart.max-file-size=${SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE:2000MB}
|
||||
spring.servlet.multipart.max-request-size=${SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE:2000MB}
|
||||
server.servlet.session.tracking-modes=cookie
|
||||
server.servlet.context-path=${SYSTEM_ROOTURIPATH:/}
|
||||
spring.devtools.restart.enabled=true
|
||||
|
||||
@@ -81,6 +81,7 @@ security:
|
||||
revocation:
|
||||
mode: none # Revocation checking mode: 'none' (disabled), 'ocsp' (OCSP only), 'crl' (CRL only), 'ocsp+crl' (OCSP with CRL fallback)
|
||||
hardFail: false # Fail validation if revocation status cannot be determined (true=strict, false=soft-fail)
|
||||
xFrameOptions: DENY # X-Frame-Options header value. Options: 'DENY' (default, prevents all framing), 'SAMEORIGIN' (allows framing from same domain), 'DISABLED' (no X-Frame-Options header sent). Note: automatically set to DISABLED when login is disabled
|
||||
|
||||
premium:
|
||||
key: 00000000-0000-0000-0000-000000000000
|
||||
@@ -202,7 +203,9 @@ system:
|
||||
name: postgres # set the name of your database. Should match the name of the database you create
|
||||
customPaths:
|
||||
pipeline:
|
||||
pipelineDir: "" # Defaults to /pipeline
|
||||
watchedFoldersDir: "" # Defaults to /pipeline/watchedFolders
|
||||
watchedFoldersDirs: [] # List of watched folder directories. Defaults to watchedFoldersDir or /pipeline/watchedFolders.
|
||||
finishedFoldersDir: "" # Defaults to /pipeline/finishedFolders
|
||||
operations:
|
||||
weasyprint: "" # Defaults to /opt/venv/bin/weasyprint
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
{
|
||||
"dependencies": [
|
||||
{
|
||||
"moduleName": "ch.qos.logback:logback-classic",
|
||||
"moduleUrl": "http://www.qos.ch",
|
||||
"moduleVersion": "1.5.22",
|
||||
"moduleLicense": "GNU Lesser General Public License",
|
||||
"moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "ch.qos.logback:logback-classic",
|
||||
"moduleUrl": "http://www.qos.ch",
|
||||
@@ -7,6 +14,13 @@
|
||||
"moduleLicense": "GNU Lesser General Public License",
|
||||
"moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "ch.qos.logback:logback-core",
|
||||
"moduleUrl": "http://www.qos.ch",
|
||||
"moduleVersion": "1.5.22",
|
||||
"moduleLicense": "GNU Lesser General Public License",
|
||||
"moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "ch.qos.logback:logback-core",
|
||||
"moduleUrl": "http://www.qos.ch",
|
||||
@@ -358,6 +372,13 @@
|
||||
"moduleLicense": "CDDL+GPL License",
|
||||
"moduleLicenseUrl": "http://glassfish.java.net/public/CDDL+GPL_1_1.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.sun.xml.bind:jaxb-core",
|
||||
"moduleUrl": "https://www.eclipse.org",
|
||||
"moduleVersion": "4.0.6",
|
||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.sun.xml.bind:jaxb-impl",
|
||||
"moduleUrl": "https://www.eclipse.org",
|
||||
@@ -365,6 +386,13 @@
|
||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.sun.xml.bind:jaxb-impl",
|
||||
"moduleUrl": "https://www.eclipse.org",
|
||||
"moduleVersion": "4.0.6",
|
||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.twelvemonkeys.common:common-image",
|
||||
"moduleVersion": "3.13.0",
|
||||
@@ -758,6 +786,13 @@
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "io.swagger.core.v3:swagger-annotations-jakarta",
|
||||
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-annotations",
|
||||
"moduleVersion": "2.2.42",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "io.swagger.core.v3:swagger-core-jakarta",
|
||||
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-core",
|
||||
@@ -765,6 +800,13 @@
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "io.swagger.core.v3:swagger-core-jakarta",
|
||||
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-core",
|
||||
"moduleVersion": "2.2.42",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "io.swagger.core.v3:swagger-models-jakarta",
|
||||
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-models",
|
||||
@@ -772,6 +814,13 @@
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "io.swagger.core.v3:swagger-models-jakarta",
|
||||
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-models",
|
||||
"moduleVersion": "2.2.42",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "jakarta.activation:jakarta.activation-api",
|
||||
"moduleUrl": "https://www.eclipse.org",
|
||||
@@ -898,6 +947,13 @@
|
||||
"moduleLicense": "GPL2 w/ CPE",
|
||||
"moduleLicenseUrl": "https://oss.oracle.com/licenses/CDDL+GPL-1.1"
|
||||
},
|
||||
{
|
||||
"moduleName": "javax.xml.bind:jaxb-api",
|
||||
"moduleUrl": "http://www.oracle.com/",
|
||||
"moduleVersion": "2.4.0-b180830.0359",
|
||||
"moduleLicense": "GPL2 w/ CPE",
|
||||
"moduleLicenseUrl": "https://oss.oracle.com/licenses/CDDL+GPL-1.1"
|
||||
},
|
||||
{
|
||||
"moduleName": "net.bytebuddy:byte-buddy",
|
||||
"moduleVersion": "1.17.8",
|
||||
@@ -1165,6 +1221,13 @@
|
||||
"moduleLicense": "The MIT License",
|
||||
"moduleLicenseUrl": "http://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.checkerframework:checker-qual",
|
||||
"moduleUrl": "https://checkerframework.org/",
|
||||
"moduleVersion": "3.52.0",
|
||||
"moduleLicense": "The MIT License",
|
||||
"moduleLicenseUrl": "http://opensource.org/licenses/MIT"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.commonmark:commonmark",
|
||||
"moduleVersion": "0.27.0",
|
||||
@@ -1696,6 +1759,13 @@
|
||||
"moduleLicense": "BSD-2-Clause",
|
||||
"moduleLicenseUrl": "https://jdbc.postgresql.org/about/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.postgresql:postgresql",
|
||||
"moduleUrl": "https://jdbc.postgresql.org/",
|
||||
"moduleVersion": "42.7.9",
|
||||
"moduleLicense": "BSD-2-Clause",
|
||||
"moduleLicenseUrl": "https://jdbc.postgresql.org/about/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.simplejavamail:core-module",
|
||||
"moduleVersion": "8.12.6",
|
||||
|
||||
+20
-8
@@ -85,10 +85,13 @@ class EditTableOfContentsControllerTest {
|
||||
when(mockOutlineItem.getNextSibling()).thenReturn(null);
|
||||
|
||||
// When
|
||||
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
|
||||
ResponseEntity<List<Map<String, Object>>> response =
|
||||
editTableOfContentsController.extractBookmarks(mockFile);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getBody());
|
||||
List<Map<String, Object>> result = response.getBody();
|
||||
assertEquals(1, result.size());
|
||||
|
||||
Map<String, Object> bookmark = result.get(0);
|
||||
@@ -107,10 +110,13 @@ class EditTableOfContentsControllerTest {
|
||||
when(mockCatalog.getDocumentOutline()).thenReturn(null);
|
||||
|
||||
// When
|
||||
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
|
||||
ResponseEntity<List<Map<String, Object>>> response =
|
||||
editTableOfContentsController.extractBookmarks(mockFile);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getBody());
|
||||
List<Map<String, Object>> result = response.getBody();
|
||||
assertTrue(result.isEmpty());
|
||||
verify(mockDocument).close();
|
||||
}
|
||||
@@ -141,10 +147,13 @@ class EditTableOfContentsControllerTest {
|
||||
when(childItem.getNextSibling()).thenReturn(null);
|
||||
|
||||
// When
|
||||
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
|
||||
ResponseEntity<List<Map<String, Object>>> response =
|
||||
editTableOfContentsController.extractBookmarks(mockFile);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getBody());
|
||||
List<Map<String, Object>> result = response.getBody();
|
||||
assertEquals(1, result.size());
|
||||
|
||||
Map<String, Object> parentBookmark = result.get(0);
|
||||
@@ -177,10 +186,13 @@ class EditTableOfContentsControllerTest {
|
||||
when(mockOutlineItem.getNextSibling()).thenReturn(null);
|
||||
|
||||
// When
|
||||
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
|
||||
ResponseEntity<List<Map<String, Object>>> response =
|
||||
editTableOfContentsController.extractBookmarks(mockFile);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getBody());
|
||||
List<Map<String, Object>> result = response.getBody();
|
||||
assertEquals(1, result.size());
|
||||
|
||||
Map<String, Object> bookmark = result.get(0);
|
||||
|
||||
+567
@@ -0,0 +1,567 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class StampControllerTest {
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private StampController stampController;
|
||||
|
||||
private Method processStampTextMethod;
|
||||
private Method processCustomDateFormatMethod;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws NoSuchMethodException {
|
||||
processStampTextMethod =
|
||||
StampController.class.getDeclaredMethod(
|
||||
"processStampText",
|
||||
String.class,
|
||||
int.class,
|
||||
int.class,
|
||||
String.class,
|
||||
PDDocument.class);
|
||||
processStampTextMethod.setAccessible(true);
|
||||
|
||||
processCustomDateFormatMethod =
|
||||
StampController.class.getDeclaredMethod(
|
||||
"processCustomDateFormat", String.class, LocalDateTime.class);
|
||||
processCustomDateFormatMethod.setAccessible(true);
|
||||
}
|
||||
|
||||
private String invokeProcessStampText(
|
||||
String stampText, int pageNumber, int totalPages, String filename, PDDocument document)
|
||||
throws Exception {
|
||||
try {
|
||||
return (String)
|
||||
processStampTextMethod.invoke(
|
||||
stampController, stampText, pageNumber, totalPages, filename, document);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw (Exception) e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
private String invokeProcessCustomDateFormat(String format, LocalDateTime now)
|
||||
throws Exception {
|
||||
try {
|
||||
return (String) processCustomDateFormatMethod.invoke(stampController, format, now);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw (Exception) e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Basic Variable Substitution Tests")
|
||||
class BasicVariableTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @page_number with current page")
|
||||
void testPageNumberReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("Page @page_number", 5, 20, "test.pdf", null);
|
||||
assertEquals("Page 5", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @total_pages with total page count")
|
||||
void testTotalPagesReplacement() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("of @total_pages pages", 1, 100, "test.pdf", null);
|
||||
assertEquals("of 100 pages", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace combined page variables")
|
||||
void testCombinedPageVariables() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText(
|
||||
"Page @page_number of @total_pages", 5, 20, "test.pdf", null);
|
||||
assertEquals("Page 5 of 20", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @page alias")
|
||||
void testPageAlias() throws Exception {
|
||||
String result = invokeProcessStampText("Page @page", 7, 10, "test.pdf", null);
|
||||
assertEquals("Page 7", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @page_count alias for total pages")
|
||||
void testPageCountAlias() throws Exception {
|
||||
String result = invokeProcessStampText("Total: @page_count", 1, 50, "test.pdf", null);
|
||||
assertEquals("Total: 50", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Filename Variable Tests")
|
||||
class FilenameTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @filename with filename without extension")
|
||||
void testFilenameWithoutExtension() throws Exception {
|
||||
String result = invokeProcessStampText("File: @filename", 1, 1, "document.pdf", null);
|
||||
assertEquals("File: document", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @filename_full with full filename")
|
||||
void testFilenameWithExtension() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("File: @filename_full", 1, 1, "document.pdf", null);
|
||||
assertEquals("File: document.pdf", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle filename without extension")
|
||||
void testFilenameWithoutDot() throws Exception {
|
||||
String result = invokeProcessStampText("@filename", 1, 1, "document", null);
|
||||
assertEquals("document", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle null filename")
|
||||
void testNullFilename() throws Exception {
|
||||
String result = invokeProcessStampText("File: @filename", 1, 1, null, null);
|
||||
assertEquals("File: ", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle filename with multiple dots")
|
||||
void testFilenameMultipleDots() throws Exception {
|
||||
String result = invokeProcessStampText("@filename", 1, 1, "my.document.v2.pdf", null);
|
||||
assertEquals("my.document.v2", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle hidden file (starts with dot)")
|
||||
void testHiddenFile() throws Exception {
|
||||
String result = invokeProcessStampText("@filename", 1, 1, ".hidden.pdf", null);
|
||||
assertEquals(".hidden", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Date/Time Variable Tests")
|
||||
class DateTimeTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @date with current date")
|
||||
void testDateReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("Date: @date", 1, 1, "test.pdf", null);
|
||||
assertTrue(
|
||||
result.matches("Date: \\d{4}-\\d{2}-\\d{2}"),
|
||||
"Date should match YYYY-MM-DD format");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @time with current time")
|
||||
void testTimeReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("Time: @time", 1, 1, "test.pdf", null);
|
||||
assertTrue(
|
||||
result.matches("Time: \\d{2}:\\d{2}:\\d{2}"),
|
||||
"Time should match HH:mm:ss format");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @datetime with combined date and time")
|
||||
void testDateTimeReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("@datetime", 1, 1, "test.pdf", null);
|
||||
// DateTime format: YYYY-MM-DD HH:mm:ss
|
||||
assertTrue(
|
||||
result.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}"),
|
||||
"DateTime should match YYYY-MM-DD HH:mm:ss format");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @year with current year")
|
||||
void testYearReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("© @year", 1, 1, "test.pdf", null);
|
||||
int currentYear = LocalDateTime.now().getYear();
|
||||
assertEquals("© " + currentYear, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @month with zero-padded month")
|
||||
void testMonthReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("Month: @month", 1, 1, "test.pdf", null);
|
||||
assertTrue(result.matches("Month: \\d{2}"), "Month should be zero-padded");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @day with zero-padded day")
|
||||
void testDayReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("Day: @day", 1, 1, "test.pdf", null);
|
||||
assertTrue(result.matches("Day: \\d{2}"), "Day should be zero-padded");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Custom Date Format Tests")
|
||||
class CustomDateFormatTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle custom date format dd/MM/yyyy")
|
||||
void testCustomDateFormatSlash() throws Exception {
|
||||
String result = invokeProcessStampText("@date{dd/MM/yyyy}", 1, 1, "test.pdf", null);
|
||||
assertTrue(
|
||||
result.matches("\\d{2}/\\d{2}/\\d{4}"),
|
||||
"Should match dd/MM/yyyy format: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle custom date format with time")
|
||||
void testCustomDateFormatWithTime() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("@date{yyyy-MM-dd HH:mm}", 1, 1, "test.pdf", null);
|
||||
assertTrue(
|
||||
result.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}"),
|
||||
"Should match yyyy-MM-dd HH:mm format: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle multiple custom date formats in same text")
|
||||
void testMultipleCustomDateFormats() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText(
|
||||
"Start: @date{dd/MM/yyyy} End: @date{yyyy}", 1, 1, "test.pdf", null);
|
||||
assertTrue(result.contains("/"), "Should contain slash from first format");
|
||||
// Should have year twice (once with slashes, once alone)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Custom Date Format Security Tests")
|
||||
class CustomDateFormatSecurityTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should not match format that is too long - regex won't capture it")
|
||||
void testFormatTooLong() throws Exception {
|
||||
String longFormat = "y".repeat(51); // 51 chars, over the 50 char regex limit
|
||||
String result =
|
||||
invokeProcessStampText("@date{" + longFormat + "}", 1, 1, "test.pdf", null);
|
||||
// The CUSTOM_DATE_PATTERN only captures up to 50 chars, so this won't match
|
||||
// The @date part will be replaced by simple replacement, leaving {yyy...}
|
||||
assertTrue(
|
||||
result.contains("{"), "Should contain { because regex didn't match: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should reject format with unsafe characters - shell injection attempt")
|
||||
void testShellInjectionAttempt() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("@date{yyyy-MM-dd$(rm -rf /)}", 1, 1, "test.pdf", null);
|
||||
assertEquals("[invalid format]", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should reject format with unsafe characters - semicolon")
|
||||
void testSemicolonInjection() throws Exception {
|
||||
String result = invokeProcessStampText("@date{yyyy;rm}", 1, 1, "test.pdf", null);
|
||||
assertEquals("[invalid format]", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should reject format with unsafe characters - backticks")
|
||||
void testBacktickInjection() throws Exception {
|
||||
String result = invokeProcessStampText("@date{`whoami`}", 1, 1, "test.pdf", null);
|
||||
assertEquals("[invalid format]", result);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"$(cmd)", "`cmd`", ";cmd", "|cmd", "&cmd", "<cmd", ">cmd"})
|
||||
@DisplayName("Should reject various injection attempts")
|
||||
void testVariousInjectionAttempts(String injection) throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("@date{yyyy" + injection + "}", 1, 1, "test.pdf", null);
|
||||
assertEquals("[invalid format]", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should accept valid format characters")
|
||||
void testValidFormatCharacters() throws Exception {
|
||||
// All these should be valid based on SAFE_DATE_FORMAT_PATTERN: yMdHhmsS/-:.,
|
||||
// '+EGuwWDFzZXa and space
|
||||
String result =
|
||||
invokeProcessStampText("@date{yyyy-MM-dd HH:mm:ss}", 1, 1, "test.pdf", null);
|
||||
assertFalse(
|
||||
result.startsWith("[invalid"), "Valid format should be accepted: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle invalid DateTimeFormatter pattern gracefully")
|
||||
void testInvalidFormatterPattern() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// Use 'sssss' - too many seconds digits will throw IllegalArgumentException from
|
||||
// DateTimeFormatter
|
||||
// Note: The pattern 'sssss' passes the SAFE_DATE_FORMAT_PATTERN but fails
|
||||
// DateTimeFormatter.ofPattern()
|
||||
String result = invokeProcessCustomDateFormat("sssss", now);
|
||||
assertTrue(
|
||||
result.startsWith("[invalid format:"),
|
||||
"Invalid pattern should return error message: " + result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Escape Sequence Tests")
|
||||
class EscapeSequenceTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should convert @@ to literal @")
|
||||
void testDoubleAtEscape() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("Email: test@@example.com", 1, 1, "test.pdf", null);
|
||||
assertEquals("Email: test@example.com", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should preserve @@ before variable")
|
||||
void testEscapeBeforeVariable() throws Exception {
|
||||
String result = invokeProcessStampText("@@date is @date", 1, 1, "test.pdf", null);
|
||||
// @@date should become @date, and @date should be replaced with actual date
|
||||
assertTrue(result.startsWith("@date is "), "Should start with literal @date");
|
||||
assertTrue(
|
||||
result.matches("@date is \\d{4}-\\d{2}-\\d{2}"),
|
||||
"Should have date after: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle multiple escape sequences")
|
||||
void testMultipleEscapes() throws Exception {
|
||||
String result = invokeProcessStampText("@@one @@two @@three", 1, 1, "test.pdf", null);
|
||||
assertEquals("@one @two @three", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle escape at end of string")
|
||||
void testEscapeAtEnd() throws Exception {
|
||||
String result = invokeProcessStampText("Contact: user@@", 1, 1, "test.pdf", null);
|
||||
assertEquals("Contact: user@", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Document Metadata Tests")
|
||||
class DocumentMetadataTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @author with document author")
|
||||
void testAuthorReplacement() throws Exception {
|
||||
PDDocument doc = new PDDocument();
|
||||
PDDocumentInformation info = new PDDocumentInformation();
|
||||
info.setAuthor("John Doe");
|
||||
doc.setDocumentInformation(info);
|
||||
|
||||
try {
|
||||
String result = invokeProcessStampText("Author: @author", 1, 1, "test.pdf", doc);
|
||||
assertEquals("Author: John Doe", result);
|
||||
} finally {
|
||||
doc.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @title with document title")
|
||||
void testTitleReplacement() throws Exception {
|
||||
PDDocument doc = new PDDocument();
|
||||
PDDocumentInformation info = new PDDocumentInformation();
|
||||
info.setTitle("My Document Title");
|
||||
doc.setDocumentInformation(info);
|
||||
|
||||
try {
|
||||
String result = invokeProcessStampText("Title: @title", 1, 1, "test.pdf", doc);
|
||||
assertEquals("Title: My Document Title", result);
|
||||
} finally {
|
||||
doc.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @subject with document subject")
|
||||
void testSubjectReplacement() throws Exception {
|
||||
PDDocument doc = new PDDocument();
|
||||
PDDocumentInformation info = new PDDocumentInformation();
|
||||
info.setSubject("Important Subject");
|
||||
doc.setDocumentInformation(info);
|
||||
|
||||
try {
|
||||
String result = invokeProcessStampText("Subject: @subject", 1, 1, "test.pdf", doc);
|
||||
assertEquals("Subject: Important Subject", result);
|
||||
} finally {
|
||||
doc.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle null metadata gracefully")
|
||||
void testNullMetadata() throws Exception {
|
||||
PDDocument doc = new PDDocument();
|
||||
// Don't set any document information
|
||||
|
||||
try {
|
||||
String result =
|
||||
invokeProcessStampText("@author @title @subject", 1, 1, "test.pdf", doc);
|
||||
assertEquals(" ", result); // All should be empty strings
|
||||
} finally {
|
||||
doc.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle null document gracefully")
|
||||
void testNullDocument() throws Exception {
|
||||
String result = invokeProcessStampText("Author: @author", 1, 1, "test.pdf", null);
|
||||
assertEquals("Author: ", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("UUID Variable Tests")
|
||||
class UuidTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should generate 8-character UUID")
|
||||
void testUuidLength() throws Exception {
|
||||
String result = invokeProcessStampText("ID: @uuid", 1, 1, "test.pdf", null);
|
||||
// UUID format: "ID: " + 8 chars
|
||||
assertEquals(12, result.length(), "Should be 'ID: ' + 8 char UUID");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should generate different UUIDs for each call")
|
||||
void testUuidUniqueness() throws Exception {
|
||||
String result1 = invokeProcessStampText("@uuid", 1, 1, "test.pdf", null);
|
||||
String result2 = invokeProcessStampText("@uuid", 1, 1, "test.pdf", null);
|
||||
assertNotEquals(result1, result2, "UUIDs should be unique");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("UUID should contain only hex characters")
|
||||
void testUuidFormat() throws Exception {
|
||||
String result = invokeProcessStampText("@uuid", 1, 1, "test.pdf", null);
|
||||
assertTrue(result.matches("[0-9a-f]{8}"), "UUID should be 8 hex characters: " + result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Edge Cases and Error Handling")
|
||||
class EdgeCaseTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle null stamp text")
|
||||
void testNullStampText() throws Exception {
|
||||
String result = invokeProcessStampText(null, 1, 1, "test.pdf", null);
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle empty stamp text")
|
||||
void testEmptyStampText() throws Exception {
|
||||
String result = invokeProcessStampText("", 1, 1, "test.pdf", null);
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle text with no variables")
|
||||
void testNoVariables() throws Exception {
|
||||
String result = invokeProcessStampText("Just plain text", 1, 1, "test.pdf", null);
|
||||
assertEquals("Just plain text", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle unknown variables")
|
||||
void testUnknownVariable() throws Exception {
|
||||
String result = invokeProcessStampText("@unknown_var", 1, 1, "test.pdf", null);
|
||||
assertEquals("@unknown_var", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should preserve text around variables")
|
||||
void testPreservesSurroundingText() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("Before @page_number After", 5, 10, "test.pdf", null);
|
||||
assertEquals("Before 5 After", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle multiple same variables")
|
||||
void testMultipleSameVariables() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("@page_number / @page_number", 3, 10, "test.pdf", null);
|
||||
assertEquals("3 / 3", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle variables adjacent to each other")
|
||||
void testAdjacentVariables() throws Exception {
|
||||
String result = invokeProcessStampText("@page@page_number", 5, 10, "test.pdf", null);
|
||||
// @page should be replaced first (it's in the order), then @page_number
|
||||
// Since @page_number is longer and comes first in replace chain, should work
|
||||
assertEquals("55", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Complex Scenario Tests")
|
||||
class ComplexScenarioTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle legal footer template")
|
||||
void testLegalFooterTemplate() throws Exception {
|
||||
String template = "© @year - All Rights Reserved\\n@filename - Page @page_number";
|
||||
String result = invokeProcessStampText(template, 3, 15, "contract.pdf", null);
|
||||
|
||||
int year = LocalDateTime.now().getYear();
|
||||
String expected = "© " + year + " - All Rights Reserved\\ncontract - Page 3";
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle Brazilian date format template")
|
||||
void testBrazilianDateFormat() throws Exception {
|
||||
String template = "Documento criado em @date{dd/MM/yyyy} às @time";
|
||||
String result = invokeProcessStampText(template, 1, 1, "doc.pdf", null);
|
||||
|
||||
assertTrue(result.startsWith("Documento criado em "));
|
||||
assertTrue(result.contains("/"));
|
||||
assertTrue(result.contains(":"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"'Page @page_number of @total_pages', 1, 10, 'Page 1 of 10'",
|
||||
"'Page @page_number of @total_pages', 5, 20, 'Page 5 of 20'",
|
||||
"'Page @page_number of @total_pages', 100, 1000, 'Page 100 of 1000'"
|
||||
})
|
||||
@DisplayName("Should handle page number template with various values")
|
||||
void testPageNumberTemplates(String template, int page, int total, String expected)
|
||||
throws Exception {
|
||||
String result = invokeProcessStampText(template, page, total, "test.pdf", null);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ dependencies {
|
||||
api 'org.springframework.boot:spring-boot-starter-mail'
|
||||
api 'org.springframework.boot:spring-boot-starter-cache'
|
||||
api 'com.github.ben-manes.caffeine:caffeine'
|
||||
api 'io.swagger.core.v3:swagger-core-jakarta:2.2.41'
|
||||
api 'io.swagger.core.v3:swagger-core-jakarta:2.2.42'
|
||||
implementation 'com.bucket4j:bucket4j_jdk17-core:8.15.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
||||
@@ -62,7 +62,7 @@ dependencies {
|
||||
runtimeOnly "io.jsonwebtoken:jjwt-impl:$jwtVersion"
|
||||
runtimeOnly "io.jsonwebtoken:jjwt-jackson:$jwtVersion"
|
||||
runtimeOnly 'com.h2database:h2:2.3.232' // Don't upgrade h2database
|
||||
runtimeOnly 'org.postgresql:postgresql:42.7.8'
|
||||
runtimeOnly 'org.postgresql:postgresql:42.7.9'
|
||||
constraints {
|
||||
implementation "org.opensaml:opensaml-core:$openSamlVersion"
|
||||
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
|
||||
|
||||
+3
-3
@@ -139,6 +139,7 @@ public class ProprietaryUIDataController {
|
||||
|
||||
// Add enableLogin flag so frontend doesn't need to call /app-config
|
||||
data.setEnableLogin(securityProps.isEnableLogin());
|
||||
data.setSsoAutoLogin(applicationProperties.getPremium().getProFeatures().isSsoAutoLogin());
|
||||
|
||||
// Check if this is first-time setup with default credentials
|
||||
// The isFirstLogin flag captures: default username/password usage and unchanged state
|
||||
@@ -218,9 +219,7 @@ public class ProprietaryUIDataController {
|
||||
String backendUrl = getBackendBaseUrl();
|
||||
String fullSamlPath = backendUrl + saml2AuthenticationPath;
|
||||
|
||||
if (!applicationProperties.getPremium().getProFeatures().isSsoAutoLogin()) {
|
||||
providerList.put(fullSamlPath, samlIdp + " (SAML 2)");
|
||||
}
|
||||
providerList.put(fullSamlPath, samlIdp + " (SAML 2)");
|
||||
}
|
||||
|
||||
// Remove null entries
|
||||
@@ -533,6 +532,7 @@ public class ProprietaryUIDataController {
|
||||
@Data
|
||||
public static class LoginData {
|
||||
private Boolean enableLogin;
|
||||
private boolean ssoAutoLogin;
|
||||
private Map<String, String> providerList;
|
||||
private String loginMethod;
|
||||
private boolean altLogin;
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@ public class PersistentAuditEvent {
|
||||
private String principal;
|
||||
private String type;
|
||||
|
||||
@Lob private String data; // JSON blob
|
||||
@Column(columnDefinition = "text")
|
||||
private String data; // JSON blob
|
||||
|
||||
private Instant timestamp;
|
||||
}
|
||||
|
||||
+54
-3
@@ -8,6 +8,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.authentication.ProviderManager;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
@@ -185,11 +186,39 @@ public class SecurityConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
public SecurityFilterChain samlFilterChain(
|
||||
HttpSecurity http,
|
||||
@Lazy IPRateLimitingFilter rateLimitingFilter,
|
||||
@Lazy JwtAuthenticationFilter jwtAuthenticationFilter)
|
||||
throws Exception {
|
||||
http.securityMatcher("/saml2/**", "/login/saml2/**");
|
||||
|
||||
SessionCreationPolicy sessionPolicy =
|
||||
(securityProperties.isSaml2Active() && runningProOrHigher)
|
||||
? SessionCreationPolicy.IF_REQUIRED
|
||||
: SessionCreationPolicy.STATELESS;
|
||||
|
||||
return configureSecurity(http, rateLimitingFilter, jwtAuthenticationFilter, sessionPolicy);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
public SecurityFilterChain filterChain(
|
||||
HttpSecurity http,
|
||||
@Lazy IPRateLimitingFilter rateLimitingFilter,
|
||||
@Lazy JwtAuthenticationFilter jwtAuthenticationFilter)
|
||||
throws Exception {
|
||||
SessionCreationPolicy sessionPolicy = SessionCreationPolicy.STATELESS;
|
||||
return configureSecurity(http, rateLimitingFilter, jwtAuthenticationFilter, sessionPolicy);
|
||||
}
|
||||
|
||||
private SecurityFilterChain configureSecurity(
|
||||
HttpSecurity http,
|
||||
@Lazy IPRateLimitingFilter rateLimitingFilter,
|
||||
@Lazy JwtAuthenticationFilter jwtAuthenticationFilter,
|
||||
SessionCreationPolicy sessionPolicy)
|
||||
throws Exception {
|
||||
// Enable CORS only if we have configured origins
|
||||
CorsConfigurationSource corsSource = corsConfigurationSource();
|
||||
if (corsSource != null) {
|
||||
@@ -201,6 +230,30 @@ public class SecurityConfiguration {
|
||||
|
||||
http.csrf(CsrfConfigurer::disable);
|
||||
|
||||
// Configure X-Frame-Options based on settings.yml configuration
|
||||
// When login is disabled, automatically disable X-Frame-Options to allow embedding
|
||||
if (!loginEnabledValue) {
|
||||
http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.disable()));
|
||||
} else {
|
||||
String xFrameOption = securityProperties.getXFrameOptions();
|
||||
if (xFrameOption != null) {
|
||||
http.headers(
|
||||
headers -> {
|
||||
if ("DISABLED".equalsIgnoreCase(xFrameOption)) {
|
||||
headers.frameOptions(frameOptions -> frameOptions.disable());
|
||||
} else if ("SAMEORIGIN".equalsIgnoreCase(xFrameOption)) {
|
||||
headers.frameOptions(frameOptions -> frameOptions.sameOrigin());
|
||||
} else {
|
||||
// Default to DENY
|
||||
headers.frameOptions(frameOptions -> frameOptions.deny());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If not configured, use default DENY
|
||||
http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.deny()));
|
||||
}
|
||||
}
|
||||
|
||||
if (loginEnabledValue) {
|
||||
|
||||
http.addFilterBefore(
|
||||
@@ -209,9 +262,7 @@ public class SecurityConfiguration {
|
||||
.addFilterBefore(jwtAuthenticationFilter, UserAuthenticationFilter.class);
|
||||
|
||||
http.sessionManagement(
|
||||
sessionManagement ->
|
||||
sessionManagement.sessionCreationPolicy(
|
||||
SessionCreationPolicy.STATELESS));
|
||||
sessionManagement -> sessionManagement.sessionCreationPolicy(sessionPolicy));
|
||||
http.authenticationProvider(daoAuthenticationProvider());
|
||||
http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()));
|
||||
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package stirling.software.proprietary.security.configuration.ee;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import stirling.software.common.service.LicenseServiceInterface;
|
||||
import stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License;
|
||||
|
||||
/**
|
||||
* Service that provides dynamic license checking instead of cached beans. This ensures that when
|
||||
* admins update the license key, the changes are immediately reflected in the UI and config
|
||||
* endpoints without requiring a restart.
|
||||
*
|
||||
* <p>Note: Some components (EnterpriseEndpointAspect, PremiumEndpointAspect, filters) still inject
|
||||
* cached beans at startup for performance. These will require a restart to reflect license changes.
|
||||
* This is acceptable because: 1. Most deployments add licenses during initial setup 2. License
|
||||
* changes in production typically warrant a restart anyway 3. UI reflects changes immediately
|
||||
* (banner disappears, license status updates)
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DynamicLicenseService implements LicenseServiceInterface {
|
||||
|
||||
private final LicenseKeyChecker licenseKeyChecker;
|
||||
|
||||
/**
|
||||
* Get the current license type dynamically (not cached).
|
||||
*
|
||||
* @return Current license: NORMAL, SERVER, or ENTERPRISE
|
||||
*/
|
||||
public License getCurrentLicense() {
|
||||
return licenseKeyChecker.getPremiumLicenseEnabledResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunningProOrHigher() {
|
||||
License license = getCurrentLicense();
|
||||
return license == License.SERVER || license == License.ENTERPRISE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunningEE() {
|
||||
return getCurrentLicense() == License.ENTERPRISE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLicenseTypeName() {
|
||||
return getCurrentLicense().name();
|
||||
}
|
||||
}
|
||||
+14
-2
@@ -309,10 +309,16 @@ public class AdminLicenseController {
|
||||
}
|
||||
|
||||
try {
|
||||
log.info(
|
||||
"License upload: original filename='{}', size={} bytes, contentType='{}'",
|
||||
file.getOriginalFilename(),
|
||||
file.getSize(),
|
||||
file.getContentType());
|
||||
// Validate certificate format by reading content
|
||||
byte[] fileBytes = file.getBytes();
|
||||
String content = new String(fileBytes, StandardCharsets.UTF_8);
|
||||
if (!content.trim().startsWith("-----BEGIN LICENSE FILE-----")) {
|
||||
log.warn("License upload rejected: invalid certificate header");
|
||||
return ResponseEntity.badRequest()
|
||||
.body(
|
||||
Map.of(
|
||||
@@ -324,9 +330,15 @@ public class AdminLicenseController {
|
||||
|
||||
// Get config directory and target path
|
||||
Path configPath = Paths.get(InstallationPathConfig.getConfigPath());
|
||||
Path targetPath = configPath.resolve(filename).normalize();
|
||||
Path configPathAbs = configPath.toAbsolutePath().normalize();
|
||||
Path targetPath = configPathAbs.resolve(filename).normalize();
|
||||
log.info(
|
||||
"License upload paths: configPath='{}', targetPath='{}'",
|
||||
configPathAbs,
|
||||
targetPath.toAbsolutePath());
|
||||
// Prevent directory traversal: ensure targetPath is inside configPath
|
||||
if (!targetPath.startsWith(configPath.normalize().toAbsolutePath())) {
|
||||
if (!targetPath.startsWith(configPathAbs)) {
|
||||
log.warn("License upload rejected: target path outside config path");
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("success", false, "error", "Invalid file path"));
|
||||
}
|
||||
|
||||
+55
@@ -186,6 +186,13 @@ public class AdminSettingsController {
|
||||
+ HtmlUtils.htmlEscape(key)));
|
||||
}
|
||||
|
||||
// Validate pipeline path settings
|
||||
String validationError = validatePipelinePathSetting(key, value);
|
||||
if (validationError != null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("error", HtmlUtils.htmlEscape(validationError)));
|
||||
}
|
||||
|
||||
log.info("Admin updating setting: {} = {}", key, value);
|
||||
GeneralUtils.saveKeyToSettings(key, value);
|
||||
|
||||
@@ -642,6 +649,54 @@ public class AdminSettingsController {
|
||||
return true;
|
||||
}
|
||||
|
||||
private String validatePipelinePathSetting(String key, Object value) {
|
||||
// Validate pipeline path settings
|
||||
if (key.startsWith("system.customPaths.pipeline.watchedFoldersDirs")
|
||||
&& value instanceof java.util.List) {
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.List<String> paths = (java.util.List<String>) value;
|
||||
|
||||
// Check for empty or all-blank paths
|
||||
if (paths.isEmpty()) {
|
||||
return null; // Empty is OK, will use default
|
||||
}
|
||||
|
||||
// Validate each path
|
||||
java.util.Set<String> normalizedPaths = new java.util.HashSet<>();
|
||||
for (String path : paths) {
|
||||
if (path != null && !path.trim().isEmpty()) {
|
||||
try {
|
||||
java.nio.file.Path normalized =
|
||||
java.nio.file.Paths.get(path.trim()).toAbsolutePath().normalize();
|
||||
String normalizedStr = normalized.toString();
|
||||
|
||||
// Check for duplicates
|
||||
if (normalizedPaths.contains(normalizedStr)) {
|
||||
return "Duplicate path detected: " + path;
|
||||
}
|
||||
normalizedPaths.add(normalizedStr);
|
||||
} catch (java.nio.file.InvalidPathException e) {
|
||||
return "Invalid path: " + path + " - " + e.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for overlapping paths
|
||||
java.util.List<String> pathList = new java.util.ArrayList<>(normalizedPaths);
|
||||
for (int i = 0; i < pathList.size(); i++) {
|
||||
java.nio.file.Path path1 = java.nio.file.Paths.get(pathList.get(i));
|
||||
for (int j = i + 1; j < pathList.size(); j++) {
|
||||
java.nio.file.Path path2 = java.nio.file.Paths.get(pathList.get(j));
|
||||
if (path1.startsWith(path2) || path2.startsWith(path1)) {
|
||||
return "Overlapping paths detected: " + path1 + " and " + path2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // Valid
|
||||
}
|
||||
|
||||
private Object getSettingByKey(String key) {
|
||||
if (key == null || key.trim().isEmpty()) {
|
||||
return null;
|
||||
|
||||
+4
-1
@@ -286,7 +286,10 @@ public class AuthController {
|
||||
|
||||
log.debug("Token refreshed for user: {}", username);
|
||||
|
||||
return ResponseEntity.ok(Map.of("access_token", newToken, "expires_in", 3600));
|
||||
return ResponseEntity.ok(
|
||||
Map.of(
|
||||
"user", buildUserResponse(user),
|
||||
"session", Map.of("access_token", newToken, "expires_in", 3600)));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Token refresh error", e);
|
||||
|
||||
+18
-17
@@ -69,27 +69,28 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
if (!apiKeyExists(request, response)) {
|
||||
String jwtToken = jwtService.extractToken(request);
|
||||
|
||||
if (jwtToken == null) {
|
||||
// Allow auth endpoints to pass through without JWT
|
||||
if (!isPublicAuthEndpoint(requestURI, contextPath)) {
|
||||
// For API requests, return 401 JSON
|
||||
String acceptHeader = request.getHeader("Accept");
|
||||
if (requestURI.startsWith(contextPath + "/api/")
|
||||
|| (acceptHeader != null
|
||||
&& acceptHeader.contains("application/json"))) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().write("{\"error\":\"Authentication required\"}");
|
||||
return;
|
||||
}
|
||||
// Check if this is a public endpoint BEFORE validating JWT
|
||||
// This allows public endpoints to work even with expired tokens in the request
|
||||
if (isPublicAuthEndpoint(requestURI, contextPath)) {
|
||||
// For public auth endpoints, skip JWT validation and continue
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// For HTML requests (SPA routes), let React Router handle it (serve
|
||||
// index.html)
|
||||
filterChain.doFilter(request, response);
|
||||
if (jwtToken == null) {
|
||||
// No JWT token and not a public endpoint
|
||||
// For API requests, return 401 JSON
|
||||
String acceptHeader = request.getHeader("Accept");
|
||||
if (requestURI.startsWith(contextPath + "/api/")
|
||||
|| (acceptHeader != null && acceptHeader.contains("application/json"))) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().write("{\"error\":\"Authentication required\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
// For public auth endpoints without JWT, continue to the endpoint
|
||||
// For HTML requests (SPA routes), let React Router handle it (serve
|
||||
// index.html)
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,6 @@ public class User implements UserDetails, Serializable {
|
||||
|
||||
@ElementCollection
|
||||
@MapKeyColumn(name = "setting_key")
|
||||
@Lob
|
||||
@Column(name = "setting_value", columnDefinition = "text")
|
||||
@CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "user_id"))
|
||||
@JsonIgnore
|
||||
|
||||
+40
@@ -17,6 +17,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.proprietary.audit.AuditEventType;
|
||||
import stirling.software.proprietary.audit.AuditLevel;
|
||||
import stirling.software.proprietary.audit.Audited;
|
||||
import stirling.software.proprietary.security.oauth2.TauriOAuthUtils;
|
||||
|
||||
@Slf4j
|
||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||
@@ -33,9 +34,33 @@ public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthentica
|
||||
|
||||
if (exception instanceof Saml2AuthenticationException) {
|
||||
Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error();
|
||||
if (TauriSamlUtils.isTauriRelayState(request)) {
|
||||
String redirectUrl =
|
||||
TauriOAuthUtils.defaultTauriCallbackPath(request.getContextPath());
|
||||
String nonce = TauriSamlUtils.extractNonceFromRequest(request);
|
||||
if (nonce != null) {
|
||||
redirectUrl = appendQueryParam(redirectUrl, "nonce", nonce);
|
||||
}
|
||||
redirectUrl = appendQueryParam(redirectUrl, "errorOAuth", error.getErrorCode());
|
||||
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
|
||||
return;
|
||||
}
|
||||
getRedirectStrategy()
|
||||
.sendRedirect(request, response, "/login?errorOAuth=" + error.getErrorCode());
|
||||
} else if (exception instanceof ProviderNotFoundException) {
|
||||
if (TauriSamlUtils.isTauriRelayState(request)) {
|
||||
String redirectUrl =
|
||||
TauriOAuthUtils.defaultTauriCallbackPath(request.getContextPath());
|
||||
String nonce = TauriSamlUtils.extractNonceFromRequest(request);
|
||||
if (nonce != null) {
|
||||
redirectUrl = appendQueryParam(redirectUrl, "nonce", nonce);
|
||||
}
|
||||
redirectUrl =
|
||||
appendQueryParam(
|
||||
redirectUrl, "errorOAuth", "not_authentication_provider_found");
|
||||
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
|
||||
return;
|
||||
}
|
||||
getRedirectStrategy()
|
||||
.sendRedirect(
|
||||
request,
|
||||
@@ -43,4 +68,19 @@ public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthentica
|
||||
"/login?errorOAuth=not_authentication_provider_found");
|
||||
}
|
||||
}
|
||||
|
||||
private String appendQueryParam(String path, String key, String value) {
|
||||
if (path == null || path.isBlank()) {
|
||||
return path;
|
||||
}
|
||||
String separator = path.contains("?") ? "&" : "?";
|
||||
String encodedKey =
|
||||
java.net.URLEncoder.encode(key, java.nio.charset.StandardCharsets.UTF_8);
|
||||
String encodedValue =
|
||||
value == null
|
||||
? ""
|
||||
: java.net.URLEncoder.encode(
|
||||
value, java.nio.charset.StandardCharsets.UTF_8);
|
||||
return path + separator + encodedKey + "=" + encodedValue;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-1
@@ -33,6 +33,7 @@ import stirling.software.proprietary.audit.AuditEventType;
|
||||
import stirling.software.proprietary.audit.AuditLevel;
|
||||
import stirling.software.proprietary.audit.Audited;
|
||||
import stirling.software.proprietary.security.model.AuthenticationType;
|
||||
import stirling.software.proprietary.security.oauth2.TauriOAuthUtils;
|
||||
import stirling.software.proprietary.security.service.JwtServiceInterface;
|
||||
import stirling.software.proprietary.security.service.LoginAttemptService;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
@@ -233,7 +234,16 @@ public class CustomSaml2AuthenticationSuccessHandler
|
||||
String redirectPath = resolveRedirectPath(request, contextPath);
|
||||
String origin = resolveOrigin(request);
|
||||
clearRedirectCookie(response);
|
||||
return origin + redirectPath + "#access_token=" + jwt;
|
||||
String url = origin + redirectPath + "#access_token=" + jwt;
|
||||
|
||||
String nonce = TauriSamlUtils.extractNonceFromRequest(request);
|
||||
if (nonce != null) {
|
||||
url +=
|
||||
"&nonce="
|
||||
+ java.net.URLEncoder.encode(
|
||||
nonce, java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,6 +266,9 @@ public class CustomSaml2AuthenticationSuccessHandler
|
||||
}
|
||||
|
||||
private String resolveRedirectPath(HttpServletRequest request, String contextPath) {
|
||||
if (TauriSamlUtils.isTauriRelayState(request)) {
|
||||
return TauriOAuthUtils.defaultTauriCallbackPath(contextPath);
|
||||
}
|
||||
return extractRedirectPathFromCookie(request)
|
||||
.filter(path -> path.startsWith("/"))
|
||||
.orElseGet(() -> defaultCallbackPath(contextPath));
|
||||
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
package stirling.software.proprietary.security.saml2;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.proprietary.security.service.JwtServiceInterface;
|
||||
|
||||
@Slf4j
|
||||
public class JwtSaml2AuthenticationRequestRepository
|
||||
implements Saml2AuthenticationRequestRepository<Saml2PostAuthenticationRequest> {
|
||||
private final Map<String, String> tokenStore;
|
||||
private final JwtServiceInterface jwtService;
|
||||
private final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||
|
||||
private static final String SAML_REQUEST_TOKEN = "stirling_saml_request_token";
|
||||
|
||||
public JwtSaml2AuthenticationRequestRepository(
|
||||
Map<String, String> tokenStore,
|
||||
JwtServiceInterface jwtService,
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
||||
this.tokenStore = tokenStore;
|
||||
this.jwtService = jwtService;
|
||||
this.relyingPartyRegistrationRepository = relyingPartyRegistrationRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAuthenticationRequest(
|
||||
Saml2PostAuthenticationRequest authRequest,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
if (!jwtService.isJwtEnabled()) {
|
||||
log.debug("V2 is not enabled, skipping SAMLRequest token storage");
|
||||
return;
|
||||
}
|
||||
|
||||
if (authRequest == null) {
|
||||
removeAuthenticationRequest(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> claims = serializeSamlRequest(authRequest);
|
||||
String token = jwtService.generateToken("", claims);
|
||||
String relayState = authRequest.getRelayState();
|
||||
|
||||
tokenStore.put(relayState, token);
|
||||
request.setAttribute(SAML_REQUEST_TOKEN, relayState);
|
||||
response.addHeader(SAML_REQUEST_TOKEN, relayState);
|
||||
|
||||
log.debug("Saved SAMLRequest token with RelayState: {}", relayState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Saml2PostAuthenticationRequest loadAuthenticationRequest(HttpServletRequest request) {
|
||||
String token = extractTokenFromStore(request);
|
||||
|
||||
if (token == null) {
|
||||
log.debug("No SAMLResponse token found in RelayState");
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> claims = jwtService.extractClaims(token);
|
||||
return deserializeSamlRequest(claims);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Saml2PostAuthenticationRequest removeAuthenticationRequest(
|
||||
HttpServletRequest request, HttpServletResponse response) {
|
||||
Saml2PostAuthenticationRequest authRequest = loadAuthenticationRequest(request);
|
||||
|
||||
String relayStateId = request.getParameter("RelayState");
|
||||
if (relayStateId != null) {
|
||||
tokenStore.remove(relayStateId);
|
||||
log.debug("Removed SAMLRequest token for RelayState ID: {}", relayStateId);
|
||||
}
|
||||
|
||||
return authRequest;
|
||||
}
|
||||
|
||||
private String extractTokenFromStore(HttpServletRequest request) {
|
||||
String authnRequestId = request.getParameter("RelayState");
|
||||
|
||||
if (authnRequestId != null && !authnRequestId.isEmpty()) {
|
||||
String token = tokenStore.get(authnRequestId);
|
||||
|
||||
if (token != null) {
|
||||
tokenStore.remove(authnRequestId);
|
||||
log.debug("Retrieved SAMLRequest token for RelayState ID: {}", authnRequestId);
|
||||
return token;
|
||||
} else {
|
||||
log.warn("No SAMLRequest token found for RelayState ID: {}", authnRequestId);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, Object> serializeSamlRequest(Saml2PostAuthenticationRequest authRequest) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
|
||||
claims.put("id", authRequest.getId());
|
||||
claims.put("relyingPartyRegistrationId", authRequest.getRelyingPartyRegistrationId());
|
||||
claims.put("authenticationRequestUri", authRequest.getAuthenticationRequestUri());
|
||||
claims.put("samlRequest", authRequest.getSamlRequest());
|
||||
claims.put("relayState", authRequest.getRelayState());
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
private Saml2PostAuthenticationRequest deserializeSamlRequest(Map<String, Object> claims) {
|
||||
String relyingPartyRegistrationId = (String) claims.get("relyingPartyRegistrationId");
|
||||
RelyingPartyRegistration relyingPartyRegistration =
|
||||
relyingPartyRegistrationRepository.findByRegistrationId(relyingPartyRegistrationId);
|
||||
|
||||
if (relyingPartyRegistration == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Saml2PostAuthenticationRequest.withRelyingPartyRegistration(relyingPartyRegistration)
|
||||
.id((String) claims.get("id"))
|
||||
.authenticationRequestUri((String) claims.get("authenticationRequestUri"))
|
||||
.samlRequest((String) claims.get("samlRequest"))
|
||||
.relayState((String) claims.get("relayState"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+13
-39
@@ -3,7 +3,6 @@ package stirling.software.proprietary.security.saml2;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -12,12 +11,10 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -27,7 +24,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.model.ApplicationProperties.Security.SAML2;
|
||||
import stirling.software.proprietary.security.service.JwtServiceInterface;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
@@ -153,53 +149,31 @@ public class Saml2Configuration {
|
||||
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||
public Saml2AuthenticationRequestRepository<Saml2PostAuthenticationRequest>
|
||||
saml2AuthenticationRequestRepository(
|
||||
JwtServiceInterface jwtService,
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
||||
return new JwtSaml2AuthenticationRequestRepository(
|
||||
new ConcurrentHashMap<>(), jwtService, relyingPartyRegistrationRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository,
|
||||
Saml2AuthenticationRequestRepository<Saml2PostAuthenticationRequest>
|
||||
saml2AuthenticationRequestRepository) {
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
||||
OpenSaml4AuthenticationRequestResolver resolver =
|
||||
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
|
||||
|
||||
resolver.setRelayStateResolver(
|
||||
request -> {
|
||||
String tauriParam = request.getParameter("tauri");
|
||||
if (!"1".equals(tauriParam)) {
|
||||
return null;
|
||||
}
|
||||
String nonce = request.getParameter("nonce");
|
||||
return TauriSamlUtils.buildRelayState(nonce);
|
||||
});
|
||||
|
||||
resolver.setAuthnRequestCustomizer(
|
||||
customizer -> {
|
||||
HttpServletRequest request = customizer.getRequest();
|
||||
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
||||
Saml2PostAuthenticationRequest saml2AuthenticationRequest =
|
||||
saml2AuthenticationRequestRepository.loadAuthenticationRequest(request);
|
||||
|
||||
if (saml2AuthenticationRequest != null) {
|
||||
String sessionId = request.getSession(false).getId();
|
||||
// Generate a unique AuthnRequest ID for each SAML request
|
||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||
|
||||
log.debug(
|
||||
"Retrieving SAML 2 authentication request ID from the current HTTP session {}",
|
||||
sessionId);
|
||||
|
||||
String authenticationRequestId = saml2AuthenticationRequest.getId();
|
||||
|
||||
if (!authenticationRequestId.isBlank()) {
|
||||
authnRequest.setID(authenticationRequestId);
|
||||
} else {
|
||||
log.warn(
|
||||
"No authentication request found for HTTP session {}. Generating new ID",
|
||||
sessionId);
|
||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||
}
|
||||
} else {
|
||||
log.debug("Generating new authentication request ID");
|
||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||
}
|
||||
logAuthnRequestDetails(authnRequest);
|
||||
logHttpRequestDetails(request);
|
||||
});
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package stirling.software.proprietary.security.saml2;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/** Utility helpers for the Tauri desktop SAML flow. */
|
||||
public final class TauriSamlUtils {
|
||||
|
||||
public static final String TAURI_RELAY_STATE_PREFIX = "tauri:";
|
||||
|
||||
private TauriSamlUtils() {
|
||||
// Utility class - prevent instantiation
|
||||
}
|
||||
|
||||
public static boolean isTauriRelayState(HttpServletRequest request) {
|
||||
String relayState = request.getParameter("RelayState");
|
||||
return relayState != null
|
||||
&& (relayState.equals("tauri") || relayState.startsWith(TAURI_RELAY_STATE_PREFIX));
|
||||
}
|
||||
|
||||
public static String extractNonceFromRelayState(String relayState) {
|
||||
if (relayState == null || !relayState.startsWith(TAURI_RELAY_STATE_PREFIX)) {
|
||||
return null;
|
||||
}
|
||||
String[] parts = relayState.split(":");
|
||||
if (parts.length >= 2) {
|
||||
String nonce = parts[parts.length - 1];
|
||||
return nonce.isBlank() ? null : nonce;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String extractNonceFromRequest(HttpServletRequest request) {
|
||||
return extractNonceFromRelayState(request.getParameter("RelayState"));
|
||||
}
|
||||
|
||||
public static String buildRelayState(String nonce) {
|
||||
if (nonce == null || nonce.isBlank()) {
|
||||
return "tauri";
|
||||
}
|
||||
return TAURI_RELAY_STATE_PREFIX + nonce;
|
||||
}
|
||||
}
|
||||
+1
@@ -67,6 +67,7 @@ public class UserService implements UserServiceInterface {
|
||||
|
||||
private final ApplicationProperties.Security.OAUTH2 oAuth2;
|
||||
|
||||
@Transactional
|
||||
public void processSSOPostLogin(
|
||||
String username,
|
||||
String ssoProviderId,
|
||||
|
||||
+3
-2
@@ -182,8 +182,9 @@ class AuthControllerLoginTest {
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/refresh"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").value("new-token"))
|
||||
.andExpect(jsonPath("$.expires_in").value(3600));
|
||||
.andExpect(jsonPath("$.user").exists())
|
||||
.andExpect(jsonPath("$.session.access_token").value("new-token"))
|
||||
.andExpect(jsonPath("$.session.expires_in").value(3600));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
-243
@@ -1,243 +0,0 @@
|
||||
package stirling.software.proprietary.security.saml2;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.NullAndEmptySource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import stirling.software.proprietary.security.service.JwtServiceInterface;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class JwtSaml2AuthenticationRequestRepositoryTest {
|
||||
|
||||
private static final String SAML_REQUEST_TOKEN = "stirling_saml_request_token";
|
||||
|
||||
private Map<String, String> tokenStore;
|
||||
|
||||
@Mock private JwtServiceInterface jwtService;
|
||||
|
||||
@Mock private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||
|
||||
private JwtSaml2AuthenticationRequestRepository jwtSaml2AuthenticationRequestRepository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
tokenStore = new ConcurrentHashMap<>();
|
||||
jwtSaml2AuthenticationRequestRepository =
|
||||
new JwtSaml2AuthenticationRequestRepository(
|
||||
tokenStore, jwtService, relyingPartyRegistrationRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveAuthenticationRequest() {
|
||||
var authRequest = mock(Saml2PostAuthenticationRequest.class);
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
var response = mock(MockHttpServletResponse.class);
|
||||
String token = "testToken";
|
||||
String id = "testId";
|
||||
String relayState = "testRelayState";
|
||||
String authnRequestUri = "example.com/authnRequest";
|
||||
String samlRequest = "testSamlRequest";
|
||||
String relyingPartyRegistrationId = "stirling-pdf";
|
||||
|
||||
when(jwtService.isJwtEnabled()).thenReturn(true);
|
||||
when(authRequest.getRelayState()).thenReturn(relayState);
|
||||
when(authRequest.getId()).thenReturn(id);
|
||||
when(authRequest.getAuthenticationRequestUri()).thenReturn(authnRequestUri);
|
||||
when(authRequest.getSamlRequest()).thenReturn(samlRequest);
|
||||
when(authRequest.getRelyingPartyRegistrationId()).thenReturn(relyingPartyRegistrationId);
|
||||
when(jwtService.generateToken(eq(""), anyMap())).thenReturn(token);
|
||||
|
||||
jwtSaml2AuthenticationRequestRepository.saveAuthenticationRequest(
|
||||
authRequest, request, response);
|
||||
|
||||
verify(request).setAttribute(SAML_REQUEST_TOKEN, relayState);
|
||||
verify(response).addHeader(SAML_REQUEST_TOKEN, relayState);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveAuthenticationRequestWithNullRequest() {
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
var response = mock(MockHttpServletResponse.class);
|
||||
|
||||
jwtSaml2AuthenticationRequestRepository.saveAuthenticationRequest(null, request, response);
|
||||
|
||||
assertTrue(tokenStore.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadAuthenticationRequest() {
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
var relyingPartyRegistration = mock(RelyingPartyRegistration.class);
|
||||
var assertingPartyMetadata = mock(AssertingPartyMetadata.class);
|
||||
String relayState = "testRelayState";
|
||||
String token = "testToken";
|
||||
Map<String, Object> claims =
|
||||
Map.of(
|
||||
"id", "testId",
|
||||
"relyingPartyRegistrationId", "stirling-pdf",
|
||||
"authenticationRequestUri", "example.com/authnRequest",
|
||||
"samlRequest", "testSamlRequest",
|
||||
"relayState", relayState);
|
||||
|
||||
when(request.getParameter("RelayState")).thenReturn(relayState);
|
||||
when(jwtService.extractClaims(token)).thenReturn(claims);
|
||||
when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf"))
|
||||
.thenReturn(relyingPartyRegistration);
|
||||
when(relyingPartyRegistration.getRegistrationId()).thenReturn("stirling-pdf");
|
||||
when(relyingPartyRegistration.getAssertingPartyMetadata())
|
||||
.thenReturn(assertingPartyMetadata);
|
||||
when(assertingPartyMetadata.getSingleSignOnServiceLocation())
|
||||
.thenReturn("https://example.com/sso");
|
||||
tokenStore.put(relayState, token);
|
||||
|
||||
var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request);
|
||||
|
||||
assertNotNull(result);
|
||||
assertFalse(tokenStore.containsKey(relayState));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@NullAndEmptySource
|
||||
void loadAuthenticationRequestWithInvalidRelayState(String relayState) {
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
when(request.getParameter("RelayState")).thenReturn(relayState);
|
||||
|
||||
var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadAuthenticationRequestWithNonExistentToken() {
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
when(request.getParameter("RelayState")).thenReturn("nonExistentRelayState");
|
||||
|
||||
var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadAuthenticationRequestWithNullRelyingPartyRegistration() {
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
String relayState = "testRelayState";
|
||||
String token = "testToken";
|
||||
Map<String, Object> claims =
|
||||
Map.of(
|
||||
"id", "testId",
|
||||
"relyingPartyRegistrationId", "stirling-pdf",
|
||||
"authenticationRequestUri", "example.com/authnRequest",
|
||||
"samlRequest", "testSamlRequest",
|
||||
"relayState", relayState);
|
||||
|
||||
when(request.getParameter("RelayState")).thenReturn(relayState);
|
||||
when(jwtService.extractClaims(token)).thenReturn(claims);
|
||||
when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf"))
|
||||
.thenReturn(null);
|
||||
tokenStore.put(relayState, token);
|
||||
|
||||
var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAuthenticationRequest() {
|
||||
var request = mock(HttpServletRequest.class);
|
||||
var response = mock(HttpServletResponse.class);
|
||||
var relyingPartyRegistration = mock(RelyingPartyRegistration.class);
|
||||
var assertingPartyMetadata = mock(AssertingPartyMetadata.class);
|
||||
String relayState = "testRelayState";
|
||||
String token = "testToken";
|
||||
Map<String, Object> claims =
|
||||
Map.of(
|
||||
"id", "testId",
|
||||
"relyingPartyRegistrationId", "stirling-pdf",
|
||||
"authenticationRequestUri", "example.com/authnRequest",
|
||||
"samlRequest", "testSamlRequest",
|
||||
"relayState", relayState);
|
||||
|
||||
when(request.getParameter("RelayState")).thenReturn(relayState);
|
||||
when(jwtService.extractClaims(token)).thenReturn(claims);
|
||||
when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf"))
|
||||
.thenReturn(relyingPartyRegistration);
|
||||
when(relyingPartyRegistration.getRegistrationId()).thenReturn("stirling-pdf");
|
||||
when(relyingPartyRegistration.getAssertingPartyMetadata())
|
||||
.thenReturn(assertingPartyMetadata);
|
||||
when(assertingPartyMetadata.getSingleSignOnServiceLocation())
|
||||
.thenReturn("https://example.com/sso");
|
||||
tokenStore.put(relayState, token);
|
||||
|
||||
var result =
|
||||
jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest(
|
||||
request, response);
|
||||
|
||||
assertNotNull(result);
|
||||
assertFalse(tokenStore.containsKey(relayState));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAuthenticationRequestWithNullRelayState() {
|
||||
var request = mock(HttpServletRequest.class);
|
||||
var response = mock(HttpServletResponse.class);
|
||||
when(request.getParameter("RelayState")).thenReturn(null);
|
||||
|
||||
var result =
|
||||
jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest(
|
||||
request, response);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAuthenticationRequestWithNonExistentToken() {
|
||||
var request = mock(HttpServletRequest.class);
|
||||
var response = mock(HttpServletResponse.class);
|
||||
when(request.getParameter("RelayState")).thenReturn("nonExistentRelayState");
|
||||
|
||||
var result =
|
||||
jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest(
|
||||
request, response);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAuthenticationRequestWithOnlyRelayState() {
|
||||
var request = mock(HttpServletRequest.class);
|
||||
var response = mock(HttpServletResponse.class);
|
||||
String relayState = "testRelayState";
|
||||
|
||||
when(request.getParameter("RelayState")).thenReturn(relayState);
|
||||
|
||||
var result =
|
||||
jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest(
|
||||
request, response);
|
||||
|
||||
assertNull(result);
|
||||
assertFalse(tokenStore.containsKey(relayState));
|
||||
}
|
||||
}
|
||||
+21
-3
@@ -5,7 +5,7 @@ plugins {
|
||||
id "org.springframework.boot" version "3.5.9"
|
||||
id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
|
||||
id "io.swagger.swaggerhub" version "1.3.2"
|
||||
id "com.diffplug.spotless" version "8.1.0"
|
||||
id "com.diffplug.spotless" version "8.2.1"
|
||||
id "com.github.jk1.dependency-license-report" version "3.0.1"
|
||||
//id "nebula.lint" version "19.0.3"
|
||||
id "org.sonarqube" version "7.2.2.6593"
|
||||
@@ -67,7 +67,7 @@ springBoot {
|
||||
|
||||
allprojects {
|
||||
group = 'stirling.software'
|
||||
version = '2.4.0'
|
||||
version = '2.4.6'
|
||||
|
||||
configurations.configureEach {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
@@ -461,6 +461,24 @@ swaggerhubUpload {
|
||||
oas = "3.0.0" // The version of the OpenAPI Specification you"re using
|
||||
}
|
||||
|
||||
repositories {
|
||||
if (!rootProject.ext.mavenUrl.isEmpty()) {
|
||||
maven {
|
||||
url = rootProject.ext.mavenUrl + '/releases'
|
||||
credentials(PasswordCredentials) {
|
||||
username = rootProject.ext.username
|
||||
password = rootProject.ext.password
|
||||
}
|
||||
authentication {
|
||||
basic(BasicAuthentication)
|
||||
}
|
||||
allowInsecureProtocol = true
|
||||
}
|
||||
}
|
||||
maven { url = "https://build.shibboleth.net/maven/releases" }
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':stirling-pdf')
|
||||
implementation project(':common')
|
||||
@@ -537,4 +555,4 @@ tasks.register('buildRestartHelper', Jar) {
|
||||
doLast {
|
||||
println "restart-helper.jar created at: ${destinationDirectory.get()}/restart-helper.jar"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ RUN DISABLE_ADDITIONAL_FEATURES=true \
|
||||
./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||
|
||||
# Stage 2: Runtime image
|
||||
FROM alpine:3.23.2@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62
|
||||
FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659
|
||||
|
||||
ENV LANG=C.UTF-8 \
|
||||
LC_ALL=C.UTF-8
|
||||
@@ -58,6 +58,7 @@ LABEL org.opencontainers.image.keywords="PDF, manipulation, ultra-lite, API, Spr
|
||||
# Copy scripts
|
||||
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
||||
COPY scripts/stirling-diagnostics.sh /scripts/stirling-diagnostics.sh
|
||||
|
||||
# Copy built JAR from build stage
|
||||
COPY --from=build /app/app/core/build/libs/*.jar /app.jar
|
||||
|
||||
Generated
+261
-217
@@ -11,27 +11,28 @@
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@embedpdf/core": "^1.5.0",
|
||||
"@embedpdf/engines": "^1.5.0",
|
||||
"@embedpdf/plugin-annotation": "^1.5.0",
|
||||
"@embedpdf/plugin-bookmark": "^1.5.0",
|
||||
"@embedpdf/plugin-export": "^1.5.0",
|
||||
"@embedpdf/plugin-history": "^1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^1.5.0",
|
||||
"@embedpdf/plugin-loader": "^1.5.0",
|
||||
"@embedpdf/plugin-pan": "^1.5.0",
|
||||
"@embedpdf/plugin-print": "^1.5.0",
|
||||
"@embedpdf/plugin-redaction": "^1.5.0",
|
||||
"@embedpdf/plugin-render": "^1.5.0",
|
||||
"@embedpdf/plugin-rotate": "^1.5.0",
|
||||
"@embedpdf/plugin-scroll": "^1.5.0",
|
||||
"@embedpdf/plugin-search": "^1.5.0",
|
||||
"@embedpdf/plugin-selection": "^1.5.0",
|
||||
"@embedpdf/plugin-spread": "^1.5.0",
|
||||
"@embedpdf/plugin-thumbnail": "^1.5.0",
|
||||
"@embedpdf/plugin-tiling": "^1.5.0",
|
||||
"@embedpdf/plugin-viewport": "^1.5.0",
|
||||
"@embedpdf/plugin-zoom": "^1.5.0",
|
||||
"@embedpdf/core": "^2.5.0",
|
||||
"@embedpdf/engines": "^2.5.0",
|
||||
"@embedpdf/models": "^2.5.0",
|
||||
"@embedpdf/plugin-annotation": "^2.5.0",
|
||||
"@embedpdf/plugin-bookmark": "^2.5.0",
|
||||
"@embedpdf/plugin-document-manager": "^2.5.0",
|
||||
"@embedpdf/plugin-export": "^2.5.0",
|
||||
"@embedpdf/plugin-history": "^2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^2.5.0",
|
||||
"@embedpdf/plugin-pan": "^2.5.0",
|
||||
"@embedpdf/plugin-print": "^2.5.0",
|
||||
"@embedpdf/plugin-redaction": "^2.5.0",
|
||||
"@embedpdf/plugin-render": "^2.5.0",
|
||||
"@embedpdf/plugin-rotate": "^2.5.0",
|
||||
"@embedpdf/plugin-scroll": "^2.5.0",
|
||||
"@embedpdf/plugin-search": "^2.5.0",
|
||||
"@embedpdf/plugin-selection": "^2.5.0",
|
||||
"@embedpdf/plugin-spread": "^2.5.0",
|
||||
"@embedpdf/plugin-thumbnail": "^2.5.0",
|
||||
"@embedpdf/plugin-tiling": "^2.5.0",
|
||||
"@embedpdf/plugin-viewport": "^2.5.0",
|
||||
"@embedpdf/plugin-zoom": "^2.5.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@iconify/react": "^6.0.2",
|
||||
@@ -47,13 +48,13 @@
|
||||
"@supabase/supabase-js": "^2.47.13",
|
||||
"@tailwindcss/postcss": "^4.1.13",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.0",
|
||||
"@tauri-apps/plugin-http": "^2.5.4",
|
||||
"@tauri-apps/plugin-shell": "^2.3.3",
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||
"@tauri-apps/plugin-http": "^2.5.6",
|
||||
"@tauri-apps/plugin-shell": "^2.3.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"axios": "^1.12.2",
|
||||
"globals": "^17.0.0",
|
||||
"axios": "^1.13.2",
|
||||
"globals": "^17.1.0",
|
||||
"i18next": "^25.5.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"jszip": "^3.10.1",
|
||||
@@ -62,7 +63,7 @@
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
"peerjs": "^1.5.5",
|
||||
"posthog-js": "^1.268.0",
|
||||
"qrcode.react": "^4.1.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-i18next": "^15.7.3",
|
||||
@@ -75,10 +76,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@iconify-json/material-symbols": "^1.2.48",
|
||||
"@iconify/utils": "^3.0.2",
|
||||
"@iconify-json/material-symbols": "^1.2.53",
|
||||
"@iconify/utils": "^3.1.0",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@tauri-apps/cli": "^2.5.0",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
@@ -552,13 +553,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/core": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.5.0.tgz",
|
||||
"integrity": "sha512-Yrh9XoVaT8cUgzgqpJ7hx5wg6BqQrCFirqqlSwVb+Ly9oNn4fZbR9GycIWmzJOU5XBnaOJjXfQSaDyoNP0woNA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-2.5.0.tgz",
|
||||
"integrity": "sha512-nI7GnA5xCNtJHAdKBLPKJVvi4+yAKjy1sysaDf+qp+z3D81Hy8oAcl///QTaZ9ob0SL2jyqi3x//hKl0Rwmgrw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/engines": "1.5.0",
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/engines": "2.5.0",
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
@@ -569,13 +570,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/engines": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.5.0.tgz",
|
||||
"integrity": "sha512-/GzhjHFHWfOaX7vjgFJX/pyq668wYjoda1bZ9MpwF/EF000Wwy2Q0AOhprjldPFz8ASKjwKwqsXmaqrK99yOAQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-2.5.0.tgz",
|
||||
"integrity": "sha512-SEknNmQrYvkAZgJllRKXuvXSrHSndDQsr7b3mrIVa9bzV6TeZua0a/YUlvI3/jf74Sdajru3XKPe22iHEOH4Zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0",
|
||||
"@embedpdf/pdfium": "1.5.0"
|
||||
"@embedpdf/fonts-arabic": "1.0.0",
|
||||
"@embedpdf/fonts-hebrew": "1.0.0",
|
||||
"@embedpdf/fonts-jp": "1.0.0",
|
||||
"@embedpdf/fonts-kr": "1.0.0",
|
||||
"@embedpdf/fonts-latin": "1.0.0",
|
||||
"@embedpdf/fonts-sc": "1.0.0",
|
||||
"@embedpdf/fonts-tc": "1.0.0",
|
||||
"@embedpdf/models": "2.5.0",
|
||||
"@embedpdf/pdfium": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
@@ -585,64 +593,125 @@
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-arabic": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-arabic/-/fonts-arabic-1.0.0.tgz",
|
||||
"integrity": "sha512-SnGvQb+LwPZQO2WjjvlmXrJZolJUfLYbLZQSaYUw1vrQyMyJKT4LewvJGG+hZ+Yz2fz7OMIQ+4Gc98mGODZtOg==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-hebrew": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-hebrew/-/fonts-hebrew-1.0.0.tgz",
|
||||
"integrity": "sha512-5HVAKGL7VqPeTxxADDrSqAFBxfmAXdP8fIqrPwJIKkqdK2643bOer8CqnnpO3/nPoFhkzxhttWMB9BGiqSW62w==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-jp": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-jp/-/fonts-jp-1.0.0.tgz",
|
||||
"integrity": "sha512-BY2tv/mcICUUKf+M/bizf3RU65PMqKClJ/e5o9mgMibxyML0OQvEDwYMRPODQkKgJKXCO3ScHmVvcmXp6kt+fA==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-kr": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-kr/-/fonts-kr-1.0.0.tgz",
|
||||
"integrity": "sha512-bh88HXSvOBS581kgmihWY7Ijp9hBsvlmXogFG5LSNx9UBAobRcakZiFMGieRBc06hUSkpo7WhjaFM/z/SfQ8dQ==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-latin": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-latin/-/fonts-latin-1.0.0.tgz",
|
||||
"integrity": "sha512-LLYysdr8O6sRNzhmW3PbF3AeA8xnqvOi4XLFfIfNlW5uEZ+qsJdcfd78Q78sFJMhlaOAYFMziMMsnOzmx463rA==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-sc": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-sc/-/fonts-sc-1.0.0.tgz",
|
||||
"integrity": "sha512-ETXl7XCwaQLSSvMO3EUDwMNqtL64kX2LlFxarTRi/NsIGGOIxUurGfKtrkmtnKHrWy1jAJSt6oxK2uJhvdvQIw==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-tc": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-tc/-/fonts-tc-1.0.0.tgz",
|
||||
"integrity": "sha512-rGZJbVD6DYS5BbXdpEMnWkpVF0Knar+bsiyb2o3+YRx7O8eyFubEBQUSUInirQk69HA6fc3GhYCg7TyC/oD76Q==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/models": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.5.0.tgz",
|
||||
"integrity": "sha512-x/1li3jdag+IzfZkcfRLKLqASLep4v6dgVi3z0JArwaicFra8k1IY2xaVTrwcZyx7pRb/rxvoO9yLHW0Y34NFw==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-2.5.0.tgz",
|
||||
"integrity": "sha512-wu7XgargYBQEh46hVnfsmkTF6TvuoP9nAkTASR60s5ourjlT12qL9RiFLpwGkOBfs8E58h8V5hkgKsra5t03Lw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@embedpdf/pdfium": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.5.0.tgz",
|
||||
"integrity": "sha512-PI32t2U4ThZC907n2Iwr8E5WqmC574G83u3V9ysNFl29N9kasrY9RiLSzU4W/yQvXPjIbpQHBsbMKXLjCFBI9w==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-2.5.0.tgz",
|
||||
"integrity": "sha512-2VEO4cNZsV8ig9upS+C+x3Tb58aqNxiAdaUMlD2ZZT8FgszhsV9xMyEuM2maFRdjeT7EO37FtzYBdXc/K67ivA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-annotation": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.5.0.tgz",
|
||||
"integrity": "sha512-mxEPI6xYwOGaf9fYfoywuj6nwA10eHFPBuN066MzwphDk6DOHJGZ3Vq8zNQBXh20c/Lb25PL718D7MZWxZLUHg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-2.5.0.tgz",
|
||||
"integrity": "sha512-S5zCeWU3hM9jrnaGuW5RAXt+AzXXvQbFtAdCtxHW1hFADiZ97FKr8KS9MGCkkj6C9madtZP6iUJikvnhoLCABQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0",
|
||||
"@embedpdf/utils": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0",
|
||||
"@embedpdf/utils": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-history": "1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
||||
"@embedpdf/plugin-selection": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-history": "2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.5.0",
|
||||
"@embedpdf/plugin-selection": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-bookmark": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-bookmark/-/plugin-bookmark-1.5.0.tgz",
|
||||
"integrity": "sha512-s3C9PtVesy5X8Ds/C9TEElFiqfKGRklG/uNPTROpNoolfpi0h7qX2xqqh/9+FzKH2nHjVcPB7Pp432v16h7eRA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-bookmark/-/plugin-bookmark-2.5.0.tgz",
|
||||
"integrity": "sha512-2N5kGoamUrQqWZC5SMWIhdyBHqZN/CdcGf8GVH71FFw3AU6rmZ1AD/AkLzgqoYGIuZFE8ACckdrhtbpsZMmSDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-document-manager": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-document-manager/-/plugin-document-manager-2.5.0.tgz",
|
||||
"integrity": "sha512-I8Z/0B7R/YhtVaJFruwFO+QBLIDmQfHx9WVlrDXWZs68YiGwEbjSyizEIEqtulUJxcXfPs2Tf7oIBbdSuPG2NQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-export": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.5.0.tgz",
|
||||
"integrity": "sha512-luk68mNW9l2X31qk4b02phKaqDl9aDXUAgHVz1EWrgwXQ3Oz9WEdu60utYARYDiepDo3Caadll8RwctYSf/anA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-2.5.0.tgz",
|
||||
"integrity": "sha512-KC9jXqwcxe76QqfxLx0tnrSdFoApTFOpT+dwrvox186uxYKSmSt1JHFWe4THB/A63hCNr8uMwyswYdFO8fWNHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -651,31 +720,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-history": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.5.0.tgz",
|
||||
"integrity": "sha512-p7PTNNaIr4gH3jLwX+eLJe1DeUXgi21kVGN6SRx/pocH8esg4jqoOeD/YiRRZoZnPOiy0jBXVhkPkwSmY7a2hQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-2.5.0.tgz",
|
||||
"integrity": "sha512-Av9NBSE9Or1Y6cXcNWpx0bBZN3yI4vywa6kSNjhaqOrgpQDWMaTO57eApJpyHzBodqEztY+klE9YJ7MH88zm6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-interaction-manager": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.5.0.tgz",
|
||||
"integrity": "sha512-ckHgTfvkW6c5Ta7Mc+Dl9C2foVnvEpqEJ84wyBnqrU0OWbe/jsiPhyKBVeartMGqNI/kVfaQTXupyrKhekAVmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -683,16 +736,16 @@
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-loader": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.5.0.tgz",
|
||||
"integrity": "sha512-P4YpIZfaW69etYIjphyaL4cGl2pB14h3OdTE0tRQ2pZYZHFLTvlt4q9B3PVSdhlSrHK5nob7jfLGon2U7xCslg==",
|
||||
"node_modules/@embedpdf/plugin-interaction-manager": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-2.5.0.tgz",
|
||||
"integrity": "sha512-QrmowLVvC5FNZdvVr2kczSDdnHHOuhf+So0VG5Ythts/OL1bIR/0OOpuyJsScTyo5boYnRkXv8yPf8htL57YKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -701,17 +754,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-pan": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.5.0.tgz",
|
||||
"integrity": "sha512-EMQ08dHqLkZmFVuLOO6h3AAinFPQoA1r6OlL9z+p0sswq31JAgd4X7+xjYIpI01z/V3+cTzPHzp7qwob5E4tbA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-2.5.0.tgz",
|
||||
"integrity": "sha512-DfdA+hBm9kGYYy7OuJym6azk2h2U/Geirud+tmVzFSL7+OZ3tZ3K9fqj07w66zx0msyUVlYrXzkYSU9NEmwpLA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
||||
"@embedpdf/plugin-viewport": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.5.0",
|
||||
"@embedpdf/plugin-viewport": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -720,15 +773,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-print": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-1.5.0.tgz",
|
||||
"integrity": "sha512-rjorvNxAZfO9X4cFZVU9fHnldMWqMceJGmr3mH+yj7KdHePvNDDP+omyZyZKtxlUZENaeDI2h6k5z0GbhBz6sQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-2.5.0.tgz",
|
||||
"integrity": "sha512-qejq7/0K9hh3hzop+u+Qmn7ijTqGcDhxaiXoPkyl91CZVOyAD8qMBzWnhC7vRNOB7hcYgBP81uegE3se+EIlcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0",
|
||||
@@ -737,34 +790,37 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-redaction": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-1.5.0.tgz",
|
||||
"integrity": "sha512-txiukr5UKAGvJzl6dVBmmIT1v3r/t4e2qYm1hqU2faGgNCa2dwk79x9mDBlvWwxlJXCDFuFE+7Ps9/nU6qmU2w==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-2.5.0.tgz",
|
||||
"integrity": "sha512-G0cm1hLWi09gU8WV+IShq2XHkmLtEbk+EvD3dIiyJV2kbOjgwGSC2Ezt8br3DzH6R/0bF6RAbDIpFyS2Q0oMfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0",
|
||||
"@embedpdf/utils": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0",
|
||||
"@embedpdf/utils": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
||||
"@embedpdf/plugin-selection": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-annotation": "2.5.0",
|
||||
"@embedpdf/plugin-history": "2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.5.0",
|
||||
"@embedpdf/plugin-selection": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-render": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.5.0.tgz",
|
||||
"integrity": "sha512-ywwSj0ByrlkvrJIHKRzqxARkOZriki8VJUC+T4MV8fGyF4CzvCRJyKlPktahFz+VxhoodqTh7lBCib68dH+GvA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-2.5.0.tgz",
|
||||
"integrity": "sha512-nrTmg8cVMohcKYiQ/7erErsaWlyaq20OtXbVjmnPNnqz4amJLAjlPyudTJRlWWPyIiri9SF4A0ue5ICDY2sypg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -773,15 +829,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-rotate": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.5.0.tgz",
|
||||
"integrity": "sha512-5EmBCsq0VfrE3xWY6ofuVm8S6aK95EbAycRIk1wczcmTdvpsuXZ6P2ZaECUgYMcpZ6uAg4/kGf8X8VVZuCihSQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-2.5.0.tgz",
|
||||
"integrity": "sha512-crFsXduaxNZJmVRfgklBpO4x4i9cRxPmfFBvdIoyJ1ea6AGOCL0rQKQcfqHTFdgtPzlVUiIg6Hi2v+033jdLUg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -790,16 +846,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-scroll": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.5.0.tgz",
|
||||
"integrity": "sha512-RNmTZCZ8X1mA8cw9M7TMDuhO9GtkOalGha2bBL3En3D1IlDRS7PzNNMSMV7eqT7OQICSTltlpJ8p8Qi5esvL/Q==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-2.5.0.tgz",
|
||||
"integrity": "sha512-AdLuSgvAaukLl1uQ0FbswcAIPFaR3Jk2ZbEJpWLd9E6iQ+66Cta0Sz8d5J6ndx7VBlRYAZwoqiXF85utJxpQ5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-viewport": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-viewport": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -808,16 +864,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-search": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.5.0.tgz",
|
||||
"integrity": "sha512-TB5b0H8Iobx/azVUBIlG2ClaKtf0y3/Xi3E/iB8BwvkIE2+g6EGfp8IMXIn8WDXST6bbvJEP31Ab0Ilp6SVkiw==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-2.5.0.tgz",
|
||||
"integrity": "sha512-ycHJh05vBZ1PTSdEMgdx6K1py0oklwbwY2eXO4nD54EN9EVZgWlYC4Q+u8nyGOiNL6VmJYcqJ0HmjSWBdmGWBw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-loader": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -826,17 +881,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-selection": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.5.0.tgz",
|
||||
"integrity": "sha512-zrxLBAZQoPswDuf9q9DrYaQc6B0Ysc2U1hueTjNH/4+ydfl0BFXZkKR63C2e3YmWtXvKjkoIj0GyPzsiBORLUw==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-2.5.0.tgz",
|
||||
"integrity": "sha512-M3WDjahig/6KE83SZGvTaJWhqEOIzH002k2fpJVuks926UBnfgYCH8uqV7SOUQTneQDmIa0PlyFiuEXDw1Ocrw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0",
|
||||
"@embedpdf/utils": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
||||
"@embedpdf/plugin-viewport": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -845,16 +900,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-spread": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.5.0.tgz",
|
||||
"integrity": "sha512-3EU5Cp+fPQSiMjvMR/P2kXxXry/RlnxHLs4JeskAaH95QcqWW3VD+DrHkWSiLFkdhI18rNNGNlMc5RvDGvbXGQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-2.5.0.tgz",
|
||||
"integrity": "sha512-kG8HZMZmbpUVDxCOEyQzIiMPW+VjjebOl93V+quAH+GAI5Tkg6exPyyQ2+/DOJPCtYX4Kh2z2aeoyK2b7NRgIQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-loader": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -863,16 +917,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-thumbnail": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.5.0.tgz",
|
||||
"integrity": "sha512-Z2qpyyr5s2M6460KDGu1Vk6rdbQFIoCpnyFAT6e7UaTIKkqJSNpmjqMsBU5PosYCFu/cClpHPvS7tg9/IKAk6g==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-2.5.0.tgz",
|
||||
"integrity": "sha512-iWofJSXKbWrgvS2fe8v3U1+e2wjBRXD2i1DUcJKnTrqyfjZ8YzUomc5EzdG2RT7uUjtqrcu7463TZ9JHXUkASQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-render": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-render": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -881,18 +935,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-tiling": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.5.0.tgz",
|
||||
"integrity": "sha512-0Vx9elHNpMM+zv8hEoZXBEm8Q0+4kU52LxOlTYRr1A5FskF836sUct6g1ngwK1bmfbAfpz+62PnYI2EeilDZig==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-2.5.0.tgz",
|
||||
"integrity": "sha512-oih0GyGOJvfaXPLSEY+qfC05UUU1ZkADEbr6uCwRMmdHIXu/0ZTJnAToegfWXtfE+Sw0J5wscVkipXXIX5azlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-render": "1.5.0",
|
||||
"@embedpdf/plugin-scroll": "1.5.0",
|
||||
"@embedpdf/plugin-viewport": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-render": "2.5.0",
|
||||
"@embedpdf/plugin-scroll": "2.5.0",
|
||||
"@embedpdf/plugin-viewport": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -901,15 +955,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-viewport": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.5.0.tgz",
|
||||
"integrity": "sha512-G8GDyYRhfehw72+r4qKkydnA5+AU8qH67g01Y12b0DzI0VIzymh/05Z4dK8DsY3jyWPXJfw2hlg5+KDHaMBHgQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-2.5.0.tgz",
|
||||
"integrity": "sha512-z0AXHA9Z3rZdCLje7P2NsQbxKLJ4b/l8lgzXOVn5Ow/pIPE0D2P3fn9WzImHTNI1RNrZMdkW9OH3lfkEXFTqHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -918,19 +972,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-zoom": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.5.0.tgz",
|
||||
"integrity": "sha512-LiDkCd5/IXg2CRORl1Yikan2op+AYXSxhHzCFatyBdwzVj+n4y9I74OwCI62Mar8WDAIMyXZDCQxGPToSm+zDw==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-2.5.0.tgz",
|
||||
"integrity": "sha512-HWJlqXOXdv/kttV+XWCCStUZAeLl66AuaO8BsnPlAPwEADLLCH4tR4XqJQoWr7/r5watKP7UeQ00FsWu0oGclw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0",
|
||||
"hammerjs": "^2.0.8"
|
||||
"@embedpdf/models": "2.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
||||
"@embedpdf/plugin-scroll": "1.5.0",
|
||||
"@embedpdf/plugin-viewport": "1.5.0",
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-scroll": "2.5.0",
|
||||
"@embedpdf/plugin-viewport": "2.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -939,14 +991,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/utils": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.5.0.tgz",
|
||||
"integrity": "sha512-L6jsAPQPGM8ne+MMFAd5gqXb1RNEgNyh16VvVUVKcVnJlBhwil59nVeEQ0cwPhjF5qVeY6MQDIOjBzJqkgXOYg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-2.5.0.tgz",
|
||||
"integrity": "sha512-JjYj6BRzu9oesA1JOqKPFMEWKinjvJIjziWu1j6lDXxLsE59bkShjUKbaEG+lkXRspuZRWNP++rzE2p2Ht4veg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
@@ -1898,9 +1951,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify-json/material-symbols": {
|
||||
"version": "1.2.50",
|
||||
"resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.50.tgz",
|
||||
"integrity": "sha512-71tjHR70h46LHtBFab3fAd2V/wPTO7JMV5lKnRn3IcF303LaFgAlO0BZeTJDcmCv9d0snRZmnoLZAJVD7/eisw==",
|
||||
"version": "1.2.53",
|
||||
"resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.53.tgz",
|
||||
"integrity": "sha512-2jXBKFdNzL9zy6chnJqubykL9WZno7rEP6/isSzpp6fKJJMXXhRtVkaGw1Clle0RlXGWzVkd/eiYUH8f9/ILrQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -4128,27 +4181,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-fs": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.4.tgz",
|
||||
"integrity": "sha512-MTorXxIRmOnOPT1jZ3w96vjSuScER38ryXY88vl5F0uiKdnvTKKTtaEjTEo8uPbl4e3gnUtfsDVwC7h77GQLvQ==",
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.5.tgz",
|
||||
"integrity": "sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-http": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-http/-/plugin-http-2.5.4.tgz",
|
||||
"integrity": "sha512-/i4U/9za3mrytTgfRn5RHneKubZE/dwRmshYwyMvNRlkWjvu1m4Ma72kcbVJMZFGXpkbl+qLyWMGrihtWB76Zg==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-http/-/plugin-http-2.5.6.tgz",
|
||||
"integrity": "sha512-KhCK3TDNDF4vdz75/j+KNQipYKf+295Visa8r32QcXScg0+D3JwShcCM6D+FN8WuDF24X3KSiAB8QtRxW6jKRA==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-shell": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.3.tgz",
|
||||
"integrity": "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ==",
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.4.tgz",
|
||||
"integrity": "sha512-ktsRWf8wHLD17aZEyqE8c5x98eNAuTizR1FSX475zQ4TxaiJnhwksLygQz+AGwckJL5bfEP13nWrlTNQJUpKpA==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
@@ -7033,9 +7086,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz",
|
||||
"integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz",
|
||||
"integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
@@ -8556,9 +8609,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.0.0.tgz",
|
||||
"integrity": "sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==",
|
||||
"version": "17.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz",
|
||||
"integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -8660,15 +8713,6 @@
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hammerjs": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
|
||||
"integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-bigints": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||
@@ -12176,9 +12220,9 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz",
|
||||
"integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==",
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
|
||||
"integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
@@ -12198,12 +12242,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz",
|
||||
"integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==",
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
|
||||
"integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.11.0"
|
||||
"react-router": "7.13.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -13448,9 +13492,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.46.1.tgz",
|
||||
"integrity": "sha512-ynjfCHD3nP2el70kN5Pmg37sSi0EjOm9FgHYQdC4giWG/hzO3AatzXXJJgP305uIhGQxSufJLuYWtkY8uK/8RA==",
|
||||
"version": "5.48.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.48.2.tgz",
|
||||
"integrity": "sha512-VPWD+UyoSFZ7Nxix5K/F8yWiKWOiROkLlWYXOZReE0TUycw+58YWB3D6lAKT+57xmN99wRX4H3oZmw0NPy7y3Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -13462,7 +13506,7 @@
|
||||
"aria-query": "^5.3.1",
|
||||
"axobject-query": "^4.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"devalue": "^5.5.0",
|
||||
"devalue": "^5.6.2",
|
||||
"esm-env": "^1.2.1",
|
||||
"esrap": "^2.2.1",
|
||||
"is-reference": "^3.0.3",
|
||||
|
||||
+33
-31
@@ -7,27 +7,28 @@
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@embedpdf/core": "^1.5.0",
|
||||
"@embedpdf/engines": "^1.5.0",
|
||||
"@embedpdf/plugin-annotation": "^1.5.0",
|
||||
"@embedpdf/plugin-bookmark": "^1.5.0",
|
||||
"@embedpdf/plugin-export": "^1.5.0",
|
||||
"@embedpdf/plugin-history": "^1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^1.5.0",
|
||||
"@embedpdf/plugin-loader": "^1.5.0",
|
||||
"@embedpdf/plugin-pan": "^1.5.0",
|
||||
"@embedpdf/plugin-print": "^1.5.0",
|
||||
"@embedpdf/plugin-redaction": "^1.5.0",
|
||||
"@embedpdf/plugin-render": "^1.5.0",
|
||||
"@embedpdf/plugin-rotate": "^1.5.0",
|
||||
"@embedpdf/plugin-scroll": "^1.5.0",
|
||||
"@embedpdf/plugin-search": "^1.5.0",
|
||||
"@embedpdf/plugin-selection": "^1.5.0",
|
||||
"@embedpdf/plugin-spread": "^1.5.0",
|
||||
"@embedpdf/plugin-thumbnail": "^1.5.0",
|
||||
"@embedpdf/plugin-tiling": "^1.5.0",
|
||||
"@embedpdf/plugin-viewport": "^1.5.0",
|
||||
"@embedpdf/plugin-zoom": "^1.5.0",
|
||||
"@embedpdf/core": "^2.5.0",
|
||||
"@embedpdf/engines": "^2.5.0",
|
||||
"@embedpdf/models": "^2.5.0",
|
||||
"@embedpdf/plugin-annotation": "^2.5.0",
|
||||
"@embedpdf/plugin-bookmark": "^2.5.0",
|
||||
"@embedpdf/plugin-export": "^2.5.0",
|
||||
"@embedpdf/plugin-history": "^2.5.0",
|
||||
"@embedpdf/plugin-document-manager": "^2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^2.5.0",
|
||||
"@embedpdf/plugin-pan": "^2.5.0",
|
||||
"@embedpdf/plugin-print": "^2.5.0",
|
||||
"@embedpdf/plugin-redaction": "^2.5.0",
|
||||
"@embedpdf/plugin-render": "^2.5.0",
|
||||
"@embedpdf/plugin-rotate": "^2.5.0",
|
||||
"@embedpdf/plugin-scroll": "^2.5.0",
|
||||
"@embedpdf/plugin-search": "^2.5.0",
|
||||
"@embedpdf/plugin-selection": "^2.5.0",
|
||||
"@embedpdf/plugin-spread": "^2.5.0",
|
||||
"@embedpdf/plugin-thumbnail": "^2.5.0",
|
||||
"@embedpdf/plugin-tiling": "^2.5.0",
|
||||
"@embedpdf/plugin-viewport": "^2.5.0",
|
||||
"@embedpdf/plugin-zoom": "^2.5.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@iconify/react": "^6.0.2",
|
||||
@@ -43,13 +44,13 @@
|
||||
"@supabase/supabase-js": "^2.47.13",
|
||||
"@tailwindcss/postcss": "^4.1.13",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.0",
|
||||
"@tauri-apps/plugin-http": "^2.5.4",
|
||||
"@tauri-apps/plugin-shell": "^2.3.3",
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||
"@tauri-apps/plugin-http": "^2.5.6",
|
||||
"@tauri-apps/plugin-shell": "^2.3.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"axios": "^1.12.2",
|
||||
"globals": "^17.0.0",
|
||||
"axios": "^1.13.2",
|
||||
"globals": "^17.1.0",
|
||||
"i18next": "^25.5.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"jszip": "^3.10.1",
|
||||
@@ -58,7 +59,7 @@
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
"peerjs": "^1.5.5",
|
||||
"posthog-js": "^1.268.0",
|
||||
"qrcode.react": "^4.1.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-i18next": "^15.7.3",
|
||||
@@ -70,6 +71,7 @@
|
||||
"web-vitals": "^5.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"pretauri-build": "node scripts/build-provisioner.mjs",
|
||||
"predev": "npm run generate-icons",
|
||||
"dev": "vite",
|
||||
"prebuild": "npm run generate-icons",
|
||||
@@ -126,10 +128,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@iconify-json/material-symbols": "^1.2.48",
|
||||
"@iconify/utils": "^3.0.2",
|
||||
"@iconify-json/material-symbols": "^1.2.53",
|
||||
"@iconify/utils": "^3.1.0",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@tauri-apps/cli": "^2.5.0",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user