Compare commits
1100 Commits
v6.6.6-pre
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10679e9899 | ||
|
|
c51dad39e0 | ||
|
|
1d350a3a29 | ||
|
|
81e3cce070 | ||
|
|
f9de2d77b5 | ||
|
|
3956eaf389 | ||
|
|
d13e2c2d87 | ||
|
|
ebf2371da9 | ||
|
|
fa4b12448d | ||
|
|
5fe6dfa551 | ||
|
|
6061c8b0a5 | ||
|
|
1ac0aa8a3e | ||
|
|
5d04d7f01f | ||
|
|
9c97e347c5 | ||
|
|
22967d123d | ||
|
|
3fed650254 | ||
|
|
b57b2083d3 | ||
|
|
1f47e8c62e | ||
|
|
d7ce653d74 | ||
|
|
07c803efee | ||
|
|
26b6d9133e | ||
|
|
146084bdb3 | ||
|
|
fa82b4630b | ||
|
|
d00841030f | ||
|
|
c517bb0be6 | ||
|
|
e585d8be8f | ||
|
|
8be76832a5 | ||
|
|
99df266a3e | ||
|
|
5660874992 | ||
|
|
b0dade9da3 | ||
|
|
a533858804 | ||
|
|
d3bcc984e7 | ||
|
|
99e8307a80 | ||
|
|
73926ea392 | ||
|
|
5ff24526b7 | ||
|
|
32ed1c57bd | ||
|
|
f4c3a95348 | ||
|
|
b1a908343a | ||
|
|
7f9d7eb36e | ||
|
|
30820e29fc | ||
|
|
a85ea2e0f7 | ||
|
|
993e713955 | ||
|
|
3151e30db1 | ||
|
|
eb5219dd68 | ||
|
|
bb44783369 | ||
|
|
33b46c4db3 | ||
|
|
3730aae62a | ||
|
|
065062d58a | ||
|
|
7b2f58e68e | ||
|
|
e2fc23fcf8 | ||
|
|
6f56ef284d | ||
|
|
08a644ba39 | ||
|
|
6ae19ac4a6 | ||
|
|
7761cbe81d | ||
|
|
f981d88150 | ||
|
|
e2a23eaa0d | ||
|
|
9d510b3c08 | ||
|
|
a98f5ac45e | ||
|
|
b989e964c0 | ||
|
|
3ff6eefa06 | ||
|
|
67fde9be3c | ||
|
|
df7ac89723 | ||
|
|
358df9f53b | ||
|
|
02e3bfaa8a | ||
|
|
dde74fa73b | ||
|
|
100e3fe75f | ||
|
|
af7930cea2 | ||
|
|
6b4f6b909c | ||
|
|
9a6e5cd7cc | ||
|
|
9f64b6ec7a | ||
|
|
77f720e34c | ||
|
|
168dcb7824 | ||
|
|
759186a212 | ||
|
|
71ed7a76ea | ||
|
|
bd939b22c7 | ||
|
|
c327f77294 | ||
|
|
d907d79beb | ||
|
|
93b879927c | ||
|
|
0c545d4cf9 | ||
|
|
95c90c1517 | ||
|
|
cb731fa858 | ||
|
|
9bb3b09ecf | ||
|
|
7c8f541d3e | ||
|
|
ce41687382 | ||
|
|
4b083dea5c | ||
|
|
c84473c1eb | ||
|
|
7fc078f3e6 | ||
|
|
cbbd538248 | ||
|
|
825f6e562b | ||
|
|
a278afb260 | ||
|
|
2fbeea717c | ||
|
|
c7259e4663 | ||
|
|
69a2669342 | ||
|
|
42d1ca8fd4 | ||
|
|
1cf52d8b39 | ||
|
|
6e482afab2 | ||
|
|
ddf3295e6d | ||
|
|
79e087abd3 | ||
|
|
a7cf51bdf7 | ||
|
|
dfdb31e2f8 | ||
|
|
3508ddc3ca | ||
|
|
137fc6b928 | ||
|
|
e6f5295420 | ||
|
|
2bb08921c3 | ||
|
|
ee2d0e4c30 | ||
|
|
c43a838572 | ||
|
|
17ff6a8013 | ||
|
|
62ad6a0d08 | ||
|
|
5c049fa867 | ||
|
|
619f17114a | ||
|
|
1c1431014c | ||
|
|
9d1d7b7e34 | ||
|
|
f68ca1e786 | ||
|
|
8d16a30064 | ||
|
|
cf601c33c0 | ||
|
|
588cd39d7c | ||
|
|
79ebfa9b7a | ||
|
|
0c6b2746d1 | ||
|
|
978972c55c | ||
|
|
37854fc577 | ||
|
|
5537e193a6 | ||
|
|
0d42b2b133 | ||
|
|
44bd7972d4 | ||
|
|
5143eb39f7 | ||
|
|
cf51883b3e | ||
|
|
484ca0c78a | ||
|
|
8f5cad0e2c | ||
|
|
988512a571 | ||
|
|
f8bd380051 | ||
|
|
281131dbba | ||
|
|
ea3a61077a | ||
|
|
d1a898b40d | ||
|
|
a521a81ef0 | ||
|
|
2505c61975 | ||
|
|
ab5a54dbb6 | ||
|
|
44ad8fa60a | ||
|
|
5b27a241d7 | ||
|
|
084019ca65 | ||
|
|
ba147af8fe | ||
|
|
1b3f4db07d | ||
|
|
c36705d458 | ||
|
|
0e126cb8cf | ||
|
|
c48183a539 | ||
|
|
50f380dbbe | ||
|
|
66023a9a68 | ||
|
|
c3fbc3354c | ||
|
|
a7d2ed11f3 | ||
|
|
899aec2658 | ||
|
|
74e47587e2 | ||
|
|
6a3dc92572 | ||
|
|
e3a4667422 | ||
|
|
c4dd99bba9 | ||
|
|
cb70f3c318 | ||
|
|
588b6f9882 | ||
|
|
375f69ca1e | ||
|
|
a32e5cc139 | ||
|
|
8e00137751 | ||
|
|
003db50833 | ||
|
|
bc519c2c20 | ||
|
|
3b41fa8cfa | ||
|
|
39ed0f6d2d | ||
|
|
710f796832 | ||
|
|
9ec5fb7263 | ||
|
|
407db457d5 | ||
|
|
0c5d2cfcd1 | ||
|
|
87ace375bb | ||
|
|
d010020f3b | ||
|
|
c60227a98f | ||
|
|
2824681bff | ||
|
|
073a3e3946 | ||
|
|
93e91127a0 | ||
|
|
b60a6cff56 | ||
|
|
1f3b1963d9 | ||
|
|
4915f57abb | ||
|
|
97c6fc97d5 | ||
|
|
b68421bbc3 | ||
|
|
2d10559754 | ||
|
|
b398a7b546 | ||
|
|
1711d2102d | ||
|
|
97cea230f3 | ||
|
|
b6a0fe9465 | ||
|
|
06c50659bb | ||
|
|
244b47f548 | ||
|
|
b72a244d93 | ||
|
|
c1e069d4dc | ||
|
|
f99994085a | ||
|
|
32fd0dd78c | ||
|
|
a557b6b2b4 | ||
|
|
e84583c776 | ||
|
|
a548b0d543 | ||
|
|
de94f15383 | ||
|
|
7045d986ef | ||
|
|
de7ae9cf09 | ||
|
|
ab3d6888dc | ||
|
|
98a70891f3 | ||
|
|
52e7326a2c | ||
|
|
bfd2e3b07a | ||
|
|
799f5e30d3 | ||
|
|
d3e544c3c0 | ||
|
|
866fd55834 | ||
|
|
74ce1fba32 | ||
|
|
a11b93b4cc | ||
|
|
066f2baa03 | ||
|
|
e02396280f | ||
|
|
a654c80746 | ||
|
|
3b50f4bd7c | ||
|
|
cc1f77f5bc | ||
|
|
381fce4a82 | ||
|
|
bc3be97cee | ||
|
|
1c389208a7 | ||
|
|
cbeed2d3d0 | ||
|
|
3d974ad144 | ||
|
|
749042a05d | ||
|
|
52413b82ee | ||
|
|
212a7ec083 | ||
|
|
cee94fe113 | ||
|
|
e1ead2519a | ||
|
|
80330a25ac | ||
|
|
508470e970 | ||
|
|
bc64b4b5c7 | ||
|
|
48d8494ead | ||
|
|
2a51d2ed96 | ||
|
|
cfabcc7bf6 | ||
|
|
90fc8fd0fc | ||
|
|
ff54533e33 | ||
|
|
2072f0b5ba | ||
|
|
6efc720a45 | ||
|
|
c7cb1efe9c | ||
|
|
e193531246 | ||
|
|
2aa53f414e | ||
|
|
843c15d754 | ||
|
|
fb19582088 | ||
|
|
8040466cbe | ||
|
|
302b4d7acd | ||
|
|
a8ccc24d46 | ||
|
|
b2fb071a7b | ||
|
|
204d7b97d5 | ||
|
|
f3da709aac | ||
|
|
0ab8afb838 | ||
|
|
d50999547f | ||
|
|
04741b0eba | ||
|
|
ba86fe32e7 | ||
|
|
9deb7d7fdc | ||
|
|
55eb64e5ca | ||
|
|
a5f50f3f2b | ||
|
|
47214eb5b3 | ||
|
|
599509d417 | ||
|
|
9d366fc359 | ||
|
|
0e1ed0bde6 | ||
|
|
6ad7824bf2 | ||
|
|
1174f51c07 | ||
|
|
1950dda1ab | ||
|
|
8231b6d5be | ||
|
|
0feacbe6eb | ||
|
|
80b5f5adca | ||
|
|
13650f36e6 | ||
|
|
3f58d99069 | ||
|
|
0c8a025cf6 | ||
|
|
5014df4859 | ||
|
|
34a491e2ef | ||
|
|
884e4ca88e | ||
|
|
a670c5e86c | ||
|
|
af1fba79be | ||
|
|
ac44de0bf4 | ||
|
|
f013a241ce | ||
|
|
0e29a7206d | ||
|
|
689b3f299c | ||
|
|
02ccb990bd | ||
|
|
61fe4f0d57 | ||
|
|
0a920195d5 | ||
|
|
18896bf56d | ||
|
|
098c9041a0 | ||
|
|
61a41d8eb2 | ||
|
|
e76073d5c8 | ||
|
|
8c34added7 | ||
|
|
66fc6b93ae | ||
|
|
881d5a8008 | ||
|
|
5d263de954 | ||
|
|
c8d0494000 | ||
|
|
a9b48b5aa5 | ||
|
|
f08a951eef | ||
|
|
8758a4bc86 | ||
|
|
aae328f8c8 | ||
|
|
1953578a33 | ||
|
|
543bdd79d9 | ||
|
|
e0e1a3c8e4 | ||
|
|
f1d84f448e | ||
|
|
7c5c21f15d | ||
|
|
41ffaeebe3 | ||
|
|
5d9b44b647 | ||
|
|
a18d2c5650 | ||
|
|
e0379bcf12 | ||
|
|
e91242d5a2 | ||
|
|
8177187b3a | ||
|
|
6b3e1144bc | ||
|
|
dfec88f52d | ||
|
|
b8df67659a | ||
|
|
861da64581 | ||
|
|
ab147a2cc9 | ||
|
|
e13191e894 | ||
|
|
7f69ea8dc0 | ||
|
|
ef2140696b | ||
|
|
4607900c3b | ||
|
|
3258d55796 | ||
|
|
35e6966c39 | ||
|
|
885756b259 | ||
|
|
5fbc1b937c | ||
|
|
7e444e9fc2 | ||
|
|
c051237914 | ||
|
|
3855b0dd28 | ||
|
|
afcc9e096a | ||
|
|
f4df1fbff4 | ||
|
|
45b3a5af91 | ||
|
|
f54b18e652 | ||
|
|
b1210d19ad | ||
|
|
21cbcc79c6 | ||
|
|
a7d0c8fb0f | ||
|
|
1e3dc54d81 | ||
|
|
48f294fd83 | ||
|
|
298ad0de4b | ||
|
|
c7953f9231 | ||
|
|
afd97eae7d | ||
|
|
f4e558b7e8 | ||
|
|
12c99c646e | ||
|
|
6c1a2eedbe | ||
|
|
8a73216035 | ||
|
|
c6a93f12f7 | ||
|
|
09f44d94b3 | ||
|
|
c26748154a | ||
|
|
2474f915d4 | ||
|
|
53f940cd23 | ||
|
|
991b648854 | ||
|
|
663f057a9a | ||
|
|
61963fb824 | ||
|
|
bdf3cf5b36 | ||
|
|
5cc459594b | ||
|
|
8d315e52df | ||
|
|
48a24a8704 | ||
|
|
cdce52f0e5 | ||
|
|
d12ccbeac4 | ||
|
|
0b1620105a | ||
|
|
2ae9c98acb | ||
|
|
ed00848a1e | ||
|
|
06f7741dbf | ||
|
|
8d3b7cace8 | ||
|
|
8f0775e337 | ||
|
|
444cb6aa0c | ||
|
|
b4acc19ea2 | ||
|
|
1ef17cd861 | ||
|
|
e564e930e5 | ||
|
|
a30badbbe0 | ||
|
|
b33d21fdb3 | ||
|
|
78da83f7db | ||
|
|
8f6313d4ec | ||
|
|
14962a5622 | ||
|
|
b8048e7592 | ||
|
|
cf9823e123 | ||
|
|
1667dbfde0 | ||
|
|
416436a612 | ||
|
|
dc1b724d8d | ||
|
|
080dc44175 | ||
|
|
be148297a2 | ||
|
|
6cf6d8c876 | ||
|
|
3921f50feb | ||
|
|
6fc63be56a | ||
|
|
6a03f9a6fe | ||
|
|
721fdf09b3 | ||
|
|
bd4a52318b | ||
|
|
3978865902 | ||
|
|
c1228ee426 | ||
|
|
d0c39dc932 | ||
|
|
63a8586d7c | ||
|
|
e0a79c033e | ||
|
|
fa12f127ce | ||
|
|
10916eadd5 | ||
|
|
7f4e8e9c8f | ||
|
|
d06840f934 | ||
|
|
75f4df8b51 | ||
|
|
e9ea6d27ae | ||
|
|
48019d43c3 | ||
|
|
04dbeb633d | ||
|
|
71631865c4 | ||
|
|
d4a39cf481 | ||
|
|
3a71dfff64 | ||
|
|
d8c865b3ce | ||
|
|
71356b798c | ||
|
|
66ddd1741f | ||
|
|
8a60f3c8a7 | ||
|
|
25d2a40c50 | ||
|
|
17b389146c | ||
|
|
bd1f609b39 | ||
|
|
01e1831d57 | ||
|
|
c341baa781 | ||
|
|
995a0c33c3 | ||
|
|
75845cb42d | ||
|
|
822d6acfb0 | ||
|
|
2c4510a717 | ||
|
|
ac2391c91a | ||
|
|
e805563ce5 | ||
|
|
88fb1d920e | ||
|
|
dca60fad7a | ||
|
|
8226b05e7e | ||
|
|
0106331978 | ||
|
|
f5c0e7d2e9 | ||
|
|
4568b24351 | ||
|
|
042502f41f | ||
|
|
d342d73818 | ||
|
|
3b922216c1 | ||
|
|
10fa9b6812 | ||
|
|
33ccbf790b | ||
|
|
50b4baee4b | ||
|
|
be57a56095 | ||
|
|
f351453b9c | ||
|
|
dda67d3351 | ||
|
|
23e1e744e8 | ||
|
|
4b0affe182 | ||
|
|
ab836bc747 | ||
|
|
cf86d7e352 | ||
|
|
05a36d3878 | ||
|
|
0417084a39 | ||
|
|
cdfe39f226 | ||
|
|
b73dde3a48 | ||
|
|
0dea597226 | ||
|
|
2f38928c89 | ||
|
|
35c7b5e952 | ||
|
|
ba28b17263 | ||
|
|
51b0e004fa | ||
|
|
8cb59b02a8 | ||
|
|
38bfd130a3 | ||
|
|
369d90e057 | ||
|
|
c27cdd1734 | ||
|
|
e7e4f39311 | ||
|
|
0443a21e05 | ||
|
|
50c01886ec | ||
|
|
a9e1219f6c | ||
|
|
7bc31dde70 | ||
|
|
65f2f1d08f | ||
|
|
684027eaab | ||
|
|
1c3ec9c3bb | ||
|
|
3a8ff2c05d | ||
|
|
d5bd179c68 | ||
|
|
8b938a39cf | ||
|
|
c9610cbc39 | ||
|
|
931733d605 | ||
|
|
44e5d0e195 | ||
|
|
08b83dc3fd | ||
|
|
b7f261a836 | ||
|
|
d0b4ca33c2 | ||
|
|
160391f5a9 | ||
|
|
dfe4a96b02 | ||
|
|
a3f67eb519 | ||
|
|
0f9d52552b | ||
|
|
a217de4c39 | ||
|
|
d2d85e63f6 | ||
|
|
7a6077b5ff | ||
|
|
d48c4d9729 | ||
|
|
6d677401bf | ||
|
|
a3d4fa2f86 | ||
|
|
59e19b6a22 | ||
|
|
1a76da40d1 | ||
|
|
cb15ba01f0 | ||
|
|
78af7f136e | ||
|
|
cc6a95b579 | ||
|
|
4b3f723bdc | ||
|
|
d372e2ff76 | ||
|
|
4201d1cb1e | ||
|
|
afed70ba63 | ||
|
|
be488346c5 | ||
|
|
eeeb688439 | ||
|
|
b84ce77326 | ||
|
|
30fca423dc | ||
|
|
fabbb31572 | ||
|
|
ac76ac004e | ||
|
|
9d2051183a | ||
|
|
942fdb51d5 | ||
|
|
d2600a3168 | ||
|
|
c4248cce22 | ||
|
|
16f16f9fed | ||
|
|
d49cb976bc | ||
|
|
6fae6a9865 | ||
|
|
06f3730756 | ||
|
|
30e1333f75 | ||
|
|
0d6fa98767 | ||
|
|
ae6c9edd0d | ||
|
|
35de1f1c4e | ||
|
|
57142f4afb | ||
|
|
cd72d65b89 | ||
|
|
2199ab0513 | ||
|
|
e93f058109 | ||
|
|
b68de49cbd | ||
|
|
3f05934b6b | ||
|
|
3a5713dbb7 | ||
|
|
4c43158285 | ||
|
|
daa743b3b3 | ||
|
|
41f0ae18c4 | ||
|
|
e6b8aefe5b | ||
|
|
8b2437cb16 | ||
|
|
292495ab0d | ||
|
|
017b137d7f | ||
|
|
7969030313 | ||
|
|
c8efad4c3f | ||
|
|
7bf9d8f675 | ||
|
|
e275f15f00 | ||
|
|
30017a5217 | ||
|
|
64c5cbe8c3 | ||
|
|
b2b226573c | ||
|
|
69f796998f | ||
|
|
4d64be3ac7 | ||
|
|
4408b794d6 | ||
|
|
666da8a879 | ||
|
|
b166342579 | ||
|
|
433f5bf7d2 | ||
|
|
b8ae153ef5 | ||
|
|
bb59c2bab7 | ||
|
|
ab7c6c5118 | ||
|
|
85c1ea449e | ||
|
|
b51d679b78 | ||
|
|
2a2bc9e625 | ||
|
|
d00f059567 | ||
|
|
81a840347c | ||
|
|
e691675bf9 | ||
|
|
9cd57c3ae1 | ||
|
|
0e3310a39b | ||
|
|
447818ac2a | ||
|
|
dd0eb846b0 | ||
|
|
1b62ca4b21 | ||
|
|
97ece03853 | ||
|
|
33a72a0d9d | ||
|
|
62e61829b4 | ||
|
|
ad9d12260e | ||
|
|
fe54c9495f | ||
|
|
456afce6ca | ||
|
|
8ecbb4d4a4 | ||
|
|
92564397f4 | ||
|
|
62b2805c9d | ||
|
|
985af8b0bb | ||
|
|
58c3b180dc | ||
|
|
d260959b96 | ||
|
|
6e11197348 | ||
|
|
04d5bef4d2 | ||
|
|
1ccc5ce4e1 | ||
|
|
a94ef1db80 | ||
|
|
5513c624c3 | ||
|
|
1c493a8dc0 | ||
|
|
3fa051a6c3 | ||
|
|
876171b83d | ||
|
|
68521298ff | ||
|
|
c648926e49 | ||
|
|
1cd259a261 | ||
|
|
ef1fe07157 | ||
|
|
9ae8f7f406 | ||
|
|
363cbaac32 | ||
|
|
435b2cf23c | ||
|
|
bd1a87f5f0 | ||
|
|
c57218adb2 | ||
|
|
a56d2a3eca | ||
|
|
b324130e30 | ||
|
|
566a6e5836 | ||
|
|
bcca407a5e | ||
|
|
10c6832718 | ||
|
|
1ad8db20b4 | ||
|
|
43233747be | ||
|
|
56354afa9b | ||
|
|
7f686a817b | ||
|
|
10534ff75a | ||
|
|
da39be79a4 | ||
|
|
8a1ca80e28 | ||
|
|
ca29a3a272 | ||
|
|
1a115bda82 | ||
|
|
130e49bf1d | ||
|
|
552c84f910 | ||
|
|
d1a0111705 | ||
|
|
5bc285041b | ||
|
|
c96a522a1b | ||
|
|
def2dadca1 | ||
|
|
46a52e2b2f | ||
|
|
1835776200 | ||
|
|
efaa4893bf | ||
|
|
be8580cc4b | ||
|
|
e43bb3123b | ||
|
|
3078e3584c | ||
|
|
6689849f97 | ||
|
|
7281ee1565 | ||
|
|
4c12ee3b14 | ||
|
|
dc0ae69f65 | ||
|
|
0dba4ba653 | ||
|
|
758d8689ab | ||
|
|
f119ccf5a0 | ||
|
|
c86c6486b9 | ||
|
|
6ed5c163ba | ||
|
|
2c14530e3c | ||
|
|
6d30e1921e | ||
|
|
7ff84a9932 | ||
|
|
bcff01b0bf | ||
|
|
8cd09342e1 | ||
|
|
802e78d3b7 | ||
|
|
e6c938e5d0 | ||
|
|
7b6595124f | ||
|
|
c33408a1f7 | ||
|
|
160d53701b | ||
|
|
1f19d1925a | ||
|
|
50680a4f2e | ||
|
|
18307b2e03 | ||
|
|
b03ad80451 | ||
|
|
dedfe1f421 | ||
|
|
1cf583d197 | ||
|
|
1deaa4c30d | ||
|
|
90316f106a | ||
|
|
63693f908d | ||
|
|
6905e4a2a1 | ||
|
|
4489a54e82 | ||
|
|
6d48915945 | ||
|
|
00f3a7f4db | ||
|
|
f9c47ab233 | ||
|
|
28712b205f | ||
|
|
69b1fb1bfd | ||
|
|
8a22f9215f | ||
|
|
c5ebc01978 | ||
|
|
f29b468fc1 | ||
|
|
e796fbb990 | ||
|
|
37a122c981 | ||
|
|
4f426a73f6 | ||
|
|
1bcb74cd85 | ||
|
|
cd88c8de78 | ||
|
|
eee288b45b | ||
|
|
797cb7615d | ||
|
|
ca4667ff1e | ||
|
|
66d1143ca0 | ||
|
|
f310916c76 | ||
|
|
5d3d8ab932 | ||
|
|
07117c90d1 | ||
|
|
fd91c18460 | ||
|
|
5d92c0e85e | ||
|
|
2a12c04518 | ||
|
|
d08cae6fa3 | ||
|
|
d7f9de1881 | ||
|
|
962190cc57 | ||
|
|
4527866276 | ||
|
|
088dfcd4dc | ||
|
|
6c317b6e64 | ||
|
|
6b66c273b4 | ||
|
|
60f31008c0 | ||
|
|
078f74db97 | ||
|
|
a0b025cf59 | ||
|
|
bc695f5af9 | ||
|
|
9685e63b09 | ||
|
|
142791360c | ||
|
|
e004ed2f4b | ||
|
|
23ed487252 | ||
|
|
efefec3c20 | ||
|
|
3d2ad1cb9b | ||
|
|
90d3016938 | ||
|
|
438f9fc94d | ||
|
|
82ec88cc2f | ||
|
|
149611041e | ||
|
|
b12c79462e | ||
|
|
fbf34fb730 | ||
|
|
e1fe3eb710 | ||
|
|
76ae2e0e5a | ||
|
|
a57063adf7 | ||
|
|
ff0157e624 | ||
|
|
af9701feb8 | ||
|
|
93c1f31588 | ||
|
|
1964e54476 | ||
|
|
4682255d5f | ||
|
|
a503898b21 | ||
|
|
21352dae07 | ||
|
|
8470c7ac6b | ||
|
|
28aa86f0aa | ||
|
|
3ed214269a | ||
|
|
37b5183be2 | ||
|
|
a79896aa2e | ||
|
|
d5bd40873e | ||
|
|
52f1809d22 | ||
|
|
51d8fa7268 | ||
|
|
4d1167a6d6 | ||
|
|
8fa1459e5b | ||
|
|
baf3914be8 | ||
|
|
bd2721d3ec | ||
|
|
f30b96b360 | ||
|
|
4772c0e110 | ||
|
|
4a0af08ae5 | ||
|
|
a71129df4b | ||
|
|
de6acfa1ce | ||
|
|
ccf075dc65 | ||
|
|
1d8ac3cf86 | ||
|
|
623a23492f | ||
|
|
7a8ff89c5c | ||
|
|
eda70def2a | ||
|
|
08fd75edc7 | ||
|
|
15ea53864f | ||
|
|
056ee0d58e | ||
|
|
377cd64556 | ||
|
|
b37744d574 | ||
|
|
a7f21fe0c6 | ||
|
|
955ca99cf3 | ||
|
|
98f5bb4124 | ||
|
|
b3943f005d | ||
|
|
8d4178b984 | ||
|
|
2a88ed38c4 | ||
|
|
52dce7dfd3 | ||
|
|
6ebee92542 | ||
|
|
1b5646f526 | ||
|
|
7024e4b40d | ||
|
|
bc2e27d7da | ||
|
|
189da2bfe2 | ||
|
|
12e6afbaad | ||
|
|
142ebe3d27 | ||
|
|
7579f6e42a | ||
|
|
38c25cae74 | ||
|
|
408496eb7c | ||
|
|
4d61c74a8b | ||
|
|
190c610466 | ||
|
|
85b7e3ebe3 | ||
|
|
c6d3fc06a3 | ||
|
|
d220525ac7 | ||
|
|
5e4a631ff2 | ||
|
|
9099ce42b9 | ||
|
|
df226fea22 | ||
|
|
851d2e9151 | ||
|
|
e67ee4ffdb | ||
|
|
2baf975847 | ||
|
|
c1672ebc8e | ||
|
|
bbbd291065 | ||
|
|
0a3c1efdd4 | ||
|
|
89121a2608 | ||
|
|
23cf264d4d | ||
|
|
b3130225b5 | ||
|
|
65512defed | ||
|
|
3b1c8748f1 | ||
|
|
aba660eddb | ||
|
|
137eac7dbf | ||
|
|
fdbd08f511 | ||
|
|
ace1cec1f6 | ||
|
|
0c15e524d7 | ||
|
|
b0b5b1c30d | ||
|
|
30b4c85c5a | ||
|
|
910f9cadfe | ||
|
|
6de37ebd16 | ||
|
|
de57c4e87e | ||
|
|
b85cf66490 | ||
|
|
8e638ea9a6 | ||
|
|
b4849ec495 | ||
|
|
09c12d52ac | ||
|
|
db6a2ddd7e | ||
|
|
12ef9463ab | ||
|
|
fa5fda0c3b | ||
|
|
251609e274 | ||
|
|
a557ad177e | ||
|
|
c0287e49d8 | ||
|
|
78e838f2f0 | ||
|
|
c1f216c7c7 | ||
|
|
b75ff99e4c | ||
|
|
780dd8ade9 | ||
|
|
e1c10b7653 | ||
|
|
be9505f8fe | ||
|
|
d6bcd4f94f | ||
|
|
7d2196f4c3 | ||
|
|
0539174317 | ||
|
|
b4b52e12d5 | ||
|
|
f2e0b1cfa2 | ||
|
|
8020e2a263 | ||
|
|
6112d9b1b0 | ||
|
|
4a1fbcbd31 | ||
|
|
a02a3230f1 | ||
|
|
0218bb4990 | ||
|
|
3769c03565 | ||
|
|
d96cb10476 | ||
|
|
b6b6123434 | ||
|
|
b40877fcc1 | ||
|
|
af5ae29b73 | ||
|
|
082fceebbe | ||
|
|
f1dab80a06 | ||
|
|
cbf2fac2cf | ||
|
|
4564bd7180 | ||
|
|
d12ad7b882 | ||
|
|
f8e9f07a00 | ||
|
|
4ac3891e07 | ||
|
|
a2d55d3fdd | ||
|
|
d015a24300 | ||
|
|
be38acbede | ||
|
|
34d0cb4dc7 | ||
|
|
18d0558b19 | ||
|
|
d4469f3a2d | ||
|
|
43b760b4bf | ||
|
|
3d47932c09 | ||
|
|
7196d6e1bf | ||
|
|
ec06a7d861 | ||
|
|
ef23f0d18e | ||
|
|
e9abc5f07f | ||
|
|
6f9d6ff849 | ||
|
|
eab18d3c11 | ||
|
|
3a509a6a97 | ||
|
|
615c6f4e24 | ||
|
|
96660b4539 | ||
|
|
1be284974b | ||
|
|
790b6478dc | ||
|
|
106344b33e | ||
|
|
85a2a4b873 | ||
|
|
4ab694de0c | ||
|
|
5e193c1725 | ||
|
|
2b055c028c | ||
|
|
b341749e45 | ||
|
|
6ae381b1fd | ||
|
|
352b6cbe04 | ||
|
|
e5e6d2701e | ||
|
|
9ad1924488 | ||
|
|
2aadbfc64a | ||
|
|
1c5d652f93 | ||
|
|
b2355a3b2d | ||
|
|
4ee6d089d5 | ||
|
|
6bd81cbff5 | ||
|
|
b912190c5e | ||
|
|
43a826e2e5 | ||
|
|
8ae64a9dcf | ||
|
|
4ce9faf39b | ||
|
|
d94a67d0af | ||
|
|
d650d91d82 | ||
|
|
e14f59256d | ||
|
|
d3322a4a15 | ||
|
|
a65842e31f | ||
|
|
74fde66b51 | ||
|
|
c3ea155a7b | ||
|
|
c4b81e3d2c | ||
|
|
6f6ed1a741 | ||
|
|
311680c090 | ||
|
|
5e54aa553a | ||
|
|
6913970830 | ||
|
|
014e453e57 | ||
|
|
25b5341f76 | ||
|
|
1df51f9609 | ||
|
|
65d13189b3 | ||
|
|
0913011120 | ||
|
|
30ddc18eb1 | ||
|
|
697d755744 | ||
|
|
e3f23ddc79 | ||
|
|
094acc40e8 | ||
|
|
ebd4991de8 | ||
|
|
d04a8fad4c | ||
|
|
cb14bffc5a | ||
|
|
a4c4d17381 | ||
|
|
21eb27f6e3 | ||
|
|
abf0fc7942 | ||
|
|
c2703edfde | ||
|
|
d14b90ab20 | ||
|
|
48f4924932 | ||
|
|
765551988a | ||
|
|
4883eb0d1b | ||
|
|
06a9a93d1c | ||
|
|
c92a0e1d43 | ||
|
|
c6b5ee164b | ||
|
|
c65075f887 | ||
|
|
a1f678a3a1 | ||
|
|
fe3fefaa4e | ||
|
|
3ccebcb0d1 | ||
|
|
dbbae0eef2 | ||
|
|
f11c4881f3 | ||
|
|
6398c6d7ce | ||
|
|
973ce8c3a7 | ||
|
|
d971869283 | ||
|
|
987002b8f3 | ||
|
|
f73fe495a5 | ||
|
|
fe7b0e2bc7 | ||
|
|
23937c54e0 | ||
|
|
b3d0fd9d2f | ||
|
|
497aaf6143 | ||
|
|
9d6db3a93b | ||
|
|
8a6f3e6809 | ||
|
|
6c419716a4 | ||
|
|
d1a769205c | ||
|
|
b784e342c9 | ||
|
|
0d82fd51c7 | ||
|
|
3b4d905485 | ||
|
|
53c63f0f4b | ||
|
|
5553e3cd8d | ||
|
|
1e195e07e0 | ||
|
|
b6f0e15951 | ||
|
|
da224303fe | ||
|
|
67ee130a9e | ||
|
|
18d908fa63 | ||
|
|
c61f58854e | ||
|
|
f789ecd2f1 | ||
|
|
1fdc30804a | ||
|
|
5ba10d0acb | ||
|
|
12803d8154 | ||
|
|
36c391ccff | ||
|
|
765fb6297c | ||
|
|
66255769ad | ||
|
|
04a8d38641 | ||
|
|
859d020031 | ||
|
|
3c541117d0 | ||
|
|
80ca2e5215 | ||
|
|
19f2aa2997 | ||
|
|
ec657f30c7 | ||
|
|
7e84d495f5 | ||
|
|
c3baedd93c | ||
|
|
ae9676f744 | ||
|
|
7ec156a5d1 | ||
|
|
b80cbea1bc | ||
|
|
4600fa9f32 | ||
|
|
6e0b3e5cdc | ||
|
|
519ff87f5d | ||
|
|
d4a363e37e | ||
|
|
a3cfc45fef | ||
|
|
60602e02d9 | ||
|
|
44366f7872 | ||
|
|
2f18d8c204 | ||
|
|
08efbee52b | ||
|
|
eac8d78c5d | ||
|
|
db73673374 | ||
|
|
281cdb7264 | ||
|
|
101c80d820 | ||
|
|
1e06f65d9e | ||
|
|
eea85709ed | ||
|
|
cbca974529 | ||
|
|
fc27f57580 | ||
|
|
e50dd6606e | ||
|
|
8c9e232d65 | ||
|
|
ef98888394 | ||
|
|
e4b9ba34df | ||
|
|
169e0ec9df | ||
|
|
2779353a32 | ||
|
|
35aabb987c | ||
|
|
06f02070c7 | ||
|
|
d138d3e786 | ||
|
|
dbea68d33a | ||
|
|
72bcabf615 | ||
|
|
5db68eac24 | ||
|
|
7d9d88860e | ||
|
|
06c80ad982 | ||
|
|
c4335527f8 | ||
|
|
90d27a2ad8 | ||
|
|
93fd0a9af0 | ||
|
|
987b0aeb41 | ||
|
|
8dc5ac0b25 | ||
|
|
d310a47523 | ||
|
|
bd8fa3776d | ||
|
|
305796af53 | ||
|
|
60c10a69a3 | ||
|
|
c8aad9839f | ||
|
|
64016a1326 | ||
|
|
a489c7ad8e | ||
|
|
afb9ba7ad6 | ||
|
|
b1de5b1120 | ||
|
|
144a23e89b | ||
|
|
a6763a3e5d | ||
|
|
f047ec787a | ||
|
|
d80c368ccb | ||
|
|
9b60173b8c | ||
|
|
8556974ef1 | ||
|
|
edf9f3a2be | ||
|
|
7bb9414be8 | ||
|
|
6e439adb51 | ||
|
|
f6b783e74a | ||
|
|
171b81461c | ||
|
|
83db76aed8 | ||
|
|
e6d1bb7e5c | ||
|
|
a3d9fe76d6 | ||
|
|
0f4f154637 | ||
|
|
7d112a208f | ||
|
|
c867d39d8d | ||
|
|
83b6c939f7 | ||
|
|
e1e4eb5d6f | ||
|
|
a14c08f122 | ||
|
|
5fd50dcf45 | ||
|
|
b2ac4ee245 | ||
|
|
0ad7c0546b | ||
|
|
748381fef3 | ||
|
|
7eb9e42210 | ||
|
|
36a4b67ef4 | ||
|
|
94b35e3d5f | ||
|
|
c5aa82b76a | ||
|
|
c2920e195f | ||
|
|
92a78a419e | ||
|
|
4f27c2b852 | ||
|
|
ece5779b41 | ||
|
|
167aaa8491 | ||
|
|
de622b055b | ||
|
|
7e88b930ec | ||
|
|
c9adc2b852 | ||
|
|
91d3aba611 | ||
|
|
48bc11dcb5 | ||
|
|
9ee0b32cac | ||
|
|
4b6b74604b | ||
|
|
7ad3fc4751 | ||
|
|
e604450cfb | ||
|
|
9cfbc83896 | ||
|
|
98b2f4ec35 | ||
|
|
47e30f2daf | ||
|
|
6c21da6959 | ||
|
|
c5fe71d390 | ||
|
|
d14ffcb736 | ||
|
|
e694aca70b | ||
|
|
3c58cb1b9c | ||
|
|
dc452cdadf | ||
|
|
e855365cbb | ||
|
|
cffa288227 | ||
|
|
89ddced342 | ||
|
|
7457328b59 | ||
|
|
44ff413810 | ||
|
|
d174c2c2d8 | ||
|
|
480f4c9a8c | ||
|
|
7474cc5d8a | ||
|
|
bda5c4f5dd | ||
|
|
4f5034c167 | ||
|
|
9470db1f4d | ||
|
|
dfeb910ac9 | ||
|
|
a5745795be | ||
|
|
98f2b5dd08 | ||
|
|
f642c7570e | ||
|
|
dca9ea24d7 | ||
|
|
43fd7b6000 | ||
|
|
1553ec3bd4 | ||
|
|
37d54811e0 | ||
|
|
0f2af6eb37 | ||
|
|
91546228fa | ||
|
|
5488ff06e0 | ||
|
|
ef7f050bc5 | ||
|
|
e5c94d9698 | ||
|
|
4d3e0ac5d9 | ||
|
|
0a2f0372be | ||
|
|
e378fc3cfb | ||
|
|
75a1d74d9c | ||
|
|
1404685296 | ||
|
|
0ebed9b46f | ||
|
|
af69352361 | ||
|
|
840e3b861f | ||
|
|
a7cfe7fe04 | ||
|
|
a324cf0fcd | ||
|
|
0919f4c85b | ||
|
|
fd4cc6a1e8 | ||
|
|
d6ae3d4f16 | ||
|
|
9c1819467a | ||
|
|
08410ef209 | ||
|
|
6cd3242454 | ||
|
|
986c1a7bc8 | ||
|
|
a7703ec996 | ||
|
|
52d2eb3f59 | ||
|
|
b19fed41ef | ||
|
|
82a4b2c769 | ||
|
|
1d52cde3b3 | ||
|
|
aca4cc0ace | ||
|
|
8f9a78feb9 | ||
|
|
45484a43f1 | ||
|
|
63665e6e9c | ||
|
|
151665c880 | ||
|
|
15e475873e | ||
|
|
f846af75e8 | ||
|
|
5c7ab3793f | ||
|
|
a501f0cdef | ||
|
|
4b4c2606cd | ||
|
|
901e7581fe | ||
|
|
55fb233ce4 | ||
|
|
dd77ee4a8e | ||
|
|
a7ae6d7b47 | ||
|
|
34dfac8bb2 | ||
|
|
599f783c04 | ||
|
|
c7f82d3d46 | ||
|
|
8b346c6b44 | ||
|
|
e386764151 | ||
|
|
c0acecc6e9 | ||
|
|
a1724134ec | ||
|
|
530b830586 | ||
|
|
363a72c3ad | ||
|
|
c1a6daf9d2 | ||
|
|
8a7a73678c | ||
|
|
6cc8c7cb9d | ||
|
|
e2d1771a7a | ||
|
|
23f7dd6ee3 | ||
|
|
9884ace309 | ||
|
|
70284ac440 | ||
|
|
f24caad997 | ||
|
|
32729350f6 | ||
|
|
4929d190a5 | ||
|
|
0e211dc91b | ||
|
|
8663ab2d28 | ||
|
|
ba0ecaf70f | ||
|
|
acb4f5924e | ||
|
|
46553a80ad | ||
|
|
417334d140 | ||
|
|
e254657813 | ||
|
|
b087df8d97 | ||
|
|
47eb74d5ba | ||
|
|
f0ac047978 | ||
|
|
e57e246991 | ||
|
|
6934cdd122 | ||
|
|
aa7fb74312 | ||
|
|
0e2a77ced7 | ||
|
|
2d135d708e | ||
|
|
26d34f896b | ||
|
|
87c58cae83 | ||
|
|
8429067ae5 | ||
|
|
c514a4d503 | ||
|
|
391d04b45c | ||
|
|
f974c00a63 | ||
|
|
9f0107c002 | ||
|
|
6ce82e915e | ||
|
|
864797fc99 | ||
|
|
fc9677f419 | ||
|
|
975a551728 |
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,12 +1,14 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve DbGate
|
||||
about: Create a report to help us improve DbGate (in ENGLISH)
|
||||
title: 'BUG: Say something here'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please keep communication in ENGLISH to reach more contributors.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,12 +1,14 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for DbGate
|
||||
about: Suggest an idea for DbGate (in ENGLISH)
|
||||
title: 'FEAT: '
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please keep communication in ENGLISH to reach more contributors.
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/question.md
vendored
4
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,12 +1,14 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about how to do something
|
||||
about: Ask a question about how to do something (in ENGLISH)
|
||||
title: 'QUESTION: Summary of your question'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please keep communication in ENGLISH to reach more contributors.
|
||||
|
||||
**Details:**
|
||||
Details about your question
|
||||
|
||||
|
||||
50
.github/workflows/build-app-beta.yaml
vendored
50
.github/workflows/build-app-beta.yaml
vendored
@@ -6,9 +6,13 @@ name: Electron app BETA
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: dbgate-app
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -17,6 +21,10 @@ jobs:
|
||||
- windows-2022
|
||||
- ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: python -m pip install --upgrade pip setuptools
|
||||
- name: Install python 3.11 (MacOS)
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
@@ -60,21 +68,53 @@ jobs:
|
||||
- name: Install Snapcraft
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
uses: samuelmeuli/action-snapcraft@v1
|
||||
- name: Publish
|
||||
- name: Publish Windows
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
|
||||
yarn run build:app
|
||||
- name: Publish MacOS
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
|
||||
yarn run build:app
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
|
||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.APPLECERT_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLECERT_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{secrets.APPLE_APP_SPECIFIC_PASSWORD}}
|
||||
- name: Publish Linux
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
|
||||
yarn run build:app
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
- name: Azure login (OIDC)
|
||||
uses: azure/login@v2
|
||||
if: matrix.os == 'windows-2022'
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_TC_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TC_TENANT_ID }}
|
||||
allow-no-subscriptions: true
|
||||
- name: Sign Windows artifacts with Azure Trusted Signing
|
||||
uses: azure/trusted-signing-action@v0
|
||||
if: matrix.os == 'windows-2022'
|
||||
with:
|
||||
endpoint: https://wus3.codesigning.azure.net/
|
||||
trusted-signing-account-name: DbGate
|
||||
certificate-profile-name: DbGate-Release
|
||||
files-folder: app/dist
|
||||
files-folder-filter: exe
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
- name: Fix YML hashes
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
|
||||
yarn run fixYmlHashes
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
mkdir artifacts
|
||||
|
||||
50
.github/workflows/build-app-check.yaml
vendored
50
.github/workflows/build-app-check.yaml
vendored
@@ -6,9 +6,13 @@ name: Electron app check build
|
||||
push:
|
||||
tags:
|
||||
- check-[0-9]+-[0-9]+-[0-9]+.[0-9]+
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: dbgate-app
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -17,6 +21,10 @@ jobs:
|
||||
- windows-2022
|
||||
- ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: python -m pip install --upgrade pip setuptools
|
||||
- name: Install python 3.11 (MacOS)
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
@@ -56,21 +64,53 @@ jobs:
|
||||
- name: Install Snapcraft
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
uses: samuelmeuli/action-snapcraft@v1
|
||||
- name: Publish
|
||||
- name: Publish Windows
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
|
||||
yarn run build:app
|
||||
- name: Publish MacOS
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
|
||||
yarn run build:app
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
|
||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.APPLECERT_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLECERT_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{secrets.APPLE_APP_SPECIFIC_PASSWORD}}
|
||||
- name: Publish Linux
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
|
||||
yarn run build:app
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
- name: Azure login (OIDC)
|
||||
uses: azure/login@v2
|
||||
if: matrix.os == 'windows-2022'
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_TC_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TC_TENANT_ID }}
|
||||
allow-no-subscriptions: true
|
||||
- name: Sign Windows artifacts with Azure Trusted Signing
|
||||
uses: azure/trusted-signing-action@v0
|
||||
if: matrix.os == 'windows-2022'
|
||||
with:
|
||||
endpoint: https://wus3.codesigning.azure.net/
|
||||
trusted-signing-account-name: DbGate
|
||||
certificate-profile-name: DbGate-Release
|
||||
files-folder: app/dist
|
||||
files-folder-filter: exe
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
- name: Fix YML hashes
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
|
||||
yarn run fixYmlHashes
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
mkdir artifacts
|
||||
|
||||
58
.github/workflows/build-app-pro-beta.yaml
vendored
58
.github/workflows/build-app-pro-beta.yaml
vendored
@@ -6,9 +6,13 @@ name: Electron app PREMIUM BETA
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+-premium-beta.[0-9]+
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: dbgate-app
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -17,6 +21,10 @@ jobs:
|
||||
- windows-2022
|
||||
- ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: python -m pip install --upgrade pip setuptools
|
||||
- name: Install python 3.11 (MacOS)
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
@@ -39,7 +47,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 11203754aad94189b565c2816d37760b15c8e07f
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
@@ -87,23 +95,61 @@ jobs:
|
||||
cd dbgate-merged
|
||||
|
||||
yarn fillPackagedPlugins
|
||||
- name: Publish
|
||||
- name: Publish Windows
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
|
||||
yarn run build:app
|
||||
- name: Publish MacOS
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
|
||||
yarn run build:app
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
|
||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.APPLECERT_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLECERT_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{secrets.APPLE_APP_SPECIFIC_PASSWORD}}
|
||||
- name: Publish Linux
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
|
||||
yarn run build:app
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
- name: Azure login (OIDC)
|
||||
uses: azure/login@v2
|
||||
if: matrix.os == 'windows-2022'
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_TC_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TC_TENANT_ID }}
|
||||
allow-no-subscriptions: true
|
||||
- name: Sign Windows artifacts with Azure Trusted Signing
|
||||
uses: azure/trusted-signing-action@v0
|
||||
if: matrix.os == 'windows-2022'
|
||||
with:
|
||||
endpoint: https://wus3.codesigning.azure.net/
|
||||
trusted-signing-account-name: DbGate
|
||||
certificate-profile-name: DbGate-Release
|
||||
files-folder: ../dbgate-merged/app/dist
|
||||
files-folder-filter: exe
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
- name: Fix YML hashes
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
|
||||
yarn run fixYmlHashes
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
mkdir artifacts
|
||||
|
||||
58
.github/workflows/build-app-pro.yaml
vendored
58
.github/workflows/build-app-pro.yaml
vendored
@@ -6,9 +6,13 @@ name: Electron app PREMIUM
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: dbgate-app
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -17,6 +21,10 @@ jobs:
|
||||
- windows-2022
|
||||
- ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: python -m pip install --upgrade pip setuptools
|
||||
- name: Install python 3.11 (MacOS)
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
@@ -39,7 +47,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 11203754aad94189b565c2816d37760b15c8e07f
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
@@ -87,23 +95,61 @@ jobs:
|
||||
cd dbgate-merged
|
||||
|
||||
yarn fillPackagedPlugins
|
||||
- name: Publish
|
||||
- name: Publish Windows
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
|
||||
yarn run build:app
|
||||
- name: Publish MacOS
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
|
||||
yarn run build:app
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
|
||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.APPLECERT_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLECERT_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{secrets.APPLE_APP_SPECIFIC_PASSWORD}}
|
||||
- name: Publish Linux
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
|
||||
yarn run build:app
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
- name: Azure login (OIDC)
|
||||
uses: azure/login@v2
|
||||
if: matrix.os == 'windows-2022'
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_TC_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TC_TENANT_ID }}
|
||||
allow-no-subscriptions: true
|
||||
- name: Sign Windows artifacts with Azure Trusted Signing
|
||||
uses: azure/trusted-signing-action@v0
|
||||
if: matrix.os == 'windows-2022'
|
||||
with:
|
||||
endpoint: https://wus3.codesigning.azure.net/
|
||||
trusted-signing-account-name: DbGate
|
||||
certificate-profile-name: DbGate-Release
|
||||
files-folder: ../dbgate-merged/app/dist
|
||||
files-folder-filter: exe
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
- name: Fix YML hashes
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
|
||||
yarn run fixYmlHashes
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
mkdir artifacts
|
||||
|
||||
50
.github/workflows/build-app.yaml
vendored
50
.github/workflows/build-app.yaml
vendored
@@ -6,9 +6,13 @@ name: Electron app
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: dbgate-app
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -17,6 +21,10 @@ jobs:
|
||||
- windows-2022
|
||||
- ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: python -m pip install --upgrade pip setuptools
|
||||
- name: Install python 3.11 (MacOS)
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
@@ -56,24 +64,56 @@ jobs:
|
||||
- name: Install Snapcraft
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
uses: samuelmeuli/action-snapcraft@v1
|
||||
- name: Publish
|
||||
- name: Publish Windows
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
|
||||
yarn run build:app
|
||||
- name: Publish MacOS
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
|
||||
yarn run build:app
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
|
||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.APPLECERT_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLECERT_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{secrets.APPLE_APP_SPECIFIC_PASSWORD}}
|
||||
- name: Publish Linux
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
|
||||
yarn run build:app
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
- name: generatePadFile
|
||||
run: |
|
||||
yarn generatePadFile
|
||||
- name: Azure login (OIDC)
|
||||
uses: azure/login@v2
|
||||
if: matrix.os == 'windows-2022'
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_TC_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TC_TENANT_ID }}
|
||||
allow-no-subscriptions: true
|
||||
- name: Sign Windows artifacts with Azure Trusted Signing
|
||||
uses: azure/trusted-signing-action@v0
|
||||
if: matrix.os == 'windows-2022'
|
||||
with:
|
||||
endpoint: https://wus3.codesigning.azure.net/
|
||||
trusted-signing-account-name: DbGate
|
||||
certificate-profile-name: DbGate-Release
|
||||
files-folder: app/dist
|
||||
files-folder-filter: exe
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
- name: Fix YML hashes
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
|
||||
yarn run fixYmlHashes
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
mkdir artifacts
|
||||
|
||||
20
.github/workflows/build-cloud-pro.yaml
vendored
20
.github/workflows/build-cloud-pro.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 11203754aad94189b565c2816d37760b15c8e07f
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
@@ -90,14 +90,6 @@ jobs:
|
||||
prerelease: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run `packer init` for Azure
|
||||
run: |
|
||||
cd ../dbgate-merged/packer
|
||||
packer init ./azure-ubuntu.pkr.hcl
|
||||
- name: Run `packer build` for Azure
|
||||
run: |
|
||||
cd ../dbgate-merged/packer
|
||||
packer build ./azure-ubuntu.pkr.hcl
|
||||
- name: Run `packer init` for AWS
|
||||
run: |
|
||||
cd ../dbgate-merged/packer
|
||||
@@ -114,16 +106,6 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}}
|
||||
AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}}
|
||||
AWS_DEFAULT_REGION: ${{secrets.AWS_DEFAULT_REGION}}
|
||||
- name: Delete old Azure VMs
|
||||
run: |
|
||||
cd ../dbgate-merged/packer
|
||||
chmod +x delete-old-azure-images.sh
|
||||
./delete-old-azure-images.sh
|
||||
env:
|
||||
AZURE_CLIENT_ID: ${{secrets.AZURE_CLIENT_ID}}
|
||||
AZURE_CLIENT_SECRET: ${{secrets.AZURE_CLIENT_SECRET}}
|
||||
AZURE_TENANT_ID: ${{secrets.AZURE_TENANT_ID}}
|
||||
AZURE_SUBSCRIPTION_ID: ${{secrets.AZURE_SUBSCRIPTION_ID}}
|
||||
- name: Delete old AMIs (AWS)
|
||||
run: |
|
||||
cd ../dbgate-merged/packer
|
||||
|
||||
2
.github/workflows/build-docker-pro.yaml
vendored
2
.github/workflows/build-docker-pro.yaml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 11203754aad94189b565c2816d37760b15c8e07f
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
31
.github/workflows/build-npm-pro.yaml
vendored
31
.github/workflows/build-npm-pro.yaml
vendored
@@ -7,6 +7,9 @@ name: NPM packages PREMIUM
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
- v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -32,7 +35,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 11203754aad94189b565c2816d37760b15c8e07f
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
@@ -49,13 +52,8 @@ jobs:
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
node adjustNpmPackageJsonPremium
|
||||
- name: Configure NPM token
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
|
||||
- name: Update npm
|
||||
run: npm install -g npm@11.5.1
|
||||
- name: Remove dbmodel - should be not published
|
||||
run: |
|
||||
cd ..
|
||||
@@ -71,28 +69,35 @@ jobs:
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
yarn setCurrentVersion
|
||||
- name: Compute npm dist-tag
|
||||
run: |
|
||||
if [[ "${GITHUB_REF_NAME}" =~ -alpha\. ]]; then
|
||||
echo "NPM_TAG=alpha" >> $GITHUB_ENV
|
||||
else
|
||||
echo "NPM_TAG=latest" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Publish dbgate-api-premium
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged/packages/api
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-web-premium
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged/packages/web
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-serve-premium
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged/packages/serve
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-cosmosdb
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged/plugins/dbgate-plugin-cosmosdb
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-firestore
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged/plugins/dbgate-plugin-firestore
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
|
||||
66
.github/workflows/build-npm.yaml
vendored
66
.github/workflows/build-npm.yaml
vendored
@@ -7,6 +7,9 @@ name: NPM packages
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
- v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -26,103 +29,110 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 22.x
|
||||
- name: Configure NPM token
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
|
||||
- name: Update npm
|
||||
run: npm install -g npm@11.5.1
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install
|
||||
- name: setCurrentVersion
|
||||
run: |
|
||||
yarn setCurrentVersion
|
||||
- name: Compute npm dist-tag
|
||||
run: |
|
||||
if [[ "${GITHUB_REF_NAME}" =~ -alpha\. ]]; then
|
||||
echo "NPM_TAG=alpha" >> $GITHUB_ENV
|
||||
else
|
||||
echo "NPM_TAG=latest" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Publish types
|
||||
working-directory: packages/types
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish tools
|
||||
working-directory: packages/tools
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish sqltree
|
||||
working-directory: packages/sqltree
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish rest
|
||||
working-directory: packages/rest
|
||||
run: |
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish api
|
||||
working-directory: packages/api
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish datalib
|
||||
working-directory: packages/datalib
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish filterparser
|
||||
working-directory: packages/filterparser
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish web
|
||||
working-directory: packages/web
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-serve
|
||||
working-directory: packages/serve
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbmodel
|
||||
working-directory: packages/dbmodel
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-csv
|
||||
working-directory: plugins/dbgate-plugin-csv
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-xml
|
||||
working-directory: plugins/dbgate-plugin-xml
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-excel
|
||||
working-directory: plugins/dbgate-plugin-excel
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-mssql
|
||||
working-directory: plugins/dbgate-plugin-mssql
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-mysql
|
||||
working-directory: plugins/dbgate-plugin-mysql
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-mongo
|
||||
working-directory: plugins/dbgate-plugin-mongo
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-postgres
|
||||
working-directory: plugins/dbgate-plugin-postgres
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-sqlite
|
||||
working-directory: plugins/dbgate-plugin-sqlite
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-redis
|
||||
working-directory: plugins/dbgate-plugin-redis
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-oracle
|
||||
working-directory: plugins/dbgate-plugin-oracle
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-clickhouse
|
||||
working-directory: plugins/dbgate-plugin-clickhouse
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-dbf
|
||||
working-directory: plugins/dbgate-plugin-dbf
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
- name: Publish dbgate-plugin-cassandra
|
||||
working-directory: plugins/dbgate-plugin-cassandra
|
||||
run: |
|
||||
npm publish
|
||||
npm publish --tag "$NPM_TAG"
|
||||
|
||||
25
.github/workflows/e2e-pro.yaml
vendored
25
.github/workflows/e2e-pro.yaml
vendored
@@ -5,10 +5,14 @@ name: Cypress tests with screenshots PREMIUM
|
||||
'on':
|
||||
push:
|
||||
branches:
|
||||
- stable
|
||||
- master
|
||||
- develop
|
||||
- feature/**
|
||||
- hotfix/**
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
e2e-tests:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -26,7 +30,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 11203754aad94189b565c2816d37760b15c8e07f
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
@@ -69,8 +73,8 @@ jobs:
|
||||
with:
|
||||
name: screenshots
|
||||
path: screenshots
|
||||
- name: Push E2E screenshots
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
- name: Push E2E screenshots - stable
|
||||
if: ${{ github.ref_name == 'stable' }}
|
||||
run: |
|
||||
git config --global user.email "info@dbgate.info"
|
||||
git config --global user.name "GitHub Actions"
|
||||
@@ -80,6 +84,17 @@ jobs:
|
||||
git add .
|
||||
git commit --amend --no-edit
|
||||
git push --force
|
||||
- name: Push E2E screenshots - master
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
run: |
|
||||
git config --global user.email "info@dbgate.info"
|
||||
git config --global user.name "GitHub Actions"
|
||||
git clone https://${{ secrets.DIFLOW_GIT_SECRET }}@github.com/dbgate/dbgate-img.git
|
||||
cp ../dbgate-merged/e2e-tests/screenshots/*.png dbgate-img/static/img-dev
|
||||
cd dbgate-img/static/img-dev
|
||||
git add .
|
||||
git commit --amend --no-edit
|
||||
git push --force
|
||||
services:
|
||||
postgres-cypress:
|
||||
image: postgres
|
||||
@@ -117,6 +132,10 @@ jobs:
|
||||
image: redis
|
||||
ports:
|
||||
- '16011:6379'
|
||||
dynamodb:
|
||||
image: amazon/dynamodb-local
|
||||
ports:
|
||||
- '16015:8000'
|
||||
mssql:
|
||||
image: mcr.microsoft.com/mssql/server
|
||||
ports:
|
||||
|
||||
55
.github/workflows/run-tests.yaml
vendored
55
.github/workflows/run-tests.yaml
vendored
@@ -9,6 +9,9 @@ name: Integration and unit tests
|
||||
- develop
|
||||
- feature/**
|
||||
- hotfix/**
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
all-tests:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -20,46 +23,51 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Checkout dbgate/dbgate-pro
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
mv dbgate-pro/* ../dbgate-pro/
|
||||
cd ..
|
||||
mkdir dbgate-merged
|
||||
cd dbgate-pro
|
||||
cd sync
|
||||
yarn
|
||||
node sync.js --nowatch
|
||||
cd ..
|
||||
- name: yarn install
|
||||
run: |
|
||||
cd ../dbgate-merged
|
||||
yarn install
|
||||
- name: Integration tests
|
||||
run: |
|
||||
cd ../dbgate-merged
|
||||
cd integration-tests
|
||||
yarn test:ci
|
||||
- name: Filter parser tests
|
||||
if: always()
|
||||
run: |
|
||||
cd ../dbgate-merged
|
||||
cd packages/filterparser
|
||||
yarn test:ci
|
||||
- name: Datalib (perspective) tests
|
||||
if: always()
|
||||
run: |
|
||||
cd ../dbgate-merged
|
||||
cd packages/datalib
|
||||
yarn test:ci
|
||||
- name: Tools tests
|
||||
if: always()
|
||||
run: |
|
||||
cd ../dbgate-merged
|
||||
cd packages/tools
|
||||
yarn test:ci
|
||||
- uses: tanmen/jest-reporter@v1
|
||||
if: always()
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
result-file: integration-tests/result.json
|
||||
action-name: Integration tests
|
||||
- uses: tanmen/jest-reporter@v1
|
||||
if: always()
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
result-file: packages/filterparser/result.json
|
||||
action-name: Filter parser test results
|
||||
- uses: tanmen/jest-reporter@v1
|
||||
if: always()
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
result-file: packages/datalib/result.json
|
||||
action-name: Datalib (perspectives) test results
|
||||
services:
|
||||
postgres-integr:
|
||||
image: postgres
|
||||
@@ -113,3 +121,14 @@ jobs:
|
||||
FIREBIRD_USE_LEGACY_AUTH: true
|
||||
ports:
|
||||
- '3050:3050'
|
||||
mongodb:
|
||||
image: mongo:4.0.12
|
||||
ports:
|
||||
- '27017:27017'
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
- mongo-config:/data/configdb
|
||||
dynamodb:
|
||||
image: amazon/dynamodb-local
|
||||
ports:
|
||||
- '8000:8000'
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,6 +24,7 @@ docker/plugins
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.translation
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
|
||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -2,5 +2,10 @@
|
||||
"jestrunner.jestCommand": "node_modules/.bin/cross-env DEVMODE=1 LOCALTEST=1 node_modules/.bin/jest",
|
||||
"cSpell.words": [
|
||||
"dbgate"
|
||||
]
|
||||
],
|
||||
"chat.tools.terminal.autoApprove": {
|
||||
"yarn workspace": true,
|
||||
"yarn --cwd packages/rest": true,
|
||||
"yarn --cwd packages/web": true
|
||||
}
|
||||
}
|
||||
9
AGENTS.md
Normal file
9
AGENTS.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# AGENTS
|
||||
|
||||
## Rules
|
||||
|
||||
- In newly added code, always use `DBGM-00000` for message/error codes; do not introduce new numbered DBGM codes such as `DBGM-00316`.
|
||||
- GUI uses Svelte4 (packages/web)
|
||||
- GUI is tested with E2E tests in `e2e-tests` folder, using Cypress. Use data-testid attribute in components to make them easier to test.
|
||||
- data-testid format: ComponentName_identifier. Use reasonable identifiers
|
||||
- don't change content of storageModel.js - this is generated from table YAMLs with "yarn storage-json" command
|
||||
425
CHANGELOG.md
425
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
119
CLAUDE.md
Normal file
119
CLAUDE.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
DbGate is a cross-platform (no)SQL database manager supporting MySQL, PostgreSQL, SQL Server, Oracle, MongoDB, Redis, SQLite, and more. It runs as a web app (Docker/NPM), an Electron desktop app, or in a browser. The monorepo uses Yarn workspaces.
|
||||
|
||||
## Development Commands
|
||||
|
||||
```sh
|
||||
yarn # install all packages (also builds TS libraries and plugins)
|
||||
yarn start # run API (port 3000) + web (port 5001) concurrently
|
||||
```
|
||||
|
||||
For more control, run these 3 commands in separate terminals:
|
||||
```sh
|
||||
yarn start:api # Express API on port 3000
|
||||
yarn start:web # Svelte frontend on port 5001
|
||||
yarn lib # watch-compile TS libraries and plugins
|
||||
```
|
||||
|
||||
For Electron development:
|
||||
```sh
|
||||
yarn start:web # web on port 5001
|
||||
yarn lib # watch TS libs/plugins
|
||||
yarn start:app # Electron app
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
```sh
|
||||
yarn build:lib # build all TS libraries (sqltree, tools, filterparser, datalib, rest)
|
||||
yarn build:api # build API
|
||||
yarn build:web # build web frontend
|
||||
yarn ts # TypeScript type-check API and web
|
||||
yarn prettier # format all source files
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
Unit tests (in packages like `dbgate-tools`):
|
||||
```sh
|
||||
yarn workspace dbgate-tools test
|
||||
```
|
||||
|
||||
Integration tests (requires Docker for database containers):
|
||||
```sh
|
||||
cd integration-tests
|
||||
yarn test:local # run all tests
|
||||
yarn test:local:path __tests__/alter-database.spec.js # run a single test file
|
||||
```
|
||||
|
||||
E2E tests (Cypress):
|
||||
```sh
|
||||
yarn cy:open # open Cypress UI
|
||||
cd e2e-tests && yarn cy:run:browse-data # run a specific spec headlessly
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Monorepo Structure
|
||||
|
||||
| Path | Package | Purpose |
|
||||
|---|---|---|
|
||||
| `packages/api` | `dbgate-api` | Express.js backend server |
|
||||
| `packages/web` | `dbgate-web` | Svelte 4 frontend (built with Rolldown) |
|
||||
| `packages/tools` | `dbgate-tools` | Shared TS utilities: SQL dumping, schema analysis, diffing, driver base classes |
|
||||
| `packages/datalib` | `dbgate-datalib` | Grid display logic, changeset management, perspectives, chart definitions |
|
||||
| `packages/sqltree` | `dbgate-sqltree` | SQL AST representation and dumping |
|
||||
| `packages/filterparser` | `dbgate-filterparser` | Parses filter strings into SQL/Mongo conditions |
|
||||
| `packages/rest` | `dbgate-rest` | REST connection support |
|
||||
| `packages/types` | `dbgate-types` | TypeScript type definitions (`.d.ts` only) |
|
||||
| `packages/aigwmock` | `dbgate-aigwmock` | Mock AI gateway server for E2E testing |
|
||||
| `plugins/dbgate-plugin-*` | — | Database drivers and file format handlers |
|
||||
| `app/` | — | Electron shell |
|
||||
| `integration-tests/` | — | Jest-based DB integration tests (Docker) |
|
||||
| `e2e-tests/` | — | Cypress E2E tests |
|
||||
|
||||
### API Backend (`packages/api`)
|
||||
|
||||
- Express.js server with controllers in `src/controllers/` — each file exposes REST endpoints via the `useController` utility
|
||||
- Database connections run in child processes (`src/proc/`) to isolate crashes and long-running operations
|
||||
- `src/shell/` contains stream-based data pipeline primitives (readers, writers, transforms) used for import/export and replication
|
||||
- Plugin drivers are loaded dynamically via `requireEngineDriver`; each plugin in `plugins/` exports a driver conforming to `DriverBase` from `dbgate-tools`
|
||||
|
||||
### Frontend (`packages/web`)
|
||||
|
||||
- Svelte 4 components; builds with Rolldown (not Vite/Webpack)
|
||||
- Global state in `src/stores.ts` using Svelte writable stores, with `writableWithStorage` / `writableWithForage` helpers for persistence
|
||||
- API calls go through `src/utility/api.ts` (`apiCall`, `apiOff`, etc.) which handles auth, error display, and cache invalidation
|
||||
- Tab system: each open editor/viewer is a "tab" tracked in `openedTabs` store; tab components live in `src/tabs/`
|
||||
- Left-panel tree items are "AppObjects" in `src/appobj/`
|
||||
- Metadata (table lists, column info) is loaded reactively via hooks in `src/utility/metadataLoaders.ts`
|
||||
- Commands/keybindings are registered in `src/commands/`
|
||||
|
||||
### Plugin Architecture
|
||||
|
||||
Each `plugins/dbgate-plugin-*` package provides:
|
||||
- **Frontend build** (`build:frontend`): bundled JS loaded by the web UI for query formatting, data rendering
|
||||
- **Backend build** (`build:backend`): Node.js driver code loaded by the API for actual DB connections
|
||||
|
||||
Plugins are copied to `plugins/dist/` via `plugins:copydist` before building the app or Docker image.
|
||||
|
||||
### Key Conventions
|
||||
|
||||
- Error/message codes use `DBGM-00000` as placeholder — do not introduce new numbered `DBGM-NNNNN` codes
|
||||
- Frontend uses **Svelte 4** (not Svelte 5)
|
||||
- E2E test selectors use `data-testid` attribute with format `ComponentName_identifier`
|
||||
- Prettier config: single quotes, 2-space indent, 120-char line width, trailing commas ES5
|
||||
- Logging via `pinomin`; pipe through `pino-pretty` for human-readable output
|
||||
|
||||
### Translation System
|
||||
|
||||
```sh
|
||||
yarn translations:extract # extract new strings
|
||||
yarn translations:add-missing # add missing translations
|
||||
yarn translations:check # check for issues
|
||||
```
|
||||
11
README.md
11
README.md
@@ -16,12 +16,9 @@ DbGate is licensed under GPL-3.0 license and is free to use for any purpose.
|
||||
|
||||
* Try it online - [demo.dbgate.org](https://demo.dbgate.org) - online demo application
|
||||
* **Download** application for Windows, Linux or Mac from [dbgate.io](https://www.dbgate.io/download/)
|
||||
* Looking for DbGate Community? **Download** from [dbgate.org](https://dbgate.org/download/)
|
||||
* Looking for DbGate Community? **Download** from [dbgate.io](https://www.dbgate.io/download-community/)
|
||||
* Run web version as [NPM package](https://www.npmjs.com/package/dbgate-serve) or as [docker image](https://hub.docker.com/r/dbgate/dbgate)
|
||||
* Use nodeJs [scripting interface](https://docs.dbgate.io/scripting) ([API documentation](https://docs.dbgate.io/apidoc))
|
||||
* [Recommend DbGate](https://testimonial.to/dbgate) | [Rate on G2](https://www.g2.com/products/dbgate/reviews)
|
||||
* [Give us feedback](https://dbgate.org/feedback) - it will help us to decide, how to improve DbGate in future
|
||||
* We [offer 2-year PREMIUM license](https://dbgate.org/review/) for any honest review on these platforms (time-limited offer)
|
||||
|
||||
## Supported databases
|
||||
* MySQL
|
||||
@@ -64,7 +61,7 @@ DbGate is licensed under GPL-3.0 license and is free to use for any purpose.
|
||||
* Edit table schema, indexes, primary and foreign keys
|
||||
* Compare and synchronize database structure
|
||||
* ER diagram
|
||||
* Light and dark theme, next themes available as plugins from github community
|
||||
* Light and dark theme, next themes available from DbGate Cloud
|
||||
* Huge support for work with related data - master/detail views, foreign key lookups, expanding columns from related tables in flat data view
|
||||
* Query designer - visual SQL query builder without writing SQL code. Complex conditions like WHERE NOT EXISTS.
|
||||
* Query perspectives – innovative nested table view over complex relational data, something like query designer on MongoDB databases
|
||||
@@ -93,12 +90,12 @@ Any contributions are welcome. If you want to contribute without coding, conside
|
||||
|
||||
* Tell your friends about DbGate or share on social networks - when more people will use DbGate, it will grow to be better
|
||||
* Purchase a [DbGate Premium](https://www.dbgate.io/purchase/premium/) license
|
||||
* Write review on [Product Hunt](https://www.producthunt.com/products/dbgate) or [G2](https://www.g2.com/products/dbgate/reviews) - we offer [2-year PREMIUM license](https://dbgate.org/review/) for reviewers (time limited offer)
|
||||
* Create issue, if you find problem in app, or you have idea to new feature. If issue already exists, you could leave comment on it, to prioritise most wanted issues
|
||||
* Create some tutorial video on [youtube](https://www.youtube.com/playlist?list=PLCo7KjCVXhr0RfUSjM9wJMsp_ShL1q61A)
|
||||
* Become a backer on [GitHub sponsors](https://github.com/sponsors/dbgate) or [Open collective](https://opencollective.com/dbgate)
|
||||
* Add a SQL script to [Public Knowledge Base](https://github.com/dbgate/dbgate-knowledge-base)
|
||||
* Where a small coding is acceptable for you, you could [create plugin](https://docs.dbgate.io/plugin-development). Plugins for new themes can be created actually without JS coding
|
||||
* Where a small coding is acceptable for you, you could [create plugin](https://docs.dbgate.io/plugin-development)
|
||||
* Create a new custom theme and share it on [DbGate Cloud](https://github.com/dbgate/dbgate-knowledge-base/tree/master/folder-Themes)
|
||||
|
||||
Thank you!
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
<p>DbGate is cross-platform database manager. It's designed to be simple to use and effective, when working with more databases simultaneously. But there are also many advanced features like schema compare, visual query designer, chart visualisation or batch export and import.</p>
|
||||
</description>
|
||||
|
||||
<url type="homepage">https://dbgate.org/</url>
|
||||
<url type="homepage">https://www.dbgate.io/</url>
|
||||
<url type="vcs-browser">https://github.com/dbgate/dbgate</url>
|
||||
<url type="contact">https://dbgate.org/about/</url>
|
||||
<url type="contact">https://www.dbgate.io/contact/</url>
|
||||
<url type="donation">https://github.com/sponsors/dbgate</url>
|
||||
<url type="bugtracker">https://github.com/dbgate/dbgate/issues</url>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dbgate",
|
||||
"version": "6.0.0-alpha.1",
|
||||
"version": "7.0.0-alpha.1",
|
||||
"private": true,
|
||||
"author": "Jan Prochazka <jenasoft.database@gmail.com>",
|
||||
"description": "Opensource database administration tool",
|
||||
@@ -117,7 +117,7 @@
|
||||
"scripts": {
|
||||
"start": "cross-env ELECTRON_START_URL=http://localhost:5001 DEVMODE=1 electron .",
|
||||
"start:local": "cross-env electron .",
|
||||
"dist": "electron-builder",
|
||||
"dist": "electron-builder --publish never",
|
||||
"build": "cd ../packages/api && yarn build && cd ../web && yarn build && cd ../../app && yarn dist",
|
||||
"build:local": "cd ../packages/api && yarn build && cd ../web && yarn build && cd ../../app && yarn predist",
|
||||
"postinstall": "yarn rebuild && patch-package",
|
||||
@@ -128,7 +128,7 @@
|
||||
"devDependencies": {
|
||||
"copyfiles": "^2.2.0",
|
||||
"cross-env": "^6.0.3",
|
||||
"electron": "30.0.2",
|
||||
"electron": "38.6.0",
|
||||
"electron-builder": "25.1.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,16 @@ let mainModule;
|
||||
let appUpdateStatus = '';
|
||||
let settingsJson = {};
|
||||
|
||||
function getTranslated(key) {
|
||||
if (typeof key === 'string' && global.TRANSLATION_DATA?.[key]) {
|
||||
return global.TRANSLATION_DATA?.[key];
|
||||
}
|
||||
if (typeof key?._transKey === 'string') {
|
||||
return global.TRANSLATION_DATA?.[key._transKey] ?? key._transOptions?.defaultMessage;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
process.on('uncaughtException', function (error) {
|
||||
console.error('uncaughtException', error);
|
||||
});
|
||||
@@ -63,6 +73,7 @@ try {
|
||||
let mainWindow;
|
||||
let mainMenu;
|
||||
let runCommandOnLoad = null;
|
||||
let mainWindowMenuSet = false;
|
||||
|
||||
log.transports.file.level = 'debug';
|
||||
autoUpdater.logger = log;
|
||||
@@ -85,17 +96,22 @@ function formatKeyText(keyText) {
|
||||
return keyText.replace('CtrlOrCommand+', 'Ctrl+');
|
||||
}
|
||||
|
||||
function commandItem(item) {
|
||||
function commandItem(item, disableAll = false) {
|
||||
const id = item.command;
|
||||
const command = commands[id];
|
||||
if (item.skipInApp) {
|
||||
return { skip: true };
|
||||
}
|
||||
if (!command) {
|
||||
return { skip: true };
|
||||
}
|
||||
return {
|
||||
id,
|
||||
label: command ? command.menuName || command.toolbarName || command.name : id,
|
||||
label: command
|
||||
? getTranslated(command.menuName) || getTranslated(command.toolbarName) || getTranslated(command.name)
|
||||
: id,
|
||||
accelerator: formatKeyText(command ? command.keyText : undefined),
|
||||
enabled: command ? command.enabled : false,
|
||||
enabled: command ? command.enabled && (!disableAll || command.systemCommand) : false,
|
||||
click() {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('run-command', id);
|
||||
@@ -107,14 +123,14 @@ function commandItem(item) {
|
||||
};
|
||||
}
|
||||
|
||||
function buildMenu() {
|
||||
function buildMenu(disableAll = false) {
|
||||
let template = _cloneDeepWith(mainMenuDefinition({ editMenu: true, isMac: isMac() }), item => {
|
||||
if (item.divider) {
|
||||
return { type: 'separator' };
|
||||
}
|
||||
|
||||
if (item.command) {
|
||||
return commandItem(item);
|
||||
return commandItem(item, disableAll);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -129,7 +145,7 @@ function buildMenu() {
|
||||
{
|
||||
label: 'DbGate',
|
||||
submenu: [
|
||||
commandItem({ command: 'about.show' }),
|
||||
commandItem({ command: 'about.show' }, disableAll),
|
||||
{ role: 'services' },
|
||||
{ role: 'hide' },
|
||||
{ role: 'hideOthers' },
|
||||
@@ -145,22 +161,28 @@ function buildMenu() {
|
||||
}
|
||||
|
||||
ipcMain.on('update-commands', async (event, arg) => {
|
||||
commands = JSON.parse(arg);
|
||||
const parsed = JSON.parse(arg);
|
||||
commands = parsed.commands;
|
||||
const isModalOpened = parsed.isModalOpened;
|
||||
const dbgatePage = parsed.dbgatePage;
|
||||
for (const key of Object.keys(commands)) {
|
||||
const menu = mainMenu.getMenuItemById(key);
|
||||
if (!menu) continue;
|
||||
const command = commands[key];
|
||||
|
||||
// rebuild menu
|
||||
if (menu.label != command.text || menu.accelerator != command.keyText) {
|
||||
mainMenu = buildMenu();
|
||||
if (global.TRANSLATION_DATA && (menu.label != command.text || menu.accelerator != command.keyText)) {
|
||||
mainMenu = buildMenu(isModalOpened || !!dbgatePage);
|
||||
|
||||
Menu.setApplicationMenu(mainMenu);
|
||||
// mainWindow.setMenu(mainMenu);
|
||||
if (!mainWindowMenuSet) {
|
||||
mainWindow.setMenu(mainMenu);
|
||||
mainWindowMenuSet = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
menu.enabled = command.enabled;
|
||||
menu.enabled = command.enabled && !isModalOpened && !dbgatePage;
|
||||
}
|
||||
});
|
||||
ipcMain.on('quit-app', async (event, arg) => {
|
||||
@@ -303,6 +325,12 @@ ipcMain.on('check-for-updates', async (event, url) => {
|
||||
autoUpdater.autoDownload = false;
|
||||
autoUpdater.checkForUpdates();
|
||||
});
|
||||
ipcMain.on('translation-data', async (event, arg) => {
|
||||
global.TRANSLATION_DATA = JSON.parse(arg);
|
||||
mainMenu = buildMenu();
|
||||
Menu.setApplicationMenu(mainMenu);
|
||||
mainWindow.setMenu(mainMenu);
|
||||
});
|
||||
|
||||
function fillMissingSettings(value) {
|
||||
const res = {
|
||||
@@ -372,6 +400,14 @@ function createWindow() {
|
||||
},
|
||||
});
|
||||
|
||||
mainWindow.webContents.session.webRequest.onBeforeSendHeaders(
|
||||
{ urls: ['https://*.tile.openstreetmap.org/*'] },
|
||||
(details, callback) => {
|
||||
details.requestHeaders['Referer'] = 'https://www.dbgate.io';
|
||||
callback({ requestHeaders: details.requestHeaders });
|
||||
}
|
||||
);
|
||||
|
||||
if (initialConfig['winIsMaximized']) {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
@@ -379,8 +415,8 @@ function createWindow() {
|
||||
mainWindow.setFullScreen(true);
|
||||
}
|
||||
|
||||
mainMenu = buildMenu();
|
||||
mainWindow.setMenu(mainMenu);
|
||||
// mainMenu = buildMenu();
|
||||
// mainWindow.setMenu(mainMenu);
|
||||
|
||||
function loadMainWindow() {
|
||||
const startUrl =
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
module.exports = ({ editMenu, isMac }) => [
|
||||
function _t(key, { defaultMessage, currentTranslations } = {}) {
|
||||
return (currentTranslations || global.TRANSLATION_DATA)?.[key] || defaultMessage;
|
||||
}
|
||||
|
||||
module.exports = ({ editMenu, isMac }, currentTranslations = null) => [
|
||||
{
|
||||
label: 'File',
|
||||
label: _t('menu.file', { defaultMessage: 'File', currentTranslations }),
|
||||
submenu: [
|
||||
{ command: 'new.connection', hideDisabled: true },
|
||||
{ command: 'new.sqliteDatabase', hideDisabled: true },
|
||||
@@ -28,7 +32,7 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
},
|
||||
editMenu
|
||||
? {
|
||||
label: 'Edit',
|
||||
label: _t('menu.edit', { defaultMessage: 'Edit', currentTranslations }),
|
||||
submenu: [
|
||||
{ command: 'edit.undo' },
|
||||
{ command: 'edit.redo' },
|
||||
@@ -53,7 +57,7 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
label: 'View',
|
||||
label: _t('menu.view', { defaultMessage: 'View', currentTranslations }),
|
||||
submenu: [
|
||||
{ command: 'app.reload', hideDisabled: true },
|
||||
{ command: 'app.toggleDevTools', hideDisabled: true },
|
||||
@@ -72,10 +76,12 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
{ command: 'app.zoomIn', hideDisabled: true },
|
||||
{ command: 'app.zoomOut', hideDisabled: true },
|
||||
{ command: 'app.zoomReset', hideDisabled: true },
|
||||
{ divider: true },
|
||||
{ command: 'app.showLogs', hideDisabled: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Tools',
|
||||
label: _t('menu.tools', { defaultMessage: 'Tools', currentTranslations }),
|
||||
submenu: [
|
||||
{ command: 'database.search', hideDisabled: true },
|
||||
{ command: 'commandPalette.show', hideDisabled: true },
|
||||
@@ -91,6 +97,8 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
{ divider: true },
|
||||
{ command: 'app.exportConnections', hideDisabled: true },
|
||||
{ command: 'app.importConnections', hideDisabled: true },
|
||||
{ divider: true },
|
||||
{ command: 'app.managePlugins', hideDisabled: true },
|
||||
],
|
||||
},
|
||||
...(isMac
|
||||
@@ -102,7 +110,7 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: 'Help',
|
||||
label: _t('menu.help', { defaultMessage: 'Help', currentTranslations }),
|
||||
submenu: [
|
||||
{ command: 'app.openDocs', hideDisabled: true },
|
||||
{ command: 'app.openWeb', hideDisabled: true },
|
||||
@@ -114,7 +122,7 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
{ command: 'tabs.changelog', hideDisabled: true },
|
||||
{ command: 'about.show', hideDisabled: true },
|
||||
{ divider: true },
|
||||
{ command: 'file.checkForUpdates', hideDisabled: true },
|
||||
{ command: 'app.checkForUpdates', hideDisabled: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
503
app/yarn.lock
503
app/yarn.lock
@@ -16,9 +16,9 @@
|
||||
ajv-keywords "^3.4.1"
|
||||
|
||||
"@electron/asar@^3.2.7":
|
||||
version "3.2.17"
|
||||
resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.2.17.tgz#91d28087aad80d1a1c8cc4e667c6476edf50f949"
|
||||
integrity sha512-OcWImUI686w8LkghQj9R2ynZ2ME693Ek6L1SiaAgqGKzBaTIZw3fHDqN82Rcl+EU1Gm9EgkJ5KLIY/q5DCRbbA==
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.4.1.tgz#4e9196a4b54fba18c56cd8d5cac67c5bdc588065"
|
||||
integrity sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==
|
||||
dependencies:
|
||||
commander "^5.0.0"
|
||||
glob "^7.1.6"
|
||||
@@ -98,6 +98,18 @@
|
||||
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
||||
integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
|
||||
|
||||
"@isaacs/balanced-match@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29"
|
||||
integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==
|
||||
|
||||
"@isaacs/brace-expansion@^5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3"
|
||||
integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==
|
||||
dependencies:
|
||||
"@isaacs/balanced-match" "^4.0.1"
|
||||
|
||||
"@isaacs/cliui@^8.0.2":
|
||||
version "8.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
|
||||
@@ -202,16 +214,23 @@
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/ms@*":
|
||||
version "0.7.34"
|
||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433"
|
||||
integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78"
|
||||
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
|
||||
|
||||
"@types/node@*", "@types/node@^20.9.0":
|
||||
version "20.12.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.10.tgz#8f0c3f12b0f075eee1fe20c1afb417e9765bef76"
|
||||
integrity sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==
|
||||
"@types/node@*":
|
||||
version "24.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.0.tgz#6b79086b0dfc54e775a34ba8114dcc4e0221f31f"
|
||||
integrity sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
undici-types "~7.16.0"
|
||||
|
||||
"@types/node@^22.7.7":
|
||||
version "22.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.0.tgz#849606ef3920850583a4e7ee0930987c35ad80be"
|
||||
integrity sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==
|
||||
dependencies:
|
||||
undici-types "~6.21.0"
|
||||
|
||||
"@types/plist@^3.0.1":
|
||||
version "3.0.5"
|
||||
@@ -229,9 +248,9 @@
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/verror@^1.10.3":
|
||||
version "1.10.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.10.tgz#d5a4b56abac169bfbc8b23d291363a682e6fa087"
|
||||
integrity sha512-l4MM0Jppn18hb9xmM6wwD1uTdShpf9Pn80aXTStnK1C94gtPvJcV2FrDmbOQUAQfJ1cKZHktkQUDwEqaAKXMMg==
|
||||
version "1.10.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.11.tgz#d3d6b418978c8aa202d41e5bb3483227b6ecc1bb"
|
||||
integrity sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.10.3"
|
||||
@@ -241,9 +260,9 @@
|
||||
"@types/node" "*"
|
||||
|
||||
"@xmldom/xmldom@^0.8.8":
|
||||
version "0.8.10"
|
||||
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99"
|
||||
integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
|
||||
version "0.8.11"
|
||||
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz#b79de2d67389734c57c52595f7a7305e30c2d608"
|
||||
integrity sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==
|
||||
|
||||
"@yarnpkg/lockfile@^1.1.0":
|
||||
version "1.1.0"
|
||||
@@ -263,14 +282,14 @@ agent-base@6, agent-base@^6.0.2:
|
||||
debug "4"
|
||||
|
||||
agent-base@^7.1.0, agent-base@^7.1.2:
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1"
|
||||
integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8"
|
||||
integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
|
||||
|
||||
agentkeepalive@^4.2.1:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923"
|
||||
integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a"
|
||||
integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==
|
||||
dependencies:
|
||||
humanize-ms "^1.2.1"
|
||||
|
||||
@@ -303,9 +322,9 @@ ansi-regex@^5.0.1:
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-regex@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654"
|
||||
integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1"
|
||||
integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==
|
||||
|
||||
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
version "4.3.0"
|
||||
@@ -315,9 +334,9 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
color-convert "^2.0.1"
|
||||
|
||||
ansi-styles@^6.1.0:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
|
||||
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
|
||||
version "6.2.3"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041"
|
||||
integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==
|
||||
|
||||
app-builder-bin@5.0.0-alpha.10:
|
||||
version "5.0.0-alpha.10"
|
||||
@@ -363,9 +382,9 @@ app-builder-lib@25.1.8:
|
||||
temp-file "^3.4.0"
|
||||
|
||||
"aproba@^1.0.3 || ^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
|
||||
integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.1.0.tgz#75500a190313d95c64e871e7e4284c6ac219f0b1"
|
||||
integrity sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==
|
||||
|
||||
are-we-there-yet@^3.0.0:
|
||||
version "3.0.1"
|
||||
@@ -395,10 +414,10 @@ async-exit-hook@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3"
|
||||
integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==
|
||||
|
||||
async@^3.2.3:
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
|
||||
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
|
||||
async@^3.2.6:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
|
||||
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
@@ -447,33 +466,33 @@ boolean@^3.0.1:
|
||||
integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843"
|
||||
integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
brace-expansion@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
|
||||
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7"
|
||||
integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
|
||||
braces@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||
braces@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
|
||||
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
|
||||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
fill-range "^7.1.1"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
buffer-equal-constant-time@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
|
||||
@@ -499,10 +518,10 @@ builder-util-runtime@9.2.10:
|
||||
debug "^4.3.4"
|
||||
sax "^1.2.4"
|
||||
|
||||
builder-util-runtime@9.2.5:
|
||||
version "9.2.5"
|
||||
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.5.tgz#0afdffa0adb5c84c14926c7dd2cf3c6e96e9be83"
|
||||
integrity sha512-HjIDfhvqx/8B3TDN4GbABQcgpewTU4LMRTQPkVpKYV3lsuxEJoIfvg09GyWTNmfVNSUAYf+fbTN//JX4TH20pg==
|
||||
builder-util-runtime@9.3.1:
|
||||
version "9.3.1"
|
||||
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.3.1.tgz#0daedde0f6d381f2a00a50a407b166fe7dca1a67"
|
||||
integrity sha512-2/egrNDDnRaxVwK3A+cJq6UOlqOdedGA7JPqCeJjN2Zjk1/QB/6QUi3b714ScIGS7HafFXTyzJEOr5b44I3kvQ==
|
||||
dependencies:
|
||||
debug "^4.3.4"
|
||||
sax "^1.2.4"
|
||||
@@ -571,7 +590,15 @@ cacheable-request@^7.0.2:
|
||||
normalize-url "^6.0.1"
|
||||
responselike "^2.0.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2:
|
||||
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
|
||||
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
function-bind "^1.1.2"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
@@ -744,9 +771,9 @@ cross-env@^6.0.3:
|
||||
cross-spawn "^7.0.0"
|
||||
|
||||
cross-spawn@^6.0.5:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
|
||||
version "6.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57"
|
||||
integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==
|
||||
dependencies:
|
||||
nice-try "^1.0.4"
|
||||
path-key "^2.0.1"
|
||||
@@ -754,26 +781,19 @@ cross-spawn@^6.0.5:
|
||||
shebang-command "^1.2.0"
|
||||
which "^1.2.9"
|
||||
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3, cross-spawn@^7.0.6:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
|
||||
dependencies:
|
||||
path-key "^3.1.0"
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^4.3.3:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
|
||||
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4:
|
||||
version "4.4.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
|
||||
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
@@ -825,9 +845,9 @@ delegates@^1.0.0:
|
||||
integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
|
||||
|
||||
detect-libc@^2.0.1:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
|
||||
integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad"
|
||||
integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
|
||||
|
||||
detect-node@^2.0.4:
|
||||
version "2.1.0"
|
||||
@@ -878,9 +898,18 @@ dotenv-expand@^11.0.6:
|
||||
dotenv "^16.4.5"
|
||||
|
||||
dotenv@^16.4.5:
|
||||
version "16.4.7"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26"
|
||||
integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==
|
||||
version "16.6.1"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020"
|
||||
integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==
|
||||
|
||||
dunder-proto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
|
||||
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
|
||||
dependencies:
|
||||
call-bind-apply-helpers "^1.0.1"
|
||||
es-errors "^1.3.0"
|
||||
gopd "^1.2.0"
|
||||
|
||||
eastasianwidth@^0.2.0:
|
||||
version "0.2.0"
|
||||
@@ -936,11 +965,11 @@ electron-publish@25.1.7:
|
||||
mime "^2.5.2"
|
||||
|
||||
electron-updater@^6.3.4:
|
||||
version "6.3.4"
|
||||
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.3.4.tgz#3934bc89875bb524c2cbbd11041114e97c0c2496"
|
||||
integrity sha512-uZUo7p1Y53G4tl6Cgw07X1yF8Jlz6zhaL7CQJDZ1fVVkOaBfE2cWtx80avwDVi8jHp+I/FWawrMgTAeCCNIfAg==
|
||||
version "6.6.2"
|
||||
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.6.2.tgz#3e65e044f1a99b00d61e200e24de8e709c69ce99"
|
||||
integrity sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==
|
||||
dependencies:
|
||||
builder-util-runtime "9.2.5"
|
||||
builder-util-runtime "9.3.1"
|
||||
fs-extra "^10.1.0"
|
||||
js-yaml "^4.1.0"
|
||||
lazy-val "^1.0.5"
|
||||
@@ -949,13 +978,13 @@ electron-updater@^6.3.4:
|
||||
semver "^7.6.3"
|
||||
tiny-typed-emitter "^2.1.0"
|
||||
|
||||
electron@30.0.2:
|
||||
version "30.0.2"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-30.0.2.tgz#95ba019216bf8be9f3097580123e33ea37497733"
|
||||
integrity sha512-zv7T+GG89J/hyWVkQsLH4Y/rVEfqJG5M/wOBIGNaDdqd8UV9/YZPdS7CuFeaIj0H9LhCt95xkIQNpYB/3svOkQ==
|
||||
electron@38.6.0:
|
||||
version "38.6.0"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-38.6.0.tgz#c862bff41d42776e307bf5cc92503dda23612339"
|
||||
integrity sha512-68OFNxJlrEStA+t8k5atzf4frJddvRR1N1oalr49Ll8YZ0+0nEsDhw4UNhTCoZKTjSYcxFF/4rt+sco+OlnB3g==
|
||||
dependencies:
|
||||
"@electron/get" "^2.0.0"
|
||||
"@types/node" "^20.9.0"
|
||||
"@types/node" "^22.7.7"
|
||||
extract-zip "^2.0.1"
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
@@ -976,9 +1005,9 @@ encoding@^0.1.13:
|
||||
iconv-lite "^0.6.2"
|
||||
|
||||
end-of-stream@^1.1.0:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
|
||||
version "1.4.5"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c"
|
||||
integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
@@ -992,27 +1021,42 @@ err-code@^2.0.2:
|
||||
resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
|
||||
integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==
|
||||
|
||||
es-define-property@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
|
||||
integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
|
||||
dependencies:
|
||||
get-intrinsic "^1.2.4"
|
||||
es-define-property@^1.0.0, es-define-property@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
|
||||
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
|
||||
|
||||
es-errors@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
||||
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
||||
|
||||
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
|
||||
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
|
||||
es-set-tostringtag@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
|
||||
integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
get-intrinsic "^1.2.6"
|
||||
has-tostringtag "^1.0.2"
|
||||
hasown "^2.0.2"
|
||||
|
||||
es6-error@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
|
||||
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27"
|
||||
integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
|
||||
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
|
||||
|
||||
escape-string-regexp@^4.0.0:
|
||||
version "4.0.0"
|
||||
@@ -1020,9 +1064,9 @@ escape-string-regexp@^4.0.0:
|
||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||
|
||||
exponential-backoff@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6"
|
||||
integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz#51cf92c1c0493c766053f9d3abee4434c244d2f6"
|
||||
integrity sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==
|
||||
|
||||
extract-zip@^2.0.1:
|
||||
version "2.0.1"
|
||||
@@ -1064,10 +1108,10 @@ filelist@^1.0.4:
|
||||
dependencies:
|
||||
minimatch "^5.0.1"
|
||||
|
||||
fill-range@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
||||
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
|
||||
fill-range@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
|
||||
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
@@ -1079,20 +1123,22 @@ find-yarn-workspace-root@^2.0.0:
|
||||
micromatch "^4.0.2"
|
||||
|
||||
foreground-child@^3.1.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77"
|
||||
integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f"
|
||||
integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==
|
||||
dependencies:
|
||||
cross-spawn "^7.0.0"
|
||||
cross-spawn "^7.0.6"
|
||||
signal-exit "^4.0.1"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4"
|
||||
integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
es-set-tostringtag "^2.1.0"
|
||||
hasown "^2.0.2"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fs-extra@^10.0.0, fs-extra@^10.1.0:
|
||||
@@ -1105,9 +1151,9 @@ fs-extra@^10.0.0, fs-extra@^10.1.0:
|
||||
universalify "^2.0.0"
|
||||
|
||||
fs-extra@^11.1.1:
|
||||
version "11.2.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b"
|
||||
integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==
|
||||
version "11.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.2.tgz#c838aeddc6f4a8c74dd15f85e11fe5511bfe02a4"
|
||||
integrity sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
@@ -1168,16 +1214,29 @@ get-caller-file@^2.0.5:
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
|
||||
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
|
||||
get-intrinsic@^1.2.6:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
|
||||
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
|
||||
dependencies:
|
||||
call-bind-apply-helpers "^1.0.2"
|
||||
es-define-property "^1.0.1"
|
||||
es-errors "^1.3.0"
|
||||
es-object-atoms "^1.1.1"
|
||||
function-bind "^1.1.2"
|
||||
has-proto "^1.0.1"
|
||||
has-symbols "^1.0.3"
|
||||
hasown "^2.0.0"
|
||||
get-proto "^1.0.1"
|
||||
gopd "^1.2.0"
|
||||
has-symbols "^1.1.0"
|
||||
hasown "^2.0.2"
|
||||
math-intrinsics "^1.1.0"
|
||||
|
||||
get-proto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
|
||||
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
|
||||
dependencies:
|
||||
dunder-proto "^1.0.1"
|
||||
es-object-atoms "^1.0.0"
|
||||
|
||||
get-stream@^5.1.0:
|
||||
version "5.2.0"
|
||||
@@ -1241,12 +1300,10 @@ globalthis@^1.0.1:
|
||||
define-properties "^1.2.1"
|
||||
gopd "^1.0.1"
|
||||
|
||||
gopd@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
|
||||
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
gopd@^1.0.1, gopd@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
|
||||
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
|
||||
|
||||
got@^11.7.0, got@^11.8.5:
|
||||
version "11.8.6"
|
||||
@@ -1282,22 +1339,24 @@ has-property-descriptors@^1.0.0:
|
||||
dependencies:
|
||||
es-define-property "^1.0.0"
|
||||
|
||||
has-proto@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd"
|
||||
integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
|
||||
has-symbols@^1.0.3, has-symbols@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
|
||||
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
|
||||
|
||||
has-symbols@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
|
||||
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
|
||||
has-tostringtag@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
|
||||
integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
|
||||
dependencies:
|
||||
has-symbols "^1.0.3"
|
||||
|
||||
has-unicode@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||
integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==
|
||||
|
||||
hasown@^2.0.0:
|
||||
hasown@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
|
||||
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
|
||||
@@ -1312,9 +1371,9 @@ hosted-git-info@^4.1.0:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
|
||||
integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5"
|
||||
integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==
|
||||
|
||||
http-proxy-agent@^5.0.0:
|
||||
version "5.0.0"
|
||||
@@ -1412,13 +1471,10 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1,
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ip-address@^9.0.5:
|
||||
version "9.0.5"
|
||||
resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a"
|
||||
integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==
|
||||
dependencies:
|
||||
jsbn "1.1.0"
|
||||
sprintf-js "^1.1.3"
|
||||
ip-address@^10.0.1:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.1.0.tgz#d8dcffb34d0e02eb241427444a6e23f5b0595aa4"
|
||||
integrity sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==
|
||||
|
||||
is-ci@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -1487,9 +1543,9 @@ isbinaryfile@^4.0.8:
|
||||
integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==
|
||||
|
||||
isbinaryfile@^5.0.0:
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-5.0.4.tgz#2a2edefa76cafa66613fe4c1ea52f7f031017bdf"
|
||||
integrity sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==
|
||||
version "5.0.6"
|
||||
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-5.0.6.tgz#01eac28867aeffaebaee7eaf21d1dd3a67d7c0c7"
|
||||
integrity sha512-I+NmIfBHUl+r2wcDd6JwE9yWje/PIVY/R5/CmV8dXLZd5K+L9X2klAOwfAHNnondLXkbHyTAleQAWonpTJBTtw==
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -1506,14 +1562,13 @@ jackspeak@^3.1.2:
|
||||
"@pkgjs/parseargs" "^0.11.0"
|
||||
|
||||
jake@^10.8.5:
|
||||
version "10.9.1"
|
||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.1.tgz#8dc96b7fcc41cb19aa502af506da4e1d56f5e62b"
|
||||
integrity sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==
|
||||
version "10.9.4"
|
||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.4.tgz#d626da108c63d5cfb00ab5c25fadc7e0084af8e6"
|
||||
integrity sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==
|
||||
dependencies:
|
||||
async "^3.2.3"
|
||||
chalk "^4.0.2"
|
||||
async "^3.2.6"
|
||||
filelist "^1.0.4"
|
||||
minimatch "^3.1.2"
|
||||
picocolors "^1.1.1"
|
||||
|
||||
js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
@@ -1522,11 +1577,6 @@ js-yaml@^4.1.0:
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
jsbn@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
|
||||
integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==
|
||||
|
||||
json-buffer@3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
|
||||
@@ -1555,9 +1605,9 @@ jsonfile@^4.0.0:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonfile@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
||||
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62"
|
||||
integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==
|
||||
dependencies:
|
||||
universalify "^2.0.0"
|
||||
optionalDependencies:
|
||||
@@ -1580,11 +1630,11 @@ jsonwebtoken@^9.0.2:
|
||||
semver "^7.5.4"
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9"
|
||||
integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "1.0.1"
|
||||
buffer-equal-constant-time "^1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
@@ -1729,12 +1779,17 @@ matcher@^3.0.0:
|
||||
dependencies:
|
||||
escape-string-regexp "^4.0.0"
|
||||
|
||||
math-intrinsics@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
|
||||
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
|
||||
|
||||
micromatch@^4.0.2:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
|
||||
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
|
||||
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
|
||||
dependencies:
|
||||
braces "^3.0.2"
|
||||
braces "^3.0.3"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
mime-db@1.52.0:
|
||||
@@ -1770,13 +1825,13 @@ mimic-response@^3.1.0:
|
||||
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
|
||||
|
||||
minimatch@^10.0.0:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b"
|
||||
integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==
|
||||
version "10.1.1"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55"
|
||||
integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
"@isaacs/brace-expansion" "^5.0.0"
|
||||
|
||||
minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
@@ -1871,11 +1926,6 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
ms@^2.0.0, ms@^2.1.1, ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
@@ -1892,9 +1942,9 @@ nice-try@^1.0.4:
|
||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||
|
||||
node-abi@^3.45.0:
|
||||
version "3.71.0"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.71.0.tgz#52d84bbcd8575efb71468fbaa1f9a49b2c242038"
|
||||
integrity sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==
|
||||
version "3.80.0"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.80.0.tgz#d7390951f27caa129cceeec01e1c20fc9f07581c"
|
||||
integrity sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA==
|
||||
dependencies:
|
||||
semver "^7.3.5"
|
||||
|
||||
@@ -1904,9 +1954,9 @@ node-addon-api@^1.6.3:
|
||||
integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==
|
||||
|
||||
node-api-version@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/node-api-version/-/node-api-version-0.2.0.tgz#5177441da2b1046a4d4547ab9e0972eed7b1ac1d"
|
||||
integrity sha512-fthTTsi8CxaBXMaBAD7ST2uylwvsnYxh2PfaScwpMhos6KlSFajXQPcM4ogNE1q2s3Lbz9GCGqeIHC+C6OZnKg==
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/node-api-version/-/node-api-version-0.2.1.tgz#19bad54f6d65628cbee4e607a325e4488ace2de9"
|
||||
integrity sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==
|
||||
dependencies:
|
||||
semver "^7.3.5"
|
||||
|
||||
@@ -2081,6 +2131,11 @@ pend@~1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
picocolors@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||
|
||||
picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
@@ -2119,9 +2174,9 @@ promise-retry@^2.0.1:
|
||||
retry "^0.12.0"
|
||||
|
||||
pump@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
||||
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d"
|
||||
integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==
|
||||
dependencies:
|
||||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
@@ -2261,9 +2316,9 @@ sanitize-filename@^1.6.3:
|
||||
truncate-utf8-bytes "^1.0.0"
|
||||
|
||||
sax@^1.2.4:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
|
||||
integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.3.tgz#fcebae3b756cdc8428321805f4b70f16ec0ab5db"
|
||||
integrity sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==
|
||||
|
||||
semver-compare@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -2280,15 +2335,10 @@ semver@^6.2.0:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.3.2:
|
||||
version "7.6.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.1.tgz#60bfe090bf907a25aa8119a72b9f90ef7ca281b2"
|
||||
integrity sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA==
|
||||
|
||||
semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3:
|
||||
version "7.6.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
|
||||
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
|
||||
semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3:
|
||||
version "7.7.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946"
|
||||
integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==
|
||||
|
||||
serialize-error@^7.0.1:
|
||||
version "7.0.1"
|
||||
@@ -2372,11 +2422,11 @@ socks-proxy-agent@^7.0.0:
|
||||
socks "^2.6.2"
|
||||
|
||||
socks@^2.6.2:
|
||||
version "2.8.3"
|
||||
resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5"
|
||||
integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==
|
||||
version "2.8.7"
|
||||
resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.7.tgz#e2fb1d9a603add75050a2067db8c381a0b5669ea"
|
||||
integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==
|
||||
dependencies:
|
||||
ip-address "^9.0.5"
|
||||
ip-address "^10.0.1"
|
||||
smart-buffer "^4.2.0"
|
||||
|
||||
source-map-support@^0.5.19:
|
||||
@@ -2392,7 +2442,7 @@ source-map@^0.6.0:
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
sprintf-js@^1.1.2, sprintf-js@^1.1.3:
|
||||
sprintf-js@^1.1.2:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"
|
||||
integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==
|
||||
@@ -2454,9 +2504,9 @@ string_decoder@~1.1.1:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba"
|
||||
integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==
|
||||
dependencies:
|
||||
ansi-regex "^6.0.1"
|
||||
|
||||
@@ -2522,9 +2572,9 @@ tmp@^0.0.33:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
tmp@^0.2.0:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae"
|
||||
integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==
|
||||
version "0.2.5"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8"
|
||||
integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==
|
||||
|
||||
to-regex-range@^5.0.1:
|
||||
version "5.0.1"
|
||||
@@ -2546,14 +2596,19 @@ type-fest@^0.13.1:
|
||||
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
|
||||
|
||||
typescript@^5.4.3:
|
||||
version "5.7.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
|
||||
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
|
||||
version "5.9.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
|
||||
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
|
||||
|
||||
undici-types@~5.26.4:
|
||||
version "5.26.5"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||
undici-types@~6.21.0:
|
||||
version "6.21.0"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb"
|
||||
integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
|
||||
|
||||
undici-types@~7.16.0:
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46"
|
||||
integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==
|
||||
|
||||
unique-filename@^2.0.0:
|
||||
version "2.0.1"
|
||||
@@ -2592,9 +2647,9 @@ uri-js@^4.2.2:
|
||||
punycode "^2.1.0"
|
||||
|
||||
utf8-byte-length@^1.0.1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61"
|
||||
integrity sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz#f9f63910d15536ee2b2d5dd4665389715eac5c1e"
|
||||
integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==
|
||||
|
||||
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
|
||||
110
common/fixYmlHashes.js
Normal file
110
common/fixYmlHashes.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import { createHash } from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import YAML from 'yaml';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
async function sha512Base64(filePath) {
|
||||
const buf = await fs.readFile(filePath);
|
||||
const h = createHash('sha512');
|
||||
h.update(buf);
|
||||
return h.digest('base64');
|
||||
}
|
||||
|
||||
async function fileSize(filePath) {
|
||||
const st = await fs.stat(filePath);
|
||||
return st.size;
|
||||
}
|
||||
|
||||
async function fixOneYaml(ymlPath, distDir) {
|
||||
let raw;
|
||||
try {
|
||||
raw = await fs.readFile(ymlPath, 'utf8');
|
||||
} catch (e) {
|
||||
console.error(`✗ Cannot read ${ymlPath}:`, e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
let doc;
|
||||
try {
|
||||
doc = YAML.parse(raw);
|
||||
} catch (e) {
|
||||
console.error(`✗ Cannot parse YAML ${ymlPath}:`, e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!doc || !Array.isArray(doc.files)) {
|
||||
console.warn(`! ${path.basename(ymlPath)} has no 'files' array — skipped.`);
|
||||
return;
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
|
||||
// Update each files[i].sha512 and files[i].size based on files[i].url
|
||||
for (const entry of doc.files) {
|
||||
if (!entry?.url) continue;
|
||||
|
||||
const target = path.resolve(distDir, entry.url);
|
||||
try {
|
||||
const [hash, size] = await Promise.all([sha512Base64(target), fileSize(target)]);
|
||||
if (entry.sha512 !== hash || entry.size !== size) {
|
||||
console.log(`• ${path.basename(ymlPath)}: refresh ${entry.url}`);
|
||||
entry.sha512 = hash;
|
||||
entry.size = size;
|
||||
changed = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`! Missing or unreadable file for ${entry.url} (referenced by ${path.basename(ymlPath)}): ${e.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update top-level sha512 for the primary "path" file if present
|
||||
if (doc.path) {
|
||||
const primary = path.resolve(distDir, doc.path);
|
||||
try {
|
||||
const hash = await sha512Base64(primary);
|
||||
if (doc.sha512 !== hash) {
|
||||
console.log(`• ${path.basename(ymlPath)}: refresh top-level sha512 for path=${doc.path}`);
|
||||
doc.sha512 = hash;
|
||||
changed = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`! Primary 'path' file not found for ${path.basename(ymlPath)}: ${doc.path} (${e.message})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
const out = YAML.stringify(doc);
|
||||
await fs.writeFile(ymlPath, out, 'utf8');
|
||||
console.log(`✓ Updated ${path.basename(ymlPath)}`);
|
||||
} else {
|
||||
console.log(`= No changes for ${path.basename(ymlPath)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const distDir = path.resolve(process.argv[2] ?? path.join(__dirname, '..', 'app', 'dist'));
|
||||
const entries = await fs.readdir(distDir);
|
||||
const ymls = entries.filter(f => f.toLowerCase().endsWith('.yml'));
|
||||
|
||||
if (ymls.length === 0) {
|
||||
console.warn(`No .yml files found in ${distDir}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Scanning ${distDir}`);
|
||||
for (const y of ymls) {
|
||||
await fixOneYaml(path.join(distDir, y), distDir);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -6,7 +6,7 @@ const { getFiles } = require('./helpers');
|
||||
|
||||
const readFilePromise = promisify(fs.readFile);
|
||||
|
||||
const translationRegex = /_t\(\s*['"]([^'"]+)['"]\s*,\s*\{\s*defaultMessage\s*:\s*['"]([^'"]+)['"]\s*\}/g;
|
||||
const translationRegex = /_t\(\s*['"]([^'"]+)['"]\s*,\s*\{\s*defaultMessage\s*:\s*(?:'([^'\\]*(?:\\.[^'\\]*)*)'|"([^"\\]*(?:\\.[^"\\]*)*)"|\`([^`\\]*(?:\\.[^`\\]*)*(?:\{[^}]*\}[^`\\]*(?:\\.[^`\\]*)*)*)\`)(?:\s*,\s*[^}]*)*\s*\}/g;
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
@@ -20,7 +20,8 @@ async function extractTranslationsFromFile(file) {
|
||||
let match;
|
||||
|
||||
while ((match = translationRegex.exec(content)) !== null) {
|
||||
const [_, key, defaultText] = match;
|
||||
const [_, key, singleQuotedText, doubleQuotedText, templateLiteral] = match;
|
||||
const defaultText = singleQuotedText || doubleQuotedText || templateLiteral;
|
||||
translations[key] = defaultText;
|
||||
}
|
||||
|
||||
|
||||
@@ -160,4 +160,31 @@ program
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('sort')
|
||||
.description('Sort translation files by keys')
|
||||
.action(() => {
|
||||
try {
|
||||
const languages = getAllNonDefaultLanguages();
|
||||
for (const language of languages) {
|
||||
const filePath = `./translations/${language}.json`;
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const translations = JSON.parse(content);
|
||||
const sortedTranslations = {};
|
||||
Object.keys(translations)
|
||||
.sort()
|
||||
.forEach(key => {
|
||||
// @ts-ignore
|
||||
sortedTranslations[key] = translations[key];
|
||||
});
|
||||
fs.writeFileSync(filePath, JSON.stringify(sortedTranslations, null, 2), 'utf-8');
|
||||
console.log(`Sorted translations for language: ${language}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error('Error during sort:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = { program };
|
||||
|
||||
133
common/translations-cli/translate.js
Normal file
133
common/translations-cli/translate.js
Normal file
@@ -0,0 +1,133 @@
|
||||
require('dotenv').config({ path: '.env.translation' });
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const OpenAI = require('openai');
|
||||
|
||||
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
||||
|
||||
const translationsDir = path.join(__dirname, '../../translations');
|
||||
const enFilePath = path.join(translationsDir, 'en.json');
|
||||
|
||||
const languageNames = {
|
||||
'cs.json': 'Czech',
|
||||
'de.json': 'German',
|
||||
'es.json': 'Spanish',
|
||||
'fr.json': 'French',
|
||||
'it.json': 'Italian',
|
||||
'ja.json': 'Japanese',
|
||||
'ko.json': 'Korean',
|
||||
'pt.json': 'Portuguese',
|
||||
'sk.json': 'Slovak',
|
||||
'zh.json': 'Chinese'
|
||||
};
|
||||
|
||||
// Read source (english)
|
||||
const enTranslations = JSON.parse(fs.readFileSync(enFilePath, 'utf8'));
|
||||
const enKeys = Object.keys(enTranslations);
|
||||
|
||||
// Get all translation files
|
||||
const translationFiles = fs.readdirSync(translationsDir)
|
||||
.filter(file => file.endsWith('.json') && file !== 'en.json')
|
||||
.sort();
|
||||
|
||||
console.log(`Found ${enKeys.length} keys in en.json\n`);
|
||||
console.log('='.repeat(80));
|
||||
|
||||
async function translateMissingIds({file, translations, missingIds}){
|
||||
const languageName = languageNames[file];
|
||||
if (!languageName) {
|
||||
console.log(`No language name mapping for file: ${file}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build object with only missing translations
|
||||
const needed = {};
|
||||
missingIds.forEach(key => {
|
||||
needed[key] = enTranslations[key];
|
||||
});
|
||||
|
||||
// Get all existing translations as style examples
|
||||
const existingTranslations = {};
|
||||
Object.keys(translations).forEach(key => {
|
||||
if (translations[key] && !translations[key].startsWith('***')) {
|
||||
existingTranslations[key] = {
|
||||
en: enTranslations[key],
|
||||
translated: translations[key]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const prompt = `You are a professional translator for DbGate, a database management application.
|
||||
|
||||
Translate the following English UI strings to ${languageName}.
|
||||
|
||||
IMPORTANT RULES:
|
||||
1. Preserve ALL placeholders exactly as they appear: {plugin}, {columnNumber}, {0}, {1}, etc.
|
||||
2. Maintain technical terminology appropriately for database software
|
||||
3. Match the translation style, tone, and formality of the existing translations shown below
|
||||
4. Keep the same level of brevity or verbosity as the existing translations
|
||||
5. Return ONLY valid JSON - no markdown, no explanations, no code blocks
|
||||
6. Use the same keys as provided
|
||||
|
||||
EXISTING TRANSLATIONS (for style reference):
|
||||
${JSON.stringify(existingTranslations, null, 2)}
|
||||
|
||||
STRINGS TO TRANSLATE:
|
||||
${JSON.stringify(needed, null, 2)}
|
||||
|
||||
Return format: {"key": "translated value", ...}`;
|
||||
|
||||
const response = await client.chat.completions.create({
|
||||
model: 'gpt-5.1',
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are a professional translator specializing in software localization. Match the style and tone of existing translations. Return only valid JSON.' },
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
temperature: 0.2
|
||||
});
|
||||
|
||||
let translatedJson = response.choices[0].message.content.trim();
|
||||
|
||||
// Remove markdown code blocks if present
|
||||
translatedJson = translatedJson.replace(/^```json\n?/, '').replace(/\n?```$/, '');
|
||||
|
||||
return JSON.parse(translatedJson);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
for (const file of translationFiles) {
|
||||
const filePath = path.join(translationsDir, file);
|
||||
const translations = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
|
||||
const missingIds = enKeys.filter(key => !translations.hasOwnProperty(key) || (typeof translations[key] === 'string' && translations[key].startsWith('***')));
|
||||
|
||||
|
||||
console.log(`\n${file.toUpperCase()}`);
|
||||
console.log('-'.repeat(80));
|
||||
|
||||
if (missingIds.length === 0) {
|
||||
console.log('✓ All translations complete!');
|
||||
continue;
|
||||
} else {
|
||||
console.log(`Found ${missingIds.length} untranslated IDs\n`);
|
||||
}
|
||||
|
||||
const newTranslations = await translateMissingIds({file, translations, missingIds});
|
||||
|
||||
if (!newTranslations) {
|
||||
console.log(`Skipping file due to translation error: ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(newTranslations)) {
|
||||
translations[key] = value;
|
||||
console.log(`Translated: ${key} => ${value}`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(filePath, JSON.stringify(translations, null, 2) + '\n', 'utf8');
|
||||
console.log(`\n✓ Updated translations written to ${file}`);
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('Translation complete!\n');
|
||||
})();
|
||||
@@ -4,6 +4,7 @@ const volatilePackages = [
|
||||
'@clickhouse/client',
|
||||
'bson', // this package is already bundled and is used in mongodb
|
||||
'mongodb',
|
||||
'mongodb-old',
|
||||
'mongodb-client-encryption',
|
||||
'tedious',
|
||||
'msnodesqlv8',
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mssql: true,
|
||||
oracle: true,
|
||||
sqlite: true,
|
||||
mongo: true
|
||||
mongo: true,
|
||||
dynamo: true,
|
||||
};
|
||||
|
||||
@@ -3,8 +3,58 @@ const os = require('os');
|
||||
const fs = require('fs');
|
||||
|
||||
const baseDir = path.join(os.homedir(), '.dbgate');
|
||||
const testApiPidFile = path.join(__dirname, 'tmpdata', 'test-api.pid');
|
||||
const aigwmockPidFile = path.join(__dirname, 'tmpdata', 'aigwmock.pid');
|
||||
|
||||
function readProcessStartTime(pid) {
|
||||
if (process.platform === 'linux') {
|
||||
try {
|
||||
const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf-8');
|
||||
return stat.split(' ')[21] || null;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isPidStillOurs(meta) {
|
||||
if (!meta || !(meta.pid > 0)) return false;
|
||||
if (process.platform === 'linux' && meta.startTime) {
|
||||
const current = readProcessStartTime(meta.pid);
|
||||
return current === meta.startTime;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function stopProcessByPidFile(pidFile) {
|
||||
if (!fs.existsSync(pidFile)) return;
|
||||
try {
|
||||
const content = fs.readFileSync(pidFile, 'utf-8').trim();
|
||||
let meta;
|
||||
try {
|
||||
meta = JSON.parse(content);
|
||||
} catch (_) {
|
||||
const pid = Number(content);
|
||||
meta = Number.isInteger(pid) && pid > 0 ? { pid } : null;
|
||||
}
|
||||
if (isPidStillOurs(meta)) {
|
||||
process.kill(meta.pid);
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore stale PID files and dead processes
|
||||
}
|
||||
try {
|
||||
fs.unlinkSync(pidFile);
|
||||
} catch (err) {
|
||||
// ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
function clearTestingData() {
|
||||
stopProcessByPidFile(testApiPidFile);
|
||||
stopProcessByPidFile(aigwmockPidFile);
|
||||
|
||||
if (fs.existsSync(path.join(baseDir, 'connections-e2etests.jsonl'))) {
|
||||
fs.unlinkSync(path.join(baseDir, 'connections-e2etests.jsonl'));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ module.exports = defineConfig({
|
||||
// baseUrl: 'http://localhost:3000',
|
||||
// trashAssetsBeforeRuns: false,
|
||||
chromeWebSecurity: false,
|
||||
reporter: process.env.CI ? 'mocha-reporter-gha' : 'spec',
|
||||
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
@@ -36,6 +37,9 @@ module.exports = defineConfig({
|
||||
case 'browse-data':
|
||||
serverProcess = exec('yarn start:browse-data');
|
||||
break;
|
||||
case 'rest':
|
||||
serverProcess = exec('yarn start:rest');
|
||||
break;
|
||||
case 'team':
|
||||
serverProcess = exec('yarn start:team');
|
||||
break;
|
||||
@@ -48,6 +52,12 @@ module.exports = defineConfig({
|
||||
case 'charts':
|
||||
serverProcess = exec('yarn start:charts');
|
||||
break;
|
||||
case 'redis':
|
||||
serverProcess = exec('yarn start:redis');
|
||||
break;
|
||||
case 'ai-chat':
|
||||
serverProcess = exec('yarn start:ai-chat');
|
||||
break;
|
||||
}
|
||||
|
||||
await waitOn({ resources: ['http://localhost:3000'] });
|
||||
|
||||
@@ -113,6 +113,18 @@ describe('Add connection', () => {
|
||||
cy.contains('performance_schema');
|
||||
});
|
||||
|
||||
it('Plugin tab', () => {
|
||||
cy.testid('WidgetIconPanel_menu').click();
|
||||
cy.contains('Tools').click();
|
||||
cy.contains('Manage plugins').click();
|
||||
cy.contains('dbgate-plugin-theme-total-white').click();
|
||||
// text from plugin markdown
|
||||
cy.contains('Total white theme');
|
||||
// wait for load logos
|
||||
cy.wait(2000);
|
||||
cy.themeshot('view-plugin-tab');
|
||||
});
|
||||
|
||||
it('export connections', () => {
|
||||
cy.testid('WidgetIconPanel_menu').click();
|
||||
cy.contains('Tools').click();
|
||||
|
||||
105
e2e-tests/cypress/e2e/ai-chat.cy.js
Normal file
105
e2e-tests/cypress/e2e/ai-chat.cy.js
Normal file
@@ -0,0 +1,105 @@
|
||||
Cypress.on('uncaught:exception', err => {
|
||||
if (err.message.includes("Failed to execute 'importScripts' on 'WorkerGlobalScope'")) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('http://localhost:3000');
|
||||
cy.viewport(1250, 900);
|
||||
});
|
||||
|
||||
describe('Database Chat (MySQL)', () => {
|
||||
it('Database chat - chart of popular genres', () => {
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.testid('TabsPanel_buttonNewObject').click();
|
||||
cy.testid('NewObjectModal_databaseChat').click();
|
||||
cy.wait(1000);
|
||||
cy.get('body').realType('show me chart of most popular genres');
|
||||
cy.get('body').realPress('Enter');
|
||||
cy.testid('DatabaseChatTab_executeAllQueries', { timeout: 30000 }).click();
|
||||
cy.testid('chart-canvas', { timeout: 30000 }).should($c =>
|
||||
expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/)
|
||||
);
|
||||
cy.themeshot('database-chat-chart');
|
||||
});
|
||||
|
||||
it('Database chat - find most popular artist', () => {
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.testid('TabsPanel_buttonNewObject').click();
|
||||
cy.testid('NewObjectModal_databaseChat').click();
|
||||
cy.wait(1000);
|
||||
cy.get('body').realType('find most popular artist');
|
||||
cy.get('body').realPress('Enter');
|
||||
cy.testid('DatabaseChatTab_executeAllQueries', { timeout: 30000 }).click();
|
||||
cy.contains('Iron Maiden', { timeout: 30000 });
|
||||
cy.themeshot('database-chat-popular-artist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GraphQL Chat', () => {
|
||||
it('GraphQL chat - list users', () => {
|
||||
cy.contains('REST GraphQL').click();
|
||||
cy.testid('TabsPanel_buttonNewObject').click();
|
||||
cy.testid('NewObjectModal_graphqlChat').click();
|
||||
cy.wait(1000);
|
||||
cy.get('body').realType('list all users');
|
||||
cy.get('body').realPress('Enter');
|
||||
cy.testid('GraphQlChatTab_executeAllQueries', { timeout: 30000 }).click();
|
||||
cy.contains('users', { timeout: 30000 });
|
||||
cy.themeshot('graphql-chat-list-users');
|
||||
});
|
||||
|
||||
it('GraphQL chat - product categories chart', () => {
|
||||
cy.contains('REST GraphQL').click();
|
||||
cy.testid('TabsPanel_buttonNewObject').click();
|
||||
cy.testid('NewObjectModal_graphqlChat').click();
|
||||
cy.wait(1000);
|
||||
cy.get('body').realType('show me a chart of product categories');
|
||||
cy.get('body').realPress('Enter');
|
||||
cy.testid('GraphQlChatTab_executeAllQueries', { timeout: 30000 }).click();
|
||||
cy.testid('chart-canvas', { timeout: 30000 }).should($c =>
|
||||
expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/)
|
||||
);
|
||||
cy.themeshot('graphql-chat-categories-chart');
|
||||
});
|
||||
|
||||
it('GraphQL chat - find most expensive product', () => {
|
||||
cy.contains('REST GraphQL').click();
|
||||
cy.testid('TabsPanel_buttonNewObject').click();
|
||||
cy.testid('NewObjectModal_graphqlChat').click();
|
||||
cy.wait(1000);
|
||||
cy.get('body').realType('find the most expensive product');
|
||||
cy.get('body').realPress('Enter');
|
||||
cy.testid('GraphQlChatTab_executeAllQueries', { timeout: 30000 }).click();
|
||||
cy.contains('products', { timeout: 30000 });
|
||||
cy.themeshot('graphql-chat-expensive-product');
|
||||
});
|
||||
|
||||
it('GraphQL chat - show all categories', () => {
|
||||
cy.contains('REST GraphQL').click();
|
||||
cy.testid('TabsPanel_buttonNewObject').click();
|
||||
cy.testid('NewObjectModal_graphqlChat').click();
|
||||
cy.wait(1000);
|
||||
cy.get('body').realType('show all categories');
|
||||
cy.get('body').realPress('Enter');
|
||||
cy.testid('GraphQlChatTab_executeAllQueries', { timeout: 30000 }).click();
|
||||
cy.contains('categories', { timeout: 30000 });
|
||||
cy.themeshot('graphql-chat-all-categories');
|
||||
});
|
||||
|
||||
it('Explain query error', () => {
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.testid('TabsPanel_buttonNewObject').click();
|
||||
cy.testid('NewObjectModal_query').click();
|
||||
cy.wait(1000);
|
||||
cy.get('body').realType('select * from Invoice2');
|
||||
cy.contains('Execute').click();
|
||||
cy.testid('MessageViewRow-explainErrorButton-1').click();
|
||||
cy.testid('ChatCodeRenderer_useSqlButton', { timeout: 30000 });
|
||||
cy.themeshot('explain-query-error');
|
||||
});
|
||||
});
|
||||
@@ -60,7 +60,7 @@ describe('Data browser data', () => {
|
||||
cy.contains('MyChinook').click();
|
||||
cy.testid('SqlObjectList_search').clear().type('album');
|
||||
cy.contains('Tables (1/11)');
|
||||
cy.contains('347 rows, InnoDB');
|
||||
cy.contains('347 rows, 65.5 KB, InnoDB');
|
||||
cy.testid('SqlObjectList_searchMenuDropDown').click();
|
||||
cy.contains('Column name').click();
|
||||
cy.contains('Tables (2/11)');
|
||||
@@ -85,14 +85,16 @@ describe('Data browser data', () => {
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.contains('Album').click();
|
||||
|
||||
// hide what is not needed
|
||||
cy.testid('WidgetIconPanel_database').click();
|
||||
cy.testid('DataGrid_itemReferences').click();
|
||||
|
||||
cy.testid('DataFilterControl_input_Title').type('Rock{enter}');
|
||||
cy.contains('Rows: 7');
|
||||
cy.testid('DataFilterControl_input_AlbumId').type('>10xxx{enter}');
|
||||
cy.contains('Rows: 7');
|
||||
cy.testid('DataFilterControl_filtermenu_Title').click();
|
||||
// hide what is not needed
|
||||
cy.testid('WidgetIconPanel_database').click();
|
||||
cy.testid('DataGrid_itemReferences').click();
|
||||
cy.testid('DataFilterControl_filtermenu_ArtistId').click();
|
||||
cy.themeshot('data-browser-filter');
|
||||
cy.testid('DataGridCore_button_clearFilters').click();
|
||||
cy.contains('Rows: 347');
|
||||
@@ -202,7 +204,7 @@ describe('Data browser data', () => {
|
||||
cy.themeshot('query-editor-join-wizard');
|
||||
});
|
||||
|
||||
it('Mongo JSON data view', () => {
|
||||
it('Mongo query JSON data view', () => {
|
||||
cy.contains('Mongo-connection').click();
|
||||
cy.contains('MgChinook').click();
|
||||
cy.contains('Customer').click();
|
||||
@@ -213,9 +215,10 @@ describe('Data browser data', () => {
|
||||
cy.contains('Open query').click();
|
||||
cy.wait(1000);
|
||||
cy.contains('Execute').click();
|
||||
cy.testid('WidgetIconPanel_cell-data').click();
|
||||
cy.testid('TabContent_1').contains('Leonie').rightclick();
|
||||
cy.contains('Show cell data').click();
|
||||
// test JSON view
|
||||
cy.contains('Country: "Brazil"');
|
||||
cy.contains('Country: "Germany"');
|
||||
cy.themeshot('mongo-query-json-view');
|
||||
});
|
||||
|
||||
@@ -293,7 +296,8 @@ describe('Data browser data', () => {
|
||||
// cy.contains('location').click();
|
||||
cy.contains('14.2').click();
|
||||
cy.contains('13.9').click({ shiftKey: true });
|
||||
cy.testid('WidgetIconPanel_cell-data').click();
|
||||
cy.testid('WidgetIconPanel_database').click();
|
||||
cy.testid('TableDataTab_toggleCellDataView').click();
|
||||
cy.wait(2000);
|
||||
cy.themeshot('cell-map-view');
|
||||
});
|
||||
@@ -310,17 +314,6 @@ describe('Data browser data', () => {
|
||||
cy.themeshot('search-in-connections');
|
||||
});
|
||||
|
||||
it('Plugin tab', () => {
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Manage plugins').click();
|
||||
cy.contains('dbgate-plugin-theme-total-white').click();
|
||||
// text from plugin markdown
|
||||
cy.contains('Total white theme');
|
||||
// wait for load logos
|
||||
cy.wait(2000);
|
||||
cy.themeshot('view-plugin-tab');
|
||||
});
|
||||
|
||||
it('Edit mongo data JSON', () => {
|
||||
// TODO FIX: Missing button+ctx menu Revert all changes, missing button+ctx menu add document
|
||||
// TODO: Dark theme - not visible changed and deleted document
|
||||
@@ -348,7 +341,7 @@ describe('Data browser data', () => {
|
||||
cy.themeshot('save-changes-mongodb');
|
||||
});
|
||||
|
||||
it('Edit mongo data JSON', () => {
|
||||
it('Mongo JSON cell view', () => {
|
||||
// TODO FIX: Auto expand cell view
|
||||
cy.contains('Mongo-connection').click();
|
||||
cy.contains('MgRivers').click();
|
||||
@@ -358,7 +351,8 @@ describe('Data browser data', () => {
|
||||
cy.testid('ColumnManagerRow_checkbox_countries.1').click();
|
||||
cy.testid('ColumnManagerRow_checkbox__id').click();
|
||||
cy.testid('DataFilterControl_input_countries.1').type('EXISTS{enter}');
|
||||
cy.testid('WidgetIconPanel_cell-data').click();
|
||||
cy.contains('Austria').click();
|
||||
cy.testid('CollectionDataTab_toggleCellDataView').click();
|
||||
cy.themeshot('mongodb-json-cell-view');
|
||||
});
|
||||
|
||||
@@ -483,4 +477,78 @@ describe('Data browser data', () => {
|
||||
cy.testid('DataDeployTab_importIntoDb').click();
|
||||
cy.themeshot('data-replicator');
|
||||
});
|
||||
|
||||
it('Form cell view', () => {
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.contains('Invoice').click();
|
||||
cy.contains('Rows: 412');
|
||||
cy.get('[data-row="0"][data-col="header"]').click();
|
||||
cy.get('[data-row="1"][data-col="header"]').click();
|
||||
cy.get('[data-row="0"][data-col="header"]').click();
|
||||
cy.contains('Autodetect - Form');
|
||||
cy.themeshot('form-cell-view');
|
||||
});
|
||||
|
||||
it('Group by', () => {
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.contains('Album').click();
|
||||
cy.testid('WidgetIconPanel_database').click();
|
||||
cy.testid('ColumnHeaderControl_dropdown_ArtistId').click();
|
||||
cy.contains('Group by').click();
|
||||
cy.testid('ColumnHeaderControl_dropdown_Title').first().click();
|
||||
cy.themeshot('data-browser-group-by');
|
||||
});
|
||||
|
||||
it('Filter by expanded column', () => {
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.contains('Album').click();
|
||||
cy.testid('WidgetIconPanel_database').click();
|
||||
cy.testid('ColumnManagerRow_expand_ArtistId').click();
|
||||
cy.testid('ColumnManagerRow_checkbox_ArtistId.Name').click();
|
||||
cy.testid('ColumnManagerRow_checkbox_ArtistId').click();
|
||||
cy.testid('DataFilterControl_input_ArtistId.Name').type('mich{enter}');
|
||||
cy.themeshot('data-browser-filter-by-expanded');
|
||||
});
|
||||
|
||||
it('DynamoDB', () => {
|
||||
cy.contains('Dynamo-connection').click();
|
||||
cy.contains('us-east-1').click();
|
||||
|
||||
cy.contains('Album').click();
|
||||
cy.contains('Pearl Jam').click();
|
||||
cy.themeshot('dynamodb-table-data');
|
||||
cy.contains('Switch to JSON').click();
|
||||
cy.themeshot('dynamodb-json-view');
|
||||
|
||||
cy.contains('Customer').click();
|
||||
cy.testid('DataFilterControl_input_CustomerId').type('<=10{enter}');
|
||||
cy.contains('Rows: 10');
|
||||
cy.wait(1000);
|
||||
cy.contains('Helena').click().rightclick();
|
||||
cy.contains('Show cell data').click();
|
||||
cy.contains('City: "Prague"');
|
||||
cy.themeshot('dynamodb-query-json-view');
|
||||
|
||||
cy.contains('Switch to JSON').click();
|
||||
cy.contains('Leonie').rightclick();
|
||||
cy.contains('Edit document').click();
|
||||
|
||||
Array.from({ length: 11 }).forEach(() => cy.realPress('ArrowDown'));
|
||||
Array.from({ length: 14 }).forEach(() => cy.realPress('ArrowRight'));
|
||||
Array.from({ length: 7 }).forEach(() => cy.realPress('Delete'));
|
||||
cy.realType('Italy');
|
||||
cy.testid('EditJsonModal_saveButton').click();
|
||||
|
||||
cy.contains('Helena').rightclick();
|
||||
cy.contains('Delete document').click();
|
||||
cy.contains('Save').click();
|
||||
cy.themeshot('dynamodb-save-changes');
|
||||
|
||||
cy.testid('SqlObjectList_addButton').click();
|
||||
cy.contains('New collection/container').click();
|
||||
cy.themeshot('dynamodb-new-collection');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,61 +110,127 @@ describe('Charts', () => {
|
||||
cy.themeshot('new-object-window');
|
||||
});
|
||||
|
||||
it('Database chat - charts', () => {
|
||||
it('Switch language', () => {
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.testid('TabsPanel_buttonNewObject').click();
|
||||
cy.testid('NewObjectModal_databaseChat').click();
|
||||
cy.wait(1000);
|
||||
cy.get('body').realType('show me chart of most popular genres');
|
||||
cy.get('body').realPress('{enter}');
|
||||
cy.testid('DatabaseChatTab_executeAllQueries', { timeout: 30000 }).click();
|
||||
cy.wait(5000);
|
||||
cy.testid('chart-canvas').should($c => expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/));
|
||||
cy.themeshot('database-chat-chart');
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('Deutsch');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Sprache');
|
||||
cy.themeshot('switch-language-de');
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('Français');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Langue');
|
||||
cy.themeshot('switch-language-fr');
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('Español');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Idioma');
|
||||
cy.themeshot('switch-language-es');
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('Čeština');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Jazyk');
|
||||
cy.themeshot('switch-language-cs');
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('中文');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('语言');
|
||||
cy.themeshot('switch-language-zh');
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('English');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings');
|
||||
});
|
||||
|
||||
it('Database chat', () => {
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.testid('TabsPanel_buttonNewObject').click();
|
||||
cy.testid('NewObjectModal_databaseChat').click();
|
||||
cy.wait(1000);
|
||||
cy.get('body').realType('find most popular artist');
|
||||
cy.get('body').realPress('{enter}');
|
||||
cy.testid('DatabaseChatTab_executeAllQueries', { timeout: 30000 }).click();
|
||||
cy.wait(30000);
|
||||
// cy.contains('Iron Maiden');
|
||||
cy.themeshot('database-chat');
|
||||
it('Settings', () => {
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.themeshot('app-settings-general');
|
||||
|
||||
// cy.testid('DatabaseChatTab_promptInput').click();
|
||||
// cy.get('body').realType('I need top 10 songs with the biggest income');
|
||||
// cy.get('body').realPress('{enter}');
|
||||
// cy.contains('Hot Girl', { timeout: 20000 });
|
||||
// cy.wait(1000);
|
||||
// cy.themeshot('database-chat');
|
||||
cy.contains('Behaviour').click();
|
||||
cy.themeshot('app-settings-behaviour');
|
||||
cy.get('[data-testid=BehaviourSettings_useTabPreviewMode]').uncheck();
|
||||
|
||||
// SQL Editor
|
||||
cy.contains('SQL Editor').click();
|
||||
cy.get('[data-testid=SQLEditorSettings_sqlCommandsCase]').select('lowerCase');
|
||||
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('charts_sample').click();
|
||||
cy.contains('employees').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.contains('Customer').rightclick();
|
||||
cy.contains('SQL template').click();
|
||||
cy.contains('CREATE TABLE').click();
|
||||
cy.contains('create table');
|
||||
|
||||
// Default Actions
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Default Actions').click();
|
||||
cy.get('[data-testid=DefaultActionsSettings_useLastUsedAction]').uncheck();
|
||||
|
||||
// Themes
|
||||
cy.contains('Themes').click();
|
||||
cy.themeshot('app-settings-themes');
|
||||
cy.testid('ThemeSkeleton-Dark-built-in').click();
|
||||
cy.testid('ThemeSkeleton-Light-built-in').click();
|
||||
|
||||
// General
|
||||
cy.contains(/^General$/).click();
|
||||
cy.contains('charts_sample');
|
||||
cy.get('[data-testid=GeneralSettings_lockedDatabaseMode]').check();
|
||||
cy.contains('Connections').click();
|
||||
cy.contains('charts_sample').should('not.exist');
|
||||
|
||||
// Datagrid
|
||||
cy.contains('Data grid').click();
|
||||
cy.get('[data-testid=DataGridSettings_showHintColumns]').uncheck();
|
||||
cy.wait(500);
|
||||
cy.contains('Album').click();
|
||||
cy.contains('AC/DC').should('not.exist');
|
||||
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Keyboard shortcuts').click();
|
||||
cy.themeshot('app-settings-keyboard-shortcuts');
|
||||
cy.contains('Chart').click();
|
||||
cy.testid('CommandModal_keyboardButton').click();
|
||||
cy.realPress(['Control', 'g']);
|
||||
cy.realPress('Enter');
|
||||
cy.contains('OK').click();
|
||||
cy.contains('Ctrl+G');
|
||||
|
||||
cy.contains('AI').click();
|
||||
cy.themeshot('app-settings-ai');
|
||||
cy.get('[data-testid=AISettings_addProviderButton]').click();
|
||||
cy.contains('Provider 1');
|
||||
cy.get('[data-testid=AiProviderCard_removeButton]').click();
|
||||
cy.contains('Are you sure you want to remove Provider 1 provider?');
|
||||
cy.contains('OK').click();
|
||||
cy.contains('Provider 1').should('not.exist');
|
||||
});
|
||||
|
||||
it('Explain query error', () => {
|
||||
it('Custom theme', () => {
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Themes').click();
|
||||
cy.testid('ThemeSettings-themeList').contains('Green-Sample').click();
|
||||
cy.testid('WidgetIconPanel_file').click();
|
||||
cy.themeshot('green-theme', { keepTheme: true });
|
||||
|
||||
cy.testid('ThemeSettings-themeList').contains('Solarized-light').click();
|
||||
cy.testid('WidgetIconPanel_database').click();
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.testid('TabsPanel_buttonNewObject').click();
|
||||
cy.testid('NewObjectModal_query').click();
|
||||
cy.wait(1000);
|
||||
cy.get('body').realType('select * from Invoice2');
|
||||
cy.contains('Execute').click();
|
||||
cy.testid('MessageViewRow-explainErrorButton-1').click();
|
||||
cy.testid('ChatCodeRenderer_useSqlButton', { timeout: 30000 });
|
||||
cy.themeshot('explain-query-error');
|
||||
cy.contains('Customer').click();
|
||||
cy.contains('Leonie');
|
||||
cy.testid('WidgetIconPanel_file').click();
|
||||
|
||||
// cy.testid('TabsPanel_buttonNewObject').click();
|
||||
// cy.testid('NewObjectModal_databaseChat').click();
|
||||
// cy.wait(1000);
|
||||
// cy.get('body').realType('show me chart of most popular genres');
|
||||
// cy.get('body').realPress('{enter}');
|
||||
// cy.testid('DatabaseChatTab_executeAllQueries', { timeout: 30000 }).click();
|
||||
// cy.wait(5000);
|
||||
// cy.testid('chart-canvas').should($c => expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/));
|
||||
// cy.themeshot('database-chat-chart');
|
||||
cy.themeshot('solarized-theme', { keepTheme: true });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,8 @@ const { formatQueryWithoutParams } = require('dbgate-tools');
|
||||
|
||||
global.DBGATE_PACKAGES = {
|
||||
'dbgate-tools': require('dbgate-tools'),
|
||||
'dbgate-sqltree': require('dbgate-sqltree'),
|
||||
'dbgate-datalib': require('dbgate-datalib'),
|
||||
};
|
||||
|
||||
function requireEngineDriver(engine) {
|
||||
@@ -50,6 +52,9 @@ function multiTest(testProps, testDefinition) {
|
||||
if (localconfig.mongo && !testProps.skipMongo) {
|
||||
it('MongoDB', () => testDefinition('Mongo-connection', 'my_guitar_shop', 'mongo@dbgate-plugin-mongo'));
|
||||
}
|
||||
if (localconfig.dynamo && !testProps.skipMongo) {
|
||||
it('DynamoDB', () => testDefinition('Dynamo-connection', null, 'dynamodb@dbgate-plugin-dynamodb'));
|
||||
}
|
||||
}
|
||||
|
||||
describe('Transactions', () => {
|
||||
@@ -103,13 +108,70 @@ describe('Transactions', () => {
|
||||
|
||||
describe('Backup table', () => {
|
||||
multiTest({ skipMongo: true }, (connectionName, databaseName, engine, options = {}) => {
|
||||
const implicitTransactions = options.implicitTransactions ?? false;
|
||||
|
||||
cy.contains(connectionName).click();
|
||||
if (databaseName) cy.contains(databaseName).click();
|
||||
cy.contains('customers').rightclick();
|
||||
cy.contains('addresses').rightclick();
|
||||
cy.contains('Create table backup').click();
|
||||
cy.testid('ConfirmSqlModal_okButton').click();
|
||||
cy.contains('_customers').click();
|
||||
cy.contains('Rows: 8').should('be.visible');
|
||||
cy.testid('app-object-group-items-table-backups').contains('addresses').click();
|
||||
cy.contains('Rows: 12').should('be.visible');
|
||||
cy.testid('app-object-group-items-tables').contains('addresses').click();
|
||||
|
||||
cy.contains('Ridgewood').click();
|
||||
cy.testid('TableDataTab_deleteSelectedRows').click();
|
||||
cy.contains('Rosewood').click();
|
||||
cy.testid('TableDataTab_deleteSelectedRows').click();
|
||||
|
||||
cy.contains('Vermont').click();
|
||||
cy.get('body').realType('Wermont{enter}');
|
||||
|
||||
cy.testid('TableDataTab_insertNewRow').click();
|
||||
cy.get('body').realType('Modranska{enter}');
|
||||
cy.realPress(['ArrowLeft']);
|
||||
cy.realPress(['ArrowLeft']);
|
||||
cy.get('body').realType('13{enter}');
|
||||
cy.realPress(['ArrowRight']);
|
||||
cy.get('body').realType('1{enter}');
|
||||
cy.realPress(['ArrowRight']);
|
||||
cy.realPress(['ArrowRight']);
|
||||
cy.realPress(['ArrowRight']);
|
||||
cy.get('body').realType('Prague{enter}');
|
||||
cy.realPress(['ArrowRight']);
|
||||
cy.get('body').realType('CZ{enter}');
|
||||
cy.realPress(['ArrowRight']);
|
||||
cy.get('body').realType('10000{enter}');
|
||||
cy.realPress(['ArrowRight']);
|
||||
cy.get('body').realType('111222333{enter}');
|
||||
|
||||
cy.testid('TableDataTab_save').click();
|
||||
cy.testid('ConfirmSqlModal_okButton', { timeout: 10000 }).click();
|
||||
cy.contains('Rows: 11').should('be.visible'); // wait for save
|
||||
|
||||
cy.testid('app-object-group-items-table-backups').contains('addresses').rightclick();
|
||||
cy.contains('restore script').click();
|
||||
cy.contains('UPDATE'); // wait for query
|
||||
cy.testid('QueryTab_executeButton').click();
|
||||
cy.contains('Query execution finished');
|
||||
|
||||
if (implicitTransactions) {
|
||||
cy.testid('QueryTab_commitTransactionButton').click();
|
||||
cy.contains('Commit Transaction finished');
|
||||
}
|
||||
|
||||
cy.realPress('F1');
|
||||
cy.realType('Close all');
|
||||
cy.realPress('Enter');
|
||||
// cy.testid('CloseTabModal_buttonConfirm').click();
|
||||
cy.wait(1000);
|
||||
|
||||
cy.testid('app-object-group-items-tables').contains('addresses', { timeout: 10000 }).click();
|
||||
|
||||
// check whether data was successfully restored
|
||||
cy.contains('Rows: 12').should('be.visible');
|
||||
cy.contains('Ridgewood');
|
||||
cy.contains('Vermont');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -146,13 +208,15 @@ describe('Import CSV', () => {
|
||||
cy.contains('Import').click();
|
||||
|
||||
cy.get('input[type=file]').selectFile('cypress/fixtures/customers-20.csv', { force: true });
|
||||
cy.contains('customers-20');
|
||||
cy.testid('ImportExportConfigurator_tableMappingSection').contains('customers-20');
|
||||
cy.testid('ImportExportTab_preview_content').contains('50ddd99fAdF48B3').should('be.visible');
|
||||
|
||||
cy.testid('ImportExportTab_executeButton').click();
|
||||
cy.contains('20 rows written').should('be.visible');
|
||||
cy.testid('ImportExportConfigurator_tableMappingSection').contains('20 rows written').should('be.visible');
|
||||
|
||||
cy.testid('SqlObjectList_refreshButton').click();
|
||||
cy.testid('DatabasStatusMenu_refreshFull').click();
|
||||
// cy.contains('Refresh DB structure (incremental)').click();
|
||||
cy.testid('SqlObjectList_container').contains('customers-20').click();
|
||||
cy.contains('Rows: 20').should('be.visible');
|
||||
|
||||
@@ -178,7 +242,7 @@ describe('Import CSV - source error', () => {
|
||||
cy.testid('ImportExportTab_preview_content').contains('Invalid Closing Quote').should('be.visible');
|
||||
|
||||
cy.testid('ImportExportTab_executeButton').click();
|
||||
cy.testid('ImportExportConfigurator_errorInfoIcon_customers-20-err').click();
|
||||
cy.testid('ImportExportConfigurator_errorInfoIcon_customers-20-err', { timeout: 10000 }).click();
|
||||
|
||||
cy.testid('ErrorMessageModal_message').contains('Invalid Closing Quote').should('be.visible');
|
||||
});
|
||||
@@ -197,7 +261,7 @@ describe('Import CSV - target error', () => {
|
||||
cy.contains('customers-20');
|
||||
cy.testid('ImportExportConfigurator_targetName_customers-20').clear().type('system."]`');
|
||||
cy.testid('ImportExportTab_executeButton').click();
|
||||
cy.testid('ImportExportConfigurator_errorInfoIcon_customers-20').click();
|
||||
cy.testid('ImportExportConfigurator_errorInfoIcon_customers-20', { timeout: 10000 }).click();
|
||||
cy.testid('ErrorMessageModal_message').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
120
e2e-tests/cypress/e2e/redis.cy.js
Normal file
120
e2e-tests/cypress/e2e/redis.cy.js
Normal file
@@ -0,0 +1,120 @@
|
||||
Cypress.on('uncaught:exception', (err, runnable) => {
|
||||
// if the error message matches the one about WorkerGlobalScope importScripts
|
||||
if (err.message.includes("Failed to execute 'importScripts' on 'WorkerGlobalScope'")) {
|
||||
// return false to let Cypress know we intentionally want to ignore this error
|
||||
return false;
|
||||
}
|
||||
// otherwise let Cypress throw the error
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('http://localhost:3000');
|
||||
cy.viewport(1250, 900);
|
||||
});
|
||||
|
||||
describe('Redis data', () => {
|
||||
it('String test', () => {
|
||||
cy.contains('Redis-connection').click();
|
||||
cy.contains('db1').click();
|
||||
cy.contains('app').click();
|
||||
cy.contains('version').click();
|
||||
cy.testid('RedisValueDetail_AceEditor').click().realPress('Backspace').realType('1');
|
||||
cy.contains('Save').click();
|
||||
cy.contains('OK').click();
|
||||
});
|
||||
|
||||
it('Hash test', () => {
|
||||
cy.contains('Redis-connection').click();
|
||||
cy.contains('db1').click();
|
||||
cy.contains('user').click();
|
||||
cy.contains('alice').click();
|
||||
cy.testid('RedisKeyDetailTab_RenameKeyButton').click();
|
||||
cy.themeshot('redis-rename-key');
|
||||
cy.realType('3');
|
||||
cy.contains('OK').click();
|
||||
cy.contains('age').click();
|
||||
cy.testid('RedisValueHashDetail_ValueSection').click().realPress('Backspace').realType('8');
|
||||
cy.contains('Add field').click();
|
||||
cy.testid('RedisValueListLikeEdit_key').click().realType('phone');
|
||||
cy.testid('RedisValueListLikeEdit_value').click().realType('123-456-7890');
|
||||
cy.contains('Refresh').click();
|
||||
cy.themeshot('redis-hash-edit');
|
||||
cy.contains('Save').click();
|
||||
cy.themeshot('redis-hash-script-edit');
|
||||
cy.contains('OK').click();
|
||||
});
|
||||
|
||||
it('List test', () => {
|
||||
cy.contains('Redis-connection').click();
|
||||
cy.contains('db1').click();
|
||||
cy.contains('queue').click();
|
||||
cy.contains('emails').click();
|
||||
cy.contains('Add field').click();
|
||||
cy.testid('RedisValueListLikeEdit_value').click().realType('reset');
|
||||
cy.contains('Save').click();
|
||||
cy.contains('OK').click();
|
||||
});
|
||||
|
||||
it('Set test', () => {
|
||||
cy.contains('Redis-connection').click();
|
||||
cy.contains('db1').click();
|
||||
cy.contains('tags').click();
|
||||
cy.contains('Add field').click();
|
||||
cy.testid('RedisValueListLikeEdit_value').click().realType('newtag');
|
||||
cy.contains('Save').click();
|
||||
cy.contains('OK').click();
|
||||
});
|
||||
|
||||
it('ZSet test', () => {
|
||||
cy.contains('Redis-connection').click();
|
||||
cy.contains('db1').click();
|
||||
cy.contains('leaderboard').click();
|
||||
cy.contains('alice').click();
|
||||
cy.testid('RedisValueZSetDetail_score')
|
||||
.click()
|
||||
.realPress('Backspace')
|
||||
.realPress('Backspace')
|
||||
.realPress('Backspace')
|
||||
.realType('35');
|
||||
cy.contains('Save').click();
|
||||
cy.contains('OK').click();
|
||||
cy.contains('35').should('exist');
|
||||
});
|
||||
|
||||
it('JSON test', () => {
|
||||
cy.contains('Redis-connection').click();
|
||||
cy.contains('db1').click();
|
||||
cy.contains('user').click();
|
||||
cy.contains('1:*').click();
|
||||
cy.contains('json').click();
|
||||
cy.testid('RedisValueDetail_displaySelect').select('JSON view');
|
||||
cy.themeshot('redis-json-detail');
|
||||
});
|
||||
|
||||
it('Stream test', () => {
|
||||
cy.contains('Redis-connection').click();
|
||||
cy.contains('db1').click();
|
||||
cy.contains('events').click();
|
||||
cy.contains('Add field').click();
|
||||
cy.testid('RedisValueListLikeEdit_field').click().realType('message');
|
||||
cy.testid('RedisValueListLikeEdit_value').click().realType('Hello, World!');
|
||||
cy.contains('Save').click();
|
||||
cy.contains('OK').click();
|
||||
cy.themeshot('redis-stream');
|
||||
});
|
||||
|
||||
it('Add key', () => {
|
||||
cy.contains('Redis-connection').click();
|
||||
cy.contains('db1').click();
|
||||
cy.testid('RedisKeysTree_addKeyDropdown').click();
|
||||
cy.contains('String').click();
|
||||
cy.testid('NewRedisKeyTab_keyName').click().realType('newstringkey');
|
||||
cy.testid('RedisValueDetail_AceEditor').click().realType('This is a new string key.');
|
||||
cy.contains('Save').click();
|
||||
cy.contains('OK').click();
|
||||
cy.contains('newstringkey').should('exist');
|
||||
cy.testid('RedisKeysTree_addKeyDropdown').click();
|
||||
cy.contains('Hash').click();
|
||||
cy.themeshot('redis-add-hash-key');
|
||||
});
|
||||
});
|
||||
39
e2e-tests/cypress/e2e/rest.cy.js
Normal file
39
e2e-tests/cypress/e2e/rest.cy.js
Normal file
@@ -0,0 +1,39 @@
|
||||
Cypress.on('uncaught:exception', err => {
|
||||
if (err.message.includes("Failed to execute 'importScripts' on 'WorkerGlobalScope'")) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('http://localhost:3000');
|
||||
cy.viewport(1250, 900);
|
||||
});
|
||||
|
||||
describe('REST API connections', () => {
|
||||
it('GraphQL test', () => {
|
||||
cy.contains('REST GraphQL').click();
|
||||
cy.contains('products').click();
|
||||
cy.testid('GraphQlExplorerNode_toggle_products').click();
|
||||
cy.testid('GraphQlExplorerNode_checkbox_products.name').click();
|
||||
cy.testid('GraphQlExplorerNode_checkbox_products.price').click();
|
||||
cy.testid('GraphQlExplorerNode_checkbox_products.description').click();
|
||||
cy.testid('GraphQlExplorerNode_checkbox_products.category').click();
|
||||
cy.testid('GraphQlQueryTab_execute').click();
|
||||
cy.contains('Electronics');
|
||||
cy.themeshot('rest-graphql-query');
|
||||
});
|
||||
it('REST OpenAPI test', () => {
|
||||
cy.contains('REST OpenAPI').click();
|
||||
cy.contains('/api/categories').click();
|
||||
cy.testid('RestApiEndpointTab_execute').click();
|
||||
cy.contains('Electronics');
|
||||
cy.themeshot('rest-openapi-query');
|
||||
});
|
||||
it('REST OData test', () => {
|
||||
cy.contains('REST OData').click();
|
||||
cy.contains('/Users').click();
|
||||
cy.testid('ODataEndpointTab_execute').click();
|
||||
cy.contains('Henry');
|
||||
cy.themeshot('rest-odata-query');
|
||||
});
|
||||
});
|
||||
@@ -36,9 +36,11 @@ Cypress.Commands.add(
|
||||
prevSubject: 'optional',
|
||||
},
|
||||
(subject, file, options) => {
|
||||
cy.window().then(win => {
|
||||
win.__changeCurrentTheme('theme-dark');
|
||||
});
|
||||
if (!options?.keepTheme) {
|
||||
cy.window().then(win => {
|
||||
win.__changeCurrentTheme('dark');
|
||||
});
|
||||
}
|
||||
|
||||
// cy.screenshot(`${file}-dark`, {
|
||||
// onAfterScreenshot: (doc, props) => {
|
||||
@@ -63,9 +65,11 @@ Cypress.Commands.add(
|
||||
// });
|
||||
// });
|
||||
|
||||
cy.window().then(win => {
|
||||
win.__changeCurrentTheme('theme-light');
|
||||
});
|
||||
if (!options?.keepTheme) {
|
||||
cy.window().then(win => {
|
||||
win.__changeCurrentTheme('light');
|
||||
});
|
||||
}
|
||||
|
||||
if (subject) {
|
||||
cy.wrap(subject).screenshot(`${file}-light`, options);
|
||||
|
||||
253
e2e-tests/data/files/themes/Green-Sample
Normal file
253
e2e-tests/data/files/themes/Green-Sample
Normal file
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"themeName": "Green-Sample",
|
||||
"themeType": "light",
|
||||
"themeVariables": {
|
||||
"--theme-generic-font": "oklch(27% 0.07 130)",
|
||||
"--theme-generic-font-hover": "oklch(40% 0.15 130)",
|
||||
"--theme-generic-font-grayed": "oklch(65% 0.05 130)",
|
||||
"--theme-link-foreground": "oklch(40% 0.25 130)",
|
||||
"--theme-content-background": "oklch(95% 0.05 130)",
|
||||
"--theme-widget-panel-background": "oklch(80% 0.1 130)",
|
||||
"--theme-widget-panel-foreground": "oklch(27% 0.07 130)",
|
||||
"--theme-widget-icon-background-active": "oklch(50% 0.12 130)",
|
||||
"--theme-widget-icon-foreground-active": "white",
|
||||
"--theme-widget-icon-foreground-hover": "white",
|
||||
"--theme-widget-icon-border-active": "1px solid white",
|
||||
"--theme-scrollbar-background": "oklch(90% 0.08 130)",
|
||||
"--theme-scrollbar-thumb-background": "oklch(70% 0.12 130)",
|
||||
"--theme-scrollbar-thumb-background-hover": "oklch(40% 0.15 130)",
|
||||
"--theme-scrollbar-corner-background": "oklch(85% 0.1 130)",
|
||||
"--theme-tabs-panel-border": "1px solid oklch(95% 0.05 130)",
|
||||
"--theme-tabs-panel-foreground": "oklch(20% 0.06 130)",
|
||||
"--theme-tabs-panel-active-foreground": "oklch(10% 0.06 130)",
|
||||
"--theme-tabs-panel-background": "oklch(95.5% 0.04 130)",
|
||||
"--theme-tabs-panel-active-background": "oklch(80% 0.12 130)",
|
||||
"--theme-tabs-panel-item-background": "oklch(90% 0.1 130)",
|
||||
"--theme-tabs-panel-active-border": "1px solid oklch(50% 0.2 130)",
|
||||
"--theme-splitter-active": "oklch(50% 0.2 130)",
|
||||
"--theme-splitter-button-background": "oklch(90% 0.1 130)",
|
||||
"--theme-splitter-button-background-active": "oklch(85% 0.15 130)",
|
||||
"--theme-splitter-button-foreground": "oklch(10% 0.06 130)",
|
||||
"--theme-sidebar-background": "oklch(90% 0.1 130)",
|
||||
"--theme-sidebar-background-hover": "oklch(80% 0.12 130)",
|
||||
"--theme-sidebar-background-active": "oklch(75% 0.14 130)",
|
||||
"--theme-sidebar-background-focused": "oklch(70% 0.18 130)",
|
||||
"--theme-sidebar-foreground": "oklch(20% 0.06 130)",
|
||||
"--theme-sidebar-foreground-button": "oklch(40% 0.12 130)",
|
||||
"--theme-sidebar-foreground-grayed": "oklch(65% 0.05 130)",
|
||||
"--theme-sidebar-foreground-hover": "oklch(50% 0.25 130)",
|
||||
"--theme-sidebar-section-background": "oklch(65% 0.05 130)",
|
||||
"--theme-sidebar-section-border": "none",
|
||||
"--theme-sidebar-section-border-top": "1px solid oklch(80% 0.1 130)",
|
||||
"--theme-sidebar-section-foreground": "oklch(10% 0.06 130)",
|
||||
"--theme-sidebar-border": "none",
|
||||
"--theme-altsidebar-background": "oklch(95% 0.05 130)",
|
||||
"--theme-altsidebar-background-grayed": "oklch(97% 0.02 130)",
|
||||
"--theme-altsidebar-background-hover": "oklch(85% 0.1 130)",
|
||||
"--theme-altsidebar-background-active": "oklch(80% 0.12 130)",
|
||||
"--theme-altsidebar-background-focused": "oklch(75% 0.15 130)",
|
||||
"--theme-altsidebar-foreground": "oklch(20% 0.06 130)",
|
||||
"--theme-altsidebar-foreground-button": "oklch(40% 0.12 130)",
|
||||
"--theme-altsidebar-foreground-grayed": "oklch(65% 0.05 130)",
|
||||
"--theme-altsidebar-foreground-hover": "oklch(50% 0.25 130)",
|
||||
"--theme-altsidebar-section-background": "oklch(97% 0.02 130)",
|
||||
"--theme-altsidebar-section-border": "none",
|
||||
"--theme-altsidebar-section-border-top": "1px solid oklch(85% 0.1 130)",
|
||||
"--theme-altsidebar-section-foreground": "oklch(10% 0.06 130)",
|
||||
"--theme-altsidebar-border": "1px solid oklch(90% 0.1 130)",
|
||||
"--theme-searchbox-background": "oklch(80% 0.12 130)",
|
||||
"--theme-searchbox-placeholder": "oklch(65% 0.05 130)",
|
||||
"--theme-searchbox-border": "1px solid oklch(70% 0.15 130)",
|
||||
"--theme-searchbox-background-filtered": "oklch(95% 0.04 110)",
|
||||
"--theme-altsearchbox-background": "oklch(90% 0.1 130)",
|
||||
"--theme-altsearchbox-placeholder": "oklch(65% 0.05 130)",
|
||||
"--theme-altsearchbox-border": "1px solid oklch(80% 0.1 130)",
|
||||
"--theme-inlinebutton-foreground": "oklch(40% 0.12 130)",
|
||||
"--theme-inlinebutton-foreground-disabled": "oklch(65% 0.05 130)",
|
||||
"--theme-inlinebutton-foreground-hover": "black",
|
||||
"--theme-inlinebutton-circle-hover-background": "oklch(85% 0.1 130)",
|
||||
"--theme-inlinebutton-bordered-border": "1px solid oklch(85% 0.1 130)",
|
||||
"--theme-inlinebutton-bordered-hover-border": "1px solid oklch(70% 0.15 130)",
|
||||
"--theme-inlinebutton-bordered-background": "linear-gradient(to bottom, oklch(95% 0.04 130) 5%, oklch(90% 0.1 130) 100%)",
|
||||
"--theme-inlinebutton-bordered-hover-background": "linear-gradient(to bottom, oklch(90% 0.1 130) 5%, oklch(95% 0.04 130) 100%)",
|
||||
"--theme-datagrid-background": "oklch(95% 0.04 130)",
|
||||
"--theme-datagrid-foreground": "oklch(20% 0.06 130)",
|
||||
"--theme-datagrid-foreground-grayed": "oklch(65% 0.05 130)",
|
||||
"--theme-datagrid-border-horizontal": "1px solid oklch(90% 0.1 130)",
|
||||
"--theme-datagrid-border-vertical": "1px solid oklch(95% 0.04 130)",
|
||||
"--theme-datagrid-cell-background": "oklch(97% 0.02 130)",
|
||||
"--theme-datagrid-headercell-background": "oklch(95% 0.04 130)",
|
||||
"--theme-datagrid-cell-background-alt": "oklch(95% 0.04 130)",
|
||||
"--theme-datagrid-cell-background-alt2": "oklch(90% 0.1 130)",
|
||||
"--theme-datagrid-filter-background": "oklch(90% 0.1 130)",
|
||||
"--theme-datagrid-filter-border": "1px solid oklch(85% 0.1 130)",
|
||||
"--theme-datagrid-filter-ok-background": "oklch(95% 0.1 135)",
|
||||
"--theme-datagrid-filter-error-background": "oklch(95% 0.12 30)",
|
||||
"--theme-datagrid-modified-row-background": "oklch(95% 0.1 135)",
|
||||
"--theme-datagrid-modified-cell-background": "oklch(90% 0.15 135)",
|
||||
"--theme-datagrid-inserted-row-background": "oklch(95% 0.1 110)",
|
||||
"--theme-datagrid-deleted-row-background": "oklch(95% 0.1 25)",
|
||||
"--theme-datagrid-selected-cell-background": "oklch(80% 0.1 130)",
|
||||
"--theme-datagrid-focused-cell-background": "oklch(75% 0.15 130)",
|
||||
"--theme-datagrid-focused-cell-border-horizontal": "1px solid oklch(70% 0.2 130)",
|
||||
"--theme-datagrid-focused-cell-border-vertical": "1px solid oklch(70% 0.2 130)",
|
||||
"--theme-datagrid-selected-point-marker": "oklch(50% 0.25 130)",
|
||||
"--theme-datagrid-corner-label-background": "oklch(75% 0.15 130)",
|
||||
"--theme-datagrid-corner-label-border": "1px solid oklch(70% 0.2 130)",
|
||||
"--theme-datagrid-detail-header-background": "oklch(85% 0.05 130)",
|
||||
"--theme-datagrid-detail-header-border": "1px solid oklch(80% 0.1 130)",
|
||||
"--theme-datagrid-cell-foreground-value-green": "oklch(45% 0.2 140)",
|
||||
"--theme-checkbox-check": "oklch(90% 0.1 130)",
|
||||
"--theme-checkbox-background": "oklch(40% 0.25 130)",
|
||||
"--theme-checkbox-border": "1px solid oklch(70% 0.15 130)",
|
||||
"--theme-checkbox-mark": "white",
|
||||
"--theme-checkbox-background-disabled": "oklch(95% 0.04 130)",
|
||||
"--theme-checkbox-background-disabled-before": "oklch(70% 0.15 130)",
|
||||
"--theme-checkbox-hover-not-disabled": "oklch(65% 0.05 130)",
|
||||
"--theme-checkbox-background-inherited": "oklch(85% 0.1 130)",
|
||||
"--theme-table-border": "1px solid oklch(85% 0.1 130)",
|
||||
"--theme-table-cell-background": "oklch(97% 0.02 130)",
|
||||
"--theme-table-cell-empty-background": "oklch(95% 0.04 130)",
|
||||
"--theme-table-cell-empty-foreground": "oklch(65% 0.05 130)",
|
||||
"--theme-table-header-background": "oklch(95% 0.04 130)",
|
||||
"--theme-table-selected-background": "oklch(75% 0.15 130)",
|
||||
"--theme-table-active-background": "oklch(80% 0.1 130)",
|
||||
"--theme-table-hover-background": "oklch(95% 0.04 130)",
|
||||
"--theme-table-added-background": "oklch(95% 0.1 110)",
|
||||
"--theme-table-changed-background": "oklch(95% 0.1 135)",
|
||||
"--theme-table-deleted-background": "oklch(95% 0.1 25)",
|
||||
"--theme-cell-active-border": "2px solid oklch(50% 0.25 130)",
|
||||
"--theme-object-header-background": "oklch(95% 0.04 130)",
|
||||
"--theme-modal-background": "oklch(97% 0.02 130)",
|
||||
"--theme-modal-header-background": "oklch(85% 0.1 130)",
|
||||
"--theme-modal-footer-background": "oklch(97% 0.02 130)",
|
||||
"--theme-modal-border": "1px solid oklch(85% 0.1 130)",
|
||||
"--theme-modal-overlay-background": "color-mix(in srgb, #124012 40%, transparent)",
|
||||
"--theme-modal-shadow": "0 20px 25px -5px color-mix(in srgb, #124012 10%, transparent)",
|
||||
"--theme-modal-close-hover-background": "oklch(70% 0.15 130)",
|
||||
"--theme-formbutton-foreground": "white",
|
||||
"--theme-formbutton-border": "1px solid oklch(40% 0.25 130)",
|
||||
"--theme-formbutton-border-hover": "1px solid oklch(50% 0.3 130)",
|
||||
"--theme-formbutton-border-active": "2px solid oklch(55% 0.35 130)",
|
||||
"--theme-formbutton-background": "oklch(40% 0.25 130)",
|
||||
"--theme-formbutton-background-disabled": "oklch(85% 0.1 130)",
|
||||
"--theme-formbutton-border-disabled": "1px solid oklch(85% 0.1 130)",
|
||||
"--theme-formbutton-foreground-disabled": "oklch(65% 0.05 130)",
|
||||
"--theme-formbutton-background-hover": "oklch(35% 0.3 130)",
|
||||
"--theme-formbutton-background-active": "oklch(35% 0.3 130)",
|
||||
"--theme-outlinebutton-foreground": "oklch(10% 0.06 130)",
|
||||
"--theme-outlinebutton-border": "1px solid oklch(40% 0.25 130)",
|
||||
"--theme-outlinebutton-hover-foreground": "oklch(40% 0.25 130)",
|
||||
"--theme-outlinebutton-hover-border": "2px solid oklch(50% 0.3 130)",
|
||||
"--theme-tabs-control-background": "oklch(95% 0.04 130)",
|
||||
"--theme-tabs-control-border": "1px solid oklch(90% 0.1 130)",
|
||||
"--theme-tabs-control-selected-background": "oklch(98% 0.01 130)",
|
||||
"--theme-tabs-control-selected-border": "2px solid oklch(50% 0.25 130)",
|
||||
"--theme-inline-tabs-border": "1px solid oklch(90% 0.1 130)",
|
||||
"--theme-inline-tabs-border-active": "2px solid oklch(50% 0.25 130)",
|
||||
"--theme-toolstrip-background": "oklch(97% 0.02 130)",
|
||||
"--theme-toolstrip-border": "1px solid oklch(90% 0.1 130)",
|
||||
"--theme-toolstrip-button-foreground": "oklch(27% 0.07 130)",
|
||||
"--theme-panel-border-subtle": "1px solid color-mix(in srgb, oklch(20% 0.06 130) 5%, transparent)",
|
||||
"--theme-panel-type-label-color": "oklch(65% 0.05 130)",
|
||||
"--theme-toolstrip-button-foreground-disabled": "oklch(65% 0.05 130)",
|
||||
"--theme-toolstrip-button-foreground-icon": "oklch(40% 0.12 130)",
|
||||
"--theme-toolstrip-button-background": "oklch(97% 0.02 130)",
|
||||
"--theme-toolstrip-button-background-hover": "oklch(95% 0.04 130)",
|
||||
"--theme-toolstrip-button-background-active": "oklch(90% 0.1 130)",
|
||||
"--theme-toolstrip-button-border": "1px solid oklch(90% 0.1 130)",
|
||||
"--theme-toolstrip-button-border-hover": "1px solid oklch(85% 0.1 130)",
|
||||
"--theme-toolstrip-button-border-disabled": "1px solid oklch(90% 0.1 130)",
|
||||
"--theme-toolstrip-button-split-separator-border": "1px solid oklch(85% 0.1 130)",
|
||||
"--theme-designer-background": "oklch(97% 0.02 130)",
|
||||
"--theme-designer-item-background": "oklch(95% 0.04 130)",
|
||||
"--theme-designer-selection-marker": "oklch(35% 0.3 130)",
|
||||
"--theme-designer-item-border": "1px solid oklch(90% 0.1 130)",
|
||||
"--theme-designer-stroke-color": "oklch(65% 0.05 130)",
|
||||
"--theme-designer-arrow-color": "oklch(27% 0.07 130)",
|
||||
"--theme-designer-select-reactangle-foreground": "oklch(50% 0.25 130)",
|
||||
"--theme-designer-header-background-1": "oklch(70% 0.15 130)",
|
||||
"--theme-designer-header-background-2": "oklch(70% 0.18 180)",
|
||||
"--theme-designer-header-background-3": "oklch(68% 0.15 100)",
|
||||
"--theme-designer-header-background-grayed": "oklch(85% 0.1 130)",
|
||||
"--theme-designer-close-background": "oklch(90% 0.1 130)",
|
||||
"--theme-designer-close-background-hover": "oklch(85% 0.1 130)",
|
||||
"--theme-designer-close-background-active": "oklch(70% 0.15 130)",
|
||||
"--theme-designer-drag-column-background": "oklch(90% 0.2 110)",
|
||||
"--theme-designer-select-column-background": "oklch(90% 0.1 130)",
|
||||
"--theme-statusbar-background": "oklch(40% 0.25 130)",
|
||||
"--theme-statusbar-foreground": "oklch(95% 0.04 130)",
|
||||
"--theme-statusbar-background-hover": "oklch(35% 0.3 130)",
|
||||
"--theme-statusbar-button-background": "oklch(85% 0.1 130)",
|
||||
"--theme-statusbar-button-foreground": "oklch(27% 0.07 130)",
|
||||
"--theme-statusbar-icon-error": "oklch(80% 0.1 25)",
|
||||
"--theme-statusbar-icon-ok": "oklch(85% 0.2 130)",
|
||||
"--theme-aichat-user-background": "oklch(93% 0.06 130)",
|
||||
"--theme-aichat-assistant-background": "oklch(95% 0.04 130)",
|
||||
"--theme-applog-details-background": "oklch(98% 0.01 130)",
|
||||
"--theme-input-border": "1px solid oklch(85% 0.1 130)",
|
||||
"--theme-input-border-hover": "1px solid oklch(70% 0.15 130)",
|
||||
"--theme-input-border-hover-color": "oklch(70% 0.15 130)",
|
||||
"--theme-input-border-focus": "1px solid oklch(50% 0.25 130)",
|
||||
"--theme-input-border-focus-color": "oklch(50% 0.25 130)",
|
||||
"--theme-input-border-disabled": "1px solid oklch(90% 0.1 130)",
|
||||
"--theme-input-background": "white",
|
||||
"--theme-input-foreground": "oklch(20% 0.06 130)",
|
||||
"--theme-input-placeholder": "oklch(65% 0.05 130)",
|
||||
"--theme-input-background-disabled": "oklch(95% 0.04 130)",
|
||||
"--theme-input-foreground-disabled": "oklch(65% 0.05 130)",
|
||||
"--theme-input-focus-ring": "0 0 0 3px color-mix(in srgb, oklch(50% 0.25 130) 10%, transparent)",
|
||||
"--theme-input-multi-clear-background": "oklch(90% 0.1 130)",
|
||||
"--theme-input-multi-clear-foreground": "oklch(40% 0.12 130)",
|
||||
"--theme-input-multi-clear-hover": "oklch(85% 0.1 130)",
|
||||
"--theme-input-shadow": "0 1px 2px 0 color-mix(in srgb, oklch(20% 0.06 130) 5%, transparent)",
|
||||
"--theme-input-shadow-hover": "0 4px 6px -2px color-mix(in srgb, oklch(20% 0.06 130) 8%, transparent)",
|
||||
"--theme-input-shadow-focus": "0 1px 2px 0 color-mix(in srgb, oklch(20% 0.06 130) 5%, transparent)",
|
||||
"--theme-input-inplace-select-shadow": "0 1px 10px 1px oklch(40% 0.12 130)",
|
||||
"--theme-color-selected-border": "2px solid oklch(27% 0.07 130)",
|
||||
"--theme-new-object-button-background": "oklch(90% 0.1 130)",
|
||||
"--theme-new-object-button-background-hover": "oklch(85% 0.1 130)",
|
||||
"--theme-status-valid-background": "oklch(95% 0.1 110)",
|
||||
"--theme-status-testing-background": "oklch(95% 0.1 135)",
|
||||
"--theme-status-error-background": "oklch(95% 0.1 25)",
|
||||
"--theme-status-unconfigured-background": "oklch(95% 0.04 130)",
|
||||
"--theme-status-untested-background": "oklch(94% 0.1 65)",
|
||||
"--theme-dropdown-icon-hover": "oklch(45% 0.3 130)",
|
||||
"--theme-icon-picker-background": "oklch(90% 0.1 130)",
|
||||
"--theme-icon-picker-border": "1px solid oklch(85% 0.1 130)",
|
||||
"--theme-icon-picker-hover": "oklch(85% 0.1 130)",
|
||||
"--theme-icon-picker-selected": "oklch(80% 0.15 130)",
|
||||
"--theme-dbkey-background": "oklch(98% 0.01 130)",
|
||||
"--theme-dbkey-border": "1px solid oklch(90% 0.1 130)",
|
||||
"--theme-dbkey-icon-hover": "oklch(70% 0.15 130)",
|
||||
"--theme-chip-background": "oklch(85% 0.1 130)",
|
||||
"--theme-titlebar-background": "oklch(85% 0.1 130)",
|
||||
"--theme-titlebar-button-hover": "oklch(70% 0.15 130)",
|
||||
"--theme-card-background": "oklch(90% 0.1 130)",
|
||||
"--theme-card-border": "1px solid oklch(85% 0.1 130)",
|
||||
"--theme-content-background-hover": "oklch(95% 0.04 130)",
|
||||
"--theme-admin-menu-item-hover": "oklch(95% 0.04 130)",
|
||||
"--theme-admin-menu-item-active": "oklch(85% 0.1 130)",
|
||||
"--theme-admin-menu-background": "oklch(90% 0.1 130)",
|
||||
"--theme-admin-menu-border": "1px solid oklch(90% 0.1 130)",
|
||||
"--theme-json-tree-string-color": "oklch(45% 0.3 110)",
|
||||
"--theme-json-tree-symbol-color": "oklch(45% 0.3 110)",
|
||||
"--theme-json-tree-boolean-color": "oklch(40% 0.25 130)",
|
||||
"--theme-json-tree-function-color": "oklch(40% 0.25 130)",
|
||||
"--theme-json-tree-number-color": "oklch(50% 0.3 130)",
|
||||
"--theme-json-tree-label-color": "oklch(55% 0.3 140)",
|
||||
"--theme-json-tree-arrow-color": "oklch(65% 0.05 130)",
|
||||
"--theme-json-tree-null-color": "oklch(65% 0.05 130)",
|
||||
"--theme-json-tree-undefined-color": "oklch(65% 0.05 130)",
|
||||
"--theme-json-tree-date-color": "oklch(65% 0.05 130)",
|
||||
"--theme-json-tree-deleted-background": "oklch(95% 0.1 25)",
|
||||
"--theme-json-tree-modified-background": "oklch(95% 0.1 135)",
|
||||
"--theme-json-tree-inserted-background": "oklch(95% 0.1 110)",
|
||||
"--theme-icon-blue": "oklch(40% 0.25 130)",
|
||||
"--theme-icon-green": "oklch(45% 0.2 140)",
|
||||
"--theme-icon-red": "oklch(40% 0.3 25)",
|
||||
"--theme-icon-gold": "oklch(50% 0.2 60)",
|
||||
"--theme-icon-yellow": "oklch(50% 0.15 80)",
|
||||
"--theme-icon-magenta": "oklch(45% 0.3 135)"
|
||||
}
|
||||
}
|
||||
15
e2e-tests/data/redis-db1.redis
Normal file
15
e2e-tests/data/redis-db1.redis
Normal file
@@ -0,0 +1,15 @@
|
||||
HSET "actor:1000" "first_name" "Sandra"
|
||||
HSET "actor:1000" "last_name" "Bullock"
|
||||
HSET "actor:1000" "date_of_birth" "1964"
|
||||
|
||||
HSET "actor:1001" "first_name" "Jon"
|
||||
HSET "actor:1001" "last_name" "Hamm"
|
||||
HSET "actor:1001" "date_of_birth" "1971"
|
||||
|
||||
HSET "actor:1002" "first_name" "Allison"
|
||||
HSET "actor:1002" "last_name" "Janney"
|
||||
HSET "actor:1002" "date_of_birth" "1959"
|
||||
|
||||
HSET "actor:1003" "first_name" "Steve"
|
||||
HSET "actor:1003" "last_name" "Coogan"
|
||||
HSET "actor:1003" "date_of_birth" "1965"
|
||||
14
e2e-tests/data/redis-db2.redis
Normal file
14
e2e-tests/data/redis-db2.redis
Normal file
@@ -0,0 +1,14 @@
|
||||
SET app:name "App"
|
||||
SET app:version "1.0.0"
|
||||
SET app:env "test"
|
||||
SET user:1:json "{\"id\":1,\"name\":\"Alice\",\"email\":\"alice@app.test\",\"roles\":[\"admin\",\"user\"],\"settings\":{\"theme\":\"dark\",\"language\":\"sk\"}}"
|
||||
SET user:2:json "{\"id\":2,\"name\":\"Bob\",\"email\":\"bob@app.test\",\"roles\":[\"user\"],\"settings\":{\"theme\":\"light\",\"language\":\"en\"}}"
|
||||
RPUSH queue:emails "welcome" "reset-password" "newsletter" "promotion" "weekly-digest"
|
||||
HSET user:alice name "Alice" email "alice@app.test" active "true" age "29" country "SK"
|
||||
HSET user:bob name "Bob" email "bob@app.test" active "false" age "34" country "CZ"
|
||||
SADD tags "app" "backend" "database" "redis" "test" "production"
|
||||
ZADD leaderboard 100 "alice" 250 "bob" 180 "carol" 90 "dave" 300 "eve"
|
||||
XADD events * type "login" userId "1" ip "127.0.0.1" device "web"
|
||||
XADD events * type "update-profile" userId "1" field "email" old "alice@app.test" new "alice@new.app"
|
||||
XADD events * type "login" userId "2" ip "10.0.0.5" device "mobile"
|
||||
XADD events * type "logout" userId "1" reason "manual"
|
||||
@@ -5,14 +5,14 @@ services:
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: Pwd2020Db
|
||||
ports:
|
||||
ports:
|
||||
- 16000:5432
|
||||
|
||||
mariadb:
|
||||
image: mariadb
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
restart: always
|
||||
ports:
|
||||
ports:
|
||||
- 16004:3306
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=Pwd2020Db
|
||||
@@ -20,21 +20,21 @@ services:
|
||||
mysql-ssh-login:
|
||||
build: containers/mysql-ssh-login
|
||||
restart: always
|
||||
ports:
|
||||
ports:
|
||||
- 16017:3306
|
||||
- "16012:22"
|
||||
- '16012:22'
|
||||
|
||||
mysql-ssh-keyfile:
|
||||
build: containers/mysql-ssh-keyfile
|
||||
restart: always
|
||||
ports:
|
||||
ports:
|
||||
- 16007:3306
|
||||
- "16008:22"
|
||||
- '16008:22'
|
||||
|
||||
dex:
|
||||
build: containers/dex
|
||||
ports:
|
||||
- "16009:5556"
|
||||
- '16009:5556'
|
||||
|
||||
mongo:
|
||||
image: mongo:4.4.29
|
||||
@@ -50,6 +50,11 @@ services:
|
||||
ports:
|
||||
- 16011:6379
|
||||
|
||||
dynamodb:
|
||||
image: amazon/dynamodb-local
|
||||
ports:
|
||||
- 16015:8000
|
||||
|
||||
mssql:
|
||||
image: mcr.microsoft.com/mssql/server
|
||||
restart: always
|
||||
|
||||
14
e2e-tests/env/ai-chat/.env
vendored
Normal file
14
e2e-tests/env/ai-chat/.env
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
CONNECTIONS=mysql,graphql
|
||||
|
||||
LOCAL_AI_GATEWAY=true
|
||||
|
||||
LABEL_mysql=MySql-connection
|
||||
SERVER_mysql=localhost
|
||||
USER_mysql=root
|
||||
PASSWORD_mysql=Pwd2020Db
|
||||
PORT_mysql=16004
|
||||
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
||||
|
||||
LABEL_graphql=REST GraphQL
|
||||
ENGINE_graphql=graphql@rest
|
||||
APISERVERURL1_graphql=http://localhost:4444/graphql/noauth
|
||||
11
e2e-tests/env/browse-data/.env
vendored
11
e2e-tests/env/browse-data/.env
vendored
@@ -1,4 +1,4 @@
|
||||
CONNECTIONS=mysql,postgres,mongo,redis
|
||||
CONNECTIONS=mysql,postgres,mongo,dynamo
|
||||
|
||||
LABEL_mysql=MySql-connection
|
||||
SERVER_mysql=localhost
|
||||
@@ -23,7 +23,8 @@ PASSWORD_mongo=Pwd2020Db
|
||||
PORT_mongo=16010
|
||||
ENGINE_mongo=mongo@dbgate-plugin-mongo
|
||||
|
||||
LABEL_redis=Redis-connection
|
||||
SERVER_redis=localhost
|
||||
ENGINE_redis=redis@dbgate-plugin-redis
|
||||
PORT_redis=16011
|
||||
LABEL_dynamo=Dynamo-connection
|
||||
SERVER_dynamo=localhost
|
||||
PORT_dynamo=16015
|
||||
AUTH_TYPE_dynamo=onpremise
|
||||
ENGINE_dynamo=dynamodb@dbgate-plugin-dynamodb
|
||||
|
||||
9
e2e-tests/env/multi-sql/.env
vendored
9
e2e-tests/env/multi-sql/.env
vendored
@@ -1,4 +1,4 @@
|
||||
CONNECTIONS=mysql,postgres,mssql,oracle,sqlite,mongo
|
||||
CONNECTIONS=mysql,postgres,mssql,oracle,sqlite,mongo,dynamo
|
||||
LOG_CONNECTION_SENSITIVE_VALUES=true
|
||||
|
||||
LABEL_mysql=MySql-connection
|
||||
@@ -43,3 +43,10 @@ PASSWORD_mongo=Pwd2020Db
|
||||
PORT_mongo=16010
|
||||
ENGINE_mongo=mongo@dbgate-plugin-mongo
|
||||
|
||||
LABEL_dynamo=Dynamo-connection
|
||||
SERVER_dynamo=localhost
|
||||
PORT_dynamo=16015
|
||||
AUTH_TYPE_dynamo=onpremise
|
||||
DATABASE_dynamo=localhost
|
||||
ENGINE_dynamo=dynamodb@dbgate-plugin-dynamodb
|
||||
|
||||
|
||||
6
e2e-tests/env/redis/.env
vendored
Normal file
6
e2e-tests/env/redis/.env
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
CONNECTIONS=redis
|
||||
|
||||
LABEL_redis=Redis-connection
|
||||
SERVER_redis=localhost
|
||||
ENGINE_redis=redis@dbgate-plugin-redis
|
||||
PORT_redis=16011
|
||||
14
e2e-tests/env/rest/.env
vendored
Normal file
14
e2e-tests/env/rest/.env
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
CONNECTIONS=odata,openapi,graphql
|
||||
|
||||
LABEL_odata=REST OData
|
||||
ENGINE_odata=odata@rest
|
||||
APISERVERURL1_odata=http://localhost:4444/odata/noauth
|
||||
|
||||
LABEL_openapi=REST OpenAPI
|
||||
ENGINE_openapi=openapi@rest
|
||||
APISERVERURL1_openapi=http://localhost:4444/openapi.json
|
||||
APISERVERURL2_openapi=http://localhost:4444/openapi/noauth
|
||||
|
||||
LABEL_graphql=REST GraphQL
|
||||
ENGINE_graphql=graphql@rest
|
||||
APISERVERURL1_graphql=http://localhost:4444/graphql/noauth
|
||||
168
e2e-tests/init/ai-chat.js
Normal file
168
e2e-tests/init/ai-chat.js
Normal file
@@ -0,0 +1,168 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawn, spawnSync } = require('child_process');
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const testApiDir = path.join(rootDir, 'test-api');
|
||||
const aigwmockDir = path.join(rootDir, 'packages', 'aigwmock');
|
||||
const tmpDataDir = path.resolve(__dirname, '..', 'tmpdata');
|
||||
const testApiPidFile = path.join(tmpDataDir, 'test-api.pid');
|
||||
const aigwmockPidFile = path.join(tmpDataDir, 'aigwmock.pid');
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
const dbgateApi = require('dbgate-api');
|
||||
dbgateApi.initializeApiEnvironment();
|
||||
const dbgatePluginMysql = require('dbgate-plugin-mysql');
|
||||
dbgateApi.registerPlugins(dbgatePluginMysql);
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// --- MySQL setup (same as charts init) ---
|
||||
|
||||
async function initMySqlDatabase(dbname, inputFile) {
|
||||
const connection = {
|
||||
server: process.env.SERVER_mysql,
|
||||
user: process.env.USER_mysql,
|
||||
password: process.env.PASSWORD_mysql,
|
||||
port: process.env.PORT_mysql,
|
||||
engine: 'mysql@dbgate-plugin-mysql',
|
||||
};
|
||||
|
||||
await dbgateApi.executeQuery({
|
||||
connection,
|
||||
sql: `DROP DATABASE IF EXISTS ${dbname}`,
|
||||
});
|
||||
|
||||
await dbgateApi.executeQuery({
|
||||
connection,
|
||||
sql: `CREATE DATABASE ${dbname}`,
|
||||
});
|
||||
|
||||
await dbgateApi.importDatabase({
|
||||
connection: { ...connection, database: dbname },
|
||||
inputFile,
|
||||
});
|
||||
}
|
||||
|
||||
// --- Process management helpers ---
|
||||
|
||||
function readProcessStartTime(pid) {
|
||||
if (process.platform === 'linux') {
|
||||
try {
|
||||
const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf-8');
|
||||
return stat.split(' ')[21] || null;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isPidStillOurs(meta) {
|
||||
if (!meta || !(meta.pid > 0)) return false;
|
||||
if (process.platform === 'linux' && meta.startTime) {
|
||||
const current = readProcessStartTime(meta.pid);
|
||||
return current === meta.startTime;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function stopProcess(pidFile) {
|
||||
if (!fs.existsSync(pidFile)) return;
|
||||
try {
|
||||
const content = fs.readFileSync(pidFile, 'utf-8').trim();
|
||||
let meta;
|
||||
try {
|
||||
meta = JSON.parse(content);
|
||||
} catch (_) {
|
||||
const pid = Number(content);
|
||||
meta = Number.isInteger(pid) && pid > 0 ? { pid } : null;
|
||||
}
|
||||
if (isPidStillOurs(meta)) {
|
||||
process.kill(meta.pid);
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore stale pid or already terminated
|
||||
}
|
||||
try {
|
||||
fs.unlinkSync(pidFile);
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDependencies(dir, checkFile) {
|
||||
if (fs.existsSync(checkFile)) return;
|
||||
const command = isWindows ? 'cmd.exe' : 'yarn';
|
||||
const args = isWindows ? ['/c', 'yarn install --silent'] : ['install', '--silent'];
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: dir,
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`DBGM-00297 Failed to install dependencies in ${dir}`);
|
||||
}
|
||||
}
|
||||
|
||||
function startBackgroundProcess(dir, pidFile, port) {
|
||||
const command = isWindows ? 'cmd.exe' : 'yarn';
|
||||
const args = isWindows ? ['/c', 'yarn start'] : ['start'];
|
||||
const child = spawn(command, args, {
|
||||
cwd: dir,
|
||||
env: { ...process.env, PORT: String(port) },
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
child.unref();
|
||||
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
||||
const meta = { pid: child.pid };
|
||||
const startTime = readProcessStartTime(child.pid);
|
||||
if (startTime) meta.startTime = startTime;
|
||||
fs.writeFileSync(pidFile, JSON.stringify(meta));
|
||||
}
|
||||
|
||||
async function waitForReady(url, timeoutMs = 30000) {
|
||||
const startedAt = Date.now();
|
||||
while (Date.now() - startedAt < timeoutMs) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (response.ok) return;
|
||||
} catch (err) {
|
||||
// continue waiting
|
||||
}
|
||||
await delay(500);
|
||||
}
|
||||
throw new Error(`DBGM-00305 Server at ${url} did not start in time`);
|
||||
}
|
||||
|
||||
// --- Main ---
|
||||
|
||||
async function run() {
|
||||
// 1. Set up MyChinook MySQL database
|
||||
console.log('[ai-chat init] Setting up MyChinook database...');
|
||||
await initMySqlDatabase('MyChinook', path.resolve(path.join(__dirname, '../data/chinook-mysql.sql')));
|
||||
|
||||
// 2. Start test-api (GraphQL/REST server on port 4444)
|
||||
console.log('[ai-chat init] Starting test-api on port 4444...');
|
||||
stopProcess(testApiPidFile);
|
||||
ensureDependencies(testApiDir, path.join(testApiDir, 'node_modules', 'swagger-jsdoc', 'package.json'));
|
||||
startBackgroundProcess(testApiDir, testApiPidFile, 4444);
|
||||
await waitForReady('http://localhost:4444/openapi.json');
|
||||
console.log('[ai-chat init] test-api is ready');
|
||||
|
||||
// 3. Start aigwmock (AI Gateway mock on port 3110)
|
||||
console.log('[ai-chat init] Starting aigwmock on port 3110...');
|
||||
stopProcess(aigwmockPidFile);
|
||||
ensureDependencies(aigwmockDir, path.join(aigwmockDir, 'node_modules', 'express', 'package.json'));
|
||||
startBackgroundProcess(aigwmockDir, aigwmockPidFile, 3110);
|
||||
await waitForReady('http://localhost:3110/openrouter/v1/models');
|
||||
console.log('[ai-chat init] aigwmock is ready');
|
||||
}
|
||||
|
||||
run().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -8,6 +8,8 @@ const dbgatePluginMysql = require('dbgate-plugin-mysql');
|
||||
dbgateApi.registerPlugins(dbgatePluginMysql);
|
||||
const dbgatePluginPostgres = require('dbgate-plugin-postgres');
|
||||
dbgateApi.registerPlugins(dbgatePluginPostgres);
|
||||
const dbgatePluginDynamodb = require('dbgate-plugin-dynamodb');
|
||||
dbgateApi.registerPlugins(dbgatePluginDynamodb);
|
||||
|
||||
async function initMySqlDatabase(dbname, inputFile) {
|
||||
await dbgateApi.executeQuery({
|
||||
@@ -125,44 +127,32 @@ async function initMongoDatabase(dbname, inputDirectory) {
|
||||
// });
|
||||
}
|
||||
|
||||
async function initRedisDatabase(inputDirectory) {
|
||||
await dbgateApi.executeQuery({
|
||||
connection: {
|
||||
server: process.env.SERVER_redis,
|
||||
user: process.env.USER_redis,
|
||||
password: process.env.PASSWORD_redis,
|
||||
port: process.env.PORT_redis,
|
||||
engine: 'redis@dbgate-plugin-redis',
|
||||
},
|
||||
sql: 'FLUSHALL',
|
||||
});
|
||||
async function initDynamoDatabase(inputDirectory) {
|
||||
const dynamodbConnection = {
|
||||
server: process.env.SERVER_dynamo,
|
||||
port: process.env.PORT_dynamo,
|
||||
authType: 'onpremise',
|
||||
engine: 'dynamodb@dbgate-plugin-dynamodb',
|
||||
};
|
||||
|
||||
const driver = dbgatePluginDynamodb.drivers.find(d => d.engine === 'dynamodb@dbgate-plugin-dynamodb');
|
||||
const pool = await driver.connect(dynamodbConnection);
|
||||
const collections = await driver.listCollections(pool);
|
||||
for (const collection of collections) {
|
||||
await driver.dropTable(pool, collection);
|
||||
}
|
||||
await driver.disconnect(pool);
|
||||
|
||||
for (const file of fs.readdirSync(inputDirectory)) {
|
||||
await dbgateApi.executeQuery({
|
||||
connection: {
|
||||
server: process.env.SERVER_redis,
|
||||
user: process.env.USER_redis,
|
||||
password: process.env.PASSWORD_redis,
|
||||
port: process.env.PORT_redis,
|
||||
engine: 'redis@dbgate-plugin-redis',
|
||||
database: 0,
|
||||
},
|
||||
sqlFile: path.join(inputDirectory, file),
|
||||
// logScriptItems: true,
|
||||
const pureName = path.parse(file).name;
|
||||
const src = await dbgateApi.jsonLinesReader({ fileName: path.join(inputDirectory, file) });
|
||||
const dst = await dbgateApi.tableWriter({
|
||||
connection: dynamodbConnection,
|
||||
pureName,
|
||||
createIfNotExists: true,
|
||||
});
|
||||
await dbgateApi.copyStream(src, dst);
|
||||
}
|
||||
|
||||
// await dbgateApi.importDatabase({
|
||||
// connection: {
|
||||
// server: process.env.SERVER_postgres,
|
||||
// user: process.env.USER_postgres,
|
||||
// password: process.env.PASSWORD_postgres,
|
||||
// port: process.env.PORT_postgres,
|
||||
// database: dbname,
|
||||
// engine: 'postgres@dbgate-plugin-postgres',
|
||||
// },
|
||||
// inputFile,
|
||||
// });
|
||||
}
|
||||
|
||||
const baseDir = path.join(os.homedir(), '.dbgate');
|
||||
@@ -188,7 +178,7 @@ async function run() {
|
||||
await initMongoDatabase('MgChinook', path.resolve(path.join(__dirname, '../data/chinook-jsonl')));
|
||||
await initMongoDatabase('MgRivers', path.resolve(path.join(__dirname, '../data/rivers-jsonl')));
|
||||
|
||||
await initRedisDatabase(path.resolve(path.join(__dirname, '../data/redis')));
|
||||
await initDynamoDatabase(path.resolve(path.join(__dirname, '../data/chinook-jsonl')));
|
||||
|
||||
await copyFolder(
|
||||
path.resolve(path.join(__dirname, '../data/chinook-jsonl')),
|
||||
|
||||
@@ -90,6 +90,11 @@ async function run() {
|
||||
path.join(baseDir, 'files-e2etests', 'sql')
|
||||
);
|
||||
|
||||
await copyFolder(
|
||||
path.resolve(path.join(__dirname, '../data/files/themes')),
|
||||
path.join(baseDir, 'files-e2etests', 'themes')
|
||||
);
|
||||
|
||||
await initMySqlDatabase('MyChinook', path.resolve(path.join(__dirname, '../data/chinook-mysql.sql')));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ const dbgatePluginMysql = require('dbgate-plugin-mysql');
|
||||
dbgateApi.registerPlugins(dbgatePluginMysql);
|
||||
const dbgatePluginPostgres = require('dbgate-plugin-postgres');
|
||||
dbgateApi.registerPlugins(dbgatePluginPostgres);
|
||||
const dbgatePluginDynamodb = require('dbgate-plugin-dynamodb');
|
||||
dbgateApi.registerPlugins(dbgatePluginDynamodb);
|
||||
|
||||
async function createDb(connection, dropDbSql, createDbSql, database = 'my_guitar_shop', { dropDatabaseName } = {}) {
|
||||
if (dropDbSql) {
|
||||
@@ -125,6 +127,28 @@ async function run() {
|
||||
{ dropDatabaseName: 'my_guitar_shop' }
|
||||
);
|
||||
}
|
||||
|
||||
if (localconfig.dynamo) {
|
||||
const dynamodbConnection = {
|
||||
server: process.env.SERVER_dynamo,
|
||||
port: process.env.PORT_dynamo,
|
||||
authType: 'onpremise',
|
||||
engine: 'dynamodb@dbgate-plugin-dynamodb',
|
||||
};
|
||||
|
||||
const driver = dbgatePluginDynamodb.drivers.find(d => d.engine === 'dynamodb@dbgate-plugin-dynamodb');
|
||||
const pool = await driver.connect(dynamodbConnection);
|
||||
const collections = await driver.listCollections(pool);
|
||||
for (const collection of collections) {
|
||||
await driver.dropTable(pool, collection);
|
||||
}
|
||||
await driver.disconnect(pool);
|
||||
|
||||
await dbgateApi.importDbFromFolder({
|
||||
connection: dynamodbConnection,
|
||||
folder: path.resolve(path.join(__dirname, '../data/my-guitar-shop')),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
dbgateApi.runScript(run);
|
||||
|
||||
55
e2e-tests/init/redis.js
Normal file
55
e2e-tests/init/redis.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const dbgateApi = require('dbgate-api');
|
||||
dbgateApi.initializeApiEnvironment();
|
||||
const dbgatePluginRedis = require('dbgate-plugin-redis');
|
||||
dbgateApi.registerPlugins(dbgatePluginRedis);
|
||||
|
||||
async function initRedisDatabase() {
|
||||
await dbgateApi.executeQuery({
|
||||
connection: {
|
||||
server: process.env.SERVER_redis,
|
||||
user: process.env.USER_redis,
|
||||
password: process.env.PASSWORD_redis,
|
||||
port: process.env.PORT_redis,
|
||||
engine: 'redis@dbgate-plugin-redis',
|
||||
},
|
||||
sql: 'FLUSHALL',
|
||||
});
|
||||
|
||||
const files = [
|
||||
{
|
||||
file: path.resolve(__dirname, '../data/redis-db1.redis'),
|
||||
database: 0,
|
||||
},
|
||||
{
|
||||
file: path.resolve(__dirname, '../data/redis-db2.redis'),
|
||||
database: 1,
|
||||
},
|
||||
];
|
||||
|
||||
for (const { file, database } of files) {
|
||||
await dbgateApi.executeQuery({
|
||||
connection: {
|
||||
server: process.env.SERVER_redis,
|
||||
user: process.env.USER_redis,
|
||||
password: process.env.PASSWORD_redis,
|
||||
port: process.env.PORT_redis,
|
||||
engine: 'redis@dbgate-plugin-redis',
|
||||
database,
|
||||
},
|
||||
sqlFile: file,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
await initRedisDatabase();
|
||||
}
|
||||
|
||||
dbgateApi.runScript(run);
|
||||
|
||||
module.exports = {
|
||||
initRedisDatabase,
|
||||
};
|
||||
133
e2e-tests/init/rest.js
Normal file
133
e2e-tests/init/rest.js
Normal file
@@ -0,0 +1,133 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawn, spawnSync } = require('child_process');
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const testApiDir = path.join(rootDir, 'test-api');
|
||||
const pidFile = path.resolve(__dirname, '..', 'tmpdata', 'test-api.pid');
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function waitForApiReady(timeoutMs = 30000) {
|
||||
const startedAt = Date.now();
|
||||
|
||||
while (Date.now() - startedAt < timeoutMs) {
|
||||
try {
|
||||
const response = await fetch('http://localhost:4444/openapi.json');
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
// continue waiting
|
||||
}
|
||||
|
||||
await delay(500);
|
||||
}
|
||||
|
||||
throw new Error('DBGM-00306 test-api did not start on port 4444 in time');
|
||||
}
|
||||
|
||||
function readProcessStartTime(pid) {
|
||||
if (process.platform === 'linux') {
|
||||
try {
|
||||
const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf-8');
|
||||
return stat.split(' ')[21] || null;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isPidStillOurs(meta) {
|
||||
if (!meta || !(meta.pid > 0)) return false;
|
||||
if (process.platform === 'linux' && meta.startTime) {
|
||||
const current = readProcessStartTime(meta.pid);
|
||||
return current === meta.startTime;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function stopPreviousTestApi() {
|
||||
if (!fs.existsSync(pidFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(pidFile, 'utf-8').trim();
|
||||
let meta;
|
||||
try {
|
||||
meta = JSON.parse(content);
|
||||
} catch (_) {
|
||||
const pid = Number(content);
|
||||
meta = Number.isInteger(pid) && pid > 0 ? { pid } : null;
|
||||
}
|
||||
if (isPidStillOurs(meta)) {
|
||||
process.kill(meta.pid);
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore stale pid file or already terminated process
|
||||
}
|
||||
|
||||
try {
|
||||
fs.unlinkSync(pidFile);
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
function startTestApi() {
|
||||
const command = isWindows ? 'cmd.exe' : 'yarn';
|
||||
const args = isWindows ? ['/c', 'yarn start'] : ['start'];
|
||||
|
||||
const child = spawn(command, args, {
|
||||
cwd: testApiDir,
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: '4444',
|
||||
},
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
|
||||
child.unref();
|
||||
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
||||
const meta = { pid: child.pid };
|
||||
const startTime = readProcessStartTime(child.pid);
|
||||
if (startTime) meta.startTime = startTime;
|
||||
fs.writeFileSync(pidFile, JSON.stringify(meta));
|
||||
}
|
||||
|
||||
function ensureTestApiDependencies() {
|
||||
const dependencyCheckFile = path.join(testApiDir, 'node_modules', 'swagger-jsdoc', 'package.json');
|
||||
if (fs.existsSync(dependencyCheckFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const installCommand = isWindows ? 'cmd.exe' : 'yarn';
|
||||
const installArgs = isWindows ? ['/c', 'yarn install --silent'] : ['install', '--silent'];
|
||||
const result = spawnSync(installCommand, installArgs, {
|
||||
cwd: testApiDir,
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error('DBGM-00307 Failed to install test-api dependencies');
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
stopPreviousTestApi();
|
||||
ensureTestApiDependencies();
|
||||
startTestApi();
|
||||
await waitForApiReady();
|
||||
}
|
||||
|
||||
run().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -10,39 +10,45 @@
|
||||
"cypress-real-events": "^1.13.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"kill-port": "^2.0.1",
|
||||
"mocha-reporter-gha": "^1.1.1",
|
||||
"start-server-and-test": "^2.0.8"
|
||||
},
|
||||
"scripts": {
|
||||
"cy:open": "cypress open --config experimentalInteractiveRunEvents=true",
|
||||
|
||||
"cy:run:add-connection": "cypress run --spec cypress/e2e/add-connection.cy.js",
|
||||
"cy:run:portal": "cypress run --spec cypress/e2e/portal.cy.js",
|
||||
"cy:run:oauth": "cypress run --spec cypress/e2e/oauth.cy.js",
|
||||
"cy:run:browse-data": "cypress run --spec cypress/e2e/browse-data.cy.js",
|
||||
"cy:run:rest": "cypress run --spec cypress/e2e/rest.cy.js",
|
||||
"cy:run:team": "cypress run --spec cypress/e2e/team.cy.js",
|
||||
"cy:run:multi-sql": "cypress run --spec cypress/e2e/multi-sql.cy.js",
|
||||
"cy:run:cloud": "cypress run --spec cypress/e2e/cloud.cy.js",
|
||||
"cy:run:charts": "cypress run --spec cypress/e2e/charts.cy.js",
|
||||
|
||||
"cy:run:redis": "cypress run --spec cypress/e2e/redis.cy.js",
|
||||
"cy:run:ai-chat": "cypress run --spec cypress/e2e/ai-chat.cy.js",
|
||||
"start:add-connection": "node clearTestingData && cd .. && node packer/build/bundle.js --listen-api --run-e2e-tests",
|
||||
"start:portal": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/portal/.env node e2e-tests/init/portal.js && env-cmd -f e2e-tests/env/portal/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
|
||||
"start:oauth": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/oauth/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
|
||||
"start:browse-data": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/browse-data/.env node e2e-tests/init/browse-data.js && env-cmd -f e2e-tests/env/browse-data/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
|
||||
"start:rest": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/rest/.env node e2e-tests/init/rest.js && env-cmd -f e2e-tests/env/rest/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
|
||||
"start:team": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/team/.env node e2e-tests/init/team.js && env-cmd -f e2e-tests/env/team/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
|
||||
"start:multi-sql": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/multi-sql/.env node e2e-tests/init/multi-sql.js && env-cmd -f e2e-tests/env/multi-sql/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
|
||||
"start:cloud": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/cloud/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
|
||||
"start:charts": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/charts/.env node e2e-tests/init/charts.js && env-cmd -f e2e-tests/env/charts/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
|
||||
|
||||
"start:redis": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/redis/.env node e2e-tests/init/redis.js && env-cmd -f e2e-tests/env/redis/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
|
||||
"start:ai-chat": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/ai-chat/.env node e2e-tests/init/ai-chat.js && env-cmd -f e2e-tests/env/ai-chat/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
|
||||
"test:add-connection": "start-server-and-test start:add-connection http://localhost:3000 cy:run:add-connection",
|
||||
"test:portal": "start-server-and-test start:portal http://localhost:3000 cy:run:portal",
|
||||
"test:oauth": "start-server-and-test start:oauth http://localhost:3000 cy:run:oauth",
|
||||
"test:browse-data": "start-server-and-test start:browse-data http://localhost:3000 cy:run:browse-data",
|
||||
"test:rest": "start-server-and-test start:rest http://localhost:3000 cy:run:rest",
|
||||
"test:team": "start-server-and-test start:team http://localhost:3000 cy:run:team",
|
||||
"test:multi-sql": "start-server-and-test start:multi-sql http://localhost:3000 cy:run:multi-sql",
|
||||
"test:cloud": "start-server-and-test start:cloud http://localhost:3000 cy:run:cloud",
|
||||
"test:charts": "start-server-and-test start:charts http://localhost:3000 cy:run:charts",
|
||||
|
||||
"test": "yarn test:add-connection && yarn test:portal && yarn test:oauth && yarn test:browse-data && yarn test:team && yarn test:multi-sql && yarn test:cloud && yarn test:charts",
|
||||
"test:redis": "start-server-and-test start:redis http://localhost:3000 cy:run:redis",
|
||||
"test:ai-chat": "start-server-and-test start:ai-chat http://localhost:3000 cy:run:ai-chat",
|
||||
"test": "yarn test:add-connection && yarn test:portal && yarn test:oauth && yarn test:browse-data && yarn test:rest && yarn test:team && yarn test:multi-sql && yarn test:cloud && yarn test:charts && yarn test:redis && yarn test:ai-chat",
|
||||
"test:ci": "yarn test"
|
||||
},
|
||||
"dependencies": {}
|
||||
|
||||
2
e2e-tests/tmpdata/.gitignore
vendored
Normal file
2
e2e-tests/tmpdata/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
test-api.pid
|
||||
aigwmock.pid
|
||||
@@ -2,6 +2,34 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@actions/core@^1.10.1":
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.11.1.tgz#ae683aac5112438021588030efb53b1adb86f172"
|
||||
integrity sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==
|
||||
dependencies:
|
||||
"@actions/exec" "^1.1.1"
|
||||
"@actions/http-client" "^2.0.1"
|
||||
|
||||
"@actions/exec@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@actions/exec/-/exec-1.1.1.tgz#2e43f28c54022537172819a7cf886c844221a611"
|
||||
integrity sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==
|
||||
dependencies:
|
||||
"@actions/io" "^1.0.1"
|
||||
|
||||
"@actions/http-client@^2.0.1":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-2.2.3.tgz#31fc0b25c0e665754ed39a9f19a8611fc6dab674"
|
||||
integrity sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==
|
||||
dependencies:
|
||||
tunnel "^0.0.6"
|
||||
undici "^5.25.4"
|
||||
|
||||
"@actions/io@^1.0.1":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@actions/io/-/io-1.1.3.tgz#4cdb6254da7962b07473ff5c335f3da485d94d71"
|
||||
integrity sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==
|
||||
|
||||
"@colors/colors@1.5.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
||||
@@ -39,6 +67,11 @@
|
||||
debug "^3.1.0"
|
||||
lodash.once "^4.1.1"
|
||||
|
||||
"@fastify/busboy@^2.0.0":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
|
||||
integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==
|
||||
|
||||
"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
|
||||
version "9.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
|
||||
@@ -947,6 +980,13 @@ minimist@^1.2.8:
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
||||
mocha-reporter-gha@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/mocha-reporter-gha/-/mocha-reporter-gha-1.1.1.tgz#e1248abd0769f55b57b36ccd7db2b0b6573d5adf"
|
||||
integrity sha512-CFbcgM56V4yWlbF91XuwrE6a5X/IqjVXTPefO7m8cY8Es8G1UhJ2KKOrk16AcSemRzVWXp2Fdy3bWJ7j45snWw==
|
||||
dependencies:
|
||||
"@actions/core" "^1.10.1"
|
||||
|
||||
ms@^2.1.1, ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
@@ -1292,6 +1332,11 @@ tunnel-agent@^0.6.0:
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
tunnel@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
@@ -1307,6 +1352,13 @@ undici-types@~6.20.0:
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
|
||||
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
|
||||
|
||||
undici@^5.25.4:
|
||||
version "5.29.0"
|
||||
resolved "https://registry.yarnpkg.com/undici/-/undici-5.29.0.tgz#419595449ae3f2cdcba3580a2e8903399bd1f5a3"
|
||||
integrity sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==
|
||||
dependencies:
|
||||
"@fastify/busboy" "^2.0.0"
|
||||
|
||||
universalify@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
||||
|
||||
@@ -12,6 +12,7 @@ const {
|
||||
} = require('dbgate-tools');
|
||||
|
||||
function pickImportantTableInfo(engine, table) {
|
||||
if (!table) return table;
|
||||
const props = ['columnName', 'defaultValue'];
|
||||
if (!engine.skipNullability) props.push('notNull');
|
||||
if (!engine.skipAutoIncrement) props.push('autoIncrement');
|
||||
@@ -25,6 +26,15 @@ function pickImportantTableInfo(engine, table) {
|
||||
.map(props =>
|
||||
_.omitBy(props, (v, k) => k == 'defaultValue' && v == 'NULL' && engine.setNullDefaultInsteadOfDrop)
|
||||
),
|
||||
|
||||
// TODO:
|
||||
foreignKeys: table.foreignKeys
|
||||
.sort((a, b) => a.refTableName.localeCompare(b.refTableName))
|
||||
.map(fk => ({
|
||||
constraintType: fk.constraintType,
|
||||
refTableName: fk.refTableName,
|
||||
columns: fk.columns.map(col => ({ columnName: col.columnName, refColumnName: col.refColumnName })),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +43,7 @@ function checkTableStructure(engine, t1, t2) {
|
||||
expect(pickImportantTableInfo(engine, t1)).toEqual(pickImportantTableInfo(engine, t2));
|
||||
}
|
||||
|
||||
async function testTableDiff(engine, conn, driver, mangle) {
|
||||
async function testTableDiff(engine, conn, driver, mangle, changedTable = 't1') {
|
||||
const initQuery = formatQueryWithoutParams(driver, `create table ~t0 (~id int not null primary key)`);
|
||||
await driver.query(conn, transformSqlForEngine(engine, initQuery));
|
||||
|
||||
@@ -68,17 +78,39 @@ async function testTableDiff(engine, conn, driver, mangle) {
|
||||
await driver.query(conn, transformSqlForEngine(engine, query));
|
||||
}
|
||||
|
||||
const tget = x => x.tables.find(y => y.pureName == 't1');
|
||||
const structure1 = generateDbPairingId(extendDatabaseInfo(await driver.analyseFull(conn)));
|
||||
if (!engine.skipReferences) {
|
||||
const query = formatQueryWithoutParams(
|
||||
driver,
|
||||
`create table ~t3 (~id int not null primary key, ~fkval int ${
|
||||
driver.dialect.implicitNullDeclaration ? '' : 'null'
|
||||
})`
|
||||
);
|
||||
|
||||
await driver.query(conn, transformSqlForEngine(engine, query));
|
||||
}
|
||||
|
||||
const tget = x => x?.tables?.find(y => y.pureName == changedTable);
|
||||
const structure1Source = await driver.analyseFull(conn);
|
||||
const structure1 = generateDbPairingId(extendDatabaseInfo(structure1Source));
|
||||
let structure2 = _.cloneDeep(structure1);
|
||||
mangle(tget(structure2));
|
||||
structure2 = extendDatabaseInfo(structure2);
|
||||
|
||||
const { sql } = getAlterTableScript(tget(structure1), tget(structure2), {}, structure1, structure2, driver);
|
||||
|
||||
// sleep 1s - some engines have update datetime precision only to seconds
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
console.log('RUNNING ALTER SQL', driver.engine, ':', sql);
|
||||
|
||||
await driver.script(conn, sql);
|
||||
|
||||
// TODO:
|
||||
// if (!engine.skipIncrementalAnalysis) {
|
||||
// const structure2RealIncremental = await driver.analyseIncremental(conn, structure1Source);
|
||||
// checkTableStructure(engine, tget(structure2RealIncremental), tget(structure2));
|
||||
// }
|
||||
|
||||
const structure2Real = extendDatabaseInfo(await driver.analyseFull(conn));
|
||||
|
||||
checkTableStructure(engine, tget(structure2Real), tget(structure2));
|
||||
@@ -87,6 +119,7 @@ async function testTableDiff(engine, conn, driver, mangle) {
|
||||
|
||||
const TESTED_COLUMNS = ['col_pk', 'col_std', 'col_def', 'col_fk', 'col_ref', 'col_idx', 'col_uq'];
|
||||
// const TESTED_COLUMNS = ['col_pk'];
|
||||
// const TESTED_COLUMNS = ['col_fk'];
|
||||
// const TESTED_COLUMNS = ['col_idx'];
|
||||
// const TESTED_COLUMNS = ['col_def'];
|
||||
// const TESTED_COLUMNS = ['col_std'];
|
||||
@@ -150,11 +183,25 @@ describe('Alter table', () => {
|
||||
)(
|
||||
'Drop column - %s - %s',
|
||||
testWrapper(async (conn, driver, column, engine) => {
|
||||
await testTableDiff(engine, conn, driver, tbl => (tbl.columns = tbl.columns.filter(x => x.columnName != column)));
|
||||
await testTableDiff(engine, conn, driver,
|
||||
tbl => {
|
||||
tbl.columns = tbl.columns.filter(x => x.columnName != column);
|
||||
tbl.foreignKeys = tbl.foreignKeys
|
||||
.map(fk => ({
|
||||
...fk,
|
||||
columns: fk.columns.filter(col => col.columnName != column)
|
||||
}))
|
||||
.filter(fk => fk.columns.length > 0);
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(createEnginesColumnsSource(engines.filter(x => !x.skipNullable && !x.skipChangeNullability)))(
|
||||
test.each(
|
||||
createEnginesColumnsSource(engines.filter(x => !x.skipNullability && !x.skipChangeNullability)).filter(
|
||||
([_label, col]) => !col.endsWith('_pk')
|
||||
)
|
||||
)(
|
||||
'Change nullability - %s - %s',
|
||||
testWrapper(async (conn, driver, column, engine) => {
|
||||
await testTableDiff(
|
||||
@@ -173,7 +220,11 @@ describe('Alter table', () => {
|
||||
engine,
|
||||
conn,
|
||||
driver,
|
||||
tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, columnName: 'col_renamed' } : x)))
|
||||
tbl => {
|
||||
tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, columnName: 'col_renamed' } : x));
|
||||
tbl.foreignKeys = tbl.foreignKeys.map(fk => ({...fk, columns: fk.columns.map(col => col.columnName == column ? { ...col, columnName: 'col_renamed' } : col)
|
||||
}));
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
@@ -214,6 +265,48 @@ describe('Alter table', () => {
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => !x.skipReferences).map(engine => [engine.label, engine]))(
|
||||
'Drop FK - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testTableDiff(
|
||||
engine,
|
||||
conn,
|
||||
driver,
|
||||
tbl => {
|
||||
tbl.foreignKeys = [];
|
||||
},
|
||||
't2'
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => !x.skipReferences).map(engine => [engine.label, engine]))(
|
||||
'Create FK - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testTableDiff(
|
||||
engine,
|
||||
conn,
|
||||
driver,
|
||||
tbl => {
|
||||
tbl.foreignKeys = [
|
||||
{
|
||||
constraintType: 'foreignKey',
|
||||
pureName: 't3',
|
||||
refTableName: 't1',
|
||||
columns: [
|
||||
{
|
||||
columnName: 'fkval',
|
||||
refColumnName: 'col_ref',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
't3'
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
// test.each(engines.map(engine => [engine.label, engine]))(
|
||||
// 'Change autoincrement - %s',
|
||||
// testWrapper(async (conn, driver, engine) => {
|
||||
|
||||
536
integration-tests/__tests__/collection.spec.js
Normal file
536
integration-tests/__tests__/collection.spec.js
Normal file
@@ -0,0 +1,536 @@
|
||||
const requireEngineDriver = require('dbgate-api/src/utility/requireEngineDriver');
|
||||
const crypto = require('crypto');
|
||||
const stream = require('stream');
|
||||
const { mongoDbEngine, dynamoDbEngine } = require('../engines');
|
||||
const tableWriter = require('dbgate-api/src/shell/tableWriter');
|
||||
const tableReader = require('dbgate-api/src/shell/tableReader');
|
||||
const copyStream = require('dbgate-api/src/shell/copyStream');
|
||||
|
||||
function randomCollectionName() {
|
||||
return 'test_' + crypto.randomBytes(6).toString('hex');
|
||||
}
|
||||
|
||||
const documentEngines = [
|
||||
{ label: 'MongoDB', engine: mongoDbEngine },
|
||||
{ label: 'DynamoDB', engine: dynamoDbEngine },
|
||||
];
|
||||
|
||||
async function connectEngine(engine) {
|
||||
const driver = requireEngineDriver(engine.connection);
|
||||
const conn = await driver.connect(engine.connection);
|
||||
return { driver, conn };
|
||||
}
|
||||
|
||||
async function createCollection(driver, conn, collectionName, engine) {
|
||||
if (engine.connection.engine.startsWith('dynamodb')) {
|
||||
await driver.operation(conn, {
|
||||
type: 'createCollection',
|
||||
collection: {
|
||||
name: collectionName,
|
||||
partitionKey: '_id',
|
||||
partitionKeyType: 'S',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await driver.operation(conn, {
|
||||
type: 'createCollection',
|
||||
collection: { name: collectionName },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function dropCollection(driver, conn, collectionName) {
|
||||
try {
|
||||
await driver.operation(conn, {
|
||||
type: 'dropCollection',
|
||||
collection: collectionName,
|
||||
});
|
||||
} catch (e) {
|
||||
// Ignore errors when dropping (collection may not exist)
|
||||
}
|
||||
}
|
||||
|
||||
async function insertDocument(driver, conn, collectionName, doc) {
|
||||
return driver.updateCollection(conn, {
|
||||
inserts: [{ pureName: collectionName, document: {}, fields: doc }],
|
||||
updates: [],
|
||||
deletes: [],
|
||||
});
|
||||
}
|
||||
|
||||
async function readAll(driver, conn, collectionName) {
|
||||
return driver.readCollection(conn, { pureName: collectionName, limit: 1000 });
|
||||
}
|
||||
|
||||
async function updateDocument(driver, conn, collectionName, condition, fields) {
|
||||
return driver.updateCollection(conn, {
|
||||
inserts: [],
|
||||
updates: [{ pureName: collectionName, condition, fields }],
|
||||
deletes: [],
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteDocument(driver, conn, collectionName, condition) {
|
||||
return driver.updateCollection(conn, {
|
||||
inserts: [],
|
||||
updates: [],
|
||||
deletes: [{ pureName: collectionName, condition }],
|
||||
});
|
||||
}
|
||||
|
||||
describe('Collection CRUD', () => {
|
||||
describe.each(documentEngines.map(e => [e.label, e.engine]))('%s', (label, engine) => {
|
||||
let driver;
|
||||
let conn;
|
||||
let collectionName;
|
||||
|
||||
beforeAll(async () => {
|
||||
const result = await connectEngine(engine);
|
||||
driver = result.driver;
|
||||
conn = result.conn;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (conn) {
|
||||
await driver.close(conn);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
collectionName = randomCollectionName();
|
||||
await createCollection(driver, conn, collectionName, engine);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await dropCollection(driver, conn, collectionName);
|
||||
});
|
||||
|
||||
// ---- INSERT ----
|
||||
|
||||
test('insert a single document', async () => {
|
||||
const res = await insertDocument(driver, conn, collectionName, {
|
||||
_id: 'doc1',
|
||||
name: 'Alice',
|
||||
age: 30,
|
||||
});
|
||||
expect(res.inserted.length).toBe(1);
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].name).toBe('Alice');
|
||||
expect(all.rows[0].age).toBe(30);
|
||||
});
|
||||
|
||||
test('insert multiple documents', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'a1', name: 'Alice' });
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'a2', name: 'Bob' });
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'a3', name: 'Charlie' });
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(3);
|
||||
const names = all.rows.map(r => r.name).sort();
|
||||
expect(names).toEqual(['Alice', 'Bob', 'Charlie']);
|
||||
});
|
||||
|
||||
test('insert document with nested object', async () => {
|
||||
await insertDocument(driver, conn, collectionName, {
|
||||
_id: 'nested1',
|
||||
name: 'Alice',
|
||||
address: { city: 'Prague', zip: '11000' },
|
||||
});
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].address.city).toBe('Prague');
|
||||
expect(all.rows[0].address.zip).toBe('11000');
|
||||
});
|
||||
|
||||
// ---- READ ----
|
||||
|
||||
test('read from empty collection returns no rows', async () => {
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(0);
|
||||
});
|
||||
|
||||
test('read with limit', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'l1', name: 'A' });
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'l2', name: 'B' });
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'l3', name: 'C' });
|
||||
|
||||
const limited = await driver.readCollection(conn, {
|
||||
pureName: collectionName,
|
||||
limit: 2,
|
||||
});
|
||||
expect(limited.rows.length).toBe(2);
|
||||
});
|
||||
|
||||
test('count documents', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'c1', name: 'A' });
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'c2', name: 'B' });
|
||||
|
||||
const result = await driver.readCollection(conn, {
|
||||
pureName: collectionName,
|
||||
countDocuments: true,
|
||||
});
|
||||
expect(result.count).toBe(2);
|
||||
});
|
||||
|
||||
test('count documents on empty collection returns zero', async () => {
|
||||
const result = await driver.readCollection(conn, {
|
||||
pureName: collectionName,
|
||||
countDocuments: true,
|
||||
});
|
||||
expect(result.count).toBe(0);
|
||||
});
|
||||
|
||||
// ---- UPDATE ----
|
||||
|
||||
test('update an existing document', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'u1', name: 'Alice', age: 25 });
|
||||
|
||||
const res = await updateDocument(driver, conn, collectionName, { _id: 'u1' }, { name: 'Alice Updated' });
|
||||
expect(res.errorMessage).toBeUndefined();
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].name).toBe('Alice Updated');
|
||||
});
|
||||
|
||||
test('update does not create new document', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'u2', name: 'Bob' });
|
||||
|
||||
await updateDocument(driver, conn, collectionName, { _id: 'nonexistent' }, { name: 'Ghost' });
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].name).toBe('Bob');
|
||||
});
|
||||
|
||||
test('update only specified fields', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'u3', name: 'Carol', age: 40, city: 'London' });
|
||||
|
||||
await updateDocument(driver, conn, collectionName, { _id: 'u3' }, { age: 41 });
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].name).toBe('Carol');
|
||||
expect(all.rows[0].age).toBe(41);
|
||||
expect(all.rows[0].city).toBe('London');
|
||||
});
|
||||
|
||||
// ---- DELETE ----
|
||||
|
||||
test('delete an existing document', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'd1', name: 'Alice' });
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'd2', name: 'Bob' });
|
||||
|
||||
const res = await deleteDocument(driver, conn, collectionName, { _id: 'd1' });
|
||||
expect(res.errorMessage).toBeUndefined();
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].name).toBe('Bob');
|
||||
});
|
||||
|
||||
test('delete non-existing document does not affect collection', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'dx1', name: 'Alice' });
|
||||
|
||||
await deleteDocument(driver, conn, collectionName, { _id: 'nonexistent' });
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].name).toBe('Alice');
|
||||
});
|
||||
|
||||
test('delete all documents leaves empty collection', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'da1', name: 'A' });
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'da2', name: 'B' });
|
||||
|
||||
await deleteDocument(driver, conn, collectionName, { _id: 'da1' });
|
||||
await deleteDocument(driver, conn, collectionName, { _id: 'da2' });
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(0);
|
||||
});
|
||||
|
||||
// ---- EDGE CASES ----
|
||||
|
||||
test('insert and read document with empty string field', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'e1', name: '', value: 'test' });
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].name).toBe('');
|
||||
expect(all.rows[0].value).toBe('test');
|
||||
});
|
||||
|
||||
test('insert and read document with numeric values', async () => {
|
||||
await insertDocument(driver, conn, collectionName, {
|
||||
_id: 'n1',
|
||||
intVal: 42,
|
||||
floatVal: 3.14,
|
||||
zero: 0,
|
||||
negative: -10,
|
||||
});
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].intVal).toBe(42);
|
||||
expect(all.rows[0].floatVal).toBeCloseTo(3.14);
|
||||
expect(all.rows[0].zero).toBe(0);
|
||||
expect(all.rows[0].negative).toBe(-10);
|
||||
});
|
||||
|
||||
test('insert and read document with boolean values', async () => {
|
||||
await insertDocument(driver, conn, collectionName, {
|
||||
_id: 'b1',
|
||||
active: true,
|
||||
deleted: false,
|
||||
});
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].active).toBe(true);
|
||||
expect(all.rows[0].deleted).toBe(false);
|
||||
});
|
||||
|
||||
test('reading non-existing collection returns error or empty', async () => {
|
||||
const result = await driver.readCollection(conn, {
|
||||
pureName: 'nonexistent_collection_' + crypto.randomBytes(4).toString('hex'),
|
||||
limit: 10,
|
||||
});
|
||||
// Depending on the driver, this may return an error or empty rows
|
||||
if (result.errorMessage) {
|
||||
expect(typeof result.errorMessage).toBe('string');
|
||||
} else {
|
||||
expect(result.rows.length).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('replace full document via update with document field', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'r1', name: 'Original', extra: 'data' });
|
||||
|
||||
await driver.updateCollection(conn, {
|
||||
inserts: [],
|
||||
updates: [
|
||||
{
|
||||
pureName: collectionName,
|
||||
condition: { _id: 'r1' },
|
||||
document: { _id: 'r1', name: 'Replaced' },
|
||||
fields: {},
|
||||
},
|
||||
],
|
||||
deletes: [],
|
||||
});
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].name).toBe('Replaced');
|
||||
});
|
||||
|
||||
test('insert then update then delete lifecycle', async () => {
|
||||
// Insert
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'life1', name: 'Lifecycle', status: 'created' });
|
||||
let all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(1);
|
||||
expect(all.rows[0].status).toBe('created');
|
||||
|
||||
// Update
|
||||
await updateDocument(driver, conn, collectionName, { _id: 'life1' }, { status: 'updated' });
|
||||
all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows[0].status).toBe('updated');
|
||||
|
||||
// Delete
|
||||
await deleteDocument(driver, conn, collectionName, { _id: 'life1' });
|
||||
all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createDocumentImportStream(documents) {
|
||||
const pass = new stream.PassThrough({ objectMode: true });
|
||||
pass.write({ __isStreamHeader: true, __isDynamicStructure: true });
|
||||
for (const doc of documents) {
|
||||
pass.write(doc);
|
||||
}
|
||||
pass.end();
|
||||
return pass;
|
||||
}
|
||||
|
||||
function createExportStream() {
|
||||
const writable = new stream.Writable({ objectMode: true });
|
||||
writable.resultArray = [];
|
||||
writable._write = (chunk, encoding, callback) => {
|
||||
writable.resultArray.push(chunk);
|
||||
callback();
|
||||
};
|
||||
return writable;
|
||||
}
|
||||
|
||||
describe('Collection Import/Export', () => {
|
||||
describe.each(documentEngines.map(e => [e.label, e.engine]))('%s', (label, engine) => {
|
||||
let driver;
|
||||
let conn;
|
||||
let collectionName;
|
||||
|
||||
beforeAll(async () => {
|
||||
const result = await connectEngine(engine);
|
||||
driver = result.driver;
|
||||
conn = result.conn;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (conn) {
|
||||
await driver.close(conn);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
collectionName = randomCollectionName();
|
||||
await createCollection(driver, conn, collectionName, engine);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await dropCollection(driver, conn, collectionName);
|
||||
});
|
||||
|
||||
test('import documents via stream', async () => {
|
||||
const documents = [
|
||||
{ _id: 'imp1', name: 'Alice', age: 30 },
|
||||
{ _id: 'imp2', name: 'Bob', age: 25 },
|
||||
{ _id: 'imp3', name: 'Charlie', age: 35 },
|
||||
];
|
||||
|
||||
const reader = createDocumentImportStream(documents);
|
||||
const writer = await tableWriter({
|
||||
systemConnection: conn,
|
||||
driver,
|
||||
pureName: collectionName,
|
||||
createIfNotExists: true,
|
||||
});
|
||||
await copyStream(reader, writer);
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(3);
|
||||
const names = all.rows.map(r => r.name).sort();
|
||||
expect(names).toEqual(['Alice', 'Bob', 'Charlie']);
|
||||
});
|
||||
|
||||
test('export documents via stream', async () => {
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'exp1', name: 'Alice', city: 'Prague' });
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'exp2', name: 'Bob', city: 'Vienna' });
|
||||
await insertDocument(driver, conn, collectionName, { _id: 'exp3', name: 'Charlie', city: 'Berlin' });
|
||||
|
||||
const reader = await tableReader({
|
||||
systemConnection: conn,
|
||||
driver,
|
||||
pureName: collectionName,
|
||||
});
|
||||
const writer = createExportStream();
|
||||
await copyStream(reader, writer);
|
||||
|
||||
const rows = writer.resultArray.filter(x => !x.__isStreamHeader);
|
||||
expect(rows.length).toBe(3);
|
||||
const names = rows.map(r => r.name).sort();
|
||||
expect(names).toEqual(['Alice', 'Bob', 'Charlie']);
|
||||
});
|
||||
|
||||
test('import then export round-trip', async () => {
|
||||
const documents = [
|
||||
{ _id: 'rt1', name: 'Alice', value: 100 },
|
||||
{ _id: 'rt2', name: 'Bob', value: 200 },
|
||||
{ _id: 'rt3', name: 'Charlie', value: 300 },
|
||||
{ _id: 'rt4', name: 'Diana', value: 400 },
|
||||
];
|
||||
|
||||
// Import
|
||||
const importReader = createDocumentImportStream(documents);
|
||||
const importWriter = await tableWriter({
|
||||
systemConnection: conn,
|
||||
driver,
|
||||
pureName: collectionName,
|
||||
createIfNotExists: true,
|
||||
});
|
||||
await copyStream(importReader, importWriter);
|
||||
|
||||
// Export
|
||||
const exportReader = await tableReader({
|
||||
systemConnection: conn,
|
||||
driver,
|
||||
pureName: collectionName,
|
||||
});
|
||||
const exportWriter = createExportStream();
|
||||
await copyStream(exportReader, exportWriter);
|
||||
|
||||
const rows = exportWriter.resultArray.filter(x => !x.__isStreamHeader);
|
||||
expect(rows.length).toBe(4);
|
||||
|
||||
const sortedRows = rows.sort((a, b) => a._id.localeCompare(b._id));
|
||||
for (const doc of documents) {
|
||||
const found = sortedRows.find(r => r._id === doc._id);
|
||||
expect(found).toBeDefined();
|
||||
expect(found.name).toBe(doc.name);
|
||||
expect(found.value).toBe(doc.value);
|
||||
}
|
||||
});
|
||||
|
||||
test('import documents with nested objects', async () => {
|
||||
const documents = [
|
||||
{ _id: 'nest1', name: 'Alice', address: { city: 'Prague', zip: '11000' } },
|
||||
{ _id: 'nest2', name: 'Bob', address: { city: 'Vienna', zip: '1010' } },
|
||||
];
|
||||
|
||||
const reader = createDocumentImportStream(documents);
|
||||
const writer = await tableWriter({
|
||||
systemConnection: conn,
|
||||
driver,
|
||||
pureName: collectionName,
|
||||
createIfNotExists: true,
|
||||
});
|
||||
await copyStream(reader, writer);
|
||||
|
||||
const all = await readAll(driver, conn, collectionName);
|
||||
expect(all.rows.length).toBe(2);
|
||||
|
||||
const alice = all.rows.find(r => r.name === 'Alice');
|
||||
expect(alice.address.city).toBe('Prague');
|
||||
expect(alice.address.zip).toBe('11000');
|
||||
});
|
||||
|
||||
test('import many documents', async () => {
|
||||
const documents = [];
|
||||
for (let i = 0; i < 150; i++) {
|
||||
documents.push({ _id: `many${i}`, name: `Name${i}`, index: i });
|
||||
}
|
||||
|
||||
const reader = createDocumentImportStream(documents);
|
||||
const writer = await tableWriter({
|
||||
systemConnection: conn,
|
||||
driver,
|
||||
pureName: collectionName,
|
||||
createIfNotExists: true,
|
||||
});
|
||||
await copyStream(reader, writer);
|
||||
|
||||
const result = await driver.readCollection(conn, {
|
||||
pureName: collectionName,
|
||||
countDocuments: true,
|
||||
});
|
||||
expect(result.count).toBe(150);
|
||||
});
|
||||
|
||||
test('export empty collection returns no data rows', async () => {
|
||||
const reader = await tableReader({
|
||||
systemConnection: conn,
|
||||
driver,
|
||||
pureName: collectionName,
|
||||
});
|
||||
const writer = createExportStream();
|
||||
await copyStream(reader, writer);
|
||||
|
||||
const rows = writer.resultArray.filter(x => !x.__isStreamHeader);
|
||||
expect(rows.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -303,4 +303,52 @@ describe('Data replicator', () => {
|
||||
}),
|
||||
15 * 1000
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => !x.skipDataReplicator).map(engine => [engine.label, engine]))(
|
||||
'Skip columns for update - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
runCommandOnDriver(conn, driver, dmp =>
|
||||
dmp.createTable({
|
||||
pureName: 't1',
|
||||
columns: [
|
||||
{ columnName: 'id', dataType: 'int', autoIncrement: true, notNull: true },
|
||||
{ columnName: 'key', dataType: 'varchar(50)', notNull: true },
|
||||
{ columnName: 'val', dataType: 'varchar(50)' },
|
||||
],
|
||||
primaryKey: {
|
||||
columns: [{ columnName: 'id' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const getcfg = (v1 = 'v1') => ({
|
||||
systemConnection: conn,
|
||||
driver,
|
||||
items: [
|
||||
{
|
||||
name: 't1',
|
||||
matchColumns: ['key'],
|
||||
skipUpdateColumns: ['val'],
|
||||
findExisting: true,
|
||||
updateExisting: true,
|
||||
createNew: true,
|
||||
jsonArray: [
|
||||
{ key: '1', val: v1 },
|
||||
{ key: '2', val: 'v2' },
|
||||
{ key: '3', val: 'v3' },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await dataReplicator(getcfg('v1'));
|
||||
|
||||
const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select ~val from ~t1 where ~key='1'`));
|
||||
expect(res1.rows[0].val).toEqual('v1');
|
||||
|
||||
await dataReplicator(getcfg('v2'));
|
||||
const res2 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select ~val from ~t1 where ~key='1'`));
|
||||
expect(res2.rows[0].val).toEqual('v1');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -49,6 +49,32 @@ class StreamHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class BinaryTestStreamHandler {
|
||||
constructor(resolve, reject, expectedValue) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
this.expectedValue = expectedValue;
|
||||
this.rowsReceived = [];
|
||||
}
|
||||
row(row) {
|
||||
try {
|
||||
this.rowsReceived.push(row);
|
||||
if (this.expectedValue) {
|
||||
expect(row).toEqual(this.expectedValue);
|
||||
}
|
||||
} catch (error) {
|
||||
this.reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
recordset(columns) {}
|
||||
done(result) {
|
||||
this.resolve(this.rowsReceived);
|
||||
}
|
||||
info(msg) {}
|
||||
}
|
||||
|
||||
|
||||
function executeStreamItem(driver, conn, sql) {
|
||||
return new Promise(resolve => {
|
||||
const handler = new StreamHandler(resolve);
|
||||
@@ -223,4 +249,51 @@ describe('Query', () => {
|
||||
expect(row[keys[0]] == 1).toBeTruthy();
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => x.binaryDataType).map(engine => [engine.label, engine]))(
|
||||
'Binary - %s',
|
||||
testWrapper(async (dbhan, driver, engine) => {
|
||||
await runCommandOnDriver(dbhan, driver, dmp =>
|
||||
dmp.createTable({
|
||||
pureName: 't1',
|
||||
columns: [
|
||||
{ columnName: 'id', dataType: 'int', notNull: true, autoIncrement: true },
|
||||
{ columnName: 'val', dataType: engine.binaryDataType },
|
||||
],
|
||||
primaryKey: {
|
||||
columns: [{ columnName: 'id' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
const structure = await driver.analyseFull(dbhan);
|
||||
const table = structure.tables.find(x => x.pureName == 't1');
|
||||
|
||||
const dmp = driver.createDumper();
|
||||
dmp.putCmd("INSERT INTO ~t1 (~val) VALUES (%v)", {
|
||||
$binary: { base64: 'iVBORw0KWgo=' },
|
||||
});
|
||||
await driver.query(dbhan, dmp.s, {discardResult: true});
|
||||
|
||||
const dmp2 = driver.createDumper();
|
||||
dmp2.put('SELECT ~val FROM ~t1');
|
||||
const res = await driver.query(dbhan, dmp2.s);
|
||||
|
||||
const row = res.rows[0];
|
||||
const keys = Object.keys(row);
|
||||
expect(keys.length).toEqual(1);
|
||||
expect(row[keys[0]]).toEqual({$binary: {base64: 'iVBORw0KWgo='}});
|
||||
|
||||
const res2 = await driver.readQuery(dbhan, dmp2.s);
|
||||
const rows = await Array.fromAsync(res2);
|
||||
const rowsVal = rows.filter(r => r.val != null);
|
||||
|
||||
expect(rowsVal.length).toEqual(1);
|
||||
expect(rowsVal[0].val).toEqual({$binary: {base64: 'iVBORw0KWgo='}});
|
||||
|
||||
const res3 = await new Promise((resolve, reject) => {
|
||||
const handler = new BinaryTestStreamHandler(resolve, reject, {val: {$binary: {base64: 'iVBORw0KWgo='}}});
|
||||
driver.stream(dbhan, dmp2.s, handler);
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -33,7 +33,9 @@ describe('Schema tests', () => {
|
||||
expect(schemas2.find(x => x.schemaName == 'myschema')).toBeTruthy();
|
||||
expect(schemas2.length).toEqual(count + 1);
|
||||
expect(schemas2.find(x => x.isDefault).schemaName).toEqual(engine.defaultSchemaName);
|
||||
expect(structure2).toBeNull();
|
||||
if (!engine.skipIncrementalAnalysis) {
|
||||
expect(structure2).toBeNull();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -51,7 +53,9 @@ describe('Schema tests', () => {
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
const schemas2 = await driver.listSchemas(conn);
|
||||
expect(schemas2.find(x => x.schemaName == 'myschema')).toBeFalsy();
|
||||
expect(structure2).toBeNull();
|
||||
if (!engine.skipIncrementalAnalysis) {
|
||||
expect(structure2).toBeNull();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ describe('Table analyse', () => {
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Table add - incremental analysis - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
|
||||
@@ -112,7 +112,7 @@ describe('Table analyse', () => {
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Table remove - incremental analysis - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql(engine)));
|
||||
@@ -130,7 +130,7 @@ describe('Table analyse', () => {
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Table change - incremental analysis - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql(engine)));
|
||||
|
||||
@@ -44,7 +44,7 @@ services:
|
||||
# - 15942:9042
|
||||
#
|
||||
# clickhouse:
|
||||
# image: bitnami/clickhouse:24.8.4
|
||||
# image: bitnamilegacy/clickhouse:24.8.4
|
||||
# restart: always
|
||||
# ports:
|
||||
# - 15005:8123
|
||||
@@ -123,5 +123,22 @@ services:
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
mongodb:
|
||||
image: mongo:4.0.12
|
||||
restart: always
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
- mongo-config:/data/configdb
|
||||
ports:
|
||||
- 27017:27017
|
||||
|
||||
dynamodb:
|
||||
image: amazon/dynamodb-local
|
||||
restart: always
|
||||
ports:
|
||||
- 8000:8000
|
||||
|
||||
volumes:
|
||||
firebird-data:
|
||||
mongo-data:
|
||||
mongo-config:
|
||||
|
||||
@@ -44,6 +44,7 @@ const mysqlEngine = {
|
||||
supportRenameSqlObject: false,
|
||||
dbSnapshotBySeconds: true,
|
||||
dumpFile: 'data/chinook-mysql.sql',
|
||||
binaryDataType: 'blob',
|
||||
dumpChecks: [
|
||||
{
|
||||
sql: 'select count(*) as res from genre',
|
||||
@@ -186,6 +187,7 @@ const mariaDbEngine = {
|
||||
/** @type {import('dbgate-types').TestEngineInfo} */
|
||||
const postgreSqlEngine = {
|
||||
label: 'PostgreSQL',
|
||||
skipIncrementalAnalysis: true,
|
||||
connection: {
|
||||
engine: 'postgres@dbgate-plugin-postgres',
|
||||
password: 'Pwd2020Db',
|
||||
@@ -216,6 +218,7 @@ const postgreSqlEngine = {
|
||||
supportSchemas: true,
|
||||
supportRenameSqlObject: true,
|
||||
defaultSchemaName: 'public',
|
||||
binaryDataType: 'bytea',
|
||||
dumpFile: 'data/chinook-postgre.sql',
|
||||
dumpChecks: [
|
||||
{
|
||||
@@ -446,6 +449,7 @@ const sqlServerEngine = {
|
||||
supportTableComments: true,
|
||||
supportColumnComments: true,
|
||||
// skipSeparateSchemas: true,
|
||||
binaryDataType: 'varbinary(100)',
|
||||
triggers: [
|
||||
{
|
||||
testName: 'triggers before each row',
|
||||
@@ -506,6 +510,7 @@ const sqliteEngine = {
|
||||
},
|
||||
},
|
||||
],
|
||||
binaryDataType: 'blob',
|
||||
};
|
||||
|
||||
const libsqlFileEngine = {
|
||||
@@ -619,6 +624,7 @@ const oracleEngine = {
|
||||
},
|
||||
},
|
||||
],
|
||||
binaryDataType: 'blob',
|
||||
};
|
||||
|
||||
/** @type {import('dbgate-types').TestEngineInfo} */
|
||||
@@ -732,6 +738,27 @@ const firebirdEngine = {
|
||||
skipDropReferences: true,
|
||||
};
|
||||
|
||||
/** @type {import('dbgate-types').TestEngineInfo} */
|
||||
const mongoDbEngine = {
|
||||
label: 'MongoDB',
|
||||
connection: {
|
||||
engine: 'mongo@dbgate-plugin-mongo',
|
||||
server: 'localhost',
|
||||
port: 27017,
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('dbgate-types').TestEngineInfo} */
|
||||
const dynamoDbEngine = {
|
||||
label: 'DynamoDB',
|
||||
connection: {
|
||||
engine: 'dynamodb@dbgate-plugin-dynamodb',
|
||||
server: 'localhost',
|
||||
port: 8000,
|
||||
authType: 'onpremise',
|
||||
},
|
||||
};
|
||||
|
||||
const enginesOnCi = [
|
||||
// all engines, which would be run on GitHub actions
|
||||
mysqlEngine,
|
||||
@@ -754,16 +781,16 @@ const enginesOnLocal = [
|
||||
// cassandraEngine,
|
||||
// mysqlEngine,
|
||||
// mariaDbEngine,
|
||||
// postgreSqlEngine,
|
||||
// sqlServerEngine,
|
||||
postgreSqlEngine,
|
||||
//sqlServerEngine,
|
||||
// sqliteEngine,
|
||||
// cockroachDbEngine,
|
||||
// clickhouseEngine,
|
||||
// libsqlFileEngine,
|
||||
// libsqlWsEngine,
|
||||
// oracleEngine,
|
||||
//oracleEngine,
|
||||
// duckdbEngine,
|
||||
firebirdEngine,
|
||||
//firebirdEngine,
|
||||
];
|
||||
|
||||
/** @type {import('dbgate-types').TestEngineInfo[] & Record<string, import('dbgate-types').TestEngineInfo>} */
|
||||
@@ -782,3 +809,5 @@ module.exports.libsqlFileEngine = libsqlFileEngine;
|
||||
module.exports.libsqlWsEngine = libsqlWsEngine;
|
||||
module.exports.duckdbEngine = duckdbEngine;
|
||||
module.exports.firebirdEngine = firebirdEngine;
|
||||
module.exports.mongoDbEngine = mongoDbEngine;
|
||||
module.exports.dynamoDbEngine = dynamoDbEngine;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
module.exports = {
|
||||
setupFilesAfterEnv: ['<rootDir>/setupTests.js'],
|
||||
reporters: ['default', 'github-actions'],
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "dbgate-integration-tests",
|
||||
"version": "6.0.0-alpha.1",
|
||||
"homepage": "https://dbgate.org/",
|
||||
"version": "7.0.0-alpha.1",
|
||||
"homepage": "https://www.dbgate.io/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate.git"
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"jest": "^27.0.1",
|
||||
"jest": "^28.1.3",
|
||||
"pino-pretty": "^11.2.2",
|
||||
"tmp": "^0.2.3"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
global.DBGATE_PACKAGES = {
|
||||
'dbgate-tools': require('dbgate-tools'),
|
||||
'dbgate-sqltree': require('dbgate-sqltree'),
|
||||
'dbgate-datalib': require('dbgate-datalib'),
|
||||
};
|
||||
|
||||
const { prettyFactory } = require('pino-pretty');
|
||||
|
||||
@@ -22,7 +22,9 @@ async function connect(engine, database) {
|
||||
if (engine.generateDbFile) {
|
||||
const conn = await driver.connect({
|
||||
...connection,
|
||||
databaseFile: (engine.databaseFileLocationOnServer ?? 'dbtemp/') + database,
|
||||
databaseFile:
|
||||
(engine.databaseFileLocationOnServer ?? (process.env.CITEST ? 'dbtemp/' : 'integration-tests/dbtemp/')) +
|
||||
database,
|
||||
});
|
||||
return conn;
|
||||
} else {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
const requireEngineDriver = require('dbgate-api/src/utility/requireEngineDriver');
|
||||
const engines = require('./engines');
|
||||
const { mongoDbEngine, dynamoDbEngine } = require('./engines');
|
||||
global.DBGATE_PACKAGES = {
|
||||
'dbgate-tools': require('dbgate-tools'),
|
||||
'dbgate-sqltree': require('dbgate-sqltree'),
|
||||
'dbgate-datalib': require('dbgate-datalib'),
|
||||
};
|
||||
|
||||
async function connectEngine(engine) {
|
||||
const { connection } = engine;
|
||||
const driver = requireEngineDriver(connection);
|
||||
for (;;) {
|
||||
for (; ;) {
|
||||
try {
|
||||
const conn = await driver.connect(connection);
|
||||
await driver.getVersion(conn);
|
||||
@@ -25,7 +27,8 @@ async function connectEngine(engine) {
|
||||
|
||||
async function run() {
|
||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
||||
await Promise.all(engines.map(engine => connectEngine(engine)));
|
||||
const documentEngines = [mongoDbEngine, dynamoDbEngine];
|
||||
await Promise.all([...engines, ...documentEngines].map(engine => connectEngine(engine)));
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "6.6.6-premium-beta.18",
|
||||
"version": "7.1.8",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
@@ -22,6 +22,7 @@
|
||||
"start:api:auth": "yarn workspace dbgate-api start:auth | pino-pretty",
|
||||
"start:api:dblogin": "yarn workspace dbgate-api start:dblogin | pino-pretty",
|
||||
"start:api:storage": "yarn workspace dbgate-api start:storage | pino-pretty",
|
||||
"start:api:sfill": "yarn workspace dbgate-api start:sfill | pino-pretty",
|
||||
"start:api:storage:built": "yarn workspace dbgate-api start:storage:built | pino-pretty",
|
||||
"start:api:azure": "yarn workspace dbgate-api start:azure | pino-pretty",
|
||||
"start:api:e2e:team": "yarn workspace dbgate-api start:e2e:team | pino-pretty",
|
||||
@@ -29,13 +30,15 @@
|
||||
"start:web": "yarn workspace dbgate-web dev",
|
||||
"start:sqltree": "yarn workspace dbgate-sqltree start",
|
||||
"start:tools": "yarn workspace dbgate-tools start",
|
||||
"start:rest": "yarn workspace dbgate-rest start",
|
||||
"start:datalib": "yarn workspace dbgate-datalib start",
|
||||
"start:filterparser": "yarn workspace dbgate-filterparser start",
|
||||
"build:sqltree": "yarn workspace dbgate-sqltree build",
|
||||
"build:datalib": "yarn workspace dbgate-datalib build",
|
||||
"build:filterparser": "yarn workspace dbgate-filterparser build",
|
||||
"build:tools": "yarn workspace dbgate-tools build",
|
||||
"build:lib": "yarn build:sqltree && yarn build:tools && yarn build:filterparser && yarn build:datalib",
|
||||
"build:rest": "yarn workspace dbgate-rest build",
|
||||
"build:lib": "yarn build:sqltree && yarn build:tools && yarn build:filterparser && yarn build:datalib && yarn build:rest",
|
||||
"build:app": "yarn plugins:copydist && cd app && yarn install && yarn build",
|
||||
"build:api": "yarn workspace dbgate-api build",
|
||||
"build:api:doc": "yarn workspace dbgate-api build:doc",
|
||||
@@ -52,6 +55,7 @@
|
||||
"generatePadFile": "node generatePadFile",
|
||||
"fillPackagedPlugins": "node fillPackagedPlugins",
|
||||
"resetPackagedPlugins": "node resetPackagedPlugins",
|
||||
"fixYmlHashes": "cd common && yarn init -y && yarn add yaml -W && cd .. && node common/fixYmlHashes.js app/dist",
|
||||
"prettier": "prettier --write packages/api/src && prettier --write packages/datalib/src && prettier --write packages/filterparser/src && prettier --write packages/sqltree/src && prettier --write packages/tools/src && prettier --write packages/types && prettier --write packages/web/src && prettier --write app/src",
|
||||
"copy:docker:build": "copyfiles packages/api/dist/* docker -f && copyfiles packages/web/public/* docker -u 2 && copyfiles \"packages/web/public/**/*\" docker -u 2 && copyfiles \"plugins/dist/**/*\" docker/plugins -u 2",
|
||||
"copy:packer:build": "copyfiles packages/api/dist/* packer/build -f && copyfiles packages/web/public/* packer/build -u 2 && copyfiles \"packages/web/public/**/*\" packer/build -u 2 && copyfiles \"plugins/dist/**/*\" packer/build/plugins -u 2 && copyfiles packer/install-packages.sh packer/build -f && yarn install:drivers:packer",
|
||||
@@ -61,7 +65,7 @@
|
||||
"prepare:packer": "yarn plugins:copydist && yarn build:web && yarn build:api && yarn copy:packer:build",
|
||||
"build:e2e": "yarn build:lib && yarn prepare:packer",
|
||||
"start": "concurrently --kill-others-on-fail \"yarn start:api\" \"yarn start:web\"",
|
||||
"lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\" \"yarn build:plugins:frontend:watch\"",
|
||||
"lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\" \"yarn start:rest\" \"yarn build:plugins:frontend:watch\"",
|
||||
"ts:api": "yarn workspace dbgate-api ts",
|
||||
"ts:web": "yarn workspace dbgate-web ts",
|
||||
"ts": "yarn ts:api && yarn ts:web",
|
||||
@@ -73,6 +77,8 @@
|
||||
"translations:add-missing": "node common/translations-cli/index.js add-missing",
|
||||
"translations:remove-unused": "node common/translations-cli/index.js remove-unused",
|
||||
"translations:check": "node common/translations-cli/index.js check",
|
||||
"translations:sort": "node common/translations-cli/index.js sort",
|
||||
"translations:translate": "node common/translations-cli/translate.js",
|
||||
"errors": "node common/assign-dbgm-codes.mjs ."
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
14
packages/aigwmock/package.json
Normal file
14
packages/aigwmock/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "dbgate-aigwmock",
|
||||
"version": "1.0.0",
|
||||
"description": "Mock AI Gateway server for E2E testing",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "node src/index.js"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.6",
|
||||
"express": "^5.2.1"
|
||||
}
|
||||
}
|
||||
202
packages/aigwmock/src/index.js
Normal file
202
packages/aigwmock/src/index.js
Normal file
@@ -0,0 +1,202 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
|
||||
const responses = JSON.parse(fs.readFileSync(path.join(__dirname, 'mockResponses.json'), 'utf-8'));
|
||||
|
||||
let callCounter = 0;
|
||||
|
||||
// GET /openrouter/v1/models
|
||||
app.get('/openrouter/v1/models', (req, res) => {
|
||||
res.json({
|
||||
data: [{ id: 'mock-model', name: 'Mock Model' }],
|
||||
preferredModel: 'mock-model',
|
||||
});
|
||||
});
|
||||
|
||||
// POST /openrouter/v1/chat/completions
|
||||
app.post('/openrouter/v1/chat/completions', (req, res) => {
|
||||
const messages = req.body.messages || [];
|
||||
|
||||
// Find the first user message (skip system messages)
|
||||
const userMessage = messages.find(m => m.role === 'user');
|
||||
if (!userMessage) {
|
||||
return streamTextResponse(res, "I don't have enough context to help. Please ask a question.");
|
||||
}
|
||||
|
||||
// Count assistant messages to determine the current step
|
||||
const assistantCount = messages.filter(m => m.role === 'assistant').length;
|
||||
|
||||
// Find matching scenario by regex
|
||||
const scenario = responses.scenarios.find(s => {
|
||||
const regex = new RegExp(s.match, 'i');
|
||||
return regex.test(userMessage.content);
|
||||
});
|
||||
|
||||
if (!scenario) {
|
||||
console.log(`[aigwmock] No scenario matched for: "${userMessage.content}"`);
|
||||
return streamTextResponse(res, "I'm a mock AI assistant. I don't have a prepared response for that question.");
|
||||
}
|
||||
|
||||
const step = scenario.steps[assistantCount];
|
||||
if (!step) {
|
||||
console.log(`[aigwmock] No more steps for scenario (step ${assistantCount})`);
|
||||
return streamTextResponse(res, "I've completed my analysis of this topic.");
|
||||
}
|
||||
|
||||
console.log(`[aigwmock] Scenario matched: "${scenario.match}", step ${assistantCount}, type: ${step.type}`);
|
||||
|
||||
if (step.type === 'tool_calls') {
|
||||
return streamToolCallResponse(res, step.tool_calls);
|
||||
} else {
|
||||
return streamTextResponse(res, step.content);
|
||||
}
|
||||
});
|
||||
|
||||
function streamTextResponse(res, content) {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
Connection: 'keep-alive',
|
||||
});
|
||||
|
||||
const id = `chatcmpl-mock-${Date.now()}`;
|
||||
const created = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Split content into chunks for realistic streaming
|
||||
const chunkSize = 20;
|
||||
const chunks = [];
|
||||
for (let i = 0; i < content.length; i += chunkSize) {
|
||||
chunks.push(content.substring(i, i + chunkSize));
|
||||
}
|
||||
|
||||
// Send initial role chunk
|
||||
writeSSE(res, {
|
||||
id,
|
||||
object: 'chat.completion.chunk',
|
||||
created,
|
||||
model: 'mock-model',
|
||||
choices: [{ index: 0, delta: { role: 'assistant', content: '' }, finish_reason: null }],
|
||||
});
|
||||
|
||||
// Send content chunks
|
||||
for (const chunk of chunks) {
|
||||
writeSSE(res, {
|
||||
id,
|
||||
object: 'chat.completion.chunk',
|
||||
created,
|
||||
model: 'mock-model',
|
||||
choices: [{ index: 0, delta: { content: chunk }, finish_reason: null }],
|
||||
});
|
||||
}
|
||||
|
||||
// Send finish
|
||||
writeSSE(res, {
|
||||
id,
|
||||
object: 'chat.completion.chunk',
|
||||
created,
|
||||
model: 'mock-model',
|
||||
choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
|
||||
});
|
||||
|
||||
res.write('data: [DONE]\n\n');
|
||||
res.end();
|
||||
}
|
||||
|
||||
function streamToolCallResponse(res, toolCalls) {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
Connection: 'keep-alive',
|
||||
});
|
||||
|
||||
const id = `chatcmpl-mock-${Date.now()}`;
|
||||
const created = Math.floor(Date.now() / 1000);
|
||||
|
||||
for (let i = 0; i < toolCalls.length; i++) {
|
||||
const tc = toolCalls[i];
|
||||
const callId = `call_mock_${++callCounter}`;
|
||||
const args = JSON.stringify(tc.arguments);
|
||||
|
||||
if (i === 0) {
|
||||
// First tool call: include role
|
||||
writeSSE(res, {
|
||||
id,
|
||||
object: 'chat.completion.chunk',
|
||||
created,
|
||||
model: 'mock-model',
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
role: 'assistant',
|
||||
content: null,
|
||||
tool_calls: [{ index: i, id: callId, type: 'function', function: { name: tc.name, arguments: '' } }],
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
// Additional tool calls
|
||||
writeSSE(res, {
|
||||
id,
|
||||
object: 'chat.completion.chunk',
|
||||
created,
|
||||
model: 'mock-model',
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
tool_calls: [{ index: i, id: callId, type: 'function', function: { name: tc.name, arguments: '' } }],
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Stream the arguments
|
||||
writeSSE(res, {
|
||||
id,
|
||||
object: 'chat.completion.chunk',
|
||||
created,
|
||||
model: 'mock-model',
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
tool_calls: [{ index: i, function: { arguments: args } }],
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Send finish with tool_calls reason
|
||||
writeSSE(res, {
|
||||
id,
|
||||
object: 'chat.completion.chunk',
|
||||
created,
|
||||
model: 'mock-model',
|
||||
choices: [{ index: 0, delta: {}, finish_reason: 'tool_calls' }],
|
||||
});
|
||||
|
||||
res.write('data: [DONE]\n\n');
|
||||
res.end();
|
||||
}
|
||||
|
||||
function writeSSE(res, data) {
|
||||
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
||||
}
|
||||
|
||||
const port = process.env.PORT || 3110;
|
||||
app.listen(port, () => {
|
||||
console.log(`[aigwmock] AI Gateway mock server listening on port ${port}`);
|
||||
});
|
||||
193
packages/aigwmock/src/mockResponses.json
Normal file
193
packages/aigwmock/src/mockResponses.json
Normal file
@@ -0,0 +1,193 @@
|
||||
{
|
||||
"scenarios": [
|
||||
{
|
||||
"match": "chart.*popular.*genre|popular.*genre.*chart|most popular genre",
|
||||
"steps": [
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{ "name": "get_table_schema", "arguments": { "table": "Genre" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{ "name": "get_table_schema", "arguments": { "table": "Track" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{
|
||||
"name": "execute_sql_select",
|
||||
"arguments": {
|
||||
"sql": "SELECT g.Name AS genre, COUNT(t.TrackId) AS track_count FROM Genre g JOIN Track t ON g.GenreId = t.GenreId GROUP BY g.Name ORDER BY track_count DESC LIMIT 10"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"content": "Here is a chart showing the most popular genres by track count:\n\n```chart\n{\"type\":\"bar\",\"data\":{\"labels\":[\"Rock\",\"Latin\",\"Metal\",\"Alternative & Punk\",\"Jazz\",\"Blues\",\"Classical\",\"R&B/Soul\",\"Reggae\",\"Pop\"],\"datasets\":[{\"label\":\"Track Count\",\"data\":[1297,579,374,332,130,81,74,61,58,48]}]},\"options\":{\"plugins\":{\"title\":{\"display\":true,\"text\":\"Most Popular Genres by Track Count\"}}}}\n```"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": "most popular artist|popular artist|top artist",
|
||||
"steps": [
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{ "name": "get_table_schema", "arguments": { "table": "Artist" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{ "name": "get_table_schema", "arguments": { "table": "Album" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{ "name": "get_table_schema", "arguments": { "table": "Track" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{
|
||||
"name": "execute_sql_select",
|
||||
"arguments": {
|
||||
"sql": "SELECT ar.Name AS artist, COUNT(t.TrackId) AS track_count FROM Artist ar JOIN Album al ON ar.ArtistId = al.ArtistId JOIN Track t ON al.AlbumId = t.AlbumId GROUP BY ar.Name ORDER BY track_count DESC LIMIT 10"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"content": "The most popular artist by number of tracks is **Iron Maiden** with 213 tracks, followed by **U2** with 135 tracks and **Led Zeppelin** with 114 tracks."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": "list.*user|show.*user|get.*user",
|
||||
"steps": [
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{ "name": "graphql_introspect_schema", "arguments": {} }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{
|
||||
"name": "execute_graphql_query",
|
||||
"arguments": {
|
||||
"query": "{ users { id firstName lastName email } }"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"content": "Here are the users from the GraphQL API. The system contains multiple registered users with their names and email addresses."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": "chart.*product.*categor|product.*categor.*chart|chart.*categor",
|
||||
"steps": [
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{ "name": "graphql_introspect_schema", "arguments": {} }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{
|
||||
"name": "execute_graphql_query",
|
||||
"arguments": {
|
||||
"query": "{ products { category } }"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"content": "Here is a bar chart showing the distribution of products across categories:\n\n```chart\n{\"type\":\"bar\",\"data\":{\"labels\":[\"Electronics\",\"Clothing\",\"Books\",\"Home & Garden\",\"Sports\",\"Toys\"],\"datasets\":[{\"label\":\"Number of Products\",\"data\":[35,30,33,38,32,32]}]},\"options\":{\"plugins\":{\"title\":{\"display\":true,\"text\":\"Products by Category\"}}}}\n```"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": "most expensive product|expensive.*product|highest price",
|
||||
"steps": [
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{ "name": "graphql_introspect_schema", "arguments": {} }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{
|
||||
"name": "execute_graphql_query",
|
||||
"arguments": {
|
||||
"query": "{ products { id name price category } }"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"content": "Based on the query results, I found the most expensive product in the system. The product details are shown in the query results above."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": "show.*categor|list.*categor|all.*categor",
|
||||
"steps": [
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{ "name": "graphql_introspect_schema", "arguments": {} }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{
|
||||
"name": "execute_graphql_query",
|
||||
"arguments": {
|
||||
"query": "{ categories { id name description active } }"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"content": "Here are all the categories available in the system. Each category has a name, description, and active status indicating whether it is currently in use."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": "Explain the following error|doesn't exist|does not exist",
|
||||
"steps": [
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{ "name": "get_table_schema", "arguments": { "table": "Invoice" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"content": "The error occurs because the table `Invoice2` does not exist in the `MyChinook` database. The correct table name is `Invoice`. Here is the corrected query:\n\n```sql\nSELECT * FROM Invoice\n```\n\nThe table name had a typo — `Invoice2` instead of `Invoice`. The `Invoice` table contains columns like `InvoiceId`, `CustomerId`, `InvoiceDate`, `Total`, and billing address fields."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,7 +2,7 @@ DEVMODE=1
|
||||
SHELL_SCRIPTING=1
|
||||
ALLOW_DBGATE_PRIVATE_CLOUD=1
|
||||
DEVWEB=1
|
||||
LOCAL_AUTH_PROXY=1
|
||||
# LOCAL_AUTH_PROXY=1
|
||||
# LOCAL_AI_GATEWAY=true
|
||||
|
||||
# REDIRECT_TO_DBGATE_CLOUD_LOGIN=1
|
||||
|
||||
7
packages/api/env/portal/.env
vendored
7
packages/api/env/portal/.env
vendored
@@ -1,6 +1,7 @@
|
||||
DEVMODE=1
|
||||
DEVWEB=1
|
||||
|
||||
CONNECTIONS=mysql,postgres,mongo,redis,mssql,oracle
|
||||
CONNECTIONS=mysql,postgres,mongo,redis,mssql,oracle,mongourl
|
||||
|
||||
LABEL_mysql=MySql
|
||||
SERVER_mysql=dbgatedckstage1.sprinx.cz
|
||||
@@ -43,6 +44,10 @@ PORT_oracle=1521
|
||||
ENGINE_oracle=oracle@dbgate-plugin-oracle
|
||||
SERVICE_NAME_oracle=xe
|
||||
|
||||
LABEL_mongourl=Mongo URL
|
||||
URL_mongourl=mongodb://root:Pwd2020Db@dbgatedckstage1.sprinx.cz:27017
|
||||
ENGINE_mongourl=mongo@dbgate-plugin-mongo
|
||||
|
||||
# SETTINGS_dataGrid.showHintColumns=1
|
||||
|
||||
# docker run -p 3000:3000 -e CONNECTIONS=mongo -e URL_mongo=mongodb://localhost:27017 -e ENGINE_mongo=mongo@dbgate-plugin-mongo -e LABEL_mongo=mongo dbgate/dbgate:beta
|
||||
|
||||
54
packages/api/env/sfill/.env
vendored
Normal file
54
packages/api/env/sfill/.env
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
DEVMODE=1
|
||||
DEVWEB=1
|
||||
|
||||
# STORAGE_SERVER=localhost
|
||||
# STORAGE_USER=root
|
||||
# STORAGE_PASSWORD=Pwd2020Db
|
||||
# STORAGE_PORT=3306
|
||||
# STORAGE_DATABASE=dbgate-filled
|
||||
# STORAGE_ENGINE=mysql@dbgate-plugin-mysql
|
||||
|
||||
STORAGE_SERVER=localhost
|
||||
STORAGE_USER=postgres
|
||||
STORAGE_PASSWORD=Pwd2020Db
|
||||
STORAGE_PORT=5432
|
||||
STORAGE_DATABASE=dbgate_sfill
|
||||
STORAGE_ENGINE=postgres@dbgate-plugin-postgres
|
||||
|
||||
|
||||
CONNECTIONS=mysql,postgres,mongo,redis
|
||||
|
||||
LABEL_mysql=MySql
|
||||
SERVER_mysql=dbgatedckstage1.sprinx.cz
|
||||
USER_mysql=root
|
||||
PASSWORD_mysql=Pwd2020Db
|
||||
PORT_mysql=3306
|
||||
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
||||
|
||||
LABEL_postgres=Postgres
|
||||
SERVER_postgres=dbgatedckstage1.sprinx.cz
|
||||
USER_postgres=postgres
|
||||
PASSWORD_postgres=Pwd2020Db
|
||||
PORT_postgres=5432
|
||||
ENGINE_postgres=postgres@dbgate-plugin-postgres
|
||||
|
||||
LABEL_mongo=Mongo
|
||||
SERVER_mongo=dbgatedckstage1.sprinx.cz
|
||||
USER_mongo=root
|
||||
PASSWORD_mongo=Pwd2020Db
|
||||
PORT_mongo=27017
|
||||
ENGINE_mongo=mongo@dbgate-plugin-mongo
|
||||
|
||||
LABEL_redis=Redis
|
||||
SERVER_redis=dbgatedckstage1.sprinx.cz
|
||||
ENGINE_redis=redis@dbgate-plugin-redis
|
||||
PORT_redis=6379
|
||||
|
||||
ROLE_test1_CONNECTIONS=mysql
|
||||
ROLE_test1_PERMISSIONS=widgets/*
|
||||
ROLE_test1_DATABASES_db1_CONNECTION=mysql
|
||||
ROLE_test1_DATABASES_db1_PERMISSION=run_script
|
||||
ROLE_test1_DATABASES_db1_DATABASES=db1
|
||||
ROLE_test1_DATABASES_db2_CONNECTION=redis
|
||||
ROLE_test1_DATABASES_db2_PERMISSION=run_script
|
||||
ROLE_test1_DATABASES_db2_DATABASES=db2
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "dbgate-api",
|
||||
"main": "src/index.js",
|
||||
"version": "6.0.0-alpha.1",
|
||||
"homepage": "https://dbgate.org/",
|
||||
"version": "7.0.0-alpha.1",
|
||||
"homepage": "https://www.dbgate.io/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate.git"
|
||||
@@ -24,16 +24,17 @@
|
||||
"activedirectory2": "^2.1.0",
|
||||
"archiver": "^7.0.1",
|
||||
"async-lock": "^1.2.6",
|
||||
"axios": "^0.21.1",
|
||||
"axios": "^1.13.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"byline": "^5.0.0",
|
||||
"compare-versions": "^3.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^6.0.3",
|
||||
"dbgate-datalib": "^6.0.0-alpha.1",
|
||||
"dbgate-query-splitter": "^4.11.7",
|
||||
"dbgate-sqltree": "^6.0.0-alpha.1",
|
||||
"dbgate-tools": "^6.0.0-alpha.1",
|
||||
"dbgate-datalib": "^7.0.0-alpha.1",
|
||||
"dbgate-query-splitter": "^4.12.0",
|
||||
"dbgate-rest": "^7.0.0-alpha.1",
|
||||
"dbgate-sqltree": "^7.0.0-alpha.1",
|
||||
"dbgate-tools": "^7.0.0-alpha.1",
|
||||
"debug": "^4.3.4",
|
||||
"diff": "^5.0.0",
|
||||
"diff2html": "^3.4.13",
|
||||
@@ -75,6 +76,7 @@
|
||||
"start:dblogin": "env-cmd -f env/dblogin/.env node src/index.js --listen-api",
|
||||
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
|
||||
"start:storage": "env-cmd -f env/storage/.env node src/index.js --listen-api",
|
||||
"start:sfill": "env-cmd -f env/sfill/.env node src/index.js --listen-api",
|
||||
"start:storage:built": "env-cmd -f env/storage/.env cross-env DEVMODE= BUILTWEBMODE=1 node dist/bundle.js --listen-api",
|
||||
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
|
||||
"start:azure": "env-cmd -f env/azure/.env node src/index.js --listen-api",
|
||||
@@ -86,7 +88,7 @@
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"dbgate-types": "^6.0.0-alpha.1",
|
||||
"dbgate-types": "^7.0.0-alpha.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"jsdoc-to-markdown": "^9.0.5",
|
||||
"node-loader": "^1.0.2",
|
||||
|
||||
@@ -19,6 +19,26 @@ const unzipDirectory = require('../shell/unzipDirectory');
|
||||
|
||||
const logger = getLogger('archive');
|
||||
|
||||
/**
|
||||
* Rejects any archive name (folder or file) that contains path-traversal
|
||||
* sequences, directory separators, or null bytes. These values are used
|
||||
* directly in path.join() calls; allowing traversal would let callers read
|
||||
* or write arbitrary files outside the archive directory.
|
||||
*/
|
||||
function assertSafeArchiveName(name, label) {
|
||||
if (typeof name !== 'string' || name.length === 0) {
|
||||
throw new Error(`DBGM-00000 Invalid ${label}: must be a non-empty string`);
|
||||
}
|
||||
if (name.includes('\0') || name.includes('..') || name.includes('/') || name.includes('\\')) {
|
||||
throw new Error(`DBGM-00000 Invalid ${label}: path traversal not allowed`);
|
||||
}
|
||||
// Reject names that resolve to the archive root itself (e.g. '.')
|
||||
const resolved = path.resolve(archivedir(), name);
|
||||
if (resolved === path.resolve(archivedir())) {
|
||||
throw new Error(`DBGM-00000 Invalid ${label}: must not resolve to the archive root`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
folders_meta: true,
|
||||
async folders() {
|
||||
@@ -39,6 +59,7 @@ module.exports = {
|
||||
|
||||
createFolder_meta: true,
|
||||
async createFolder({ folder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
await fs.mkdir(path.join(archivedir(), folder));
|
||||
socket.emitChanged('archive-folders-changed');
|
||||
return true;
|
||||
@@ -46,8 +67,12 @@ module.exports = {
|
||||
|
||||
createLink_meta: true,
|
||||
async createLink({ linkedFolder }) {
|
||||
if ( typeof linkedFolder !== 'string' || linkedFolder.length === 0) {
|
||||
throw new Error(`DBGM-00000 Invalid linkedFolder: must be a non-empty string`);
|
||||
}
|
||||
assertSafeArchiveName(path.parse(linkedFolder).name, 'linkedFolder');
|
||||
const folder = await this.getNewArchiveFolder({ database: path.parse(linkedFolder).name + '.link' });
|
||||
fs.writeFile(path.join(archivedir(), folder), linkedFolder);
|
||||
await fs.writeFile(path.join(archivedir(), folder), linkedFolder);
|
||||
clearArchiveLinksCache();
|
||||
socket.emitChanged('archive-folders-changed');
|
||||
return folder;
|
||||
@@ -71,6 +96,7 @@ module.exports = {
|
||||
|
||||
files_meta: true,
|
||||
async files({ folder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
try {
|
||||
if (folder.endsWith('.zip')) {
|
||||
if (await fs.exists(path.join(archivedir(), folder))) {
|
||||
@@ -121,6 +147,9 @@ module.exports = {
|
||||
|
||||
createFile_meta: true,
|
||||
async createFile({ folder, file, fileType, tableInfo }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
assertSafeArchiveName(fileType, 'fileType');
|
||||
await fs.writeFile(
|
||||
path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
|
||||
tableInfo ? JSON.stringify({ __isStreamHeader: true, tableInfo }) : ''
|
||||
@@ -131,6 +160,9 @@ module.exports = {
|
||||
|
||||
deleteFile_meta: true,
|
||||
async deleteFile({ folder, file, fileType }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
assertSafeArchiveName(fileType, 'fileType');
|
||||
await fs.unlink(path.join(resolveArchiveFolder(folder), `${file}.${fileType}`));
|
||||
socket.emitChanged(`archive-files-changed`, { folder });
|
||||
return true;
|
||||
@@ -138,6 +170,10 @@ module.exports = {
|
||||
|
||||
renameFile_meta: true,
|
||||
async renameFile({ folder, file, newFile, fileType }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
assertSafeArchiveName(newFile, 'newFile');
|
||||
assertSafeArchiveName(fileType, 'fileType');
|
||||
await fs.rename(
|
||||
path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
|
||||
path.join(resolveArchiveFolder(folder), `${newFile}.${fileType}`)
|
||||
@@ -148,6 +184,8 @@ module.exports = {
|
||||
|
||||
modifyFile_meta: true,
|
||||
async modifyFile({ folder, file, changeSet, mergedRows, mergeKey, mergeMode }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
await jsldata.closeDataStore(`archive://${folder}/${file}`);
|
||||
const changedFilePath = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
|
||||
|
||||
@@ -187,6 +225,8 @@ module.exports = {
|
||||
|
||||
renameFolder_meta: true,
|
||||
async renameFolder({ folder, newFolder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(newFolder, 'newFolder');
|
||||
const uniqueName = await this.getNewArchiveFolder({ database: newFolder });
|
||||
await fs.rename(path.join(archivedir(), folder), path.join(archivedir(), uniqueName));
|
||||
socket.emitChanged(`archive-folders-changed`);
|
||||
@@ -196,6 +236,7 @@ module.exports = {
|
||||
deleteFolder_meta: true,
|
||||
async deleteFolder({ folder }) {
|
||||
if (!folder) throw new Error('Missing folder parameter');
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
if (folder.endsWith('.link') || folder.endsWith('.zip')) {
|
||||
await fs.unlink(path.join(archivedir(), folder));
|
||||
} else {
|
||||
@@ -207,6 +248,8 @@ module.exports = {
|
||||
|
||||
saveText_meta: true,
|
||||
async saveText({ folder, file, text }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
await fs.writeFile(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), text);
|
||||
socket.emitChanged(`archive-files-changed`, { folder });
|
||||
return true;
|
||||
@@ -214,6 +257,8 @@ module.exports = {
|
||||
|
||||
saveJslData_meta: true,
|
||||
async saveJslData({ folder, file, jslid, changeSet }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
const source = getJslFileName(jslid);
|
||||
const target = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
|
||||
if (changeSet) {
|
||||
@@ -232,11 +277,20 @@ module.exports = {
|
||||
|
||||
saveRows_meta: true,
|
||||
async saveRows({ folder, file, rows }) {
|
||||
const fileStream = fs.createWriteStream(path.join(resolveArchiveFolder(folder), `${file}.jsonl`));
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
const filePath = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
|
||||
const fileStream = fs.createWriteStream(filePath);
|
||||
for (const row of rows) {
|
||||
await fileStream.write(JSON.stringify(row) + '\n');
|
||||
const ok = fileStream.write(JSON.stringify(row) + '\n');
|
||||
if (!ok) {
|
||||
await new Promise(resolve => fileStream.once('drain', resolve));
|
||||
}
|
||||
}
|
||||
await fileStream.close();
|
||||
await new Promise((resolve, reject) => {
|
||||
fileStream.end(() => resolve());
|
||||
fileStream.on('error', reject);
|
||||
});
|
||||
socket.emitChanged(`archive-files-changed`, { folder });
|
||||
return true;
|
||||
},
|
||||
@@ -256,6 +310,8 @@ module.exports = {
|
||||
|
||||
getArchiveData_meta: true,
|
||||
async getArchiveData({ folder, file }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
let rows;
|
||||
if (folder.endsWith('.zip')) {
|
||||
rows = await unzipJsonLinesFile(path.join(archivedir(), folder), `${file}.jsonl`);
|
||||
@@ -270,7 +326,7 @@ module.exports = {
|
||||
if (!fileName?.endsWith('.zip')) {
|
||||
throw new Error(`${fileName} is not a ZIP file`);
|
||||
}
|
||||
|
||||
assertSafeArchiveName(fileName.slice(0, -4), 'fileName');
|
||||
const folder = await this.getNewArchiveFolder({ database: fileName });
|
||||
await fs.copyFile(filePath, path.join(archivedir(), folder));
|
||||
socket.emitChanged(`archive-folders-changed`);
|
||||
@@ -280,6 +336,7 @@ module.exports = {
|
||||
|
||||
zip_meta: true,
|
||||
async zip({ folder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
const newFolder = await this.getNewArchiveFolder({ database: folder + '.zip' });
|
||||
await zipDirectory(path.join(archivedir(), folder), path.join(archivedir(), newFolder));
|
||||
socket.emitChanged(`archive-folders-changed`);
|
||||
@@ -289,6 +346,7 @@ module.exports = {
|
||||
|
||||
unzip_meta: true,
|
||||
async unzip({ folder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
const newFolder = await this.getNewArchiveFolder({ database: folder.slice(0, -4) });
|
||||
await unzipDirectory(path.join(archivedir(), folder), path.join(archivedir(), newFolder));
|
||||
socket.emitChanged(`archive-folders-changed`);
|
||||
@@ -298,6 +356,7 @@ module.exports = {
|
||||
|
||||
getZippedPath_meta: true,
|
||||
async getZippedPath({ folder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
if (folder.endsWith('.zip')) {
|
||||
return { filePath: path.join(archivedir(), folder) };
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ function authMiddleware(req, res, next) {
|
||||
'/stream',
|
||||
'/storage/get-connections-for-login-page',
|
||||
'/storage/set-admin-password',
|
||||
'/storage/request-password-reset',
|
||||
'/storage/reset-password',
|
||||
'/auth/get-providers',
|
||||
'/connections/dblogin-web',
|
||||
'/connections/dblogin-app',
|
||||
|
||||
@@ -35,8 +35,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
refreshPublicFiles_meta: true,
|
||||
async refreshPublicFiles({ isRefresh }) {
|
||||
await refreshPublicFiles(isRefresh);
|
||||
async refreshPublicFiles({ isRefresh }, req) {
|
||||
await refreshPublicFiles(isRefresh, req?.headers?.['x-ui-language']);
|
||||
return {
|
||||
status: 'ok',
|
||||
};
|
||||
|
||||
@@ -71,6 +71,7 @@ module.exports = {
|
||||
const isLicenseValid = checkedLicense?.status == 'ok';
|
||||
const logoutUrl = storageConnectionError ? null : await authProvider.getLogoutUrl();
|
||||
const adminConfig = storageConnectionError ? null : await storage.readConfig({ group: 'admin' });
|
||||
const settingsConfig = storageConnectionError ? null : await storage.readConfig({ group: 'settings' });
|
||||
|
||||
storage.startRefreshLicense();
|
||||
|
||||
@@ -121,6 +122,7 @@ module.exports = {
|
||||
allowPrivateCloud: platformInfo.isElectron || !!process.env.ALLOW_DBGATE_PRIVATE_CLOUD,
|
||||
...currentVersion,
|
||||
redirectToDbGateCloudLogin: !!process.env.REDIRECT_TO_DBGATE_CLOUD_LOGIN,
|
||||
preferrendLanguage: settingsConfig?.['storage.language'] || process.env.LANGUAGE || null,
|
||||
};
|
||||
|
||||
return configResult;
|
||||
@@ -287,16 +289,11 @@ module.exports = {
|
||||
const res = await lock.acquire('settings', async () => {
|
||||
const currentValue = await this.loadSettings();
|
||||
try {
|
||||
let updated = currentValue;
|
||||
let updated = {
|
||||
...currentValue,
|
||||
...values,
|
||||
};
|
||||
if (process.env.STORAGE_DATABASE) {
|
||||
updated = {
|
||||
...currentValue,
|
||||
..._.mapValues(values, v => {
|
||||
if (v === true) return 'true';
|
||||
if (v === false) return 'false';
|
||||
return v;
|
||||
}),
|
||||
};
|
||||
await storage.writeConfig({
|
||||
group: 'settings',
|
||||
config: updated,
|
||||
|
||||
@@ -14,15 +14,22 @@ const JsonLinesDatabase = require('../utility/JsonLinesDatabase');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const { safeJsonParse, getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
const { connectionHasPermission, testConnectionPermission, loadPermissionsFromRequest } = require('../utility/hasPermission');
|
||||
const {
|
||||
connectionHasPermission,
|
||||
testConnectionPermission,
|
||||
loadPermissionsFromRequest,
|
||||
} = require('../utility/hasPermission');
|
||||
const pipeForkLogs = require('../utility/pipeForkLogs');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { getAuthProviderById } = require('../auth/authProvider');
|
||||
const { startTokenChecking } = require('../utility/authProxy');
|
||||
const { extractConnectionsFromEnv } = require('../utility/envtools');
|
||||
const { MissingCredentialsError } = require('../utility/exceptions');
|
||||
|
||||
const logger = getLogger('connections');
|
||||
|
||||
let volatileConnections = {};
|
||||
let pendingTestSubprocesses = {}; // Map of conid -> subprocess for MS Entra auth flows
|
||||
|
||||
function getNamedArgs() {
|
||||
const res = {};
|
||||
@@ -57,55 +64,7 @@ function getDatabaseFileLabel(databaseFile) {
|
||||
|
||||
function getPortalCollections() {
|
||||
if (process.env.CONNECTIONS) {
|
||||
const connections = _.compact(process.env.CONNECTIONS.split(',')).map(id => ({
|
||||
_id: id,
|
||||
engine: process.env[`ENGINE_${id}`],
|
||||
server: process.env[`SERVER_${id}`],
|
||||
user: process.env[`USER_${id}`],
|
||||
password: process.env[`PASSWORD_${id}`],
|
||||
passwordMode: process.env[`PASSWORD_MODE_${id}`],
|
||||
port: process.env[`PORT_${id}`],
|
||||
databaseUrl: process.env[`URL_${id}`],
|
||||
useDatabaseUrl: !!process.env[`URL_${id}`],
|
||||
databaseFile: process.env[`FILE_${id}`]?.replace(
|
||||
'%%E2E_TEST_DATA_DIRECTORY%%',
|
||||
path.join(path.dirname(path.dirname(__dirname)), 'e2e-tests', 'tmpdata')
|
||||
),
|
||||
socketPath: process.env[`SOCKET_PATH_${id}`],
|
||||
serviceName: process.env[`SERVICE_NAME_${id}`],
|
||||
authType: process.env[`AUTH_TYPE_${id}`] || (process.env[`SOCKET_PATH_${id}`] ? 'socket' : undefined),
|
||||
defaultDatabase:
|
||||
process.env[`DATABASE_${id}`] ||
|
||||
(process.env[`FILE_${id}`] ? getDatabaseFileLabel(process.env[`FILE_${id}`]) : null),
|
||||
singleDatabase: !!process.env[`DATABASE_${id}`] || !!process.env[`FILE_${id}`],
|
||||
displayName: process.env[`LABEL_${id}`],
|
||||
isReadOnly: process.env[`READONLY_${id}`],
|
||||
databases: process.env[`DBCONFIG_${id}`] ? safeJsonParse(process.env[`DBCONFIG_${id}`]) : null,
|
||||
allowedDatabases: process.env[`ALLOWED_DATABASES_${id}`]?.replace(/\|/g, '\n'),
|
||||
allowedDatabasesRegex: process.env[`ALLOWED_DATABASES_REGEX_${id}`],
|
||||
parent: process.env[`PARENT_${id}`] || undefined,
|
||||
useSeparateSchemas: !!process.env[`USE_SEPARATE_SCHEMAS_${id}`],
|
||||
localDataCenter: process.env[`LOCAL_DATA_CENTER_${id}`],
|
||||
|
||||
// SSH tunnel
|
||||
useSshTunnel: process.env[`USE_SSH_${id}`],
|
||||
sshHost: process.env[`SSH_HOST_${id}`],
|
||||
sshPort: process.env[`SSH_PORT_${id}`],
|
||||
sshMode: process.env[`SSH_MODE_${id}`],
|
||||
sshLogin: process.env[`SSH_LOGIN_${id}`],
|
||||
sshPassword: process.env[`SSH_PASSWORD_${id}`],
|
||||
sshKeyfile: process.env[`SSH_KEY_FILE_${id}`],
|
||||
sshKeyfilePassword: process.env[`SSH_KEY_FILE_PASSWORD_${id}`],
|
||||
|
||||
// SSL
|
||||
useSsl: process.env[`USE_SSL_${id}`],
|
||||
sslCaFile: process.env[`SSL_CA_FILE_${id}`],
|
||||
sslCertFile: process.env[`SSL_CERT_FILE_${id}`],
|
||||
sslCertFilePassword: process.env[`SSL_CERT_FILE_PASSWORD_${id}`],
|
||||
sslKeyFile: process.env[`SSL_KEY_FILE_${id}`],
|
||||
sslRejectUnauthorized: process.env[`SSL_REJECT_UNAUTHORIZED_${id}`],
|
||||
trustServerCertificate: process.env[`SSL_TRUST_CERTIFICATE_${id}`],
|
||||
}));
|
||||
const connections = extractConnectionsFromEnv(process.env);
|
||||
|
||||
for (const conn of connections) {
|
||||
for (const prop in process.env) {
|
||||
@@ -116,7 +75,10 @@ function getPortalCollections() {
|
||||
}
|
||||
}
|
||||
|
||||
logger.info({ connections: connections.map(pickSafeConnectionInfo) }, 'DBGM-00005 Using connections from ENV variables');
|
||||
logger.info(
|
||||
{ connections: connections.map(pickSafeConnectionInfo) },
|
||||
'DBGM-00005 Using connections from ENV variables'
|
||||
);
|
||||
const noengine = connections.filter(x => !x.engine);
|
||||
if (noengine.length > 0) {
|
||||
logger.warn(
|
||||
@@ -222,6 +184,15 @@ module.exports = {
|
||||
);
|
||||
}
|
||||
await this.checkUnsavedConnectionsLimit();
|
||||
|
||||
if (process.env.STORAGE_DATABASE && process.env.CONNECTIONS) {
|
||||
const storage = require('./storage');
|
||||
try {
|
||||
await storage.fillStorageConnectionsFromEnv();
|
||||
} catch (err) {
|
||||
logger.error(extractErrorLogData(err), 'DBGM-00268 Error filling storage connections from env');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
list_meta: true,
|
||||
@@ -231,10 +202,10 @@ module.exports = {
|
||||
|
||||
const storageConnections = await storage.connections(req);
|
||||
if (storageConnections) {
|
||||
return storageConnections;
|
||||
return storageConnections.map(maskConnection);
|
||||
}
|
||||
if (portalConnections) {
|
||||
if (platformInfo.allowShellConnection) return portalConnections;
|
||||
if (platformInfo.allowShellConnection) return portalConnections.map(x => encryptConnection(x));
|
||||
return portalConnections.map(maskConnection).filter(x => connectionHasPermission(x, loadedPermissions));
|
||||
}
|
||||
return (await this.datastore.find()).filter(x => connectionHasPermission(x, loadedPermissions));
|
||||
@@ -270,14 +241,60 @@ module.exports = {
|
||||
);
|
||||
pipeForkLogs(subprocess);
|
||||
subprocess.send({ ...connection, requestDbList });
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let isWaitingForVolatile = false;
|
||||
|
||||
const cleanup = () => {
|
||||
if (connection._id && pendingTestSubprocesses[connection._id]) {
|
||||
delete pendingTestSubprocesses[connection._id];
|
||||
}
|
||||
};
|
||||
|
||||
subprocess.on('message', resp => {
|
||||
if (handleProcessCommunication(resp, subprocess)) return;
|
||||
// @ts-ignore
|
||||
const { msgtype } = resp;
|
||||
const { msgtype, missingCredentialsDetail } = resp;
|
||||
if (msgtype == 'connected' || msgtype == 'error') {
|
||||
cleanup();
|
||||
resolve(resp);
|
||||
}
|
||||
if (msgtype == 'missingCredentials') {
|
||||
if (missingCredentialsDetail?.redirectToDbLogin) {
|
||||
// Store the subprocess for later when volatile connection is ready
|
||||
isWaitingForVolatile = true;
|
||||
pendingTestSubprocesses[connection._id] = {
|
||||
subprocess,
|
||||
requestDbList,
|
||||
};
|
||||
// Return immediately with redirectToDbLogin status in the old format
|
||||
resolve({
|
||||
missingCredentials: true,
|
||||
detail: {
|
||||
...missingCredentialsDetail,
|
||||
keepErrorResponseFromApi: true,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
reject(new MissingCredentialsError(missingCredentialsDetail));
|
||||
}
|
||||
});
|
||||
|
||||
subprocess.on('exit', code => {
|
||||
// If exit happens while waiting for volatile, that's expected
|
||||
if (isWaitingForVolatile && code === 0) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
cleanup();
|
||||
if (code !== 0) {
|
||||
reject(new Error(`Test subprocess exited with code ${code}`));
|
||||
}
|
||||
});
|
||||
|
||||
subprocess.on('error', err => {
|
||||
cleanup();
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -310,6 +327,38 @@ module.exports = {
|
||||
return testRes;
|
||||
} else {
|
||||
volatileConnections[res._id] = res;
|
||||
|
||||
// Check if there's a pending test subprocess waiting for this volatile connection
|
||||
const pendingTest = pendingTestSubprocesses[conid];
|
||||
if (pendingTest) {
|
||||
const { subprocess, requestDbList } = pendingTest;
|
||||
try {
|
||||
// Send the volatile connection to the waiting subprocess
|
||||
subprocess.send({ ...res, requestDbList, isVolatileResolved: true });
|
||||
|
||||
// Wait for the test result and emit it as an event
|
||||
subprocess.once('message', resp => {
|
||||
if (handleProcessCommunication(resp, subprocess)) return;
|
||||
const { msgtype } = resp;
|
||||
if (msgtype == 'connected' || msgtype == 'error') {
|
||||
// Emit SSE event with test result
|
||||
socket.emit(`connection-test-result-${conid}`, {
|
||||
...resp,
|
||||
volatileConId: res._id,
|
||||
});
|
||||
delete pendingTestSubprocesses[conid];
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(extractErrorLogData(err), 'DBGM-00118 Error sending volatile connection to test subprocess');
|
||||
socket.emit(`connection-test-result-${conid}`, {
|
||||
msgtype: 'error',
|
||||
error: err.message,
|
||||
});
|
||||
delete pendingTestSubprocesses[conid];
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
},
|
||||
@@ -435,15 +484,69 @@ module.exports = {
|
||||
|
||||
const storageConnection = await storage.getConnection({ conid });
|
||||
if (storageConnection) {
|
||||
return storageConnection;
|
||||
return mask ? maskConnection(storageConnection) : storageConnection;
|
||||
}
|
||||
|
||||
if (portalConnections) {
|
||||
const res = portalConnections.find(x => x._id == conid) || null;
|
||||
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
|
||||
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : encryptConnection(res);
|
||||
}
|
||||
const res = await this.datastore.get(conid);
|
||||
return res || null;
|
||||
if (res) return res;
|
||||
|
||||
// In a forked runner-script child process, ask the parent for connections that may be
|
||||
// volatile (in-memory only, e.g. ask-for-password). We only do this when
|
||||
// there really is a parent (process.send exists) to avoid an infinite loop
|
||||
// when the parent's own getCore falls through here.
|
||||
// The check is intentionally narrow: only runner scripts pass
|
||||
// --process-display-name script, so connect/session/ssh-forward subprocesses
|
||||
// are not affected and continue to return null immediately.
|
||||
if (process.send && processArgs.processDisplayName === 'script') {
|
||||
const conn = await new Promise(resolve => {
|
||||
let resolved = false;
|
||||
|
||||
const cleanup = () => {
|
||||
process.removeListener('message', handler);
|
||||
process.removeListener('disconnect', onDisconnect);
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
|
||||
const settle = value => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
cleanup();
|
||||
resolve(value);
|
||||
}
|
||||
};
|
||||
|
||||
const handler = message => {
|
||||
if (message?.msgtype === 'volatile-connection-response' && message.conid === conid) {
|
||||
settle(message.conn || null);
|
||||
}
|
||||
};
|
||||
|
||||
const onDisconnect = () => settle(null);
|
||||
|
||||
const timeout = setTimeout(() => settle(null), 5000);
|
||||
// Don't let the timer alone keep the process alive if all other work is done
|
||||
timeout.unref();
|
||||
|
||||
process.on('message', handler);
|
||||
process.once('disconnect', onDisconnect);
|
||||
|
||||
try {
|
||||
process.send({ msgtype: 'get-volatile-connection', conid });
|
||||
} catch {
|
||||
settle(null);
|
||||
}
|
||||
});
|
||||
if (conn) {
|
||||
volatileConnections[conn._id] = conn; // cache for subsequent calls
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
get_meta: true,
|
||||
@@ -453,6 +556,9 @@ module.exports = {
|
||||
_id: '__model',
|
||||
};
|
||||
}
|
||||
if (!conid) {
|
||||
return null;
|
||||
}
|
||||
await testConnectionPermission(conid, req);
|
||||
return this.getCore({ conid, mask: true });
|
||||
},
|
||||
@@ -502,7 +608,11 @@ module.exports = {
|
||||
state,
|
||||
client: 'web',
|
||||
});
|
||||
res.redirect(authResp.url);
|
||||
if (authResp?.url) {
|
||||
res.redirect(authResp.url);
|
||||
return;
|
||||
}
|
||||
res.json({ error: 'No URL returned from auth provider' });
|
||||
},
|
||||
|
||||
dbloginApp_meta: true,
|
||||
|
||||
@@ -15,6 +15,7 @@ const {
|
||||
getLogger,
|
||||
extractErrorLogData,
|
||||
filterStructureBySchema,
|
||||
serializeJsTypesForJsonStringify,
|
||||
} = require('dbgate-tools');
|
||||
const { html, parse } = require('diff2html');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
@@ -29,7 +30,17 @@ const generateDeploySql = require('../shell/generateDeploySql');
|
||||
const { createTwoFilesPatch } = require('diff');
|
||||
const diff2htmlPage = require('../utility/diff2htmlPage');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const { testConnectionPermission, hasPermission, loadPermissionsFromRequest, loadTablePermissionsFromRequest, getTablePermissionRole, loadDatabasePermissionsFromRequest, getDatabasePermissionRole, getTablePermissionRoleLevelIndex, testDatabaseRolePermission } = require('../utility/hasPermission');
|
||||
const {
|
||||
testConnectionPermission,
|
||||
hasPermission,
|
||||
loadPermissionsFromRequest,
|
||||
loadTablePermissionsFromRequest,
|
||||
getTablePermissionRole,
|
||||
loadDatabasePermissionsFromRequest,
|
||||
getDatabasePermissionRole,
|
||||
getTablePermissionRoleLevelIndex,
|
||||
testDatabaseRolePermission,
|
||||
} = require('../utility/hasPermission');
|
||||
const { MissingCredentialsError } = require('../utility/exceptions');
|
||||
const pipeForkLogs = require('../utility/pipeForkLogs');
|
||||
const crypto = require('crypto');
|
||||
@@ -84,10 +95,12 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
handle_response(conid, database, { msgid, ...response }) {
|
||||
const [resolve, reject, additionalData] = this.requests[msgid];
|
||||
resolve(response);
|
||||
if (additionalData?.auditLogger) {
|
||||
additionalData?.auditLogger(response);
|
||||
const [resolve, reject, additionalData] = this.requests[msgid] || [];
|
||||
if (resolve) {
|
||||
resolve(response);
|
||||
if (additionalData?.auditLogger) {
|
||||
additionalData?.auditLogger(response);
|
||||
}
|
||||
}
|
||||
delete this.requests[msgid];
|
||||
},
|
||||
@@ -100,7 +113,7 @@ module.exports = {
|
||||
socket.emitChanged(`database-status-changed`, { conid, database });
|
||||
},
|
||||
|
||||
handle_ping() { },
|
||||
handle_ping() {},
|
||||
|
||||
// session event handlers
|
||||
|
||||
@@ -155,6 +168,11 @@ module.exports = {
|
||||
if (!connection) {
|
||||
throw new Error(`databaseConnections: Connection with conid="${conid}" not found`);
|
||||
}
|
||||
|
||||
if (connection.engine?.endsWith('@rest')) {
|
||||
return { isApiConnection: true };
|
||||
}
|
||||
|
||||
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
||||
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
||||
}
|
||||
@@ -209,12 +227,13 @@ module.exports = {
|
||||
this.close(conid, database, false);
|
||||
});
|
||||
|
||||
subprocess.send({
|
||||
const connectMessage = serializeJsTypesForJsonStringify({
|
||||
msgtype: 'connect',
|
||||
connection: { ...connection, database },
|
||||
structure: lastClosed ? lastClosed.structure : null,
|
||||
globalSettings: await config.getSettings(),
|
||||
});
|
||||
subprocess.send(connectMessage);
|
||||
return newOpened;
|
||||
},
|
||||
|
||||
@@ -222,9 +241,10 @@ module.exports = {
|
||||
sendRequest(conn, message, additionalData = {}) {
|
||||
const msgid = crypto.randomUUID();
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
this.requests[msgid] = [resolve, reject, additionalData];
|
||||
this.requests[msgid] = [resolve, reject, additionalData, conn.conid, conn.database];
|
||||
try {
|
||||
conn.subprocess.send({ msgid, ...message });
|
||||
const serializedMessage = serializeJsTypesForJsonStringify({ msgid, ...message });
|
||||
conn.subprocess.send(serializedMessage);
|
||||
} catch (err) {
|
||||
logger.error(extractErrorLogData(err), 'DBGM-00115 Error sending request do process');
|
||||
this.close(conn.conid, conn.database);
|
||||
@@ -246,33 +266,34 @@ module.exports = {
|
||||
},
|
||||
|
||||
sqlSelect_meta: true,
|
||||
async sqlSelect({ conid, database, select, auditLogSessionGroup }, req) {
|
||||
async sqlSelect({ conid, database, select, commandTimeout, auditLogSessionGroup }, req) {
|
||||
await testConnectionPermission(conid, req);
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
const res = await this.sendRequest(
|
||||
opened,
|
||||
{ msgtype: 'sqlSelect', select },
|
||||
{ msgtype: 'sqlSelect', select, commandTimeout },
|
||||
{
|
||||
auditLogger:
|
||||
auditLogSessionGroup && select?.from?.name?.pureName
|
||||
? response => {
|
||||
sendToAuditLog(req, {
|
||||
category: 'dbop',
|
||||
component: 'DatabaseConnectionsController',
|
||||
event: 'sql.select',
|
||||
action: 'select',
|
||||
severity: 'info',
|
||||
conid,
|
||||
database,
|
||||
schemaName: select?.from?.name?.schemaName,
|
||||
pureName: select?.from?.name?.pureName,
|
||||
sumint1: response?.rows?.length,
|
||||
sessionParam: `${conid}::${database}::${select?.from?.name?.schemaName || '0'}::${select?.from?.name?.pureName
|
||||
sendToAuditLog(req, {
|
||||
category: 'dbop',
|
||||
component: 'DatabaseConnectionsController',
|
||||
event: 'sql.select',
|
||||
action: 'select',
|
||||
severity: 'info',
|
||||
conid,
|
||||
database,
|
||||
schemaName: select?.from?.name?.schemaName,
|
||||
pureName: select?.from?.name?.pureName,
|
||||
sumint1: response?.rows?.length,
|
||||
sessionParam: `${conid}::${database}::${select?.from?.name?.schemaName || '0'}::${
|
||||
select?.from?.name?.pureName
|
||||
}`,
|
||||
sessionGroup: auditLogSessionGroup,
|
||||
message: `Loaded table data from ${select?.from?.name?.pureName}`,
|
||||
});
|
||||
}
|
||||
sessionGroup: auditLogSessionGroup,
|
||||
message: `Loaded table data from ${select?.from?.name?.pureName}`,
|
||||
});
|
||||
}
|
||||
: null,
|
||||
}
|
||||
);
|
||||
@@ -325,9 +346,12 @@ module.exports = {
|
||||
},
|
||||
|
||||
collectionData_meta: true,
|
||||
async collectionData({ conid, database, options, auditLogSessionGroup }, req) {
|
||||
async collectionData({ conid, database, options, commandTimeout, auditLogSessionGroup }, req) {
|
||||
await testConnectionPermission(conid, req);
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
if (commandTimeout && options) {
|
||||
options.commandTimeout = commandTimeout;
|
||||
}
|
||||
const res = await this.sendRequest(
|
||||
opened,
|
||||
{ msgtype: 'collectionData', options },
|
||||
@@ -335,21 +359,21 @@ module.exports = {
|
||||
auditLogger:
|
||||
auditLogSessionGroup && options?.pureName
|
||||
? response => {
|
||||
sendToAuditLog(req, {
|
||||
category: 'dbop',
|
||||
component: 'DatabaseConnectionsController',
|
||||
event: 'nosql.collectionData',
|
||||
action: 'select',
|
||||
severity: 'info',
|
||||
conid,
|
||||
database,
|
||||
pureName: options?.pureName,
|
||||
sumint1: response?.result?.rows?.length,
|
||||
sessionParam: `${conid}::${database}::${options?.pureName}`,
|
||||
sessionGroup: auditLogSessionGroup,
|
||||
message: `Loaded collection data ${options?.pureName}`,
|
||||
});
|
||||
}
|
||||
sendToAuditLog(req, {
|
||||
category: 'dbop',
|
||||
component: 'DatabaseConnectionsController',
|
||||
event: 'nosql.collectionData',
|
||||
action: 'select',
|
||||
severity: 'info',
|
||||
conid,
|
||||
database,
|
||||
pureName: options?.pureName,
|
||||
sumint1: response?.result?.rows?.length,
|
||||
sessionParam: `${conid}::${database}::${options?.pureName}`,
|
||||
sessionGroup: auditLogSessionGroup,
|
||||
message: `Loaded collection data ${options?.pureName}`,
|
||||
});
|
||||
}
|
||||
: null,
|
||||
}
|
||||
);
|
||||
@@ -382,6 +406,12 @@ module.exports = {
|
||||
return null;
|
||||
},
|
||||
|
||||
dispatchRedisKeysChanged_meta: true,
|
||||
dispatchRedisKeysChanged({ conid, database }) {
|
||||
socket.emit(`redis-keys-changed-${conid}-${database}`);
|
||||
return null;
|
||||
},
|
||||
|
||||
loadKeys_meta: true,
|
||||
async loadKeys({ conid, database, root, filter, limit }, req) {
|
||||
await testConnectionPermission(conid, req);
|
||||
@@ -451,14 +481,23 @@ module.exports = {
|
||||
|
||||
const databasePermissions = await loadDatabasePermissionsFromRequest(req);
|
||||
const tablePermissions = await loadTablePermissionsFromRequest(req);
|
||||
const databasePermissionRole = getDatabasePermissionRole(conid, database, databasePermissions);
|
||||
const fieldsAndRoles = [
|
||||
[changeSet.inserts, 'create_update_delete'],
|
||||
[changeSet.deletes, 'create_update_delete'],
|
||||
[changeSet.updates, 'update_only'],
|
||||
]
|
||||
];
|
||||
for (const [operations, requiredRole] of fieldsAndRoles) {
|
||||
for (const operation of operations) {
|
||||
const role = getTablePermissionRole(conid, database, 'tables', operation.schemaName, operation.pureName, tablePermissions, databasePermissions);
|
||||
const role = getTablePermissionRole(
|
||||
conid,
|
||||
database,
|
||||
'tables',
|
||||
operation.schemaName,
|
||||
operation.pureName,
|
||||
tablePermissions,
|
||||
databasePermissionRole
|
||||
);
|
||||
if (getTablePermissionRoleLevelIndex(role) < getTablePermissionRoleLevelIndex(requiredRole)) {
|
||||
throw new Error('DBGM-00262 Permission not granted');
|
||||
}
|
||||
@@ -475,6 +514,20 @@ module.exports = {
|
||||
return res.result || null;
|
||||
},
|
||||
|
||||
multiCallMethod_meta: true,
|
||||
async multiCallMethod({ conid, database, callList }, req) {
|
||||
await testConnectionPermission(conid, req);
|
||||
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
const res = await this.sendRequest(opened, { msgtype: 'multiCallMethod', callList });
|
||||
if (res.errorMessage) {
|
||||
return {
|
||||
errorMessage: res.errorMessage,
|
||||
};
|
||||
}
|
||||
return res.result || null;
|
||||
},
|
||||
|
||||
status_meta: true,
|
||||
async status({ conid, database }, req) {
|
||||
if (!conid) {
|
||||
@@ -532,6 +585,24 @@ module.exports = {
|
||||
};
|
||||
},
|
||||
|
||||
pingDatabases_meta: true,
|
||||
async pingDatabases({ databases }, req) {
|
||||
if (!databases || !Array.isArray(databases)) return { status: 'ok' };
|
||||
for (const { conid, database } of databases) {
|
||||
if (!conid || !database) continue;
|
||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||
if (existing) {
|
||||
try {
|
||||
existing.subprocess.send({ msgtype: 'ping' });
|
||||
} catch (err) {
|
||||
logger.error(extractErrorLogData(err), 'DBGM-00308 Error pinging DB connection');
|
||||
this.close(conid, database);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { status: 'ok' };
|
||||
},
|
||||
|
||||
refresh_meta: true,
|
||||
async refresh({ conid, database, keepOpen }, req) {
|
||||
await testConnectionPermission(conid, req);
|
||||
@@ -574,6 +645,15 @@ module.exports = {
|
||||
structure: existing.structure,
|
||||
};
|
||||
socket.emitChanged(`database-status-changed`, { conid, database });
|
||||
|
||||
// Reject all pending requests for this connection
|
||||
for (const [msgid, entry] of Object.entries(this.requests)) {
|
||||
const [resolve, reject, additionalData, reqConid, reqDatabase] = entry;
|
||||
if (reqConid === conid && reqDatabase === database) {
|
||||
reject('DBGM-00309 Database connection closed');
|
||||
delete this.requests[msgid];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -628,7 +708,15 @@ module.exports = {
|
||||
function applyTablePermissionRole(list, objectTypeField) {
|
||||
const res = [];
|
||||
for (const item of list ?? []) {
|
||||
const tablePermissionRole = getTablePermissionRole(conid, database, objectTypeField, item.schemaName, item.pureName, tablePermissions, databasePermissionRole);
|
||||
const tablePermissionRole = getTablePermissionRole(
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
item.schemaName,
|
||||
item.pureName,
|
||||
tablePermissions,
|
||||
databasePermissionRole
|
||||
);
|
||||
if (tablePermissionRole != 'deny') {
|
||||
res.push({
|
||||
...item,
|
||||
@@ -647,7 +735,7 @@ module.exports = {
|
||||
functions: applyTablePermissionRole(opened.structure.functions, 'functions'),
|
||||
triggers: applyTablePermissionRole(opened.structure.triggers, 'triggers'),
|
||||
collections: applyTablePermissionRole(opened.structure.collections, 'collections'),
|
||||
}
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -881,17 +969,17 @@ module.exports = {
|
||||
return {
|
||||
...(command == 'backup'
|
||||
? driver.backupDatabaseCommand(
|
||||
connection,
|
||||
{ outputFile, database, options, selectedTables, skippedTables, argsFormat },
|
||||
// @ts-ignore
|
||||
externalTools
|
||||
)
|
||||
connection,
|
||||
{ outputFile, database, options, selectedTables, skippedTables, argsFormat },
|
||||
// @ts-ignore
|
||||
externalTools
|
||||
)
|
||||
: driver.restoreDatabaseCommand(
|
||||
connection,
|
||||
{ inputFile, database, options, argsFormat },
|
||||
// @ts-ignore
|
||||
externalTools
|
||||
)),
|
||||
connection,
|
||||
{ inputFile, database, options, argsFormat },
|
||||
// @ts-ignore
|
||||
externalTools
|
||||
)),
|
||||
transformMessage: driver.transformNativeCommandMessage
|
||||
? message => driver.transformNativeCommandMessage(message, command)
|
||||
: null,
|
||||
@@ -990,7 +1078,10 @@ module.exports = {
|
||||
async executeSessionQuery({ sesid, conid, database, sql }, req) {
|
||||
await testConnectionPermission(conid, req);
|
||||
logger.info({ sesid, sql }, 'DBGM-00010 Processing query');
|
||||
sessions.dispatchMessage(sesid, 'Query execution started');
|
||||
sessions.dispatchMessage(sesid, {
|
||||
message: 'Query execution started',
|
||||
sql,
|
||||
});
|
||||
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
opened.subprocess.send({ msgtype: 'executeSessionQuery', sql, sesid });
|
||||
|
||||
@@ -15,7 +15,8 @@ const getDiagramExport = require('../utility/getDiagramExport');
|
||||
const apps = require('./apps');
|
||||
const getMapExport = require('../utility/getMapExport');
|
||||
const dbgateApi = require('../shell');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, getSqlFrontMatter } = require('dbgate-tools');
|
||||
const yaml = require('js-yaml');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
const { checkSecureFilePathsWithoutDirectory, checkSecureDirectories } = require('../utility/security');
|
||||
const { copyAppLogsIntoFile, getRecentAppLogRecords } = require('../utility/appLogStore');
|
||||
@@ -35,13 +36,46 @@ function deserialize(format, text) {
|
||||
|
||||
module.exports = {
|
||||
list_meta: true,
|
||||
async list({ folder }, req) {
|
||||
async list({ folder, parseFrontMatter }, req) {
|
||||
const loadedPermissions = await loadPermissionsFromRequest(req);
|
||||
if (!hasPermission(`files/${folder}/read`, loadedPermissions)) return [];
|
||||
const dir = path.join(filesdir(), folder);
|
||||
if (!(await fs.exists(dir))) return [];
|
||||
const files = (await fs.readdir(dir)).map(file => ({ folder, file }));
|
||||
return files;
|
||||
const fileNames = await fs.readdir(dir);
|
||||
if (!parseFrontMatter) {
|
||||
return fileNames.map(file => ({ folder, file }));
|
||||
}
|
||||
const result = [];
|
||||
for (const file of fileNames) {
|
||||
const item = { folder, file };
|
||||
let fh;
|
||||
try {
|
||||
fh = await require('fs').promises.open(path.join(dir, file), 'r');
|
||||
const buf = new Uint8Array(512);
|
||||
const { bytesRead } = await fh.read(buf, 0, 512, 0);
|
||||
let text = Buffer.from(buf.buffer, 0, bytesRead).toString('utf-8');
|
||||
|
||||
if (text.includes('-- >>>') && !text.includes('-- <<<')) {
|
||||
const stat = await fh.stat();
|
||||
const fullSize = Math.min(stat.size, 4096);
|
||||
if (fullSize > 512) {
|
||||
const fullBuf = new Uint8Array(fullSize);
|
||||
const { bytesRead: fullBytesRead } = await fh.read(fullBuf, 0, fullSize, 0);
|
||||
text = Buffer.from(fullBuf.buffer, 0, fullBytesRead).toString('utf-8');
|
||||
}
|
||||
}
|
||||
|
||||
const fm = getSqlFrontMatter(text, yaml);
|
||||
if (fm?.connectionId) item.connectionId = fm.connectionId;
|
||||
if (fm?.databaseName) item.databaseName = fm.databaseName;
|
||||
} catch (e) {
|
||||
// ignore read errors for individual files
|
||||
} finally {
|
||||
if (fh) await fh.close().catch(() => {});
|
||||
}
|
||||
result.push(item);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
listAll_meta: true,
|
||||
@@ -68,6 +102,7 @@ module.exports = {
|
||||
await fs.unlink(path.join(filesdir(), folder, file));
|
||||
socket.emitChanged(`files-changed`, { folder });
|
||||
socket.emitChanged(`all-files-changed`);
|
||||
this.emitChangedFolder(folder);
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -140,6 +175,15 @@ module.exports = {
|
||||
return deserialize(format, text);
|
||||
},
|
||||
|
||||
emitChangedFolder(folder) {
|
||||
if (folder == 'themes') {
|
||||
socket.emitChanged(`file-themes-changed`);
|
||||
}
|
||||
if (folder == 'favorites') {
|
||||
socket.emitChanged('files-changed-favorites');
|
||||
}
|
||||
},
|
||||
|
||||
save_meta: true,
|
||||
async save({ folder, file, data, format }, req) {
|
||||
const loadedPermissions = await loadPermissionsFromRequest(req);
|
||||
@@ -173,6 +217,8 @@ module.exports = {
|
||||
if (folder == 'shell') {
|
||||
scheduler.reload();
|
||||
}
|
||||
this.emitChangedFolder(folder);
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
@@ -240,8 +286,15 @@ module.exports = {
|
||||
},
|
||||
|
||||
exportDiagram_meta: true,
|
||||
async exportDiagram({ filePath, html, css, themeType, themeClassName, watermark }) {
|
||||
await fs.writeFile(filePath, getDiagramExport(html, css, themeType, themeClassName, watermark));
|
||||
async exportDiagram({ filePath, html, css, themeType, themeVariables, watermark }) {
|
||||
await fs.writeFile(filePath, getDiagramExport(html, css, themeType, themeVariables, watermark));
|
||||
return true;
|
||||
},
|
||||
|
||||
exportDiagramPng_meta: true,
|
||||
async exportDiagramPng({ filePath, pngBase64 }) {
|
||||
const base64 = pngBase64.replace(/^data:image\/png;base64,/, '');
|
||||
await fs.writeFile(filePath, Buffer.from(base64, 'base64'));
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -346,4 +399,20 @@ module.exports = {
|
||||
}
|
||||
return res;
|
||||
},
|
||||
|
||||
getFileThemes_meta: true,
|
||||
async getFileThemes(_params, req) {
|
||||
const loadedPermissions = await loadPermissionsFromRequest(req);
|
||||
if (!hasPermission(`files/themes/read`, loadedPermissions)) return [];
|
||||
const dir = path.join(filesdir(), 'themes');
|
||||
if (!(await fs.exists(dir))) return [];
|
||||
const files = await fs.readdir(dir);
|
||||
const res = [];
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const text = await fs.readFile(filePath, { encoding: 'utf-8' });
|
||||
res.push(JSON.parse(text));
|
||||
}
|
||||
return res;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const { filterName } = require('dbgate-tools');
|
||||
const { filterName, getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const logger = getLogger('jsldata');
|
||||
const { jsldir, archivedir } = require('../utility/directories');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const lineReader = require('line-reader');
|
||||
const _ = require('lodash');
|
||||
const { __ } = require('lodash/fp');
|
||||
@@ -149,6 +152,10 @@ module.exports = {
|
||||
|
||||
getRows_meta: true,
|
||||
async getRows({ jslid, offset, limit, filters, sort, formatterFunction }) {
|
||||
const fileName = getJslFileName(jslid);
|
||||
if (!fs.existsSync(fileName)) {
|
||||
return [];
|
||||
}
|
||||
const datastore = await this.ensureDatastore(jslid, formatterFunction);
|
||||
return datastore.getRows(offset, limit, _.isEmpty(filters) ? null : filters, _.isEmpty(sort) ? null : sort);
|
||||
},
|
||||
@@ -159,6 +166,72 @@ module.exports = {
|
||||
return fs.existsSync(fileName);
|
||||
},
|
||||
|
||||
streamRows_meta: {
|
||||
method: 'get',
|
||||
raw: true,
|
||||
},
|
||||
streamRows(req, res) {
|
||||
const { jslid } = req.query;
|
||||
if (!jslid) {
|
||||
res.status(400).json({ apiErrorMessage: 'Missing jslid' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Reject file:// jslids — they resolve to arbitrary server-side paths
|
||||
if (jslid.startsWith('file://')) {
|
||||
res.status(403).json({ apiErrorMessage: 'Forbidden jslid scheme' });
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = getJslFileName(jslid);
|
||||
|
||||
if (!fs.existsSync(fileName)) {
|
||||
res.status(404).json({ apiErrorMessage: 'File not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Dereference symlinks and normalize case (Windows) before the allow-list check.
|
||||
// realpathSync is safe here because existsSync confirmed the file is present.
|
||||
// path.resolve() alone cannot dereference symlinks, so a symlink inside an allowed
|
||||
// root could otherwise point to an arbitrary external path.
|
||||
const normalize = p => (process.platform === 'win32' ? p.toLowerCase() : p);
|
||||
const resolveRoot = r => { try { return fs.realpathSync(r); } catch { return path.resolve(r); } };
|
||||
|
||||
let realFile;
|
||||
try {
|
||||
realFile = fs.realpathSync(fileName);
|
||||
} catch {
|
||||
res.status(403).json({ apiErrorMessage: 'Forbidden path' });
|
||||
return;
|
||||
}
|
||||
|
||||
const allowedRoots = [jsldir(), archivedir()].map(r => normalize(resolveRoot(r)) + path.sep);
|
||||
const isAllowed = allowedRoots.some(root => normalize(realFile).startsWith(root));
|
||||
if (!isAllowed) {
|
||||
logger.warn({ jslid, realFile }, 'DBGM-00000 streamRows rejected path outside allowed roots');
|
||||
res.status(403).json({ apiErrorMessage: 'Forbidden path' });
|
||||
return;
|
||||
}
|
||||
res.setHeader('Content-Type', 'application/x-ndjson');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
const stream = fs.createReadStream(realFile, 'utf-8');
|
||||
|
||||
req.on('close', () => {
|
||||
stream.destroy();
|
||||
});
|
||||
|
||||
stream.on('error', err => {
|
||||
logger.error(extractErrorLogData(err), 'DBGM-00000 Error streaming JSONL file');
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ apiErrorMessage: 'Stream error' });
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
|
||||
stream.pipe(res);
|
||||
},
|
||||
|
||||
getStats_meta: true,
|
||||
getStats({ jslid }) {
|
||||
const file = `${getJslFileName(jslid)}.stats`;
|
||||
|
||||
@@ -33,19 +33,35 @@ function readCore(reader, skip, limit, filter) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
read_meta: true,
|
||||
async read({ skip, limit, filter }) {
|
||||
function readJsonl({ skip, limit, filter }) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const fileName = path.join(datadir(), 'query-history.jsonl');
|
||||
// @ts-ignore
|
||||
if (!(await fs.exists(fileName))) return [];
|
||||
if (!(await fs.exists(fileName))) return resolve([]);
|
||||
const reader = fsReverse(fileName);
|
||||
const res = await readCore(reader, skip, limit, filter);
|
||||
return res;
|
||||
resolve(res);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
read_meta: true,
|
||||
async read({ skip, limit, filter }, req) {
|
||||
const storage = require('./storage');
|
||||
const storageResult = await storage.readQueryHistory({ skip, limit, filter }, req);
|
||||
if (storageResult) return storageResult;
|
||||
return readJsonl({ skip, limit, filter });
|
||||
},
|
||||
|
||||
write_meta: true,
|
||||
async write({ data }) {
|
||||
async write({ data }, req) {
|
||||
const storage = require('./storage');
|
||||
const written = await storage.writeQueryHistory({ data }, req);
|
||||
if (written) {
|
||||
socket.emit('query-history-changed');
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
const fileName = path.join(datadir(), 'query-history.jsonl');
|
||||
await fs.appendFile(fileName, JSON.stringify(data) + '\n');
|
||||
socket.emit('query-history-changed');
|
||||
|
||||
41
packages/api/src/controllers/restConnections.js
Normal file
41
packages/api/src/controllers/restConnections.js
Normal file
@@ -0,0 +1,41 @@
|
||||
module.exports = {
|
||||
disconnect_meta: true,
|
||||
async disconnect({ conid }, req) {
|
||||
return null;
|
||||
},
|
||||
|
||||
getApiInfo_meta: true,
|
||||
async getApiInfo({ conid }, req) {
|
||||
return null;
|
||||
},
|
||||
|
||||
restStatus_meta: true,
|
||||
async restStatus() {
|
||||
return {};
|
||||
},
|
||||
|
||||
ping_meta: true,
|
||||
async ping({ conidArray, strmid }) {
|
||||
return null;
|
||||
},
|
||||
|
||||
refresh_meta: true,
|
||||
async refresh({ conid, keepOpen }, req) {
|
||||
return null;
|
||||
},
|
||||
|
||||
testConnection_meta: true,
|
||||
async testConnection({ conid }, req) {
|
||||
return null;
|
||||
},
|
||||
|
||||
execute_meta: true,
|
||||
async execute({ conid, method, endpoint, parameters, server }, req) {
|
||||
return null;
|
||||
},
|
||||
|
||||
apiQuery_meta: true,
|
||||
async apiQuery({ conid, server, query, variables }, req) {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -10,6 +10,7 @@ const {
|
||||
extractShellApiPlugins,
|
||||
compileShellApiFunctionName,
|
||||
jsonScriptToJavascript,
|
||||
assertValidShellApiFunctionName,
|
||||
getLogger,
|
||||
safeJsonParse,
|
||||
pinoLogRecordToMessageRecord,
|
||||
@@ -54,19 +55,23 @@ logger.info('DBGM-00014 Finished job script');
|
||||
dbgateApi.runScript(run);
|
||||
`;
|
||||
|
||||
const loaderScriptTemplate = (prefix, functionName, props, runid) => `
|
||||
const loaderScriptTemplate = (functionName, props, runid) => {
|
||||
const plugins = extractShellApiPlugins(functionName, props);
|
||||
const prefix = plugins.map(packageName => `// @require ${packageName}\n`).join('');
|
||||
return `
|
||||
${prefix}
|
||||
const dbgateApi = require(process.env.DBGATE_API);
|
||||
dbgateApi.initializeApiEnvironment();
|
||||
${requirePluginsTemplate(extractShellApiPlugins(functionName, props))}
|
||||
${requirePluginsTemplate(plugins)}
|
||||
require=null;
|
||||
async function run() {
|
||||
const reader=await ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});
|
||||
const writer=await dbgateApi.collectorWriter({runid: '${runid}'});
|
||||
const writer=await dbgateApi.collectorWriter({runid: ${JSON.stringify(runid)}});
|
||||
await dbgateApi.copyStream(reader, writer);
|
||||
}
|
||||
dbgateApi.runScript(run);
|
||||
`;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
/** @type {import('dbgate-types').OpenedRunner[]} */
|
||||
@@ -172,7 +177,7 @@ module.exports = {
|
||||
byline(subprocess.stderr).on('data', pipeDispatcher('error'));
|
||||
subprocess.on('exit', code => {
|
||||
// console.log('... EXITED', code);
|
||||
this.rejectRequest(runid, { message: 'No data returned, maybe input data source is too big' });
|
||||
this.rejectRequest(runid, { message: 'DBGM-00281 No data returned, maybe input data source is too big' });
|
||||
logger.info({ code, pid: subprocess.pid }, 'DBGM-00016 Exited process');
|
||||
socket.emit(`runner-done-${runid}`, code);
|
||||
this.opened = this.opened.filter(x => x.runid != runid);
|
||||
@@ -196,6 +201,27 @@ module.exports = {
|
||||
// @ts-ignore
|
||||
const { msgtype } = message;
|
||||
if (handleProcessCommunication(message, subprocess)) return;
|
||||
if (msgtype === 'get-volatile-connection') {
|
||||
const connections = require('./connections');
|
||||
// @ts-ignore
|
||||
const conid = message.conid;
|
||||
if (!conid || typeof conid !== 'string') return;
|
||||
const trySend = payload => {
|
||||
if (!subprocess.connected) return;
|
||||
try {
|
||||
subprocess.send(payload);
|
||||
} catch {
|
||||
// child disconnected between the check and the send — ignore
|
||||
}
|
||||
};
|
||||
connections.getCore({ conid }).then(conn => {
|
||||
trySend({ msgtype: 'volatile-connection-response', conid, conn: conn?.unsaved ? conn : null });
|
||||
}).catch(err => {
|
||||
logger.error({ ...extractErrorLogData(err), conid }, 'DBGM-00000 Error resolving volatile connection for child process');
|
||||
trySend({ msgtype: 'volatile-connection-response', conid, conn: null });
|
||||
});
|
||||
return;
|
||||
}
|
||||
this[`handle_${msgtype}`](runid, message);
|
||||
});
|
||||
return _.pick(newOpened, ['runid']);
|
||||
@@ -225,7 +251,7 @@ module.exports = {
|
||||
subprocess.on('exit', code => {
|
||||
console.log('... EXITED', code);
|
||||
logger.info({ code, pid: subprocess.pid }, 'DBGM-00017 Exited process');
|
||||
this.dispatchMessage(runid, `Finished external process with code ${code}`);
|
||||
this.dispatchMessage(runid, `DBGM-00282 Finished external process with code ${code}`);
|
||||
socket.emit(`runner-done-${runid}`, code);
|
||||
if (onFinished) {
|
||||
onFinished();
|
||||
@@ -233,7 +259,7 @@ module.exports = {
|
||||
this.opened = this.opened.filter(x => x.runid != runid);
|
||||
});
|
||||
subprocess.on('spawn', () => {
|
||||
this.dispatchMessage(runid, `Started external process ${command}`);
|
||||
this.dispatchMessage(runid, `DBGM-00283 Started external process ${command}`);
|
||||
});
|
||||
subprocess.on('error', error => {
|
||||
console.log('... ERROR subprocess', error);
|
||||
@@ -279,7 +305,7 @@ module.exports = {
|
||||
if (script.type == 'json') {
|
||||
if (!platformInfo.isElectron) {
|
||||
if (!checkSecureDirectoriesInScript(script)) {
|
||||
return { errorMessage: 'Unallowed directories in script' };
|
||||
return { errorMessage: 'DBGM-00284 Unallowed directories in script' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,10 +325,10 @@ module.exports = {
|
||||
action: 'script',
|
||||
severity: 'warn',
|
||||
detail: script,
|
||||
message: 'Scripts are not allowed',
|
||||
message: 'DBGM-00285 Scripts are not allowed',
|
||||
});
|
||||
|
||||
return { errorMessage: 'Shell scripting is not allowed' };
|
||||
return { errorMessage: 'DBGM-00286 Shell scripting is not allowed' };
|
||||
}
|
||||
|
||||
sendToAuditLog(req, {
|
||||
@@ -312,7 +338,7 @@ module.exports = {
|
||||
action: 'script',
|
||||
severity: 'info',
|
||||
detail: script,
|
||||
message: 'Running JS script',
|
||||
message: 'DBGM-00287 Running JS script',
|
||||
});
|
||||
|
||||
return this.startCore(runid, scriptTemplate(script, false));
|
||||
@@ -327,7 +353,7 @@ module.exports = {
|
||||
async cancel({ runid }) {
|
||||
const runner = this.opened.find(x => x.runid == runid);
|
||||
if (!runner) {
|
||||
throw new Error('Invalid runner');
|
||||
throw new Error('DBGM-00288 Invalid runner');
|
||||
}
|
||||
runner.subprocess.kill();
|
||||
return { state: 'ok' };
|
||||
@@ -353,17 +379,15 @@ module.exports = {
|
||||
async loadReader({ functionName, props }) {
|
||||
if (!platformInfo.isElectron) {
|
||||
if (props?.fileName && !checkSecureDirectories(props.fileName)) {
|
||||
return { errorMessage: 'Unallowed file' };
|
||||
return { errorMessage: 'DBGM-00289 Unallowed file' };
|
||||
}
|
||||
}
|
||||
const prefix = extractShellApiPlugins(functionName)
|
||||
.map(packageName => `// @require ${packageName}\n`)
|
||||
.join('');
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
assertValidShellApiFunctionName(functionName);
|
||||
const runid = crypto.randomUUID();
|
||||
this.requests[runid] = { resolve, reject, exitOnStreamError: true };
|
||||
this.startCore(runid, loaderScriptTemplate(prefix, functionName, props, runid));
|
||||
this.startCore(runid, loaderScriptTemplate(functionName, props, runid));
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
@@ -371,7 +395,7 @@ module.exports = {
|
||||
scriptResult_meta: true,
|
||||
async scriptResult({ script }) {
|
||||
if (script.type != 'json') {
|
||||
return { errorMessage: 'Only JSON scripts are allowed' };
|
||||
return { errorMessage: 'DBGM-00290 Only JSON scripts are allowed' };
|
||||
}
|
||||
|
||||
const promise = new Promise(async (resolve, reject) => {
|
||||
|
||||
@@ -171,7 +171,7 @@ module.exports = {
|
||||
const databasePermissions = await loadDatabasePermissionsFromRequest(req);
|
||||
const res = [];
|
||||
for (const db of opened?.databases ?? []) {
|
||||
const databasePermissionRole = getDatabasePermissionRole(db.id, db.name, databasePermissions);
|
||||
const databasePermissionRole = getDatabasePermissionRole(conid, db.name, databasePermissions);
|
||||
if (databasePermissionRole != 'deny') {
|
||||
res.push({
|
||||
...db,
|
||||
|
||||
@@ -83,6 +83,16 @@ module.exports = {
|
||||
socket.emit(`session-recordset-${sesid}`, { jslid, resultIndex });
|
||||
},
|
||||
|
||||
handle_endrecordset(sesid, props) {
|
||||
const { jslid, rowCount, durationMs } = props;
|
||||
this.dispatchMessage(sesid, {
|
||||
message: `Query returned ${rowCount} rows in ${durationMs} ms`,
|
||||
rowCount,
|
||||
durationMs,
|
||||
jslid,
|
||||
});
|
||||
},
|
||||
|
||||
handle_stats(sesid, stats) {
|
||||
jsldata.notifyChangedStats(stats);
|
||||
},
|
||||
@@ -97,6 +107,12 @@ module.exports = {
|
||||
socket.emit(`session-initialize-file-${jslid}`);
|
||||
},
|
||||
|
||||
handle_changedCurrentDatabase(sesid, props) {
|
||||
const { database } = props;
|
||||
this.dispatchMessage(sesid, `Current database changed to ${database}`);
|
||||
socket.emit(`session-changedb-${sesid}`, { database });
|
||||
},
|
||||
|
||||
handle_ping() {},
|
||||
|
||||
create_meta: true,
|
||||
@@ -182,7 +198,10 @@ module.exports = {
|
||||
});
|
||||
|
||||
logger.info({ sesid, sql }, 'DBGM-00019 Processing query');
|
||||
this.dispatchMessage(sesid, 'Query execution started');
|
||||
this.dispatchMessage(sesid, {
|
||||
message: 'Query execution started',
|
||||
sql,
|
||||
});
|
||||
session.subprocess.send({
|
||||
msgtype: 'executeQuery',
|
||||
sql,
|
||||
@@ -209,6 +228,19 @@ module.exports = {
|
||||
return { state: 'ok' };
|
||||
},
|
||||
|
||||
setIsolationLevel_meta: true,
|
||||
async setIsolationLevel({ sesid, level }) {
|
||||
const session = this.opened.find(x => x.sesid == sesid);
|
||||
if (!session) {
|
||||
throw new Error('Invalid session');
|
||||
}
|
||||
|
||||
logger.info({ sesid, level }, 'DBGM-00315 Setting transaction isolation level');
|
||||
session.subprocess.send({ msgtype: 'setIsolationLevel', level });
|
||||
|
||||
return { state: 'ok' };
|
||||
},
|
||||
|
||||
executeReader_meta: true,
|
||||
async executeReader({ conid, database, sql, queryName, appFolder }) {
|
||||
const { sesid } = await this.create({ conid, database });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
module.exports = {
|
||||
version: '6.0.0-alpha.1',
|
||||
version: '7.0.0-alpha.1',
|
||||
buildTime: '2024-12-01T00:00:00Z'
|
||||
};
|
||||
|
||||
@@ -147,6 +147,7 @@ const shell = require('./shell/index');
|
||||
global.DBGATE_PACKAGES = {
|
||||
'dbgate-tools': require('dbgate-tools'),
|
||||
'dbgate-sqltree': require('dbgate-sqltree'),
|
||||
'dbgate-datalib': require('dbgate-datalib'),
|
||||
};
|
||||
|
||||
if (processArgs.startProcess) {
|
||||
|
||||
@@ -14,6 +14,7 @@ const socket = require('./utility/socket');
|
||||
const connections = require('./controllers/connections');
|
||||
const serverConnections = require('./controllers/serverConnections');
|
||||
const databaseConnections = require('./controllers/databaseConnections');
|
||||
const restConnections = require('./controllers/restConnections');
|
||||
const metadata = require('./controllers/metadata');
|
||||
const sessions = require('./controllers/sessions');
|
||||
const runners = require('./controllers/runners');
|
||||
@@ -267,6 +268,7 @@ function useAllControllers(app, electron) {
|
||||
useController(app, electron, '/auth', auth);
|
||||
useController(app, electron, '/cloud', cloud);
|
||||
useController(app, electron, '/team-files', teamFiles);
|
||||
useController(app, electron, '/rest-connections', restConnections);
|
||||
}
|
||||
|
||||
function setElectronSender(electronSender) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { connectUtility } = require('../utility/connectUtility');
|
||||
const { connectUtility, getRestAuthFromConnection } = require('../utility/connectUtility');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const { pickSafeConnectionInfo } = require('../utility/crypting');
|
||||
const _ = require('lodash');
|
||||
@@ -18,13 +18,39 @@ Platform: ${process.platform}
|
||||
|
||||
function start() {
|
||||
childProcessChecker();
|
||||
process.on('message', async connection => {
|
||||
|
||||
let isWaitingForVolatile = false;
|
||||
|
||||
const handleConnection = async connection => {
|
||||
// @ts-ignore
|
||||
const { requestDbList } = connection;
|
||||
if (handleProcessCommunication(connection)) return;
|
||||
|
||||
try {
|
||||
const driver = requireEngineDriver(connection);
|
||||
const dbhan = await connectUtility(driver, connection, 'app');
|
||||
const connectionChanged = driver?.beforeConnectionSave ? driver.beforeConnectionSave(connection) : connection;
|
||||
if (driver?.databaseEngineTypes?.includes('rest')) {
|
||||
connectionChanged.restAuth = getRestAuthFromConnection(connection);
|
||||
}
|
||||
|
||||
if (!connection.isVolatileResolved) {
|
||||
if (connectionChanged.useRedirectDbLogin) {
|
||||
process.send({
|
||||
msgtype: 'missingCredentials',
|
||||
missingCredentialsDetail: {
|
||||
// @ts-ignore
|
||||
conid: connection._id,
|
||||
redirectToDbLogin: true,
|
||||
keepErrorResponseFromApi: true,
|
||||
},
|
||||
});
|
||||
// Don't exit - wait for volatile connection to be sent
|
||||
isWaitingForVolatile = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const dbhan = await connectUtility(driver, connectionChanged, 'app');
|
||||
let version = {
|
||||
version: 'Unknown',
|
||||
};
|
||||
@@ -45,6 +71,16 @@ function start() {
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on('message', async connection => {
|
||||
// If we're waiting for volatile and receive a new connection, use it
|
||||
if (isWaitingForVolatile) {
|
||||
isWaitingForVolatile = false;
|
||||
await handleConnection(connection);
|
||||
} else {
|
||||
await handleConnection(connection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -234,12 +234,12 @@ async function handleRunOperation({ msgid, operation, useTransaction }, skipRead
|
||||
}
|
||||
}
|
||||
|
||||
async function handleQueryData({ msgid, sql, range }, skipReadonlyCheck = false) {
|
||||
async function handleQueryData({ msgid, sql, range, commandTimeout }, skipReadonlyCheck = false) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
|
||||
const res = await driver.query(dbhan, sql, { range });
|
||||
const res = await driver.query(dbhan, sql, { range, commandTimeout });
|
||||
process.send({ msgtype: 'response', msgid, ...serializeJsTypesForJsonStringify(res) });
|
||||
} catch (err) {
|
||||
process.send({
|
||||
@@ -250,11 +250,11 @@ async function handleQueryData({ msgid, sql, range }, skipReadonlyCheck = false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSqlSelect({ msgid, select }) {
|
||||
async function handleSqlSelect({ msgid, select, commandTimeout }) {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
const dmp = driver.createDumper();
|
||||
dumpSqlSelect(dmp, select);
|
||||
return handleQueryData({ msgid, sql: dmp.s, range: select.range }, true);
|
||||
return handleQueryData({ msgid, sql: dmp.s, range: select.range, commandTimeout }, true);
|
||||
}
|
||||
|
||||
async function handleDriverDataCore(msgid, callMethod, { logName }) {
|
||||
@@ -368,6 +368,107 @@ async function handleSaveTableData({ msgid, changeSet }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMultiCallMethod({ msgid, callList }) {
|
||||
try {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
await driver.invokeMethodCallList(dbhan, callList);
|
||||
|
||||
// for (const change of changeSet.changes) {
|
||||
// if (change.type === 'string') {
|
||||
// await driver.query(dbhan, `SET "${change.key}" "${change.value}"`);
|
||||
// } else if (change.type === 'json') {
|
||||
// await driver.query(dbhan, `JSON.SET "${change.key}" $ '${change.value.replace(/'/g, "\\'")}'`);
|
||||
// } else if (change.type === 'hash') {
|
||||
// if (change.updates && Array.isArray(change.updates)) {
|
||||
// for (const update of change.updates) {
|
||||
// await driver.query(dbhan, `HSET "${change.key}" "${update.key}" "${update.value}"`);
|
||||
|
||||
// if (update.ttl !== undefined && update.ttl !== null && update.ttl !== -1) {
|
||||
// try {
|
||||
// await dbhan.client.call('HEXPIRE', change.key, update.ttl, 'FIELDS', 1, update.key);
|
||||
// } catch (e) {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (change.inserts && Array.isArray(change.inserts)) {
|
||||
// for (const insert of change.inserts) {
|
||||
// await driver.query(dbhan, `HSET "${change.key}" "${insert.key}" "${insert.value}"`);
|
||||
|
||||
// if (insert.ttl !== undefined && insert.ttl !== null && insert.ttl !== -1) {
|
||||
// try {
|
||||
// await dbhan.client.call('HEXPIRE', change.key, insert.ttl, 'FIELDS', 1, insert.key);
|
||||
// } catch (e) {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (change.deletes && Array.isArray(change.deletes)) {
|
||||
// for (const delKey of change.deletes) {
|
||||
// await driver.query(dbhan, `HDEL "${change.key}" "${delKey}"`);
|
||||
// }
|
||||
// }
|
||||
// } else if (change.type === 'zset') {
|
||||
// if (change.updates && Array.isArray(change.updates)) {
|
||||
// for (const update of change.updates) {
|
||||
// await driver.query(dbhan, `ZADD "${change.key}" ${update.score} "${update.member}"`);
|
||||
// }
|
||||
// }
|
||||
// if (change.inserts && Array.isArray(change.inserts)) {
|
||||
// for (const insert of change.inserts) {
|
||||
// await driver.query(dbhan, `ZADD "${change.key}" ${insert.score} "${insert.member}"`);
|
||||
// }
|
||||
// }
|
||||
// if (change.deletes && Array.isArray(change.deletes)) {
|
||||
// for (const delMember of change.deletes) {
|
||||
// await driver.query(dbhan, `ZREM "${change.key}" "${delMember}"`);
|
||||
// }
|
||||
// }
|
||||
// } else if (change.type === 'list') {
|
||||
// if (change.updates && Array.isArray(change.updates)) {
|
||||
// for (const update of change.updates) {
|
||||
// await driver.query(dbhan, `LSET "${change.key}" ${update.index} "${update.value}"`);
|
||||
// }
|
||||
// }
|
||||
// if (change.inserts && Array.isArray(change.inserts)) {
|
||||
// for (const insert of change.inserts) {
|
||||
// await driver.query(dbhan, `RPUSH "${change.key}" "${insert.value}"`);
|
||||
// }
|
||||
// }
|
||||
// } else if (change.type === 'set') {
|
||||
// if (change.inserts && Array.isArray(change.inserts)) {
|
||||
// for (const insert of change.inserts) {
|
||||
// await driver.query(dbhan, `SADD "${change.key}" "${insert.value}"`);
|
||||
// }
|
||||
// }
|
||||
// if (change.deletes && Array.isArray(change.deletes)) {
|
||||
// for (const delValue of change.deletes) {
|
||||
// await driver.query(dbhan, `SREM "${change.key}" "${delValue}"`);
|
||||
// }
|
||||
// }
|
||||
// } else if (change.type === 'stream') {
|
||||
// if (change.inserts && Array.isArray(change.inserts)) {
|
||||
// for (const insert of change.inserts) {
|
||||
// const streamId = insert.id === '*' || !insert.id ? '*' : insert.id;
|
||||
// await driver.query(dbhan, `XADD "${change.key}" ${streamId} value "${insert.value}"`);
|
||||
// }
|
||||
// }
|
||||
// if (change.deletes && Array.isArray(change.deletes)) {
|
||||
// for (const delId of change.deletes) {
|
||||
// await driver.query(dbhan, `XDEL "${change.key}" "${delId}"`);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
process.send({ msgtype: 'response', msgid });
|
||||
} catch (err) {
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
errorMessage: extractErrorMessage(err, 'Error saving Redis data'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSqlPreview({ msgid, objects, options }) {
|
||||
await waitStructure();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
@@ -501,6 +602,7 @@ const messageHandlers = {
|
||||
schemaList: handleSchemaList,
|
||||
executeSessionQuery: handleExecuteSessionQuery,
|
||||
evalJsonScript: handleEvalJsonScript,
|
||||
multiCallMethod: handleMultiCallMethod,
|
||||
// runCommand: handleRunCommand,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const connectProcess = require('./connectProcess');
|
||||
const databaseConnectionProcess = require('./databaseConnectionProcess');
|
||||
const serverConnectionProcess = require('./serverConnectionProcess');
|
||||
const restConnectionProcess = require('./restConnectionProcess');
|
||||
const sessionProcess = require('./sessionProcess');
|
||||
const jslDatastoreProcess = require('./jslDatastoreProcess');
|
||||
const sshForwardProcess = require('./sshForwardProcess');
|
||||
@@ -9,6 +10,7 @@ module.exports = {
|
||||
connectProcess,
|
||||
databaseConnectionProcess,
|
||||
serverConnectionProcess,
|
||||
restConnectionProcess,
|
||||
sessionProcess,
|
||||
jslDatastoreProcess,
|
||||
sshForwardProcess,
|
||||
|
||||
7
packages/api/src/proc/restConnectionProcess.js
Normal file
7
packages/api/src/proc/restConnectionProcess.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
|
||||
function start() {
|
||||
childProcessChecker();
|
||||
}
|
||||
|
||||
module.exports = { start };
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user