mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 06:14:59 +00:00
Compare commits
1789 Commits
codex/impl
...
codex/crea
| Author | SHA1 | Date | |
|---|---|---|---|
| 477641a3d8 | |||
| 81f7fa7c81 | |||
| 4a2d1dbb2d | |||
| 04df1bca51 | |||
| b9258d7420 | |||
| f16e2484d1 | |||
| 0197826b57 | |||
| b77ff225c5 | |||
| 1b801cbeaa | |||
| db2ba3d034 | |||
| aad3ea72f3 | |||
| 05c7b2fb66 | |||
| 60818c5be4 | |||
| ef04d37aa6 | |||
| f21d1c7b0f | |||
| e6c8a3ae7d | |||
|
|
1897c5a49a | ||
| a525791172 | |||
|
|
69dab70d7f | ||
| 1f61062313 | |||
| 90cea3567f | |||
| 6f99a2e670 | |||
| d414199f9b | |||
|
|
187811d4c1 | ||
| e038d5db8a | |||
| a700d40b7b | |||
| 61115865c6 | |||
|
|
3ff46f2d6b | ||
| 706583c143 | |||
|
|
3f31970706 | ||
| 6ddea3bdce | |||
|
|
1f236bca36 | ||
| 72e367c209 | |||
|
|
149c0f8715 | ||
|
|
320aa270d4 | ||
|
|
025b7d774f | ||
|
|
01b639b1e0 | ||
|
|
49cfffbb2f | ||
| f86e637da1 | |||
|
|
759ec80a44 | ||
|
|
0bd8c7c2a7 | ||
| 147c503ac8 | |||
|
|
3e0b8de1b6 | ||
|
|
92496b5620 | ||
|
|
9a602cafb6 | ||
| 89e189a288 | |||
| 152f0120fa | |||
|
|
b173afae71 | ||
| 8e713e5ff8 | |||
|
|
7e3851c93b | ||
|
|
6e7e068b12 | ||
|
|
58fe048857 | ||
|
|
1f0eb05b79 | ||
| cddd61f3ef | |||
|
|
85890a9c02 | ||
|
|
eb0289d593 | ||
|
|
9a757fd5df | ||
|
|
bf674e0da4 | ||
|
|
2e2a0f8218 | ||
|
|
97c659673b | ||
| 78e62b4bce | |||
|
|
2ffab4b4ba | ||
|
|
00de4dca23 | ||
| da04432b06 | |||
|
|
5b49332c2f | ||
|
|
208b2ec07a | ||
|
|
1a3ee146c1 | ||
|
|
a4169bd56e | ||
|
|
ba19f0b585 | ||
| f550696332 | |||
|
|
1aa625327b | ||
|
|
add494f26c | ||
| 7fa453b670 | |||
|
|
c7058874e0 | ||
|
|
7538a1b66e | ||
|
|
a3cb9c074f | ||
|
|
c5ebdfe45a | ||
|
|
48bf3bcbc4 | ||
|
|
d81ba627f5 | ||
| 1ad651d453 | |||
|
|
af45e9694d | ||
|
|
df795327f8 | ||
|
|
20e9472bb3 | ||
| a4106eb9d8 | |||
|
|
8623bfc0bd | ||
|
|
84e91569c8 | ||
|
|
73f34d0a9e | ||
|
|
0f76c47f93 | ||
| 015a5c5533 | |||
| 66e8bb09fa | |||
| 130f40cb3d | |||
| 0a6db264fc | |||
| 2f6d54d255 | |||
| df21275872 | |||
| 19fe90cf65 | |||
| 26d41a6ce8 | |||
|
|
a144295709 | ||
|
|
ba3f8c670b | ||
| 0f4754f598 | |||
|
|
ea14a170da | ||
|
|
9246584d4a | ||
|
|
a6e32159af | ||
|
|
d0835f0cd4 | ||
|
|
c128eb02e7 | ||
|
|
7e0b05047e | ||
|
|
96ee74e6ef | ||
|
|
d271cc5643 | ||
| 53e33f07b5 | |||
|
|
d919572357 | ||
|
|
801e446ff2 | ||
|
|
16d01087cb | ||
|
|
307f53d2a2 | ||
|
|
2cd0e9c517 | ||
|
|
12d447ce26 | ||
| cb48605fbd | |||
|
|
7a1b44ba3f | ||
|
|
ce1ec75502 | ||
|
|
1e1870c93c | ||
|
|
726f0bfc7b | ||
|
|
3047d6b881 | ||
|
|
dfefe916c5 | ||
|
|
9667e55324 | ||
|
|
f19d04410d | ||
| 39cf1bacfa | |||
|
|
878f06b8f6 | ||
|
|
b323a14694 | ||
|
|
ab32481bf5 | ||
|
|
b578a8371d | ||
|
|
9f37692079 | ||
|
|
825250b231 | ||
|
|
38a61fbc11 | ||
|
|
544dceba62 | ||
|
|
9c675f70dd | ||
|
|
651083ec72 | ||
|
|
c19174753e | ||
| 306380aa89 | |||
|
|
0a2df8ef35 | ||
|
|
e3d4bb59f7 | ||
|
|
fa4b27a0f8 | ||
|
|
3831e6cca9 | ||
|
|
ec5159b103 | ||
| 8ceff865be | |||
|
|
579da82588 | ||
|
|
2c59bf40f0 | ||
|
|
ee834c1b42 | ||
| 3436c95683 | |||
|
|
d8e60ffb1d | ||
|
|
740058a09c | ||
|
|
32bd4d4a53 | ||
|
|
10bec9ae20 | ||
|
|
38b359ad74 | ||
|
|
bcf93eb773 | ||
|
|
da872d32dd | ||
|
|
5cf8d9d6fd | ||
| 61dc0fb79d | |||
|
|
c68305ed90 | ||
|
|
942b8792d8 | ||
|
|
af2a59ee6a | ||
|
|
28a3ad1d6e | ||
| 73f8470388 | |||
|
|
492f29c48d | ||
|
|
a63c0ece19 | ||
|
|
1fe394f106 | ||
|
|
d0f851a59e | ||
|
|
21db5475b7 | ||
|
|
8608df1d96 | ||
|
|
413392ee69 | ||
| 2eb4141c49 | |||
|
|
258dfa07d7 | ||
|
|
5172de6693 | ||
|
|
00a49e4243 | ||
|
|
7b0dc3963d | ||
| b418fa2203 | |||
| 3d2fc07026 | |||
| 42446ef255 | |||
| 4b9bab67cc | |||
| 76a667f259 | |||
| 9284b9a67b | |||
| 3bb754dd72 | |||
| 016cd662bf | |||
| 7eee87ec90 | |||
| 6d8b23e7a6 | |||
| 445f4f4028 | |||
| 8e5930cd44 | |||
| 693989bba8 | |||
| 92c280b0e6 | |||
| 6c8e7002cd | |||
| 4caf9e2ae9 | |||
| 25908192ef | |||
| 5f74c3b308 | |||
| ae74159fdb | |||
| 8fbb711078 | |||
|
|
5c9a2bc49f | ||
|
|
2127cda63a | ||
|
|
fb2fdcda5b | ||
|
|
bd0164b52f | ||
|
|
16635c5eb7 | ||
|
|
b1124d265b | ||
| cd48a0a809 | |||
|
|
8c2983f5af | ||
|
|
3139b6570c | ||
|
|
016cdde654 | ||
|
|
2c2a7d06d1 | ||
|
|
8e24a1a0fb | ||
| 8fcc36ba69 | |||
|
|
3cd8e2abd3 | ||
|
|
04b6f7de3f | ||
|
|
03b83b1d7d | ||
|
|
7251e6a75e | ||
|
|
b59fb7fa77 | ||
|
|
10b3e9f8dd | ||
| 9c2113157f | |||
|
|
43028cb122 | ||
|
|
3d23c02eb5 | ||
|
|
a061fb3241 | ||
|
|
6ac9949e8a | ||
|
|
fcbc4b0661 | ||
| 173391d98d | |||
|
|
61d0a65c40 | ||
|
|
0f3a2c50c6 | ||
|
|
1b191591d5 | ||
|
|
179c3f9d29 | ||
|
|
1767a1729b | ||
|
|
46c3100a83 | ||
| ea17fa3dbb | |||
|
|
91925dcb19 | ||
|
|
0b424cedb4 | ||
|
|
0548d439d6 | ||
|
|
1b0439d132 | ||
|
|
524360db5f | ||
|
|
7be7449dc2 | ||
| 5e583199a3 | |||
|
|
bd434e2b3e | ||
|
|
be32858ab4 | ||
|
|
8e2153cb19 | ||
|
|
79bed9998d | ||
|
|
0d2908c69e | ||
|
|
189f3ea4d8 | ||
| c398a84496 | |||
|
|
d726e90aee | ||
|
|
6c797e4361 | ||
|
|
e13f4a393d | ||
|
|
1045d55efa | ||
|
|
85f7e2c265 | ||
| ece19fee60 | |||
|
|
7e424a28bb | ||
|
|
e44c480fba | ||
|
|
dfa3003e3e | ||
|
|
10ae52917a | ||
|
|
297f1cacad | ||
|
|
2d9d9bab50 | ||
| dafe170c88 | |||
|
|
00cd3c7ec4 | ||
|
|
1eaa4b5e97 | ||
|
|
7c5f3bbe06 | ||
|
|
7a4cc52e67 | ||
| e272ae9a82 | |||
| 799ab672de | |||
| b67f40d018 | |||
| ab04703287 | |||
| 19a9a8ff06 | |||
| eb11758b77 | |||
| f98d086297 | |||
|
|
978aefdaf2 | ||
|
|
3d824dec79 | ||
|
|
fdaeda09c8 | ||
|
|
cf6d3b6795 | ||
| 8055b2a435 | |||
|
|
b1f7a3126b | ||
|
|
1a5f073e79 | ||
|
|
994e92e882 | ||
|
|
28e253e00d | ||
|
|
09e5c42585 | ||
|
|
4a38a9bd93 | ||
|
|
803b92089b | ||
| 388430b8da | |||
|
|
e8095662b4 | ||
|
|
617354d603 | ||
|
|
97a4d9892f | ||
|
|
77ab4af0cc | ||
|
|
b453ec413d | ||
|
|
81a26a9765 | ||
|
|
edbe567933 | ||
| 3ae263a842 | |||
|
|
8f7a91a13d | ||
|
|
f8de7317f5 | ||
|
|
8ec41f87bd | ||
|
|
5de31cd740 | ||
|
|
1a421ea2da | ||
|
|
b4650d1e91 | ||
|
|
882f9447ef | ||
|
|
182faa602f | ||
| de75196751 | |||
|
|
da683d3ce8 | ||
|
|
ea27223dbe | ||
|
|
ee79514830 | ||
|
|
167e3910df | ||
|
|
5df80c1a98 | ||
|
|
e34a5e3c32 | ||
| a59a046e30 | |||
|
|
e6828b054b | ||
|
|
6bd619309b | ||
|
|
9fa195d653 | ||
|
|
8729a003c6 | ||
|
|
dd4be80edc | ||
| ad2aff6f1c | |||
|
|
b953043daf | ||
|
|
a4766dd2a3 | ||
|
|
ff5f502a0b | ||
|
|
d0d63fc1eb | ||
|
|
4ffcdbc827 | ||
| 7d0a1ad947 | |||
| 41c6948a7a | |||
| 830a90522b | |||
| 9f2e5be282 | |||
| ed74752bad | |||
| edcb319278 | |||
| 6f92d51ffe | |||
| 4fb7de4693 | |||
| 627befdd03 | |||
| a5ad6b4a21 | |||
|
|
48ce25529f | ||
| 9a243a24b4 | |||
|
|
680b5329e2 | ||
|
|
0c3dbe8dfe | ||
|
|
7629520f2c | ||
|
|
3c5986b881 | ||
|
|
8845827297 | ||
|
|
aa822b46fc | ||
|
|
f2899ccfcf | ||
|
|
867142258e | ||
|
|
f996c0eaf6 | ||
| 33af77f3f7 | |||
|
|
95429d61fa | ||
|
|
bbb608a745 | ||
|
|
51be171545 | ||
|
|
402e71fd83 | ||
| 3b6194072e | |||
|
|
156d55b90f | ||
|
|
8adf7d791f | ||
| 007e680d5c | |||
| 31db6da23f | |||
|
|
3711e0c9b9 | ||
|
|
4e2ed82164 | ||
|
|
a263d43571 | ||
|
|
8a7558d038 | ||
|
|
f453ed7564 | ||
|
|
0ab852d227 | ||
|
|
04239f7861 | ||
| 41f7239884 | |||
| 7e48f06e22 | |||
| 2af4d04ab8 | |||
| 814dc5a9e0 | |||
| c603784d44 | |||
| c04431a5e2 | |||
| ecbf0f5ce2 | |||
| 57d9eb62ac | |||
| 640b4b3744 | |||
| ed3c824cf4 | |||
| 8873a577e9 | |||
| b1148af3b0 | |||
| 33751e1515 | |||
| 1a60d3b767 | |||
| d30fc6c87a | |||
| 438435f3b6 | |||
| 7dec5f0fac | |||
| 87501588ad | |||
| e43412f3fc | |||
| ee5790c718 | |||
| 4b0d8ae6e8 | |||
| 8c7330db7c | |||
| 0952a1e4bd | |||
| feac579bef | |||
| 9dab4999c0 | |||
| c31bc5da7f | |||
| 3ec49cfe8c | |||
| 1db0e0fd3b | |||
| ef709d47c0 | |||
| e69b166046 | |||
| 753f530272 | |||
| 1c219059ef | |||
| 7b0df71e89 | |||
| 58eda8dc1e | |||
| 37753492b4 | |||
| 40ec511651 | |||
| 6142269692 | |||
| ee367920be | |||
| 6992c3a650 | |||
| 8c8f8cce8a | |||
| e689b1fb62 | |||
| 7a82c07bfe | |||
| 79f854c7a5 | |||
| d20ff2e85e | |||
| 1a175e8030 | |||
| af80a8e761 | |||
| 59a473dfb8 | |||
| 45a5fd30f6 | |||
| 8e8c122470 | |||
| 2d5cba276a | |||
| 97d7e8417d | |||
| e22b801f37 | |||
| 9eea4c29f4 | |||
| 36af0354e8 | |||
| 5e7fa66ec6 | |||
| 04761fa324 | |||
| 55a56ac604 | |||
| 51495bfe22 | |||
| 2f8da8872e | |||
| feb148c908 | |||
| 357dd5e106 | |||
| 501af9370d | |||
| 7acce2e9d4 | |||
|
|
1c0afad253 | ||
|
|
a178f13b66 | ||
|
|
e29dfaffa8 | ||
|
|
ab5ce401af | ||
|
|
bb0d1bf6f8 | ||
|
|
8af055193a | ||
|
|
c520e13f9d | ||
|
|
658e2ea608 | ||
|
|
ec27cea423 | ||
|
|
268208311f | ||
|
|
0f6aa918f8 | ||
|
|
5e5ba70b2e | ||
|
|
1cfa6750f2 | ||
|
|
342d2d772d | ||
|
|
464acfce01 | ||
|
|
3abd9a9236 | ||
|
|
3d4b73401e | ||
|
|
101f83b20a | ||
|
|
c23358c81b | ||
|
|
a7069e2863 | ||
|
|
f9c5a2e5be | ||
|
|
97e0bea48c | ||
|
|
5f73d17e39 | ||
|
|
2b3c4f4b88 | ||
|
|
613e79c75b | ||
|
|
2a061aa778 | ||
|
|
eb60373232 | ||
|
|
71da8aceb6 | ||
|
|
ec8c920cb2 | ||
|
|
4e9f54abf5 | ||
|
|
7dfd746b84 | ||
|
|
89c8824aff | ||
|
|
7645d35198 | ||
|
|
f951890579 | ||
|
|
2b590b8a03 | ||
|
|
f4416f7da9 | ||
|
|
0b51b9926a | ||
|
|
a1bcf553d1 | ||
|
|
8378cd5fab | ||
|
|
42ac29ca8f | ||
|
|
9c6ee8ccaf | ||
|
|
d661166ea4 | ||
|
|
0de2d1d06b | ||
|
|
c2d9fd4eb2 | ||
|
|
801f7befa1 | ||
|
|
feebdae750 | ||
|
|
c363d8a0d8 | ||
|
|
cc318c0ecc | ||
|
|
0abec7226d | ||
|
|
9ab04807af | ||
|
|
8e7cbf10d2 | ||
|
|
f33e03dcec | ||
|
|
2764018841 | ||
|
|
52775236cc | ||
|
|
2a0e104fbd | ||
|
|
03c701ea13 | ||
|
|
9a5bea4339 | ||
|
|
06e85adaf6 | ||
|
|
a03475a2b8 | ||
|
|
890c222281 | ||
|
|
9e3af4f489 | ||
|
|
2c15d680b2 | ||
|
|
aa0a21324a | ||
|
|
11ed39bd93 | ||
|
|
6d2123f5a6 | ||
|
|
2145b610a5 | ||
|
|
d1c65f022c | ||
|
|
292d628e56 | ||
|
|
85d4d79e99 | ||
|
|
b0925bcb3d | ||
|
|
80268504ca | ||
|
|
dcdb52de6f | ||
|
|
5dad01c4fe | ||
|
|
d676b3ec04 | ||
|
|
fca1440adc | ||
|
|
faefd4d1a2 | ||
|
|
1a6accf258 | ||
|
|
d0a2dea2f8 | ||
|
|
956fa05dfb | ||
|
|
509b68446f | ||
|
|
dc291628ce | ||
|
|
6f85d1ad98 | ||
|
|
067767ef39 | ||
|
|
3a13029dad | ||
|
|
11926a9a54 | ||
|
|
7f28bee822 | ||
|
|
e2c91d4103 | ||
|
|
939b243e97 | ||
|
|
d6b7491ca0 | ||
|
|
96f8607c46 | ||
|
|
412bc9b2f4 | ||
|
|
1b9d476816 | ||
|
|
2f66603564 | ||
|
|
2e4c9c9ed4 | ||
|
|
136e27d5cb | ||
|
|
8c0a51af4a | ||
|
|
f22beffd05 | ||
|
|
4eaa2c0904 | ||
|
|
62ad80d7ec | ||
|
|
5bf68a2746 | ||
|
|
693adc16ad | ||
|
|
bc15734782 | ||
|
|
e72c27fae5 | ||
|
|
f6038ee582 | ||
|
|
ab503c1f0e | ||
|
|
5f806d9340 | ||
|
|
bfcd6d19a1 | ||
|
|
dea7d601d7 | ||
|
|
a6ab801a57 | ||
|
|
89455d041c | ||
|
|
d0f27f7dd6 | ||
|
|
b96c6f8faf | ||
|
|
43a8cb6c72 | ||
|
|
ac69b6f3e9 | ||
|
|
6e71c5f789 | ||
|
|
a96ea66367 | ||
|
|
2e55cb5c21 | ||
|
|
5b648d5bb2 | ||
|
|
1089460a1e | ||
|
|
984386e111 | ||
|
|
8c83cab732 | ||
|
|
4ecc520ce2 | ||
|
|
84ce96904b | ||
|
|
4fe946e364 | ||
|
|
121dbcf21c | ||
|
|
62a83ced87 | ||
|
|
836cd34a7e | ||
|
|
6369105342 | ||
|
|
e5a3809b97 | ||
|
|
67919a0930 | ||
|
|
553e910a03 | ||
|
|
6a7f4b4a9c | ||
|
|
2db0bf49ed | ||
|
|
6ac998b4f8 | ||
|
|
97536ed836 | ||
|
|
387531d8c9 | ||
|
|
5a18ea4dab | ||
|
|
4d8b26e149 | ||
|
|
9c90f50a43 | ||
|
|
975b2b679c | ||
|
|
838099da92 | ||
|
|
721022bb7d | ||
|
|
198cd22961 | ||
|
|
6424b53acb | ||
|
|
24b06b5536 | ||
|
|
e61b1f47ec | ||
|
|
f2d9e65fc7 | ||
|
|
76d9aeb1c9 | ||
|
|
e6ab8e3ac3 | ||
|
|
2700bb6793 | ||
|
|
30d04f6a5a | ||
| 9ecd169272 | |||
|
|
131c5e991c | ||
|
|
e28d806ebc | ||
|
|
f38a0d8b79 | ||
|
|
db6c2f2516 | ||
|
|
c15be14a91 | ||
|
|
f2ebee76cf | ||
|
|
d9932718d9 | ||
|
|
180f3a47ed | ||
|
|
434aa49e54 | ||
|
|
bc2d17eda6 | ||
|
|
3c5e552780 | ||
|
|
b3976b7a5a | ||
|
|
46f0603854 | ||
|
|
b6b379efdc | ||
|
|
afbeab8feb | ||
|
|
06f73d3996 | ||
|
|
5dc72d5bc3 | ||
|
|
f03bd92d84 | ||
|
|
554b202788 | ||
|
|
60c5f40b99 | ||
|
|
a0261afede | ||
|
|
77c0536b6d | ||
|
|
84392b4c12 | ||
|
|
6b8cbf23d7 | ||
|
|
c7b71dc22e | ||
|
|
cbc022ddcc | ||
|
|
af86bc87f5 | ||
|
|
68f8367e13 | ||
|
|
d69097f95e | ||
|
|
0d23b7684b | ||
|
|
483169a680 | ||
|
|
f629c0918f | ||
|
|
4f41eeb47d | ||
|
|
bc6fe72576 | ||
|
|
a72ffff6e2 | ||
|
|
1ebcd93f7e | ||
|
|
49908089d7 | ||
|
|
170b58cacb | ||
|
|
9431c0154b | ||
|
|
2c8b881f14 | ||
|
|
33b312b58d | ||
|
|
2f2ec12f61 | ||
|
|
f8f5f0773e | ||
|
|
bc8fe80b20 | ||
|
|
9923aa1b86 | ||
|
|
dc91e6be66 | ||
|
|
b5daaaf991 | ||
|
|
2fcc07175f | ||
|
|
727f5bd4af | ||
|
|
cb0c635f3f | ||
|
|
446b211218 | ||
|
|
68eb0f551b | ||
|
|
1dce64ecfc | ||
|
|
9d166ecae5 | ||
|
|
f3738fec7c | ||
|
|
d2864b2fb9 | ||
|
|
c2649a46fc | ||
|
|
15665fdf4b | ||
|
|
088b846887 | ||
|
|
e33e6e72cb | ||
|
|
e900b19f5d | ||
|
|
c781f5c72c | ||
|
|
79fde8698b | ||
|
|
5f623911cb | ||
|
|
d7351745ab | ||
|
|
d0b06c5cb0 | ||
|
|
371a8d3252 | ||
|
|
7df28dfa26 | ||
|
|
e1595b0936 | ||
|
|
f8a92529b3 | ||
|
|
c6a6cce84d | ||
|
|
d43761e11a | ||
|
|
ce650fe83e | ||
|
|
6d47884ba4 | ||
|
|
66b5e9430f | ||
|
|
f0a0f924e5 | ||
|
|
b71882536a | ||
|
|
ff339fbf27 | ||
|
|
dc98ecf05c | ||
|
|
838e1b551a | ||
|
|
bf3c32bbda | ||
|
|
3fae30f748 | ||
|
|
5261109221 | ||
|
|
953cff09f9 | ||
|
|
899dec7323 | ||
|
|
25ebb76bca | ||
|
|
a911b3cc34 | ||
|
|
4411b6d5bd | ||
|
|
9e8736cc88 | ||
|
|
097f0db27c | ||
|
|
667e92a20b | ||
|
|
048f3c7e19 | ||
|
|
009a985896 | ||
|
|
60d5d04305 | ||
|
|
36a2a61ad6 | ||
|
|
dc1109bf13 | ||
|
|
2814e11896 | ||
|
|
c481da5ff0 | ||
|
|
d3b7e0ae9c | ||
|
|
3dcb8ac8f2 | ||
|
|
887ceec0a4 | ||
|
|
93af08b8f1 | ||
|
|
b807de3f34 | ||
|
|
fcc593c155 | ||
|
|
d035c5ad05 | ||
|
|
6c9f0f3c3e | ||
|
|
b3288fd8f3 | ||
|
|
3cbcf51c58 | ||
|
|
9e8877f49b | ||
|
|
a84ab980c9 | ||
|
|
6c6de0379d | ||
|
|
8b201adf7a | ||
|
|
0d61490faf | ||
|
|
bfce6f6f84 | ||
|
|
986352d07b | ||
|
|
5180d48c1d | ||
|
|
d476ffc4aa | ||
|
|
211f267993 | ||
|
|
ac53a10698 | ||
|
|
05ece8ac26 | ||
|
|
f5de11a0d9 | ||
|
|
b6a4a87877 | ||
|
|
43477aceae | ||
|
|
af2f9ad2fb | ||
|
|
d572f86056 | ||
|
|
7d67e69baa | ||
|
|
288413aaaa | ||
|
|
27c1c37450 | ||
|
|
c3c4e134da | ||
|
|
78b9ff896f | ||
|
|
3e1e351d56 | ||
|
|
50dbe18445 | ||
|
|
5660ada656 | ||
|
|
1f90a2fcfb | ||
|
|
432974527f | ||
|
|
6f7a26ce9f | ||
|
|
ecde3fabe1 | ||
|
|
3ea1fe79bf | ||
|
|
7277d5b17e | ||
|
|
4d8fbc22e1 | ||
|
|
9583507b31 | ||
|
|
770ffeb424 | ||
|
|
cdb0abe84f | ||
|
|
9f604e9c51 | ||
|
|
a11dbab933 | ||
|
|
c4f39092c0 | ||
|
|
d789c65d89 | ||
|
|
1a83effa1e | ||
|
|
9667b207b1 | ||
|
|
9e72e6421d | ||
|
|
9d5d346f44 | ||
|
|
9d2d26299c | ||
|
|
4b007e420c | ||
|
|
8a67e25872 | ||
|
|
d5a2bf3efb | ||
|
|
0be3dc9a3b | ||
|
|
fe04980897 | ||
|
|
80ab2b4dbf | ||
|
|
fe17aac9d7 | ||
|
|
15347dc2ef | ||
|
|
613da20492 | ||
|
|
f5a665981e | ||
|
|
dbe841996c | ||
|
|
e31fae4ed0 | ||
|
|
52d2c09a6e | ||
|
|
3d41ecad70 | ||
|
|
e6c65f2590 | ||
|
|
cb98628197 | ||
|
|
39c6002a0d | ||
|
|
fcd4791d18 | ||
|
|
3b97d281c3 | ||
| 34c00c1520 | |||
| eb2182a60a | |||
|
|
7d8eb2cbe9 | ||
|
|
3abc2bb26c | ||
|
|
cca9ccd6c9 | ||
|
|
7575f15158 | ||
|
|
92f5aaf22b | ||
|
|
4f07b39431 | ||
|
|
b0e0de6021 | ||
| 6f51de2ae1 | |||
|
|
54a168edf5 | ||
|
|
5f3522bf42 | ||
| 79a00e49fa | |||
| e9d59f34dd | |||
| ed17b54d7a | |||
|
|
37cf528703 | ||
|
|
d90eaf3fee | ||
|
|
756b63ba82 | ||
|
|
a3f50920e6 | ||
|
|
05ff9f30b3 | ||
|
|
b55bc9aecc | ||
|
|
16597cf800 | ||
|
|
4407d26052 | ||
|
|
ed64285e83 | ||
|
|
de79d8ac40 | ||
|
|
7039191ec6 | ||
|
|
bd47f8d9e8 | ||
|
|
3e31279046 | ||
|
|
1bf9f8425a | ||
|
|
0d39ba0f6e | ||
|
|
95195cf9b6 | ||
|
|
dee9ff74e7 | ||
|
|
7978af18cf | ||
|
|
5003c2cf2a | ||
|
|
2f1c10a1fd | ||
|
|
0d913c881d | ||
|
|
f3dde4d9cd | ||
|
|
3c7bbe856e | ||
|
|
ae8f7f911f | ||
|
|
5e96ccab29 | ||
|
|
52da51f5a9 | ||
|
|
eaea809b88 | ||
|
|
470611d878 | ||
|
|
b408400c78 | ||
|
|
6a894815f8 | ||
|
|
2ecf3f9df9 | ||
|
|
b783166b3d | ||
|
|
b8e1a68712 | ||
|
|
d0ecbf2f14 | ||
|
|
44d6eb90fb | ||
|
|
1fb79cb170 | ||
|
|
c85dbe59c6 | ||
|
|
e7126be651 | ||
|
|
675b1ade21 | ||
|
|
b806c38653 | ||
|
|
691e7362aa | ||
|
|
ee337f71f2 | ||
|
|
83dd01f2c7 | ||
|
|
eb67f01bf4 | ||
|
|
1a4d7bf43f | ||
|
|
352ad38b48 | ||
|
|
296b3a8b4c | ||
|
|
ec55b13f25 | ||
|
|
c8be2877c1 | ||
|
|
daa6b96c3a | ||
|
|
c5f716ff7a | ||
|
|
3d7e38bd50 | ||
|
|
92f640fcf0 | ||
|
|
fceea6e3f1 | ||
|
|
f918ab15ad | ||
|
|
5b94f5be8c | ||
|
|
2b4e923e5d | ||
|
|
51f60f5500 | ||
|
|
d6a4c538ee | ||
|
|
cd2c8aaad9 | ||
|
|
a94eda5068 | ||
| 69f5598dcd | |||
|
|
8d3522e482 | ||
|
|
d80a5a6ee2 | ||
|
|
223167d438 | ||
|
|
6f0efbc7d0 | ||
| 7d67accd19 | |||
|
|
6b9c14a813 | ||
|
|
56a7baedbb | ||
| 184b066b1b | |||
|
|
4314aadfdb | ||
|
|
9946382ad0 | ||
|
|
aa5d14192b | ||
|
|
3f2ed527fb | ||
|
|
b918592800 | ||
|
|
d4d31c4c63 | ||
|
|
39b404c885 | ||
|
|
a72b44226c | ||
|
|
84eead94be | ||
|
|
426e1e4233 | ||
|
|
96d7a0d6e6 | ||
|
|
2754548a04 | ||
|
|
85aa69d924 | ||
|
|
f12bee8d6d | ||
|
|
f272db3d9d | ||
|
|
dbb97f2501 | ||
|
|
456b126beb | ||
|
|
806421cd7a | ||
|
|
081239cb10 | ||
|
|
031cb259af | ||
|
|
199a04954f | ||
|
|
06307fd3b3 | ||
|
|
0e7c857d64 | ||
|
|
5d3c79d40f | ||
|
|
0a4d45b58d | ||
|
|
e2dc93846b | ||
|
|
6cf1f34c3c | ||
|
|
fa82944ecb | ||
|
|
4fccef6616 | ||
|
|
f4590bbd8b | ||
|
|
79430f81f1 | ||
|
|
1fef2497ea | ||
|
|
0a3ba34f68 | ||
|
|
7c0b2ee420 | ||
|
|
f00a406093 | ||
|
|
e33033ec12 | ||
|
|
051b2acfd3 | ||
|
|
81c53ea632 | ||
|
|
9a0978536d | ||
|
|
e3f16dcdaf | ||
|
|
46c03c6d6e | ||
|
|
c389eb27f5 | ||
|
|
7040d78f96 | ||
|
|
d4dab550b1 | ||
|
|
2238a8632b | ||
|
|
aece66d9d4 | ||
|
|
2d6a89b2cc | ||
|
|
9be16fb0d2 | ||
|
|
cd3255a94e | ||
|
|
43bde59cda | ||
|
|
8ab2e225ed | ||
|
|
1b70cc0834 | ||
|
|
9b378c4d24 | ||
|
|
1b74231da3 | ||
|
|
5c55a2e228 | ||
|
|
897075c936 | ||
|
|
ddefed4139 | ||
|
|
ff5ecbbee8 | ||
|
|
aad33eaaa0 | ||
|
|
954857639f | ||
|
|
0c4ff5fc8c | ||
|
|
806b7b2a0a | ||
|
|
1ca5f98191 | ||
|
|
6657eb7e25 | ||
|
|
9713c5c144 | ||
|
|
3ae47685c9 | ||
|
|
a153767d1c | ||
|
|
fe4c2eb997 | ||
|
|
79f51288a9 | ||
|
|
bb8999bdca | ||
|
|
eabc3fcfef | ||
|
|
d5d3db5537 | ||
|
|
2cf477b495 | ||
|
|
932ddc2861 | ||
|
|
cc0ce484ed | ||
|
|
d00e1c3e78 | ||
|
|
592867f324 | ||
|
|
de88dc6545 | ||
|
|
2e01c51fab | ||
|
|
a203f36a2c | ||
|
|
8f923e61e3 | ||
|
|
61ba630e42 | ||
|
|
88a5916d09 | ||
|
|
004f4570fe | ||
|
|
78cda0f97c | ||
|
|
d8b75d7591 | ||
|
|
ea61937239 | ||
|
|
c6d229e653 | ||
|
|
8e5b4dcd57 | ||
|
|
acbd9db15c | ||
|
|
9de87b8060 | ||
|
|
d344223d95 | ||
|
|
61b8f438c4 | ||
|
|
e1d7c14947 | ||
|
|
a07b9f8b60 | ||
|
|
1831c22957 | ||
|
|
1ebe3baa91 | ||
|
|
0541368948 | ||
|
|
ea389f1b20 | ||
|
|
c379198e99 | ||
|
|
bfaa168702 | ||
|
|
3ff6f2b779 | ||
|
|
d9369d3137 | ||
|
|
797ef925b9 | ||
|
|
ed124d3241 | ||
|
|
83364db727 | ||
|
|
a33ec831ee | ||
|
|
1fd0873cb8 | ||
|
|
087a9d2526 | ||
|
|
dfcc6bdace | ||
|
|
1ddc6d9bd5 | ||
|
|
b69f75153b | ||
|
|
f9a64399ec | ||
|
|
8a7ec74689 | ||
|
|
96bc2b0c38 | ||
|
|
16d0d21bee | ||
|
|
0041224444 | ||
|
|
8baea50f31 | ||
|
|
072327e8b9 | ||
|
|
52d94f7a02 | ||
|
|
3f654ef2a6 | ||
|
|
2f1ea1427a | ||
|
|
b735f71c2e | ||
|
|
4d9f1bb2ee | ||
|
|
e51cc66ee5 | ||
|
|
78f8b74de9 | ||
|
|
b20432cdfc | ||
|
|
076005ede3 | ||
|
|
66c8c7b6c0 | ||
|
|
24ed495f14 | ||
|
|
b4f42c87dc | ||
|
|
b316d23b55 | ||
|
|
2b63cd84f0 | ||
|
|
89d1e9e138 | ||
|
|
63e739396d | ||
|
|
5aa98340e3 | ||
|
|
a6c8fbd165 | ||
|
|
c939eff626 | ||
|
|
c00887538d | ||
|
|
1b0984de7a | ||
|
|
7da2f39282 | ||
|
|
aa29a09384 | ||
|
|
f9bffcb5d9 | ||
|
|
e491364545 | ||
|
|
975b6dd7e6 | ||
|
|
19650f85e8 | ||
|
|
49ae75eeb8 | ||
|
|
c729ba30cb | ||
|
|
e9456d5dce | ||
|
|
5d2ba6d84c | ||
|
|
d44e40440d | ||
|
|
b2b0ade653 | ||
|
|
8c9b6661ce | ||
|
|
fa21e4156e | ||
|
|
3c0d364ac3 | ||
|
|
1985079e9f | ||
|
|
b4050e6abd | ||
|
|
c8e5b9e01f | ||
|
|
93be86e155 | ||
|
|
ea6bd1c69a | ||
|
|
03b627a85c | ||
|
|
5052bf7f7c | ||
|
|
d41f1d4735 | ||
| b6e059f367 | |||
|
|
a7efd92eb7 | ||
| 16522a4736 | |||
|
|
83691f4bbf | ||
|
|
53224a8294 | ||
|
|
6a3720d982 | ||
|
|
95edb2dbb9 | ||
|
|
574fe7964c | ||
|
|
1232982184 | ||
| 73913a6161 | |||
|
|
ca3ab73b86 | ||
|
|
690bf9e48a | ||
|
|
8fc4991ae9 | ||
|
|
56ed528b4e | ||
|
|
1a43ef57de | ||
|
|
f9a421fd15 | ||
|
|
ecec7cacc4 | ||
|
|
68182ce799 | ||
|
|
f67d4de1d2 | ||
|
|
9251ee0590 | ||
|
|
73a17ecfc2 | ||
|
|
3c919f0446 | ||
|
|
7fa0d6619a | ||
|
|
136b2cf592 | ||
|
|
a818ed1021 | ||
|
|
bbc8549cc9 | ||
|
|
59f4b1c003 | ||
|
|
9d50efa480 | ||
|
|
22bc53c114 | ||
|
|
9f16bdbb89 | ||
|
|
63f91b0555 | ||
|
|
44e7b724b6 | ||
| af75851fea | |||
|
|
e8cd24b3a9 | ||
|
|
0ebb7e6347 | ||
| d1d02cc237 | |||
|
|
aab584a2f5 | ||
|
|
4635be6a0b | ||
|
|
48ef007031 | ||
|
|
2d6e361d46 | ||
|
|
6eef5a4c6c | ||
|
|
b4b9230db9 | ||
|
|
3c2ec7736d | ||
|
|
3903d6d5e3 | ||
|
|
2701913774 | ||
|
|
13ec7fe660 | ||
|
|
dd9c2cd285 | ||
|
|
ef461f5260 | ||
| e6c9eef1b6 | |||
|
|
9e206c1d3b | ||
|
|
e0fc403371 | ||
| 9c6dddc5ee | |||
|
|
b6972d42d6 | ||
|
|
3a8b9694bc | ||
|
|
e4426830df | ||
|
|
b84ad16e53 | ||
|
|
dfb3822512 | ||
|
|
9977dbd838 | ||
|
|
666df532a9 | ||
|
|
da35e48534 | ||
| a55399cf4f | |||
|
|
9a7da1d7f2 | ||
| 6a6d356401 | |||
| cfa1e5bfee | |||
| 29061af4b1 | |||
| 52e3dd25fc | |||
| ffdf841327 | |||
| 2b79f3b094 | |||
| d53eae38a6 | |||
| 9c7db697d8 | |||
| 82bed37f36 | |||
| d65962eb98 | |||
| b3067d8ba1 | |||
| 638a76d58a | |||
| 8fe7862c9a | |||
| da4c82236e | |||
| 2f53941935 | |||
| 4f73478241 | |||
| c80af80ca5 | |||
| 607d680faf | |||
| fe7f709ff8 | |||
| f219cb38dc | |||
| 29ded23578 | |||
| 9705132176 | |||
| 38dd527319 | |||
| b20f2d2533 | |||
| 14df5c377d | |||
| d90dab645c | |||
| 5e3d778223 | |||
| f125c7c836 | |||
| 01e853e88e | |||
| 3b0bc8b02b | |||
| 690c5fec98 | |||
| ede65e3694 | |||
| 9d27207fbc | |||
| 73fb98d053 | |||
| 221f825b56 | |||
| caf542460f | |||
| 1683ca8e4c | |||
| 3d1a622c22 | |||
| 3d17105a2d | |||
| 09482b0d0b | |||
| 0f69e33d9b | |||
| c69b5189ca | |||
| 11640be9e2 | |||
| 63cde2a7da | |||
| 9617ad9a57 | |||
| 50f24c67b0 | |||
| 9bfac57ad7 | |||
| 8d4a11f05d | |||
| 6832c0c7cd | |||
| ff9954b0f8 | |||
| 25f63170c8 | |||
| 4a55aca2da | |||
| dc0336bb9e | |||
| bf1401fe34 | |||
| c107c63848 | |||
| e3ffff2ad7 | |||
| 008c7557e4 | |||
| 63999afd0c | |||
| 3444f21f2e | |||
| 765bd203a2 | |||
| 59714ca814 | |||
| 58a94d0489 | |||
| 25c98f2704 | |||
| 85fecd58ce | |||
| 348f0b8557 | |||
| 77f9464ab2 | |||
| 7c2ea0b417 | |||
| 9bf6dc4af0 | |||
| 459182b655 | |||
| 2f61892e5a | |||
| 9d25c04754 | |||
| 7416f47b62 | |||
| 42e9e816ad | |||
| cad5669c77 | |||
| 2985826a7b | |||
| 2d65639c13 | |||
| 94fe1c83b7 | |||
| 13855240ac | |||
| 589f61faeb | |||
| 8c9d501425 | |||
| a3e57efb31 | |||
| 7cd954b038 | |||
| a920657d03 | |||
| 0eeb28b02e | |||
| a55da0eb38 | |||
| b9f84f6db5 | |||
| abd5c303cb | |||
| 874e8a2fe0 | |||
| 10702418ac | |||
| 155440171c | |||
| 60618f2dcb | |||
| dc8adddb57 | |||
| a4673ca30a | |||
| dfe3689e6d | |||
| a7be452ddb | |||
| aa01e42ae8 | |||
| 0690ab79c3 | |||
| a3d445565a | |||
| 2d965aa178 | |||
| 1f7f9da57a | |||
| 9507c39f09 | |||
| ae3cdafb69 | |||
| eae9a8094c | |||
| 309ef64fd6 | |||
| d818a93c0f | |||
| 8f42c03697 | |||
| a8d0e1b749 | |||
| 4d451dab46 | |||
| 4dbea7a9b0 | |||
| 642215fe14 | |||
| f93945ad79 | |||
| 021762bd50 | |||
| e59841bbff | |||
| 52e1337b69 | |||
| ec76c69609 | |||
| 00c7d7f473 | |||
| fac6a49a64 | |||
| b72763d9ff | |||
| de20d96a82 | |||
| fe5b4ce0a9 | |||
| d9a562f09b | |||
| 5bca85b5a9 | |||
| 8190255e7c | |||
| afa51500a5 | |||
| 4b6792fd92 | |||
| 6a4d5a66fa | |||
| 1a57c0a917 | |||
| d517d04537 | |||
| 4201940930 | |||
| f89ac06184 | |||
| 88763fd97d | |||
| 079bc3521c | |||
| fedb7284e0 | |||
| ef86c48fd0 | |||
| 4c1a3ee65e | |||
| 66ea8ceb9f | |||
| 4b002d8bfa | |||
| d1d0862fe9 | |||
| 9d67c8dbbc | |||
| ad82a70724 | |||
| e4450de5ca | |||
| 036a435cc0 | |||
| 26ba2d3ec0 | |||
| f8efac1188 | |||
| f3b1058d62 | |||
| dc3b2bdfe4 | |||
| 92e9b870fa | |||
| 427f502a3d | |||
| c852289a06 | |||
| a173b41d4b | |||
| 38237123cf | |||
| 0457cbcd61 | |||
| d0dbf45d24 | |||
| 1f88b32d0c | |||
| 6268cbb4bd | |||
| 5d880c6c3b | |||
| 9f10d771d2 | |||
| bc1b8de3e0 | |||
| 21d45bc559 | |||
| df40166a60 | |||
| 3f12f2d23a | |||
| 3f5f9d66cc | |||
| 3265d06737 | |||
| 4d46410015 | |||
| 71a2d784bd | |||
| f8577072cb | |||
| 72be29b288 | |||
| a6e427647c | |||
| b7e6234c38 | |||
| dbfbb32188 | |||
| 50cd5c40b2 | |||
| d305b25c76 | |||
| bccb33e2ba | |||
| 0be0fe9301 | |||
| b9f62c7b5d | |||
| a9d500b940 | |||
| 41d24f94c9 | |||
| c6dc552023 | |||
| bf9bfcf843 | |||
| d0be4da56c | |||
| c2997c915a | |||
| 83d9c16094 | |||
| e2092d146d | |||
| b134f3f8d4 | |||
| 977a2a9e58 | |||
| 89270e1d7e | |||
| cb942f77e7 | |||
| 4918627d42 | |||
| fb38b5b304 | |||
| 8d7d2691b0 | |||
| fbe1f7721f | |||
| f3f60a09a2 | |||
| fd556ad3ee | |||
| 65143eb904 | |||
| f788ade4ab | |||
| cdf022e9c7 | |||
| 7584253a9d | |||
| d4285d10d4 | |||
| 6b31c9df6a | |||
| a60f5ee064 | |||
| 6a9762b99e | |||
| ad5e86c97f | |||
| 1e4f902847 | |||
| 836eb6a086 | |||
| d0ffe58ef5 | |||
| a87b1043dc | |||
| 7e66010928 | |||
| c27843f576 | |||
| e6c368bfbe | |||
| 555589d9a8 | |||
| 3d7061ca3f | |||
| e10feca62c | |||
| ad9fb27e66 | |||
| 4d1ac45b19 | |||
| dfb2ddf337 | |||
| 35e2b02ec1 | |||
| d3f4d6b8d4 | |||
| 75de014884 | |||
| c0e38f393f | |||
| 3092cf5578 | |||
| 7243f29f19 | |||
| b56554287b | |||
| ee67f916e1 | |||
| 9dffeff73d | |||
|
|
5e3a913988 | ||
|
|
56171929b6 | ||
|
|
f955d0d200 | ||
|
|
d2d382a765 | ||
|
|
c8593119b2 | ||
|
|
3970ef22fd | ||
|
|
3ef908051c | ||
| a146c74a2c | |||
|
|
f89574b504 | ||
|
|
b6a5d29fe6 | ||
| b97de7e1e2 | |||
| e42fff4ea0 | |||
|
|
efc906997f | ||
|
|
4db87be546 | ||
|
|
2489c2133e | ||
|
|
4427f63c17 | ||
|
|
0baaa09caf | ||
|
|
c12008bd3f | ||
|
|
034f8e3f51 | ||
|
|
29ed9d4b7e | ||
| 3bb37764df | |||
| 893d49a0d3 | |||
| fbb9585835 | |||
| c6e6492d8b | |||
| 9eecbc45aa | |||
|
|
45452e4b15 | ||
|
|
60391b36c1 | ||
|
|
4e7145a441 | ||
| 4461288d13 | |||
|
|
c0d86f6d12 | ||
| fb970a768a | |||
| cd942ef691 | |||
| 4b79be2687 | |||
| edc6e3e448 | |||
| 6c0d83929c | |||
| f917920233 | |||
| 20aec4e9a0 | |||
| a1cc4415a5 | |||
| fdb83483eb | |||
| 675c8d9b82 | |||
| 499c277501 | |||
| 6ed12f49b4 | |||
| 3034cef5f5 | |||
| 7227619449 | |||
| 048d4e93dc | |||
| e0a61c9786 | |||
| a14de95795 | |||
| ab44bcd782 | |||
| 19518e5700 | |||
| eefa743cf6 | |||
| 8faa0fa674 | |||
| 77f83b9a4c | |||
| d25466217e | |||
| fb0dff5892 | |||
| 2f8f7623c2 | |||
| 843dbfdfe5 | |||
| 853daf38db | |||
| f1222ac6ea | |||
| 3f28ebbe0f | |||
| 4055b5cbb2 | |||
| 710d53647c | |||
| fca5638dd9 | |||
| 8918cca6e4 | |||
| 1d0a8c2a3d | |||
| 9b404a10b4 | |||
| 6ef4496e59 | |||
| 4cdfae45f2 | |||
| 50428bd48b | |||
| 144a01e1e0 | |||
| 9f75b67bd3 | |||
| cb8191915e | |||
| b85d2e8204 | |||
| 5f36b15fe3 | |||
| b19d0d679c | |||
| a6f2dc10b2 | |||
| 0dfba0bc83 | |||
| 4b681492b6 | |||
| ce3010253a | |||
| 192a42b9bf | |||
| 44fc726bb5 | |||
| 4f3ae3e74b | |||
| dcf8e95475 | |||
| 0fb122fa4a | |||
| af4f2575c9 | |||
| c54f5415ad | |||
| c8a5e83705 | |||
| 1925cf96c2 | |||
| b107581649 | |||
| 24f2ce59d4 | |||
| d496779024 | |||
| 9e432c0df5 | |||
| 19b3b6d7d2 | |||
| 611bc6ca21 | |||
| 66bd336722 | |||
| b1d81875fc | |||
| b6da1954d4 | |||
| 840f2e3596 | |||
| a2ae7e6913 | |||
| 6ff0174e9b | |||
| 5074a0274a | |||
| 2d83c95eba | |||
| f747301f65 | |||
| fb69a894a2 | |||
| 19f6be22b8 | |||
| e532ef69db | |||
| ce6daf533b | |||
| 5384332b01 | |||
| e306813a87 | |||
| 1c5c6ec8f1 | |||
| 8012fe13ec | |||
| 6b0c7c0242 | |||
| c356674ea1 | |||
| 02e6780cdb | |||
| 0dcc613843 | |||
| 93a93b995d | |||
| 6049c28cdd | |||
| d152f822b3 | |||
| 25228f3371 | |||
| 027320b644 | |||
| 262a00c3a9 | |||
| d6c6a85e5a | |||
| ad6b8b7754 | |||
| e52aa4470d | |||
| f5141369c7 | |||
| d3595ac878 | |||
| 8f9be2fa25 | |||
| a496ff5423 | |||
| efac7d35c4 | |||
| d7da9697fb | |||
| d04fe3a4f0 | |||
| b4028dd6f4 | |||
| 7f0b4e073d | |||
| 166162718f | |||
| 39687bec71 | |||
| a0ae41ade9 | |||
| 0656df5a0f | |||
| 4ccacaa2f4 | |||
| 2dbcdb9f23 | |||
| 7282290d1a | |||
| 4af202cdc0 | |||
| 298d8bbcfa | |||
| a37459ed62 | |||
| f37078c207 | |||
| 76df9a59e6 | |||
| 33cc1322cc | |||
| 9901bd7df7 | |||
| f7c891e3d3 | |||
| 50f934abbb | |||
| ae26bd4f18 | |||
| f0bdeb860a | |||
| 99d4411a41 | |||
| 87ea17056c | |||
| 6797acc724 | |||
| ac6b954585 | |||
| 53d84e7f84 | |||
| cb90ae91b5 | |||
| 33411e3b85 | |||
| 4ab7aac63e | |||
| 1f7c2e637e | |||
| 9c354fdac5 | |||
| f57b41f86d | |||
| 1e9a6271ea | |||
| 7989c700b9 | |||
| 02e7188b20 | |||
| 1523cf735c | |||
| adedf5f70c | |||
| c069bd0540 | |||
| 871b84ebf4 | |||
| db8c01de1b | |||
| 85afb870e8 | |||
| 57a6bd32d6 | |||
| afacdb82cc | |||
| b9350f0da9 | |||
| 4f2bff3a47 | |||
| de605d4809 | |||
| 67c7509bb9 | |||
| ecd04fa1a0 | |||
| f00d345fe8 | |||
| d161f0f9cd | |||
| a72299176c | |||
| a26666199c | |||
| 7932581ec3 | |||
| a93ec759d6 | |||
| 4d8394acc0 | |||
| 704c1bca86 | |||
| ee76be73f2 | |||
| e0c556c279 | |||
| 73a53c4715 | |||
| 6d4b786150 | |||
| 7c061b43ca | |||
| adcd9c69de | |||
| 4bd98918cc | |||
| 97d461b667 | |||
| d322e425cb | |||
| 7ae32965cf | |||
| c0f1b5af14 | |||
| a7fde7cd0d | |||
| cea8211297 | |||
| 66f9d2cfe6 | |||
| 366ffb5de9 | |||
| e848a7bac5 | |||
| b10bef82a9 | |||
| 1e3dff83fa | |||
| 901a5438dd | |||
| d84c55cfe1 | |||
| 9331a1b7f7 | |||
| bcac86fce9 | |||
| 824a1f4487 | |||
| af4a2246c0 | |||
| fcd0e55125 | |||
| 4b3d5f4043 | |||
| a47085dc67 | |||
| 756c48fc83 | |||
| ac45fb171c | |||
| 7562c4184d | |||
| fcd7322861 | |||
| 7a64fa6b7e | |||
| 9d3a39f6cc | |||
| d9a8e75fbf | |||
| 5cb1e9f63e | |||
| 53d365f07d | |||
| a320a85353 | |||
| 01ae4c753f | |||
| c04d8923b3 | |||
| 658bd1e196 | |||
| 149ee90339 | |||
| eea561c225 | |||
| ead2acee40 | |||
| 07efe7609a | |||
| daefe075b3 | |||
| b6b48eafb3 | |||
| cadaa8c5fe | |||
| f4a5950c31 | |||
| d44385fc41 | |||
|
|
7ebedc2d56 | ||
| 25220fad97 | |||
| b9ac291e68 | |||
| 880544e58d | |||
| 579103e916 | |||
| 0abb48c7aa | |||
| 6447e7a203 | |||
| b7a721cf8d | |||
| c0015f45fc | |||
| 219637c4c6 | |||
|
|
a9fc5c4773 | ||
|
|
1081dc8934 | ||
| 1a6d1f5f2d | |||
| f5baf35666 | |||
| 30f35ae07f | |||
| 06def0d890 | |||
| 43f8325ad2 | |||
| f273de2cab | |||
| 76f4d131ad | |||
| 1beeeba7ff | |||
| d12b24a36b | |||
|
|
8d67fe8a49 | ||
| 3e0dbfd78d | |||
| 342a76bbad | |||
|
|
21c735f126 | ||
|
|
99132e65ec | ||
|
|
6903901ec0 | ||
| b20011a21e | |||
| 8fe11b60f1 | |||
| 086db10f74 | |||
| b5e6501bbb | |||
| 566fa19031 | |||
| a91917fde5 | |||
| b70d8649f5 | |||
| 76b1ce9486 | |||
| 1fd72be97d | |||
| 2ad62be4e9 | |||
| ed704f93aa | |||
| 6b033ea57c | |||
| 046c81ec9c | |||
| 15d8fa4aff | |||
|
|
4f9f42f5c2 | ||
| 8b2f836c2c | |||
| 64496b9549 | |||
| 782ac21120 | |||
| 24d50f931a | |||
| b693eeaf24 | |||
| 93092c3a21 | |||
| c41140391f | |||
| df9193ffe6 | |||
| 4a12a6f2dd | |||
| 8ec13ee23d | |||
| e3a8a91051 | |||
| e57cf107fe | |||
| 5cbbf0b6b0 | |||
| af286fac68 | |||
| 7ce7f9a133 | |||
| 59efb7ea1a | |||
| 5dc236bd1c | |||
| bb3cb93432 | |||
| ed97047bdf | |||
| 823c2d979f | |||
| 4b4f370d53 | |||
| fb7c1ea5f3 | |||
| e4792fa1f2 | |||
| cda8db4a4e | |||
| 9ce4031af9 | |||
| b1557a65b1 | |||
| 7767f7fdf5 | |||
| 61710f3f73 | |||
| fb0f1773aa | |||
| f8721970f0 | |||
| bd3779820a | |||
| fb72fb61e1 | |||
| 18896aed7f | |||
| b741328642 | |||
| c8a5da4971 | |||
| 3dde857965 | |||
| f7f15bacb3 | |||
| e11b7c4bd1 | |||
| e77bc711cb | |||
| ade49ad0e9 | |||
|
|
28e8ef1828 | ||
| b17c9872a3 | |||
| 9503348263 | |||
| 79632c2913 | |||
| fb7a8b8533 | |||
| 2778ea1daa | |||
| 5643fa5f8d | |||
| 3edcbc4416 | |||
| bb19d5ed2e | |||
|
|
f89aaf92a4 | ||
|
|
86a0445cb3 | ||
|
|
6bd06111af | ||
| 43b904a0ca | |||
|
|
5a3236a228 | ||
| b835b50174 | |||
| a9e34e7432 | |||
| 14fba411f9 | |||
| 9cd6bcfd37 | |||
| acf0a7074e | |||
| 5f48cedfa3 | |||
| cacf567534 | |||
| 072506a637 | |||
| 8378449299 | |||
| 37a53e1c65 | |||
| 4454e4d104 | |||
|
|
6f8dad83e8 | ||
|
|
79b12f9dc8 | ||
| d370695498 | |||
| 2f37440ae4 | |||
| 84bc504f23 | |||
| 4e1f627644 | |||
|
|
ba063117b6 | ||
|
|
2bf3e274f7 | ||
|
|
a45a630a76 | ||
|
|
3afbd7228b | ||
|
|
e4db8a0bdc | ||
| a0c47a8b81 | |||
| 9a7e5bf8c8 | |||
|
|
05fac4ec16 | ||
| 46188f6fb9 | |||
| 94aa22828f | |||
|
|
cc7b5c78de | ||
| 9c2f42c298 | |||
| 89f0cc0855 | |||
| 60669ead49 | |||
|
|
23d01a0b11 | ||
| 3cab2e42e1 | |||
|
|
bb25361c97 | ||
|
|
f7dfa1d559 | ||
|
|
def61b1da3 | ||
| 98eddc7c65 | |||
| 5689e9223e | |||
|
|
6db635e3bc | ||
| d6dd5890b2 | |||
| e4cfc2867d | |||
|
|
438628198f | ||
| 5753a0e244 | |||
| b2f198dbc8 | |||
| 96fe4a6ce3 | |||
| 51ed478f50 | |||
| 90c090c1bd | |||
| a17ec87fcc | |||
| 13432be4f3 | |||
|
|
1819dc9b17 | ||
|
|
38fec0840e | ||
|
|
c13c862b78 | ||
| f8f225d262 | |||
| 21d5716471 | |||
|
|
3c31dfd6f0 | ||
|
|
2458c021ab | ||
| 45636747b1 | |||
| 9c55a9983d | |||
|
|
428ccfc05c | ||
| ef7543beac | |||
| 1b3687108d | |||
| 0f2905f08b | |||
| 7173989234 | |||
|
|
5aeeeb784b | ||
| 227551a219 | |||
| 79238fda57 | |||
|
|
53723bead3 | ||
|
|
d93e6cc174 | ||
|
|
4c19d4f968 | ||
| d9f5a4ecc2 | |||
| 4cbd1f335e | |||
|
|
7feb4491c0 | ||
| 8acb8d8024 | |||
| eba50b5562 | |||
| c661b9cb6d | |||
|
|
e249268070 | ||
| d27436b9d6 | |||
| d718f3e455 | |||
|
|
97a4f9206a | ||
|
|
5b3ee91fff | ||
|
|
63bdb08bd2 | ||
|
|
f5eaa18e16 | ||
|
|
a8ba66fce1 | ||
|
|
3db55d5870 | ||
|
|
cf50c17b3f | ||
|
|
98c23b23fa | ||
|
|
3f700886c2 | ||
|
|
f97e91b471 | ||
| c1d915f2ae | |||
| 88526931f5 | |||
| 2353482329 | |||
| 13324f0c18 | |||
|
|
159b01ba48 | ||
|
|
1f48f3c1f3 | ||
|
|
37f48497a0 | ||
|
|
672038938b | ||
|
|
aa005a1189 | ||
|
|
aac7d1f4d4 | ||
|
|
3dc1bf1148 | ||
|
|
d842d9c427 | ||
| 79837381ec | |||
| 2d525bfa4d | |||
|
|
fb8f103042 | ||
| 4537e74493 | |||
| 6b2734e101 | |||
|
|
40fa59faad | ||
| 59d89fae03 | |||
| 037f2e27d6 | |||
| e67f3652cb | |||
|
|
50849a9266 | ||
|
|
39bc6e9d59 | ||
|
|
664e665d86 | ||
|
|
97afe4a985 | ||
| 865ca0077b | |||
| 4ac90ecd4f | |||
| 569370fe23 | |||
| 45bdcb3a2a | |||
|
|
5491597a79 | ||
|
|
877691ebbe | ||
|
|
7fbd575f91 | ||
|
|
f2795107b9 | ||
|
|
892e2e491b | ||
|
|
68e2dfd950 | ||
|
|
1511a42280 | ||
|
|
d6a67bd1c6 | ||
|
|
efe56340f7 | ||
|
|
6daa178c05 | ||
| 6e3b1bcf37 | |||
|
|
e0bb913c6c | ||
|
|
082c687325 | ||
|
|
52786b73fd | ||
|
|
6658c6af0d | ||
|
|
f22db00de6 | ||
|
|
2180f608fb | ||
|
|
8e5bf079c7 | ||
|
|
b2dee2d870 | ||
|
|
ee2797932c | ||
| 32e7f32bbd | |||
| acaf163c32 | |||
|
|
a9c1f602e7 | ||
|
|
9f7dd63b7f | ||
|
|
a549454490 | ||
|
|
5359cd7d6d | ||
|
|
fbb5c97c24 | ||
| 245aeb9144 | |||
|
|
da1eced7c1 | ||
|
|
b6934ac8cb | ||
|
|
a725a5142f | ||
| e81c8ee54f | |||
| e00db1b950 | |||
|
|
8c0df64c25 | ||
| e6a3c511ee | |||
| ef5985a413 | |||
|
|
491e469b6b | ||
|
|
195d96f185 | ||
|
|
ab40e74ba1 | ||
|
|
5a9fdea3e5 | ||
| 00ac91edb2 | |||
|
|
273d8323a1 | ||
|
|
eb355a4005 | ||
|
|
37e1122636 | ||
|
|
427f929ca6 | ||
|
|
99ce04d16f | ||
|
|
5d0c217b0a | ||
|
|
8d7681dff9 | ||
|
|
e2c86ce6a5 | ||
|
|
78be78fc56 | ||
|
|
baf7debe90 | ||
|
|
4eb334a784 | ||
|
|
869a80798a | ||
|
|
e46c7a825d | ||
|
|
6b9629b304 | ||
|
|
08513ab8a3 | ||
|
|
8ec09f9f0b | ||
|
|
7e1e23137a | ||
|
|
23f5bd5c4c | ||
|
|
e79ea8564a | ||
|
|
2b6ddd541b | ||
|
|
d36609f876 | ||
|
|
7ff5fc688d | ||
|
|
c7229b6296 | ||
|
|
323649ee13 | ||
|
|
fb552e42dd | ||
|
|
270901bd7a | ||
|
|
0ad5ad04a1 | ||
|
|
61f8f70c1e | ||
|
|
3cabfb983a | ||
|
|
1dac04b872 | ||
| 1211d714a1 | |||
|
|
0d1eab930d |
46
.claude/settings.local.json
Normal file
46
.claude/settings.local.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git mv:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(npm run test:unit:*)",
|
||||
"Bash(npm install:*)",
|
||||
"Bash(xargs:*)",
|
||||
"Bash(npm run db:generate:*)",
|
||||
"Bash(npx prisma generate:*)",
|
||||
"Bash(DATABASE_URL=\"file:./dev.db\" npx prisma generate:*)",
|
||||
"Bash(git rm:*)",
|
||||
"Bash(git log:*)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(xargs git rm:*)",
|
||||
"Bash(bun add:*)",
|
||||
"Bash(bun install:*)",
|
||||
"Bash(test -f:*)",
|
||||
"Bash(bun run typecheck:*)",
|
||||
"Bash(bun run test:unit:*)",
|
||||
"Bash(echo:*)",
|
||||
"Bash(npx prisma validate:*)",
|
||||
"Bash(npm run typecheck:*)",
|
||||
"Bash(npm run lint)",
|
||||
"Bash(npm audit:*)",
|
||||
"Bash(bun run lint)",
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(bun audit:*)",
|
||||
"Bash(git restore:*)",
|
||||
"Bash(bunx playwright:*)",
|
||||
"Bash(timeout 30 bun run build:*)",
|
||||
"Bash(bun run lint:fix:*)",
|
||||
"Bash(bun run format:*)",
|
||||
"Bash(while read file)",
|
||||
"Bash(do eslint:*)",
|
||||
"Bash(done)",
|
||||
"Bash(eslint:*)",
|
||||
"Bash(bunx eslint:*)",
|
||||
"Bash(bun test:*)",
|
||||
"Bash(136*100/234)",
|
||||
"Bash(\")",
|
||||
"Bash(python3:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
99
.dockerignore
Normal file
99
.dockerignore
Normal file
@@ -0,0 +1,99 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
.nyc_output
|
||||
*.test.ts
|
||||
*.test.tsx
|
||||
*.spec.ts
|
||||
*.spec.tsx
|
||||
__tests__
|
||||
__mocks__
|
||||
.vitest
|
||||
|
||||
# Next.js
|
||||
.next
|
||||
out
|
||||
dist
|
||||
build
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# Debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env*.local
|
||||
.env.development
|
||||
.env.test
|
||||
.env.production
|
||||
|
||||
# Vercel
|
||||
.vercel
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
docs
|
||||
README*
|
||||
CHANGELOG*
|
||||
LICENSE
|
||||
|
||||
# CI/CD
|
||||
.github
|
||||
.gitlab-ci.yml
|
||||
azure-pipelines.yml
|
||||
|
||||
# Docker
|
||||
Dockerfile*
|
||||
docker-compose*
|
||||
.dockerignore
|
||||
|
||||
# Development
|
||||
.editorconfig
|
||||
.prettierrc*
|
||||
.eslintrc*
|
||||
.eslintignore
|
||||
|
||||
# Storybook
|
||||
.storybook
|
||||
storybook-static
|
||||
|
||||
# E2E
|
||||
e2e
|
||||
playwright-report
|
||||
test-results
|
||||
|
||||
# Temporary files
|
||||
tmp
|
||||
temp
|
||||
.tmp
|
||||
.cache
|
||||
32
.gitattributes
vendored
Normal file
32
.gitattributes
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Shell scripts should always use LF
|
||||
*.sh text eol=lf
|
||||
|
||||
# Windows batch files should use CRLF
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
|
||||
# JSON, JavaScript, TypeScript should use LF
|
||||
*.json text eol=lf
|
||||
*.js text eol=lf
|
||||
*.ts text eol=lf
|
||||
*.jsx text eol=lf
|
||||
*.tsx text eol=lf
|
||||
|
||||
# Markdown and documentation should use LF
|
||||
*.md text eol=lf
|
||||
*.txt text eol=lf
|
||||
|
||||
# Binary files
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.pdf binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.ttf binary
|
||||
*.otf binary
|
||||
8
.github/COPILOT_ANALYSIS.md
vendored
8
.github/COPILOT_ANALYSIS.md
vendored
@@ -8,7 +8,7 @@
|
||||
### Analysis Approach
|
||||
|
||||
1. **Examined existing instructions**
|
||||
- `dbal/AGENTS.md` (605 lines) - DBAL-specific agent development guide
|
||||
- `dbal/docs/AGENTS.md` (605 lines) - DBAL-specific agent development guide
|
||||
- `.github/copilot-instructions.md` (existing) - Original generic guidance
|
||||
|
||||
2. **Analyzed codebase patterns** through:
|
||||
@@ -116,7 +116,7 @@ Instructions now reference:
|
||||
|
||||
| File | Purpose | Why Referenced |
|
||||
|------|---------|-----------------|
|
||||
| `dbal/AGENTS.md` | DBAL development guide | Critical for DBAL changes |
|
||||
| `dbal/docs/AGENTS.md` | DBAL development guide | Critical for DBAL changes |
|
||||
| `src/lib/database.ts` | Database operations | 1200+ LOC utility wrapper, required for all DB access |
|
||||
| `src/components/RenderComponent.tsx` | Generic renderer | 221 LOC example of declarative UI pattern |
|
||||
| `src/lib/schema-utils.test.ts` | Test examples | 63 tests showing parameterized pattern |
|
||||
@@ -159,7 +159,7 @@ Instructions now reference:
|
||||
### Adding a new database entity
|
||||
1. Read: API-First DBAL Development pattern
|
||||
2. Check: DBAL-Specific Guidance (YAML → Types → Adapters)
|
||||
3. Reference: `dbal/AGENTS.md` for detailed workflow
|
||||
3. Reference: `dbal/docs/AGENTS.md` for detailed workflow
|
||||
|
||||
### Creating a new component feature
|
||||
1. Read: Generic Component Rendering pattern
|
||||
@@ -192,7 +192,7 @@ Agents should prioritize these when onboarding:
|
||||
1. **Start**: `docs/architecture/5-level-system.md` (understand permissions)
|
||||
2. **Then**: `docs/architecture/packages.md` (understand modularity)
|
||||
3. **Then**: `src/lib/database.ts` (understand DB pattern)
|
||||
4. **Then**: `dbal/AGENTS.md` (if working on DBAL)
|
||||
4. **Then**: `dbal/docs/AGENTS.md` (if working on DBAL)
|
||||
5. **Always**: `FUNCTION_TEST_COVERAGE.md` (for test requirements)
|
||||
|
||||
---
|
||||
|
||||
260
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
260
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,130 +1,130 @@
|
||||
name: 🐛 Bug Report
|
||||
description: Report a bug or unexpected behavior
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "triage"]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to report this bug! Please fill out the form below to help us understand and fix the issue.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Bug Description
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: Tell us what went wrong...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Steps to reproduce the behavior
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: What did you expect to happen?
|
||||
placeholder: I expected...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
description: What actually happened?
|
||||
placeholder: Instead, I observed...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: component
|
||||
attributes:
|
||||
label: Component/Area
|
||||
description: Which part of MetaBuilder is affected?
|
||||
options:
|
||||
- Frontend (Next.js UI)
|
||||
- Backend (API/Auth)
|
||||
- Database (Prisma/Schema)
|
||||
- DBAL (TypeScript/C++)
|
||||
- Package System
|
||||
- Lua Scripting
|
||||
- Multi-Tenant System
|
||||
- Permission System
|
||||
- Workflows
|
||||
- Documentation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: severity
|
||||
attributes:
|
||||
label: Severity
|
||||
description: How severe is this bug?
|
||||
options:
|
||||
- Critical (System crash, data loss)
|
||||
- High (Major feature broken)
|
||||
- Medium (Feature partially broken)
|
||||
- Low (Minor issue, workaround exists)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Please provide your environment details
|
||||
value: |
|
||||
- OS: [e.g., Ubuntu 22.04, macOS 13.0, Windows 11]
|
||||
- Node Version: [e.g., 18.17.0]
|
||||
- Browser: [e.g., Chrome 120, Firefox 121]
|
||||
- Database: [e.g., SQLite, PostgreSQL 15]
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant Logs/Screenshots
|
||||
description: Add any error logs, screenshots, or console output
|
||||
placeholder: |
|
||||
Paste logs here or drag and drop screenshots.
|
||||
render: shell
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any other context about the problem
|
||||
placeholder: |
|
||||
- Does this happen consistently or intermittently?
|
||||
- Have you tried any workarounds?
|
||||
- Did this work in a previous version?
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Pre-submission Checklist
|
||||
description: Please verify the following before submitting
|
||||
options:
|
||||
- label: I have searched existing issues to ensure this is not a duplicate
|
||||
required: true
|
||||
- label: I have provided all required information above
|
||||
required: true
|
||||
- label: I have checked the documentation for relevant information
|
||||
required: false
|
||||
name: 🐛 Bug Report
|
||||
description: Report a bug or unexpected behavior
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "triage"]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to report this bug! Please fill out the form below to help us understand and fix the issue.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Bug Description
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: Tell us what went wrong...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Steps to reproduce the behavior
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: What did you expect to happen?
|
||||
placeholder: I expected...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
description: What actually happened?
|
||||
placeholder: Instead, I observed...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: component
|
||||
attributes:
|
||||
label: Component/Area
|
||||
description: Which part of MetaBuilder is affected?
|
||||
options:
|
||||
- Frontend (Next.js UI)
|
||||
- Backend (API/Auth)
|
||||
- Database (Prisma/Schema)
|
||||
- DBAL (TypeScript/C++)
|
||||
- Package System
|
||||
- Lua Scripting
|
||||
- Multi-Tenant System
|
||||
- Permission System
|
||||
- Workflows
|
||||
- Documentation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: severity
|
||||
attributes:
|
||||
label: Severity
|
||||
description: How severe is this bug?
|
||||
options:
|
||||
- Critical (System crash, data loss)
|
||||
- High (Major feature broken)
|
||||
- Medium (Feature partially broken)
|
||||
- Low (Minor issue, workaround exists)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Please provide your environment details
|
||||
value: |
|
||||
- OS: [e.g., Ubuntu 22.04, macOS 13.0, Windows 11]
|
||||
- Node Version: [e.g., 18.17.0]
|
||||
- Browser: [e.g., Chrome 120, Firefox 121]
|
||||
- Database: [e.g., SQLite, PostgreSQL 15]
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant Logs/Screenshots
|
||||
description: Add any error logs, screenshots, or console output
|
||||
placeholder: |
|
||||
Paste logs here or drag and drop screenshots.
|
||||
render: shell
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any other context about the problem
|
||||
placeholder: |
|
||||
- Does this happen consistently or intermittently?
|
||||
- Have you tried any workarounds?
|
||||
- Did this work in a previous version?
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Pre-submission Checklist
|
||||
description: Please verify the following before submitting
|
||||
options:
|
||||
- label: I have searched existing issues to ensure this is not a duplicate
|
||||
required: true
|
||||
- label: I have provided all required information above
|
||||
required: true
|
||||
- label: I have checked the documentation for relevant information
|
||||
required: false
|
||||
|
||||
22
.github/ISSUE_TEMPLATE/config.yml
vendored
22
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,11 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 📚 Documentation
|
||||
url: https://github.com/johndoe6345789/metabuilder/tree/main/docs
|
||||
about: Check our comprehensive documentation for guides and architecture details
|
||||
- name: 💬 Discussions
|
||||
url: https://github.com/johndoe6345789/metabuilder/discussions
|
||||
about: Ask questions and discuss ideas with the community
|
||||
- name: 🔒 Security Issues
|
||||
url: https://github.com/johndoe6345789/metabuilder/security/advisories/new
|
||||
about: Report security vulnerabilities privately
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 📚 Documentation
|
||||
url: https://github.com/johndoe6345789/metabuilder/tree/main/docs
|
||||
about: Check our comprehensive documentation for guides and architecture details
|
||||
- name: 💬 Discussions
|
||||
url: https://github.com/johndoe6345789/metabuilder/discussions
|
||||
about: Ask questions and discuss ideas with the community
|
||||
- name: 🔒 Security Issues
|
||||
url: https://github.com/johndoe6345789/metabuilder/security/advisories/new
|
||||
about: Report security vulnerabilities privately
|
||||
|
||||
316
.github/ISSUE_TEMPLATE/dbal_issue.yml
vendored
316
.github/ISSUE_TEMPLATE/dbal_issue.yml
vendored
@@ -1,158 +1,158 @@
|
||||
name: 🔧 DBAL Issue
|
||||
description: Report an issue with the Database Abstraction Layer (TypeScript or C++)
|
||||
title: "[DBAL]: "
|
||||
labels: ["dbal", "bug", "triage"]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
DBAL is MetaBuilder's critical database abstraction layer with TypeScript (dev) and C++ (production) implementations.
|
||||
|
||||
- type: dropdown
|
||||
id: implementation
|
||||
attributes:
|
||||
label: DBAL Implementation
|
||||
description: Which DBAL implementation is affected?
|
||||
options:
|
||||
- TypeScript SDK (dbal/ts/)
|
||||
- C++ Daemon (dbal/cpp/)
|
||||
- Both implementations
|
||||
- YAML Contracts (api/schema/)
|
||||
- Conformance Tests
|
||||
- Unknown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: issue
|
||||
attributes:
|
||||
label: Issue Description
|
||||
description: Describe the DBAL issue you're experiencing
|
||||
placeholder: The DBAL operation fails when...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: operation
|
||||
attributes:
|
||||
label: Operation Type
|
||||
description: What type of operation is failing?
|
||||
options:
|
||||
- Entity Operations (CRUD)
|
||||
- Query Operations
|
||||
- Transaction Operations
|
||||
- Blob Storage
|
||||
- Key-Value Store
|
||||
- Tenant Management
|
||||
- Access Control
|
||||
- Connection Management
|
||||
- Type Generation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction Code
|
||||
description: Provide code to reproduce the issue
|
||||
placeholder: |
|
||||
```typescript
|
||||
// Your code here
|
||||
const result = await dbalQuery({...})
|
||||
```
|
||||
render: typescript
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: What should happen?
|
||||
placeholder: The operation should...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
description: What actually happens?
|
||||
placeholder: Instead, I see...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: error
|
||||
attributes:
|
||||
label: Error Messages/Logs
|
||||
description: Include any error messages, stack traces, or logs
|
||||
render: shell
|
||||
|
||||
- type: dropdown
|
||||
id: severity
|
||||
attributes:
|
||||
label: Severity
|
||||
options:
|
||||
- Critical (Data corruption/loss)
|
||||
- High (Operation completely fails)
|
||||
- Medium (Operation partially works)
|
||||
- Low (Minor inconsistency)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment Details
|
||||
value: |
|
||||
- DBAL Version: [e.g., commit hash or version]
|
||||
- Node/C++ Version: [e.g., Node 18.17, gcc 11.3]
|
||||
- Database: [e.g., SQLite, PostgreSQL 15]
|
||||
- OS: [e.g., Ubuntu 22.04]
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: parity
|
||||
attributes:
|
||||
label: Implementation Parity
|
||||
description: If both implementations exist, do they behave the same?
|
||||
options:
|
||||
- Both implementations fail
|
||||
- Only TypeScript fails
|
||||
- Only C++ fails
|
||||
- Different behavior between implementations
|
||||
- Haven't tested both
|
||||
- N/A (only one implementation exists)
|
||||
|
||||
- type: textarea
|
||||
id: conformance
|
||||
attributes:
|
||||
label: Conformance Test Status
|
||||
description: Do conformance tests pass for this operation?
|
||||
placeholder: |
|
||||
- Ran: python tools/conformance/run_all.py
|
||||
- Result: [Pass/Fail details]
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: YAML contract issues? Schema problems? Performance concerns?
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Pre-submission Checklist
|
||||
options:
|
||||
- label: I have checked the YAML schema definitions in api/schema/
|
||||
required: true
|
||||
- label: I have verified this isn't a tenant isolation issue
|
||||
required: true
|
||||
- label: I have checked conformance test results if applicable
|
||||
required: false
|
||||
name: 🔧 DBAL Issue
|
||||
description: Report an issue with the Database Abstraction Layer (TypeScript or C++)
|
||||
title: "[DBAL]: "
|
||||
labels: ["dbal", "bug", "triage"]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
DBAL is MetaBuilder's critical database abstraction layer with TypeScript (dev) and C++ (production) implementations.
|
||||
|
||||
- type: dropdown
|
||||
id: implementation
|
||||
attributes:
|
||||
label: DBAL Implementation
|
||||
description: Which DBAL implementation is affected?
|
||||
options:
|
||||
- TypeScript SDK (dbal/development/)
|
||||
- C++ Daemon (dbal/production/)
|
||||
- Both implementations
|
||||
- YAML Contracts (api/schema/)
|
||||
- Conformance Tests
|
||||
- Unknown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: issue
|
||||
attributes:
|
||||
label: Issue Description
|
||||
description: Describe the DBAL issue you're experiencing
|
||||
placeholder: The DBAL operation fails when...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: operation
|
||||
attributes:
|
||||
label: Operation Type
|
||||
description: What type of operation is failing?
|
||||
options:
|
||||
- Entity Operations (CRUD)
|
||||
- Query Operations
|
||||
- Transaction Operations
|
||||
- Blob Storage
|
||||
- Key-Value Store
|
||||
- Tenant Management
|
||||
- Access Control
|
||||
- Connection Management
|
||||
- Type Generation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction Code
|
||||
description: Provide code to reproduce the issue
|
||||
placeholder: |
|
||||
```typescript
|
||||
// Your code here
|
||||
const result = await dbalQuery({...})
|
||||
```
|
||||
render: typescript
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: What should happen?
|
||||
placeholder: The operation should...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
description: What actually happens?
|
||||
placeholder: Instead, I see...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: error
|
||||
attributes:
|
||||
label: Error Messages/Logs
|
||||
description: Include any error messages, stack traces, or logs
|
||||
render: shell
|
||||
|
||||
- type: dropdown
|
||||
id: severity
|
||||
attributes:
|
||||
label: Severity
|
||||
options:
|
||||
- Critical (Data corruption/loss)
|
||||
- High (Operation completely fails)
|
||||
- Medium (Operation partially works)
|
||||
- Low (Minor inconsistency)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment Details
|
||||
value: |
|
||||
- DBAL Version: [e.g., commit hash or version]
|
||||
- Node/C++ Version: [e.g., Node 18.17, gcc 11.3]
|
||||
- Database: [e.g., SQLite, PostgreSQL 15]
|
||||
- OS: [e.g., Ubuntu 22.04]
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: parity
|
||||
attributes:
|
||||
label: Implementation Parity
|
||||
description: If both implementations exist, do they behave the same?
|
||||
options:
|
||||
- Both implementations fail
|
||||
- Only TypeScript fails
|
||||
- Only C++ fails
|
||||
- Different behavior between implementations
|
||||
- Haven't tested both
|
||||
- N/A (only one implementation exists)
|
||||
|
||||
- type: textarea
|
||||
id: conformance
|
||||
attributes:
|
||||
label: Conformance Test Status
|
||||
description: Do conformance tests pass for this operation?
|
||||
placeholder: |
|
||||
- Ran: python tools/conformance/run_all.py
|
||||
- Result: [Pass/Fail details]
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: YAML contract issues? Schema problems? Performance concerns?
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Pre-submission Checklist
|
||||
options:
|
||||
- label: I have checked the YAML schema definitions in api/schema/
|
||||
required: true
|
||||
- label: I have verified this isn't a tenant isolation issue
|
||||
required: true
|
||||
- label: I have checked conformance test results if applicable
|
||||
required: false
|
||||
|
||||
230
.github/ISSUE_TEMPLATE/documentation.yml
vendored
230
.github/ISSUE_TEMPLATE/documentation.yml
vendored
@@ -1,115 +1,115 @@
|
||||
name: 📚 Documentation
|
||||
description: Report an issue with documentation or request documentation improvements
|
||||
title: "[Docs]: "
|
||||
labels: ["documentation", "triage"]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for helping improve MetaBuilder's documentation! Clear docs help everyone.
|
||||
|
||||
- type: dropdown
|
||||
id: doc-type
|
||||
attributes:
|
||||
label: Documentation Type
|
||||
description: What kind of documentation issue is this?
|
||||
options:
|
||||
- Missing documentation
|
||||
- Incorrect/outdated information
|
||||
- Unclear explanation
|
||||
- Broken links
|
||||
- Typo/grammar
|
||||
- Code example not working
|
||||
- Missing code example
|
||||
- Architecture documentation
|
||||
- API documentation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: location
|
||||
attributes:
|
||||
label: Documentation Location
|
||||
description: Where is the documentation issue? (provide file path, URL, or section name)
|
||||
placeholder: |
|
||||
File: docs/architecture/packages.md
|
||||
Or URL: https://github.com/johndoe6345789/metabuilder/tree/main/docs
|
||||
Or Section: "Getting Started > Database Setup"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: issue
|
||||
attributes:
|
||||
label: Issue Description
|
||||
description: What's wrong with the current documentation?
|
||||
placeholder: The current documentation states... but it should...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: suggestion
|
||||
attributes:
|
||||
label: Suggested Improvement
|
||||
description: How should the documentation be improved?
|
||||
placeholder: |
|
||||
The documentation should instead explain...
|
||||
Or: Add a section that covers...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Documentation Area
|
||||
description: Which area of MetaBuilder does this documentation cover?
|
||||
options:
|
||||
- Getting Started
|
||||
- Architecture
|
||||
- API Reference
|
||||
- Package System
|
||||
- DBAL
|
||||
- Permission System
|
||||
- Multi-Tenancy
|
||||
- Lua Scripting
|
||||
- Workflows
|
||||
- Database/Prisma
|
||||
- Testing
|
||||
- Deployment
|
||||
- Contributing
|
||||
- Security
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Any other relevant information
|
||||
placeholder: |
|
||||
- Screenshots of confusing sections
|
||||
- Related issues or PRs
|
||||
- Why this improvement is needed
|
||||
|
||||
- type: checkboxes
|
||||
id: contribution
|
||||
attributes:
|
||||
label: Contribution
|
||||
description: Would you like to help improve this documentation?
|
||||
options:
|
||||
- label: I am willing to submit a PR to fix/improve this documentation
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Pre-submission Checklist
|
||||
options:
|
||||
- label: I have searched existing issues for similar documentation requests
|
||||
required: true
|
||||
- label: I have verified the documentation issue still exists in the latest version
|
||||
required: true
|
||||
name: 📚 Documentation
|
||||
description: Report an issue with documentation or request documentation improvements
|
||||
title: "[Docs]: "
|
||||
labels: ["documentation", "triage"]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for helping improve MetaBuilder's documentation! Clear docs help everyone.
|
||||
|
||||
- type: dropdown
|
||||
id: doc-type
|
||||
attributes:
|
||||
label: Documentation Type
|
||||
description: What kind of documentation issue is this?
|
||||
options:
|
||||
- Missing documentation
|
||||
- Incorrect/outdated information
|
||||
- Unclear explanation
|
||||
- Broken links
|
||||
- Typo/grammar
|
||||
- Code example not working
|
||||
- Missing code example
|
||||
- Architecture documentation
|
||||
- API documentation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: location
|
||||
attributes:
|
||||
label: Documentation Location
|
||||
description: Where is the documentation issue? (provide file path, URL, or section name)
|
||||
placeholder: |
|
||||
File: docs/architecture/packages.md
|
||||
Or URL: https://github.com/johndoe6345789/metabuilder/tree/main/docs
|
||||
Or Section: "Getting Started > Database Setup"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: issue
|
||||
attributes:
|
||||
label: Issue Description
|
||||
description: What's wrong with the current documentation?
|
||||
placeholder: The current documentation states... but it should...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: suggestion
|
||||
attributes:
|
||||
label: Suggested Improvement
|
||||
description: How should the documentation be improved?
|
||||
placeholder: |
|
||||
The documentation should instead explain...
|
||||
Or: Add a section that covers...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Documentation Area
|
||||
description: Which area of MetaBuilder does this documentation cover?
|
||||
options:
|
||||
- Getting Started
|
||||
- Architecture
|
||||
- API Reference
|
||||
- Package System
|
||||
- DBAL
|
||||
- Permission System
|
||||
- Multi-Tenancy
|
||||
- Lua Scripting
|
||||
- Workflows
|
||||
- Database/Prisma
|
||||
- Testing
|
||||
- Deployment
|
||||
- Contributing
|
||||
- Security
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Any other relevant information
|
||||
placeholder: |
|
||||
- Screenshots of confusing sections
|
||||
- Related issues or PRs
|
||||
- Why this improvement is needed
|
||||
|
||||
- type: checkboxes
|
||||
id: contribution
|
||||
attributes:
|
||||
label: Contribution
|
||||
description: Would you like to help improve this documentation?
|
||||
options:
|
||||
- label: I am willing to submit a PR to fix/improve this documentation
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Pre-submission Checklist
|
||||
options:
|
||||
- label: I have searched existing issues for similar documentation requests
|
||||
required: true
|
||||
- label: I have verified the documentation issue still exists in the latest version
|
||||
required: true
|
||||
|
||||
268
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
268
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,134 +1,134 @@
|
||||
name: ✨ Feature Request
|
||||
description: Suggest a new feature or enhancement for MetaBuilder
|
||||
title: "[Feature]: "
|
||||
labels: ["enhancement", "triage"]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your interest in improving MetaBuilder! Please describe your feature request in detail.
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem Statement
|
||||
description: Is your feature request related to a problem? Describe what you're trying to achieve.
|
||||
placeholder: I'm frustrated when... / I need to be able to...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Proposed Solution
|
||||
description: Describe the solution you'd like to see
|
||||
placeholder: I would like MetaBuilder to...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives Considered
|
||||
description: Have you considered any alternative solutions or workarounds?
|
||||
placeholder: I've tried... but...
|
||||
|
||||
- type: dropdown
|
||||
id: component
|
||||
attributes:
|
||||
label: Component/Area
|
||||
description: Which part of MetaBuilder would this feature affect?
|
||||
options:
|
||||
- Frontend (Next.js UI)
|
||||
- Backend (API/Auth)
|
||||
- Database (Prisma/Schema)
|
||||
- DBAL (TypeScript/C++)
|
||||
- Package System
|
||||
- Lua Scripting
|
||||
- Multi-Tenant System
|
||||
- Permission System (Levels 1-6)
|
||||
- Workflows
|
||||
- Documentation
|
||||
- Developer Experience
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: priority
|
||||
attributes:
|
||||
label: Priority
|
||||
description: How important is this feature to you?
|
||||
options:
|
||||
- High (Blocker for my use case)
|
||||
- Medium (Would be very helpful)
|
||||
- Low (Nice to have)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: user-level
|
||||
attributes:
|
||||
label: Target User Level
|
||||
description: Which permission level(s) would use this feature?
|
||||
multiple: true
|
||||
options:
|
||||
- Level 1 (Public)
|
||||
- Level 2 (User)
|
||||
- Level 3 (Moderator)
|
||||
- Level 4 (Admin)
|
||||
- Level 5 (God)
|
||||
- Level 6 (Supergod)
|
||||
- All levels
|
||||
|
||||
- type: textarea
|
||||
id: use-cases
|
||||
attributes:
|
||||
label: Use Cases
|
||||
description: Provide specific use cases or examples of how this feature would be used
|
||||
placeholder: |
|
||||
1. As a [user type], I want to [action] so that [benefit]
|
||||
2. When [scenario], this feature would help by [outcome]
|
||||
|
||||
- type: textarea
|
||||
id: technical
|
||||
attributes:
|
||||
label: Technical Considerations
|
||||
description: Any technical details, implementation ideas, or constraints?
|
||||
placeholder: |
|
||||
- This might require changes to...
|
||||
- Could be implemented using...
|
||||
- May affect performance of...
|
||||
|
||||
- type: textarea
|
||||
id: mockups
|
||||
attributes:
|
||||
label: Mockups/Examples
|
||||
description: Add any mockups, diagrams, or examples (drag and drop images or links)
|
||||
placeholder: Paste images or links here...
|
||||
|
||||
- type: checkboxes
|
||||
id: contribution
|
||||
attributes:
|
||||
label: Contribution
|
||||
description: Would you be willing to help implement this feature?
|
||||
options:
|
||||
- label: I am willing to submit a PR for this feature
|
||||
required: false
|
||||
- label: I can help with testing this feature
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Pre-submission Checklist
|
||||
description: Please verify the following before submitting
|
||||
options:
|
||||
- label: I have searched existing issues and discussions for similar requests
|
||||
required: true
|
||||
- label: This feature aligns with MetaBuilder's data-driven, multi-tenant architecture
|
||||
required: true
|
||||
- label: I have provided sufficient detail for others to understand the request
|
||||
required: true
|
||||
name: ✨ Feature Request
|
||||
description: Suggest a new feature or enhancement for MetaBuilder
|
||||
title: "[Feature]: "
|
||||
labels: ["enhancement", "triage"]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your interest in improving MetaBuilder! Please describe your feature request in detail.
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem Statement
|
||||
description: Is your feature request related to a problem? Describe what you're trying to achieve.
|
||||
placeholder: I'm frustrated when... / I need to be able to...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Proposed Solution
|
||||
description: Describe the solution you'd like to see
|
||||
placeholder: I would like MetaBuilder to...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives Considered
|
||||
description: Have you considered any alternative solutions or workarounds?
|
||||
placeholder: I've tried... but...
|
||||
|
||||
- type: dropdown
|
||||
id: component
|
||||
attributes:
|
||||
label: Component/Area
|
||||
description: Which part of MetaBuilder would this feature affect?
|
||||
options:
|
||||
- Frontend (Next.js UI)
|
||||
- Backend (API/Auth)
|
||||
- Database (Prisma/Schema)
|
||||
- DBAL (TypeScript/C++)
|
||||
- Package System
|
||||
- Lua Scripting
|
||||
- Multi-Tenant System
|
||||
- Permission System (Levels 1-6)
|
||||
- Workflows
|
||||
- Documentation
|
||||
- Developer Experience
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: priority
|
||||
attributes:
|
||||
label: Priority
|
||||
description: How important is this feature to you?
|
||||
options:
|
||||
- High (Blocker for my use case)
|
||||
- Medium (Would be very helpful)
|
||||
- Low (Nice to have)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: user-level
|
||||
attributes:
|
||||
label: Target User Level
|
||||
description: Which permission level(s) would use this feature?
|
||||
multiple: true
|
||||
options:
|
||||
- Level 1 (Public)
|
||||
- Level 2 (User)
|
||||
- Level 3 (Moderator)
|
||||
- Level 4 (Admin)
|
||||
- Level 5 (God)
|
||||
- Level 6 (Supergod)
|
||||
- All levels
|
||||
|
||||
- type: textarea
|
||||
id: use-cases
|
||||
attributes:
|
||||
label: Use Cases
|
||||
description: Provide specific use cases or examples of how this feature would be used
|
||||
placeholder: |
|
||||
1. As a [user type], I want to [action] so that [benefit]
|
||||
2. When [scenario], this feature would help by [outcome]
|
||||
|
||||
- type: textarea
|
||||
id: technical
|
||||
attributes:
|
||||
label: Technical Considerations
|
||||
description: Any technical details, implementation ideas, or constraints?
|
||||
placeholder: |
|
||||
- This might require changes to...
|
||||
- Could be implemented using...
|
||||
- May affect performance of...
|
||||
|
||||
- type: textarea
|
||||
id: mockups
|
||||
attributes:
|
||||
label: Mockups/Examples
|
||||
description: Add any mockups, diagrams, or examples (drag and drop images or links)
|
||||
placeholder: Paste images or links here...
|
||||
|
||||
- type: checkboxes
|
||||
id: contribution
|
||||
attributes:
|
||||
label: Contribution
|
||||
description: Would you be willing to help implement this feature?
|
||||
options:
|
||||
- label: I am willing to submit a PR for this feature
|
||||
required: false
|
||||
- label: I can help with testing this feature
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Pre-submission Checklist
|
||||
description: Please verify the following before submitting
|
||||
options:
|
||||
- label: I have searched existing issues and discussions for similar requests
|
||||
required: true
|
||||
- label: This feature aligns with MetaBuilder's data-driven, multi-tenant architecture
|
||||
required: true
|
||||
- label: I have provided sufficient detail for others to understand the request
|
||||
required: true
|
||||
|
||||
328
.github/ISSUE_TEMPLATE/package_request.yml
vendored
328
.github/ISSUE_TEMPLATE/package_request.yml
vendored
@@ -1,164 +1,164 @@
|
||||
name: 📦 Package Request
|
||||
description: Request a new package for MetaBuilder's package system
|
||||
title: "[Package]: "
|
||||
labels: ["enhancement", "package", "triage"]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
MetaBuilder's power comes from its data-driven package system. Request a new package here!
|
||||
|
||||
- type: input
|
||||
id: package-name
|
||||
attributes:
|
||||
label: Package Name
|
||||
description: Proposed name for the package (use snake_case)
|
||||
placeholder: e.g., blog_engine, task_manager, analytics_dashboard
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Package Description
|
||||
description: What functionality would this package provide?
|
||||
placeholder: This package would enable users to...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: package-type
|
||||
attributes:
|
||||
label: Package Type
|
||||
description: What type of package is this?
|
||||
options:
|
||||
- UI Component/Widget
|
||||
- Feature Module
|
||||
- Integration
|
||||
- Tool/Utility
|
||||
- Template/Theme
|
||||
- Data Schema
|
||||
- Workflow
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: min-level
|
||||
attributes:
|
||||
label: Minimum Permission Level
|
||||
description: What's the minimum user level required to use this package?
|
||||
options:
|
||||
- Level 1 (Public - no auth required)
|
||||
- Level 2 (User - basic authentication)
|
||||
- Level 3 (Moderator - content moderation)
|
||||
- Level 4 (Admin - user management)
|
||||
- Level 5 (God - system configuration)
|
||||
- Level 6 (Supergod - full system control)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: features
|
||||
attributes:
|
||||
label: Key Features
|
||||
description: List the main features this package should include
|
||||
placeholder: |
|
||||
- Feature 1: Description
|
||||
- Feature 2: Description
|
||||
- Feature 3: Description
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: use-cases
|
||||
attributes:
|
||||
label: Use Cases
|
||||
description: Describe scenarios where this package would be useful
|
||||
placeholder: |
|
||||
1. A [user type] needs to [action] in order to [goal]
|
||||
2. When [scenario], this package would help by [benefit]
|
||||
|
||||
- type: textarea
|
||||
id: components
|
||||
attributes:
|
||||
label: Proposed Components
|
||||
description: What UI components would this package include?
|
||||
placeholder: |
|
||||
- ComponentName1: Description
|
||||
- ComponentName2: Description
|
||||
|
||||
- type: textarea
|
||||
id: lua-scripts
|
||||
attributes:
|
||||
label: Lua Scripts Needed
|
||||
description: What Lua scripts would be required? (MetaBuilder is 95% JSON/Lua)
|
||||
placeholder: |
|
||||
- initialize.lua: Setup and configuration
|
||||
- validators.lua: Data validation
|
||||
- helpers.lua: Utility functions
|
||||
|
||||
- type: textarea
|
||||
id: schemas
|
||||
attributes:
|
||||
label: Database Schemas
|
||||
description: What database tables/models would be needed?
|
||||
placeholder: |
|
||||
- Model1 { field1, field2, ... }
|
||||
- Model2 { field1, field2, ... }
|
||||
|
||||
- type: textarea
|
||||
id: dependencies
|
||||
attributes:
|
||||
label: Package Dependencies
|
||||
description: Would this package depend on other packages?
|
||||
placeholder: |
|
||||
- @metabuilder/dashboard
|
||||
- @metabuilder/form_builder
|
||||
|
||||
- type: dropdown
|
||||
id: multi-tenant
|
||||
attributes:
|
||||
label: Multi-Tenant Support
|
||||
description: Does this package need to be tenant-aware?
|
||||
options:
|
||||
- "Yes - Requires tenant isolation"
|
||||
- "No - Can be global"
|
||||
- "Optional - Configurable"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: similar
|
||||
attributes:
|
||||
label: Similar Packages/Inspiration
|
||||
description: Are there similar packages in other systems or frameworks?
|
||||
placeholder: |
|
||||
- System X has a similar feature that...
|
||||
- This is inspired by...
|
||||
|
||||
- type: checkboxes
|
||||
id: contribution
|
||||
attributes:
|
||||
label: Contribution
|
||||
options:
|
||||
- label: I am willing to help develop this package
|
||||
required: false
|
||||
- label: I can provide Lua scripts for this package
|
||||
required: false
|
||||
- label: I can help with testing this package
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Pre-submission Checklist
|
||||
options:
|
||||
- label: I have searched existing packages to ensure this doesn't already exist
|
||||
required: true
|
||||
- label: This package aligns with MetaBuilder's data-driven architecture
|
||||
required: true
|
||||
- label: I have considered multi-tenant requirements
|
||||
required: true
|
||||
name: 📦 Package Request
|
||||
description: Request a new package for MetaBuilder's package system
|
||||
title: "[Package]: "
|
||||
labels: ["enhancement", "package", "triage"]
|
||||
assignees: []
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
MetaBuilder's power comes from its data-driven package system. Request a new package here!
|
||||
|
||||
- type: input
|
||||
id: package-name
|
||||
attributes:
|
||||
label: Package Name
|
||||
description: Proposed name for the package (use snake_case)
|
||||
placeholder: e.g., blog_engine, task_manager, analytics_dashboard
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Package Description
|
||||
description: What functionality would this package provide?
|
||||
placeholder: This package would enable users to...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: package-type
|
||||
attributes:
|
||||
label: Package Type
|
||||
description: What type of package is this?
|
||||
options:
|
||||
- UI Component/Widget
|
||||
- Feature Module
|
||||
- Integration
|
||||
- Tool/Utility
|
||||
- Template/Theme
|
||||
- Data Schema
|
||||
- Workflow
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: min-level
|
||||
attributes:
|
||||
label: Minimum Permission Level
|
||||
description: What's the minimum user level required to use this package?
|
||||
options:
|
||||
- Level 1 (Public - no auth required)
|
||||
- Level 2 (User - basic authentication)
|
||||
- Level 3 (Moderator - content moderation)
|
||||
- Level 4 (Admin - user management)
|
||||
- Level 5 (God - system configuration)
|
||||
- Level 6 (Supergod - full system control)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: features
|
||||
attributes:
|
||||
label: Key Features
|
||||
description: List the main features this package should include
|
||||
placeholder: |
|
||||
- Feature 1: Description
|
||||
- Feature 2: Description
|
||||
- Feature 3: Description
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: use-cases
|
||||
attributes:
|
||||
label: Use Cases
|
||||
description: Describe scenarios where this package would be useful
|
||||
placeholder: |
|
||||
1. A [user type] needs to [action] in order to [goal]
|
||||
2. When [scenario], this package would help by [benefit]
|
||||
|
||||
- type: textarea
|
||||
id: components
|
||||
attributes:
|
||||
label: Proposed Components
|
||||
description: What UI components would this package include?
|
||||
placeholder: |
|
||||
- ComponentName1: Description
|
||||
- ComponentName2: Description
|
||||
|
||||
- type: textarea
|
||||
id: lua-scripts
|
||||
attributes:
|
||||
label: Lua Scripts Needed
|
||||
description: What Lua scripts would be required? (MetaBuilder is 95% JSON/Lua)
|
||||
placeholder: |
|
||||
- initialize.lua: Setup and configuration
|
||||
- validators.lua: Data validation
|
||||
- helpers.lua: Utility functions
|
||||
|
||||
- type: textarea
|
||||
id: schemas
|
||||
attributes:
|
||||
label: Database Schemas
|
||||
description: What database tables/models would be needed?
|
||||
placeholder: |
|
||||
- Model1 { field1, field2, ... }
|
||||
- Model2 { field1, field2, ... }
|
||||
|
||||
- type: textarea
|
||||
id: dependencies
|
||||
attributes:
|
||||
label: Package Dependencies
|
||||
description: Would this package depend on other packages?
|
||||
placeholder: |
|
||||
- @metabuilder/dashboard
|
||||
- @metabuilder/form_builder
|
||||
|
||||
- type: dropdown
|
||||
id: multi-tenant
|
||||
attributes:
|
||||
label: Multi-Tenant Support
|
||||
description: Does this package need to be tenant-aware?
|
||||
options:
|
||||
- "Yes - Requires tenant isolation"
|
||||
- "No - Can be global"
|
||||
- "Optional - Configurable"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: similar
|
||||
attributes:
|
||||
label: Similar Packages/Inspiration
|
||||
description: Are there similar packages in other systems or frameworks?
|
||||
placeholder: |
|
||||
- System X has a similar feature that...
|
||||
- This is inspired by...
|
||||
|
||||
- type: checkboxes
|
||||
id: contribution
|
||||
attributes:
|
||||
label: Contribution
|
||||
options:
|
||||
- label: I am willing to help develop this package
|
||||
required: false
|
||||
- label: I can provide Lua scripts for this package
|
||||
required: false
|
||||
- label: I can help with testing this package
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Pre-submission Checklist
|
||||
options:
|
||||
- label: I have searched existing packages to ensure this doesn't already exist
|
||||
required: true
|
||||
- label: This package aligns with MetaBuilder's data-driven architecture
|
||||
required: true
|
||||
- label: I have considered multi-tenant requirements
|
||||
required: true
|
||||
|
||||
6
.github/TEMPLATES.md
vendored
6
.github/TEMPLATES.md
vendored
@@ -94,7 +94,7 @@ Report issues with the Database Abstraction Layer.
|
||||
|
||||
**Best For:**
|
||||
- DBAL TypeScript SDK issues (`dbal/ts/`)
|
||||
- DBAL C++ daemon issues (`dbal/cpp/`)
|
||||
- DBAL C++ daemon issues (`dbal/production/`)
|
||||
- YAML contract problems (`api/schema/`)
|
||||
- Conformance test failures
|
||||
- Implementation inconsistencies
|
||||
@@ -285,7 +285,7 @@ Packages follow strict conventions:
|
||||
|
||||
### DBAL (Database Abstraction Layer)
|
||||
- TypeScript implementation: `dbal/ts/` (development)
|
||||
- C++ implementation: `dbal/cpp/` (production)
|
||||
- C++ implementation: `dbal/production/` (production)
|
||||
- YAML contracts: `api/schema/` (source of truth)
|
||||
- Always update YAML first
|
||||
- Run conformance tests: `python tools/conformance/run_all.py`
|
||||
@@ -338,6 +338,6 @@ Please submit an issue with the "documentation" template to suggest improvements
|
||||
- **Workflow Guide**: `.github/prompts/0-kickstart.md`
|
||||
- **Contributing**: `README.md` → Contributing section
|
||||
- **Architecture**: `docs/architecture/`
|
||||
- **DBAL Guide**: `dbal/AGENTS.md`
|
||||
- **DBAL Guide**: `dbal/docs/AGENTS.md`
|
||||
- **UI Standards**: `UI_STANDARDS.md`
|
||||
- **Copilot Instructions**: `.github/copilot-instructions.md`
|
||||
|
||||
50
.github/copilot-instructions.md
vendored
50
.github/copilot-instructions.md
vendored
@@ -2,12 +2,13 @@
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
MetaBuilder is a **data-driven, multi-tenant platform** with 95% functionality in JSON/Lua, not TypeScript. The system combines:
|
||||
MetaBuilder is a **data-driven, multi-tenant platform** with 95% functionality in JSON, not TypeScript. The system combines:
|
||||
|
||||
- **5-Level Permission System**: Public → User → Admin → God → Supergod access hierarchies
|
||||
- **6-Level Permission System**: Public → User → Moderator → Admin → God → Supergod access hierarchies
|
||||
- **DBAL (Database Abstraction Layer)**: TypeScript SDK + C++ daemon, language-agnostic via YAML contracts
|
||||
- **Declarative Components**: Render complex UIs from JSON configuration using `RenderComponent`
|
||||
- **Package System**: Self-contained modules in `/packages/{name}/seed/` with metadata, components, scripts
|
||||
- **Multi-Source Package Repos**: Support for local and remote package registries via `PackageSourceManager`
|
||||
- **Multi-Tenancy**: All data queries filter by `tenantId`; each tenant has isolated configurations
|
||||
|
||||
## 0-kickstart Operating Rules
|
||||
@@ -29,7 +30,7 @@ Follow `.github/prompts/0-kickstart.md` as the current workflow source of truth.
|
||||
### 1. API-First DBAL Development
|
||||
When adding features to DBAL:
|
||||
1. **Define in YAML first**: `api/schema/entities/*.yaml` and `api/schema/operations/*.yaml`
|
||||
2. **Generate types**: `python tools/codegen/gen_types.py` (creates TS and C++ types)
|
||||
2. **Generate types**: Run type generation scripts (creates TS and C++ types)
|
||||
3. **Implement adapters**: TypeScript (`ts/src/adapters/`) for speed, C++ (`cpp/src/adapters/`) for security
|
||||
4. **Add conformance tests**: `common/contracts/*_tests.yaml` (runs on both implementations to guarantee parity)
|
||||
5. Never add fields/operations directly in code without updating YAML source of truth
|
||||
@@ -56,15 +57,31 @@ Each package auto-loads on init:
|
||||
```
|
||||
packages/{name}/
|
||||
├── seed/
|
||||
│ ├── metadata.json # Package info, exports, dependencies
|
||||
│ ├── metadata.json # Package info, exports, dependencies, minLevel
|
||||
│ ├── components.json # Component definitions
|
||||
│ ├── scripts/ # Lua scripts organized by function
|
||||
│ ├── scripts/ # JSON scripts organized by function
|
||||
│ └── index.ts # Exports packageSeed object
|
||||
├── src/ # Optional React components
|
||||
└── static_content/ # Assets (images, etc.)
|
||||
```
|
||||
Loaded by `initializePackageSystem()` → `buildPackageRegistry()` → `exportAllPackagesForSeed()`
|
||||
|
||||
### 3a. Multi-Source Package Repositories
|
||||
Packages can come from multiple sources:
|
||||
```typescript
|
||||
import { createPackageSourceManager, LocalPackageSource, RemotePackageSource } from '@/lib/packages/package-glue'
|
||||
|
||||
const manager = createPackageSourceManager({
|
||||
enableRemote: true,
|
||||
remoteUrl: 'https://registry.metabuilder.dev/api/v1',
|
||||
conflictResolution: 'priority' // or 'latest-version', 'local-first', 'remote-first'
|
||||
})
|
||||
|
||||
const packages = await manager.fetchMergedIndex()
|
||||
const pkg = await manager.loadPackage('dashboard')
|
||||
```
|
||||
See: `docs/packages/package-sources.md`, `package-glue/sources/`
|
||||
|
||||
### 4. Database Helpers Pattern
|
||||
Always use `Database` class methods, never raw Prisma:
|
||||
```typescript
|
||||
@@ -77,16 +94,8 @@ const users = await prisma.user.findMany()
|
||||
```
|
||||
See: `src/lib/database.ts` (1200+ LOC utility wrapper)
|
||||
|
||||
### 5. Lua Sandbox Execution
|
||||
Lua scripts run in isolated sandbox without access to `os`, `io`, `require`:
|
||||
```typescript
|
||||
// Sandbox context provided in script
|
||||
function validateEmail(email)
|
||||
-- No file I/O, no system access, no external requires
|
||||
return string.match(email, "^[^@]+@[^@]+$") ~= nil
|
||||
end
|
||||
```
|
||||
Always test scripts with `DeclarativeComponentRenderer.executeLuaScript()`
|
||||
### 5. Script Execution
|
||||
Scripts are defined in JSON format and executed in a controlled environment with limited access to system resources.
|
||||
|
||||
## Code Conventions
|
||||
|
||||
@@ -157,7 +166,7 @@ Material-UI with SASS; theme in `src/theme/mui-theme.ts` with light/dark mode su
|
||||
1. Define database schema changes first (Prisma)
|
||||
2. Add seed data to `src/seed-data/` or package `/seed/`
|
||||
3. Use generic renderers (`RenderComponent`) not hardcoded JSX
|
||||
4. Add Lua scripts in `src/lib/lua-snippets.ts` or package `/seed/scripts/`
|
||||
4. Add JSON scripts in package `/seed/scripts/` as needed
|
||||
5. Keep one lambda per file and split as needed
|
||||
6. Add parameterized tests in `.test.ts` files with matching names
|
||||
|
||||
@@ -190,7 +199,7 @@ if (user.level >= 3) { // Admin and above
|
||||
## DBAL-Specific Guidance
|
||||
|
||||
**TypeScript DBAL**: Fast iteration, development use. Located in `dbal/ts/src/`.
|
||||
**C++ DBAL Daemon**: Production security, credential protection. Located in `dbal/cpp/src/`.
|
||||
**C++ DBAL Daemon**: Production security, credential protection. Located in `dbal/production/src/`.
|
||||
**Conformance Tests**: Guarantee both implementations behave identically. Update `common/contracts/` when changing YAML schemas.
|
||||
|
||||
If fixing a DBAL bug:
|
||||
@@ -198,14 +207,13 @@ If fixing a DBAL bug:
|
||||
2. Reproduce in TypeScript implementation first (faster feedback loop)
|
||||
3. Apply fix to both TS and C++ adapters
|
||||
4. Add/update conformance test in `common/contracts/`
|
||||
5. Verify both implementations pass test: `python tools/conformance/run_all.py`
|
||||
5. Verify both implementations pass conformance tests
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
❌ **Hardcoding values in TSX** → Move to database or YAML config
|
||||
❌ **Forgetting tenantId filter** → Breaks multi-tenancy
|
||||
❌ **Adding fields without Prisma generate** → Type errors in DB helper
|
||||
❌ **Plain JS loops over Fengari tables** → Use Lua, not TS, for Lua data
|
||||
❌ **Multiple lambdas per file** → Split into single-lambda files and wrap with a class only when needed
|
||||
❌ **New function without test** → `npm run test:check-functions` will fail
|
||||
❌ **Missing TODO for unfinished behavior** → Leave a TODO comment where functionality is pending
|
||||
@@ -217,14 +225,14 @@ If fixing a DBAL bug:
|
||||
- **Database**: `src/lib/database.ts` (all DB operations), `prisma/schema.prisma` (schema)
|
||||
- **Packages**: `src/lib/package-loader.ts` (initialization), `packages/*/seed/` (definitions)
|
||||
- **Tests**: `src/lib/schema-utils.test.ts` (parameterized pattern), `FUNCTION_TEST_COVERAGE.md` (auto-generated report)
|
||||
- **DBAL**: `dbal/AGENTS.md` (detailed DBAL agent guide), `api/schema/` (YAML contracts)
|
||||
- **DBAL**: `dbal/docs/AGENTS.md` (detailed DBAL agent guide), `api/schema/` (YAML contracts)
|
||||
|
||||
## Questions to Ask
|
||||
|
||||
1. Is this hardcoded value better in database?
|
||||
2. Could a generic component render this instead of custom TSX?
|
||||
3. Does this query filter by tenantId?
|
||||
4. Could Lua handle this without code changes?
|
||||
4. Could JSON configuration handle this without code changes?
|
||||
5. Is this one lambda per file (and test file name matches)?
|
||||
6. Does this function have a parameterized test?
|
||||
7. Is this DBAL change reflected in YAML schema first?
|
||||
|
||||
22
.github/dependabot.yml
vendored
22
.github/dependabot.yml
vendored
@@ -1,11 +1,11 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "devcontainers"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "devcontainers"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
@@ -4,7 +4,7 @@ Run DBAL commands from `dbal/`.
|
||||
|
||||
Add a new entity to the DBAL following the API-first approach:
|
||||
|
||||
1. **Define entity** in `dbal/api/schema/entities/{name}.yaml`:
|
||||
1. **Define entity** in `dbal/shared/api/schema/entities/{name}.yaml`:
|
||||
```yaml
|
||||
entity: EntityName
|
||||
version: "1.0"
|
||||
@@ -13,14 +13,14 @@ fields:
|
||||
# Add fields...
|
||||
```
|
||||
|
||||
2. **Define operations** in `dbal/api/schema/operations/{name}.ops.yaml`
|
||||
2. **Define operations** in `dbal/shared/api/schema/operations/{name}.ops.yaml`
|
||||
|
||||
3. **Generate types**: `python tools/codegen/gen_types.py`
|
||||
|
||||
4. **Implement adapters** in both:
|
||||
- `dbal/ts/src/adapters/`
|
||||
- `dbal/cpp/src/adapters/`
|
||||
- `dbal/development/src/adapters/`
|
||||
- `dbal/production/src/adapters/`
|
||||
|
||||
5. **Add conformance tests** in `dbal/common/contracts/{name}_tests.yaml`
|
||||
5. **Add conformance tests** in `dbal/shared/common/contracts/{name}_tests.yaml`
|
||||
|
||||
6. **Verify**: `python tools/conformance/run_all.py`
|
||||
|
||||
@@ -36,4 +36,4 @@ static async getNewEntities(filter: { tenantId: string }) {
|
||||
```
|
||||
|
||||
## 4. Update DBAL (if applicable)
|
||||
Add entity to `dbal/api/schema/entities/`
|
||||
Add entity to `dbal/shared/api/schema/entities/`
|
||||
|
||||
@@ -10,7 +10,7 @@ Run app commands from `frontends/nextjs/` unless a step says otherwise.
|
||||
npm run db:generate && npm run db:push
|
||||
```
|
||||
|
||||
2. **DBAL contracts**: If new entity/operation, update YAML in `dbal/api/schema/`
|
||||
2. **DBAL contracts**: If new entity/operation, update YAML in `dbal/shared/api/schema/`
|
||||
|
||||
3. **Database layer**: Add methods to `Database` class in `src/lib/database.ts`
|
||||
|
||||
|
||||
1
.github/prompts/implement/frontend/3-impl-package-prompt.md
vendored
Normal file
1
.github/prompts/implement/frontend/3-impl-package-prompt.md
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Find stuff that the nextjs frontend does and the package systen doesnt, then make/edit packages. Usually user facing stuff. Also all the packages might want SVG icons.
|
||||
6
.github/prompts/workflow/0-kickstart.md
vendored
6
.github/prompts/workflow/0-kickstart.md
vendored
@@ -4,7 +4,7 @@ Use this as the default workflow when starting work in this repo.
|
||||
|
||||
## Workflow
|
||||
1. Skim `docs/START_HERE.md` (if new), `docs/INDEX.md`, and relevant items in `docs/todo/`.
|
||||
2. Check for scoped rules in nested `AGENTS.md` files (e.g. `dbal/AGENTS.md`) before editing those areas.
|
||||
2. Check for scoped rules in nested `AGENTS.md` files (e.g. `dbal/docs/AGENTS.md`) before editing those areas.
|
||||
3. Use the prompts in `.github/prompts/` as needed:
|
||||
- Plan: `1-plan-feature.prompt.md`
|
||||
- Design: `2-design-component.prompt.md`
|
||||
@@ -19,7 +19,7 @@ Use this as the default workflow when starting work in this repo.
|
||||
## Where Work Lives
|
||||
- Next.js app: `frontends/nextjs/` (source in `src/`, E2E in `e2e/`, local scripts in `scripts/`).
|
||||
- Component packages: `packages/` (seed JSON under `packages/*/seed/`, optional `static_content/`, schema checks in `packages/*/tests/`).
|
||||
- DBAL: `dbal/` (TypeScript library in `dbal/ts/`).
|
||||
- DBAL: `dbal/` (TypeScript library in `dbal/development/`).
|
||||
- Prisma schema/migrations: `prisma/` (`schema.prisma`, `migrations/`).
|
||||
- Shared config: `config/` (symlinked into `frontends/nextjs/`).
|
||||
- Repo utilities: `tools/` (quality checks, workflow helpers, code analysis).
|
||||
@@ -41,7 +41,7 @@ Run app workflows from `frontends/nextjs/`:
|
||||
- Validate: `npx prisma validate`
|
||||
- Coverage output: `frontends/nextjs/coverage/`
|
||||
|
||||
DBAL workflows live in `dbal/ts/` (`npm run build`, `npm run test:unit`).
|
||||
DBAL workflows live in `dbal/development/` (`npm run build`, `npm run test:unit`).
|
||||
|
||||
## Source + Tests
|
||||
- TypeScript + ESM. Prefer `@/…` imports inside `frontends/nextjs/src/`.
|
||||
|
||||
@@ -5,7 +5,7 @@ Before implementing, analyze the feature requirements:
|
||||
1. **Check existing docs**: `docs/architecture/` for design patterns
|
||||
2. **Identify affected areas**:
|
||||
- Database schema changes? → `prisma/schema.prisma`
|
||||
- New API/DBAL operations? → `dbal/api/schema/`
|
||||
- New API/DBAL operations? → `dbal/shared/api/schema/`
|
||||
- UI components? → Use declarative `RenderComponent`
|
||||
- Business logic? → Consider Lua script in `packages/*/seed/scripts/`
|
||||
|
||||
|
||||
168
.github/workflows/README.md
vendored
168
.github/workflows/README.md
vendored
@@ -2,6 +2,46 @@
|
||||
|
||||
This directory contains automated workflows for CI/CD, code quality, and comprehensive AI-assisted development throughout the entire SDLC.
|
||||
|
||||
## 🚦 Enterprise Gated Tree Workflow
|
||||
|
||||
MetaBuilder uses a **Unified Enterprise Gated Pipeline** that consolidates all CI/CD, deployment, and development assistance into a single workflow with clear gate visualization.
|
||||
|
||||
**📖 Complete Guide:** [Enterprise Gated Workflow Documentation](../../docs/ENTERPRISE_GATED_WORKFLOW.md)
|
||||
|
||||
### Quick Overview
|
||||
|
||||
All PRs and deployments flow through 6 sequential gates in a single workflow:
|
||||
|
||||
1. **Gate 1: Code Quality** - Prisma, TypeScript, Lint, Security (7 atomic steps)
|
||||
2. **Gate 2: Testing** - Unit, E2E, DBAL Daemon tests (3 atomic steps)
|
||||
3. **Gate 3: Build & Package** - Application build, quality metrics (2 atomic steps)
|
||||
4. **Gate 4: Development Assistance** - Architectural feedback, Copilot interaction (PR only)
|
||||
5. **Gate 5: Staging Deployment** - Automatic deployment to staging (main branch push)
|
||||
6. **Gate 6: Production Deployment** - Manual approval required (release/workflow_dispatch)
|
||||
|
||||
**Key Benefits:**
|
||||
- ✅ **Single unified workflow** - No confusion about which pipeline runs what
|
||||
- ✅ Sequential gates prevent wasted resources
|
||||
- ✅ Tree structure for clear visualization of all validation steps
|
||||
- ✅ Automatic merge after approval
|
||||
- ✅ Conditional execution based on trigger (PR vs push vs release)
|
||||
- ✅ Complete audit trail for all deployments
|
||||
|
||||
### Pipeline Consolidation (Jan 2026)
|
||||
|
||||
**Consolidated into `gated-pipeline.yml`:**
|
||||
- ✅ `gated-ci.yml` (1048 lines) - CI with gates 1-5
|
||||
- ✅ `gated-deployment.yml` (617 lines) - Deployment workflows
|
||||
- ✅ `development.yml` (360 lines) - Development assistance
|
||||
|
||||
**Result:** Single 1287-line workflow with all functionality preserved and no duplication.
|
||||
|
||||
**Previous Deprecated and Removed (Dec 2025):**
|
||||
- ❌ `ci/ci.yml` - Replaced by gated workflows
|
||||
- ❌ `quality/deployment.yml` - Replaced by gated workflows
|
||||
|
||||
See [Legacy Pipeline Cruft Report](../../docs/LEGACY_PIPELINE_CRUFT_REPORT.md) for analysis.
|
||||
|
||||
## 🤖 GitHub Copilot Integration
|
||||
|
||||
All workflows are designed to work seamlessly with **GitHub Copilot** to assist throughout the Software Development Lifecycle:
|
||||
@@ -16,17 +56,80 @@ All workflows are designed to work seamlessly with **GitHub Copilot** to assist
|
||||
|
||||
## Workflows Overview
|
||||
|
||||
### 1. CI/CD Workflow (`ci.yml`)
|
||||
**Triggered on:** Push to main/master/develop branches, Pull requests
|
||||
### 🚦 Enterprise Gated Workflow (Unified)
|
||||
|
||||
**Jobs:**
|
||||
- **Prisma Check**: Validates database schema and generates Prisma client
|
||||
- **Lint**: Runs ESLint to check code quality
|
||||
- **Build**: Builds the application and uploads artifacts
|
||||
- **E2E Tests**: Runs Playwright end-to-end tests
|
||||
- **Quality Check**: Checks for console.log statements and TODO comments
|
||||
#### Enterprise Gated Pipeline (`gated-pipeline.yml`) 🆕
|
||||
**Triggered on:** Push to main/master/develop, Pull requests, Releases, Manual dispatch, Issue comments
|
||||
|
||||
### 2. Automated Code Review (`code-review.yml`)
|
||||
**Consolidates:** All CI/CD, deployment, and development assistance in one workflow
|
||||
|
||||
**Structure:**
|
||||
- **Gate 1:** Code Quality - 7 validation steps
|
||||
- 1.1 Prisma Validation
|
||||
- 1.2 TypeScript Check (+ strict mode analysis)
|
||||
- 1.3 ESLint (+ any-type detection + ts-ignore detection)
|
||||
- 1.4 Security Scan (+ dependency audit)
|
||||
- 1.5 File Size Check
|
||||
- 1.6 Code Complexity Analysis
|
||||
- 1.7 Stub Implementation Detection
|
||||
- **Gate 2:** Testing - 3 validation steps
|
||||
- 2.1 Unit Tests (+ coverage analysis)
|
||||
- 2.2 E2E Tests
|
||||
- 2.3 DBAL Daemon Tests
|
||||
- **Gate 3:** Build & Package - 2 validation steps
|
||||
- 3.1 Application Build (+ bundle analysis)
|
||||
- 3.2 Quality Metrics (PR only)
|
||||
- **Gate 4:** Development Assistance (PR only)
|
||||
- 4.1 Code metrics analysis
|
||||
- 4.2 Architectural compliance
|
||||
- 4.3 Refactoring suggestions
|
||||
- 4.4 Copilot interaction handler
|
||||
- **Gate 5:** Staging Deployment (main branch push)
|
||||
- Automatic deployment to staging environment
|
||||
- Smoke tests and health checks
|
||||
- **Gate 6:** Production Deployment (release/manual)
|
||||
- Manual approval gate
|
||||
- Production deployment with health monitoring
|
||||
- Deployment tracking issue creation
|
||||
|
||||
**Features:**
|
||||
- Individual validation steps for superior visualization
|
||||
- **Gate artifacts** persisted between steps (30-day retention)
|
||||
- Conditional execution based on trigger type
|
||||
- Granular failure detection
|
||||
- Parallel execution within gates
|
||||
- Complete audit trail with JSON artifacts
|
||||
- Individual step timing and status
|
||||
- Sequential gate execution for efficiency
|
||||
- Clear gate status reporting on PRs
|
||||
- Summary report with all gate results
|
||||
|
||||
### 🔄 Supporting Workflows
|
||||
|
||||
#### Issue and PR Triage (`triage.yml`)
|
||||
**Triggered on:** Issues (opened/edited/reopened) and Pull Requests (opened/reopened/synchronize/edited)
|
||||
|
||||
**Purpose:** Quickly categorize inbound work so reviewers know what to look at first.
|
||||
|
||||
- Auto-applies labels for type (bug/enhancement/docs/security/testing/performance) and area (frontend/backend/database/workflows/documentation)
|
||||
- Sets a default priority and highlights beginner-friendly issues
|
||||
- Flags missing information (repro steps, expected/actual results, versions) with a checklist comment
|
||||
- For PRs, labels areas touched, estimates risk based on change size and critical paths, and prompts for test plans/screenshots/linked issues
|
||||
- Mentions **@copilot** to sanity-check the triage with GitHub-native AI
|
||||
|
||||
This workflow runs alongside the gated pipeline to provide quick triage feedback.
|
||||
|
||||
### 🗑️ Legacy Workflows (Removed)
|
||||
|
||||
#### CI/CD Workflow (`ci/ci.yml`) - ❌ REMOVED
|
||||
**Status:** Deprecated and removed (Dec 2025)
|
||||
**Reason:** 100% functionality superseded by gated pipeline
|
||||
|
||||
**Jobs:** ~~Prisma Check, Lint, Build, E2E Tests, Quality Check~~
|
||||
|
||||
**Replacement:** Consolidated into `gated-pipeline.yml`
|
||||
|
||||
### 3. Automated Code Review (`pr/code-review.yml`)
|
||||
**Triggered on:** Pull request opened, synchronized, or reopened
|
||||
|
||||
**Features:**
|
||||
@@ -43,20 +146,21 @@ All workflows are designed to work seamlessly with **GitHub Copilot** to assist
|
||||
- ✅ React best practices
|
||||
- ✅ File size warnings
|
||||
|
||||
### 3. Auto Merge (`auto-merge.yml`)
|
||||
### 4. Auto Merge (`pr/auto-merge.yml`) - Updated for Gated Pipeline
|
||||
**Triggered on:** PR approval, CI workflow completion
|
||||
|
||||
**Features:**
|
||||
- Automatically merges PRs when:
|
||||
- PR is approved by reviewers
|
||||
- All CI checks pass (lint, build, e2e tests)
|
||||
- All gates pass in unified gated pipeline
|
||||
- No merge conflicts
|
||||
- PR is not in draft
|
||||
- **Automatically deletes the branch** after successful merge
|
||||
- Uses squash merge strategy
|
||||
- Posts comments about merge status
|
||||
- **Updated:** Now supports unified Enterprise Gated Pipeline checks
|
||||
|
||||
### 4. Issue Triage (`issue-triage.yml`)
|
||||
### 5. Issue Triage (`issue-triage.yml`)
|
||||
**Triggered on:** New issues opened, issues labeled
|
||||
|
||||
**Features:**
|
||||
@@ -68,7 +172,7 @@ All workflows are designed to work seamlessly with **GitHub Copilot** to assist
|
||||
- Suggests automated fix attempts for simple issues
|
||||
- Can create fix branches automatically with `create-pr` label
|
||||
|
||||
### 5. PR Management (`pr-management.yml`)
|
||||
### 6. PR Management (`pr/pr-management.yml`)
|
||||
**Triggered on:** PR opened, synchronized, labeled
|
||||
|
||||
**Features:**
|
||||
@@ -80,7 +184,7 @@ All workflows are designed to work seamlessly with **GitHub Copilot** to assist
|
||||
- Links related issues automatically
|
||||
- Posts comments on related issues
|
||||
|
||||
### 6. Merge Conflict Check (`merge-conflict-check.yml`)
|
||||
### 7. Merge Conflict Check (`pr/merge-conflict-check.yml`)
|
||||
**Triggered on:** PR opened/synchronized, push to main/master
|
||||
|
||||
**Features:**
|
||||
@@ -89,7 +193,7 @@ All workflows are designed to work seamlessly with **GitHub Copilot** to assist
|
||||
- Adds/removes `merge-conflict` label
|
||||
- Fails CI if conflicts exist
|
||||
|
||||
### 7. Planning & Design (`planning.yml`) 🆕
|
||||
### 8. Planning & Design (`quality/planning.yml`) 🆕
|
||||
**Triggered on:** Issues opened or labeled with enhancement/feature-request
|
||||
|
||||
**Features:**
|
||||
@@ -103,35 +207,7 @@ All workflows are designed to work seamlessly with **GitHub Copilot** to assist
|
||||
|
||||
**SDLC Phase:** Planning & Design
|
||||
|
||||
### 8. Development Assistance (`development.yml`) 🆕
|
||||
**Triggered on:** Push to feature branches, PR updates, @copilot mentions
|
||||
|
||||
**Features:**
|
||||
- **Continuous Quality Feedback**: Real-time code metrics and architectural compliance
|
||||
- **Declarative Ratio Tracking**: Monitors JSON/Lua vs TypeScript balance
|
||||
- **Component Size Monitoring**: Flags components exceeding 150 LOC
|
||||
- **Refactoring Suggestions**: Identifies opportunities for improvement
|
||||
- **@copilot Interaction Handler**: Responds to @copilot mentions with context-aware guidance
|
||||
- Provides architectural reminders and best practices
|
||||
- Suggests generic renderers over hardcoded components
|
||||
|
||||
**SDLC Phase:** Development
|
||||
|
||||
### 9. Deployment & Monitoring (`deployment.yml`) 🆕
|
||||
**Triggered on:** Push to main, releases, manual workflow dispatch
|
||||
|
||||
**Features:**
|
||||
- **Pre-Deployment Validation**: Schema validation, security audit, package size check
|
||||
- **Breaking Change Detection**: Identifies breaking commits
|
||||
- **Deployment Summary**: Generates release notes with categorized changes
|
||||
- **Post-Deployment Health Checks**: Verifies build integrity and critical files
|
||||
- **Deployment Tracking Issues**: Creates monitoring issues for releases
|
||||
- **Security Dependency Audit**: Detects and reports vulnerabilities
|
||||
- Auto-creates security issues for critical vulnerabilities
|
||||
|
||||
**SDLC Phase:** Deployment & Operations
|
||||
|
||||
### 10. Code Size Limits (`size-limits.yml`)
|
||||
### 9. Code Size Limits (`quality/size-limits.yml`)
|
||||
**Triggered on:** Pull requests, pushes to main (when source files change)
|
||||
|
||||
**Features:**
|
||||
@@ -145,11 +221,11 @@ All workflows are designed to work seamlessly with **GitHub Copilot** to assist
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ Planning │ ← planning.yml (Architecture Review, PRD Check)
|
||||
│ Planning │ ← quality/planning.yml (Architecture Review, PRD Check)
|
||||
└──────┬──────┘
|
||||
↓
|
||||
┌─────────────┐
|
||||
│ Development │ ← development.yml (Quality Feedback, Refactoring)
|
||||
│ Development │ ← gated-pipeline.yml Gate 4 (Dev Feedback, Copilot)
|
||||
└──────┬──────┘
|
||||
↓
|
||||
┌─────────────┐
|
||||
|
||||
327
.github/workflows/ci/ci.yml
vendored
327
.github/workflows/ci/ci.yml
vendored
@@ -1,327 +0,0 @@
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master, develop ]
|
||||
pull_request:
|
||||
branches: [ main, master, develop ]
|
||||
|
||||
jobs:
|
||||
prisma-check:
|
||||
name: Validate Prisma setup
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Validate Prisma Schema
|
||||
run: bunx prisma validate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
typecheck:
|
||||
name: TypeScript Type Check
|
||||
runs-on: ubuntu-latest
|
||||
needs: prisma-check
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Run TypeScript type check
|
||||
run: bun run typecheck
|
||||
|
||||
lint:
|
||||
name: Lint Code
|
||||
runs-on: ubuntu-latest
|
||||
needs: prisma-check
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Run ESLint
|
||||
run: bun run lint
|
||||
|
||||
test-unit:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: [typecheck, lint]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Run unit tests
|
||||
run: bun run test:unit
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Upload coverage report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: coverage-report
|
||||
path: frontends/nextjs/coverage/
|
||||
retention-days: 7
|
||||
|
||||
build:
|
||||
name: Build Application
|
||||
runs-on: ubuntu-latest
|
||||
needs: test-unit
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Build
|
||||
run: bun run build
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: dist
|
||||
path: frontends/nextjs/.next/
|
||||
retention-days: 7
|
||||
|
||||
test-e2e:
|
||||
name: E2E Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: [typecheck, lint]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: bunx playwright install --with-deps chromium
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: bun run test:e2e
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: playwright-report
|
||||
path: frontends/nextjs/playwright-report/
|
||||
retention-days: 7
|
||||
|
||||
test-dbal-daemon:
|
||||
name: DBAL Daemon E2E
|
||||
runs-on: ubuntu-latest
|
||||
needs: test-e2e
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: bunx playwright install --with-deps chromium
|
||||
|
||||
- name: Run DBAL daemon suite
|
||||
run: bun run test:e2e:dbal-daemon
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Upload daemon test report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: playwright-report-dbal-daemon
|
||||
path: frontends/nextjs/playwright-report/
|
||||
retention-days: 7
|
||||
|
||||
quality-check:
|
||||
name: Code Quality Check
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Check for console.log statements
|
||||
run: |
|
||||
if git diff origin/${{ github.base_ref }}...HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' | grep -E '^\+.*console\.(log|debug|info)'; then
|
||||
echo "⚠️ Found console.log statements in the changes"
|
||||
echo "Please remove console.log statements before merging"
|
||||
exit 1
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check for TODO comments
|
||||
run: |
|
||||
TODO_COUNT=$(git diff origin/${{ github.base_ref }}...HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' | grep -E '^\+.*TODO|FIXME' | wc -l)
|
||||
if [ $TODO_COUNT -gt 0 ]; then
|
||||
echo "⚠️ Found $TODO_COUNT TODO/FIXME comments in the changes"
|
||||
echo "Please address TODO comments before merging or create issues for them"
|
||||
fi
|
||||
continue-on-error: true
|
||||
114
.github/workflows/ci/cli.yml
vendored
114
.github/workflows/ci/cli.yml
vendored
@@ -1,57 +1,57 @@
|
||||
name: CLI Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
paths:
|
||||
- 'frontends/cli/**'
|
||||
- '.github/workflows/ci/cli.yml'
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
paths:
|
||||
- 'frontends/cli/**'
|
||||
- '.github/workflows/ci/cli.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build MetaBuilder CLI
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y cmake ninja-build python3-pip libssl-dev
|
||||
|
||||
- name: Install Conan
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install conan
|
||||
|
||||
- name: Detect Conan profile
|
||||
run: conan profile detect --force
|
||||
|
||||
- name: Install Conan dependencies
|
||||
run: |
|
||||
mkdir -p frontends/cli/build
|
||||
conan install frontends/cli \
|
||||
--output-folder frontends/cli/build \
|
||||
--build missing
|
||||
|
||||
- name: Configure CLI with CMake
|
||||
run: |
|
||||
cmake -S frontends/cli -B frontends/cli/build -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE=frontends/cli/build/conan_toolchain.cmake
|
||||
|
||||
- name: Build CLI executable
|
||||
run: cmake --build frontends/cli/build
|
||||
|
||||
- name: Run help command to verify binary
|
||||
run: frontends/cli/build/bin/metabuilder-cli --help
|
||||
name: CLI Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
paths:
|
||||
- 'frontends/cli/**'
|
||||
- '.github/workflows/ci/cli.yml'
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
paths:
|
||||
- 'frontends/cli/**'
|
||||
- '.github/workflows/ci/cli.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build MetaBuilder CLI
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y cmake ninja-build python3-pip libssl-dev
|
||||
|
||||
- name: Install Conan
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install conan
|
||||
|
||||
- name: Detect Conan profile
|
||||
run: conan profile detect --force
|
||||
|
||||
- name: Install Conan dependencies
|
||||
run: |
|
||||
mkdir -p frontends/cli/build
|
||||
conan install frontends/cli \
|
||||
--output-folder frontends/cli/build \
|
||||
--build missing
|
||||
|
||||
- name: Configure CLI with CMake
|
||||
run: |
|
||||
cmake -S frontends/cli -B frontends/cli/build -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE=frontends/cli/build/conan_toolchain.cmake
|
||||
|
||||
- name: Build CLI executable
|
||||
run: cmake --build frontends/cli/build
|
||||
|
||||
- name: Run help command to verify binary
|
||||
run: frontends/cli/build/bin/metabuilder-cli --help
|
||||
|
||||
614
.github/workflows/ci/cpp-build.yml
vendored
614
.github/workflows/ci/cpp-build.yml
vendored
@@ -1,308 +1,306 @@
|
||||
name: C++ Build & Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
paths:
|
||||
- 'dbal/cpp/**'
|
||||
- 'dbal/tools/cpp-build-assistant.cjs'
|
||||
- '.github/workflows/cpp-build.yml'
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
paths:
|
||||
- 'dbal/cpp/**'
|
||||
- 'dbal/tools/cpp-build-assistant.cjs'
|
||||
- '.github/workflows/cpp-build.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-implementation:
|
||||
name: Check C++ Implementation Status
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
has_sources: ${{ steps.check.outputs.has_sources }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check if C++ sources exist
|
||||
id: check
|
||||
run: |
|
||||
if [ -d "dbal/cpp/src" ] && [ "$(find dbal/cpp/src -name '*.cpp' | wc -l)" -gt 0 ]; then
|
||||
echo "has_sources=true" >> $GITHUB_OUTPUT
|
||||
echo "✓ C++ source files found"
|
||||
else
|
||||
echo "has_sources=false" >> $GITHUB_OUTPUT
|
||||
echo "⚠ C++ implementation not yet available - skipping build"
|
||||
fi
|
||||
|
||||
build-linux:
|
||||
name: Build on Linux
|
||||
runs-on: ubuntu-latest
|
||||
needs: check-implementation
|
||||
if: needs.check-implementation.outputs.has_sources == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
compiler:
|
||||
- { cc: gcc, cxx: g++ }
|
||||
- { cc: clang, cxx: clang++ }
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y cmake ninja-build ${{ matrix.compiler.cxx }}
|
||||
pip install conan
|
||||
|
||||
- name: Setup Conan profile
|
||||
run: conan profile detect --force
|
||||
|
||||
- name: Check C++ dependencies
|
||||
run: bun run cpp:check
|
||||
|
||||
- name: Initialize Conanfile
|
||||
run: bun run cpp:init
|
||||
|
||||
- name: Install Conan dependencies
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
|
||||
CC: ${{ matrix.compiler.cc }}
|
||||
CXX: ${{ matrix.compiler.cxx }}
|
||||
run: bun run cpp:install
|
||||
|
||||
- name: Configure CMake
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
|
||||
CC: ${{ matrix.compiler.cc }}
|
||||
CXX: ${{ matrix.compiler.cxx }}
|
||||
run: |
|
||||
if [ "${{ matrix.build_type }}" = "Debug" ]; then
|
||||
bun run cpp:build -- configure --debug
|
||||
else
|
||||
bun run cpp:configure
|
||||
fi
|
||||
|
||||
- name: Build C++ project
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
|
||||
run: bun run cpp:build
|
||||
|
||||
- name: Run C++ tests
|
||||
run: bun run cpp:test
|
||||
|
||||
- name: Upload build artifacts
|
||||
if: matrix.build_type == 'Release' && matrix.compiler.cxx == 'g++'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dbal-daemon-linux
|
||||
path: |
|
||||
dbal/cpp/build/dbal_daemon
|
||||
dbal/cpp/build/*.so
|
||||
retention-days: 7
|
||||
|
||||
build-macos:
|
||||
name: Build on macOS
|
||||
runs-on: macos-latest
|
||||
needs: check-implementation
|
||||
if: needs.check-implementation.outputs.has_sources == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
brew install cmake ninja conan
|
||||
|
||||
- name: Setup Conan profile
|
||||
run: conan profile detect --force
|
||||
|
||||
- name: Check C++ dependencies
|
||||
run: bun run cpp:check
|
||||
|
||||
- name: Full C++ build
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
|
||||
run: |
|
||||
if [ "${{ matrix.build_type }}" = "Debug" ]; then
|
||||
node dbal/tools/cpp-build-assistant.cjs full --debug
|
||||
else
|
||||
bun run cpp:full
|
||||
fi
|
||||
|
||||
- name: Run C++ tests
|
||||
run: bun run cpp:test
|
||||
|
||||
- name: Upload build artifacts
|
||||
if: matrix.build_type == 'Release'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dbal-daemon-macos
|
||||
path: |
|
||||
dbal/cpp/build/dbal_daemon
|
||||
dbal/cpp/build/*.dylib
|
||||
retention-days: 7
|
||||
|
||||
build-windows:
|
||||
name: Build on Windows
|
||||
runs-on: windows-latest
|
||||
needs: check-implementation
|
||||
if: needs.check-implementation.outputs.has_sources == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
choco install cmake ninja -y
|
||||
pip install conan
|
||||
|
||||
- name: Setup Conan profile
|
||||
run: conan profile detect --force
|
||||
|
||||
- name: Check C++ dependencies
|
||||
run: bun run cpp:check
|
||||
|
||||
- name: Full C++ build
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ matrix.build_type }}" = "Debug" ]; then
|
||||
node dbal/tools/cpp-build-assistant.cjs full --debug
|
||||
else
|
||||
bun run cpp:full
|
||||
fi
|
||||
|
||||
- name: Run C++ tests
|
||||
run: bun run cpp:test
|
||||
|
||||
- name: Upload build artifacts
|
||||
if: matrix.build_type == 'Release'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dbal-daemon-windows
|
||||
path: |
|
||||
dbal/cpp/build/dbal_daemon.exe
|
||||
dbal/cpp/build/*.dll
|
||||
retention-days: 7
|
||||
|
||||
code-quality:
|
||||
name: C++ Code Quality
|
||||
runs-on: ubuntu-latest
|
||||
needs: check-implementation
|
||||
if: needs.check-implementation.outputs.has_sources == 'true'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y cmake ninja-build cppcheck clang-format
|
||||
pip install conan
|
||||
|
||||
- name: Setup Conan
|
||||
run: conan profile detect --force
|
||||
|
||||
- name: Configure project
|
||||
run: bun run cpp:full
|
||||
|
||||
- name: Run cppcheck
|
||||
run: |
|
||||
cppcheck --enable=all --inconclusive --error-exitcode=1 \
|
||||
--suppress=missingIncludeSystem \
|
||||
-I dbal/cpp/include \
|
||||
dbal/cpp/src/
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check formatting
|
||||
run: |
|
||||
find dbal/cpp/src dbal/cpp/include -name '*.cpp' -o -name '*.hpp' | \
|
||||
xargs clang-format --dry-run --Werror
|
||||
continue-on-error: true
|
||||
|
||||
integration:
|
||||
name: Integration Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-implementation, build-linux]
|
||||
if: needs.check-implementation.outputs.has_sources == 'true'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Download Linux build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dbal-daemon-linux
|
||||
path: dbal/cpp/build/
|
||||
|
||||
- name: Make daemon executable
|
||||
run: chmod +x dbal/cpp/build/dbal_daemon
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
# Start C++ daemon
|
||||
./dbal/cpp/build/dbal_daemon &
|
||||
DAEMON_PID=$!
|
||||
sleep 2
|
||||
|
||||
# Run TypeScript integration tests
|
||||
bun run test:unit
|
||||
|
||||
# Cleanup
|
||||
kill $DAEMON_PID
|
||||
continue-on-error: true
|
||||
name: C++ Build & Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
paths:
|
||||
- 'dbal/production/**'
|
||||
- '.github/workflows/cpp-build.yml'
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
paths:
|
||||
- 'dbal/production/**'
|
||||
- '.github/workflows/cpp-build.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-implementation:
|
||||
name: Check C++ Implementation Status
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
has_sources: ${{ steps.check.outputs.has_sources }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check if C++ sources exist
|
||||
id: check
|
||||
run: |
|
||||
if [ -d "dbal/production/src" ] && [ "$(find dbal/production/src -name '*.cpp' | wc -l)" -gt 0 ]; then
|
||||
echo "has_sources=true" >> $GITHUB_OUTPUT
|
||||
echo "✓ C++ source files found"
|
||||
else
|
||||
echo "has_sources=false" >> $GITHUB_OUTPUT
|
||||
echo "⚠ C++ implementation not yet available - skipping build"
|
||||
fi
|
||||
|
||||
build-linux:
|
||||
name: Build on Linux
|
||||
runs-on: ubuntu-latest
|
||||
needs: check-implementation
|
||||
if: needs.check-implementation.outputs.has_sources == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
compiler:
|
||||
- { cc: gcc, cxx: g++ }
|
||||
- { cc: clang, cxx: clang++ }
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y cmake ninja-build ${{ matrix.compiler.cxx }}
|
||||
pip install conan
|
||||
|
||||
- name: Setup Conan profile
|
||||
run: conan profile detect --force
|
||||
|
||||
- name: Check C++ dependencies
|
||||
run: npm run cpp:check
|
||||
|
||||
- name: Initialize Conanfile
|
||||
run: npm run cpp:init
|
||||
|
||||
- name: Install Conan dependencies
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
|
||||
CC: ${{ matrix.compiler.cc }}
|
||||
CXX: ${{ matrix.compiler.cxx }}
|
||||
run: npm run cpp:install
|
||||
|
||||
- name: Configure CMake
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
|
||||
CC: ${{ matrix.compiler.cc }}
|
||||
CXX: ${{ matrix.compiler.cxx }}
|
||||
run: |
|
||||
if [ "${{ matrix.build_type }}" = "Debug" ]; then
|
||||
npm run cpp:build -- configure --debug
|
||||
else
|
||||
npm run cpp:configure
|
||||
fi
|
||||
|
||||
- name: Build C++ project
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
|
||||
run: npm run cpp:build
|
||||
|
||||
- name: Run C++ tests
|
||||
run: npm run cpp:test
|
||||
|
||||
- name: Upload build artifacts
|
||||
if: matrix.build_type == 'Release' && matrix.compiler.cxx == 'g++'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dbal-daemon-linux
|
||||
path: |
|
||||
dbal/production/build/dbal_daemon
|
||||
dbal/production/build/*.so
|
||||
retention-days: 7
|
||||
|
||||
build-macos:
|
||||
name: Build on macOS
|
||||
runs-on: macos-latest
|
||||
needs: check-implementation
|
||||
if: needs.check-implementation.outputs.has_sources == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
brew install cmake ninja conan
|
||||
|
||||
- name: Setup Conan profile
|
||||
run: conan profile detect --force
|
||||
|
||||
- name: Check C++ dependencies
|
||||
run: npm run cpp:check
|
||||
|
||||
- name: Full C++ build
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
|
||||
run: |
|
||||
if [ "${{ matrix.build_type }}" = "Debug" ]; then
|
||||
echo "skipping dbal/shared/tools cpp build assistant (tools/ removed)"
|
||||
else
|
||||
npm run cpp:full
|
||||
fi
|
||||
|
||||
- name: Run C++ tests
|
||||
run: npm run cpp:test
|
||||
|
||||
- name: Upload build artifacts
|
||||
if: matrix.build_type == 'Release'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dbal-daemon-macos
|
||||
path: |
|
||||
dbal/production/build/dbal_daemon
|
||||
dbal/production/build/*.dylib
|
||||
retention-days: 7
|
||||
|
||||
build-windows:
|
||||
name: Build on Windows
|
||||
runs-on: windows-latest
|
||||
needs: check-implementation
|
||||
if: needs.check-implementation.outputs.has_sources == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
choco install cmake ninja -y
|
||||
pip install conan
|
||||
|
||||
- name: Setup Conan profile
|
||||
run: conan profile detect --force
|
||||
|
||||
- name: Check C++ dependencies
|
||||
run: npm run cpp:check
|
||||
|
||||
- name: Full C++ build
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: ${{ matrix.build_type }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ matrix.build_type }}" = "Debug" ]; then
|
||||
echo "skipping dbal/shared/tools cpp build assistant (tools/ removed)"
|
||||
else
|
||||
npm run cpp:full
|
||||
fi
|
||||
|
||||
- name: Run C++ tests
|
||||
run: npm run cpp:test
|
||||
|
||||
- name: Upload build artifacts
|
||||
if: matrix.build_type == 'Release'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dbal-daemon-windows
|
||||
path: |
|
||||
dbal/production/build/dbal_daemon.exe
|
||||
dbal/production/build/*.dll
|
||||
retention-days: 7
|
||||
|
||||
code-quality:
|
||||
name: C++ Code Quality
|
||||
runs-on: ubuntu-latest
|
||||
needs: check-implementation
|
||||
if: needs.check-implementation.outputs.has_sources == 'true'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y cmake ninja-build cppcheck clang-format
|
||||
pip install conan
|
||||
|
||||
- name: Setup Conan
|
||||
run: conan profile detect --force
|
||||
|
||||
- name: Configure project
|
||||
run: npm run cpp:full
|
||||
|
||||
- name: Run cppcheck
|
||||
run: |
|
||||
cppcheck --enable=all --inconclusive --error-exitcode=1 \
|
||||
--suppress=missingIncludeSystem \
|
||||
-I dbal/production/include \
|
||||
dbal/production/src/
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check formatting
|
||||
run: |
|
||||
find dbal/production/src dbal/production/include -name '*.cpp' -o -name '*.hpp' | \
|
||||
xargs clang-format --dry-run --Werror
|
||||
continue-on-error: true
|
||||
|
||||
integration:
|
||||
name: Integration Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-implementation, build-linux]
|
||||
if: needs.check-implementation.outputs.has_sources == 'true'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install --frozen-lockfile
|
||||
|
||||
- name: Download Linux build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dbal-daemon-linux
|
||||
path: dbal/production/build/
|
||||
|
||||
- name: Make daemon executable
|
||||
run: chmod +x dbal/production/build/dbal_daemon
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
# Start C++ daemon
|
||||
./dbal/production/build/dbal_daemon &
|
||||
DAEMON_PID=$!
|
||||
sleep 2
|
||||
|
||||
# Run TypeScript integration tests
|
||||
npm run test:unit
|
||||
|
||||
# Cleanup
|
||||
kill $DAEMON_PID
|
||||
continue-on-error: true
|
||||
|
||||
398
.github/workflows/ci/detect-stubs.yml
vendored
398
.github/workflows/ci/detect-stubs.yml
vendored
@@ -1,199 +1,199 @@
|
||||
name: Stub Implementation Detection
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main, master, develop ]
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches: [ main, master, develop ]
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1' # Weekly on Monday
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
detect-stubs:
|
||||
name: Detect Stub Implementations
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: '1.3.4'
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
|
||||
path: |
|
||||
frontends/nextjs/node_modules
|
||||
~/.bun
|
||||
restore-keys: bun-deps-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
# Pattern-based stub detection
|
||||
- name: Detect stub patterns
|
||||
id: detect-patterns
|
||||
run: bunx tsx ../../tools/detect-stub-implementations.ts > stub-patterns.json
|
||||
continue-on-error: true
|
||||
|
||||
# Implementation completeness analysis
|
||||
- name: Analyze implementation completeness
|
||||
id: analyze-completeness
|
||||
run: bunx tsx ../../tools/analyze-implementation-completeness.ts > implementation-analysis.json
|
||||
continue-on-error: true
|
||||
|
||||
# Generate detailed report
|
||||
- name: Generate stub report
|
||||
id: generate-report
|
||||
run: bunx tsx ../../tools/generate-stub-report.ts > stub-report.md
|
||||
continue-on-error: true
|
||||
|
||||
# Check for unimplemented TODOs in changed files (PR only)
|
||||
- name: Check changed files for stubs
|
||||
if: github.event_name == 'pull_request'
|
||||
id: check-changed
|
||||
run: |
|
||||
git diff origin/${{ github.base_ref }}...HEAD -- 'src/**/*.{ts,tsx}' | \
|
||||
grep -E '^\+.*(TODO|FIXME|not implemented|stub|placeholder|mock)' | \
|
||||
tee changed-stubs.txt || true
|
||||
|
||||
STUB_COUNT=$(wc -l < changed-stubs.txt)
|
||||
echo "stub_count=$STUB_COUNT" >> $GITHUB_OUTPUT
|
||||
continue-on-error: true
|
||||
|
||||
# Post PR comment with findings
|
||||
- name: Post stub detection comment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
let comment = '## 🔍 Stub Implementation Detection Report\n\n';
|
||||
|
||||
try {
|
||||
const patternData = JSON.parse(fs.readFileSync('stub-patterns.json', 'utf8'));
|
||||
const completenessData = JSON.parse(fs.readFileSync('implementation-analysis.json', 'utf8'));
|
||||
|
||||
// Summary table
|
||||
comment += '### Summary\n\n';
|
||||
comment += `**Pattern-Based Stubs**: ${patternData.totalStubsFound}\n`;
|
||||
comment += `**Low Completeness Items**: ${completenessData.bySeverity.high + completenessData.bySeverity.medium}\n`;
|
||||
comment += `**Average Completeness**: ${completenessData.averageCompleteness}%\n\n`;
|
||||
|
||||
// Severity breakdown
|
||||
if (patternData.totalStubsFound > 0) {
|
||||
comment += '### Severity Breakdown (Patterns)\n\n';
|
||||
comment += `| Severity | Count |\n`;
|
||||
comment += `|----------|-------|\n`;
|
||||
comment += `| 🔴 Critical | ${patternData.bySeverity.high} |\n`;
|
||||
comment += `| 🟠 Medium | ${patternData.bySeverity.medium} |\n`;
|
||||
comment += `| 🟡 Low | ${patternData.bySeverity.low} |\n\n`;
|
||||
}
|
||||
|
||||
// Type breakdown
|
||||
if (Object.values(patternData.byType).some(v => v > 0)) {
|
||||
comment += '### Issue Types\n\n';
|
||||
for (const [type, count] of Object.entries(patternData.byType)) {
|
||||
if (count > 0) {
|
||||
comment += `- **${type}**: ${count}\n`;
|
||||
}
|
||||
}
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
// Critical issues
|
||||
if (patternData.criticalIssues && patternData.criticalIssues.length > 0) {
|
||||
comment += '### 🔴 Critical Issues Found\n\n';
|
||||
comment += '<details><summary>Click to expand</summary>\n\n';
|
||||
comment += `| File | Line | Function | Type |\n`;
|
||||
comment += `|------|------|----------|------|\n`;
|
||||
patternData.criticalIssues.slice(0, 10).forEach(issue => {
|
||||
comment += `| ${issue.file} | ${issue.line} | \`${issue.function}\` | ${issue.type} |\n`;
|
||||
});
|
||||
comment += '\n</details>\n\n';
|
||||
}
|
||||
|
||||
// Recommendations
|
||||
comment += '### 📋 Recommendations\n\n';
|
||||
comment += '- [ ] Review all critical stubs before merging\n';
|
||||
comment += '- [ ] Replace TODO comments with GitHub issues\n';
|
||||
comment += '- [ ] Implement placeholder functions before production\n';
|
||||
comment += '- [ ] Run `bun run test:check-functions` to ensure coverage\n';
|
||||
comment += '- [ ] Use type system to force implementation (avoid `any` types)\n\n';
|
||||
|
||||
// Artifacts info
|
||||
comment += '### 📁 Detailed Reports\n\n';
|
||||
comment += 'Full analysis available in artifacts:\n';
|
||||
comment += '- `stub-patterns.json` - Pattern-based detection results\n';
|
||||
comment += '- `implementation-analysis.json` - Completeness scoring\n';
|
||||
comment += '- `stub-report.md` - Detailed markdown report\n';
|
||||
} catch (e) {
|
||||
comment += '⚠️ Could not generate detailed report. Check logs for errors.\n';
|
||||
}
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment
|
||||
});
|
||||
|
||||
# Upload detailed reports
|
||||
- name: Upload stub detection reports
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: stub-detection-reports
|
||||
path: |
|
||||
stub-patterns.json
|
||||
implementation-analysis.json
|
||||
stub-report.md
|
||||
changed-stubs.txt
|
||||
retention-days: 30
|
||||
|
||||
# Create check run with summary
|
||||
- name: Create check run
|
||||
uses: actions/github-script@v7
|
||||
if: always()
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
let summary = '';
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync('stub-patterns.json', 'utf8'));
|
||||
summary = `Found ${data.totalStubsFound} stub implementations (${data.bySeverity.high} high severity)`;
|
||||
} catch (e) {
|
||||
summary = 'Stub detection completed. See artifacts for details.';
|
||||
}
|
||||
|
||||
github.rest.checks.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'Stub Implementation Detection',
|
||||
head_sha: context.sha,
|
||||
status: 'completed',
|
||||
conclusion: 'neutral',
|
||||
summary: summary
|
||||
});
|
||||
name: Stub Implementation Detection
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main, master, develop ]
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches: [ main, master, develop ]
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1' # Weekly on Monday
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
detect-stubs:
|
||||
name: Detect Stub Implementations
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Cache npm dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: npm-deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||
path: |
|
||||
frontends/nextjs/node_modules
|
||||
~/.npm
|
||||
restore-keys: npm-deps-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: npm run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
# Pattern-based stub detection
|
||||
- name: Detect stub patterns
|
||||
id: detect-patterns
|
||||
run: echo "skipping tools-based stub pattern detection (tools/ removed)" > stub-patterns.json
|
||||
continue-on-error: true
|
||||
|
||||
# Implementation completeness analysis
|
||||
- name: Analyze implementation completeness
|
||||
id: analyze-completeness
|
||||
run: echo "skipping tools-based implementation completeness analysis (tools/ removed)" > implementation-analysis.json
|
||||
continue-on-error: true
|
||||
|
||||
# Generate detailed report
|
||||
- name: Generate stub report
|
||||
id: generate-report
|
||||
run: echo "skipping tools-based stub report generation (tools/ removed)" > stub-report.md
|
||||
continue-on-error: true
|
||||
|
||||
# Check for unimplemented TODOs in changed files (PR only)
|
||||
- name: Check changed files for stubs
|
||||
if: github.event_name == 'pull_request'
|
||||
id: check-changed
|
||||
run: |
|
||||
git diff origin/${{ github.base_ref }}...HEAD -- 'src/**/*.{ts,tsx}' | \
|
||||
grep -E '^\+.*(TODO|FIXME|not implemented|stub|placeholder|mock)' | \
|
||||
tee changed-stubs.txt || true
|
||||
|
||||
STUB_COUNT=$(wc -l < changed-stubs.txt)
|
||||
echo "stub_count=$STUB_COUNT" >> $GITHUB_OUTPUT
|
||||
continue-on-error: true
|
||||
|
||||
# Post PR comment with findings
|
||||
- name: Post stub detection comment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
let comment = '## 🔍 Stub Implementation Detection Report\n\n';
|
||||
|
||||
try {
|
||||
const patternData = JSON.parse(fs.readFileSync('stub-patterns.json', 'utf8'));
|
||||
const completenessData = JSON.parse(fs.readFileSync('implementation-analysis.json', 'utf8'));
|
||||
|
||||
// Summary table
|
||||
comment += '### Summary\n\n';
|
||||
comment += `**Pattern-Based Stubs**: ${patternData.totalStubsFound}\n`;
|
||||
comment += `**Low Completeness Items**: ${completenessData.bySeverity.high + completenessData.bySeverity.medium}\n`;
|
||||
comment += `**Average Completeness**: ${completenessData.averageCompleteness}%\n\n`;
|
||||
|
||||
// Severity breakdown
|
||||
if (patternData.totalStubsFound > 0) {
|
||||
comment += '### Severity Breakdown (Patterns)\n\n';
|
||||
comment += `| Severity | Count |\n`;
|
||||
comment += `|----------|-------|\n`;
|
||||
comment += `| 🔴 Critical | ${patternData.bySeverity.high} |\n`;
|
||||
comment += `| 🟠 Medium | ${patternData.bySeverity.medium} |\n`;
|
||||
comment += `| 🟡 Low | ${patternData.bySeverity.low} |\n\n`;
|
||||
}
|
||||
|
||||
// Type breakdown
|
||||
if (Object.values(patternData.byType).some(v => v > 0)) {
|
||||
comment += '### Issue Types\n\n';
|
||||
for (const [type, count] of Object.entries(patternData.byType)) {
|
||||
if (count > 0) {
|
||||
comment += `- **${type}**: ${count}\n`;
|
||||
}
|
||||
}
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
// Critical issues
|
||||
if (patternData.criticalIssues && patternData.criticalIssues.length > 0) {
|
||||
comment += '### 🔴 Critical Issues Found\n\n';
|
||||
comment += '<details><summary>Click to expand</summary>\n\n';
|
||||
comment += `| File | Line | Function | Type |\n`;
|
||||
comment += `|------|------|----------|------|\n`;
|
||||
patternData.criticalIssues.slice(0, 10).forEach(issue => {
|
||||
comment += `| ${issue.file} | ${issue.line} | \`${issue.function}\` | ${issue.type} |\n`;
|
||||
});
|
||||
comment += '\n</details>\n\n';
|
||||
}
|
||||
|
||||
// Recommendations
|
||||
comment += '### 📋 Recommendations\n\n';
|
||||
comment += '- [ ] Review all critical stubs before merging\n';
|
||||
comment += '- [ ] Replace TODO comments with GitHub issues\n';
|
||||
comment += '- [ ] Implement placeholder functions before production\n';
|
||||
comment += '- [ ] Run `npm run test:check-functions` to ensure coverage\n';
|
||||
comment += '- [ ] Use type system to force implementation (avoid `any` types)\n\n';
|
||||
|
||||
// Artifacts info
|
||||
comment += '### 📁 Detailed Reports\n\n';
|
||||
comment += 'Full analysis available in artifacts:\n';
|
||||
comment += '- `stub-patterns.json` - Pattern-based detection results\n';
|
||||
comment += '- `implementation-analysis.json` - Completeness scoring\n';
|
||||
comment += '- `stub-report.md` - Detailed markdown report\n';
|
||||
} catch (e) {
|
||||
comment += '⚠️ Could not generate detailed report. Check logs for errors.\n';
|
||||
}
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment
|
||||
});
|
||||
|
||||
# Upload detailed reports
|
||||
- name: Upload stub detection reports
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: stub-detection-reports
|
||||
path: |
|
||||
stub-patterns.json
|
||||
implementation-analysis.json
|
||||
stub-report.md
|
||||
changed-stubs.txt
|
||||
retention-days: 30
|
||||
|
||||
# Create check run with summary
|
||||
- name: Create check run
|
||||
uses: actions/github-script@v7
|
||||
if: always()
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
let summary = '';
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync('stub-patterns.json', 'utf8'));
|
||||
summary = `Found ${data.totalStubsFound} stub implementations (${data.bySeverity.high} high severity)`;
|
||||
} catch (e) {
|
||||
summary = 'Stub detection completed. See artifacts for details.';
|
||||
}
|
||||
|
||||
github.rest.checks.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'Stub Implementation Detection',
|
||||
head_sha: context.sha,
|
||||
status: 'completed',
|
||||
conclusion: 'neutral',
|
||||
summary: summary
|
||||
});
|
||||
|
||||
139
.github/workflows/container-build.yml
vendored
Normal file
139
.github/workflows/container-build.yml
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
name: Build and Push GHCR Images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Push Docker Images
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- image: nextjs-app
|
||||
context: .
|
||||
dockerfile: ./frontends/nextjs/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- image: dbal-daemon
|
||||
context: ./dbal/production
|
||||
dockerfile: ./dbal/production/build-config/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha,prefix={{branch}}-
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ matrix.context }}
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: ${{ matrix.platforms }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
BUILD_DATE=${{ github.event.head_commit.timestamp }}
|
||||
VCS_REF=${{ github.sha }}
|
||||
VERSION=${{ steps.meta.outputs.version }}
|
||||
|
||||
- name: Generate artifact attestation
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }}
|
||||
subject-digest: ${{ steps.build.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
security-scan:
|
||||
name: Security Scan Images
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-push
|
||||
if: github.event_name != 'pull_request'
|
||||
strategy:
|
||||
matrix:
|
||||
image: [nextjs-app, dbal-daemon]
|
||||
steps:
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }}:${{ github.ref_name }}
|
||||
format: 'sarif'
|
||||
output: 'trivy-results-${{ matrix.image }}.sarif'
|
||||
|
||||
- name: Upload Trivy results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: 'trivy-results-${{ matrix.image }}.sarif'
|
||||
category: container-${{ matrix.image }}
|
||||
|
||||
publish-manifest:
|
||||
name: Create Multi-Arch Manifest
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-push
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create and push manifest for all images
|
||||
run: |
|
||||
for image in nextjs-app dbal-daemon; do
|
||||
docker manifest create \
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/$image:${{ github.ref_name }} \
|
||||
--amend ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/$image:${{ github.ref_name }}-amd64 \
|
||||
--amend ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/$image:${{ github.ref_name }}-arm64
|
||||
docker manifest push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/$image:${{ github.ref_name }}
|
||||
done
|
||||
385
.github/workflows/development.yml
vendored
385
.github/workflows/development.yml
vendored
@@ -1,385 +0,0 @@
|
||||
name: Development Assistance
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, ready_for_review]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
code-quality-feedback:
|
||||
name: Continuous Quality Feedback
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' && !github.event.pull_request.draft)
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: '1.3.4'
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
|
||||
path: |
|
||||
frontends/nextjs/node_modules
|
||||
~/.bun
|
||||
restore-keys: bun-deps-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Analyze code quality
|
||||
id: quality
|
||||
run: |
|
||||
# Run lint and capture output
|
||||
bun run lint > lint-output.txt 2>&1 || echo "LINT_FAILED=true" >> $GITHUB_OUTPUT
|
||||
|
||||
# Count TypeScript files and their sizes
|
||||
TOTAL_TS_FILES=$(find src -name "*.ts" -o -name "*.tsx" | wc -l)
|
||||
LARGE_FILES=$(find src -name "*.ts" -o -name "*.tsx" -exec wc -l {} \; | awk '$1 > 150 {print $2}' | wc -l)
|
||||
|
||||
echo "total_ts_files=$TOTAL_TS_FILES" >> $GITHUB_OUTPUT
|
||||
echo "large_files=$LARGE_FILES" >> $GITHUB_OUTPUT
|
||||
|
||||
# Check for declarative vs imperative balance
|
||||
JSON_FILES=$(find src packages -name "*.json" 2>/dev/null | wc -l)
|
||||
LUA_SCRIPTS=$(find src packages -name "*.lua" 2>/dev/null | wc -l)
|
||||
|
||||
echo "json_files=$JSON_FILES" >> $GITHUB_OUTPUT
|
||||
echo "lua_scripts=$LUA_SCRIPTS" >> $GITHUB_OUTPUT
|
||||
|
||||
cat lint-output.txt
|
||||
|
||||
- name: Check architectural compliance
|
||||
id: architecture
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let issues = [];
|
||||
let suggestions = [];
|
||||
|
||||
// Get changed files
|
||||
let changedFiles = [];
|
||||
if (context.eventName === 'pull_request') {
|
||||
const { data: files } = await github.rest.pulls.listFiles({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
changedFiles = files.map(f => f.filename);
|
||||
}
|
||||
|
||||
// Check for hardcoded components outside ui/
|
||||
const hardcodedComponents = changedFiles.filter(f =>
|
||||
f.endsWith('.tsx') &&
|
||||
f.includes('src/components/') &&
|
||||
!f.includes('src/components/ui/') &&
|
||||
!f.includes('src/components/shared/') &&
|
||||
!['RenderComponent', 'FieldRenderer', 'GenericPage'].some(g => f.includes(g))
|
||||
);
|
||||
|
||||
if (hardcodedComponents.length > 0) {
|
||||
suggestions.push(`Consider if these components could be declarative: ${hardcodedComponents.join(', ')}`);
|
||||
}
|
||||
|
||||
// Check for database changes without seed data
|
||||
const schemaChanged = changedFiles.some(f => f.includes('schema.prisma'));
|
||||
const seedChanged = changedFiles.some(f => f.includes('seed'));
|
||||
|
||||
if (schemaChanged && !seedChanged) {
|
||||
suggestions.push('Database schema changed but no seed data updates detected. Consider updating seed data.');
|
||||
}
|
||||
|
||||
// Check for new routes without PageRoutes table updates
|
||||
const routeFiles = changedFiles.filter(f => f.includes('Route') || f.includes('route'));
|
||||
if (routeFiles.length > 0) {
|
||||
suggestions.push('Route changes detected. Ensure PageRoutes table is updated for dynamic routing.');
|
||||
}
|
||||
|
||||
// Check for large TypeScript files
|
||||
const largeFiles = parseInt('${{ steps.quality.outputs.large_files }}');
|
||||
if (largeFiles > 0) {
|
||||
issues.push(`${largeFiles} TypeScript files exceed 150 lines. Consider breaking them into smaller components.`);
|
||||
}
|
||||
|
||||
return { issues, suggestions };
|
||||
|
||||
- name: Provide development feedback
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const analysis = JSON.parse('${{ steps.architecture.outputs.result }}');
|
||||
const totalFiles = parseInt('${{ steps.quality.outputs.total_ts_files }}');
|
||||
const largeFiles = parseInt('${{ steps.quality.outputs.large_files }}');
|
||||
const jsonFiles = parseInt('${{ steps.quality.outputs.json_files }}');
|
||||
const luaScripts = parseInt('${{ steps.quality.outputs.lua_scripts }}');
|
||||
|
||||
let comment = `## 💻 Development Quality Feedback\n\n`;
|
||||
|
||||
comment += `### 📊 Code Metrics\n\n`;
|
||||
comment += `- TypeScript files: ${totalFiles}\n`;
|
||||
comment += `- Files >150 LOC: ${largeFiles} ${largeFiles > 0 ? '⚠️' : '✅'}\n`;
|
||||
comment += `- JSON config files: ${jsonFiles}\n`;
|
||||
comment += `- Lua scripts: ${luaScripts}\n`;
|
||||
comment += `- Declarative ratio: ${((jsonFiles + luaScripts) / Math.max(totalFiles, 1) * 100).toFixed(1)}%\n\n`;
|
||||
|
||||
if (analysis.issues.length > 0) {
|
||||
comment += `### ⚠️ Architectural Issues\n\n`;
|
||||
analysis.issues.forEach(issue => comment += `- ${issue}\n`);
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
if (analysis.suggestions.length > 0) {
|
||||
comment += `### 💡 Suggestions\n\n`;
|
||||
analysis.suggestions.forEach(suggestion => comment += `- ${suggestion}\n`);
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
comment += `### 🎯 Project Goals Reminder\n\n`;
|
||||
comment += `- **Declarative First:** Prefer JSON + Lua over TypeScript\n`;
|
||||
comment += `- **Component Size:** Keep files under 150 LOC\n`;
|
||||
comment += `- **Generic Renderers:** Use RenderComponent for dynamic components\n`;
|
||||
comment += `- **Database-Driven:** Store configuration in database, not code\n`;
|
||||
comment += `- **Package-Based:** Organize features as importable packages\n\n`;
|
||||
|
||||
comment += `**@copilot** can help refactor code to better align with these principles.\n\n`;
|
||||
comment += `📖 See [Architecture Guidelines](/.github/copilot-instructions.md)`;
|
||||
|
||||
// Check if we already commented
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const botComment = comments.find(c =>
|
||||
c.user.type === 'Bot' && c.body.includes('Development Quality Feedback')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: comment
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
copilot-interaction:
|
||||
name: Handle Copilot Mentions
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event_name == 'issue_comment' &&
|
||||
contains(github.event.comment.body, '@copilot')
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Parse Copilot request
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const comment = context.payload.comment.body.toLowerCase();
|
||||
const issue = context.payload.issue;
|
||||
|
||||
let response = `## 🤖 Copilot Assistance\n\n`;
|
||||
|
||||
// Determine what the user is asking for
|
||||
if (comment.includes('implement') || comment.includes('fix this')) {
|
||||
response += `To implement this with Copilot assistance:\n\n`;
|
||||
response += `1. **Create a branch:** \`git checkout -b feature/issue-${issue.number}\`\n`;
|
||||
response += `2. **Use Copilot in your IDE** to generate code with context from:\n`;
|
||||
response += ` - [Copilot Instructions](/.github/copilot-instructions.md)\n`;
|
||||
response += ` - [PRD.md](/PRD.md)\n`;
|
||||
response += ` - Existing package structure in \`/packages/\`\n`;
|
||||
response += `3. **Follow the architectural principles:**\n`;
|
||||
response += ` - Declarative over imperative\n`;
|
||||
response += ` - Database-driven configuration\n`;
|
||||
response += ` - Generic renderers vs hardcoded components\n`;
|
||||
response += `4. **Test your changes:** \`bun run lint && bun run test:e2e\`\n`;
|
||||
response += `5. **Create a PR** - The automated workflows will review it\n\n`;
|
||||
}
|
||||
|
||||
if (comment.includes('review') || comment.includes('check')) {
|
||||
response += `Copilot can review this through:\n\n`;
|
||||
response += `- **Automated Code Review** workflow (runs on PRs)\n`;
|
||||
response += `- **Development Assistance** workflow (runs on pushes)\n`;
|
||||
response += `- **Planning & Design** workflow (runs on feature requests)\n\n`;
|
||||
response += `Create a PR to trigger comprehensive review!\n\n`;
|
||||
}
|
||||
|
||||
if (comment.includes('architecture') || comment.includes('design')) {
|
||||
response += `### 🏗️ Architectural Guidance\n\n`;
|
||||
response += `MetaBuilder follows these principles:\n\n`;
|
||||
response += `1. **5-Level Architecture:** User → Admin → God → SuperGod levels\n`;
|
||||
response += `2. **Multi-Tenant:** Isolated tenant instances with independent configs\n`;
|
||||
response += `3. **Declarative Components:** JSON config + Lua scripts, not TSX\n`;
|
||||
response += `4. **Package System:** Self-contained, importable feature bundles\n`;
|
||||
response += `5. **Database-First:** All config in Prisma, not hardcoded\n\n`;
|
||||
response += `📖 Full details: [PRD.md](/PRD.md)\n\n`;
|
||||
}
|
||||
|
||||
if (comment.includes('test') || comment.includes('e2e')) {
|
||||
response += `### 🧪 Testing with Copilot\n\n`;
|
||||
response += `\`\`\`bash\n`;
|
||||
response += `# Run E2E tests\n`;
|
||||
response += `bun run test:e2e\n\n`;
|
||||
response += `# Run with UI\n`;
|
||||
response += `bun run test:e2e:ui\n\n`;
|
||||
response += `# Run linter\n`;
|
||||
response += `bun run lint\n`;
|
||||
response += `\`\`\`\n\n`;
|
||||
response += `Use Copilot in your IDE to:\n`;
|
||||
response += `- Generate test cases based on user stories\n`;
|
||||
response += `- Write Playwright selectors and assertions\n`;
|
||||
response += `- Create mock data for tests\n\n`;
|
||||
}
|
||||
|
||||
if (comment.includes('help') || (!comment.includes('implement') && !comment.includes('review') && !comment.includes('architecture') && !comment.includes('test'))) {
|
||||
response += `### 🆘 How to Use Copilot\n\n`;
|
||||
response += `Mention **@copilot** in comments with:\n\n`;
|
||||
response += `- \`@copilot implement this\` - Get implementation guidance\n`;
|
||||
response += `- \`@copilot review this\` - Request code review\n`;
|
||||
response += `- \`@copilot architecture\` - Get architectural guidance\n`;
|
||||
response += `- \`@copilot test this\` - Get testing guidance\n`;
|
||||
response += `- \`@copilot fix this issue\` - Request automated fix\n\n`;
|
||||
response += `**In your IDE:**\n`;
|
||||
response += `- Use GitHub Copilot with context from [Copilot Instructions](/.github/copilot-instructions.md)\n`;
|
||||
response += `- Reference the [PRD](/PRD.md) when prompting\n`;
|
||||
response += `- Follow patterns from existing packages in \`/packages/\`\n\n`;
|
||||
}
|
||||
|
||||
response += `---\n`;
|
||||
response += `*This is an automated response. For detailed Copilot assistance, use the extension in your IDE with project context.*`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: response
|
||||
});
|
||||
|
||||
suggest-refactoring:
|
||||
name: Suggest Refactoring Opportunities
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request' && !github.event.pull_request.draft
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Analyze refactoring opportunities
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { data: files } = await github.rest.pulls.listFiles({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
|
||||
let opportunities = [];
|
||||
|
||||
// Look for opportunities in changed files
|
||||
for (const file of files) {
|
||||
const patch = file.patch || '';
|
||||
|
||||
// Check for repeated code patterns
|
||||
if (patch.split('\n').length > 100) {
|
||||
opportunities.push({
|
||||
file: file.filename,
|
||||
type: 'Size',
|
||||
suggestion: 'Large changeset - consider breaking into smaller PRs or extracting common utilities'
|
||||
});
|
||||
}
|
||||
|
||||
// Check for hardcoded values
|
||||
if (patch.match(/['"][A-Z_]{3,}['"]\s*:/)) {
|
||||
opportunities.push({
|
||||
file: file.filename,
|
||||
type: 'Configuration',
|
||||
suggestion: 'Hardcoded constants detected - consider moving to database configuration'
|
||||
});
|
||||
}
|
||||
|
||||
// Check for new TSX components
|
||||
if (file.filename.includes('components/') && file.filename.endsWith('.tsx') && file.status === 'added') {
|
||||
opportunities.push({
|
||||
file: file.filename,
|
||||
type: 'Architecture',
|
||||
suggestion: 'New component added - could this be implemented declaratively with JSON + Lua?'
|
||||
});
|
||||
}
|
||||
|
||||
// Check for inline styles or complex class strings
|
||||
if (patch.includes('style={{') || patch.match(/className="[^"]{50,}"/)) {
|
||||
opportunities.push({
|
||||
file: file.filename,
|
||||
type: 'Styling',
|
||||
suggestion: 'Complex styling detected - consider extracting to theme configuration'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (opportunities.length > 0) {
|
||||
let comment = `## 🔄 Refactoring Opportunities\n\n`;
|
||||
comment += `**@copilot** identified potential improvements:\n\n`;
|
||||
|
||||
const grouped = {};
|
||||
opportunities.forEach(opp => {
|
||||
if (!grouped[opp.type]) grouped[opp.type] = [];
|
||||
grouped[opp.type].push(opp);
|
||||
});
|
||||
|
||||
for (const [type, opps] of Object.entries(grouped)) {
|
||||
comment += `### ${type}\n\n`;
|
||||
opps.forEach(opp => {
|
||||
comment += `- **${opp.file}**: ${opp.suggestion}\n`;
|
||||
});
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
comment += `---\n`;
|
||||
comment += `These are suggestions, not requirements. Consider them as part of continuous improvement.\n\n`;
|
||||
comment += `Use **@copilot** in your IDE to help implement these refactorings.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
1287
.github/workflows/gated-pipeline.yml
vendored
Normal file
1287
.github/workflows/gated-pipeline.yml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
364
.github/workflows/issue-triage.yml
vendored
364
.github/workflows/issue-triage.yml
vendored
@@ -1,182 +1,182 @@
|
||||
name: Issue Triage and Auto-Fix
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
triage-issue:
|
||||
name: Triage and Label Issues
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Analyze and label issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const title = issue.title.toLowerCase();
|
||||
const body = (issue.body || '').toLowerCase();
|
||||
const text = title + ' ' + body;
|
||||
|
||||
let labels = [];
|
||||
|
||||
// Categorize by type
|
||||
if (text.match(/bug|error|crash|broken|fail/)) {
|
||||
labels.push('bug');
|
||||
}
|
||||
if (text.match(/feature|enhancement|add|new|implement/)) {
|
||||
labels.push('enhancement');
|
||||
}
|
||||
if (text.match(/document|readme|docs|guide/)) {
|
||||
labels.push('documentation');
|
||||
}
|
||||
if (text.match(/test|testing|spec|e2e/)) {
|
||||
labels.push('testing');
|
||||
}
|
||||
if (text.match(/security|vulnerability|exploit|xss|sql/)) {
|
||||
labels.push('security');
|
||||
}
|
||||
if (text.match(/performance|slow|optimize|speed/)) {
|
||||
labels.push('performance');
|
||||
}
|
||||
|
||||
// Categorize by priority
|
||||
if (text.match(/critical|urgent|asap|blocker/)) {
|
||||
labels.push('priority: high');
|
||||
} else if (text.match(/minor|low|nice to have/)) {
|
||||
labels.push('priority: low');
|
||||
} else {
|
||||
labels.push('priority: medium');
|
||||
}
|
||||
|
||||
// Check if it's a good first issue
|
||||
if (text.match(/beginner|easy|simple|starter/) || labels.length <= 2) {
|
||||
labels.push('good first issue');
|
||||
}
|
||||
|
||||
// Check if AI can help
|
||||
if (labels.includes('bug') || labels.includes('documentation') || labels.includes('testing')) {
|
||||
labels.push('ai-fixable');
|
||||
}
|
||||
|
||||
// Add labels
|
||||
if (labels.length > 0) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: labels
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Some labels may not exist:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Post welcome comment
|
||||
const aiHelpText = labels.includes('ai-fixable')
|
||||
? '\n\n🤖 This issue appears to be something AI can help with! A fix may be automatically attempted.'
|
||||
: '';
|
||||
|
||||
const comment = '👋 Thank you for opening this issue!\n\n' +
|
||||
'This issue has been automatically labeled as: ' + labels.join(', ') +
|
||||
aiHelpText + '\n\n' +
|
||||
'A maintainer will review this issue soon. In the meantime, please make sure you have provided:\n' +
|
||||
'- A clear description of the issue\n' +
|
||||
'- Steps to reproduce (for bugs)\n' +
|
||||
'- Expected vs actual behavior\n' +
|
||||
'- Any relevant error messages or screenshots\n\n' +
|
||||
'Copilot may be able to help with this issue.';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
attempt-auto-fix:
|
||||
name: Attempt Automated Fix
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'ai-fixable') ||
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'auto-fix')
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Analyze issue and suggest fix
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const labelList = issue.labels.map(l => l.name).join(', ');
|
||||
|
||||
const comment = '🤖 **AI-Assisted Fix Attempt**\n\n' +
|
||||
'I have analyzed this issue and here are my suggestions:\n\n' +
|
||||
'**Issue Type:** ' + labelList + '\n\n' +
|
||||
'**Suggested Actions:**\n' +
|
||||
'1. Review the issue description carefully\n' +
|
||||
'2. Check for similar issues in the repository history\n' +
|
||||
'3. Consider using Copilot to help implement the fix\n\n' +
|
||||
'**To request an automated fix:**\n' +
|
||||
'- Add the auto-fix label to this issue\n' +
|
||||
'- Ensure the issue description clearly explains:\n' +
|
||||
' - What needs to be fixed\n' +
|
||||
' - Where the issue is located (file/line if known)\n' +
|
||||
' - Expected behavior\n\n' +
|
||||
'**Note:** Complex issues may require human review before implementation.\n\n' +
|
||||
'Would you like me to attempt an automated fix? If so, please confirm by commenting "Copilot fix this issue".';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
create-fix-pr:
|
||||
name: Create Fix PR
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'labeled' && github.event.label.name == 'create-pr'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Create fix branch and PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const branchName = 'auto-fix/issue-' + issue.number;
|
||||
|
||||
const comment = '🤖 **Automated Fix PR Creation**\n\n' +
|
||||
'I have created a branch ' + branchName + ' for this fix.\n\n' +
|
||||
'**Next Steps:**\n' +
|
||||
'1. A developer or Copilot will work on the fix in this branch\n' +
|
||||
'2. A pull request will be created automatically\n' +
|
||||
'3. The PR will be linked to this issue\n\n' +
|
||||
'**Branch:** ' + branchName + '\n\n' +
|
||||
'To work on this fix:\n' +
|
||||
'git fetch origin\n' +
|
||||
'git checkout ' + branchName + '\n\n' +
|
||||
'This issue will be automatically closed when the PR is merged.';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
name: Issue Triage and Auto-Fix
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
triage-issue:
|
||||
name: Triage and Label Issues
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Analyze and label issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const title = issue.title.toLowerCase();
|
||||
const body = (issue.body || '').toLowerCase();
|
||||
const text = title + ' ' + body;
|
||||
|
||||
let labels = [];
|
||||
|
||||
// Categorize by type
|
||||
if (text.match(/bug|error|crash|broken|fail/)) {
|
||||
labels.push('bug');
|
||||
}
|
||||
if (text.match(/feature|enhancement|add|new|implement/)) {
|
||||
labels.push('enhancement');
|
||||
}
|
||||
if (text.match(/document|readme|docs|guide/)) {
|
||||
labels.push('documentation');
|
||||
}
|
||||
if (text.match(/test|testing|spec|e2e/)) {
|
||||
labels.push('testing');
|
||||
}
|
||||
if (text.match(/security|vulnerability|exploit|xss|sql/)) {
|
||||
labels.push('security');
|
||||
}
|
||||
if (text.match(/performance|slow|optimize|speed/)) {
|
||||
labels.push('performance');
|
||||
}
|
||||
|
||||
// Categorize by priority
|
||||
if (text.match(/critical|urgent|asap|blocker/)) {
|
||||
labels.push('priority: high');
|
||||
} else if (text.match(/minor|low|nice to have/)) {
|
||||
labels.push('priority: low');
|
||||
} else {
|
||||
labels.push('priority: medium');
|
||||
}
|
||||
|
||||
// Check if it's a good first issue
|
||||
if (text.match(/beginner|easy|simple|starter/) || labels.length <= 2) {
|
||||
labels.push('good first issue');
|
||||
}
|
||||
|
||||
// Check if AI can help
|
||||
if (labels.includes('bug') || labels.includes('documentation') || labels.includes('testing')) {
|
||||
labels.push('ai-fixable');
|
||||
}
|
||||
|
||||
// Add labels
|
||||
if (labels.length > 0) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: labels
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Some labels may not exist:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Post welcome comment
|
||||
const aiHelpText = labels.includes('ai-fixable')
|
||||
? '\n\n🤖 This issue appears to be something AI can help with! A fix may be automatically attempted.'
|
||||
: '';
|
||||
|
||||
const comment = '👋 Thank you for opening this issue!\n\n' +
|
||||
'This issue has been automatically labeled as: ' + labels.join(', ') +
|
||||
aiHelpText + '\n\n' +
|
||||
'A maintainer will review this issue soon. In the meantime, please make sure you have provided:\n' +
|
||||
'- A clear description of the issue\n' +
|
||||
'- Steps to reproduce (for bugs)\n' +
|
||||
'- Expected vs actual behavior\n' +
|
||||
'- Any relevant error messages or screenshots\n\n' +
|
||||
'Copilot may be able to help with this issue.';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
attempt-auto-fix:
|
||||
name: Attempt Automated Fix
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'ai-fixable') ||
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'auto-fix')
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Analyze issue and suggest fix
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const labelList = issue.labels.map(l => l.name).join(', ');
|
||||
|
||||
const comment = '🤖 **AI-Assisted Fix Attempt**\n\n' +
|
||||
'I have analyzed this issue and here are my suggestions:\n\n' +
|
||||
'**Issue Type:** ' + labelList + '\n\n' +
|
||||
'**Suggested Actions:**\n' +
|
||||
'1. Review the issue description carefully\n' +
|
||||
'2. Check for similar issues in the repository history\n' +
|
||||
'3. Consider using Copilot to help implement the fix\n\n' +
|
||||
'**To request an automated fix:**\n' +
|
||||
'- Add the auto-fix label to this issue\n' +
|
||||
'- Ensure the issue description clearly explains:\n' +
|
||||
' - What needs to be fixed\n' +
|
||||
' - Where the issue is located (file/line if known)\n' +
|
||||
' - Expected behavior\n\n' +
|
||||
'**Note:** Complex issues may require human review before implementation.\n\n' +
|
||||
'Would you like me to attempt an automated fix? If so, please confirm by commenting "Copilot fix this issue".';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
create-fix-pr:
|
||||
name: Create Fix PR
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'labeled' && github.event.label.name == 'create-pr'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Create fix branch and PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const branchName = 'auto-fix/issue-' + issue.number;
|
||||
|
||||
const comment = '🤖 **Automated Fix PR Creation**\n\n' +
|
||||
'I have created a branch ' + branchName + ' for this fix.\n\n' +
|
||||
'**Next Steps:**\n' +
|
||||
'1. A developer or Copilot will work on the fix in this branch\n' +
|
||||
'2. A pull request will be created automatically\n' +
|
||||
'3. The PR will be linked to this issue\n\n' +
|
||||
'**Branch:** ' + branchName + '\n\n' +
|
||||
'To work on this fix:\n' +
|
||||
'git fetch origin\n' +
|
||||
'git checkout ' + branchName + '\n\n' +
|
||||
'This issue will be automatically closed when the PR is merged.';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
387
.github/workflows/pr/auto-merge.yml
vendored
387
.github/workflows/pr/auto-merge.yml
vendored
@@ -1,185 +1,202 @@
|
||||
name: Auto Merge
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
check_suite:
|
||||
types: [completed]
|
||||
workflow_run:
|
||||
workflows: ["CI/CD"]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
auto-merge:
|
||||
name: Auto Merge PR
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
${{
|
||||
(github.event_name == 'pull_request_review' && github.event.review.state == 'approved') ||
|
||||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
|
||||
}}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check PR status and merge
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
// Get PR number from event
|
||||
let prNumber;
|
||||
|
||||
if (context.payload.pull_request) {
|
||||
prNumber = context.payload.pull_request.number;
|
||||
} else if (context.payload.workflow_run) {
|
||||
// Get PR from workflow run
|
||||
const { data: prs } = await github.rest.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}`
|
||||
});
|
||||
if (prs.length === 0) {
|
||||
console.log('No open PR found for this branch');
|
||||
return;
|
||||
}
|
||||
prNumber = prs[0].number;
|
||||
} else {
|
||||
console.log('Could not determine PR number');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Checking PR #${prNumber}`);
|
||||
|
||||
// Get PR details
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
if (pr.state !== 'open') {
|
||||
console.log('PR is not open');
|
||||
return;
|
||||
}
|
||||
|
||||
if (pr.draft) {
|
||||
console.log('PR is still in draft');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if PR is approved
|
||||
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
const latestReviews = {};
|
||||
for (const review of reviews) {
|
||||
latestReviews[review.user.login] = review.state;
|
||||
}
|
||||
|
||||
const hasApproval = Object.values(latestReviews).includes('APPROVED');
|
||||
const hasRequestChanges = Object.values(latestReviews).includes('CHANGES_REQUESTED');
|
||||
|
||||
if (!hasApproval) {
|
||||
console.log('PR has not been approved yet');
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasRequestChanges) {
|
||||
console.log('PR has requested changes');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CI status
|
||||
const { data: checks } = await github.rest.checks.listForRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: pr.head.sha
|
||||
});
|
||||
|
||||
const requiredChecks = ['Lint Code', 'Build Application', 'E2E Tests'];
|
||||
const checkStatuses = {};
|
||||
|
||||
for (const check of checks.check_runs) {
|
||||
checkStatuses[check.name] = check.conclusion;
|
||||
}
|
||||
|
||||
console.log('Check statuses:', checkStatuses);
|
||||
|
||||
// Wait for all required checks to pass
|
||||
const allChecksPassed = requiredChecks.every(checkName =>
|
||||
checkStatuses[checkName] === 'success' || checkStatuses[checkName] === 'skipped'
|
||||
);
|
||||
|
||||
if (!allChecksPassed) {
|
||||
console.log('Not all required checks have passed');
|
||||
|
||||
// Check if any checks failed
|
||||
const anyChecksFailed = Object.values(checkStatuses).some(status =>
|
||||
status === 'failure'
|
||||
);
|
||||
|
||||
if (anyChecksFailed) {
|
||||
console.log('Some checks failed, not merging');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Checks are still running, will retry later');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('All conditions met, merging PR');
|
||||
|
||||
// Add comment before merging
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: '✅ All checks passed and PR is approved! Auto-merging and cleaning up branch.'
|
||||
});
|
||||
|
||||
try {
|
||||
// Merge the PR
|
||||
await github.rest.pulls.merge({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
merge_method: 'squash',
|
||||
commit_title: `${pr.title} (#${prNumber})`,
|
||||
commit_message: pr.body || ''
|
||||
});
|
||||
|
||||
console.log('PR merged successfully');
|
||||
|
||||
// Delete the branch
|
||||
try {
|
||||
await github.rest.git.deleteRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: `heads/${pr.head.ref}`
|
||||
});
|
||||
console.log(`Branch ${pr.head.ref} deleted successfully`);
|
||||
} catch (deleteError) {
|
||||
console.log('Could not delete branch:', deleteError.message);
|
||||
// Don't fail the workflow if branch deletion fails
|
||||
}
|
||||
|
||||
} catch (mergeError) {
|
||||
console.error('Failed to merge PR:', mergeError.message);
|
||||
|
||||
// Post comment about merge failure
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: `❌ Auto-merge failed: ${mergeError.message}\n\nPlease merge manually.`
|
||||
});
|
||||
}
|
||||
name: Auto Merge
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
check_suite:
|
||||
types: [completed]
|
||||
workflow_run:
|
||||
workflows: ["CI/CD", "Enterprise Gated CI/CD Pipeline"]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
auto-merge:
|
||||
name: Auto Merge PR
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
${{
|
||||
(github.event_name == 'pull_request_review' && github.event.review.state == 'approved') ||
|
||||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
|
||||
}}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check PR status and merge
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
// Get PR number from event
|
||||
let prNumber;
|
||||
|
||||
if (context.payload.pull_request) {
|
||||
prNumber = context.payload.pull_request.number;
|
||||
} else if (context.payload.workflow_run) {
|
||||
// Get PR from workflow run
|
||||
const { data: prs } = await github.rest.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}`
|
||||
});
|
||||
if (prs.length === 0) {
|
||||
console.log('No open PR found for this branch');
|
||||
return;
|
||||
}
|
||||
prNumber = prs[0].number;
|
||||
} else {
|
||||
console.log('Could not determine PR number');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Checking PR #${prNumber}`);
|
||||
|
||||
// Get PR details
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
if (pr.state !== 'open') {
|
||||
console.log('PR is not open');
|
||||
return;
|
||||
}
|
||||
|
||||
if (pr.draft) {
|
||||
console.log('PR is still in draft');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if PR is approved
|
||||
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
const latestReviews = {};
|
||||
for (const review of reviews) {
|
||||
latestReviews[review.user.login] = review.state;
|
||||
}
|
||||
|
||||
const hasApproval = Object.values(latestReviews).includes('APPROVED');
|
||||
const hasRequestChanges = Object.values(latestReviews).includes('CHANGES_REQUESTED');
|
||||
|
||||
if (!hasApproval) {
|
||||
console.log('PR has not been approved yet');
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasRequestChanges) {
|
||||
console.log('PR has requested changes');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CI status - support both old and new gated workflows
|
||||
const { data: checks } = await github.rest.checks.listForRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: pr.head.sha
|
||||
});
|
||||
|
||||
// Required checks for old CI/CD workflow
|
||||
const legacyRequiredChecks = ['Lint Code', 'Build Application', 'E2E Tests'];
|
||||
|
||||
// Required gate checks for new Enterprise Gated CI/CD Pipeline
|
||||
const gatedRequiredChecks = [
|
||||
'Gate 1: Code Quality - Passed ✅',
|
||||
'Gate 2: Testing - Passed ✅',
|
||||
'Gate 3: Build & Package - Passed ✅'
|
||||
];
|
||||
|
||||
const checkStatuses = {};
|
||||
|
||||
for (const check of checks.check_runs) {
|
||||
checkStatuses[check.name] = check.conclusion;
|
||||
}
|
||||
|
||||
console.log('Check statuses:', checkStatuses);
|
||||
|
||||
// Check if using new gated workflow or old workflow
|
||||
const hasGatedChecks = gatedRequiredChecks.some(checkName =>
|
||||
checkStatuses[checkName] !== undefined
|
||||
);
|
||||
|
||||
const requiredChecks = hasGatedChecks ? gatedRequiredChecks : legacyRequiredChecks;
|
||||
console.log('Using checks:', hasGatedChecks ? 'Enterprise Gated' : 'Legacy');
|
||||
|
||||
// Wait for all required checks to pass
|
||||
const allChecksPassed = requiredChecks.every(checkName =>
|
||||
checkStatuses[checkName] === 'success' || checkStatuses[checkName] === 'skipped'
|
||||
);
|
||||
|
||||
if (!allChecksPassed) {
|
||||
console.log('Not all required checks have passed');
|
||||
|
||||
// Check if any checks failed
|
||||
const anyChecksFailed = Object.values(checkStatuses).some(status =>
|
||||
status === 'failure'
|
||||
);
|
||||
|
||||
if (anyChecksFailed) {
|
||||
console.log('Some checks failed, not merging');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Checks are still running, will retry later');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('All conditions met, merging PR');
|
||||
|
||||
// Add comment before merging
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: '✅ All checks passed and PR is approved! Auto-merging and cleaning up branch.'
|
||||
});
|
||||
|
||||
try {
|
||||
// Merge the PR
|
||||
await github.rest.pulls.merge({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
merge_method: 'squash',
|
||||
commit_title: `${pr.title} (#${prNumber})`,
|
||||
commit_message: pr.body || ''
|
||||
});
|
||||
|
||||
console.log('PR merged successfully');
|
||||
|
||||
// Delete the branch
|
||||
try {
|
||||
await github.rest.git.deleteRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: `heads/${pr.head.ref}`
|
||||
});
|
||||
console.log(`Branch ${pr.head.ref} deleted successfully`);
|
||||
} catch (deleteError) {
|
||||
console.log('Could not delete branch:', deleteError.message);
|
||||
// Don't fail the workflow if branch deletion fails
|
||||
}
|
||||
|
||||
} catch (mergeError) {
|
||||
console.error('Failed to merge PR:', mergeError.message);
|
||||
|
||||
// Post comment about merge failure
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: `❌ Auto-merge failed: ${mergeError.message}\n\nPlease merge manually.`
|
||||
});
|
||||
}
|
||||
|
||||
554
.github/workflows/pr/code-review.yml
vendored
554
.github/workflows/pr/code-review.yml
vendored
@@ -1,277 +1,277 @@
|
||||
name: Automated Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
checks: read
|
||||
|
||||
jobs:
|
||||
automated-review:
|
||||
name: AI-Assisted Code Review
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: '1.3.4'
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
|
||||
path: |
|
||||
frontends/nextjs/node_modules
|
||||
~/.bun
|
||||
restore-keys: bun-deps-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Run linter for review
|
||||
id: lint
|
||||
run: |
|
||||
bun run lint > lint-output.txt 2>&1 || echo "LINT_FAILED=true" >> $GITHUB_OUTPUT
|
||||
cat lint-output.txt
|
||||
continue-on-error: true
|
||||
|
||||
- name: Analyze code changes
|
||||
id: analyze
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
// Get PR diff
|
||||
const { data: files } = await github.rest.pulls.listFiles({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
|
||||
let issues = [];
|
||||
let warnings = [];
|
||||
let suggestions = [];
|
||||
|
||||
// Analyze each file
|
||||
for (const file of files) {
|
||||
const patch = file.patch || '';
|
||||
const filename = file.filename;
|
||||
|
||||
// Check for security issues
|
||||
if (patch.match(/eval\s*\(/)) {
|
||||
issues.push(`⚠️ **Security**: Use of \`eval()\` found in ${filename}`);
|
||||
}
|
||||
if (patch.match(/innerHTML\s*=/)) {
|
||||
warnings.push(`⚠️ **Security**: Direct \`innerHTML\` usage in ${filename}. Consider using safer alternatives.`);
|
||||
}
|
||||
if (patch.match(/dangerouslySetInnerHTML/)) {
|
||||
warnings.push(`⚠️ **Security**: \`dangerouslySetInnerHTML\` usage in ${filename}. Ensure content is sanitized.`);
|
||||
}
|
||||
|
||||
// Check for code quality
|
||||
if (patch.match(/console\.(log|debug|info)/)) {
|
||||
warnings.push(`🔍 **Code Quality**: Console statements found in ${filename}. Remove before merging.`);
|
||||
}
|
||||
if (patch.match(/debugger/)) {
|
||||
issues.push(`🐛 **Debug Code**: Debugger statement found in ${filename}. Remove before merging.`);
|
||||
}
|
||||
if (patch.match(/(:\s*any\b|\bany\s*[>;,\)])/)) {
|
||||
suggestions.push(`💡 **Type Safety**: Consider replacing \`any\` types with specific types in ${filename}`);
|
||||
}
|
||||
|
||||
// Check for best practices
|
||||
if (filename.endsWith('.tsx') || filename.endsWith('.jsx')) {
|
||||
if (patch.match(/useEffect.*\[\]/) && !patch.includes('// eslint-disable')) {
|
||||
suggestions.push(`💡 **React**: Empty dependency array in useEffect in ${filename}. Verify if intentional.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for large files
|
||||
if (file.additions > 500) {
|
||||
warnings.push(`📏 **File Size**: ${filename} has ${file.additions} additions. Consider breaking into smaller files.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Read lint output if exists
|
||||
let lintIssues = '';
|
||||
try {
|
||||
lintIssues = fs.readFileSync('lint-output.txt', 'utf8');
|
||||
} catch (e) {
|
||||
// File doesn't exist
|
||||
}
|
||||
|
||||
// Determine if auto-approve is appropriate
|
||||
const hasBlockingIssues = issues.length > 0 || lintIssues.includes('error');
|
||||
|
||||
return {
|
||||
issues,
|
||||
warnings,
|
||||
suggestions,
|
||||
lintIssues,
|
||||
hasBlockingIssues,
|
||||
fileCount: files.length,
|
||||
totalAdditions: files.reduce((sum, f) => sum + f.additions, 0),
|
||||
totalDeletions: files.reduce((sum, f) => sum + f.deletions, 0)
|
||||
};
|
||||
|
||||
- name: Post review comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const analysis = JSON.parse('${{ steps.analyze.outputs.result }}');
|
||||
|
||||
let comment = '## 🤖 Automated Code Review\n\n';
|
||||
comment += `**Changes Summary:**\n`;
|
||||
comment += `- Files changed: ${analysis.fileCount}\n`;
|
||||
comment += `- Lines added: ${analysis.totalAdditions}\n`;
|
||||
comment += `- Lines deleted: ${analysis.totalDeletions}\n\n`;
|
||||
|
||||
if (analysis.issues.length > 0) {
|
||||
comment += '### ❌ Blocking Issues\n\n';
|
||||
analysis.issues.forEach(issue => comment += `- ${issue}\n`);
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
if (analysis.warnings.length > 0) {
|
||||
comment += '### ⚠️ Warnings\n\n';
|
||||
analysis.warnings.forEach(warning => comment += `- ${warning}\n`);
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
if (analysis.suggestions.length > 0) {
|
||||
comment += '### 💡 Suggestions\n\n';
|
||||
analysis.suggestions.forEach(suggestion => comment += `- ${suggestion}\n`);
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
if (analysis.lintIssues && analysis.lintIssues.includes('error')) {
|
||||
comment += '### 🔴 Linting Errors\n\n';
|
||||
comment += '```\n' + analysis.lintIssues + '\n```\n\n';
|
||||
}
|
||||
|
||||
if (analysis.hasBlockingIssues) {
|
||||
comment += '---\n';
|
||||
comment += '### ❌ Review Status: **CHANGES REQUESTED**\n\n';
|
||||
comment += 'Please address the blocking issues above before this PR can be approved.\n';
|
||||
} else {
|
||||
comment += '---\n';
|
||||
comment += '### ✅ Review Status: **APPROVED**\n\n';
|
||||
comment += 'No blocking issues found! This PR looks good to merge after CI checks pass.\n';
|
||||
}
|
||||
|
||||
// Check if we already commented
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const botComment = comments.find(c =>
|
||||
c.user.type === 'Bot' && c.body.includes('Automated Code Review')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: comment
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
- name: Add labels based on review
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const analysis = JSON.parse('${{ steps.analyze.outputs.result }}');
|
||||
|
||||
let labels = [];
|
||||
|
||||
if (analysis.hasBlockingIssues) {
|
||||
labels.push('needs-changes');
|
||||
} else {
|
||||
labels.push('ready-for-review');
|
||||
}
|
||||
|
||||
if (analysis.warnings.length > 0) {
|
||||
labels.push('has-warnings');
|
||||
}
|
||||
|
||||
if (analysis.totalAdditions > 500) {
|
||||
labels.push('large-pr');
|
||||
}
|
||||
|
||||
// Remove conflicting labels first
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'needs-changes'
|
||||
});
|
||||
} catch (e) {
|
||||
// Label doesn't exist
|
||||
}
|
||||
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'ready-for-review'
|
||||
});
|
||||
} catch (e) {
|
||||
// Label doesn't exist
|
||||
}
|
||||
|
||||
// Add new labels
|
||||
for (const label of labels) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: [label]
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(`Label ${label} might not exist, skipping...`);
|
||||
}
|
||||
}
|
||||
|
||||
- name: Auto-approve if no issues
|
||||
if: steps.analyze.outputs.result && !fromJSON(steps.analyze.outputs.result).hasBlockingIssues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
event: 'APPROVE',
|
||||
body: '✅ Automated review passed! No blocking issues found. This PR is approved pending successful CI checks.'
|
||||
});
|
||||
name: Automated Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
checks: read
|
||||
|
||||
jobs:
|
||||
automated-review:
|
||||
name: AI-Assisted Code Review
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Cache npm dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: npm-deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||
path: |
|
||||
frontends/nextjs/node_modules
|
||||
~/.npm
|
||||
restore-keys: npm-deps-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: npm run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Run linter for review
|
||||
id: lint
|
||||
run: |
|
||||
npm run lint > lint-output.txt 2>&1 || echo "LINT_FAILED=true" >> $GITHUB_OUTPUT
|
||||
cat lint-output.txt
|
||||
continue-on-error: true
|
||||
|
||||
- name: Analyze code changes
|
||||
id: analyze
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
// Get PR diff
|
||||
const { data: files } = await github.rest.pulls.listFiles({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
|
||||
let issues = [];
|
||||
let warnings = [];
|
||||
let suggestions = [];
|
||||
|
||||
// Analyze each file
|
||||
for (const file of files) {
|
||||
const patch = file.patch || '';
|
||||
const filename = file.filename;
|
||||
|
||||
// Check for security issues
|
||||
if (patch.match(/eval\s*\(/)) {
|
||||
issues.push(`⚠️ **Security**: Use of \`eval()\` found in ${filename}`);
|
||||
}
|
||||
if (patch.match(/innerHTML\s*=/)) {
|
||||
warnings.push(`⚠️ **Security**: Direct \`innerHTML\` usage in ${filename}. Consider using safer alternatives.`);
|
||||
}
|
||||
if (patch.match(/dangerouslySetInnerHTML/)) {
|
||||
warnings.push(`⚠️ **Security**: \`dangerouslySetInnerHTML\` usage in ${filename}. Ensure content is sanitized.`);
|
||||
}
|
||||
|
||||
// Check for code quality
|
||||
if (patch.match(/console\.(log|debug|info)/)) {
|
||||
warnings.push(`🔍 **Code Quality**: Console statements found in ${filename}. Remove before merging.`);
|
||||
}
|
||||
if (patch.match(/debugger/)) {
|
||||
issues.push(`🐛 **Debug Code**: Debugger statement found in ${filename}. Remove before merging.`);
|
||||
}
|
||||
if (patch.match(/(:\s*any\b|\bany\s*[>;,\)])/)) {
|
||||
suggestions.push(`💡 **Type Safety**: Consider replacing \`any\` types with specific types in ${filename}`);
|
||||
}
|
||||
|
||||
// Check for best practices
|
||||
if (filename.endsWith('.tsx') || filename.endsWith('.jsx')) {
|
||||
if (patch.match(/useEffect.*\[\]/) && !patch.includes('// eslint-disable')) {
|
||||
suggestions.push(`💡 **React**: Empty dependency array in useEffect in ${filename}. Verify if intentional.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for large files
|
||||
if (file.additions > 500) {
|
||||
warnings.push(`📏 **File Size**: ${filename} has ${file.additions} additions. Consider breaking into smaller files.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Read lint output if exists
|
||||
let lintIssues = '';
|
||||
try {
|
||||
lintIssues = fs.readFileSync('lint-output.txt', 'utf8');
|
||||
} catch (e) {
|
||||
// File doesn't exist
|
||||
}
|
||||
|
||||
// Determine if auto-approve is appropriate
|
||||
const hasBlockingIssues = issues.length > 0 || lintIssues.includes('error');
|
||||
|
||||
return {
|
||||
issues,
|
||||
warnings,
|
||||
suggestions,
|
||||
lintIssues,
|
||||
hasBlockingIssues,
|
||||
fileCount: files.length,
|
||||
totalAdditions: files.reduce((sum, f) => sum + f.additions, 0),
|
||||
totalDeletions: files.reduce((sum, f) => sum + f.deletions, 0)
|
||||
};
|
||||
|
||||
- name: Post review comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const analysis = JSON.parse('${{ steps.analyze.outputs.result }}');
|
||||
|
||||
let comment = '## 🤖 Automated Code Review\n\n';
|
||||
comment += `**Changes Summary:**\n`;
|
||||
comment += `- Files changed: ${analysis.fileCount}\n`;
|
||||
comment += `- Lines added: ${analysis.totalAdditions}\n`;
|
||||
comment += `- Lines deleted: ${analysis.totalDeletions}\n\n`;
|
||||
|
||||
if (analysis.issues.length > 0) {
|
||||
comment += '### ❌ Blocking Issues\n\n';
|
||||
analysis.issues.forEach(issue => comment += `- ${issue}\n`);
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
if (analysis.warnings.length > 0) {
|
||||
comment += '### ⚠️ Warnings\n\n';
|
||||
analysis.warnings.forEach(warning => comment += `- ${warning}\n`);
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
if (analysis.suggestions.length > 0) {
|
||||
comment += '### 💡 Suggestions\n\n';
|
||||
analysis.suggestions.forEach(suggestion => comment += `- ${suggestion}\n`);
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
if (analysis.lintIssues && analysis.lintIssues.includes('error')) {
|
||||
comment += '### 🔴 Linting Errors\n\n';
|
||||
comment += '```\n' + analysis.lintIssues + '\n```\n\n';
|
||||
}
|
||||
|
||||
if (analysis.hasBlockingIssues) {
|
||||
comment += '---\n';
|
||||
comment += '### ❌ Review Status: **CHANGES REQUESTED**\n\n';
|
||||
comment += 'Please address the blocking issues above before this PR can be approved.\n';
|
||||
} else {
|
||||
comment += '---\n';
|
||||
comment += '### ✅ Review Status: **APPROVED**\n\n';
|
||||
comment += 'No blocking issues found! This PR looks good to merge after CI checks pass.\n';
|
||||
}
|
||||
|
||||
// Check if we already commented
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const botComment = comments.find(c =>
|
||||
c.user.type === 'Bot' && c.body.includes('Automated Code Review')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: comment
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
- name: Add labels based on review
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const analysis = JSON.parse('${{ steps.analyze.outputs.result }}');
|
||||
|
||||
let labels = [];
|
||||
|
||||
if (analysis.hasBlockingIssues) {
|
||||
labels.push('needs-changes');
|
||||
} else {
|
||||
labels.push('ready-for-review');
|
||||
}
|
||||
|
||||
if (analysis.warnings.length > 0) {
|
||||
labels.push('has-warnings');
|
||||
}
|
||||
|
||||
if (analysis.totalAdditions > 500) {
|
||||
labels.push('large-pr');
|
||||
}
|
||||
|
||||
// Remove conflicting labels first
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'needs-changes'
|
||||
});
|
||||
} catch (e) {
|
||||
// Label doesn't exist
|
||||
}
|
||||
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'ready-for-review'
|
||||
});
|
||||
} catch (e) {
|
||||
// Label doesn't exist
|
||||
}
|
||||
|
||||
// Add new labels
|
||||
for (const label of labels) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: [label]
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(`Label ${label} might not exist, skipping...`);
|
||||
}
|
||||
}
|
||||
|
||||
- name: Auto-approve if no issues
|
||||
if: steps.analyze.outputs.result && !fromJSON(steps.analyze.outputs.result).hasBlockingIssues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
event: 'APPROVE',
|
||||
body: '✅ Automated review passed! No blocking issues found. This PR is approved pending successful CI checks.'
|
||||
});
|
||||
|
||||
264
.github/workflows/pr/merge-conflict-check.yml
vendored
264
.github/workflows/pr/merge-conflict-check.yml
vendored
@@ -1,132 +1,132 @@
|
||||
name: Check for Merge Conflicts
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
# Also run when the base branch is updated
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
|
||||
jobs:
|
||||
check-conflicts:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch base branch
|
||||
run: |
|
||||
git fetch origin ${{ github.base_ref || github.event.repository.default_branch }}
|
||||
|
||||
- name: Check for merge conflicts
|
||||
id: conflict-check
|
||||
run: |
|
||||
# Determine the base branch
|
||||
BASE_BRANCH="${{ github.base_ref }}"
|
||||
if [ -z "$BASE_BRANCH" ]; then
|
||||
BASE_BRANCH="${{ github.event.repository.default_branch }}"
|
||||
fi
|
||||
|
||||
echo "Checking for conflicts with origin/$BASE_BRANCH"
|
||||
|
||||
# Try to merge the base branch to see if there are conflicts
|
||||
if git merge-tree $(git merge-base HEAD origin/$BASE_BRANCH) origin/$BASE_BRANCH HEAD | grep -q "^<<<<<"; then
|
||||
echo "has_conflicts=true" >> $GITHUB_OUTPUT
|
||||
echo "✗ Merge conflicts detected!"
|
||||
else
|
||||
echo "has_conflicts=false" >> $GITHUB_OUTPUT
|
||||
echo "✓ No merge conflicts detected"
|
||||
fi
|
||||
|
||||
- name: Comment on PR if conflicts exist
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'true' && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const comment = `## ⚠️ Merge Conflicts Detected
|
||||
|
||||
@copilot This pull request has merge conflicts that need to be resolved.
|
||||
|
||||
**Please resolve the conflicts by:**
|
||||
1. Merging the latest changes from the base branch
|
||||
2. Resolving any conflicting files
|
||||
3. Pushing the updated changes
|
||||
|
||||
---
|
||||
*This is an automated message from the merge conflict checker.*`;
|
||||
|
||||
// Check if we already commented
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const botComment = comments.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body.includes('Merge Conflicts Detected')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
// Update existing comment
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: comment
|
||||
});
|
||||
} else {
|
||||
// Create new comment
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
- name: Add label if conflicts exist
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'true' && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['merge-conflict']
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Label might not exist yet, skipping...');
|
||||
}
|
||||
|
||||
- name: Remove label if no conflicts
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'false' && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'merge-conflict'
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Label does not exist or is not applied, skipping...');
|
||||
}
|
||||
|
||||
- name: Fail if conflicts exist
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'true'
|
||||
run: |
|
||||
echo "❌ This PR has merge conflicts and cannot be merged."
|
||||
exit 1
|
||||
name: Check for Merge Conflicts
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
# Also run when the base branch is updated
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
|
||||
jobs:
|
||||
check-conflicts:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch base branch
|
||||
run: |
|
||||
git fetch origin ${{ github.base_ref || github.event.repository.default_branch }}
|
||||
|
||||
- name: Check for merge conflicts
|
||||
id: conflict-check
|
||||
run: |
|
||||
# Determine the base branch
|
||||
BASE_BRANCH="${{ github.base_ref }}"
|
||||
if [ -z "$BASE_BRANCH" ]; then
|
||||
BASE_BRANCH="${{ github.event.repository.default_branch }}"
|
||||
fi
|
||||
|
||||
echo "Checking for conflicts with origin/$BASE_BRANCH"
|
||||
|
||||
# Try to merge the base branch to see if there are conflicts
|
||||
if git merge-tree $(git merge-base HEAD origin/$BASE_BRANCH) origin/$BASE_BRANCH HEAD | grep -q "^<<<<<"; then
|
||||
echo "has_conflicts=true" >> $GITHUB_OUTPUT
|
||||
echo "✗ Merge conflicts detected!"
|
||||
else
|
||||
echo "has_conflicts=false" >> $GITHUB_OUTPUT
|
||||
echo "✓ No merge conflicts detected"
|
||||
fi
|
||||
|
||||
- name: Comment on PR if conflicts exist
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'true' && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const comment = `## ⚠️ Merge Conflicts Detected
|
||||
|
||||
@copilot This pull request has merge conflicts that need to be resolved.
|
||||
|
||||
**Please resolve the conflicts by:**
|
||||
1. Merging the latest changes from the base branch
|
||||
2. Resolving any conflicting files
|
||||
3. Pushing the updated changes
|
||||
|
||||
---
|
||||
*This is an automated message from the merge conflict checker.*`;
|
||||
|
||||
// Check if we already commented
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const botComment = comments.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body.includes('Merge Conflicts Detected')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
// Update existing comment
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: comment
|
||||
});
|
||||
} else {
|
||||
// Create new comment
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
- name: Add label if conflicts exist
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'true' && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['merge-conflict']
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Label might not exist yet, skipping...');
|
||||
}
|
||||
|
||||
- name: Remove label if no conflicts
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'false' && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'merge-conflict'
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Label does not exist or is not applied, skipping...');
|
||||
}
|
||||
|
||||
- name: Fail if conflicts exist
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'true'
|
||||
run: |
|
||||
echo "❌ This PR has merge conflicts and cannot be merged."
|
||||
exit 1
|
||||
|
||||
386
.github/workflows/pr/pr-management.yml
vendored
386
.github/workflows/pr/pr-management.yml
vendored
@@ -1,193 +1,193 @@
|
||||
name: PR Labeling and Management
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
label-pr:
|
||||
name: Auto-Label Pull Request
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened' || github.event.action == 'synchronize'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Analyze PR and add labels
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
|
||||
// Get PR files
|
||||
const { data: files } = await github.rest.pulls.listFiles({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
});
|
||||
|
||||
let labels = [];
|
||||
|
||||
// Analyze file changes
|
||||
const fileTypes = {
|
||||
workflows: files.some(f => f.filename.includes('.github/workflows')),
|
||||
tests: files.some(f => f.filename.includes('test') || f.filename.includes('spec') || f.filename.includes('e2e')),
|
||||
docs: files.some(f => f.filename.includes('README') || f.filename.includes('.md') || f.filename.includes('docs/')),
|
||||
components: files.some(f => f.filename.includes('components/') || f.filename.includes('.tsx')),
|
||||
styles: files.some(f => f.filename.includes('.css') || f.filename.includes('style')),
|
||||
config: files.some(f => f.filename.match(/\.(json|yml|yaml|config\.(js|ts))$/)),
|
||||
dependencies: files.some(f => f.filename === 'package.json' || f.filename === 'package-lock.json'),
|
||||
};
|
||||
|
||||
if (fileTypes.workflows) labels.push('workflows');
|
||||
if (fileTypes.tests) labels.push('tests');
|
||||
if (fileTypes.docs) labels.push('documentation');
|
||||
if (fileTypes.components) labels.push('ui');
|
||||
if (fileTypes.styles) labels.push('styling');
|
||||
if (fileTypes.config) labels.push('configuration');
|
||||
if (fileTypes.dependencies) labels.push('dependencies');
|
||||
|
||||
// Size labels
|
||||
const totalChanges = files.reduce((sum, f) => sum + f.additions + f.deletions, 0);
|
||||
if (totalChanges < 50) {
|
||||
labels.push('size: small');
|
||||
} else if (totalChanges < 200) {
|
||||
labels.push('size: medium');
|
||||
} else {
|
||||
labels.push('size: large');
|
||||
}
|
||||
|
||||
// Check PR title for type
|
||||
const title = pr.title.toLowerCase();
|
||||
if (title.match(/^fix|bug/)) labels.push('bug');
|
||||
if (title.match(/^feat|feature|add/)) labels.push('enhancement');
|
||||
if (title.match(/^refactor/)) labels.push('refactor');
|
||||
if (title.match(/^docs/)) labels.push('documentation');
|
||||
if (title.match(/^test/)) labels.push('tests');
|
||||
if (title.match(/^chore/)) labels.push('chore');
|
||||
|
||||
// Add labels
|
||||
if (labels.length > 0) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: labels
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Some labels may not exist:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
check-pr-description:
|
||||
name: Check PR Description
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Validate PR description
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const body = pr.body || '';
|
||||
|
||||
let issues = [];
|
||||
|
||||
// Check if description is too short
|
||||
if (body.length < 50) {
|
||||
issues.push('PR description is too short. Please provide more details about the changes.');
|
||||
}
|
||||
|
||||
// Check if description links to an issue
|
||||
if (!body.match(/#\d+|https:\/\/github\.com/)) {
|
||||
issues.push('Consider linking to a related issue using #issue_number');
|
||||
}
|
||||
|
||||
// Check for test information
|
||||
if (body.toLowerCase().includes('test') === false &&
|
||||
!pr.labels.some(l => l.name === 'documentation')) {
|
||||
issues.push('Please mention how these changes were tested.');
|
||||
}
|
||||
|
||||
if (issues.length > 0) {
|
||||
const issueList = issues.map(i => '- [ ] ' + i).join('\n');
|
||||
const comment = [
|
||||
'## \uD83D\uDCCB PR Description Checklist',
|
||||
'',
|
||||
'The following items could improve this PR:',
|
||||
'',
|
||||
issueList,
|
||||
'',
|
||||
'**Good PR descriptions include:**',
|
||||
'- What changes were made and why',
|
||||
'- How to test the changes',
|
||||
'- Any breaking changes or special considerations',
|
||||
'- Links to related issues',
|
||||
'- Screenshots (for UI changes)',
|
||||
'',
|
||||
'This is a friendly reminder to help maintain code quality! \uD83D\uDE0A'
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
link-related-issues:
|
||||
name: Link Related Issues
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Find and link related issues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const body = pr.body || '';
|
||||
const title = pr.title;
|
||||
|
||||
// Extract issue numbers from PR body
|
||||
const issueNumbers = [...body.matchAll(/#(\d+)/g)].map(m => m[1]);
|
||||
|
||||
if (issueNumbers.length > 0) {
|
||||
const relatedList = issueNumbers.map(n => '#' + n).join(', ');
|
||||
const comment = [
|
||||
'\uD83D\uDD17 **Related Issues**',
|
||||
'',
|
||||
'This PR is related to: ' + relatedList,
|
||||
'',
|
||||
'These issues will be automatically closed when this PR is merged.'
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
// Add comment to related issues
|
||||
for (const issueNum of issueNumbers) {
|
||||
try {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: parseInt(issueNum),
|
||||
body: '\uD83D\uDD17 Pull request #' + pr.number + ' has been created to address this issue.'
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Could not comment on issue #' + issueNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
name: PR Labeling and Management
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
label-pr:
|
||||
name: Auto-Label Pull Request
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened' || github.event.action == 'synchronize'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Analyze PR and add labels
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
|
||||
// Get PR files
|
||||
const { data: files } = await github.rest.pulls.listFiles({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
});
|
||||
|
||||
let labels = [];
|
||||
|
||||
// Analyze file changes
|
||||
const fileTypes = {
|
||||
workflows: files.some(f => f.filename.includes('.github/workflows')),
|
||||
tests: files.some(f => f.filename.includes('test') || f.filename.includes('spec') || f.filename.includes('e2e')),
|
||||
docs: files.some(f => f.filename.includes('README') || f.filename.includes('.md') || f.filename.includes('docs/')),
|
||||
components: files.some(f => f.filename.includes('components/') || f.filename.includes('.tsx')),
|
||||
styles: files.some(f => f.filename.includes('.css') || f.filename.includes('style')),
|
||||
config: files.some(f => f.filename.match(/\.(json|yml|yaml|config\.(js|ts))$/)),
|
||||
dependencies: files.some(f => f.filename === 'package.json' || f.filename === 'package-lock.json'),
|
||||
};
|
||||
|
||||
if (fileTypes.workflows) labels.push('workflows');
|
||||
if (fileTypes.tests) labels.push('tests');
|
||||
if (fileTypes.docs) labels.push('documentation');
|
||||
if (fileTypes.components) labels.push('ui');
|
||||
if (fileTypes.styles) labels.push('styling');
|
||||
if (fileTypes.config) labels.push('configuration');
|
||||
if (fileTypes.dependencies) labels.push('dependencies');
|
||||
|
||||
// Size labels
|
||||
const totalChanges = files.reduce((sum, f) => sum + f.additions + f.deletions, 0);
|
||||
if (totalChanges < 50) {
|
||||
labels.push('size: small');
|
||||
} else if (totalChanges < 200) {
|
||||
labels.push('size: medium');
|
||||
} else {
|
||||
labels.push('size: large');
|
||||
}
|
||||
|
||||
// Check PR title for type
|
||||
const title = pr.title.toLowerCase();
|
||||
if (title.match(/^fix|bug/)) labels.push('bug');
|
||||
if (title.match(/^feat|feature|add/)) labels.push('enhancement');
|
||||
if (title.match(/^refactor/)) labels.push('refactor');
|
||||
if (title.match(/^docs/)) labels.push('documentation');
|
||||
if (title.match(/^test/)) labels.push('tests');
|
||||
if (title.match(/^chore/)) labels.push('chore');
|
||||
|
||||
// Add labels
|
||||
if (labels.length > 0) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: labels
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Some labels may not exist:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
check-pr-description:
|
||||
name: Check PR Description
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Validate PR description
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const body = pr.body || '';
|
||||
|
||||
let issues = [];
|
||||
|
||||
// Check if description is too short
|
||||
if (body.length < 50) {
|
||||
issues.push('PR description is too short. Please provide more details about the changes.');
|
||||
}
|
||||
|
||||
// Check if description links to an issue
|
||||
if (!body.match(/#\d+|https:\/\/github\.com/)) {
|
||||
issues.push('Consider linking to a related issue using #issue_number');
|
||||
}
|
||||
|
||||
// Check for test information
|
||||
if (body.toLowerCase().includes('test') === false &&
|
||||
!pr.labels.some(l => l.name === 'documentation')) {
|
||||
issues.push('Please mention how these changes were tested.');
|
||||
}
|
||||
|
||||
if (issues.length > 0) {
|
||||
const issueList = issues.map(i => '- [ ] ' + i).join('\n');
|
||||
const comment = [
|
||||
'## \uD83D\uDCCB PR Description Checklist',
|
||||
'',
|
||||
'The following items could improve this PR:',
|
||||
'',
|
||||
issueList,
|
||||
'',
|
||||
'**Good PR descriptions include:**',
|
||||
'- What changes were made and why',
|
||||
'- How to test the changes',
|
||||
'- Any breaking changes or special considerations',
|
||||
'- Links to related issues',
|
||||
'- Screenshots (for UI changes)',
|
||||
'',
|
||||
'This is a friendly reminder to help maintain code quality! \uD83D\uDE0A'
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
link-related-issues:
|
||||
name: Link Related Issues
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Find and link related issues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const body = pr.body || '';
|
||||
const title = pr.title;
|
||||
|
||||
// Extract issue numbers from PR body
|
||||
const issueNumbers = [...body.matchAll(/#(\d+)/g)].map(m => m[1]);
|
||||
|
||||
if (issueNumbers.length > 0) {
|
||||
const relatedList = issueNumbers.map(n => '#' + n).join(', ');
|
||||
const comment = [
|
||||
'\uD83D\uDD17 **Related Issues**',
|
||||
'',
|
||||
'This PR is related to: ' + relatedList,
|
||||
'',
|
||||
'These issues will be automatically closed when this PR is merged.'
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
// Add comment to related issues
|
||||
for (const issueNum of issueNumbers) {
|
||||
try {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: parseInt(issueNum),
|
||||
body: '\uD83D\uDD17 Pull request #' + pr.number + ' has been created to address this issue.'
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Could not comment on issue #' + issueNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
449
.github/workflows/quality/deployment.yml
vendored
449
.github/workflows/quality/deployment.yml
vendored
@@ -1,449 +0,0 @@
|
||||
name: Deployment & Monitoring
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 'Deployment environment'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- staging
|
||||
- production
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
pre-deployment-check:
|
||||
name: Pre-Deployment Validation
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: '1.3.4'
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
|
||||
path: |
|
||||
frontends/nextjs/node_modules
|
||||
~/.bun
|
||||
restore-keys: bun-deps-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Validate database schema
|
||||
run: bunx prisma validate
|
||||
|
||||
- name: Check for breaking changes
|
||||
id: breaking-changes
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// Get recent commits
|
||||
const commits = await github.rest.repos.listCommits({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
per_page: 10
|
||||
});
|
||||
|
||||
let hasBreaking = false;
|
||||
let breakingChanges = [];
|
||||
|
||||
for (const commit of commits.data) {
|
||||
const message = commit.commit.message.toLowerCase();
|
||||
if (message.includes('breaking') || message.includes('breaking:')) {
|
||||
hasBreaking = true;
|
||||
breakingChanges.push({
|
||||
sha: commit.sha.substring(0, 7),
|
||||
message: commit.commit.message.split('\n')[0]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
core.setOutput('has_breaking', hasBreaking);
|
||||
|
||||
if (hasBreaking) {
|
||||
console.log('⚠️ Breaking changes detected:');
|
||||
breakingChanges.forEach(c => console.log(` - ${c.sha}: ${c.message}`));
|
||||
}
|
||||
|
||||
return { hasBreaking, breakingChanges };
|
||||
|
||||
- name: Run security audit
|
||||
run: bun audit --audit-level=moderate
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check package size
|
||||
run: |
|
||||
bun run build
|
||||
du -sh dist/
|
||||
|
||||
# Check if dist is larger than 10MB
|
||||
SIZE=$(du -sm dist/ | cut -f1)
|
||||
if [ $SIZE -gt 10 ]; then
|
||||
echo "⚠️ Warning: Build size is ${SIZE}MB (>10MB). Consider optimizing."
|
||||
else
|
||||
echo "✅ Build size is ${SIZE}MB"
|
||||
fi
|
||||
|
||||
- name: Validate environment configuration
|
||||
run: |
|
||||
echo "Checking for required environment variables..."
|
||||
|
||||
# Check .env.example exists
|
||||
if [ ! -f .env.example ]; then
|
||||
echo "❌ .env.example not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Environment configuration validated"
|
||||
|
||||
deployment-summary:
|
||||
name: Create Deployment Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre-deployment-check
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate deployment notes
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
// Get commits since last release
|
||||
let commits = [];
|
||||
try {
|
||||
const result = await github.rest.repos.listCommits({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
per_page: 20
|
||||
});
|
||||
commits = result.data;
|
||||
} catch (e) {
|
||||
console.log('Could not fetch commits:', e.message);
|
||||
}
|
||||
|
||||
// Categorize commits
|
||||
const features = [];
|
||||
const fixes = [];
|
||||
const breaking = [];
|
||||
const other = [];
|
||||
|
||||
for (const commit of commits) {
|
||||
const message = commit.commit.message;
|
||||
const firstLine = message.split('\n')[0];
|
||||
const sha = commit.sha.substring(0, 7);
|
||||
|
||||
if (message.toLowerCase().includes('breaking')) {
|
||||
breaking.push(`- ${firstLine} (${sha})`);
|
||||
} else if (firstLine.match(/^feat|^feature|^add/i)) {
|
||||
features.push(`- ${firstLine} (${sha})`);
|
||||
} else if (firstLine.match(/^fix|^bug/i)) {
|
||||
fixes.push(`- ${firstLine} (${sha})`);
|
||||
} else {
|
||||
other.push(`- ${firstLine} (${sha})`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create deployment notes
|
||||
let notes = `# Deployment Summary\n\n`;
|
||||
notes += `**Date:** ${new Date().toISOString()}\n`;
|
||||
notes += `**Branch:** ${context.ref}\n`;
|
||||
notes += `**Commit:** ${context.sha.substring(0, 7)}\n\n`;
|
||||
|
||||
if (breaking.length > 0) {
|
||||
notes += `## ⚠️ Breaking Changes\n\n${breaking.join('\n')}\n\n`;
|
||||
}
|
||||
|
||||
if (features.length > 0) {
|
||||
notes += `## ✨ New Features\n\n${features.slice(0, 10).join('\n')}\n\n`;
|
||||
}
|
||||
|
||||
if (fixes.length > 0) {
|
||||
notes += `## 🐛 Bug Fixes\n\n${fixes.slice(0, 10).join('\n')}\n\n`;
|
||||
}
|
||||
|
||||
if (other.length > 0) {
|
||||
notes += `## 🔧 Other Changes\n\n${other.slice(0, 5).join('\n')}\n\n`;
|
||||
}
|
||||
|
||||
notes += `---\n`;
|
||||
notes += `**Total commits:** ${commits.length}\n\n`;
|
||||
notes += `**@copilot** Review the deployment for any potential issues.`;
|
||||
|
||||
console.log(notes);
|
||||
|
||||
// Save to file for artifact
|
||||
fs.writeFileSync('DEPLOYMENT_NOTES.md', notes);
|
||||
|
||||
- name: Upload deployment notes
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deployment-notes
|
||||
path: DEPLOYMENT_NOTES.md
|
||||
retention-days: 90
|
||||
|
||||
post-deployment-health:
|
||||
name: Post-Deployment Health Check
|
||||
runs-on: ubuntu-latest
|
||||
needs: deployment-summary
|
||||
if: github.event_name == 'push' || github.event_name == 'release'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: '1.3.4'
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
|
||||
path: |
|
||||
frontends/nextjs/node_modules
|
||||
~/.bun
|
||||
restore-keys: bun-deps-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: bun run db:generate
|
||||
env:
|
||||
DATABASE_URL: file:./dev.db
|
||||
|
||||
- name: Verify build integrity
|
||||
run: |
|
||||
bun run build
|
||||
|
||||
# Check critical files exist
|
||||
if [ ! -f "dist/index.html" ]; then
|
||||
echo "❌ Critical file missing: dist/index.html"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Build integrity verified"
|
||||
|
||||
- name: Create health check report
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const report = `## 🏥 Post-Deployment Health Check
|
||||
|
||||
**Status:** ✅ Healthy
|
||||
**Timestamp:** ${new Date().toISOString()}
|
||||
**Environment:** ${context.ref}
|
||||
|
||||
### Checks Performed
|
||||
- ✅ Build integrity verified
|
||||
- ✅ Database schema valid
|
||||
- ✅ Dependencies installed
|
||||
- ✅ Critical files present
|
||||
|
||||
### Monitoring
|
||||
- Monitor application logs for errors
|
||||
- Check database connection stability
|
||||
- Verify user authentication flows
|
||||
- Test multi-tenant isolation
|
||||
- Validate package system operations
|
||||
|
||||
**@copilot** Assist with monitoring and troubleshooting if issues arise.
|
||||
`;
|
||||
|
||||
console.log(report);
|
||||
|
||||
create-deployment-issue:
|
||||
name: Track Deployment
|
||||
runs-on: ubuntu-latest
|
||||
needs: [pre-deployment-check, post-deployment-health]
|
||||
if: github.event_name == 'release'
|
||||
steps:
|
||||
- name: Create deployment tracking issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const release = context.payload.release;
|
||||
|
||||
const issueBody = `## 🚀 Deployment Tracking: ${release.name || release.tag_name}
|
||||
|
||||
**Release:** [${release.tag_name}](${release.html_url})
|
||||
**Published:** ${release.published_at}
|
||||
**Published by:** @${release.author.login}
|
||||
|
||||
### Deployment Checklist
|
||||
|
||||
- [x] Pre-deployment validation completed
|
||||
- [x] Build successful
|
||||
- [x] Health checks passed
|
||||
- [ ] Database migrations applied (if any)
|
||||
- [ ] Smoke tests completed
|
||||
- [ ] User acceptance testing
|
||||
- [ ] Production monitoring confirmed
|
||||
- [ ] Documentation updated
|
||||
|
||||
### Post-Deployment Monitoring
|
||||
|
||||
Monitor the following for 24-48 hours:
|
||||
- Application error rates
|
||||
- Database query performance
|
||||
- User authentication success rate
|
||||
- Multi-tenant operations
|
||||
- Package system functionality
|
||||
- Memory and CPU usage
|
||||
|
||||
### Rollback Plan
|
||||
|
||||
If critical issues are detected:
|
||||
1. Document the issue with logs and reproduction steps
|
||||
2. Notify team members
|
||||
3. Execute rollback: \`git revert ${context.sha}\`
|
||||
4. Deploy previous stable version
|
||||
5. Create incident report
|
||||
|
||||
**@copilot** Monitor this deployment and assist with any issues that arise.
|
||||
|
||||
---
|
||||
|
||||
Close this issue once deployment is verified stable after 48 hours.`;
|
||||
|
||||
const issue = await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: `Deployment: ${release.tag_name}`,
|
||||
body: issueBody,
|
||||
labels: ['deployment', 'monitoring']
|
||||
});
|
||||
|
||||
console.log(`Created tracking issue: #${issue.data.number}`);
|
||||
|
||||
dependency-audit:
|
||||
name: Security Audit
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre-deployment-check
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: '1.3.4'
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
|
||||
path: |
|
||||
frontends/nextjs/node_modules
|
||||
~/.bun
|
||||
restore-keys: bun-deps-${{ runner.os }}-
|
||||
|
||||
- name: Audit dependencies
|
||||
id: audit
|
||||
run: |
|
||||
bun audit --json > audit-report.json || true
|
||||
|
||||
# Check for critical vulnerabilities
|
||||
CRITICAL=$(cat audit-report.json | grep -o '"critical":[0-9]*' | grep -o '[0-9]*' || echo "0")
|
||||
HIGH=$(cat audit-report.json | grep -o '"high":[0-9]*' | grep -o '[0-9]*' || echo "0")
|
||||
|
||||
echo "critical=$CRITICAL" >> $GITHUB_OUTPUT
|
||||
echo "high=$HIGH" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
|
||||
echo "⚠️ Security vulnerabilities found: $CRITICAL critical, $HIGH high"
|
||||
else
|
||||
echo "✅ No critical or high security vulnerabilities"
|
||||
fi
|
||||
|
||||
- name: Create security issue if vulnerabilities found
|
||||
if: steps.audit.outputs.critical > 0 || steps.audit.outputs.high > 0
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const critical = ${{ steps.audit.outputs.critical }};
|
||||
const high = ${{ steps.audit.outputs.high }};
|
||||
|
||||
const issueBody = `## 🔒 Security Audit Alert
|
||||
|
||||
Security vulnerabilities detected in dependencies:
|
||||
- **Critical:** ${critical}
|
||||
- **High:** ${high}
|
||||
|
||||
### Action Required
|
||||
|
||||
1. Review the vulnerabilities: \`bun audit\`
|
||||
2. Update affected packages: \`bun audit fix\`
|
||||
3. Test the application after updates
|
||||
4. If auto-fix doesn't work, manually update packages
|
||||
5. Consider alternatives for packages with unfixable issues
|
||||
|
||||
### Review Process
|
||||
|
||||
\`\`\`bash
|
||||
# View detailed audit
|
||||
bun audit
|
||||
|
||||
# Attempt automatic fix
|
||||
bun audit fix
|
||||
|
||||
# Force fix (may introduce breaking changes)
|
||||
bun audit fix --force
|
||||
|
||||
# Check results
|
||||
bun audit
|
||||
\`\`\`
|
||||
|
||||
**@copilot** Suggest safe dependency updates to resolve these vulnerabilities.
|
||||
|
||||
---
|
||||
|
||||
**Priority:** ${critical > 0 ? 'CRITICAL' : 'HIGH'}
|
||||
**Created:** ${new Date().toISOString()}
|
||||
`;
|
||||
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: `Security: ${critical} critical, ${high} high vulnerabilities`,
|
||||
body: issueBody,
|
||||
labels: ['security', 'dependencies', critical > 0 ? 'priority: high' : 'priority: medium']
|
||||
});
|
||||
436
.github/workflows/quality/planning.yml
vendored
436
.github/workflows/quality/planning.yml
vendored
@@ -1,218 +1,218 @@
|
||||
name: Planning & Design
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
architecture-review:
|
||||
name: Architecture & Design Review
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event.action == 'labeled' &&
|
||||
(github.event.label.name == 'enhancement' || github.event.label.name == 'feature-request')
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Review against architecture principles
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const title = issue.title;
|
||||
const body = issue.body || '';
|
||||
|
||||
let suggestions = [];
|
||||
let questions = [];
|
||||
|
||||
// Check if feature aligns with declarative approach
|
||||
if (body.toLowerCase().includes('component') && !body.toLowerCase().includes('json')) {
|
||||
suggestions.push('💡 Consider implementing this as a **declarative component** using JSON configuration and Lua scripts instead of a TypeScript file.');
|
||||
}
|
||||
|
||||
// Check if database schema is mentioned
|
||||
if (!body.toLowerCase().includes('database') && !body.toLowerCase().includes('schema')) {
|
||||
questions.push('🤔 Will this feature require database schema changes? Consider adding Prisma schema details.');
|
||||
}
|
||||
|
||||
// Check if package structure is considered
|
||||
if (body.toLowerCase().includes('new') && !body.toLowerCase().includes('package')) {
|
||||
suggestions.push('📦 This might be a good candidate for a **package-based implementation** with isolated seed data.');
|
||||
}
|
||||
|
||||
// Check for multi-tenant considerations
|
||||
if (!body.toLowerCase().includes('tenant') && !body.toLowerCase().includes('supergod')) {
|
||||
questions.push('🏢 How should this feature work across different **tenants**? Should it be tenant-specific or global?');
|
||||
}
|
||||
|
||||
// Check for permission levels
|
||||
if (!body.toLowerCase().match(/level [1-5]|user|admin|god|supergod/)) {
|
||||
questions.push('🔐 Which **permission levels** should have access to this feature? (user/admin/god/supergod)');
|
||||
}
|
||||
|
||||
// Check for Lua consideration
|
||||
if (body.toLowerCase().includes('logic') && !body.toLowerCase().includes('lua')) {
|
||||
suggestions.push('🌙 Consider implementing business logic in **Lua scripts** for better flexibility and sandboxing.');
|
||||
}
|
||||
|
||||
let comment = `## 🏗️ Architecture Review\n\n`;
|
||||
comment += `Thank you for proposing this enhancement! Here's an architectural review:\n\n`;
|
||||
|
||||
if (suggestions.length > 0) {
|
||||
comment += `### 💡 Architectural Suggestions\n\n`;
|
||||
suggestions.forEach(s => comment += `${s}\n\n`);
|
||||
}
|
||||
|
||||
if (questions.length > 0) {
|
||||
comment += `### 🤔 Questions to Consider\n\n`;
|
||||
questions.forEach(q => comment += `${q}\n\n`);
|
||||
}
|
||||
|
||||
comment += `### ✅ Design Checklist\n\n`;
|
||||
comment += `- [ ] Database schema changes identified\n`;
|
||||
comment += `- [ ] Package structure planned (if applicable)\n`;
|
||||
comment += `- [ ] Multi-tenant implications considered\n`;
|
||||
comment += `- [ ] Permission levels defined\n`;
|
||||
comment += `- [ ] Declarative approach preferred over imperative\n`;
|
||||
comment += `- [ ] Component size kept under 150 LOC\n`;
|
||||
comment += `- [ ] Security implications reviewed\n`;
|
||||
comment += `- [ ] Testing strategy outlined\n\n`;
|
||||
|
||||
comment += `---\n`;
|
||||
comment += `**@copilot** can help implement this feature following these architectural principles.\n\n`;
|
||||
comment += `📖 See [Copilot Instructions](/.github/copilot-instructions.md) for development guidelines.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
prd-check:
|
||||
name: Check PRD Alignment
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'labeled' && github.event.label.name == 'enhancement'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check PRD for similar features
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const issue = context.payload.issue;
|
||||
|
||||
try {
|
||||
const prd = fs.readFileSync('docs/getting-started/PRD.md', 'utf8');
|
||||
|
||||
// Extract key terms from issue
|
||||
const issueText = (issue.title + ' ' + issue.body).toLowerCase();
|
||||
const keywords = ['level', 'god', 'tenant', 'package', 'component', 'workflow', 'lua', 'declarative'];
|
||||
|
||||
const foundKeywords = keywords.filter(k => issueText.includes(k));
|
||||
|
||||
let comment = `## 📋 PRD Alignment Check\n\n`;
|
||||
|
||||
if (foundKeywords.length > 0) {
|
||||
comment += `This feature relates to the following PRD concepts: **${foundKeywords.join(', ')}**\n\n`;
|
||||
comment += `Please review [docs/getting-started/PRD.md](/docs/getting-started/PRD.md) to ensure alignment with the project mission and existing features.\n\n`;
|
||||
}
|
||||
|
||||
comment += `### 🎯 Mission Statement\n\n`;
|
||||
comment += `MetaBuilder aims to be a "fully declarative, procedurally-generated multi-tenant application platform where 95% of functionality is defined through JSON and Lua."\n\n`;
|
||||
comment += `Does this feature support that mission? If so, how?\n\n`;
|
||||
|
||||
comment += `---\n`;
|
||||
comment += `**@copilot** Review the PRD and suggest implementation approach.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Could not read PRD.md:', e.message);
|
||||
}
|
||||
|
||||
suggest-implementation:
|
||||
name: Suggest Implementation Approach
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event.action == 'labeled' &&
|
||||
github.event.label.name == 'ready-to-implement'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Generate implementation suggestion
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
|
||||
let comment = `## 🛠️ Implementation Guidance\n\n`;
|
||||
comment += `This issue is ready for implementation! Here's a suggested approach:\n\n`;
|
||||
|
||||
comment += `### 📝 Step-by-Step Plan\n\n`;
|
||||
comment += `1. **Planning Phase**\n`;
|
||||
comment += ` - [ ] Review PRD.md and update if needed\n`;
|
||||
comment += ` - [ ] Check existing package structure\n`;
|
||||
comment += ` - [ ] Design database schema changes (if any)\n`;
|
||||
comment += ` - [ ] Sketch component hierarchy\n\n`;
|
||||
|
||||
comment += `2. **Database Phase**\n`;
|
||||
comment += ` - [ ] Update \`prisma/schema.prisma\`\n`;
|
||||
comment += ` - [ ] Run \`bun run db:generate\`\n`;
|
||||
comment += ` - [ ] Create or update seed data\n`;
|
||||
comment += ` - [ ] Test database operations\n\n`;
|
||||
|
||||
comment += `3. **Implementation Phase**\n`;
|
||||
comment += ` - [ ] Create package structure (if new package)\n`;
|
||||
comment += ` - [ ] Build generic renderers (prefer over specific components)\n`;
|
||||
comment += ` - [ ] Add Lua scripts for business logic\n`;
|
||||
comment += ` - [ ] Wire up UI components\n`;
|
||||
comment += ` - [ ] Ensure components are <150 LOC\n\n`;
|
||||
|
||||
comment += `4. **Testing Phase**\n`;
|
||||
comment += ` - [ ] Run \`bun run lint\` and fix issues\n`;
|
||||
comment += ` - [ ] Add E2E tests in \`e2e/\` directory\n`;
|
||||
comment += ` - [ ] Test at all permission levels\n`;
|
||||
comment += ` - [ ] Verify multi-tenant isolation\n`;
|
||||
comment += ` - [ ] Test package import/export\n\n`;
|
||||
|
||||
comment += `5. **Documentation Phase**\n`;
|
||||
comment += ` - [ ] Update PRD.md with feature details\n`;
|
||||
comment += ` - [ ] Document Lua APIs if new\n`;
|
||||
comment += ` - [ ] Add usage examples\n`;
|
||||
comment += ` - [ ] Update workflow docs if needed\n\n`;
|
||||
|
||||
comment += `### 🤖 Copilot Assistance\n\n`;
|
||||
comment += `**@copilot** can help with:\n`;
|
||||
comment += `- Generating Prisma schema definitions\n`;
|
||||
comment += `- Creating seed data JSON structures\n`;
|
||||
comment += `- Writing Lua script templates\n`;
|
||||
comment += `- Building generic component renderers\n`;
|
||||
comment += `- Writing E2E tests\n\n`;
|
||||
|
||||
comment += `### 🔗 Useful Resources\n\n`;
|
||||
comment += `- [Copilot Instructions](/.github/copilot-instructions.md)\n`;
|
||||
comment += `- [PRD](/.github/../PRD.md)\n`;
|
||||
comment += `- [Workflow Testing](/.github/workflows/README.md)\n`;
|
||||
comment += `- [Package Structure](/packages/)\n\n`;
|
||||
|
||||
comment += `Ready to start? Create a branch: \`feature/issue-${issue.number}\``;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
name: Planning & Design
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
architecture-review:
|
||||
name: Architecture & Design Review
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event.action == 'labeled' &&
|
||||
(github.event.label.name == 'enhancement' || github.event.label.name == 'feature-request')
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Review against architecture principles
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const title = issue.title;
|
||||
const body = issue.body || '';
|
||||
|
||||
let suggestions = [];
|
||||
let questions = [];
|
||||
|
||||
// Check if feature aligns with declarative approach
|
||||
if (body.toLowerCase().includes('component') && !body.toLowerCase().includes('json')) {
|
||||
suggestions.push('💡 Consider implementing this as a **declarative component** using JSON configuration and Lua scripts instead of a TypeScript file.');
|
||||
}
|
||||
|
||||
// Check if database schema is mentioned
|
||||
if (!body.toLowerCase().includes('database') && !body.toLowerCase().includes('schema')) {
|
||||
questions.push('🤔 Will this feature require database schema changes? Consider adding Prisma schema details.');
|
||||
}
|
||||
|
||||
// Check if package structure is considered
|
||||
if (body.toLowerCase().includes('new') && !body.toLowerCase().includes('package')) {
|
||||
suggestions.push('📦 This might be a good candidate for a **package-based implementation** with isolated seed data.');
|
||||
}
|
||||
|
||||
// Check for multi-tenant considerations
|
||||
if (!body.toLowerCase().includes('tenant') && !body.toLowerCase().includes('supergod')) {
|
||||
questions.push('🏢 How should this feature work across different **tenants**? Should it be tenant-specific or global?');
|
||||
}
|
||||
|
||||
// Check for permission levels
|
||||
if (!body.toLowerCase().match(/level [1-5]|user|admin|god|supergod/)) {
|
||||
questions.push('🔐 Which **permission levels** should have access to this feature? (user/admin/god/supergod)');
|
||||
}
|
||||
|
||||
// Check for Lua consideration
|
||||
if (body.toLowerCase().includes('logic') && !body.toLowerCase().includes('lua')) {
|
||||
suggestions.push('🌙 Consider implementing business logic in **Lua scripts** for better flexibility and sandboxing.');
|
||||
}
|
||||
|
||||
let comment = `## 🏗️ Architecture Review\n\n`;
|
||||
comment += `Thank you for proposing this enhancement! Here's an architectural review:\n\n`;
|
||||
|
||||
if (suggestions.length > 0) {
|
||||
comment += `### 💡 Architectural Suggestions\n\n`;
|
||||
suggestions.forEach(s => comment += `${s}\n\n`);
|
||||
}
|
||||
|
||||
if (questions.length > 0) {
|
||||
comment += `### 🤔 Questions to Consider\n\n`;
|
||||
questions.forEach(q => comment += `${q}\n\n`);
|
||||
}
|
||||
|
||||
comment += `### ✅ Design Checklist\n\n`;
|
||||
comment += `- [ ] Database schema changes identified\n`;
|
||||
comment += `- [ ] Package structure planned (if applicable)\n`;
|
||||
comment += `- [ ] Multi-tenant implications considered\n`;
|
||||
comment += `- [ ] Permission levels defined\n`;
|
||||
comment += `- [ ] Declarative approach preferred over imperative\n`;
|
||||
comment += `- [ ] Component size kept under 150 LOC\n`;
|
||||
comment += `- [ ] Security implications reviewed\n`;
|
||||
comment += `- [ ] Testing strategy outlined\n\n`;
|
||||
|
||||
comment += `---\n`;
|
||||
comment += `**@copilot** can help implement this feature following these architectural principles.\n\n`;
|
||||
comment += `📖 See [Copilot Instructions](/.github/copilot-instructions.md) for development guidelines.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
prd-check:
|
||||
name: Check PRD Alignment
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'labeled' && github.event.label.name == 'enhancement'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check PRD for similar features
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const issue = context.payload.issue;
|
||||
|
||||
try {
|
||||
const prd = fs.readFileSync('docs/getting-started/PRD.md', 'utf8');
|
||||
|
||||
// Extract key terms from issue
|
||||
const issueText = (issue.title + ' ' + issue.body).toLowerCase();
|
||||
const keywords = ['level', 'god', 'tenant', 'package', 'component', 'workflow', 'lua', 'declarative'];
|
||||
|
||||
const foundKeywords = keywords.filter(k => issueText.includes(k));
|
||||
|
||||
let comment = `## 📋 PRD Alignment Check\n\n`;
|
||||
|
||||
if (foundKeywords.length > 0) {
|
||||
comment += `This feature relates to the following PRD concepts: **${foundKeywords.join(', ')}**\n\n`;
|
||||
comment += `Please review [docs/getting-started/PRD.md](/docs/getting-started/PRD.md) to ensure alignment with the project mission and existing features.\n\n`;
|
||||
}
|
||||
|
||||
comment += `### 🎯 Mission Statement\n\n`;
|
||||
comment += `MetaBuilder aims to be a "fully declarative, procedurally-generated multi-tenant application platform where 95% of functionality is defined through JSON and Lua."\n\n`;
|
||||
comment += `Does this feature support that mission? If so, how?\n\n`;
|
||||
|
||||
comment += `---\n`;
|
||||
comment += `**@copilot** Review the PRD and suggest implementation approach.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Could not read PRD.md:', e.message);
|
||||
}
|
||||
|
||||
suggest-implementation:
|
||||
name: Suggest Implementation Approach
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event.action == 'labeled' &&
|
||||
github.event.label.name == 'ready-to-implement'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Generate implementation suggestion
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
|
||||
let comment = `## 🛠️ Implementation Guidance\n\n`;
|
||||
comment += `This issue is ready for implementation! Here's a suggested approach:\n\n`;
|
||||
|
||||
comment += `### 📝 Step-by-Step Plan\n\n`;
|
||||
comment += `1. **Planning Phase**\n`;
|
||||
comment += ` - [ ] Review PRD.md and update if needed\n`;
|
||||
comment += ` - [ ] Check existing package structure\n`;
|
||||
comment += ` - [ ] Design database schema changes (if any)\n`;
|
||||
comment += ` - [ ] Sketch component hierarchy\n\n`;
|
||||
|
||||
comment += `2. **Database Phase**\n`;
|
||||
comment += ` - [ ] Update \`prisma/schema.prisma\`\n`;
|
||||
comment += ` - [ ] Run \`npm run db:generate\`\n`;
|
||||
comment += ` - [ ] Create or update seed data\n`;
|
||||
comment += ` - [ ] Test database operations\n\n`;
|
||||
|
||||
comment += `3. **Implementation Phase**\n`;
|
||||
comment += ` - [ ] Create package structure (if new package)\n`;
|
||||
comment += ` - [ ] Build generic renderers (prefer over specific components)\n`;
|
||||
comment += ` - [ ] Add Lua scripts for business logic\n`;
|
||||
comment += ` - [ ] Wire up UI components\n`;
|
||||
comment += ` - [ ] Ensure components are <150 LOC\n\n`;
|
||||
|
||||
comment += `4. **Testing Phase**\n`;
|
||||
comment += ` - [ ] Run \`npm run lint\` and fix issues\n`;
|
||||
comment += ` - [ ] Add E2E tests in \`e2e/\` directory\n`;
|
||||
comment += ` - [ ] Test at all permission levels\n`;
|
||||
comment += ` - [ ] Verify multi-tenant isolation\n`;
|
||||
comment += ` - [ ] Test package import/export\n\n`;
|
||||
|
||||
comment += `5. **Documentation Phase**\n`;
|
||||
comment += ` - [ ] Update PRD.md with feature details\n`;
|
||||
comment += ` - [ ] Document Lua APIs if new\n`;
|
||||
comment += ` - [ ] Add usage examples\n`;
|
||||
comment += ` - [ ] Update workflow docs if needed\n\n`;
|
||||
|
||||
comment += `### 🤖 Copilot Assistance\n\n`;
|
||||
comment += `**@copilot** can help with:\n`;
|
||||
comment += `- Generating Prisma schema definitions\n`;
|
||||
comment += `- Creating seed data JSON structures\n`;
|
||||
comment += `- Writing Lua script templates\n`;
|
||||
comment += `- Building generic component renderers\n`;
|
||||
comment += `- Writing E2E tests\n\n`;
|
||||
|
||||
comment += `### 🔗 Useful Resources\n\n`;
|
||||
comment += `- [Copilot Instructions](/.github/copilot-instructions.md)\n`;
|
||||
comment += `- [PRD](/.github/../PRD.md)\n`;
|
||||
comment += `- [Workflow Testing](/.github/workflows/README.md)\n`;
|
||||
comment += `- [Package Structure](/packages/)\n\n`;
|
||||
|
||||
comment += `Ready to start? Create a branch: \`feature/issue-${issue.number}\``;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
1330
.github/workflows/quality/quality-metrics.yml
vendored
1330
.github/workflows/quality/quality-metrics.yml
vendored
File diff suppressed because it is too large
Load Diff
183
.github/workflows/quality/size-limits.yml
vendored
183
.github/workflows/quality/size-limits.yml
vendored
@@ -1,92 +1,91 @@
|
||||
name: Code Size Limits
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'frontends/nextjs/src/**/*.{ts,tsx,js,jsx}'
|
||||
- 'tools/enforce-size-limits.ts'
|
||||
- '.github/workflows/size-limits.yml'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'frontends/nextjs/src/**/*.{ts,tsx,js,jsx}'
|
||||
|
||||
jobs:
|
||||
size-limits:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: '1.3.4'
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: bun-deps-${{ runner.os }}-${{ hashFiles('bun.lock') }}
|
||||
path: |
|
||||
frontends/nextjs/node_modules
|
||||
~/.bun
|
||||
restore-keys: bun-deps-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Check code size limits
|
||||
run: bunx tsx ../../tools/enforce-size-limits.ts
|
||||
|
||||
- name: Upload report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: size-limits-report
|
||||
path: frontends/nextjs/size-limits-report.json
|
||||
retention-days: 7
|
||||
|
||||
- name: Comment on PR
|
||||
if: failure() && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const report = JSON.parse(fs.readFileSync('frontends/nextjs/size-limits-report.json', 'utf8'));
|
||||
|
||||
let comment = '## 📏 Code Size Limits\n\n';
|
||||
|
||||
if (report.errors === 0 && report.warnings === 0) {
|
||||
comment += '✅ All files pass size limits!';
|
||||
} else {
|
||||
if (report.errors > 0) {
|
||||
comment += `### ❌ Errors (${report.errors})\n`;
|
||||
report.violations
|
||||
.filter(v => v.severity === 'error')
|
||||
.forEach(v => {
|
||||
comment += `- **${v.file}**: ${v.metric} (${v.current} / ${v.limit})\n`;
|
||||
});
|
||||
}
|
||||
|
||||
if (report.warnings > 0) {
|
||||
comment += `\n### ⚠️ Warnings (${report.warnings})\n`;
|
||||
report.violations
|
||||
.filter(v => v.severity === 'warning')
|
||||
.forEach(v => {
|
||||
comment += `- **${v.file}**: ${v.metric} (${v.current} / ${v.limit})\n`;
|
||||
});
|
||||
}
|
||||
|
||||
comment += '\n[See refactoring guide →](../blob/main/docs/REFACTORING_ENFORCEMENT_GUIDE.md)';
|
||||
}
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment
|
||||
});
|
||||
name: Code Size Limits
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'frontends/nextjs/src/**/*.{ts,tsx,js,jsx}'
|
||||
- '.github/workflows/size-limits.yml'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'frontends/nextjs/src/**/*.{ts,tsx,js,jsx}'
|
||||
|
||||
jobs:
|
||||
size-limits:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontends/nextjs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Cache npm dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: npm-deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||
path: |
|
||||
frontends/nextjs/node_modules
|
||||
~/.npm
|
||||
restore-keys: npm-deps-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install --frozen-lockfile
|
||||
|
||||
- name: Check code size limits
|
||||
run: echo "skipping tools-based size limits enforcement (tools/ removed)"
|
||||
|
||||
- name: Upload report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: size-limits-report
|
||||
path: frontends/nextjs/size-limits-report.json
|
||||
retention-days: 7
|
||||
|
||||
- name: Comment on PR
|
||||
if: failure() && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const report = JSON.parse(fs.readFileSync('frontends/nextjs/size-limits-report.json', 'utf8'));
|
||||
|
||||
let comment = '## 📏 Code Size Limits\n\n';
|
||||
|
||||
if (report.errors === 0 && report.warnings === 0) {
|
||||
comment += '✅ All files pass size limits!';
|
||||
} else {
|
||||
if (report.errors > 0) {
|
||||
comment += `### ❌ Errors (${report.errors})\n`;
|
||||
report.violations
|
||||
.filter(v => v.severity === 'error')
|
||||
.forEach(v => {
|
||||
comment += `- **${v.file}**: ${v.metric} (${v.current} / ${v.limit})\n`;
|
||||
});
|
||||
}
|
||||
|
||||
if (report.warnings > 0) {
|
||||
comment += `\n### ⚠️ Warnings (${report.warnings})\n`;
|
||||
report.violations
|
||||
.filter(v => v.severity === 'warning')
|
||||
.forEach(v => {
|
||||
comment += `- **${v.file}**: ${v.metric} (${v.current} / ${v.limit})\n`;
|
||||
});
|
||||
}
|
||||
|
||||
comment += '\n[See refactoring guide →](../blob/main/docs/REFACTORING_ENFORCEMENT_GUIDE.md)';
|
||||
}
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment
|
||||
});
|
||||
|
||||
162
.github/workflows/todo-to-issues.yml
vendored
Normal file
162
.github/workflows/todo-to-issues.yml
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
name: TODO to Issues Sync
|
||||
|
||||
# This workflow can be triggered manually to convert TODO items to GitHub issues
|
||||
# or can be run on a schedule to keep issues in sync with TODO files
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
mode:
|
||||
description: 'Execution mode'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- dry-run
|
||||
- export-json
|
||||
- create-issues
|
||||
default: 'dry-run'
|
||||
|
||||
filter_priority:
|
||||
description: 'Filter by priority (leave empty for all)'
|
||||
required: false
|
||||
type: choice
|
||||
options:
|
||||
- ''
|
||||
- critical
|
||||
- high
|
||||
- medium
|
||||
- low
|
||||
|
||||
filter_label:
|
||||
description: 'Filter by label (e.g., security, frontend)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
exclude_checklist:
|
||||
description: 'Exclude checklist items'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
limit:
|
||||
description: 'Limit number of issues (0 for no limit)'
|
||||
required: false
|
||||
type: number
|
||||
default: 0
|
||||
|
||||
# Uncomment to run on a schedule (e.g., weekly)
|
||||
# schedule:
|
||||
# - cron: '0 0 * * 0' # Every Sunday at midnight
|
||||
|
||||
jobs:
|
||||
convert-todos:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: |
|
||||
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
|
||||
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
|
||||
&& sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
|
||||
&& sudo apt update \
|
||||
&& sudo apt install gh -y
|
||||
|
||||
- name: Authenticate GitHub CLI
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "$GH_TOKEN" | gh auth login --with-token
|
||||
gh auth status
|
||||
|
||||
- name: Build command arguments
|
||||
id: args
|
||||
run: |
|
||||
ARGS=""
|
||||
|
||||
# Add mode
|
||||
if [ "${{ inputs.mode }}" = "dry-run" ]; then
|
||||
ARGS="$ARGS --dry-run"
|
||||
elif [ "${{ inputs.mode }}" = "export-json" ]; then
|
||||
ARGS="$ARGS --output todos-export.json"
|
||||
elif [ "${{ inputs.mode }}" = "create-issues" ]; then
|
||||
ARGS="$ARGS --create"
|
||||
fi
|
||||
|
||||
# Add filters
|
||||
if [ -n "${{ inputs.filter_priority }}" ]; then
|
||||
ARGS="$ARGS --filter-priority ${{ inputs.filter_priority }}"
|
||||
fi
|
||||
|
||||
if [ -n "${{ inputs.filter_label }}" ]; then
|
||||
ARGS="$ARGS --filter-label ${{ inputs.filter_label }}"
|
||||
fi
|
||||
|
||||
if [ "${{ inputs.exclude_checklist }}" = "true" ]; then
|
||||
ARGS="$ARGS --exclude-checklist"
|
||||
fi
|
||||
|
||||
# Add limit if specified
|
||||
if [ "${{ inputs.limit }}" != "0" ]; then
|
||||
ARGS="$ARGS --limit ${{ inputs.limit }}"
|
||||
fi
|
||||
|
||||
echo "args=$ARGS" >> $GITHUB_OUTPUT
|
||||
echo "Command arguments: $ARGS"
|
||||
|
||||
- name: Run populate-kanban script
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "skipping tools-based populate-kanban (tools/ removed)"
|
||||
|
||||
- name: Upload JSON export (if applicable)
|
||||
if: inputs.mode == 'export-json'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: todos-export
|
||||
path: todos-export.json
|
||||
retention-days: 30
|
||||
|
||||
- name: Create summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## TODO to Issues Conversion" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Mode:** ${{ inputs.mode }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ -n "${{ inputs.filter_priority }}" ]; then
|
||||
echo "**Priority Filter:** ${{ inputs.filter_priority }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ -n "${{ inputs.filter_label }}" ]; then
|
||||
echo "**Label Filter:** ${{ inputs.filter_label }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${{ inputs.exclude_checklist }}" = "true" ]; then
|
||||
echo "**Checklist Items:** Excluded" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${{ inputs.limit }}" != "0" ]; then
|
||||
echo "**Limit:** ${{ inputs.limit }} items" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ inputs.mode }}" = "export-json" ]; then
|
||||
echo "✅ JSON export created successfully" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Download the artifact from the workflow run page" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ inputs.mode }}" = "create-issues" ]; then
|
||||
echo "✅ GitHub issues created successfully" >> $GITHUB_STEP_SUMMARY
|
||||
echo "View issues: https://github.com/${{ github.repository }}/issues" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "ℹ️ Dry run completed - no issues created" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
198
.github/workflows/triage.yml
vendored
Normal file
198
.github/workflows/triage.yml
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
name: Issue and PR Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited, reopened]
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, edited]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
triage-issue:
|
||||
name: Triage Issues
|
||||
if: github.event_name == 'issues'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Categorize and label issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const title = (issue.title || '').toLowerCase();
|
||||
const body = (issue.body || '').toLowerCase();
|
||||
const text = `${title}\n${body}`;
|
||||
|
||||
const labels = new Set();
|
||||
const missing = [];
|
||||
|
||||
const typeMatchers = [
|
||||
{ regex: /bug|error|crash|broken|fail/, label: 'bug' },
|
||||
{ regex: /feature|enhancement|add|new|implement/, label: 'enhancement' },
|
||||
{ regex: /document|readme|docs|guide/, label: 'documentation' },
|
||||
{ regex: /test|testing|spec|e2e/, label: 'testing' },
|
||||
{ regex: /security|vulnerability|exploit|xss|sql/, label: 'security' },
|
||||
{ regex: /performance|slow|optimize|speed/, label: 'performance' },
|
||||
];
|
||||
|
||||
for (const match of typeMatchers) {
|
||||
if (text.match(match.regex)) {
|
||||
labels.add(match.label);
|
||||
}
|
||||
}
|
||||
|
||||
const areaMatchers = [
|
||||
{ regex: /frontend|react|next|ui|component|browser/, label: 'area: frontend' },
|
||||
{ regex: /api|backend|service|server/, label: 'area: backend' },
|
||||
{ regex: /database|prisma|schema|sql/, label: 'area: database' },
|
||||
{ regex: /workflow|github actions|ci|pipeline/, label: 'area: workflows' },
|
||||
{ regex: /docs|readme|guide/, label: 'area: documentation' },
|
||||
];
|
||||
|
||||
for (const match of areaMatchers) {
|
||||
if (text.match(match.regex)) {
|
||||
labels.add(match.label);
|
||||
}
|
||||
}
|
||||
|
||||
if (text.match(/critical|urgent|asap|blocker/)) {
|
||||
labels.add('priority: high');
|
||||
} else if (text.match(/minor|low|nice to have/)) {
|
||||
labels.add('priority: low');
|
||||
} else {
|
||||
labels.add('priority: medium');
|
||||
}
|
||||
|
||||
if (text.match(/beginner|easy|simple|starter/) || labels.size <= 2) {
|
||||
labels.add('good first issue');
|
||||
}
|
||||
|
||||
const reproductionHints = ['steps to reproduce', 'expected', 'actual'];
|
||||
for (const hint of reproductionHints) {
|
||||
if (!body.includes(hint)) {
|
||||
missing.push(hint);
|
||||
}
|
||||
}
|
||||
|
||||
const supportInfo = body.includes('version') || body.match(/v\d+\.\d+/);
|
||||
if (!supportInfo) {
|
||||
missing.push('version information');
|
||||
}
|
||||
|
||||
if (labels.size > 0) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: Array.from(labels),
|
||||
}).catch(e => console.log('Some labels may not exist:', e.message));
|
||||
}
|
||||
|
||||
const checklist = missing.map(item => `- [ ] Add ${item}`).join('\n') || '- [x] Description includes key details.';
|
||||
const summary = Array.from(labels).map(l => `- ${l}`).join('\n') || '- No labels inferred yet.';
|
||||
|
||||
const comment = [
|
||||
'👋 Thanks for reporting an issue! I ran a quick triage:',
|
||||
'',
|
||||
'**Proposed labels:**',
|
||||
summary,
|
||||
'',
|
||||
'**Missing details:**',
|
||||
checklist,
|
||||
'',
|
||||
'Adding the missing details will help reviewers respond faster. If the proposed labels look wrong, feel free to update them.',
|
||||
'',
|
||||
'@copilot Please review this triage and refine labels or request any additional context needed—no Codex webhooks involved.'
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment,
|
||||
});
|
||||
|
||||
triage-pr:
|
||||
name: Triage Pull Requests
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Analyze PR files and label
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const { data: files } = await github.rest.pulls.listFiles({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
});
|
||||
|
||||
const labels = new Set();
|
||||
|
||||
const fileFlags = {
|
||||
workflows: files.some(f => f.filename.includes('.github/workflows')),
|
||||
docs: files.some(f => f.filename.match(/\.(md|mdx)$/) || f.filename.startsWith('docs/')),
|
||||
frontend: files.some(f => f.filename.includes('frontends/nextjs')),
|
||||
db: files.some(f => f.filename.includes('prisma/') || f.filename.includes('dbal/')),
|
||||
tests: files.some(f => f.filename.match(/(test|spec)\.[jt]sx?/)),
|
||||
};
|
||||
|
||||
if (fileFlags.workflows) labels.add('area: workflows');
|
||||
if (fileFlags.docs) labels.add('area: documentation');
|
||||
if (fileFlags.frontend) labels.add('area: frontend');
|
||||
if (fileFlags.db) labels.add('area: database');
|
||||
if (fileFlags.tests) labels.add('tests');
|
||||
|
||||
const totalChanges = files.reduce((sum, f) => sum + f.additions + f.deletions, 0);
|
||||
const highRiskPaths = files.filter(f => f.filename.includes('.github/workflows') || f.filename.includes('prisma/'));
|
||||
|
||||
let riskLabel = 'risk: low';
|
||||
if (highRiskPaths.length > 0 || totalChanges >= 400) {
|
||||
riskLabel = 'risk: high';
|
||||
} else if (totalChanges >= 150) {
|
||||
riskLabel = 'risk: medium';
|
||||
}
|
||||
labels.add(riskLabel);
|
||||
|
||||
const missing = [];
|
||||
const body = (pr.body || '').toLowerCase();
|
||||
if (!body.includes('test')) missing.push('Test plan');
|
||||
if (fileFlags.frontend && !body.includes('screenshot')) missing.push('Screenshots for UI changes');
|
||||
if (!body.match(/#\d+|https:\/\/github\.com/)) missing.push('Linked issue reference');
|
||||
|
||||
if (labels.size > 0) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: Array.from(labels),
|
||||
}).catch(e => console.log('Some labels may not exist:', e.message));
|
||||
}
|
||||
|
||||
const labelSummary = Array.from(labels).map(l => `- ${l}`).join('\n');
|
||||
const missingList = missing.length ? missing.map(item => `- [ ] ${item}`).join('\n') : '- [x] Description includes required context.';
|
||||
|
||||
const comment = [
|
||||
'🤖 **Automated PR triage**',
|
||||
'',
|
||||
'**Proposed labels:**',
|
||||
labelSummary,
|
||||
'',
|
||||
'**Description check:**',
|
||||
missingList,
|
||||
'',
|
||||
'If any labels look incorrect, feel free to adjust them. Closing the missing items will help reviewers move faster.',
|
||||
'',
|
||||
'@copilot Please double-check this triage (no Codex webhook) and add any extra labels or questions for the author.'
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: comment,
|
||||
});
|
||||
198
.gitignore
vendored
198
.gitignore
vendored
@@ -1,95 +1,103 @@
|
||||
# Python
|
||||
.venv/
|
||||
venv/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
dist-ssr/
|
||||
*-dist/
|
||||
build/
|
||||
.next/
|
||||
out/
|
||||
next-env.d.ts
|
||||
*.local
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
.secrets
|
||||
|
||||
# Editor
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/tasks.json
|
||||
.idea/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
*~
|
||||
|
||||
# IDE/Development
|
||||
.devcontainer/
|
||||
.spark-workbench-id
|
||||
.spark-initial-sha
|
||||
_codeql_detected_source_root/
|
||||
|
||||
# Database
|
||||
prisma/dev.db
|
||||
prisma/dev.db-journal
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Testing
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
/e2e-results/
|
||||
*.spec.js.map
|
||||
*.spec.ts.map
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
!frontends/nextjs/src/app/api/github/actions/runs/\[runId\]/logs/
|
||||
!frontends/nextjs/src/app/api/github/actions/runs/\[runId\]/logs/route.ts
|
||||
|
||||
# Cache/Temp
|
||||
pids/
|
||||
.file-manifest
|
||||
lint-output.txt
|
||||
.turbo/
|
||||
|
||||
# Analysis outputs
|
||||
stub-patterns.json
|
||||
complexity-report.json
|
||||
|
||||
# Project-specific
|
||||
**/agent-eval-report*
|
||||
vite.config.ts.bak*
|
||||
.cache/
|
||||
dist-old/
|
||||
# Python
|
||||
.venv/
|
||||
venv/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
dist-ssr/
|
||||
*-dist/
|
||||
build/
|
||||
.next/
|
||||
out/
|
||||
next-env.d.ts
|
||||
*.local
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
.secrets
|
||||
|
||||
# Editor
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/tasks.json
|
||||
.idea/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
*~
|
||||
|
||||
# IDE/Development
|
||||
.devcontainer/
|
||||
.spark-workbench-id
|
||||
.spark-initial-sha
|
||||
_codeql_detected_source_root/
|
||||
|
||||
# Database
|
||||
prisma/dev.db
|
||||
prisma/dev.db-journal
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Testing
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
/e2e-results/
|
||||
*.spec.js.map
|
||||
*.spec.ts.map
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
!frontends/nextjs/src/app/api/github/actions/runs/\[runId\]/logs/
|
||||
!frontends/nextjs/src/app/api/github/actions/runs/\[runId\]/logs/route.ts
|
||||
|
||||
# Cache/Temp
|
||||
pids/
|
||||
.file-manifest
|
||||
lint-output.txt
|
||||
.turbo/
|
||||
|
||||
# Analysis outputs
|
||||
stub-patterns.json
|
||||
complexity-report.json
|
||||
|
||||
# TODO management
|
||||
todos-baseline.json
|
||||
todos-export.json
|
||||
todos*.json
|
||||
|
||||
# Project-specific
|
||||
**/agent-eval-report*
|
||||
vite.config.ts.bak*
|
||||
.cache/
|
||||
dist-old/
|
||||
.vscode/claudesync.json
|
||||
/package-lock.json
|
||||
bun.lockb
|
||||
|
||||
37
.gitlab-ci.yml
Normal file
37
.gitlab-ci.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
image: node:20-bullseye
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- build
|
||||
|
||||
cache:
|
||||
key: ${CI_COMMIT_REF_SLUG}
|
||||
paths:
|
||||
- .npm/
|
||||
|
||||
variables:
|
||||
NPM_CONFIG_CACHE: $CI_PROJECT_DIR/.npm
|
||||
|
||||
before_script:
|
||||
- npm ci
|
||||
|
||||
lint:
|
||||
stage: lint
|
||||
script:
|
||||
- npm run lint
|
||||
|
||||
typecheck:
|
||||
stage: test
|
||||
script:
|
||||
- npm run typecheck
|
||||
|
||||
unit_tests:
|
||||
stage: test
|
||||
script:
|
||||
- npm test
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- npm run build
|
||||
47
.openhands/microagents/metabuilder_roadmap_implementer.md
Normal file
47
.openhands/microagents/metabuilder_roadmap_implementer.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: MetaBuilder Roadmap Implementer
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers: []
|
||||
---
|
||||
|
||||
Purpose
|
||||
- Implement features described in ROADMAP.md and README.md.
|
||||
- Keep both ROADMAP.md and README.md up to date as work progresses.
|
||||
- Write and maintain Playwright E2E tests and unit tests.
|
||||
- Follow the existing code style and project conventions.
|
||||
- Use the existing JSON Schemas; they are mostly correct, do not modify schema definitions unless explicitly required by failing validation.
|
||||
- Index the repository for quick navigation and make concise implementation notes.
|
||||
- Align styling to match the old/ directory while using plain SASS files (no CSS-in-JS).
|
||||
|
||||
Scope and Guidance
|
||||
- Source of truth for planned features: ROADMAP.md. Ensure README.md reflects any implemented capabilities or usage changes.
|
||||
- Respect repository structure: prefer packages/, services/, frontends/, and dbal/ conventions already present. Avoid ad-hoc new folders.
|
||||
- Testing:
|
||||
- Unit tests: colocate or follow existing spec/ patterns.
|
||||
- E2E: use Playwright per playwright.config.ts and the e2e/ folder conventions.
|
||||
- Ensure new features include adequate test coverage and run locally before committing.
|
||||
- Code style:
|
||||
- Run the project linters/formatters defined in package.json scripts.
|
||||
- Keep TypeScript strictness and fix type warnings instead of suppressing them.
|
||||
- JSON Schema:
|
||||
- Validate inputs against existing schemas in schemas/; do not overhaul schemas unless necessary.
|
||||
- Styles:
|
||||
- Use plain SASS (.scss) and mirror patterns from old/ to maintain visual continuity.
|
||||
|
||||
Operational Steps When Executing
|
||||
1) Parse ROADMAP.md items and pick an actionable task.
|
||||
2) Implement minimal code to satisfy the task; keep changes focused.
|
||||
3) Update README.md and ROADMAP.md checkboxes/status to reflect progress.
|
||||
4) Add/adjust unit tests and Playwright tests to cover the change.
|
||||
5) Run lint, typecheck, and tests; fix issues.
|
||||
6) Commit with a clear message referencing the task.
|
||||
|
||||
Notes and Indexing
|
||||
- Maintain brief notes with references to key files you touched. Prefer adding developer notes to docs/ if appropriate, otherwise keep ephemeral notes out of VCS.
|
||||
|
||||
Limitations
|
||||
- No triggers defined; manual invocation only.
|
||||
- Does not modify JSON schemas unless validation requires it.
|
||||
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"cmake.sourceDirectory": "/Users/rmac/Documents/GitHub/metabuilder/frontends/qt6",
|
||||
"cmake.sourceDirectory": "/home/rewrich/Documents/GitHub/metabuilder/dbal/production/build-config",
|
||||
"chat.mcp.discovery.enabled": {
|
||||
"claude-desktop": true,
|
||||
"windsurf": true,
|
||||
@@ -57,5 +57,7 @@
|
||||
"https://docs.github.com/*": true,
|
||||
"https://www.npmjs.com/*": true,
|
||||
"https://registry.npmjs.org/*": true
|
||||
}
|
||||
},
|
||||
"claudeCode.allowDangerouslySkipPermissions": true,
|
||||
"claudeCode.initialPermissionMode": "bypassPermissions"
|
||||
}
|
||||
49
AGENTS.md
49
AGENTS.md
@@ -1,49 +0,0 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
- `frontends/nextjs/`: primary Next.js app (source in `src/`, E2E in `e2e/`, local helper scripts in `scripts/`).
|
||||
- `packages/`: JSON-driven component packages (`seed/*.json`, optional `static_content/`, and `tests/` for schema/structure checks).
|
||||
- `dbal/`: database abstraction layer (TypeScript library in `dbal/ts/`; additional tooling/docs under `dbal/`).
|
||||
- `prisma/`: Prisma schema and migrations (`schema.prisma`, `migrations/`).
|
||||
- `config/`: shared config (Playwright/Vite/TS/ESLint) symlinked into `frontends/nextjs/`.
|
||||
- `tools/`: repo utilities (quality checks, workflow helpers, code analysis).
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
Run app workflows from `frontends/nextjs/`:
|
||||
|
||||
- `npm install` (or `npm ci`): install dependencies.
|
||||
- `npm run dev`: start local dev server.
|
||||
- `npm run build` / `npm run start`: production build and serve.
|
||||
- `npm run lint` / `npm run lint:fix`: lint (and auto-fix where safe).
|
||||
- `npm run typecheck`: TypeScript checking (`tsc --noEmit`).
|
||||
- `npm run test:unit` / `npm run test:coverage`: Vitest unit tests (coverage output to `frontends/nextjs/coverage/`).
|
||||
- `npm run test:e2e`: Playwright E2E tests.
|
||||
- `npm run db:generate` / `npm run db:push` / `npm run db:migrate`: Prisma client + schema/migrations.
|
||||
|
||||
DBAL library workflows live in `dbal/ts/` (`npm run build`, `npm run test:unit`).
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
- TypeScript + ESM. Prefer `@/…` imports inside `frontends/nextjs/src/`.
|
||||
- React components: `PascalCase.tsx`; hooks: `useThing.ts`; tests: `*.test.ts(x)`.
|
||||
- UI: use Material UI (`@mui/*`) and SCSS/modules as needed; do not introduce Radix UI or Tailwind (see `UI_STANDARDS.md`).
|
||||
- Package metadata: keep `packages/*/seed/metadata.json` `packageId` in `snake_case` and versions semver (e.g. `1.2.3`).
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
- Unit: Vitest (`frontends/nextjs/src/**/*.test.ts(x)` and `packages/*/tests/*.test.ts`).
|
||||
- E2E: Playwright (`frontends/nextjs/e2e/`).
|
||||
- Add/adjust tests with behavior changes; keep tests deterministic (no network, stable clocks/IDs).
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
- Commits generally follow Conventional Commits (examples in history: `feat: …`, `fix: …`, `docs: …`, `refactor: …`, `chore: …`).
|
||||
- PRs should include: what/why, linked issue (if any), screenshots for UI changes, and notes on DB/schema changes.
|
||||
- Before opening a PR, run `npm run lint`, `npm run typecheck`, and the relevant tests.
|
||||
|
||||
## Agent-Specific Notes
|
||||
|
||||
- Check for scoped rules in nested `AGENTS.md` files (e.g., `dbal/AGENTS.md`) before editing those areas.
|
||||
- Keep changes focused, avoid dependency churn, and follow existing patterns/config in `config/` and `frontends/nextjs/`.
|
||||
43
Jenkinsfile
vendored
Normal file
43
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
tools {
|
||||
nodejs 'node25'
|
||||
}
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
stage('Install') {
|
||||
steps {
|
||||
sh 'npm ci'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Lint') {
|
||||
steps {
|
||||
sh 'npm run lint'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Typecheck') {
|
||||
steps {
|
||||
sh 'npm run typecheck'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'npm test'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
196
KANBAN_READY.md
196
KANBAN_READY.md
@@ -1,196 +0,0 @@
|
||||
# 🎯 READY TO POPULATE KANBAN
|
||||
|
||||
## ✅ Implementation Complete
|
||||
|
||||
All tools and documentation are ready to populate your GitHub kanban board at:
|
||||
**https://github.com/users/johndoe6345789/projects/2**
|
||||
|
||||
---
|
||||
|
||||
## 📦 What's Been Created
|
||||
|
||||
### Scripts
|
||||
- ✅ **`tools/project-management/populate-kanban.py`** - Main script (775 TODO items ready)
|
||||
|
||||
### Documentation
|
||||
- ✅ **`docs/guides/POPULATE_KANBAN.md`** - Step-by-step user guide
|
||||
- ✅ **`docs/guides/KANBAN_IMPLEMENTATION_SUMMARY.md`** - Complete overview
|
||||
- ✅ **`tools/project-management/README.md`** - Detailed script reference
|
||||
- ✅ **`tools/README.md`** - Updated with project management section
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start (3 Steps)
|
||||
|
||||
### Step 1: Authenticate with GitHub CLI
|
||||
|
||||
```bash
|
||||
gh auth login
|
||||
```
|
||||
|
||||
Choose:
|
||||
- GitHub.com
|
||||
- HTTPS protocol
|
||||
- Login with web browser
|
||||
|
||||
### Step 2: Preview Issues (Recommended)
|
||||
|
||||
```bash
|
||||
cd /path/to/metabuilder
|
||||
python3 tools/project-management/populate-kanban.py --dry-run --limit 10
|
||||
```
|
||||
|
||||
This shows you what the first 10 issues will look like.
|
||||
|
||||
### Step 3: Populate the Kanban
|
||||
|
||||
**⚠️ Warning**: This will create 775 issues and take 15-20 minutes.
|
||||
|
||||
```bash
|
||||
python3 tools/project-management/populate-kanban.py --create --project-id 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 What Gets Created
|
||||
|
||||
### Statistics
|
||||
- **Total Issues**: 775
|
||||
- **By Priority**:
|
||||
- 🔴 Critical: 40 (5%)
|
||||
- 🟠 High: 386 (50%)
|
||||
- 🟡 Medium: 269 (35%)
|
||||
- 🟢 Low: 80 (10%)
|
||||
|
||||
### Top Categories
|
||||
1. **feature** (292) - New features
|
||||
2. **workflow** (182) - SDLC improvements
|
||||
3. **core** (182) - Core functionality
|
||||
4. **enhancement** (160) - Improvements
|
||||
5. **infrastructure** (141) - DevOps
|
||||
|
||||
### Example Issue
|
||||
|
||||
**Title**: `npm run typecheck`
|
||||
|
||||
**Body**:
|
||||
```markdown
|
||||
**File:** `docs/todo/core/0-kickstart.md`
|
||||
**Section:** 15-Minute Local Sanity Check (Frontend)
|
||||
**Line:** 33
|
||||
|
||||
**Task:** `npm run typecheck`
|
||||
```
|
||||
|
||||
**Labels**: `workflow`, `core`, `🟠 High`
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation Guide
|
||||
|
||||
### For Quick Start
|
||||
👉 Read: **`docs/guides/POPULATE_KANBAN.md`**
|
||||
|
||||
### For Detailed Reference
|
||||
👉 Read: **`tools/project-management/README.md`**
|
||||
|
||||
### For Complete Overview
|
||||
👉 Read: **`docs/guides/KANBAN_IMPLEMENTATION_SUMMARY.md`**
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Advanced Options
|
||||
|
||||
### Export to JSON First (Recommended)
|
||||
```bash
|
||||
python3 tools/project-management/populate-kanban.py --output issues.json
|
||||
# Review the JSON, then create
|
||||
python3 tools/project-management/populate-kanban.py --create
|
||||
```
|
||||
|
||||
### Create Only Critical Issues
|
||||
```bash
|
||||
python3 tools/project-management/populate-kanban.py --output all.json
|
||||
cat all.json | jq '[.[] | select(.priority == "🔴 Critical")]' > critical.json
|
||||
# Then manually create from critical.json (40 issues)
|
||||
```
|
||||
|
||||
### Create in Batches
|
||||
```bash
|
||||
# First 50
|
||||
python3 tools/project-management/populate-kanban.py --create --limit 50
|
||||
# Wait, then run again (note: will create duplicates, so use limit carefully)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Verification
|
||||
|
||||
Test the script is working:
|
||||
|
||||
```bash
|
||||
# 1. Check help
|
||||
python3 tools/project-management/populate-kanban.py --help
|
||||
|
||||
# 2. Dry run with 3 issues
|
||||
python3 tools/project-management/populate-kanban.py --dry-run --limit 3
|
||||
|
||||
# 3. Export sample to JSON
|
||||
python3 tools/project-management/populate-kanban.py --output /tmp/test.json --limit 5
|
||||
cat /tmp/test.json | jq '.[0]'
|
||||
```
|
||||
|
||||
All tests should complete successfully! ✅
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Not Authenticated?
|
||||
```bash
|
||||
gh auth status
|
||||
gh auth login
|
||||
```
|
||||
|
||||
### Project Not Found?
|
||||
```bash
|
||||
# List your projects
|
||||
gh project list --owner johndoe6345789
|
||||
|
||||
# Use the correct ID
|
||||
python3 populate-kanban.py --create --project-id <correct-id>
|
||||
```
|
||||
|
||||
### Rate Limited?
|
||||
The script includes automatic pausing. If you still hit limits:
|
||||
- Wait 15-30 minutes
|
||||
- Use `--limit` to create fewer at once
|
||||
|
||||
---
|
||||
|
||||
## 📋 Next Steps After Population
|
||||
|
||||
Once issues are created:
|
||||
|
||||
1. **Organize** - Use project board columns (Backlog, In Progress, Done)
|
||||
2. **Triage** - Review and adjust priorities as needed
|
||||
3. **Assign** - Assign issues to team members
|
||||
4. **Milestone** - Group issues for releases
|
||||
5. **Labels** - Add custom labels (bug, etc.) if needed
|
||||
|
||||
---
|
||||
|
||||
## 🎉 You're Ready!
|
||||
|
||||
All tools are tested and working. The kanban board is ready to be populated with 775 issues organized by priority and category.
|
||||
|
||||
**Need help?** Check the documentation files listed above.
|
||||
|
||||
**Ready to go?** Run the 3 steps in "Quick Start" above! 🚀
|
||||
|
||||
---
|
||||
|
||||
**Status**: ✅ READY TO USE
|
||||
**Issues Ready**: 775
|
||||
**Target Board**: https://github.com/users/johndoe6345789/projects/2
|
||||
**Estimated Time**: 15-20 minutes
|
||||
2577
ROADMAP.md
Normal file
2577
ROADMAP.md
Normal file
File diff suppressed because it is too large
Load Diff
147
UI_STANDARDS.md
147
UI_STANDARDS.md
@@ -1,147 +0,0 @@
|
||||
# MetaBuilder UI Standards
|
||||
|
||||
## ⚠️ CRITICAL: Prohibited Dependencies
|
||||
|
||||
**DO NOT use these libraries in this project:**
|
||||
|
||||
- ❌ **Radix UI** (`@radix-ui/*`) - Removed in favor of Material-UI
|
||||
- ❌ **Tailwind CSS** - Removed in favor of SASS + MUI styling
|
||||
- ❌ **Any Radix UI primitives** - Use Material-UI equivalents instead
|
||||
|
||||
**DO use:**
|
||||
|
||||
- ✅ **Material-UI** (`@mui/material`, `@mui/icons-material`, `@mui/x-data-grid`)
|
||||
- ✅ **SASS/SCSS** for custom styling (module pattern preferred)
|
||||
- ✅ **MUI's `sx` prop** for inline styles with theme access
|
||||
- ✅ **MUI's theme system** for consistent design tokens
|
||||
|
||||
## Why This Change?
|
||||
|
||||
1. **Consistency**: Single UI library reduces complexity
|
||||
2. **Feature-Rich**: MUI provides comprehensive components out of the box
|
||||
3. **Better Theming**: Integrated theme system with light/dark mode
|
||||
4. **Data Components**: MUI X components for advanced data tables and pickers
|
||||
5. **Enterprise-Ready**: Better accessibility and documentation
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Installing Dependencies
|
||||
|
||||
```bash
|
||||
cd frontends/nextjs
|
||||
npm install
|
||||
```
|
||||
|
||||
This will install:
|
||||
- `@mui/material` - Core UI components
|
||||
- `@mui/icons-material` - Icon library
|
||||
- `@mui/x-data-grid` - Advanced data tables
|
||||
- `@emotion/react` & `@emotion/styled` - Required peer dependencies
|
||||
- `sass` - For custom SCSS styling
|
||||
|
||||
### Using MUI Components
|
||||
|
||||
```tsx
|
||||
import { Button, TextField, Dialog } from '@mui/material'
|
||||
import { Add as AddIcon } from '@mui/icons-material'
|
||||
|
||||
function MyComponent() {
|
||||
return (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<AddIcon />}
|
||||
>
|
||||
Click Me
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Styling with SASS
|
||||
|
||||
```scss
|
||||
// MyComponent.module.scss
|
||||
.container {
|
||||
padding: 16px;
|
||||
background: var(--mui-palette-background-paper);
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
import styles from './MyComponent.module.scss'
|
||||
import { Card } from '@mui/material'
|
||||
|
||||
export function MyComponent() {
|
||||
return (
|
||||
<Card className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
{/* content */}
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Using the `sx` Prop
|
||||
|
||||
```tsx
|
||||
import { Box, Typography } from '@mui/material'
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
p: 3,
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" color="primary.main">
|
||||
Title
|
||||
</Typography>
|
||||
</Box>
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[UI Migration Guide](./docs/UI_MIGRATION.md)** - Complete migration reference
|
||||
- **[MUI Theme Configuration](./frontends/nextjs/src/theme/mui-theme.ts)** - Theme setup
|
||||
- **[Material-UI Docs](https://mui.com/)** - Official MUI documentation
|
||||
|
||||
## For AI Assistants & Code Generators
|
||||
|
||||
When working on this codebase:
|
||||
|
||||
1. **Never import from `@radix-ui/*`** - Use `@mui/material` instead
|
||||
2. **Never use Tailwind utility classes** in `className` props - Use MUI's `sx` prop or SCSS modules
|
||||
3. **Always use MUI components** for UI elements (Button, Dialog, TextField, etc.)
|
||||
4. **Use `@mui/icons-material`** for icons, not lucide-react or heroicons
|
||||
5. **Create `.module.scss` files** for component-specific custom styles
|
||||
6. **Access theme values** via `sx` prop or SASS variables
|
||||
|
||||
## Component Alternatives
|
||||
|
||||
| ❌ Don't Use | ✅ Use Instead |
|
||||
|-------------|---------------|
|
||||
| Radix UI Dialog | MUI Dialog |
|
||||
| Radix UI Select | MUI Select |
|
||||
| Radix UI Checkbox | MUI Checkbox |
|
||||
| Radix UI Switch | MUI Switch |
|
||||
| Tailwind classes | MUI sx prop or SCSS |
|
||||
| lucide-react icons | @mui/icons-material |
|
||||
|
||||
## Need Help?
|
||||
|
||||
See [docs/UI_MIGRATION.md](./docs/UI_MIGRATION.md) for:
|
||||
- Component mapping reference
|
||||
- Code examples
|
||||
- Common patterns
|
||||
- Migration checklist
|
||||
83
bun.lock
83
bun.lock
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.19.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"prisma": "^6.19.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@prisma/client": ["@prisma/client@6.19.1", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"] }, "sha512-4SXj4Oo6HyQkLUWT8Ke5R0PTAfVOKip5Roo+6+b2EDTkFg5be0FnBWiuRJc0BC0sRQIWGMLKW1XguhVfW/z3/A=="],
|
||||
|
||||
"@prisma/config": ["@prisma/config@6.19.1", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-bUL/aYkGXLwxVGhJmQMtslLT7KPEfUqmRa919fKI4wQFX4bIFUKiY8Jmio/2waAjjPYrtuDHa7EsNCnJTXxiOw=="],
|
||||
|
||||
"@prisma/debug": ["@prisma/debug@6.19.1", "", {}, "sha512-h1JImhlAd/s5nhY/e9qkAzausWldbeT+e4nZF7A4zjDYBF4BZmKDt4y0jK7EZapqOm1kW7V0e9agV/iFDy3fWw=="],
|
||||
|
||||
"@prisma/engines": ["@prisma/engines@6.19.1", "", { "dependencies": { "@prisma/debug": "6.19.1", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/fetch-engine": "6.19.1", "@prisma/get-platform": "6.19.1" } }, "sha512-xy95dNJ7DiPf9IJ3oaVfX785nbFl7oNDzclUF+DIiJw6WdWCvPl0LPU0YqQLsrwv8N64uOQkH391ujo3wSo+Nw=="],
|
||||
|
||||
"@prisma/engines-version": ["@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "", {}, "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA=="],
|
||||
|
||||
"@prisma/fetch-engine": ["@prisma/fetch-engine@6.19.1", "", { "dependencies": { "@prisma/debug": "6.19.1", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/get-platform": "6.19.1" } }, "sha512-mmgcotdaq4VtAHO6keov3db+hqlBzQS6X7tR7dFCbvXjLVTxBYdSJFRWz+dq7F9p6dvWyy1X0v8BlfRixyQK6g=="],
|
||||
|
||||
"@prisma/get-platform": ["@prisma/get-platform@6.19.1", "", { "dependencies": { "@prisma/debug": "6.19.1" } }, "sha512-zsg44QUiQAnFUyh6Fbt7c9HjMXHwFTqtrgcX7DAZmRgnkPyYT7Sh8Mn8D5PuuDYNtMOYcpLGg576MLfIORsBYw=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
|
||||
|
||||
"confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
|
||||
|
||||
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
|
||||
|
||||
"deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="],
|
||||
|
||||
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||
|
||||
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||
|
||||
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||
|
||||
"effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="],
|
||||
|
||||
"empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="],
|
||||
|
||||
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
||||
|
||||
"fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
|
||||
|
||||
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": "dist/cli.mjs" }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": "lib/jiti-cli.mjs" }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
|
||||
"nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": "dist/cli.mjs" }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="],
|
||||
|
||||
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
||||
|
||||
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
||||
|
||||
"prisma": ["prisma@6.19.1", "", { "dependencies": { "@prisma/config": "6.19.1", "@prisma/engines": "6.19.1" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": "build/index.js" }, "sha512-XRfmGzh6gtkc/Vq3LqZJcS2884dQQW3UhPo6jNRoiTW95FFQkXFg8vkYEy6og+Pyv0aY7zRQ7Wn1Cvr56XjhQQ=="],
|
||||
|
||||
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
|
||||
|
||||
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
-P ubuntu-latest=catthehacker/ubuntu:act-latest
|
||||
-P ubuntu-22.04=catthehacker/ubuntu:act-22.04
|
||||
--container-architecture linux/amd64
|
||||
--use-gitignore=false
|
||||
-P ubuntu-latest=catthehacker/ubuntu:act-latest
|
||||
-P ubuntu-22.04=catthehacker/ubuntu:act-22.04
|
||||
--container-architecture linux/amd64
|
||||
--use-gitignore=false
|
||||
83
config/bun.lock
Normal file
83
config/bun.lock
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.19.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"prisma": "^6.19.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@prisma/client": ["@prisma/client@6.19.1", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"] }, "sha512-4SXj4Oo6HyQkLUWT8Ke5R0PTAfVOKip5Roo+6+b2EDTkFg5be0FnBWiuRJc0BC0sRQIWGMLKW1XguhVfW/z3/A=="],
|
||||
|
||||
"@prisma/config": ["@prisma/config@6.19.1", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-bUL/aYkGXLwxVGhJmQMtslLT7KPEfUqmRa919fKI4wQFX4bIFUKiY8Jmio/2waAjjPYrtuDHa7EsNCnJTXxiOw=="],
|
||||
|
||||
"@prisma/debug": ["@prisma/debug@6.19.1", "", {}, "sha512-h1JImhlAd/s5nhY/e9qkAzausWldbeT+e4nZF7A4zjDYBF4BZmKDt4y0jK7EZapqOm1kW7V0e9agV/iFDy3fWw=="],
|
||||
|
||||
"@prisma/engines": ["@prisma/engines@6.19.1", "", { "dependencies": { "@prisma/debug": "6.19.1", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/fetch-engine": "6.19.1", "@prisma/get-platform": "6.19.1" } }, "sha512-xy95dNJ7DiPf9IJ3oaVfX785nbFl7oNDzclUF+DIiJw6WdWCvPl0LPU0YqQLsrwv8N64uOQkH391ujo3wSo+Nw=="],
|
||||
|
||||
"@prisma/engines-version": ["@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "", {}, "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA=="],
|
||||
|
||||
"@prisma/fetch-engine": ["@prisma/fetch-engine@6.19.1", "", { "dependencies": { "@prisma/debug": "6.19.1", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/get-platform": "6.19.1" } }, "sha512-mmgcotdaq4VtAHO6keov3db+hqlBzQS6X7tR7dFCbvXjLVTxBYdSJFRWz+dq7F9p6dvWyy1X0v8BlfRixyQK6g=="],
|
||||
|
||||
"@prisma/get-platform": ["@prisma/get-platform@6.19.1", "", { "dependencies": { "@prisma/debug": "6.19.1" } }, "sha512-zsg44QUiQAnFUyh6Fbt7c9HjMXHwFTqtrgcX7DAZmRgnkPyYT7Sh8Mn8D5PuuDYNtMOYcpLGg576MLfIORsBYw=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
|
||||
|
||||
"confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
|
||||
|
||||
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
|
||||
|
||||
"deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="],
|
||||
|
||||
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||
|
||||
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||
|
||||
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||
|
||||
"effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="],
|
||||
|
||||
"empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="],
|
||||
|
||||
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
||||
|
||||
"fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
|
||||
|
||||
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": "dist/cli.mjs" }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": "lib/jiti-cli.mjs" }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
|
||||
"nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": "dist/cli.mjs" }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="],
|
||||
|
||||
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
||||
|
||||
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
||||
|
||||
"prisma": ["prisma@6.19.1", "", { "dependencies": { "@prisma/config": "6.19.1", "@prisma/engines": "6.19.1" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": "build/index.js" }, "sha512-XRfmGzh6gtkc/Vq3LqZJcS2884dQQW3UhPo6jNRoiTW95FFQkXFg8vkYEy6og+Pyv0aY7zRQ7Wn1Cvr56XjhQQ=="],
|
||||
|
||||
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
|
||||
|
||||
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||
}
|
||||
}
|
||||
@@ -29,12 +29,13 @@ export default tseslint.config(
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
// Strict type checking rules (as warnings for gradual adoption)
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': ['warn', {
|
||||
// Strict type checking rules (as errors for stricter enforcement)
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['error', {
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
}],
|
||||
'@typescript-eslint/strict-boolean-expressions': 'warn',
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
'@typescript-eslint/no-misused-promises': 'warn',
|
||||
// Code quality rules
|
||||
|
||||
6
config/misc/env/.actrc
vendored
6
config/misc/env/.actrc
vendored
@@ -1,3 +1,3 @@
|
||||
-P ubuntu-latest=catthehacker/ubuntu:act-latest
|
||||
--env ACT=true
|
||||
-v
|
||||
-P ubuntu-latest=catthehacker/ubuntu:act-latest
|
||||
--env ACT=true
|
||||
-v
|
||||
|
||||
2
config/misc/env/.secrets.example
vendored
2
config/misc/env/.secrets.example
vendored
@@ -1 +1 @@
|
||||
GITHUB_TOKEN=ghp_your_token_here
|
||||
GITHUB_TOKEN=ghp_your_token_here
|
||||
|
||||
1651
config/package-lock.json
generated
Normal file
1651
config/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
config/package.json
Normal file
26
config/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@metabuilder/config",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"generate:package": "npx ts-node scripts/generate-package.ts",
|
||||
"extract:preview": "cd frontends/nextjs && npm run extract:preview",
|
||||
"extract:quick": "cd frontends/nextjs && npm run extract:quick",
|
||||
"extract:auto": "cd frontends/nextjs && npm run extract:auto",
|
||||
"extract:all": "cd frontends/nextjs && npm run extract:all",
|
||||
"extract:help": "cd frontends/nextjs && npm run extract:help",
|
||||
"db:generate": "npx prisma generate --schema=../prisma/schema.prisma",
|
||||
"db:migrate": "npx prisma migrate dev --schema=../prisma/schema.prisma"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@prisma/client": "^7.2.0",
|
||||
"prisma": "^7.2.0",
|
||||
"yaml": "^2.8.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/adapter-better-sqlite3": "^7.2.0",
|
||||
"better-sqlite3": "^12.5.0",
|
||||
"clsx": "^2.1.1",
|
||||
"jszip": "^3.10.1"
|
||||
}
|
||||
}
|
||||
138
dbal/.gitignore
vendored
138
dbal/.gitignore
vendored
@@ -1,69 +1,69 @@
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
*.log
|
||||
|
||||
*.o
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
*.sqlite
|
||||
*.db
|
||||
*.db-journal
|
||||
|
||||
.env
|
||||
.env.local
|
||||
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
|
||||
.vscode/
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
coverage/
|
||||
.nyc_output/
|
||||
*.lcov
|
||||
|
||||
*.generated.ts
|
||||
*.generated.hpp
|
||||
*.generated.cpp
|
||||
|
||||
compile_commands.json
|
||||
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
Makefile
|
||||
|
||||
ts/src/core/types.generated.ts
|
||||
cpp/include/dbal/types.generated.hpp
|
||||
|
||||
common/golden/ts_results.json
|
||||
common/golden/cpp_results.json
|
||||
|
||||
*.test.db
|
||||
test-results/
|
||||
|
||||
.cache/
|
||||
.pytest_cache/
|
||||
|
||||
logs/
|
||||
*.audit.log
|
||||
|
||||
*.key
|
||||
*.crt
|
||||
*.pem
|
||||
secrets.yaml
|
||||
|
||||
/var/lib/dbal/
|
||||
/var/log/dbal/
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
*.log
|
||||
|
||||
*.o
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
*.sqlite
|
||||
*.db
|
||||
*.db-journal
|
||||
|
||||
.env
|
||||
.env.local
|
||||
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
|
||||
.vscode/
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
coverage/
|
||||
.nyc_output/
|
||||
*.lcov
|
||||
|
||||
*.generated.ts
|
||||
*.generated.hpp
|
||||
*.generated.cpp
|
||||
|
||||
compile_commands.json
|
||||
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
Makefile
|
||||
|
||||
ts/src/core/types.generated.ts
|
||||
cpp/include/dbal/types.generated.hpp
|
||||
|
||||
common/golden/ts_results.json
|
||||
common/golden/cpp_results.json
|
||||
|
||||
*.test.db
|
||||
test-results/
|
||||
|
||||
.cache/
|
||||
.pytest_cache/
|
||||
|
||||
logs/
|
||||
*.audit.log
|
||||
|
||||
*.key
|
||||
*.crt
|
||||
*.pem
|
||||
secrets.yaml
|
||||
|
||||
/var/lib/dbal/
|
||||
/var/log/dbal/
|
||||
|
||||
604
dbal/AGENTS.md
604
dbal/AGENTS.md
@@ -1,604 +0,0 @@
|
||||
# Agent Development Guide for DBAL
|
||||
|
||||
This document provides guidance for AI agents and automated tools working with the DBAL codebase.
|
||||
|
||||
## Architecture Philosophy
|
||||
|
||||
The DBAL is designed as a **language-agnostic contract system** that separates:
|
||||
|
||||
1. **API Definition** (in YAML) - The source of truth
|
||||
2. **Development Implementation** (TypeScript) - Fast iteration, testing, debugging
|
||||
3. **Production Implementation** (C++) - Security, performance, isolation
|
||||
4. **Shared Test Vectors** - Guarantees behavioral consistency
|
||||
|
||||
## Key Principles for Agents
|
||||
|
||||
### 1. API Contract is Source of Truth
|
||||
|
||||
**Always start with the API definition** when adding features:
|
||||
|
||||
```
|
||||
1. Define entity in api/schema/entities/
|
||||
2. Define operations in api/schema/operations/
|
||||
3. Generate TypeScript types: python tools/codegen/gen_types.py
|
||||
4. Generate C++ types: python tools/codegen/gen_types.py --lang=cpp
|
||||
5. Implement in adapters
|
||||
6. Add conformance tests
|
||||
```
|
||||
|
||||
**Never** add fields, operations, or entities directly in TypeScript or C++ without updating the YAML schemas first.
|
||||
|
||||
### 2. TypeScript is for Development Speed
|
||||
|
||||
The TypeScript implementation prioritizes:
|
||||
- **Fast iteration** - Quick to modify and test
|
||||
- **Rich ecosystem** - npm packages, debugging tools
|
||||
- **Easy prototyping** - Try ideas quickly
|
||||
|
||||
Use TypeScript for:
|
||||
- New feature development
|
||||
- Schema iteration
|
||||
- Integration testing
|
||||
- Developer debugging
|
||||
|
||||
### 3. C++ is for Production Security
|
||||
|
||||
The C++ implementation prioritizes:
|
||||
- **Security** - Process isolation, sandboxing, no user code execution
|
||||
- **Performance** - Optimized queries, connection pooling
|
||||
- **Stability** - Static typing, memory safety
|
||||
- **Auditability** - All operations logged
|
||||
|
||||
C++ daemon provides:
|
||||
- Credential protection (user code never sees DB URLs/passwords)
|
||||
- Query validation and sanitization
|
||||
- Row-level security enforcement
|
||||
- Resource limits and quotas
|
||||
|
||||
### 4. Conformance Tests Guarantee Parity
|
||||
|
||||
Every operation **must** have conformance tests that run against both implementations:
|
||||
|
||||
```yaml
|
||||
# common/contracts/conformance_cases.yaml
|
||||
- name: "User CRUD operations"
|
||||
setup:
|
||||
- create_user:
|
||||
username: "testuser"
|
||||
email: "test@example.com"
|
||||
tests:
|
||||
- create:
|
||||
entity: Post
|
||||
input: { title: "Test", author_id: "$setup.user.id" }
|
||||
expect: { status: "success" }
|
||||
- read:
|
||||
entity: Post
|
||||
input: { id: "$prev.id" }
|
||||
expect: { title: "Test" }
|
||||
```
|
||||
|
||||
CI/CD runs these tests on **both** TypeScript and C++ implementations. If they diverge, the build fails.
|
||||
|
||||
## Development Workflow for Agents
|
||||
|
||||
### Adding a New Entity
|
||||
|
||||
```bash
|
||||
# 1. Create entity schema
|
||||
cat > api/schema/entities/comment.yaml << EOF
|
||||
entity: Comment
|
||||
version: "1.0"
|
||||
fields:
|
||||
id: { type: uuid, primary: true, generated: true }
|
||||
content: { type: text, required: true }
|
||||
post_id: { type: uuid, required: true, foreign_key: { entity: Post, field: id } }
|
||||
author_id: { type: uuid, required: true }
|
||||
created_at: { type: datetime, generated: true }
|
||||
EOF
|
||||
|
||||
# 2. Create operations
|
||||
cat > api/schema/operations/comment.ops.yaml << EOF
|
||||
operations:
|
||||
create:
|
||||
input: [content, post_id, author_id]
|
||||
output: Comment
|
||||
acl_required: ["comment:create"]
|
||||
list:
|
||||
input: [post_id]
|
||||
output: Comment[]
|
||||
acl_required: ["comment:read"]
|
||||
EOF
|
||||
|
||||
# 3. Generate types
|
||||
python tools/codegen/gen_types.py
|
||||
|
||||
# 4. Implement adapters (both TS and C++)
|
||||
# - ts/src/adapters/prisma/mapping.ts
|
||||
# - cpp/src/adapters/prisma/prisma_adapter.cpp
|
||||
|
||||
# 5. Add conformance tests
|
||||
cat > common/contracts/comment_tests.yaml << EOF
|
||||
- name: "Comment CRUD"
|
||||
operations:
|
||||
- action: create
|
||||
entity: Comment
|
||||
input: { content: "Great post!", post_id: "post_1", author_id: "user_1" }
|
||||
expected: { status: success }
|
||||
EOF
|
||||
|
||||
# 6. Run conformance
|
||||
python tools/conformance/run_all.py
|
||||
```
|
||||
|
||||
### Modifying an Existing Entity
|
||||
|
||||
```bash
|
||||
# 1. Update YAML schema
|
||||
vim api/schema/entities/user.yaml
|
||||
# Add: avatar_url: { type: string, optional: true }
|
||||
|
||||
# 2. Regenerate types
|
||||
python tools/codegen/gen_types.py
|
||||
|
||||
# 3. Create migration (if using Prisma)
|
||||
cd backends/prisma
|
||||
npx prisma migrate dev --name add_avatar_url
|
||||
|
||||
# 4. Update adapters to handle new field
|
||||
# Both ts/src/adapters/prisma/mapping.ts and C++ version
|
||||
|
||||
# 5. Add tests
|
||||
# Update common/contracts/user_tests.yaml
|
||||
|
||||
# 6. Verify conformance
|
||||
python tools/conformance/run_all.py
|
||||
```
|
||||
|
||||
### Adding a Backend Adapter
|
||||
|
||||
```bash
|
||||
# 1. Define capabilities
|
||||
cat > api/schema/capabilities.yaml << EOF
|
||||
adapters:
|
||||
mongodb:
|
||||
transactions: true
|
||||
joins: false
|
||||
full_text_search: true
|
||||
ttl: true
|
||||
EOF
|
||||
|
||||
# 2. Create TypeScript adapter
|
||||
mkdir -p ts/src/adapters/mongodb
|
||||
cat > ts/src/adapters/mongodb/index.ts << EOF
|
||||
export class MongoDBAdapter implements DBALAdapter {
|
||||
async create(entity: string, data: any): Promise<any> {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# 3. Create C++ adapter
|
||||
mkdir -p cpp/src/adapters/mongodb
|
||||
# Implement MongoDBAdapter class
|
||||
|
||||
# 4. Register adapter
|
||||
# Update ts/src/core/client.ts and cpp/src/client.cpp
|
||||
|
||||
# 5. Test conformance
|
||||
python tools/conformance/run_all.py --adapter=mongodb
|
||||
```
|
||||
|
||||
## File Organization Rules
|
||||
|
||||
### api/ (Language-Agnostic Contracts)
|
||||
|
||||
```
|
||||
api/
|
||||
├── schema/
|
||||
│ ├── entities/ # One file per entity
|
||||
│ │ ├── user.yaml
|
||||
│ │ ├── post.yaml
|
||||
│ │ └── comment.yaml
|
||||
│ ├── operations/ # One file per entity
|
||||
│ │ ├── user.ops.yaml
|
||||
│ │ ├── post.ops.yaml
|
||||
│ │ └── comment.ops.yaml
|
||||
│ ├── errors.yaml # Single file for all errors
|
||||
│ └── capabilities.yaml # Single file for all adapter capabilities
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- One entity per file
|
||||
- Use lowercase with underscores for filenames
|
||||
- Version every entity (semantic versioning)
|
||||
- Document breaking changes in comments
|
||||
|
||||
### ts/ (TypeScript Implementation)
|
||||
|
||||
```
|
||||
ts/src/
|
||||
├── core/ # Core abstractions
|
||||
│ ├── client.ts # Main DBAL client
|
||||
│ ├── types.ts # Generated from YAML
|
||||
│ └── errors.ts # Error classes
|
||||
├── adapters/ # One directory per backend
|
||||
│ ├── prisma/
|
||||
│ ├── sqlite/
|
||||
│ └── mongodb/
|
||||
├── query/ # Query builder (backend-agnostic)
|
||||
└── runtime/ # Config, secrets, telemetry
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Keep files under 300 lines
|
||||
- One class per file
|
||||
- Use barrel exports (index.ts)
|
||||
- No circular dependencies
|
||||
|
||||
### cpp/ (C++ Implementation)
|
||||
|
||||
```
|
||||
cpp/
|
||||
├── include/dbal/ # Public headers
|
||||
├── src/ # Implementation
|
||||
├── tests/ # Tests
|
||||
└── CMakeLists.txt
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Header guards: `#ifndef DBAL_CLIENT_HPP`
|
||||
- Namespace: `dbal::`
|
||||
- Use modern C++17 features
|
||||
- RAII for resource management
|
||||
|
||||
### common/ (Shared Test Vectors)
|
||||
|
||||
```
|
||||
common/
|
||||
├── fixtures/ # Sample data
|
||||
│ ├── seed/
|
||||
│ └── datasets/
|
||||
├── golden/ # Expected results
|
||||
└── contracts/ # Conformance test definitions
|
||||
├── user_tests.yaml
|
||||
├── post_tests.yaml
|
||||
└── conformance_cases.yaml
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- YAML for test definitions
|
||||
- JSON for fixtures
|
||||
- One test suite per entity
|
||||
- Include edge cases
|
||||
|
||||
## Code Generation
|
||||
|
||||
### Automated Type Generation
|
||||
|
||||
The DBAL uses Python scripts to generate TypeScript and C++ types from YAML schemas:
|
||||
|
||||
```python
|
||||
# tools/codegen/gen_types.py
|
||||
def generate_typescript_types(schema_dir: Path, output_file: Path):
|
||||
"""Generate TypeScript interfaces from YAML schemas"""
|
||||
|
||||
def generate_cpp_types(schema_dir: Path, output_dir: Path):
|
||||
"""Generate C++ structs from YAML schemas"""
|
||||
```
|
||||
|
||||
**When to regenerate:**
|
||||
- After modifying any YAML in `api/schema/`
|
||||
- Before running tests
|
||||
- As part of CI/CD pipeline
|
||||
|
||||
### Manual Code vs Generated Code
|
||||
|
||||
**Generated (Never edit manually):**
|
||||
- `ts/src/core/types.ts` - Entity interfaces
|
||||
- `ts/src/core/errors.ts` - Error classes
|
||||
- `cpp/include/dbal/types.hpp` - Entity structs
|
||||
- `cpp/include/dbal/errors.hpp` - Error types
|
||||
|
||||
**Manual (Safe to edit):**
|
||||
- Adapter implementations
|
||||
- Query builder
|
||||
- Client facade
|
||||
- Utility functions
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### 1. Unit Tests (Per Implementation)
|
||||
|
||||
```bash
|
||||
# TypeScript
|
||||
cd ts && npm run test:unit
|
||||
|
||||
# C++
|
||||
cd cpp && ./build/tests/unit_tests
|
||||
```
|
||||
|
||||
Test individual functions and classes in isolation.
|
||||
|
||||
### 2. Integration Tests (Per Implementation)
|
||||
|
||||
```bash
|
||||
# TypeScript
|
||||
cd ts && npm run test:integration
|
||||
|
||||
# C++
|
||||
cd cpp && ./build/tests/integration_tests
|
||||
```
|
||||
|
||||
Test adapters against real databases (with Docker).
|
||||
|
||||
### 3. Conformance Tests (Cross-Implementation)
|
||||
|
||||
```bash
|
||||
# Both implementations
|
||||
python tools/conformance/run_all.py
|
||||
```
|
||||
|
||||
**Critical:** These must pass for both TS and C++. If they diverge, it's a bug.
|
||||
|
||||
### 4. Security Tests (C++ Only)
|
||||
|
||||
```bash
|
||||
cd cpp && ./build/tests/security_tests
|
||||
```
|
||||
|
||||
Test sandboxing, ACL enforcement, SQL injection prevention.
|
||||
|
||||
## Security Considerations for Agents
|
||||
|
||||
### What NOT to Do
|
||||
|
||||
❌ **Never** expose database credentials to user code
|
||||
❌ **Never** allow user code to construct raw SQL queries
|
||||
❌ **Never** skip ACL checks
|
||||
❌ **Never** trust user input without validation
|
||||
❌ **Never** log sensitive data (passwords, tokens, PII)
|
||||
|
||||
### What TO Do
|
||||
|
||||
✅ **Always** validate input against schema
|
||||
✅ **Always** enforce row-level security
|
||||
✅ **Always** use parameterized queries
|
||||
✅ **Always** log security-relevant operations
|
||||
✅ **Always** test with malicious input
|
||||
|
||||
### Sandboxing Requirements (C++ Daemon)
|
||||
|
||||
The C++ daemon must:
|
||||
|
||||
1. **Run with minimal privileges** (drop root, use dedicated user)
|
||||
2. **Restrict file system access** (no write outside /var/lib/dbal/)
|
||||
3. **Limit network access** (only to DB, no outbound internet)
|
||||
4. **Enforce resource limits** (CPU, memory, connections)
|
||||
5. **Validate all RPC calls** (schema conformance, ACL checks)
|
||||
|
||||
### ACL Enforcement
|
||||
|
||||
Every operation must check:
|
||||
|
||||
```cpp
|
||||
// C++ daemon
|
||||
bool DBALDaemon::authorize(const Request& req) {
|
||||
User user = req.user();
|
||||
string entity = req.entity();
|
||||
string operation = req.operation();
|
||||
|
||||
// 1. Check entity-level permission
|
||||
if (!acl_.hasPermission(user, entity, operation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Apply row-level filter
|
||||
if (operation == "update" || operation == "delete") {
|
||||
return acl_.canAccessRow(user, entity, req.id());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Workflow
|
||||
|
||||
```yaml
|
||||
name: DBAL CI/CD
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
typescript:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: cd dbal/ts && npm ci
|
||||
- run: npm run test:unit
|
||||
- run: npm run test:integration
|
||||
|
||||
cpp:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: cd dbal/cpp && cmake -B build && cmake --build build
|
||||
- run: ./build/tests/unit_tests
|
||||
- run: ./build/tests/integration_tests
|
||||
|
||||
conformance:
|
||||
needs: [typescript, cpp]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: python dbal/tools/conformance/run_all.py
|
||||
```
|
||||
|
||||
### Pre-commit Hooks
|
||||
|
||||
```bash
|
||||
# .git/hooks/pre-commit
|
||||
#!/bin/bash
|
||||
cd dbal/api/schema
|
||||
if git diff --cached --name-only | grep -q "\.yaml$"; then
|
||||
echo "YAML schema changed, regenerating types..."
|
||||
python ../../tools/codegen/gen_types.py
|
||||
git add ../ts/src/core/types.ts
|
||||
git add ../cpp/include/dbal/types.hpp
|
||||
fi
|
||||
```
|
||||
|
||||
## Deployment Architecture
|
||||
|
||||
### Development Environment
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Spark App (TS) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ DBAL Client (TS)│
|
||||
└────────┬────────┘
|
||||
│ (direct)
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Prisma Client │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ SQLite / DB │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### Production Environment
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Spark App (TS) │
|
||||
└────────┬────────┘
|
||||
│ gRPC
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ DBAL Client (TS)│
|
||||
└────────┬────────┘
|
||||
│ gRPC/WS
|
||||
▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ DBAL Daemon(C++)│────▶│ Network Policy │
|
||||
│ [Sandboxed] │ │ (Firewall) │
|
||||
└────────┬────────┘ └─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Prisma Client │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ PostgreSQL │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### Docker Compose Example
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
dbal-daemon:
|
||||
build: ./dbal/cpp
|
||||
container_name: dbal-daemon
|
||||
ports:
|
||||
- "50051:50051"
|
||||
environment:
|
||||
- DBAL_MODE=production
|
||||
- DBAL_SANDBOX=strict
|
||||
- DATABASE_URL=postgresql://user:pass@postgres:5432/db
|
||||
volumes:
|
||||
- ./config:/config:ro
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
read_only: true
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
|
||||
postgres:
|
||||
image: postgres:15
|
||||
container_name: dbal-postgres
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=secure_password
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- internal
|
||||
|
||||
networks:
|
||||
internal:
|
||||
internal: true
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
```
|
||||
|
||||
## Troubleshooting for Agents
|
||||
|
||||
### Problem: Types out of sync with schema
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
python tools/codegen/gen_types.py
|
||||
```
|
||||
|
||||
### Problem: Conformance tests failing
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# Run verbose
|
||||
python tools/conformance/run_all.py --verbose
|
||||
|
||||
# Compare outputs
|
||||
diff common/golden/ts_results.json common/golden/cpp_results.json
|
||||
```
|
||||
|
||||
### Problem: C++ daemon won't start in production
|
||||
|
||||
**Check:**
|
||||
1. Permissions: `ls -la /var/lib/dbal/`
|
||||
2. Ports: `netstat -tlnp | grep 50051`
|
||||
3. Logs: `journalctl -u dbal-daemon`
|
||||
4. Database connectivity: `nc -zv postgres 5432`
|
||||
|
||||
### Problem: Security audit failing
|
||||
|
||||
**Review:**
|
||||
- No hardcoded secrets
|
||||
- All queries use parameters
|
||||
- ACL checks on every operation
|
||||
- Audit logs enabled
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
1. ✅ **Schema first** - Define in YAML, generate code
|
||||
2. ✅ **Test both** - TS and C++ must pass conformance tests
|
||||
3. ✅ **Security by default** - ACL on every operation
|
||||
4. ✅ **Documentation** - Update README when adding features
|
||||
5. ✅ **Versioning** - Semantic versioning for API changes
|
||||
6. ✅ **Backward compatibility** - Support N-1 versions
|
||||
7. ✅ **Fail fast** - Validate early, error clearly
|
||||
8. ✅ **Audit everything** - Log security-relevant operations
|
||||
9. ✅ **Principle of least privilege** - Minimal permissions
|
||||
10. ✅ **Defense in depth** - Multiple layers of security
|
||||
|
||||
## Resources
|
||||
|
||||
- **API Schema Reference**: [api/schema/README.md](api/schema/README.md)
|
||||
- **TypeScript Guide**: [ts/README.md](ts/README.md)
|
||||
- **C++ Guide**: [cpp/README.md](cpp/README.md)
|
||||
- **Security Guide**: [docs/SECURITY.md](../docs/SECURITY.md)
|
||||
- **Contributing**: [docs/CONTRIBUTING.md](../docs/CONTRIBUTING.md)
|
||||
@@ -1,528 +0,0 @@
|
||||
# DBAL Implementation Summary
|
||||
|
||||
## Phase 2: Hybrid Mode - COMPLETE ✅
|
||||
|
||||
A complete, production-ready DBAL system that works entirely within GitHub Spark's constraints while preparing for future C++ daemon integration.
|
||||
|
||||
## What Was Created
|
||||
|
||||
### 1. **Complete TypeScript DBAL Client**
|
||||
|
||||
#### Prisma Adapter (`ts/src/adapters/prisma-adapter.ts`) ✅
|
||||
- Full CRUD operations (create, read, update, delete, list)
|
||||
- Query timeout protection (30s default, configurable)
|
||||
- Flexible filter and sort options
|
||||
- Pagination support with hasMore indicator
|
||||
- Comprehensive error handling with proper error types
|
||||
- Capability detection (transactions, joins, JSON queries, etc.)
|
||||
- Connection pooling support
|
||||
|
||||
#### ACL Security Layer (`ts/src/adapters/acl-adapter.ts`) ✅
|
||||
- Role-based access control (user, admin, god, supergod)
|
||||
- Operation-level permissions (create, read, update, delete, list)
|
||||
- Row-level security filters (users can only access their own data)
|
||||
- Comprehensive audit logging for all operations
|
||||
- Pre-configured rules for all MetaBuilder entities
|
||||
- Configurable security policies
|
||||
|
||||
#### WebSocket Bridge (`ts/src/bridges/websocket-bridge.ts`) ✅
|
||||
- WebSocket-based RPC protocol for future C++ daemon
|
||||
- Request/response tracking with unique IDs
|
||||
- Timeout handling (30s default)
|
||||
- Auto-reconnection logic
|
||||
- Clean error propagation
|
||||
- Ready for Phase 3 integration
|
||||
|
||||
#### Enhanced Client (`ts/src/core/client.ts`) ✅
|
||||
- Automatic adapter selection based on config
|
||||
- Optional ACL wrapping for security
|
||||
- Development vs production mode switching
|
||||
- Clean, type-safe API for users, pages, and components
|
||||
- Proper resource cleanup
|
||||
|
||||
### 2. **Integration Layer**
|
||||
|
||||
#### DBAL Client Helper (`src/lib/dbal-client.ts`) ✅
|
||||
- Easy integration with MetaBuilder
|
||||
- Automatic authentication context
|
||||
- Configuration management
|
||||
- Migration helper functions
|
||||
|
||||
### 3. **Comprehensive Documentation**
|
||||
|
||||
#### Phase 2 Implementation Guide (`dbal/PHASE2_IMPLEMENTATION.md`) ✅
|
||||
- Complete architecture documentation
|
||||
- Usage examples for all operations
|
||||
- Security features explanation
|
||||
- Integration guide with MetaBuilder
|
||||
- Performance characteristics
|
||||
- Testing guidelines
|
||||
- Migration path from current system
|
||||
|
||||
#### Phase 3 Daemon Specification (`dbal/cpp/PHASE3_DAEMON.md`) ✅
|
||||
- C++ daemon architecture
|
||||
- Security hardening guidelines
|
||||
- Deployment options (Docker, Kubernetes, systemd)
|
||||
- Monitoring and metrics
|
||||
- Performance benchmarks
|
||||
- Migration guide from Phase 2
|
||||
|
||||
## Architecture (Phase 2)
|
||||
|
||||
1. **Secure database access** through a C++ daemon layer
|
||||
2. **Language-agnostic API** defined in YAML schemas
|
||||
3. **Dual implementations** in TypeScript (dev) and C++ (production)
|
||||
4. **Conformance testing** to ensure behavioral consistency
|
||||
5. **GitHub Spark integration** path for deployment
|
||||
|
||||
## Architecture (Phase 2)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ MetaBuilder Application (React/TypeScript) │
|
||||
└────────────────────────┬────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ DBAL Client │
|
||||
│ (Mode: development or production) │
|
||||
└────────────────────────┬────────────────────────────────┘
|
||||
│
|
||||
┌────────────────┴────────────────┐
|
||||
│ │
|
||||
▼ (development) ▼ (production)
|
||||
┌──────────────────┐ ┌──────────────────────┐
|
||||
│ ACL Adapter │ │ WebSocket Bridge │
|
||||
│ (Security Layer) │ │ (RPC Protocol) │
|
||||
└────────┬─────────┘ └──────────┬───────────┘
|
||||
│ │
|
||||
▼ │
|
||||
┌──────────────────┐ │
|
||||
│ Prisma Adapter │ │
|
||||
│ (DB Operations) │ │
|
||||
└────────┬─────────┘ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────┐ ┌──────────────────────┐
|
||||
│ Prisma Client │ │ C++ Daemon │
|
||||
└────────┬─────────┘ │ (Phase 3) │
|
||||
│ └──────────┬───────────┘
|
||||
▼ │
|
||||
┌──────────────────┐ │
|
||||
│ Database │◄─────────────────────┘
|
||||
│ (PostgreSQL/ │
|
||||
│ SQLite/etc) │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
### Key Benefits
|
||||
|
||||
✅ **Security**: User code never sees database credentials
|
||||
✅ **Sandboxing**: C++ daemon enforces ACL and row-level security
|
||||
✅ **Auditability**: All operations logged
|
||||
✅ **Testability**: Shared conformance tests guarantee consistency
|
||||
✅ **Flexibility**: Support multiple backends (Prisma, SQLite, MongoDB)
|
||||
|
||||
## File Structure Created
|
||||
|
||||
### API Definition (Language-Agnostic)
|
||||
|
||||
```
|
||||
dbal/api/schema/
|
||||
├── entities/ # 8 entity definitions
|
||||
│ ├── user.yaml
|
||||
│ ├── credential.yaml
|
||||
│ ├── session.yaml
|
||||
│ ├── page_view.yaml
|
||||
│ ├── component_hierarchy.yaml
|
||||
│ ├── workflow.yaml
|
||||
│ ├── lua_script.yaml
|
||||
│ └── package.yaml
|
||||
├── operations/ # 4 operation definitions
|
||||
│ ├── user.ops.yaml
|
||||
│ ├── credential.ops.yaml
|
||||
│ ├── page_view.ops.yaml
|
||||
│ └── component_hierarchy.ops.yaml
|
||||
├── errors.yaml # Standardized error codes
|
||||
└── capabilities.yaml # Backend feature matrix
|
||||
```
|
||||
|
||||
### TypeScript Implementation
|
||||
|
||||
```
|
||||
dbal/ts/
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── src/
|
||||
├── index.ts # Public API
|
||||
├── core/
|
||||
│ ├── client.ts # Main client
|
||||
│ ├── types.ts # Entity types
|
||||
│ └── errors.ts # Error handling
|
||||
├── adapters/
|
||||
│ └── adapter.ts # Adapter interface
|
||||
└── runtime/
|
||||
└── config.ts # Configuration
|
||||
```
|
||||
|
||||
### C++ Implementation
|
||||
|
||||
```
|
||||
dbal/cpp/
|
||||
├── CMakeLists.txt # Build system
|
||||
├── include/dbal/ # Public headers
|
||||
│ ├── dbal.hpp
|
||||
│ ├── client.hpp
|
||||
│ ├── types.hpp
|
||||
│ └── errors.hpp
|
||||
├── src/ # Implementation stubs
|
||||
└── README.md # C++ guide
|
||||
```
|
||||
|
||||
### Backend Schemas
|
||||
|
||||
```
|
||||
dbal/backends/
|
||||
├── prisma/
|
||||
│ └── schema.prisma # Full Prisma schema
|
||||
└── sqlite/
|
||||
└── schema.sql # Full SQLite schema with triggers
|
||||
```
|
||||
|
||||
### Tools & Scripts
|
||||
|
||||
```
|
||||
dbal/tools/
|
||||
├── codegen/
|
||||
│ └── gen_types.py # Generate TS/C++ types from YAML
|
||||
└── conformance/
|
||||
└── run_all.py # Run conformance tests
|
||||
|
||||
dbal/scripts/
|
||||
├── build.py # Build all implementations
|
||||
├── test.py # Run all tests
|
||||
└── conformance.py # Run conformance suite
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
```
|
||||
dbal/
|
||||
├── README.md # Main documentation (10KB)
|
||||
├── LICENSE # MIT License
|
||||
├── AGENTS.md # Agent development guide (14KB)
|
||||
├── PROJECT.md # Project structure overview
|
||||
└── docs/
|
||||
└── SPARK_INTEGRATION.md # GitHub Spark deployment (10KB)
|
||||
```
|
||||
|
||||
### Conformance Tests
|
||||
|
||||
```
|
||||
dbal/common/contracts/
|
||||
└── conformance_cases.yaml # Shared test vectors
|
||||
```
|
||||
|
||||
## Entity Schema Highlights
|
||||
|
||||
### User Entity
|
||||
- UUID primary key
|
||||
- Username (unique, validated)
|
||||
- Email (unique, validated)
|
||||
- Role (user/admin/god/supergod)
|
||||
- Timestamps
|
||||
|
||||
### Credential Entity
|
||||
- Secure password hash storage
|
||||
- First login flag
|
||||
- Never exposed in queries
|
||||
- Audit logging required
|
||||
|
||||
### PageView & ComponentHierarchy
|
||||
- Hierarchical component trees
|
||||
- JSON layout storage
|
||||
- Access level enforcement
|
||||
- Cascade delete support
|
||||
|
||||
### Workflow & LuaScript
|
||||
- Workflow automation
|
||||
- Sandboxed Lua execution
|
||||
- Security scanning
|
||||
- Timeout enforcement
|
||||
|
||||
### Package
|
||||
- Multi-tenant package system
|
||||
- Version management
|
||||
- Installation tracking
|
||||
|
||||
## Operations Defined
|
||||
|
||||
### User Operations
|
||||
- create, read, update, delete, list, search, count
|
||||
- Row-level security (users can only see their own data)
|
||||
- Admin override for god-tier users
|
||||
|
||||
### Credential Operations
|
||||
- verify (rate-limited login)
|
||||
- set (system-only password updates)
|
||||
- Never logs passwords
|
||||
- Audit trail required
|
||||
|
||||
### Page Operations
|
||||
- CRUD operations
|
||||
- Get by slug
|
||||
- List by level
|
||||
- Public read access
|
||||
|
||||
### Component Operations
|
||||
- CRUD operations
|
||||
- Get tree (hierarchical)
|
||||
- Reorder components
|
||||
- Move to new parent
|
||||
|
||||
## Error Handling
|
||||
|
||||
Standardized error codes across both implementations:
|
||||
|
||||
- 404 NOT_FOUND
|
||||
- 409 CONFLICT
|
||||
- 401 UNAUTHORIZED
|
||||
- 403 FORBIDDEN
|
||||
- 422 VALIDATION_ERROR
|
||||
- 429 RATE_LIMIT_EXCEEDED
|
||||
- 500 INTERNAL_ERROR
|
||||
- 503 DATABASE_ERROR
|
||||
- 501 CAPABILITY_NOT_SUPPORTED
|
||||
|
||||
Plus security-specific errors:
|
||||
- SANDBOX_VIOLATION
|
||||
- MALICIOUS_CODE_DETECTED
|
||||
|
||||
## Capabilities System
|
||||
|
||||
Backend capability detection for:
|
||||
- Transactions (nested/flat)
|
||||
- Joins (SQL-style)
|
||||
- Full-text search
|
||||
- TTL (auto-expiration)
|
||||
- JSON queries
|
||||
- Aggregations
|
||||
- Relations
|
||||
- Migrations
|
||||
|
||||
Adapters declare capabilities, client code adapts.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### 1. Define Schema (YAML)
|
||||
|
||||
```yaml
|
||||
entity: Post
|
||||
fields:
|
||||
id: { type: uuid, primary: true }
|
||||
title: { type: string, required: true }
|
||||
```
|
||||
|
||||
### 2. Generate Types
|
||||
|
||||
```bash
|
||||
python tools/codegen/gen_types.py
|
||||
```
|
||||
|
||||
### 3. Implement Adapters
|
||||
|
||||
TypeScript:
|
||||
```typescript
|
||||
class PrismaAdapter implements DBALAdapter {
|
||||
async create(entity: string, data: any) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
C++:
|
||||
```cpp
|
||||
class PrismaAdapter : public Adapter {
|
||||
Result<Entity> create(const string& entity, const Json& data) { ... }
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Write Conformance Tests
|
||||
|
||||
```yaml
|
||||
- action: create
|
||||
entity: Post
|
||||
input: { title: "Hello" }
|
||||
expected:
|
||||
status: success
|
||||
```
|
||||
|
||||
### 5. Build & Test
|
||||
|
||||
```bash
|
||||
python scripts/build.py
|
||||
python scripts/test.py
|
||||
```
|
||||
|
||||
## Deployment Options
|
||||
|
||||
### Option 1: Development (Current)
|
||||
- Direct Prisma access
|
||||
- Fast iteration
|
||||
- No daemon needed
|
||||
|
||||
### Option 2: Codespaces with Daemon
|
||||
- Background systemd service
|
||||
- Credentials isolated
|
||||
- ACL enforcement
|
||||
|
||||
### Option 3: Docker Compose
|
||||
- Production-like setup
|
||||
- Easy team sharing
|
||||
- Full isolation
|
||||
|
||||
### Option 4: Cloud with Sidecar
|
||||
- Maximum security
|
||||
- Scales with app
|
||||
- Zero-trust architecture
|
||||
|
||||
## Security Features
|
||||
|
||||
### 1. Credential Isolation
|
||||
Database URLs/passwords only in daemon config, never in app code.
|
||||
|
||||
### 2. ACL Enforcement
|
||||
```yaml
|
||||
rules:
|
||||
- entity: User
|
||||
role: [user]
|
||||
operations: [read]
|
||||
row_level_filter: "id = $user.id"
|
||||
```
|
||||
|
||||
### 3. Query Validation
|
||||
All queries parsed and validated before execution.
|
||||
|
||||
### 4. Audit Logging
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
"user": "user_123",
|
||||
"operation": "create",
|
||||
"entity": "User",
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Sandboxing
|
||||
Daemon runs with minimal privileges, restricted filesystem/network access.
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Immediate
|
||||
1. ⏳ Implement TypeScript Prisma adapter
|
||||
2. ⏳ Write unit tests
|
||||
3. ⏳ Test in Spark app
|
||||
|
||||
### Short-term
|
||||
1. ⏳ Implement C++ SQLite adapter
|
||||
2. ⏳ Build daemon binary
|
||||
3. ⏳ Deploy to Codespaces
|
||||
4. ⏳ Write conformance tests
|
||||
|
||||
### Long-term
|
||||
1. ⏳ Add MongoDB adapter
|
||||
2. ⏳ Implement gRPC protocol
|
||||
3. ⏳ Add TLS support
|
||||
4. ⏳ Production hardening
|
||||
5. ⏳ Performance optimization
|
||||
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
import { DBALClient } from '@metabuilder/dbal'
|
||||
|
||||
const client = new DBALClient({
|
||||
mode: 'production',
|
||||
adapter: 'prisma',
|
||||
endpoint: 'localhost:50051',
|
||||
auth: {
|
||||
user: currentUser,
|
||||
session: currentSession
|
||||
}
|
||||
})
|
||||
|
||||
const user = await client.users.create({
|
||||
username: 'john',
|
||||
email: 'john@example.com',
|
||||
role: 'user'
|
||||
})
|
||||
|
||||
const users = await client.users.list({
|
||||
filter: { role: 'admin' },
|
||||
sort: { createdAt: 'desc' },
|
||||
limit: 10
|
||||
})
|
||||
```
|
||||
|
||||
## Migration Path
|
||||
|
||||
```
|
||||
Phase 1: Current State
|
||||
App → Prisma → Database
|
||||
|
||||
Phase 2: Add DBAL Client (no security yet)
|
||||
App → DBAL Client (TS) → Prisma → Database
|
||||
|
||||
Phase 3: Deploy Daemon (credentials isolated)
|
||||
App → DBAL Client (TS) → DBAL Daemon (C++) → Prisma → Database
|
||||
|
||||
Phase 4: Production Hardening
|
||||
App → DBAL Client (TS) → [TLS] → DBAL Daemon (C++) → Prisma → Database
|
||||
[ACL][Audit][Sandbox]
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
Expected overhead: <20% with significantly improved security.
|
||||
|
||||
| Operation | Direct | DBAL (TS) | DBAL (C++) |
|
||||
|-----------|--------|-----------|------------|
|
||||
| SELECT | 2ms | 3ms | 2.5ms |
|
||||
| JOIN | 15ms | 17ms | 16ms |
|
||||
| Bulk (100) | 50ms | 55ms | 52ms |
|
||||
|
||||
## Files Created
|
||||
|
||||
- **54 files** total
|
||||
- **3 YAML schemas** (entities, operations, errors, capabilities)
|
||||
- **8 entity definitions**
|
||||
- **4 operation definitions**
|
||||
- **2 backend schemas** (Prisma, SQLite)
|
||||
- **3 Python tools** (codegen, conformance, build)
|
||||
- **TypeScript structure** (10+ files)
|
||||
- **C++ structure** (5+ files)
|
||||
- **Documentation** (4 major docs: 40KB total)
|
||||
|
||||
## Key Documentation
|
||||
|
||||
1. **README.md** - Architecture overview, quick start
|
||||
2. **AGENTS.md** - Development guide for AI agents
|
||||
3. **SPARK_INTEGRATION.md** - GitHub Spark deployment guide
|
||||
4. **cpp/README.md** - C++ daemon documentation
|
||||
5. **api/versioning/compat.md** - Compatibility rules
|
||||
|
||||
## Summary
|
||||
|
||||
This DBAL provides a **complete, production-ready architecture** for secure database access in GitHub Spark. It separates concerns:
|
||||
|
||||
- **YAML schemas** define the contract
|
||||
- **TypeScript** provides development speed
|
||||
- **C++** provides production security
|
||||
- **Conformance tests** ensure consistency
|
||||
|
||||
The system is ready for:
|
||||
1. TypeScript adapter implementation
|
||||
2. Integration with existing MetaBuilder code
|
||||
3. Incremental migration to secured deployment
|
||||
4. Future multi-backend support
|
||||
|
||||
All documentation is comprehensive and ready for both human developers and AI agents to work with.
|
||||
42
dbal/LICENSE
42
dbal/LICENSE
@@ -1,21 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 MetaBuilder Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 MetaBuilder Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
120
dbal/PROJECT.md
120
dbal/PROJECT.md
@@ -1,120 +0,0 @@
|
||||
# DBAL Project Structure
|
||||
|
||||
This directory contains the Database Abstraction Layer for MetaBuilder.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Main README](README.md) - Overview and architecture
|
||||
- [Agent Guide](AGENTS.md) - For AI agents and automated tools
|
||||
- [Spark Integration](docs/SPARK_INTEGRATION.md) - GitHub Spark deployment guide
|
||||
- [TypeScript Implementation](ts/README.md) - TS development guide
|
||||
- [C++ Implementation](cpp/README.md) - C++ production guide
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
dbal/
|
||||
├── README.md # Main documentation
|
||||
├── LICENSE # MIT License
|
||||
├── AGENTS.md # Agent development guide
|
||||
├── .gitignore # Git ignore rules
|
||||
│
|
||||
├── api/ # Language-agnostic API definition
|
||||
│ ├── schema/ # Entity and operation schemas
|
||||
│ │ ├── entities/ # Entity definitions (YAML)
|
||||
│ │ ├── operations/ # Operation definitions (YAML)
|
||||
│ │ ├── errors.yaml # Error codes and handling
|
||||
│ │ └── capabilities.yaml # Backend capability matrix
|
||||
│ └── versioning/
|
||||
│ └── compat.md # Compatibility rules
|
||||
│
|
||||
├── common/ # Shared resources
|
||||
│ ├── contracts/ # Conformance test definitions
|
||||
│ ├── fixtures/ # Test data
|
||||
│ └── golden/ # Expected test results
|
||||
│
|
||||
├── ts/ # TypeScript implementation
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.json
|
||||
│ ├── src/
|
||||
│ │ ├── index.ts # Public API
|
||||
│ │ ├── core/ # Core abstractions
|
||||
│ │ ├── adapters/ # Backend adapters
|
||||
│ │ ├── query/ # Query builder
|
||||
│ │ └── runtime/ # Config and telemetry
|
||||
│ └── tests/
|
||||
│
|
||||
├── cpp/ # C++ implementation
|
||||
│ ├── CMakeLists.txt
|
||||
│ ├── include/dbal/ # Public headers
|
||||
│ ├── src/ # Implementation
|
||||
│ └── tests/
|
||||
│
|
||||
├── backends/ # Backend-specific assets
|
||||
│ ├── prisma/
|
||||
│ │ └── schema.prisma # Prisma schema
|
||||
│ └── sqlite/
|
||||
│ └── schema.sql # SQLite schema
|
||||
│
|
||||
├── tools/ # Build and dev tools
|
||||
│ ├── codegen/ # Type generation scripts
|
||||
│ └── conformance/ # Test runners
|
||||
│
|
||||
├── scripts/ # Entry point scripts
|
||||
│ ├── build.py # Build all implementations
|
||||
│ ├── test.py # Run all tests
|
||||
│ └── conformance.py # Run conformance tests
|
||||
│
|
||||
└── docs/ # Additional documentation
|
||||
└── SPARK_INTEGRATION.md # GitHub Spark guide
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Generate Types
|
||||
|
||||
```bash
|
||||
python tools/codegen/gen_types.py
|
||||
```
|
||||
|
||||
### Build Everything
|
||||
|
||||
```bash
|
||||
python scripts/build.py
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
python scripts/test.py
|
||||
```
|
||||
|
||||
### Run Conformance Tests
|
||||
|
||||
```bash
|
||||
python scripts/conformance.py
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Define schema** in `api/schema/entities/` and `api/schema/operations/`
|
||||
2. **Generate types** with `python tools/codegen/gen_types.py`
|
||||
3. **Implement adapters** in `ts/src/adapters/` and `cpp/src/adapters/`
|
||||
4. **Write tests** in `common/contracts/`
|
||||
5. **Build** with `python scripts/build.py`
|
||||
6. **Test** with `python scripts/test.py`
|
||||
7. **Deploy** following `docs/SPARK_INTEGRATION.md`
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Language Agnostic**: API defined in YAML, implementations in TS and C++
|
||||
- **Security First**: C++ daemon isolates credentials, enforces ACL
|
||||
- **Development Speed**: TypeScript for rapid iteration
|
||||
- **Production Security**: C++ for hardened production deployments
|
||||
- **Conformance**: Both implementations must pass identical tests
|
||||
|
||||
## Support
|
||||
|
||||
- Issues: [GitHub Issues](https://github.com/yourorg/metabuilder/issues)
|
||||
- Discussions: [GitHub Discussions](https://github.com/yourorg/metabuilder/discussions)
|
||||
- Documentation: [docs.metabuilder.io/dbal](https://docs.metabuilder.io/dbal)
|
||||
442
dbal/README.md
442
dbal/README.md
@@ -1,437 +1,47 @@
|
||||
# Database Abstraction Layer (DBAL)
|
||||
# DBAL - Database Abstraction Layer
|
||||
|
||||
A language-agnostic database abstraction layer that provides a secure interface between client applications and database backends. The DBAL uses TypeScript for rapid development and testing, with a C++ production layer for enhanced security and performance.
|
||||
A language-agnostic database abstraction layer that provides a secure interface between client applications and database backends.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Client Application (Spark) │
|
||||
│ (TypeScript/React) │
|
||||
└────────────────────────────────┬────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DBAL Client │
|
||||
│ (TypeScript Dev / C++ Production) │
|
||||
│ ┌────────────────────┬──────────────────┬────────────────────┐ │
|
||||
│ │ Query Builder │ Validation │ Error Handling │ │
|
||||
│ └────────────────────┴──────────────────┴────────────────────┘ │
|
||||
└────────────────────────────────┬────────────────────────────────┘
|
||||
│
|
||||
┌────────────┴────────────┐
|
||||
│ IPC/RPC Bridge │
|
||||
│ (gRPC/WebSocket) │
|
||||
└────────────┬────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DBAL Daemon (C++) │
|
||||
│ [Production Only - Sandboxed] │
|
||||
│ ┌────────────────────┬──────────────────┬────────────────────┐ │
|
||||
│ │ Auth/ACL │ Query Executor │ Connection Pool │ │
|
||||
│ └────────────────────┴──────────────────┴────────────────────┘ │
|
||||
└────────────────────────────────┬────────────────────────────────┘
|
||||
│
|
||||
┌────────────┴────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────┐ ┌────────────────┐
|
||||
│ Prisma Client │ │ SQLite Direct │
|
||||
│ (Server-side) │ │ (Embedded) │
|
||||
└────────────────┘ └────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────┐ ┌────────────────┐
|
||||
│ PostgreSQL │ │ SQLite DB │
|
||||
│ MySQL │ │ │
|
||||
│ SQL Server │ │ │
|
||||
└────────────────┘ └────────────────┘
|
||||
```
|
||||
|
||||
## Supported Databases
|
||||
|
||||
The Prisma adapter behind DBAL already targets the databases you care about: PostgreSQL, MySQL, SQLite, and any other engine Prisma supports (SQL Server, CockroachDB, MongoDB, etc.). Switch between them by pointing `DATABASE_URL` at the desired backend and regenerating the Prisma client for your schema.
|
||||
|
||||
The TypeScript client exposes three Prisma-based adapters: `PrismaAdapter`, `PostgresAdapter`, and `MySQLAdapter`. Setting `config.adapter` to `'postgres'` or `'mysql'` constructs the dialect-specific adapter, which keeps the shared Prisma logic but tweaks the capabilities metadata (e.g., enabling full-text search where supported) and leaves the rest of the stack focused on validation, ACLs, and audit logging.
|
||||
|
||||
```bash
|
||||
# PostgreSQL
|
||||
export DATABASE_URL="postgresql://user:pass@db:5432/metabuilder"
|
||||
|
||||
# MySQL
|
||||
export DATABASE_URL="mysql://user:pass@db:3306/metabuilder"
|
||||
|
||||
npx prisma generate
|
||||
```
|
||||
|
||||
With `config.adapter = 'prisma'`, DBAL sends every request through `PrismaAdapter`, and Prisma handles dialect differences, migrations, and connection pooling defined in `prisma/schema.prisma` and `prisma/migrations/`. That keeps DBAL focused on validation, ACLs, and audit logging while it can still drive PostgreSQL, MySQL, or any other Prisma-supported store.
|
||||
|
||||
The C++ daemon still resides in Phase 3—the current implementation is backed by the in-memory store described in `dbal/cpp/docs/PHASE3_DAEMON.md`, so Postgres/MySQL adapters for the daemon are still future work.
|
||||
|
||||
### Native Prisma bridge
|
||||
|
||||
The Phase 3 daemon can still leverage Prisma without bundling Node by calling `NativePrismaAdapter`. Each SQL plan is serialized as a JSON payload with the `$n` or `?` placeholders plus parameters and sent to `/api/native-prisma` on the Next.js server. The API route validates `DBAL_NATIVE_PRISMA_TOKEN`, reconstructs a `Prisma.sql` template, executes the query through the shared Prisma client, and returns rows or affected counts so the daemon sees the same `SqlRow`/`int` values as a regular SQL adapter. Set the same `DBAL_NATIVE_PRISMA_TOKEN` (mirrored in `frontends/nextjs/.env.example`) when running the daemon so the bridge rejects unauthorized callers.
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **Language Agnostic**: API contracts defined in YAML/Proto, not tied to any language
|
||||
2. **Security First**: C++ daemon sandboxes all database access with ACL enforcement
|
||||
3. **Development Speed**: TypeScript implementation for rapid iteration
|
||||
4. **Zero Trust**: User code never touches database credentials or raw connections
|
||||
5. **Capability-based**: Adapters declare what they support (transactions, joins, TTL, etc.)
|
||||
6. **Testable**: Shared test vectors ensure both implementations behave identically
|
||||
|
||||
## Repository Structure
|
||||
## Structure
|
||||
|
||||
```
|
||||
dbal/
|
||||
├── api/ # Language-agnostic contracts (source of truth)
|
||||
│ ├── schema/ # Entity and operation definitions
|
||||
│ ├── idl/ # Optional: Proto/FlatBuffers schemas
|
||||
│ └── versioning/ # Compatibility rules
|
||||
├── common/ # Shared test vectors and fixtures
|
||||
├── ts/ # TypeScript implementation (development)
|
||||
├── cpp/ # C++ implementation (production)
|
||||
├── backends/ # Backend-specific assets
|
||||
├── tools/ # Code generation and build tools
|
||||
└── scripts/ # Cross-platform build scripts
|
||||
├── development/ # TypeScript implementation (fast iteration)
|
||||
├── production/ # C++ implementation (security & performance)
|
||||
├── shared/ # Shared resources (API specs, tools, etc.)
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
## Quick Links
|
||||
|
||||
### Development Mode (TypeScript)
|
||||
- 📖 **[Full Documentation](docs/README.md)** - Complete project documentation
|
||||
- 🚀 **[Quick Start](shared/docs/QUICK_START.md)** - Get started in 5 minutes
|
||||
- 🏗️ **[Architecture](docs/PROJECT.md)** - System architecture and design
|
||||
- 🤖 **[Agent Guide](docs/AGENTS.md)** - AI development guidelines
|
||||
- 📋 **[Restructure Info](docs/RESTRUCTURE_SUMMARY.md)** - Recent organizational changes
|
||||
- ☁️ **[S3 Configuration](docs/S3_CONFIGURATION.md)** - S3 blob storage setup
|
||||
|
||||
## Development
|
||||
|
||||
### TypeScript (Development)
|
||||
```bash
|
||||
cd dbal/ts
|
||||
cd development
|
||||
npm install
|
||||
npm run build
|
||||
npm test
|
||||
```
|
||||
|
||||
### Production Mode (C++ Daemon)
|
||||
|
||||
### C++ (Production)
|
||||
```bash
|
||||
cd dbal/cpp
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make
|
||||
./dbal_daemon --config=../config/prod.yaml
|
||||
cd production
|
||||
# See production/docs/ for C++ build instructions
|
||||
```
|
||||
|
||||
### GitHub Spark Integration
|
||||
|
||||
For GitHub Spark deployments, the DBAL daemon runs as a sidecar service:
|
||||
|
||||
```yaml
|
||||
# In your Spark deployment config
|
||||
services:
|
||||
dbal:
|
||||
image: your-org/dbal-daemon:latest
|
||||
ports:
|
||||
- "50051:50051" # gRPC endpoint
|
||||
environment:
|
||||
- DBAL_MODE=production
|
||||
- DBAL_SANDBOX=strict
|
||||
```
|
||||
|
||||
## Monitoring & Daemon UI
|
||||
|
||||
`frontends/dbal` is a dedicated Next.js mini-app that showcases the C++ daemon's architecture, deployment readiness, and the `ServerStatusPanel`. The main `frontends/nextjs` app re-exports the `@dbal-ui` component at `/dbal-daemon`, and the panel polls `/api/status` (the shared feed lives in `frontends/dbal/src/status.ts`). Keep this page covered with `frontends/nextjs/e2e/dbal-daemon/daemon.spec.ts` and `playwright.dbal-daemon.config.ts`, or run `npm run test:e2e:dbal-daemon` after touching the UI.
|
||||
|
||||
## Security Model
|
||||
|
||||
### Sandboxing Strategy
|
||||
|
||||
1. **Process Isolation**: Daemon runs in separate process with restricted permissions
|
||||
2. **Capability-based Security**: Each request checked against user ACL
|
||||
3. **Query Validation**: All queries parsed and validated before execution
|
||||
4. **Credential Protection**: DB credentials never exposed to client code
|
||||
5. **Audit Logging**: All operations logged for security review
|
||||
|
||||
### ACL System
|
||||
|
||||
```yaml
|
||||
user: "user_123"
|
||||
role: "editor"
|
||||
permissions:
|
||||
- entity: "posts"
|
||||
operations: [create, read, update]
|
||||
filters:
|
||||
author_id: "$user.id" # Row-level security
|
||||
- entity: "comments"
|
||||
operations: [create, read]
|
||||
```
|
||||
|
||||
## API Contract Example
|
||||
|
||||
### HTTP Utilities
|
||||
|
||||
For outbound integrations the daemon can use the new requests-inspired helper `runtime::RequestsClient`. It wraps the `cpr` HTTP helpers, exposes `get`/`post` helpers, parses JSON responses, and throws clean timeouts so code paths stay predictable.
|
||||
|
||||
Native Prisma calls route through `NativePrismaAdapter`, which currently POSTs to the `/api/native-prisma` Next.js API and returns the raw JSON rows or affected count using that helper. When the daemon calls `runQuery`/`runNonQuery`, the response is mapped back into `SqlRow` results so the rest of the stack stays unaware of the HTTP transport.
|
||||
|
||||
```cpp
|
||||
using namespace dbal::runtime;
|
||||
|
||||
RequestsClient http("https://api.prisma.example");
|
||||
auto response = http.post("/rpc/execute", jsonPayload.dump(), {{"Authorization", "Bearer ..."}});
|
||||
if (response.statusCode == 200) {
|
||||
const auto result = response.json["result"];
|
||||
// handle Prisma response
|
||||
}
|
||||
```
|
||||
|
||||
### Entity Definition (YAML)
|
||||
|
||||
```yaml
|
||||
# api/schema/entities/post.yaml
|
||||
entity: Post
|
||||
version: "1.0"
|
||||
fields:
|
||||
id:
|
||||
type: uuid
|
||||
primary: true
|
||||
generated: true
|
||||
title:
|
||||
type: string
|
||||
required: true
|
||||
max_length: 200
|
||||
content:
|
||||
type: text
|
||||
required: true
|
||||
author_id:
|
||||
type: uuid
|
||||
required: true
|
||||
foreign_key:
|
||||
entity: User
|
||||
field: id
|
||||
created_at:
|
||||
type: datetime
|
||||
generated: true
|
||||
updated_at:
|
||||
type: datetime
|
||||
auto_update: true
|
||||
```
|
||||
|
||||
### Operations (YAML)
|
||||
|
||||
```yaml
|
||||
# api/schema/operations/post.ops.yaml
|
||||
operations:
|
||||
create:
|
||||
input: [title, content, author_id]
|
||||
output: Post
|
||||
acl_required: ["post:create"]
|
||||
|
||||
read:
|
||||
input: [id]
|
||||
output: Post
|
||||
acl_required: ["post:read"]
|
||||
|
||||
update:
|
||||
input: [id, title?, content?]
|
||||
output: Post
|
||||
acl_required: ["post:update"]
|
||||
row_level_check: "author_id = $user.id"
|
||||
|
||||
delete:
|
||||
input: [id]
|
||||
output: boolean
|
||||
acl_required: ["post:delete"]
|
||||
row_level_check: "author_id = $user.id OR $user.role = 'admin'"
|
||||
|
||||
list:
|
||||
input: [filter?, sort?, page?, limit?]
|
||||
output: Post[]
|
||||
acl_required: ["post:read"]
|
||||
```
|
||||
|
||||
## Client Usage
|
||||
|
||||
### TypeScript Client
|
||||
|
||||
```typescript
|
||||
import { DBALClient } from '@metabuilder/dbal'
|
||||
|
||||
const client = new DBALClient({
|
||||
mode: 'development', // or 'production'
|
||||
endpoint: 'localhost:50051',
|
||||
auth: {
|
||||
user: currentUser,
|
||||
session: currentSession
|
||||
}
|
||||
})
|
||||
|
||||
// CRUD operations
|
||||
const post = await client.posts.create({
|
||||
title: 'Hello World',
|
||||
content: 'This is my first post',
|
||||
author_id: user.id
|
||||
})
|
||||
|
||||
const posts = await client.posts.list({
|
||||
filter: { author_id: user.id },
|
||||
sort: { created_at: 'desc' },
|
||||
limit: 10
|
||||
})
|
||||
|
||||
const updated = await client.posts.update(post.id, {
|
||||
title: 'Updated Title'
|
||||
})
|
||||
|
||||
await client.posts.delete(post.id)
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Define Schema**: Edit YAML files in `api/schema/`
|
||||
2. **Generate Code**: `python tools/codegen/gen_types.py`
|
||||
3. **Implement Adapter**: Add backend support in `ts/src/adapters/`
|
||||
4. **Write Tests**: Create conformance tests in `common/fixtures/`
|
||||
5. **Run Tests**: `npm run test:conformance`
|
||||
6. **Build C++ Daemon**: `cd cpp && cmake --build build`
|
||||
7. **Deploy**: Use Docker/Kubernetes to deploy daemon
|
||||
|
||||
## Testing
|
||||
|
||||
### Conformance Testing
|
||||
|
||||
The DBAL includes comprehensive conformance tests that ensure both TypeScript and C++ implementations behave identically:
|
||||
|
||||
```bash
|
||||
# Run all conformance tests
|
||||
python tools/conformance/run_all.py
|
||||
|
||||
# Run TS tests only
|
||||
cd ts && npm run test:conformance
|
||||
|
||||
# Run C++ tests only
|
||||
cd cpp && ./build/tests/conformance_tests
|
||||
```
|
||||
|
||||
### Test Vectors
|
||||
|
||||
Shared test vectors in `common/fixtures/` ensure consistency:
|
||||
|
||||
```yaml
|
||||
# common/contracts/conformance_cases.yaml
|
||||
- name: "Create and read post"
|
||||
operations:
|
||||
- action: create
|
||||
entity: Post
|
||||
input:
|
||||
title: "Test Post"
|
||||
content: "Test content"
|
||||
author_id: "user_123"
|
||||
expected:
|
||||
status: success
|
||||
output:
|
||||
id: "<uuid>"
|
||||
title: "Test Post"
|
||||
- action: read
|
||||
entity: Post
|
||||
input:
|
||||
id: "$prev.id"
|
||||
expected:
|
||||
status: success
|
||||
output:
|
||||
title: "Test Post"
|
||||
```
|
||||
|
||||
## Migration from Current System
|
||||
|
||||
### Phase 1: Development Mode (Complete)
|
||||
- Use TypeScript DBAL client in development
|
||||
- Direct Prisma access (no daemon)
|
||||
- Validates API contract compliance
|
||||
|
||||
### Phase 2: Hybrid Mode (Current Implementation)
|
||||
- Complete TypeScript DBAL client with Prisma adapter
|
||||
- WebSocket bridge for remote daemon communication (prepared for C++)
|
||||
- ACL enforcement and audit logging in TypeScript
|
||||
- Runs entirely in GitHub Spark environment
|
||||
- Prepares architecture for C++ daemon migration
|
||||
|
||||
### Phase 3: Full Production (Future)
|
||||
- All environments use C++ daemon
|
||||
- TypeScript client communicates via WebSocket/gRPC
|
||||
- Maximum security and performance
|
||||
- Requires infrastructure beyond GitHub Spark
|
||||
|
||||
## Capabilities System
|
||||
|
||||
Different backends support different features:
|
||||
|
||||
```yaml
|
||||
# api/schema/capabilities.yaml
|
||||
adapters:
|
||||
prisma:
|
||||
transactions: true
|
||||
joins: true
|
||||
full_text_search: false
|
||||
ttl: false
|
||||
json_queries: true
|
||||
|
||||
sqlite:
|
||||
transactions: true
|
||||
joins: true
|
||||
full_text_search: true
|
||||
ttl: false
|
||||
json_queries: true
|
||||
|
||||
mongodb:
|
||||
transactions: true
|
||||
joins: false
|
||||
full_text_search: true
|
||||
ttl: true
|
||||
json_queries: true
|
||||
```
|
||||
|
||||
Client code can check capabilities:
|
||||
|
||||
```typescript
|
||||
if (await client.capabilities.hasJoins()) {
|
||||
// Use join query
|
||||
} else {
|
||||
// Fall back to multiple queries
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Standardized errors across all implementations:
|
||||
|
||||
```yaml
|
||||
# api/schema/errors.yaml
|
||||
errors:
|
||||
NOT_FOUND:
|
||||
code: 404
|
||||
message: "Entity not found"
|
||||
|
||||
CONFLICT:
|
||||
code: 409
|
||||
message: "Entity already exists"
|
||||
|
||||
UNAUTHORIZED:
|
||||
code: 401
|
||||
message: "Authentication required"
|
||||
|
||||
FORBIDDEN:
|
||||
code: 403
|
||||
message: "Insufficient permissions"
|
||||
|
||||
VALIDATION_ERROR:
|
||||
code: 422
|
||||
message: "Validation failed"
|
||||
fields:
|
||||
- field: string
|
||||
error: string
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](../docs/CONTRIBUTING.md) for development guidelines.
|
||||
### Shared Resources
|
||||
- **API Schemas**: `shared/api/schema/`
|
||||
- **Tools**: `shared/tools/` (codegen, build assistant)
|
||||
- **Scripts**: `shared/scripts/` (build, test)
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE)
|
||||
MIT - See [LICENSE](LICENSE) file.
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# DBAL - Data Bus Abstraction Layer
|
||||
|
||||
The DBAL (Data Bus Abstraction Layer) provides a comprehensive implementation guide and source code documentation for the distributed data architecture that powers MetaBuilder.
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
### Getting Started
|
||||
|
||||
- [Quick Start Guide](./QUICK_START.md) - Setup and first steps
|
||||
- [README](./README.md) - Project overview
|
||||
|
||||
### Implementation Guides
|
||||
|
||||
- [Phase 2 Implementation](./PHASE2_IMPLEMENTATION.md) - Version 2 features and design
|
||||
- [Phase 2 Complete](./PHASE2_COMPLETE.md) - Implementation completion status
|
||||
- [Implementation Summary](./IMPLEMENTATION_SUMMARY.md) - Feature overview
|
||||
|
||||
### Architecture
|
||||
|
||||
- [Project Documentation](./PROJECT.md) - Complete project reference
|
||||
- [Agent Instructions](./AGENTS.md) - AI development guidelines
|
||||
|
||||
## 📂 Directory Structure
|
||||
|
||||
```
|
||||
dbal/
|
||||
├── QUICK_START.md # Quick start guide
|
||||
├── README.md # Project overview
|
||||
├── PROJECT.md # Complete documentation
|
||||
├── IMPLEMENTATION_SUMMARY.md # Implementation status
|
||||
├── PHASE2_IMPLEMENTATION.md # Version 2 design
|
||||
├── PHASE2_COMPLETE.md # Completion status
|
||||
├── AGENTS.md # AI development guidelines
|
||||
├── api/ # API specifications
|
||||
├── backends/ # Backend implementations
|
||||
├── common/ # Shared utilities
|
||||
├── cpp/ # C++ implementations
|
||||
├── docs/ # Additional documentation
|
||||
├── scripts/ # Utility scripts
|
||||
├── tools/ # Development tools
|
||||
└── ts/ # TypeScript implementations
|
||||
```
|
||||
|
||||
## 🎯 Key Concepts
|
||||
|
||||
DBAL provides:
|
||||
|
||||
- **Abstraction Layer** - Unified interface across multiple backends
|
||||
- **Type Safety** - Full TypeScript support
|
||||
- **Performance** - Optimized C++ implementations
|
||||
- **Flexibility** - Multiple backend options (SQL, NoSQL, etc.)
|
||||
- **Reliability** - Comprehensive test coverage
|
||||
- **Documentation** - Extensive guides and examples
|
||||
|
||||
## 📖 Common Tasks
|
||||
|
||||
### Understanding DBAL Architecture
|
||||
|
||||
See [PROJECT.md](./PROJECT.md) for complete architecture documentation.
|
||||
|
||||
### Setting Up Development Environment
|
||||
|
||||
See [QUICK_START.md](./QUICK_START.md) for setup instructions.
|
||||
|
||||
### Implementing New Features
|
||||
|
||||
See [PHASE2_IMPLEMENTATION.md](./PHASE2_IMPLEMENTATION.md) for design patterns.
|
||||
|
||||
### AI-Assisted Development
|
||||
|
||||
See [AGENTS.md](./AGENTS.md) for guidelines on working with AI development tools.
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [MetaBuilder Root README](../README.md)
|
||||
- [Architecture Guides](../docs/architecture/)
|
||||
- [Database Guide](../docs/architecture/database.md)
|
||||
|
||||
## 📄 License
|
||||
|
||||
See [LICENSE](./LICENSE) file.
|
||||
@@ -1,68 +0,0 @@
|
||||
entity: ComponentHierarchy
|
||||
version: "1.0"
|
||||
description: "Component tree structure for pages"
|
||||
|
||||
fields:
|
||||
id:
|
||||
type: uuid
|
||||
primary: true
|
||||
generated: true
|
||||
|
||||
page_id:
|
||||
type: uuid
|
||||
required: true
|
||||
foreign_key:
|
||||
entity: PageView
|
||||
field: id
|
||||
on_delete: cascade
|
||||
|
||||
parent_id:
|
||||
type: uuid
|
||||
optional: true
|
||||
foreign_key:
|
||||
entity: ComponentHierarchy
|
||||
field: id
|
||||
on_delete: cascade
|
||||
description: "Parent component (null for root)"
|
||||
|
||||
component_type:
|
||||
type: string
|
||||
required: true
|
||||
max_length: 100
|
||||
description: "Component type identifier"
|
||||
|
||||
order:
|
||||
type: integer
|
||||
required: true
|
||||
default: 0
|
||||
description: "Display order among siblings"
|
||||
|
||||
props:
|
||||
type: json
|
||||
required: true
|
||||
default: {}
|
||||
description: "Component properties"
|
||||
|
||||
created_at:
|
||||
type: datetime
|
||||
generated: true
|
||||
immutable: true
|
||||
|
||||
updated_at:
|
||||
type: datetime
|
||||
auto_update: true
|
||||
|
||||
indexes:
|
||||
- fields: [page_id]
|
||||
- fields: [parent_id]
|
||||
- fields: [page_id, order]
|
||||
|
||||
acl:
|
||||
create:
|
||||
role: [god, supergod]
|
||||
read:
|
||||
role: [admin, god, supergod]
|
||||
update:
|
||||
role: [god, supergod]
|
||||
delete:
|
||||
role: [god, supergod]
|
||||
@@ -1,60 +0,0 @@
|
||||
entity: Credential
|
||||
version: "1.0"
|
||||
description: "Secure credential storage for user authentication"
|
||||
|
||||
fields:
|
||||
id:
|
||||
type: uuid
|
||||
primary: true
|
||||
generated: true
|
||||
description: "Unique credential identifier"
|
||||
|
||||
username:
|
||||
type: string
|
||||
required: true
|
||||
unique: true
|
||||
max_length: 50
|
||||
foreign_key:
|
||||
entity: User
|
||||
field: username
|
||||
on_delete: cascade
|
||||
description: "Associated username"
|
||||
|
||||
password_hash:
|
||||
type: string
|
||||
required: true
|
||||
sensitive: true
|
||||
description: "Hashed password (never returned in queries)"
|
||||
|
||||
first_login:
|
||||
type: boolean
|
||||
required: true
|
||||
default: true
|
||||
description: "Flag indicating if password change is required"
|
||||
|
||||
created_at:
|
||||
type: datetime
|
||||
generated: true
|
||||
immutable: true
|
||||
|
||||
updated_at:
|
||||
type: datetime
|
||||
auto_update: true
|
||||
|
||||
indexes:
|
||||
- fields: [username]
|
||||
unique: true
|
||||
|
||||
acl:
|
||||
create:
|
||||
system: true
|
||||
read:
|
||||
system: true
|
||||
update:
|
||||
system: true
|
||||
delete:
|
||||
system: true
|
||||
|
||||
security:
|
||||
never_expose: [password_hash]
|
||||
audit_all_access: true
|
||||
@@ -1,70 +0,0 @@
|
||||
entity: PageView
|
||||
version: "1.0"
|
||||
description: "Page configuration and layout definition"
|
||||
|
||||
fields:
|
||||
id:
|
||||
type: uuid
|
||||
primary: true
|
||||
generated: true
|
||||
|
||||
slug:
|
||||
type: string
|
||||
required: true
|
||||
unique: true
|
||||
max_length: 255
|
||||
pattern: "^[a-z0-9-/]+$"
|
||||
description: "URL path for this page"
|
||||
|
||||
title:
|
||||
type: string
|
||||
required: true
|
||||
max_length: 255
|
||||
description: "Page title"
|
||||
|
||||
description:
|
||||
type: text
|
||||
optional: true
|
||||
description: "Page description"
|
||||
|
||||
level:
|
||||
type: integer
|
||||
required: true
|
||||
min: 1
|
||||
max: 5
|
||||
description: "Access level required (1=public, 5=supergod)"
|
||||
|
||||
layout:
|
||||
type: json
|
||||
required: true
|
||||
description: "Page layout configuration"
|
||||
|
||||
is_active:
|
||||
type: boolean
|
||||
required: true
|
||||
default: true
|
||||
|
||||
created_at:
|
||||
type: datetime
|
||||
generated: true
|
||||
immutable: true
|
||||
|
||||
updated_at:
|
||||
type: datetime
|
||||
auto_update: true
|
||||
|
||||
indexes:
|
||||
- fields: [slug]
|
||||
unique: true
|
||||
- fields: [level]
|
||||
- fields: [is_active]
|
||||
|
||||
acl:
|
||||
create:
|
||||
role: [god, supergod]
|
||||
read:
|
||||
public: true
|
||||
update:
|
||||
role: [god, supergod]
|
||||
delete:
|
||||
role: [god, supergod]
|
||||
@@ -1,80 +0,0 @@
|
||||
entity: LuaScript
|
||||
version: "1.0"
|
||||
description: "Lua script storage and execution tracking"
|
||||
|
||||
fields:
|
||||
id:
|
||||
type: uuid
|
||||
primary: true
|
||||
generated: true
|
||||
|
||||
name:
|
||||
type: string
|
||||
required: true
|
||||
unique: true
|
||||
max_length: 255
|
||||
|
||||
description:
|
||||
type: text
|
||||
optional: true
|
||||
|
||||
code:
|
||||
type: text
|
||||
required: true
|
||||
description: "Lua script code"
|
||||
|
||||
is_sandboxed:
|
||||
type: boolean
|
||||
required: true
|
||||
default: true
|
||||
description: "Whether script runs in sandbox"
|
||||
|
||||
allowed_globals:
|
||||
type: json
|
||||
required: true
|
||||
default: []
|
||||
description: "List of allowed global functions"
|
||||
|
||||
timeout_ms:
|
||||
type: integer
|
||||
required: true
|
||||
default: 5000
|
||||
min: 100
|
||||
max: 30000
|
||||
description: "Execution timeout in milliseconds"
|
||||
|
||||
created_by:
|
||||
type: uuid
|
||||
required: true
|
||||
foreign_key:
|
||||
entity: User
|
||||
field: id
|
||||
|
||||
created_at:
|
||||
type: datetime
|
||||
generated: true
|
||||
immutable: true
|
||||
|
||||
updated_at:
|
||||
type: datetime
|
||||
auto_update: true
|
||||
|
||||
indexes:
|
||||
- fields: [name]
|
||||
unique: true
|
||||
- fields: [created_by]
|
||||
- fields: [is_sandboxed]
|
||||
|
||||
acl:
|
||||
create:
|
||||
role: [god, supergod]
|
||||
read:
|
||||
role: [admin, god, supergod]
|
||||
update:
|
||||
role: [god, supergod]
|
||||
delete:
|
||||
role: [god, supergod]
|
||||
|
||||
security:
|
||||
scan_for_malicious: true
|
||||
sandbox_required: true
|
||||
@@ -1,75 +0,0 @@
|
||||
entity: Package
|
||||
version: "1.0"
|
||||
description: "Installable package definitions (forum, chat, etc.)"
|
||||
|
||||
fields:
|
||||
id:
|
||||
type: uuid
|
||||
primary: true
|
||||
generated: true
|
||||
|
||||
name:
|
||||
type: string
|
||||
required: true
|
||||
unique: true
|
||||
max_length: 255
|
||||
|
||||
version:
|
||||
type: string
|
||||
required: true
|
||||
pattern: "^\\d+\\.\\d+\\.\\d+$"
|
||||
description: "Semantic version"
|
||||
|
||||
description:
|
||||
type: text
|
||||
optional: true
|
||||
|
||||
author:
|
||||
type: string
|
||||
required: true
|
||||
max_length: 255
|
||||
|
||||
manifest:
|
||||
type: json
|
||||
required: true
|
||||
description: "Package manifest with dependencies"
|
||||
|
||||
is_installed:
|
||||
type: boolean
|
||||
required: true
|
||||
default: false
|
||||
|
||||
installed_at:
|
||||
type: datetime
|
||||
optional: true
|
||||
|
||||
installed_by:
|
||||
type: uuid
|
||||
optional: true
|
||||
foreign_key:
|
||||
entity: User
|
||||
field: id
|
||||
|
||||
created_at:
|
||||
type: datetime
|
||||
generated: true
|
||||
immutable: true
|
||||
|
||||
updated_at:
|
||||
type: datetime
|
||||
auto_update: true
|
||||
|
||||
indexes:
|
||||
- fields: [name, version]
|
||||
unique: true
|
||||
- fields: [is_installed]
|
||||
|
||||
acl:
|
||||
create:
|
||||
role: [supergod]
|
||||
read:
|
||||
role: [god, supergod]
|
||||
update:
|
||||
role: [supergod]
|
||||
delete:
|
||||
role: [supergod]
|
||||
@@ -1,58 +0,0 @@
|
||||
entity: Session
|
||||
version: "1.0"
|
||||
description: "User session tracking and management"
|
||||
|
||||
fields:
|
||||
id:
|
||||
type: uuid
|
||||
primary: true
|
||||
generated: true
|
||||
|
||||
user_id:
|
||||
type: uuid
|
||||
required: true
|
||||
foreign_key:
|
||||
entity: User
|
||||
field: id
|
||||
on_delete: cascade
|
||||
|
||||
token:
|
||||
type: string
|
||||
required: true
|
||||
unique: true
|
||||
sensitive: true
|
||||
description: "Session token"
|
||||
|
||||
expires_at:
|
||||
type: datetime
|
||||
required: true
|
||||
description: "Session expiration time"
|
||||
|
||||
created_at:
|
||||
type: datetime
|
||||
generated: true
|
||||
immutable: true
|
||||
|
||||
last_activity:
|
||||
type: datetime
|
||||
auto_update: true
|
||||
|
||||
indexes:
|
||||
- fields: [token]
|
||||
unique: true
|
||||
- fields: [user_id]
|
||||
- fields: [expires_at]
|
||||
|
||||
ttl:
|
||||
field: expires_at
|
||||
auto_delete: true
|
||||
|
||||
acl:
|
||||
create:
|
||||
system: true
|
||||
read:
|
||||
system: true
|
||||
update:
|
||||
system: true
|
||||
delete:
|
||||
system: true
|
||||
@@ -1,63 +0,0 @@
|
||||
entity: User
|
||||
version: "1.0"
|
||||
description: "User account entity with authentication and role management"
|
||||
|
||||
fields:
|
||||
id:
|
||||
type: uuid
|
||||
primary: true
|
||||
generated: true
|
||||
description: "Unique user identifier"
|
||||
|
||||
username:
|
||||
type: string
|
||||
required: true
|
||||
unique: true
|
||||
min_length: 3
|
||||
max_length: 50
|
||||
pattern: "^[a-zA-Z0-9_-]+$"
|
||||
description: "Unique username for login"
|
||||
|
||||
email:
|
||||
type: email
|
||||
required: true
|
||||
unique: true
|
||||
max_length: 255
|
||||
description: "User email address"
|
||||
|
||||
role:
|
||||
type: enum
|
||||
required: true
|
||||
values: [user, admin, god, supergod]
|
||||
default: user
|
||||
description: "User role defining access level"
|
||||
|
||||
created_at:
|
||||
type: datetime
|
||||
generated: true
|
||||
immutable: true
|
||||
description: "Account creation timestamp"
|
||||
|
||||
updated_at:
|
||||
type: datetime
|
||||
auto_update: true
|
||||
description: "Last update timestamp"
|
||||
|
||||
indexes:
|
||||
- fields: [username]
|
||||
unique: true
|
||||
- fields: [email]
|
||||
unique: true
|
||||
- fields: [role]
|
||||
|
||||
acl:
|
||||
create:
|
||||
public: true
|
||||
read:
|
||||
self: true
|
||||
admin: true
|
||||
update:
|
||||
self: true
|
||||
admin: true
|
||||
delete:
|
||||
admin: true
|
||||
@@ -1,73 +0,0 @@
|
||||
entity: Workflow
|
||||
version: "1.0"
|
||||
description: "Workflow definitions for automation"
|
||||
|
||||
fields:
|
||||
id:
|
||||
type: uuid
|
||||
primary: true
|
||||
generated: true
|
||||
|
||||
name:
|
||||
type: string
|
||||
required: true
|
||||
unique: true
|
||||
max_length: 255
|
||||
|
||||
description:
|
||||
type: text
|
||||
optional: true
|
||||
|
||||
trigger:
|
||||
type: enum
|
||||
required: true
|
||||
values: [manual, schedule, event, webhook]
|
||||
description: "Workflow trigger type"
|
||||
|
||||
trigger_config:
|
||||
type: json
|
||||
required: true
|
||||
description: "Trigger configuration"
|
||||
|
||||
steps:
|
||||
type: json
|
||||
required: true
|
||||
description: "Workflow steps definition"
|
||||
|
||||
is_active:
|
||||
type: boolean
|
||||
required: true
|
||||
default: true
|
||||
|
||||
created_by:
|
||||
type: uuid
|
||||
required: true
|
||||
foreign_key:
|
||||
entity: User
|
||||
field: id
|
||||
|
||||
created_at:
|
||||
type: datetime
|
||||
generated: true
|
||||
immutable: true
|
||||
|
||||
updated_at:
|
||||
type: datetime
|
||||
auto_update: true
|
||||
|
||||
indexes:
|
||||
- fields: [name]
|
||||
unique: true
|
||||
- fields: [trigger]
|
||||
- fields: [is_active]
|
||||
- fields: [created_by]
|
||||
|
||||
acl:
|
||||
create:
|
||||
role: [god, supergod]
|
||||
read:
|
||||
role: [admin, god, supergod]
|
||||
update:
|
||||
role: [god, supergod]
|
||||
delete:
|
||||
role: [god, supergod]
|
||||
@@ -1,70 +0,0 @@
|
||||
operations:
|
||||
create:
|
||||
description: "Add component to page hierarchy"
|
||||
input:
|
||||
required: [page_id, component_type, order, props]
|
||||
optional: [parent_id]
|
||||
output: ComponentHierarchy
|
||||
acl_required: ["component:create"]
|
||||
errors:
|
||||
- NOT_FOUND: "Page or parent component not found"
|
||||
- VALIDATION_ERROR: "Invalid component type"
|
||||
|
||||
read:
|
||||
description: "Get component by ID"
|
||||
input:
|
||||
required: [id]
|
||||
output: ComponentHierarchy
|
||||
acl_required: ["component:read"]
|
||||
errors:
|
||||
- NOT_FOUND: "Component not found"
|
||||
|
||||
update:
|
||||
description: "Update component"
|
||||
input:
|
||||
required: [id]
|
||||
optional: [parent_id, component_type, order, props]
|
||||
output: ComponentHierarchy
|
||||
acl_required: ["component:update"]
|
||||
errors:
|
||||
- NOT_FOUND: "Component not found"
|
||||
|
||||
delete:
|
||||
description: "Delete component and its children"
|
||||
input:
|
||||
required: [id]
|
||||
output: boolean
|
||||
acl_required: ["component:delete"]
|
||||
cascade: true
|
||||
errors:
|
||||
- NOT_FOUND: "Component not found"
|
||||
|
||||
get_tree:
|
||||
description: "Get full component tree for a page"
|
||||
input:
|
||||
required: [page_id]
|
||||
output: ComponentHierarchy[]
|
||||
acl_required: ["component:read"]
|
||||
hierarchical: true
|
||||
errors:
|
||||
- NOT_FOUND: "Page not found"
|
||||
|
||||
reorder:
|
||||
description: "Reorder components within same parent"
|
||||
input:
|
||||
required: [components]
|
||||
output: boolean
|
||||
acl_required: ["component:update"]
|
||||
batch: true
|
||||
errors:
|
||||
- VALIDATION_ERROR: "Invalid order array"
|
||||
|
||||
move:
|
||||
description: "Move component to new parent"
|
||||
input:
|
||||
required: [id, new_parent_id, order]
|
||||
output: ComponentHierarchy
|
||||
acl_required: ["component:update"]
|
||||
errors:
|
||||
- NOT_FOUND: "Component or parent not found"
|
||||
- VALIDATION_ERROR: "Cannot move to descendant"
|
||||
@@ -1,59 +0,0 @@
|
||||
operations:
|
||||
verify:
|
||||
description: "Verify username/password credentials"
|
||||
input:
|
||||
required: [username, password]
|
||||
output: boolean
|
||||
acl_required: []
|
||||
public: true
|
||||
rate_limit:
|
||||
max_attempts: 5
|
||||
window_seconds: 300
|
||||
errors:
|
||||
- UNAUTHORIZED: "Invalid credentials"
|
||||
- RATE_LIMIT_EXCEEDED: "Too many login attempts"
|
||||
|
||||
set:
|
||||
description: "Set or update password for user"
|
||||
input:
|
||||
required: [username, password_hash]
|
||||
output: boolean
|
||||
acl_required: ["credential:write"]
|
||||
system_only: true
|
||||
security:
|
||||
audit: true
|
||||
never_log_password: true
|
||||
errors:
|
||||
- NOT_FOUND: "User not found"
|
||||
|
||||
set_first_login_flag:
|
||||
description: "Set first login flag"
|
||||
input:
|
||||
required: [username, first_login]
|
||||
output: boolean
|
||||
acl_required: ["credential:write"]
|
||||
system_only: true
|
||||
errors:
|
||||
- NOT_FOUND: "User not found"
|
||||
|
||||
get_first_login_flag:
|
||||
description: "Get first login flag"
|
||||
input:
|
||||
required: [username]
|
||||
output: boolean
|
||||
acl_required: ["credential:read"]
|
||||
system_only: true
|
||||
errors:
|
||||
- NOT_FOUND: "User not found"
|
||||
|
||||
delete:
|
||||
description: "Delete credentials for user"
|
||||
input:
|
||||
required: [username]
|
||||
output: boolean
|
||||
acl_required: ["credential:delete"]
|
||||
system_only: true
|
||||
security:
|
||||
audit: true
|
||||
errors:
|
||||
- NOT_FOUND: "User not found"
|
||||
@@ -1,66 +0,0 @@
|
||||
operations:
|
||||
create:
|
||||
description: "Create new page"
|
||||
input:
|
||||
required: [slug, title, level, layout]
|
||||
optional: [description, is_active]
|
||||
output: PageView
|
||||
acl_required: ["page:create"]
|
||||
validation:
|
||||
- slug_unique: "Page slug must be unique"
|
||||
- slug_format: "Slug must be URL-safe"
|
||||
- level_valid: "Level must be 1-5"
|
||||
errors:
|
||||
- CONFLICT: "Page with this slug already exists"
|
||||
- VALIDATION_ERROR: "Invalid input"
|
||||
|
||||
read:
|
||||
description: "Get page by ID or slug"
|
||||
input:
|
||||
optional: [id, slug]
|
||||
output: PageView
|
||||
acl_required: []
|
||||
public: true
|
||||
errors:
|
||||
- NOT_FOUND: "Page not found"
|
||||
- VALIDATION_ERROR: "Must provide id or slug"
|
||||
|
||||
update:
|
||||
description: "Update page"
|
||||
input:
|
||||
required: [id]
|
||||
optional: [slug, title, description, level, layout, is_active]
|
||||
output: PageView
|
||||
acl_required: ["page:update"]
|
||||
errors:
|
||||
- NOT_FOUND: "Page not found"
|
||||
- CONFLICT: "Slug already in use"
|
||||
|
||||
delete:
|
||||
description: "Delete page"
|
||||
input:
|
||||
required: [id]
|
||||
output: boolean
|
||||
acl_required: ["page:delete"]
|
||||
cascade: true
|
||||
errors:
|
||||
- NOT_FOUND: "Page not found"
|
||||
|
||||
list:
|
||||
description: "List pages with filtering"
|
||||
input:
|
||||
optional: [level, is_active, page, limit, sort]
|
||||
output: PageView[]
|
||||
acl_required: []
|
||||
public: true
|
||||
pagination: true
|
||||
errors: []
|
||||
|
||||
get_by_level:
|
||||
description: "Get all pages for a specific level"
|
||||
input:
|
||||
required: [level]
|
||||
output: PageView[]
|
||||
acl_required: []
|
||||
public: true
|
||||
errors: []
|
||||
@@ -1,58 +0,0 @@
|
||||
operations:
|
||||
create:
|
||||
description: "Create a new Lua script"
|
||||
input:
|
||||
required: [name, code, is_sandboxed, allowed_globals, timeout_ms, created_by]
|
||||
optional: [description]
|
||||
output: LuaScript
|
||||
acl_required: ["lua_script:create"]
|
||||
validation:
|
||||
- name_unique: "Lua script name must be unique"
|
||||
- timeout_range: "Timeout must be between 100 and 30000 ms"
|
||||
errors:
|
||||
- CONFLICT: "Lua script name already exists"
|
||||
- VALIDATION_ERROR: "Invalid script input"
|
||||
|
||||
read:
|
||||
description: "Get Lua script by ID"
|
||||
input:
|
||||
required: [id]
|
||||
output: LuaScript
|
||||
acl_required: ["lua_script:read"]
|
||||
errors:
|
||||
- NOT_FOUND: "Lua script not found"
|
||||
|
||||
update:
|
||||
description: "Update Lua script"
|
||||
input:
|
||||
required: [id]
|
||||
optional: [name, description, code, is_sandboxed, allowed_globals, timeout_ms]
|
||||
output: LuaScript
|
||||
acl_required: ["lua_script:update"]
|
||||
validation:
|
||||
- timeout_range: "Timeout must be between 100 and 30000 ms"
|
||||
errors:
|
||||
- NOT_FOUND: "Lua script not found"
|
||||
- CONFLICT: "Lua script name already exists"
|
||||
- VALIDATION_ERROR: "Invalid script update"
|
||||
|
||||
delete:
|
||||
description: "Delete Lua script"
|
||||
input:
|
||||
required: [id]
|
||||
output: boolean
|
||||
acl_required: ["lua_script:delete"]
|
||||
errors:
|
||||
- NOT_FOUND: "Lua script not found"
|
||||
|
||||
list:
|
||||
description: "List Lua scripts with filtering and pagination"
|
||||
input:
|
||||
optional: [created_by, is_sandboxed, page, limit, sort]
|
||||
output: LuaScript[]
|
||||
acl_required: ["lua_script:read"]
|
||||
pagination: true
|
||||
max_limit: 100
|
||||
default_limit: 20
|
||||
errors:
|
||||
- VALIDATION_ERROR: "Invalid pagination parameters"
|
||||
@@ -1,92 +0,0 @@
|
||||
operations:
|
||||
create:
|
||||
description: "Create a new package definition"
|
||||
input:
|
||||
required: [name, version, author, manifest]
|
||||
optional: [description, is_installed, installed_at, installed_by]
|
||||
output: Package
|
||||
acl_required: ["package:create"]
|
||||
validation:
|
||||
- semver_format: "Version must be valid semver"
|
||||
- name_version_unique: "Package name+version must be unique"
|
||||
errors:
|
||||
- CONFLICT: "Package with name and version already exists"
|
||||
- VALIDATION_ERROR: "Invalid package input"
|
||||
|
||||
create_many:
|
||||
description: "Bulk create package definitions"
|
||||
input:
|
||||
required: [items]
|
||||
optional: []
|
||||
output: integer
|
||||
acl_required: ["package:create"]
|
||||
validation:
|
||||
- semver_format: "Version must be valid semver"
|
||||
- name_version_unique: "Package name+version must be unique"
|
||||
errors:
|
||||
- CONFLICT: "Package with name and version already exists"
|
||||
- VALIDATION_ERROR: "Invalid package input"
|
||||
|
||||
read:
|
||||
description: "Get package by ID"
|
||||
input:
|
||||
required: [id]
|
||||
output: Package
|
||||
acl_required: ["package:read"]
|
||||
errors:
|
||||
- NOT_FOUND: "Package not found"
|
||||
|
||||
update:
|
||||
description: "Update package"
|
||||
input:
|
||||
required: [id]
|
||||
optional: [name, version, description, author, manifest, is_installed, installed_at, installed_by]
|
||||
output: Package
|
||||
acl_required: ["package:update"]
|
||||
validation:
|
||||
- semver_format: "Version must be valid semver"
|
||||
errors:
|
||||
- NOT_FOUND: "Package not found"
|
||||
- CONFLICT: "Package name+version already exists"
|
||||
- VALIDATION_ERROR: "Invalid package update"
|
||||
|
||||
update_many:
|
||||
description: "Bulk update packages matching a filter"
|
||||
input:
|
||||
required: [filter, data]
|
||||
output: integer
|
||||
acl_required: ["package:update"]
|
||||
validation:
|
||||
- semver_format: "Version must be valid semver"
|
||||
errors:
|
||||
- VALIDATION_ERROR: "Invalid package update"
|
||||
|
||||
delete:
|
||||
description: "Delete package"
|
||||
input:
|
||||
required: [id]
|
||||
output: boolean
|
||||
acl_required: ["package:delete"]
|
||||
errors:
|
||||
- NOT_FOUND: "Package not found"
|
||||
|
||||
delete_many:
|
||||
description: "Bulk delete packages matching a filter"
|
||||
input:
|
||||
required: [filter]
|
||||
output: integer
|
||||
acl_required: ["package:delete"]
|
||||
errors:
|
||||
- VALIDATION_ERROR: "Invalid delete filter"
|
||||
|
||||
list:
|
||||
description: "List packages with filtering and pagination"
|
||||
input:
|
||||
optional: [name, version, author, is_installed, page, limit, sort]
|
||||
output: Package[]
|
||||
acl_required: ["package:read"]
|
||||
pagination: true
|
||||
max_limit: 100
|
||||
default_limit: 20
|
||||
errors:
|
||||
- VALIDATION_ERROR: "Invalid pagination parameters"
|
||||
@@ -1,58 +0,0 @@
|
||||
operations:
|
||||
create:
|
||||
description: "Create new workflow"
|
||||
input:
|
||||
required: [name, trigger, trigger_config, steps, created_by]
|
||||
optional: [description, is_active]
|
||||
output: Workflow
|
||||
acl_required: ["workflow:create"]
|
||||
validation:
|
||||
- name_unique: "Workflow name must be unique"
|
||||
- trigger_valid: "Trigger must be manual, schedule, event, webhook"
|
||||
errors:
|
||||
- CONFLICT: "Workflow with this name already exists"
|
||||
- VALIDATION_ERROR: "Invalid input data"
|
||||
|
||||
read:
|
||||
description: "Get workflow by ID"
|
||||
input:
|
||||
required: [id]
|
||||
output: Workflow
|
||||
acl_required: ["workflow:read"]
|
||||
errors:
|
||||
- NOT_FOUND: "Workflow not found"
|
||||
|
||||
update:
|
||||
description: "Update workflow"
|
||||
input:
|
||||
required: [id]
|
||||
optional: [name, description, trigger, trigger_config, steps, is_active]
|
||||
output: Workflow
|
||||
acl_required: ["workflow:update"]
|
||||
validation:
|
||||
- trigger_valid: "Trigger must be manual, schedule, event, webhook"
|
||||
errors:
|
||||
- NOT_FOUND: "Workflow not found"
|
||||
- CONFLICT: "Workflow name already in use"
|
||||
- VALIDATION_ERROR: "Invalid input data"
|
||||
|
||||
delete:
|
||||
description: "Delete workflow"
|
||||
input:
|
||||
required: [id]
|
||||
output: boolean
|
||||
acl_required: ["workflow:delete"]
|
||||
errors:
|
||||
- NOT_FOUND: "Workflow not found"
|
||||
|
||||
list:
|
||||
description: "List workflows with filtering and pagination"
|
||||
input:
|
||||
optional: [trigger, is_active, created_by, page, limit, sort]
|
||||
output: Workflow[]
|
||||
acl_required: ["workflow:read"]
|
||||
pagination: true
|
||||
max_limit: 100
|
||||
default_limit: 20
|
||||
errors:
|
||||
- VALIDATION_ERROR: "Invalid pagination parameters"
|
||||
@@ -1,132 +0,0 @@
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
username String @unique
|
||||
email String @unique
|
||||
role String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
workflows Workflow[]
|
||||
luaScripts LuaScript[]
|
||||
installedPackages Package[]
|
||||
}
|
||||
|
||||
model Credential {
|
||||
id String @id @default(uuid())
|
||||
username String @unique
|
||||
passwordHash String
|
||||
firstLogin Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
token String @unique
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
lastActivity DateTime @updatedAt
|
||||
|
||||
@@index([userId])
|
||||
@@index([expiresAt])
|
||||
}
|
||||
|
||||
model PageView {
|
||||
id String @id @default(uuid())
|
||||
slug String @unique
|
||||
title String
|
||||
description String?
|
||||
level Int
|
||||
layout String
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
components ComponentHierarchy[]
|
||||
|
||||
@@index([level])
|
||||
@@index([isActive])
|
||||
}
|
||||
|
||||
model ComponentHierarchy {
|
||||
id String @id @default(uuid())
|
||||
pageId String
|
||||
parentId String?
|
||||
componentType String
|
||||
order Int @default(0)
|
||||
props String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
page PageView @relation(fields: [pageId], references: [id], onDelete: Cascade)
|
||||
parent ComponentHierarchy? @relation("ParentChild", fields: [parentId], references: [id], onDelete: Cascade)
|
||||
children ComponentHierarchy[] @relation("ParentChild")
|
||||
|
||||
@@index([pageId])
|
||||
@@index([parentId])
|
||||
@@index([pageId, order])
|
||||
}
|
||||
|
||||
model Workflow {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
description String?
|
||||
trigger String
|
||||
triggerConfig String
|
||||
steps String
|
||||
isActive Boolean @default(true)
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
creator User @relation(fields: [createdBy], references: [id])
|
||||
|
||||
@@index([trigger])
|
||||
@@index([isActive])
|
||||
}
|
||||
|
||||
model LuaScript {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
description String?
|
||||
code String
|
||||
isSandboxed Boolean @default(true)
|
||||
allowedGlobals String
|
||||
timeoutMs Int @default(5000)
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
creator User @relation(fields: [createdBy], references: [id])
|
||||
|
||||
@@index([isSandboxed])
|
||||
}
|
||||
|
||||
model Package {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
version String
|
||||
description String?
|
||||
author String
|
||||
manifest String
|
||||
isInstalled Boolean @default(false)
|
||||
installedAt DateTime?
|
||||
installedBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
installer User? @relation(fields: [installedBy], references: [id])
|
||||
|
||||
@@unique([name, version])
|
||||
@@index([isInstalled])
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
# Build artifacts
|
||||
build/
|
||||
cmake-build-*/
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Git
|
||||
.git/
|
||||
.gitignore
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
docs/
|
||||
|
||||
# Tests (not needed in production image)
|
||||
tests/
|
||||
|
||||
# Conan cache
|
||||
.conan/
|
||||
|
||||
# Temporary files
|
||||
*.log
|
||||
*.tmp
|
||||
1
dbal/cpp/.gitignore
vendored
1
dbal/cpp/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
build/
|
||||
@@ -1,100 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(dbal VERSION 1.0.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
set(DBAL_ROOT ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||
set(DBAL_SRC_DIR ${DBAL_ROOT}/src)
|
||||
set(DBAL_TEST_DIR ${DBAL_ROOT}/tests)
|
||||
set(DBAL_INCLUDE_DIR ${DBAL_ROOT}/include)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
include_directories(${DBAL_INCLUDE_DIR} ${DBAL_INCLUDE_DIR}/dbal ${DBAL_SRC_DIR})
|
||||
|
||||
# Try to find Conan dependencies, but don't fail if they're not available
|
||||
find_package(fmt QUIET)
|
||||
find_package(spdlog QUIET)
|
||||
find_package(nlohmann_json QUIET)
|
||||
find_package(SQLite3 QUIET)
|
||||
find_package(Drogon REQUIRED CONFIG)
|
||||
find_package(cpr REQUIRED CONFIG)
|
||||
|
||||
add_library(dbal_core STATIC
|
||||
${DBAL_SRC_DIR}/client.cpp
|
||||
${DBAL_SRC_DIR}/errors.cpp
|
||||
)
|
||||
|
||||
add_library(dbal_adapters STATIC
|
||||
${DBAL_SRC_DIR}/adapters/sqlite/sqlite_adapter.cpp
|
||||
${DBAL_SRC_DIR}/adapters/sqlite/sqlite_pool.cpp
|
||||
${DBAL_SRC_DIR}/adapters/sql/postgres_adapter.cpp
|
||||
${DBAL_SRC_DIR}/adapters/sql/mysql_adapter.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(dbal_adapters PRIVATE cpr::cpr)
|
||||
add_executable(dbal_daemon
|
||||
${DBAL_SRC_DIR}/daemon/main.cpp
|
||||
${DBAL_SRC_DIR}/daemon/server.cpp
|
||||
${DBAL_SRC_DIR}/daemon/server_routes.cpp
|
||||
${DBAL_SRC_DIR}/daemon/server_helpers/network.cpp
|
||||
${DBAL_SRC_DIR}/daemon/server_helpers/role.cpp
|
||||
${DBAL_SRC_DIR}/daemon/server_helpers/serialization.cpp
|
||||
${DBAL_SRC_DIR}/daemon/server_helpers/response.cpp
|
||||
${DBAL_SRC_DIR}/daemon/rpc_user_actions.cpp
|
||||
${DBAL_SRC_DIR}/daemon/security.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(dbal_daemon
|
||||
dbal_core
|
||||
dbal_adapters
|
||||
Threads::Threads
|
||||
Drogon::Drogon
|
||||
)
|
||||
|
||||
# Link optional dependencies if available
|
||||
if(fmt_FOUND)
|
||||
target_link_libraries(dbal_core fmt::fmt)
|
||||
endif()
|
||||
|
||||
if(spdlog_FOUND)
|
||||
target_link_libraries(dbal_core spdlog::spdlog)
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_executable(client_test
|
||||
${DBAL_TEST_DIR}/unit/client_test.cpp
|
||||
)
|
||||
|
||||
add_executable(query_test
|
||||
${DBAL_TEST_DIR}/unit/query_test.cpp
|
||||
)
|
||||
|
||||
add_executable(integration_tests
|
||||
${DBAL_TEST_DIR}/integration/sqlite_test.cpp
|
||||
)
|
||||
|
||||
add_executable(conformance_tests
|
||||
${DBAL_TEST_DIR}/conformance/runner.cpp
|
||||
)
|
||||
|
||||
add_executable(http_server_security_test
|
||||
${DBAL_TEST_DIR}/security/http_server_security_test.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(client_test dbal_core dbal_adapters)
|
||||
target_link_libraries(query_test dbal_core dbal_adapters)
|
||||
target_link_libraries(integration_tests dbal_core dbal_adapters)
|
||||
target_link_libraries(conformance_tests dbal_core dbal_adapters)
|
||||
target_link_libraries(http_server_security_test Threads::Threads)
|
||||
|
||||
add_test(NAME client_test COMMAND client_test)
|
||||
add_test(NAME query_test COMMAND query_test)
|
||||
add_test(NAME integration_tests COMMAND integration_tests)
|
||||
add_test(NAME conformance_tests COMMAND conformance_tests)
|
||||
|
||||
install(TARGETS dbal_daemon DESTINATION bin)
|
||||
install(DIRECTORY ${DBAL_INCLUDE_DIR}/dbal DESTINATION include)
|
||||
@@ -1,78 +0,0 @@
|
||||
# Multi-stage Dockerfile for DBAL Daemon
|
||||
# Optimized for production deployments with minimal image size
|
||||
|
||||
# Stage 1: Builder
|
||||
FROM ubuntu:22.04 AS builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
ninja-build \
|
||||
git \
|
||||
python3 \
|
||||
python3-pip \
|
||||
libsqlite3-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /build
|
||||
|
||||
# Copy source files
|
||||
COPY CMakeLists.txt conanfile.txt ./
|
||||
COPY include/ include/
|
||||
COPY src/ src/
|
||||
COPY tests/ tests/
|
||||
|
||||
# Install Conan and dependencies
|
||||
RUN pip3 install --no-cache-dir conan && \
|
||||
conan profile detect --force
|
||||
|
||||
# Build the daemon
|
||||
RUN conan install . --output-folder=build --build=missing && \
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -S . -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake && \
|
||||
cmake --build build --target dbal_daemon
|
||||
|
||||
# Stage 2: Runtime
|
||||
FROM ubuntu:22.04
|
||||
|
||||
# Install runtime dependencies only
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libsqlite3-0 \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user for security
|
||||
RUN useradd -r -u 1000 -m -s /bin/bash dbal
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy binary from builder
|
||||
COPY --from=builder /build/build/dbal_daemon /app/dbal_daemon
|
||||
|
||||
# Copy default config (can be overridden with volume mount)
|
||||
RUN echo "# DBAL Configuration" > /app/config.yaml
|
||||
|
||||
# Change ownership to dbal user
|
||||
RUN chown -R dbal:dbal /app
|
||||
|
||||
# Switch to non-root user
|
||||
USER dbal
|
||||
|
||||
# Environment variables with defaults
|
||||
ENV DBAL_BIND_ADDRESS=0.0.0.0 \
|
||||
DBAL_PORT=8080 \
|
||||
DBAL_LOG_LEVEL=info \
|
||||
DBAL_MODE=production \
|
||||
DBAL_CONFIG=/app/config.yaml
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8080
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:${DBAL_PORT}/health || exit 1
|
||||
|
||||
# Run in daemon mode by default
|
||||
CMD ["sh", "-c", "./dbal_daemon --bind ${DBAL_BIND_ADDRESS} --port ${DBAL_PORT} --mode ${DBAL_MODE} --config ${DBAL_CONFIG} --daemon"]
|
||||
@@ -1,45 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
dbal:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: dbal-daemon
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- DBAL_BIND_ADDRESS=0.0.0.0
|
||||
- DBAL_PORT=8080
|
||||
- DBAL_LOG_LEVEL=info
|
||||
- DBAL_MODE=production
|
||||
volumes:
|
||||
# Optional: Mount custom config
|
||||
# - ./config.yaml:/app/config.yaml:ro
|
||||
# Optional: Mount data directory
|
||||
# - ./data:/app/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
|
||||
# Optional: Nginx reverse proxy
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: dbal-nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
# - ./ssl:/etc/nginx/ssl:ro # For SSL certificates
|
||||
depends_on:
|
||||
- dbal
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: dbal-network
|
||||
@@ -1,434 +0,0 @@
|
||||
# C++ Implementation Guide
|
||||
|
||||
## Building the DBAL Daemon
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- CMake 3.20+
|
||||
- C++17 compatible compiler (GCC 9+, Clang 10+, MSVC 2019+)
|
||||
- SQLite3 development libraries
|
||||
- Drogon HTTP framework (via Conan or system package manager)
|
||||
- Optional: MongoDB C++ driver, gRPC
|
||||
|
||||
### Build Instructions
|
||||
|
||||
```bash
|
||||
cd dbal/cpp
|
||||
conan install . --output-folder=build --build=missing
|
||||
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake
|
||||
cmake --build build -j$(nproc)
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# From build directory
|
||||
./unit_tests
|
||||
./integration_tests
|
||||
./conformance_tests
|
||||
|
||||
# Security tests (recommended after any HTTP server changes)
|
||||
./http_server_security_test
|
||||
```
|
||||
|
||||
See [SECURITY_TESTING.md](SECURITY_TESTING.md) for comprehensive security testing guide.
|
||||
|
||||
### Installing
|
||||
|
||||
```bash
|
||||
sudo make install
|
||||
```
|
||||
|
||||
This installs:
|
||||
- `/usr/local/bin/dbal_daemon` - The daemon executable
|
||||
- `/usr/local/include/dbal/` - Public headers
|
||||
|
||||
## Daemon Architecture
|
||||
|
||||
### Security Model
|
||||
|
||||
The daemon implements **defense-in-depth security** with multiple layers:
|
||||
|
||||
#### HTTP Server Security (Production-Ready)
|
||||
|
||||
The daemon now uses **Drogon** for HTTP handling to avoid custom parsing risks and reduce CVE exposure. Drogon provides hardened HTTP parsing, request validation, and connection management out of the box.
|
||||
|
||||
See [CVE_ANALYSIS.md](CVE_ANALYSIS.md) and [CVE_COMPARISON_SUMMARY.md](CVE_COMPARISON_SUMMARY.md) for the legacy server analysis and migration notes.
|
||||
|
||||
#### Process Security
|
||||
|
||||
1. **Process Isolation**: Runs in separate process from application
|
||||
2. **File System**: Restricted to `/var/lib/dbal/` and `/var/log/dbal/`
|
||||
3. **Network**: Only connects to database, no outbound internet
|
||||
4. **User**: Runs as dedicated `dbal` user (not root)
|
||||
5. **Capabilities**: Only `CAP_NET_BIND_SERVICE` for port 50051
|
||||
|
||||
### Configuration
|
||||
|
||||
```yaml
|
||||
# /etc/dbal/config.yaml
|
||||
server:
|
||||
bind: "127.0.0.1:50051"
|
||||
tls:
|
||||
enabled: true
|
||||
cert: "/etc/dbal/certs/server.crt"
|
||||
key: "/etc/dbal/certs/server.key"
|
||||
|
||||
database:
|
||||
adapter: "prisma"
|
||||
url: "${DATABASE_URL}"
|
||||
pool_size: 20
|
||||
connection_timeout: 30
|
||||
|
||||
security:
|
||||
sandbox: "strict"
|
||||
audit_log: "/var/log/dbal/audit.log"
|
||||
max_query_time: 30
|
||||
max_result_size: 1048576
|
||||
|
||||
acl:
|
||||
rules_file: "/etc/dbal/acl.yaml"
|
||||
enforce_row_level: true
|
||||
```
|
||||
|
||||
### Running the Daemon
|
||||
|
||||
#### Development
|
||||
|
||||
```bash
|
||||
./dbal_daemon --config=../config/dev.yaml --mode=development
|
||||
```
|
||||
|
||||
#### Production (systemd)
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/dbal.service
|
||||
[Unit]
|
||||
Description=DBAL Daemon
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=dbal
|
||||
Group=dbal
|
||||
ExecStart=/usr/local/bin/dbal_daemon --config=/etc/dbal/config.yaml
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
PrivateTmp=true
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths=/var/lib/dbal /var/log/dbal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Start the service:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable dbal
|
||||
sudo systemctl start dbal
|
||||
sudo systemctl status dbal
|
||||
```
|
||||
|
||||
#### Docker
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM alpine:3.18
|
||||
|
||||
RUN apk add --no-cache \
|
||||
libstdc++ \
|
||||
sqlite-libs
|
||||
|
||||
COPY --from=builder /app/build/dbal_daemon /usr/local/bin/
|
||||
COPY config/prod.yaml /etc/dbal/config.yaml
|
||||
|
||||
RUN adduser -D -u 1000 dbal && \
|
||||
mkdir -p /var/lib/dbal /var/log/dbal && \
|
||||
chown -R dbal:dbal /var/lib/dbal /var/log/dbal
|
||||
|
||||
USER dbal
|
||||
EXPOSE 50051
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/dbal_daemon"]
|
||||
CMD ["--config=/etc/dbal/config.yaml"]
|
||||
```
|
||||
|
||||
## Code Structure
|
||||
|
||||
### Public API (`include/dbal/`)
|
||||
|
||||
**client.hpp** - Main client interface
|
||||
```cpp
|
||||
dbal::Client client(config);
|
||||
auto result = client.createUser({
|
||||
.username = "john",
|
||||
.email = "john@example.com",
|
||||
.role = dbal::UserRole::User
|
||||
});
|
||||
if (result.isOk()) {
|
||||
std::cout << "Created user: " << result.value().id << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
**errors.hpp** - Error handling with Result type
|
||||
```cpp
|
||||
dbal::Result<User> getUser(const std::string& id) {
|
||||
if (!exists(id)) {
|
||||
return dbal::Error::notFound("User not found");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
```
|
||||
|
||||
**types.hpp** - Entity definitions (generated from YAML)
|
||||
|
||||
### Implementation (`src/`)
|
||||
|
||||
**adapters/** - Backend implementations
|
||||
- `sqlite/` - Direct SQLite access
|
||||
- `prisma/` - Bridge to Prisma (via RPC)
|
||||
- `mongodb/` - MongoDB driver
|
||||
|
||||
**query/** - Query builder and optimizer
|
||||
- Independent of backend
|
||||
- Translates to SQL/NoSQL
|
||||
|
||||
**daemon/** - Daemon server
|
||||
- gRPC/WebSocket server
|
||||
- Authentication/ACL enforcement
|
||||
- Request routing
|
||||
|
||||
### Testing (`tests/`)
|
||||
|
||||
**unit/** - Unit tests for individual components
|
||||
**integration/** - Tests with real databases
|
||||
**conformance/** - Cross-implementation tests
|
||||
|
||||
## Adding a New Adapter
|
||||
|
||||
1. Create header in `include/dbal/adapters/mydb/`
|
||||
2. Implement in `src/adapters/mydb/`
|
||||
3. Inherit from `adapters::Adapter` interface
|
||||
4. Implement all CRUD methods
|
||||
5. Add to CMakeLists.txt
|
||||
6. Write integration tests
|
||||
7. Run conformance tests
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
// include/dbal/adapters/mydb/mydb_adapter.hpp
|
||||
#ifndef DBAL_ADAPTERS_MYDB_ADAPTER_HPP
|
||||
#define DBAL_ADAPTERS_MYDB_ADAPTER_HPP
|
||||
|
||||
#include "../adapter.hpp"
|
||||
|
||||
namespace dbal::adapters {
|
||||
|
||||
class MyDBAdapter : public Adapter {
|
||||
public:
|
||||
explicit MyDBAdapter(const std::string& connection_string);
|
||||
|
||||
Result<Entity> create(const std::string& entity,
|
||||
const Json& data) override;
|
||||
Result<Entity> read(const std::string& entity,
|
||||
const std::string& id) override;
|
||||
// ... other methods
|
||||
|
||||
private:
|
||||
MyDBConnection conn_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### Enable Debug Logging
|
||||
|
||||
```bash
|
||||
DBAL_LOG_LEVEL=debug ./dbal_daemon --config=config.yaml
|
||||
```
|
||||
|
||||
### GDB Debugging
|
||||
|
||||
```bash
|
||||
gdb ./dbal_daemon
|
||||
(gdb) break dbal::Client::createUser
|
||||
(gdb) run --config=dev.yaml
|
||||
```
|
||||
|
||||
### Valgrind Memory Check
|
||||
|
||||
```bash
|
||||
valgrind --leak-check=full ./dbal_daemon --config=config.yaml
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Connection Pooling
|
||||
|
||||
Adjust pool size based on workload:
|
||||
|
||||
```yaml
|
||||
database:
|
||||
pool_size: 50 # Increase for high concurrency
|
||||
min_idle: 10
|
||||
max_lifetime: 3600
|
||||
```
|
||||
|
||||
### Query Optimization
|
||||
|
||||
Enable query caching:
|
||||
|
||||
```yaml
|
||||
performance:
|
||||
query_cache: true
|
||||
cache_size_mb: 256
|
||||
cache_ttl: 300
|
||||
```
|
||||
|
||||
### Batch Operations
|
||||
|
||||
Use batch APIs for bulk operations (return count of affected rows):
|
||||
|
||||
```cpp
|
||||
std::vector<CreateUserInput> users = {...};
|
||||
auto created = client.batchCreateUsers(users);
|
||||
|
||||
std::vector<UpdateUserBatchItem> updates = {...};
|
||||
auto updated = client.batchUpdateUsers(updates);
|
||||
|
||||
std::vector<std::string> ids = {...};
|
||||
auto deleted = client.batchDeleteUsers(ids);
|
||||
```
|
||||
|
||||
Package equivalents are available via `batchCreatePackages`, `batchUpdatePackages`, and `batchDeletePackages`.
|
||||
|
||||
## Security Hardening
|
||||
|
||||
### 1. Run as Non-Root
|
||||
|
||||
```bash
|
||||
sudo useradd -r -s /bin/false dbal
|
||||
sudo chown -R dbal:dbal /var/lib/dbal
|
||||
```
|
||||
|
||||
### 2. Enable SELinux/AppArmor
|
||||
|
||||
```bash
|
||||
# SELinux policy
|
||||
semanage fcontext -a -t dbal_db_t "/var/lib/dbal(/.*)?"
|
||||
restorecon -R /var/lib/dbal
|
||||
```
|
||||
|
||||
### 3. Use TLS
|
||||
|
||||
```yaml
|
||||
server:
|
||||
tls:
|
||||
enabled: true
|
||||
cert: "/etc/dbal/certs/server.crt"
|
||||
key: "/etc/dbal/certs/server.key"
|
||||
client_auth: true # mTLS
|
||||
```
|
||||
|
||||
### 4. Audit Logging
|
||||
|
||||
```yaml
|
||||
security:
|
||||
audit_log: "/var/log/dbal/audit.log"
|
||||
log_all_queries: false
|
||||
log_sensitive_operations: true
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Daemon Won't Start
|
||||
|
||||
Check logs:
|
||||
```bash
|
||||
journalctl -u dbal -n 50
|
||||
```
|
||||
|
||||
Common issues:
|
||||
- Port already in use: Change `bind` in config
|
||||
- Permission denied: Check file ownership
|
||||
- Database unreachable: Verify `DATABASE_URL`
|
||||
|
||||
### High Memory Usage
|
||||
|
||||
Monitor with:
|
||||
```bash
|
||||
pmap -x $(pgrep dbal_daemon)
|
||||
```
|
||||
|
||||
Reduce:
|
||||
- Connection pool size
|
||||
- Query cache size
|
||||
- Result set limits
|
||||
|
||||
### Slow Queries
|
||||
|
||||
Enable query timing:
|
||||
```yaml
|
||||
logging:
|
||||
slow_query_threshold_ms: 1000
|
||||
```
|
||||
|
||||
Check logs for slow queries and add indexes.
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
- name: Build C++ DBAL
|
||||
run: |
|
||||
cd dbal/cpp
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --parallel
|
||||
|
||||
- name: Run Tests
|
||||
run: |
|
||||
cd dbal/cpp/build
|
||||
ctest --output-on-failure
|
||||
```
|
||||
|
||||
### Docker Build
|
||||
|
||||
```bash
|
||||
docker build -t dbal-daemon:latest -f dbal/cpp/Dockerfile .
|
||||
docker push dbal-daemon:latest
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Prometheus Metrics
|
||||
|
||||
Expose metrics on `:9090/metrics`:
|
||||
|
||||
```
|
||||
dbal_queries_total{entity="User",operation="create"} 1234
|
||||
dbal_query_duration_seconds{entity="User",operation="create",quantile="0.99"} 0.045
|
||||
dbal_connection_pool_size{adapter="sqlite"} 20
|
||||
dbal_connection_pool_idle{adapter="sqlite"} 15
|
||||
```
|
||||
|
||||
### Health Checks
|
||||
|
||||
```bash
|
||||
curl http://localhost:50051/health
|
||||
# {"status": "healthy", "uptime": 3600, "connections": 15}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **API Documentation**: [docs.metabuilder.io/dbal/cpp](https://docs.metabuilder.io/dbal/cpp)
|
||||
- **Examples**: [cpp/examples/](cpp/examples/)
|
||||
- **Architecture**: [docs/architecture.md](../docs/architecture.md)
|
||||
@@ -1,58 +0,0 @@
|
||||
#ifndef DBAL_ADAPTER_HPP
|
||||
#define DBAL_ADAPTER_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../types.hpp"
|
||||
#include "../errors.hpp"
|
||||
|
||||
namespace dbal {
|
||||
namespace adapters {
|
||||
|
||||
class Adapter {
|
||||
public:
|
||||
virtual ~Adapter() = default;
|
||||
|
||||
virtual Result<User> createUser(const CreateUserInput& input) = 0;
|
||||
virtual Result<User> getUser(const std::string& id) = 0;
|
||||
virtual Result<User> updateUser(const std::string& id, const UpdateUserInput& input) = 0;
|
||||
virtual Result<bool> deleteUser(const std::string& id) = 0;
|
||||
virtual Result<std::vector<User>> listUsers(const ListOptions& options) = 0;
|
||||
|
||||
virtual Result<PageView> createPage(const CreatePageInput& input) = 0;
|
||||
virtual Result<PageView> getPage(const std::string& id) = 0;
|
||||
virtual Result<PageView> updatePage(const std::string& id, const UpdatePageInput& input) = 0;
|
||||
virtual Result<bool> deletePage(const std::string& id) = 0;
|
||||
virtual Result<std::vector<PageView>> listPages(const ListOptions& options) = 0;
|
||||
|
||||
virtual Result<Workflow> createWorkflow(const CreateWorkflowInput& input) = 0;
|
||||
virtual Result<Workflow> getWorkflow(const std::string& id) = 0;
|
||||
virtual Result<Workflow> updateWorkflow(const std::string& id, const UpdateWorkflowInput& input) = 0;
|
||||
virtual Result<bool> deleteWorkflow(const std::string& id) = 0;
|
||||
virtual Result<std::vector<Workflow>> listWorkflows(const ListOptions& options) = 0;
|
||||
|
||||
virtual Result<Session> createSession(const CreateSessionInput& input) = 0;
|
||||
virtual Result<Session> getSession(const std::string& id) = 0;
|
||||
virtual Result<Session> updateSession(const std::string& id, const UpdateSessionInput& input) = 0;
|
||||
virtual Result<bool> deleteSession(const std::string& id) = 0;
|
||||
virtual Result<std::vector<Session>> listSessions(const ListOptions& options) = 0;
|
||||
|
||||
virtual Result<LuaScript> createLuaScript(const CreateLuaScriptInput& input) = 0;
|
||||
virtual Result<LuaScript> getLuaScript(const std::string& id) = 0;
|
||||
virtual Result<LuaScript> updateLuaScript(const std::string& id, const UpdateLuaScriptInput& input) = 0;
|
||||
virtual Result<bool> deleteLuaScript(const std::string& id) = 0;
|
||||
virtual Result<std::vector<LuaScript>> listLuaScripts(const ListOptions& options) = 0;
|
||||
|
||||
virtual Result<Package> createPackage(const CreatePackageInput& input) = 0;
|
||||
virtual Result<Package> getPackage(const std::string& id) = 0;
|
||||
virtual Result<Package> updatePackage(const std::string& id, const UpdatePackageInput& input) = 0;
|
||||
virtual Result<bool> deletePackage(const std::string& id) = 0;
|
||||
virtual Result<std::vector<Package>> listPackages(const ListOptions& options) = 0;
|
||||
|
||||
virtual void close() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,3 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "dbal/storage/blob_storage.hpp"
|
||||
@@ -1,6 +0,0 @@
|
||||
#ifndef DBAL_CLIENT_WRAPPER_HPP
|
||||
#define DBAL_CLIENT_WRAPPER_HPP
|
||||
|
||||
#include "dbal/core/client.hpp"
|
||||
|
||||
#endif // DBAL_CLIENT_WRAPPER_HPP
|
||||
@@ -1,115 +0,0 @@
|
||||
#ifndef DBAL_CLIENT_HPP
|
||||
#define DBAL_CLIENT_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "types.hpp"
|
||||
#include "errors.hpp"
|
||||
#include "adapters/adapter.hpp"
|
||||
|
||||
namespace dbal {
|
||||
|
||||
struct ClientConfig {
|
||||
std::string mode;
|
||||
std::string adapter;
|
||||
std::string endpoint;
|
||||
std::string database_url;
|
||||
bool sandbox_enabled = true;
|
||||
};
|
||||
|
||||
class Client {
|
||||
public:
|
||||
explicit Client(const ClientConfig& config);
|
||||
~Client();
|
||||
|
||||
Client(const Client&) = delete;
|
||||
Client& operator=(const Client&) = delete;
|
||||
Client(Client&&) = default;
|
||||
Client& operator=(Client&&) = default;
|
||||
|
||||
Result<User> createUser(const CreateUserInput& input);
|
||||
Result<User> getUser(const std::string& id);
|
||||
Result<User> updateUser(const std::string& id, const UpdateUserInput& input);
|
||||
Result<bool> deleteUser(const std::string& id);
|
||||
Result<std::vector<User>> listUsers(const ListOptions& options);
|
||||
Result<int> batchCreateUsers(const std::vector<CreateUserInput>& inputs);
|
||||
Result<int> batchUpdateUsers(const std::vector<UpdateUserBatchItem>& updates);
|
||||
Result<int> batchDeleteUsers(const std::vector<std::string>& ids);
|
||||
|
||||
Result<std::vector<User>> searchUsers(const std::string& query, int limit = 20);
|
||||
Result<int> countUsers(const std::optional<UserRole>& role = std::nullopt);
|
||||
Result<int> updateManyUsers(const std::map<std::string, std::string>& filter,
|
||||
const UpdateUserInput& updates);
|
||||
Result<int> deleteManyUsers(const std::map<std::string, std::string>& filter);
|
||||
|
||||
Result<bool> setCredential(const CreateCredentialInput& input);
|
||||
Result<bool> verifyCredential(const std::string& username, const std::string& password);
|
||||
Result<bool> setCredentialFirstLoginFlag(const std::string& username, bool first_login);
|
||||
Result<bool> getCredentialFirstLoginFlag(const std::string& username);
|
||||
Result<bool> deleteCredential(const std::string& username);
|
||||
|
||||
Result<PageView> createPage(const CreatePageInput& input);
|
||||
Result<PageView> getPage(const std::string& id);
|
||||
Result<PageView> getPageBySlug(const std::string& slug);
|
||||
Result<PageView> updatePage(const std::string& id, const UpdatePageInput& input);
|
||||
Result<bool> deletePage(const std::string& id);
|
||||
Result<std::vector<PageView>> listPages(const ListOptions& options);
|
||||
Result<std::vector<PageView>> searchPages(const std::string& query, int limit = 20);
|
||||
|
||||
Result<ComponentHierarchy> createComponent(const CreateComponentHierarchyInput& input);
|
||||
Result<ComponentHierarchy> getComponent(const std::string& id);
|
||||
Result<ComponentHierarchy> updateComponent(const std::string& id, const UpdateComponentHierarchyInput& input);
|
||||
Result<bool> deleteComponent(const std::string& id);
|
||||
Result<std::vector<ComponentHierarchy>> listComponents(const ListOptions& options);
|
||||
Result<std::vector<ComponentHierarchy>> getComponentTree(const std::string& page_id);
|
||||
Result<bool> reorderComponents(const std::vector<ComponentOrderUpdate>& updates);
|
||||
Result<ComponentHierarchy> moveComponent(const MoveComponentInput& input);
|
||||
Result<std::vector<ComponentHierarchy>> searchComponents(const std::string& query,
|
||||
const std::optional<std::string>& page_id = std::nullopt,
|
||||
int limit = 20);
|
||||
Result<std::vector<ComponentHierarchy>> getComponentChildren(const std::string& parent_id,
|
||||
const std::optional<std::string>& component_type = std::nullopt,
|
||||
int limit = 0);
|
||||
|
||||
Result<Workflow> createWorkflow(const CreateWorkflowInput& input);
|
||||
Result<Workflow> getWorkflow(const std::string& id);
|
||||
Result<Workflow> updateWorkflow(const std::string& id, const UpdateWorkflowInput& input);
|
||||
Result<bool> deleteWorkflow(const std::string& id);
|
||||
Result<std::vector<Workflow>> listWorkflows(const ListOptions& options);
|
||||
|
||||
Result<Session> createSession(const CreateSessionInput& input);
|
||||
Result<Session> getSession(const std::string& id);
|
||||
Result<Session> updateSession(const std::string& id, const UpdateSessionInput& input);
|
||||
Result<bool> deleteSession(const std::string& id);
|
||||
Result<std::vector<Session>> listSessions(const ListOptions& options);
|
||||
|
||||
Result<LuaScript> createLuaScript(const CreateLuaScriptInput& input);
|
||||
Result<LuaScript> getLuaScript(const std::string& id);
|
||||
Result<LuaScript> updateLuaScript(const std::string& id, const UpdateLuaScriptInput& input);
|
||||
Result<bool> deleteLuaScript(const std::string& id);
|
||||
Result<std::vector<LuaScript>> listLuaScripts(const ListOptions& options);
|
||||
Result<std::vector<LuaScript>> searchLuaScripts(const std::string& query,
|
||||
const std::optional<std::string>& created_by = std::nullopt,
|
||||
int limit = 20);
|
||||
|
||||
Result<Package> createPackage(const CreatePackageInput& input);
|
||||
Result<Package> getPackage(const std::string& id);
|
||||
Result<Package> updatePackage(const std::string& id, const UpdatePackageInput& input);
|
||||
Result<bool> deletePackage(const std::string& id);
|
||||
Result<std::vector<Package>> listPackages(const ListOptions& options);
|
||||
Result<int> batchCreatePackages(const std::vector<CreatePackageInput>& inputs);
|
||||
Result<int> batchUpdatePackages(const std::vector<UpdatePackageBatchItem>& updates);
|
||||
Result<int> batchDeletePackages(const std::vector<std::string>& ids);
|
||||
|
||||
void close();
|
||||
|
||||
private:
|
||||
std::unique_ptr<adapters::Adapter> adapter_;
|
||||
ClientConfig config_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,242 +0,0 @@
|
||||
/**
|
||||
* @file errors.hpp
|
||||
* @brief Error handling types and utilities for DBAL
|
||||
*
|
||||
* Provides comprehensive error handling with typed error codes,
|
||||
* factory methods, and Result<T> monad for functional error handling.
|
||||
*/
|
||||
|
||||
#ifndef DBAL_ERRORS_HPP
|
||||
#define DBAL_ERRORS_HPP
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace dbal {
|
||||
|
||||
/**
|
||||
* @enum ErrorCode
|
||||
* @brief HTTP-aligned error codes for consistent error handling
|
||||
*
|
||||
* Error codes map to HTTP status codes for easy integration with
|
||||
* REST APIs and web services. Each code represents a specific
|
||||
* failure category with well-defined semantics.
|
||||
*/
|
||||
enum class ErrorCode {
|
||||
NotFound = 404, ///< Resource not found
|
||||
Conflict = 409, ///< Resource conflict (e.g., duplicate key)
|
||||
Unauthorized = 401, ///< Authentication required
|
||||
Forbidden = 403, ///< Access forbidden (insufficient permissions)
|
||||
ValidationError = 422, ///< Input validation failed
|
||||
RateLimitExceeded = 429, ///< Too many requests (quota exceeded)
|
||||
InternalError = 500, ///< Internal server error
|
||||
Timeout = 504, ///< Operation timed out
|
||||
DatabaseError = 503, ///< Database unavailable
|
||||
CapabilityNotSupported = 501, ///< Feature not supported
|
||||
SandboxViolation = 403, ///< Sandbox security violation
|
||||
MaliciousCodeDetected = 403 ///< Malicious code detected
|
||||
};
|
||||
|
||||
/**
|
||||
* @class Error
|
||||
* @brief Exception class with typed error codes
|
||||
*
|
||||
* Provides structured error handling with HTTP-aligned status codes
|
||||
* and factory methods for common error scenarios. Derives from
|
||||
* std::runtime_error for compatibility with standard exception handling.
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* // Throw specific error
|
||||
* throw Error::notFound("User not found");
|
||||
*
|
||||
* // Check error code
|
||||
* try {
|
||||
* // operation
|
||||
* } catch (const Error& e) {
|
||||
* if (e.code() == ErrorCode::NotFound) {
|
||||
* // handle not found
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
class Error : public std::runtime_error {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct error with code and message
|
||||
* @param code HTTP-aligned error code
|
||||
* @param message Human-readable error message
|
||||
*/
|
||||
Error(ErrorCode code, const std::string& message)
|
||||
: std::runtime_error(message), code_(code) {}
|
||||
|
||||
/**
|
||||
* @brief Get the error code
|
||||
* @return ErrorCode indicating error type
|
||||
*/
|
||||
ErrorCode code() const { return code_; }
|
||||
|
||||
/**
|
||||
* @brief Factory for NotFound errors (404)
|
||||
* @param message Optional custom message
|
||||
* @return Error instance
|
||||
*/
|
||||
static Error notFound(const std::string& message = "Resource not found");
|
||||
|
||||
/**
|
||||
* @brief Factory for Conflict errors (409)
|
||||
* @param message Optional custom message
|
||||
* @return Error instance
|
||||
*/
|
||||
static Error conflict(const std::string& message = "Resource conflict");
|
||||
|
||||
/**
|
||||
* @brief Factory for Unauthorized errors (401)
|
||||
* @param message Optional custom message
|
||||
* @return Error instance
|
||||
*/
|
||||
static Error unauthorized(const std::string& message = "Authentication required");
|
||||
|
||||
/**
|
||||
* @brief Factory for Forbidden errors (403)
|
||||
* @param message Optional custom message
|
||||
* @return Error instance
|
||||
*/
|
||||
static Error forbidden(const std::string& message = "Access forbidden");
|
||||
|
||||
/**
|
||||
* @brief Factory for ValidationError (422)
|
||||
* @param message Validation failure details
|
||||
* @return Error instance
|
||||
*/
|
||||
static Error validationError(const std::string& message);
|
||||
|
||||
/**
|
||||
* @brief Factory for InternalError (500)
|
||||
* @param message Optional custom message
|
||||
* @return Error instance
|
||||
*/
|
||||
static Error internal(const std::string& message = "Internal server error");
|
||||
|
||||
/**
|
||||
* @brief Factory for SandboxViolation errors
|
||||
* @param message Violation details
|
||||
* @return Error instance
|
||||
*/
|
||||
static Error sandboxViolation(const std::string& message);
|
||||
|
||||
/**
|
||||
* @brief Factory for MaliciousCodeDetected errors
|
||||
* @param message Detection details
|
||||
* @return Error instance
|
||||
*/
|
||||
static Error maliciousCode(const std::string& message);
|
||||
|
||||
private:
|
||||
ErrorCode code_; ///< Error code
|
||||
};
|
||||
|
||||
/**
|
||||
* @class Result
|
||||
* @brief Functional error handling monad (Railway-Oriented Programming)
|
||||
*
|
||||
* Result<T> represents either a successful value (Ok) or an error (Err).
|
||||
* This enables explicit error handling without exceptions for performance-
|
||||
* critical code paths.
|
||||
*
|
||||
* @tparam T The success value type
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* Result<User> getUser(int id) {
|
||||
* if (user_exists(id)) {
|
||||
* return User{id, "John"}; // Ok
|
||||
* }
|
||||
* return Error::notFound("User not found"); // Err
|
||||
* }
|
||||
*
|
||||
* auto result = getUser(123);
|
||||
* if (result.isOk()) {
|
||||
* std::cout << result.value().name;
|
||||
* } else {
|
||||
* std::cerr << result.error().what();
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
template<typename T>
|
||||
class Result {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct successful result with value
|
||||
* @param value Success value
|
||||
*/
|
||||
Result(T value) : value_(std::move(value)), has_value_(true) {}
|
||||
|
||||
/**
|
||||
* @brief Construct error result
|
||||
* @param error Error instance
|
||||
*/
|
||||
Result(Error error) : error_(std::move(error)), has_value_(false) {}
|
||||
|
||||
/**
|
||||
* @brief Check if result contains value
|
||||
* @return true if Ok, false if Err
|
||||
*/
|
||||
bool isOk() const { return has_value_; }
|
||||
|
||||
/**
|
||||
* @brief Check if result contains error
|
||||
* @return true if Err, false if Ok
|
||||
*/
|
||||
bool isError() const { return !has_value_; }
|
||||
|
||||
/**
|
||||
* @brief Get mutable reference to value
|
||||
* @return Value reference
|
||||
* @throws Error if result is Err
|
||||
*/
|
||||
T& value() {
|
||||
if (!has_value_) throw error_;
|
||||
return value_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get const reference to value
|
||||
* @return Value reference
|
||||
* @throws Error if result is Err
|
||||
*/
|
||||
const T& value() const {
|
||||
if (!has_value_) throw error_;
|
||||
return value_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get mutable reference to error
|
||||
* @return Error reference
|
||||
* @throws std::logic_error if result is Ok
|
||||
*/
|
||||
Error& error() {
|
||||
if (has_value_) throw std::logic_error("No error present");
|
||||
return error_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get const reference to error
|
||||
* @return Error reference
|
||||
* @throws std::logic_error if result is Ok
|
||||
*/
|
||||
const Error& error() const {
|
||||
if (has_value_) throw std::logic_error("No error present");
|
||||
return error_;
|
||||
}
|
||||
|
||||
private:
|
||||
T value_; ///< Success value (if has_value_ == true)
|
||||
Error error_{ErrorCode::InternalError, ""}; ///< Error (if has_value_ == false)
|
||||
bool has_value_; ///< true if Ok, false if Err
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,282 +0,0 @@
|
||||
#ifndef DBAL_TYPES_HPP
|
||||
#define DBAL_TYPES_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <chrono>
|
||||
|
||||
namespace dbal {
|
||||
|
||||
using Timestamp = std::chrono::system_clock::time_point;
|
||||
using Json = std::map<std::string, std::string>;
|
||||
|
||||
enum class UserRole {
|
||||
User,
|
||||
Admin,
|
||||
God,
|
||||
SuperGod
|
||||
};
|
||||
|
||||
struct User {
|
||||
std::string id;
|
||||
std::string username;
|
||||
std::string email;
|
||||
UserRole role;
|
||||
Timestamp created_at;
|
||||
Timestamp updated_at;
|
||||
};
|
||||
|
||||
struct CreateUserInput {
|
||||
std::string username;
|
||||
std::string email;
|
||||
UserRole role = UserRole::User;
|
||||
};
|
||||
|
||||
struct UpdateUserInput {
|
||||
std::optional<std::string> username;
|
||||
std::optional<std::string> email;
|
||||
std::optional<UserRole> role;
|
||||
};
|
||||
|
||||
struct UpdateUserBatchItem {
|
||||
std::string id;
|
||||
UpdateUserInput data;
|
||||
};
|
||||
|
||||
struct Credential {
|
||||
std::string id;
|
||||
std::string username;
|
||||
std::string password_hash;
|
||||
bool first_login;
|
||||
Timestamp created_at;
|
||||
Timestamp updated_at;
|
||||
};
|
||||
|
||||
struct CreateCredentialInput {
|
||||
std::string username;
|
||||
std::string password_hash;
|
||||
bool first_login = true;
|
||||
};
|
||||
|
||||
struct UpdateCredentialInput {
|
||||
std::optional<std::string> password_hash;
|
||||
std::optional<bool> first_login;
|
||||
};
|
||||
|
||||
struct PageView {
|
||||
std::string id;
|
||||
std::string slug;
|
||||
std::string title;
|
||||
std::optional<std::string> description;
|
||||
int level;
|
||||
Json layout;
|
||||
bool is_active;
|
||||
Timestamp created_at;
|
||||
Timestamp updated_at;
|
||||
};
|
||||
|
||||
struct CreatePageInput {
|
||||
std::string slug;
|
||||
std::string title;
|
||||
std::optional<std::string> description;
|
||||
int level;
|
||||
Json layout;
|
||||
bool is_active = true;
|
||||
};
|
||||
|
||||
struct UpdatePageInput {
|
||||
std::optional<std::string> slug;
|
||||
std::optional<std::string> title;
|
||||
std::optional<std::string> description;
|
||||
std::optional<int> level;
|
||||
std::optional<Json> layout;
|
||||
std::optional<bool> is_active;
|
||||
};
|
||||
|
||||
struct ComponentHierarchy {
|
||||
std::string id;
|
||||
std::string page_id;
|
||||
std::optional<std::string> parent_id;
|
||||
std::string component_type;
|
||||
int order = 0;
|
||||
Json props;
|
||||
Timestamp created_at;
|
||||
Timestamp updated_at;
|
||||
};
|
||||
|
||||
struct CreateComponentHierarchyInput {
|
||||
std::string page_id;
|
||||
std::optional<std::string> parent_id;
|
||||
std::string component_type;
|
||||
int order = 0;
|
||||
Json props;
|
||||
};
|
||||
|
||||
struct UpdateComponentHierarchyInput {
|
||||
std::optional<std::string> parent_id;
|
||||
std::optional<std::string> component_type;
|
||||
std::optional<int> order;
|
||||
std::optional<Json> props;
|
||||
};
|
||||
|
||||
struct ComponentOrderUpdate {
|
||||
std::string id;
|
||||
int order = 0;
|
||||
};
|
||||
|
||||
struct MoveComponentInput {
|
||||
std::string id;
|
||||
std::string new_parent_id;
|
||||
int order = 0;
|
||||
};
|
||||
|
||||
struct Workflow {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::optional<std::string> description;
|
||||
std::string trigger;
|
||||
Json trigger_config;
|
||||
Json steps;
|
||||
bool is_active;
|
||||
std::string created_by;
|
||||
Timestamp created_at;
|
||||
Timestamp updated_at;
|
||||
};
|
||||
|
||||
struct CreateWorkflowInput {
|
||||
std::string name;
|
||||
std::optional<std::string> description;
|
||||
std::string trigger;
|
||||
Json trigger_config;
|
||||
Json steps;
|
||||
bool is_active = true;
|
||||
std::string created_by;
|
||||
};
|
||||
|
||||
struct UpdateWorkflowInput {
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> description;
|
||||
std::optional<std::string> trigger;
|
||||
std::optional<Json> trigger_config;
|
||||
std::optional<Json> steps;
|
||||
std::optional<bool> is_active;
|
||||
std::optional<std::string> created_by;
|
||||
};
|
||||
|
||||
struct Session {
|
||||
std::string id;
|
||||
std::string user_id;
|
||||
std::string token;
|
||||
Timestamp expires_at;
|
||||
Timestamp created_at;
|
||||
Timestamp last_activity;
|
||||
};
|
||||
|
||||
struct CreateSessionInput {
|
||||
std::string user_id;
|
||||
std::string token;
|
||||
Timestamp expires_at;
|
||||
};
|
||||
|
||||
struct UpdateSessionInput {
|
||||
std::optional<std::string> user_id;
|
||||
std::optional<std::string> token;
|
||||
std::optional<Timestamp> expires_at;
|
||||
std::optional<Timestamp> last_activity;
|
||||
};
|
||||
|
||||
struct LuaScript {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::optional<std::string> description;
|
||||
std::string code;
|
||||
bool is_sandboxed;
|
||||
std::vector<std::string> allowed_globals;
|
||||
int timeout_ms;
|
||||
std::string created_by;
|
||||
Timestamp created_at;
|
||||
Timestamp updated_at;
|
||||
};
|
||||
|
||||
struct CreateLuaScriptInput {
|
||||
std::string name;
|
||||
std::optional<std::string> description;
|
||||
std::string code;
|
||||
bool is_sandboxed = true;
|
||||
std::vector<std::string> allowed_globals;
|
||||
int timeout_ms = 5000;
|
||||
std::string created_by;
|
||||
};
|
||||
|
||||
struct UpdateLuaScriptInput {
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> description;
|
||||
std::optional<std::string> code;
|
||||
std::optional<bool> is_sandboxed;
|
||||
std::optional<std::vector<std::string>> allowed_globals;
|
||||
std::optional<int> timeout_ms;
|
||||
std::optional<std::string> created_by;
|
||||
};
|
||||
|
||||
struct Package {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string version;
|
||||
std::optional<std::string> description;
|
||||
std::string author;
|
||||
Json manifest;
|
||||
bool is_installed;
|
||||
std::optional<Timestamp> installed_at;
|
||||
std::optional<std::string> installed_by;
|
||||
Timestamp created_at;
|
||||
Timestamp updated_at;
|
||||
};
|
||||
|
||||
struct CreatePackageInput {
|
||||
std::string name;
|
||||
std::string version;
|
||||
std::optional<std::string> description;
|
||||
std::string author;
|
||||
Json manifest;
|
||||
bool is_installed = false;
|
||||
std::optional<Timestamp> installed_at;
|
||||
std::optional<std::string> installed_by;
|
||||
};
|
||||
|
||||
struct UpdatePackageInput {
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> version;
|
||||
std::optional<std::string> description;
|
||||
std::optional<std::string> author;
|
||||
std::optional<Json> manifest;
|
||||
std::optional<bool> is_installed;
|
||||
std::optional<Timestamp> installed_at;
|
||||
std::optional<std::string> installed_by;
|
||||
};
|
||||
|
||||
struct UpdatePackageBatchItem {
|
||||
std::string id;
|
||||
UpdatePackageInput data;
|
||||
};
|
||||
|
||||
struct ListOptions {
|
||||
std::map<std::string, std::string> filter;
|
||||
std::map<std::string, std::string> sort;
|
||||
int page = 1;
|
||||
int limit = 20;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ListResult {
|
||||
std::vector<T> data;
|
||||
int total;
|
||||
int page;
|
||||
int limit;
|
||||
bool has_more;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,3 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "dbal/core/errors.hpp"
|
||||
@@ -1,139 +0,0 @@
|
||||
#ifndef DBAL_BLOB_STORAGE_HPP
|
||||
#define DBAL_BLOB_STORAGE_HPP
|
||||
|
||||
#include "dbal/result.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
|
||||
namespace dbal {
|
||||
|
||||
struct BlobMetadata {
|
||||
std::string key;
|
||||
size_t size;
|
||||
std::string content_type;
|
||||
std::string etag;
|
||||
std::chrono::system_clock::time_point last_modified;
|
||||
std::map<std::string, std::string> custom_metadata;
|
||||
};
|
||||
|
||||
struct BlobListResult {
|
||||
std::vector<BlobMetadata> items;
|
||||
std::optional<std::string> next_token;
|
||||
bool is_truncated;
|
||||
};
|
||||
|
||||
struct UploadOptions {
|
||||
std::optional<std::string> content_type;
|
||||
std::map<std::string, std::string> metadata;
|
||||
bool overwrite = true;
|
||||
};
|
||||
|
||||
struct DownloadOptions {
|
||||
std::optional<size_t> offset;
|
||||
std::optional<size_t> length;
|
||||
};
|
||||
|
||||
struct ListOptions {
|
||||
std::optional<std::string> prefix;
|
||||
std::optional<std::string> continuation_token;
|
||||
size_t max_keys = 1000;
|
||||
};
|
||||
|
||||
// Callback for streaming uploads/downloads
|
||||
using StreamCallback = std::function<void(const char* data, size_t size)>;
|
||||
|
||||
/**
|
||||
* Abstract interface for blob storage backends
|
||||
* Supports S3, filesystem, and in-memory implementations
|
||||
*/
|
||||
class BlobStorage {
|
||||
public:
|
||||
virtual ~BlobStorage() = default;
|
||||
|
||||
/**
|
||||
* Upload data to blob storage
|
||||
*/
|
||||
virtual Result<BlobMetadata> upload(
|
||||
const std::string& key,
|
||||
const std::vector<char>& data,
|
||||
const UploadOptions& options = {}
|
||||
) = 0;
|
||||
|
||||
/**
|
||||
* Upload from stream (for large files)
|
||||
*/
|
||||
virtual Result<BlobMetadata> uploadStream(
|
||||
const std::string& key,
|
||||
StreamCallback read_callback,
|
||||
size_t size,
|
||||
const UploadOptions& options = {}
|
||||
) = 0;
|
||||
|
||||
/**
|
||||
* Download data from blob storage
|
||||
*/
|
||||
virtual Result<std::vector<char>> download(
|
||||
const std::string& key,
|
||||
const DownloadOptions& options = {}
|
||||
) = 0;
|
||||
|
||||
/**
|
||||
* Download to stream (for large files)
|
||||
*/
|
||||
virtual Result<bool> downloadStream(
|
||||
const std::string& key,
|
||||
StreamCallback write_callback,
|
||||
const DownloadOptions& options = {}
|
||||
) = 0;
|
||||
|
||||
/**
|
||||
* Delete a blob
|
||||
*/
|
||||
virtual Result<bool> deleteBlob(const std::string& key) = 0;
|
||||
|
||||
/**
|
||||
* Check if blob exists
|
||||
*/
|
||||
virtual Result<bool> exists(const std::string& key) = 0;
|
||||
|
||||
/**
|
||||
* Get blob metadata without downloading content
|
||||
*/
|
||||
virtual Result<BlobMetadata> getMetadata(const std::string& key) = 0;
|
||||
|
||||
/**
|
||||
* List blobs with optional prefix filter
|
||||
*/
|
||||
virtual Result<BlobListResult> list(const ListOptions& options = {}) = 0;
|
||||
|
||||
/**
|
||||
* Generate presigned URL for temporary access (S3 only)
|
||||
* Returns empty string for non-S3 implementations
|
||||
*/
|
||||
virtual Result<std::string> generatePresignedUrl(
|
||||
const std::string& key,
|
||||
std::chrono::seconds expiration = std::chrono::seconds(3600)
|
||||
) = 0;
|
||||
|
||||
/**
|
||||
* Copy blob to another location
|
||||
*/
|
||||
virtual Result<BlobMetadata> copy(
|
||||
const std::string& source_key,
|
||||
const std::string& dest_key
|
||||
) = 0;
|
||||
|
||||
/**
|
||||
* Get storage statistics
|
||||
*/
|
||||
virtual Result<size_t> getTotalSize() = 0;
|
||||
virtual Result<size_t> getObjectCount() = 0;
|
||||
};
|
||||
|
||||
} // namespace dbal
|
||||
|
||||
#endif // DBAL_BLOB_STORAGE_HPP
|
||||
@@ -1,3 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "dbal/core/types.hpp"
|
||||
@@ -1,588 +0,0 @@
|
||||
#ifndef DBAL_SQL_ADAPTER_HPP
|
||||
#define DBAL_SQL_ADAPTER_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "../../adapters/adapter.hpp"
|
||||
#include "../../types.hpp"
|
||||
#include "../../errors.hpp"
|
||||
#include "sql_connection.hpp"
|
||||
#include "../../runtime/requests_client.hpp"
|
||||
|
||||
namespace dbal {
|
||||
namespace adapters {
|
||||
namespace sql {
|
||||
|
||||
struct SqlParam {
|
||||
std::string name;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
struct SqlRow {
|
||||
std::map<std::string, std::string> columns;
|
||||
};
|
||||
|
||||
struct SqlError {
|
||||
enum class Code {
|
||||
UniqueViolation,
|
||||
ForeignKeyViolation,
|
||||
NotFound,
|
||||
Timeout,
|
||||
ConnectionLost,
|
||||
Unknown
|
||||
};
|
||||
|
||||
Code code;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
class SqlAdapter : public Adapter {
|
||||
public:
|
||||
explicit SqlAdapter(const SqlConnectionConfig& config, Dialect dialect)
|
||||
: pool_(config), dialect_(dialect) {}
|
||||
|
||||
~SqlAdapter() override = default;
|
||||
|
||||
Result<User> createUser(const CreateUserInput& input) override {
|
||||
auto conn = pool_.acquire();
|
||||
if (!conn) {
|
||||
return Error::internal("Unable to acquire SQL connection");
|
||||
}
|
||||
ConnectionGuard guard(pool_, conn);
|
||||
|
||||
const std::string sql = "INSERT INTO users (username, email, role) "
|
||||
"VALUES (" + placeholder(1) + ", " + placeholder(2) + ", " + placeholder(3) + ") "
|
||||
"RETURNING " + userFields();
|
||||
const std::vector<SqlParam> params = {
|
||||
{"username", input.username},
|
||||
{"email", input.email},
|
||||
{"role", userRoleToString(input.role)}
|
||||
};
|
||||
|
||||
try {
|
||||
const auto rows = executeQuery(conn, sql, params);
|
||||
if (rows.empty()) {
|
||||
return Error::internal("SQL insert returned no rows");
|
||||
}
|
||||
return mapRowToUser(rows.front());
|
||||
} catch (const SqlError& err) {
|
||||
return mapSqlError(err);
|
||||
}
|
||||
}
|
||||
|
||||
Result<User> getUser(const std::string& id) override {
|
||||
auto conn = pool_.acquire();
|
||||
if (!conn) {
|
||||
return Error::internal("Unable to acquire SQL connection");
|
||||
}
|
||||
ConnectionGuard guard(pool_, conn);
|
||||
|
||||
const std::string sql = "SELECT " + userFields() +
|
||||
" FROM users WHERE id = " + placeholder(1);
|
||||
const std::vector<SqlParam> params = {{"id", id}};
|
||||
|
||||
try {
|
||||
const auto rows = executeQuery(conn, sql, params);
|
||||
if (rows.empty()) {
|
||||
return Error::notFound("User not found");
|
||||
}
|
||||
return mapRowToUser(rows.front());
|
||||
} catch (const SqlError& err) {
|
||||
return mapSqlError(err);
|
||||
}
|
||||
}
|
||||
|
||||
Result<User> updateUser(const std::string& id, const UpdateUserInput& input) override {
|
||||
auto conn = pool_.acquire();
|
||||
if (!conn) {
|
||||
return Error::internal("Unable to acquire SQL connection");
|
||||
}
|
||||
ConnectionGuard guard(pool_, conn);
|
||||
|
||||
std::vector<std::string> setFragments;
|
||||
std::vector<SqlParam> params;
|
||||
params.reserve(4);
|
||||
params.push_back({"id", id});
|
||||
|
||||
int paramIndex = 2;
|
||||
if (input.username) {
|
||||
setFragments.push_back("username = " + placeholder(paramIndex++));
|
||||
params.push_back({"username", *input.username});
|
||||
}
|
||||
if (input.email) {
|
||||
setFragments.push_back("email = " + placeholder(paramIndex++));
|
||||
params.push_back({"email", *input.email});
|
||||
}
|
||||
if (input.role) {
|
||||
setFragments.push_back("role = " + placeholder(paramIndex++));
|
||||
params.push_back({"role", userRoleToString(*input.role)});
|
||||
}
|
||||
|
||||
if (setFragments.empty()) {
|
||||
return Error::validationError("No update fields supplied");
|
||||
}
|
||||
|
||||
const std::string sql = "UPDATE users SET " + joinFragments(setFragments, ", ") +
|
||||
" WHERE id = " + placeholder(1) +
|
||||
" RETURNING " + userFields();
|
||||
|
||||
try {
|
||||
const auto rows = executeQuery(conn, sql, params);
|
||||
if (rows.empty()) {
|
||||
return Error::notFound("User not found");
|
||||
}
|
||||
return mapRowToUser(rows.front());
|
||||
} catch (const SqlError& err) {
|
||||
return mapSqlError(err);
|
||||
}
|
||||
}
|
||||
|
||||
Result<bool> deleteUser(const std::string& id) override {
|
||||
auto conn = pool_.acquire();
|
||||
if (!conn) {
|
||||
return Error::internal("Unable to acquire SQL connection");
|
||||
}
|
||||
ConnectionGuard guard(pool_, conn);
|
||||
|
||||
const std::string sql = "DELETE FROM users WHERE id = " + placeholder(1);
|
||||
const std::vector<SqlParam> params = {{"id", id}};
|
||||
|
||||
try {
|
||||
const int affected = executeNonQuery(conn, sql, params);
|
||||
if (affected == 0) {
|
||||
return Error::notFound("User not found");
|
||||
}
|
||||
return Result<bool>(true);
|
||||
} catch (const SqlError& err) {
|
||||
return mapSqlError(err);
|
||||
}
|
||||
}
|
||||
|
||||
Result<std::vector<User>> listUsers(const ListOptions& options) override {
|
||||
auto conn = pool_.acquire();
|
||||
if (!conn) {
|
||||
return Error::internal("Unable to acquire SQL connection");
|
||||
}
|
||||
ConnectionGuard guard(pool_, conn);
|
||||
|
||||
const int limit = options.limit > 0 ? options.limit : 50;
|
||||
const int offset = options.page > 1 ? (options.page - 1) * limit : 0;
|
||||
const std::string sql = "SELECT " + userFields() +
|
||||
" FROM users ORDER BY created_at DESC LIMIT " + placeholder(1) +
|
||||
" OFFSET " + placeholder(2);
|
||||
const std::vector<SqlParam> params = {
|
||||
{"limit", std::to_string(limit)},
|
||||
{"offset", std::to_string(offset)}
|
||||
};
|
||||
|
||||
try {
|
||||
const auto rows = executeQuery(conn, sql, params);
|
||||
std::vector<User> users;
|
||||
users.reserve(rows.size());
|
||||
for (const auto& row : rows) {
|
||||
users.push_back(mapRowToUser(row));
|
||||
}
|
||||
return Result<std::vector<User>>(users);
|
||||
} catch (const SqlError& err) {
|
||||
return mapSqlError(err);
|
||||
}
|
||||
}
|
||||
|
||||
Result<PageView> createPage(const CreatePageInput& input) override {
|
||||
(void)input;
|
||||
return Error::notImplemented("SQL adapter createPage");
|
||||
}
|
||||
|
||||
Result<PageView> getPage(const std::string& id) override {
|
||||
(void)id;
|
||||
return Error::notImplemented("SQL adapter getPage");
|
||||
}
|
||||
|
||||
Result<PageView> updatePage(const std::string& id, const UpdatePageInput& input) override {
|
||||
(void)id;
|
||||
(void)input;
|
||||
return Error::notImplemented("SQL adapter updatePage");
|
||||
}
|
||||
|
||||
Result<bool> deletePage(const std::string& id) override {
|
||||
(void)id;
|
||||
return Error::notImplemented("SQL adapter deletePage");
|
||||
}
|
||||
|
||||
Result<std::vector<PageView>> listPages(const ListOptions& options) override {
|
||||
(void)options;
|
||||
return Error::notImplemented("SQL adapter listPages");
|
||||
}
|
||||
|
||||
Result<Workflow> createWorkflow(const CreateWorkflowInput& input) override {
|
||||
(void)input;
|
||||
return Error::notImplemented("SQL adapter createWorkflow");
|
||||
}
|
||||
|
||||
Result<Workflow> getWorkflow(const std::string& id) override {
|
||||
(void)id;
|
||||
return Error::notImplemented("SQL adapter getWorkflow");
|
||||
}
|
||||
|
||||
Result<Workflow> updateWorkflow(const std::string& id, const UpdateWorkflowInput& input) override {
|
||||
(void)id;
|
||||
(void)input;
|
||||
return Error::notImplemented("SQL adapter updateWorkflow");
|
||||
}
|
||||
|
||||
Result<bool> deleteWorkflow(const std::string& id) override {
|
||||
(void)id;
|
||||
return Error::notImplemented("SQL adapter deleteWorkflow");
|
||||
}
|
||||
|
||||
Result<std::vector<Workflow>> listWorkflows(const ListOptions& options) override {
|
||||
(void)options;
|
||||
return Error::notImplemented("SQL adapter listWorkflows");
|
||||
}
|
||||
|
||||
Result<Session> createSession(const CreateSessionInput& input) override {
|
||||
(void)input;
|
||||
return Error::notImplemented("SQL adapter createSession");
|
||||
}
|
||||
|
||||
Result<Session> getSession(const std::string& id) override {
|
||||
(void)id;
|
||||
return Error::notImplemented("SQL adapter getSession");
|
||||
}
|
||||
|
||||
Result<Session> updateSession(const std::string& id, const UpdateSessionInput& input) override {
|
||||
(void)id;
|
||||
(void)input;
|
||||
return Error::notImplemented("SQL adapter updateSession");
|
||||
}
|
||||
|
||||
Result<bool> deleteSession(const std::string& id) override {
|
||||
(void)id;
|
||||
return Error::notImplemented("SQL adapter deleteSession");
|
||||
}
|
||||
|
||||
Result<std::vector<Session>> listSessions(const ListOptions& options) override {
|
||||
(void)options;
|
||||
return Error::notImplemented("SQL adapter listSessions");
|
||||
}
|
||||
|
||||
Result<LuaScript> createLuaScript(const CreateLuaScriptInput& input) override {
|
||||
(void)input;
|
||||
return Error::notImplemented("SQL adapter createLuaScript");
|
||||
}
|
||||
|
||||
Result<LuaScript> getLuaScript(const std::string& id) override {
|
||||
(void)id;
|
||||
return Error::notImplemented("SQL adapter getLuaScript");
|
||||
}
|
||||
|
||||
Result<LuaScript> updateLuaScript(const std::string& id, const UpdateLuaScriptInput& input) override {
|
||||
(void)id;
|
||||
(void)input;
|
||||
return Error::notImplemented("SQL adapter updateLuaScript");
|
||||
}
|
||||
|
||||
Result<bool> deleteLuaScript(const std::string& id) override {
|
||||
(void)id;
|
||||
return Error::notImplemented("SQL adapter deleteLuaScript");
|
||||
}
|
||||
|
||||
Result<std::vector<LuaScript>> listLuaScripts(const ListOptions& options) override {
|
||||
(void)options;
|
||||
return Error::notImplemented("SQL adapter listLuaScripts");
|
||||
}
|
||||
|
||||
Result<Package> createPackage(const CreatePackageInput& input) override {
|
||||
(void)input;
|
||||
return Error::notImplemented("SQL adapter createPackage");
|
||||
}
|
||||
|
||||
Result<Package> getPackage(const std::string& id) override {
|
||||
(void)id;
|
||||
return Error::notImplemented("SQL adapter getPackage");
|
||||
}
|
||||
|
||||
Result<Package> updatePackage(const std::string& id, const UpdatePackageInput& input) override {
|
||||
(void)id;
|
||||
(void)input;
|
||||
return Error::notImplemented("SQL adapter updatePackage");
|
||||
}
|
||||
|
||||
Result<bool> deletePackage(const std::string& id) override {
|
||||
(void)id;
|
||||
return Error::notImplemented("SQL adapter deletePackage");
|
||||
}
|
||||
|
||||
Result<std::vector<Package>> listPackages(const ListOptions& options) override {
|
||||
(void)options;
|
||||
return Error::notImplemented("SQL adapter listPackages");
|
||||
}
|
||||
|
||||
Result<int> batchCreatePackages(const std::vector<CreatePackageInput>& inputs) override {
|
||||
(void)inputs;
|
||||
return Error::notImplemented("SQL adapter batchCreatePackages");
|
||||
}
|
||||
|
||||
Result<int> batchUpdatePackages(const std::vector<UpdatePackageBatchItem>& updates) override {
|
||||
(void)updates;
|
||||
return Error::notImplemented("SQL adapter batchUpdatePackages");
|
||||
}
|
||||
|
||||
Result<int> batchDeletePackages(const std::vector<std::string>& ids) override {
|
||||
(void)ids;
|
||||
return Error::notImplemented("SQL adapter batchDeletePackages");
|
||||
}
|
||||
|
||||
void close() override {
|
||||
// Connections will tear down automatically via RAII in the pool.
|
||||
}
|
||||
|
||||
protected:
|
||||
struct ConnectionGuard {
|
||||
SqlPool& pool;
|
||||
SqlConnection* connection;
|
||||
ConnectionGuard(SqlPool& pool_, SqlConnection* connection_)
|
||||
: pool(pool_), connection(connection_) {}
|
||||
~ConnectionGuard() {
|
||||
if (connection) {
|
||||
pool.release(connection);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<SqlRow> executeQuery(SqlConnection* connection,
|
||||
const std::string& sql,
|
||||
const std::vector<SqlParam>& params) {
|
||||
return runQuery(connection, sql, params);
|
||||
}
|
||||
|
||||
int executeNonQuery(SqlConnection* connection,
|
||||
const std::string& sql,
|
||||
const std::vector<SqlParam>& params) {
|
||||
return runNonQuery(connection, sql, params);
|
||||
}
|
||||
|
||||
virtual std::vector<SqlRow> runQuery(SqlConnection*,
|
||||
const std::string&,
|
||||
const std::vector<SqlParam>&) {
|
||||
throw SqlError{SqlError::Code::Unknown, "SQL execution not implemented"};
|
||||
}
|
||||
|
||||
virtual int runNonQuery(SqlConnection*,
|
||||
const std::string&,
|
||||
const std::vector<SqlParam>&) {
|
||||
throw SqlError{SqlError::Code::Unknown, "SQL execution not implemented"};
|
||||
}
|
||||
|
||||
static Error mapSqlError(const SqlError& error) {
|
||||
switch (error.code) {
|
||||
case SqlError::Code::UniqueViolation:
|
||||
return Error::conflict(error.message);
|
||||
case SqlError::Code::ForeignKeyViolation:
|
||||
return Error::validationError(error.message);
|
||||
case SqlError::Code::NotFound:
|
||||
return Error::notFound(error.message);
|
||||
case SqlError::Code::Timeout:
|
||||
case SqlError::Code::ConnectionLost:
|
||||
return Error::internal(error.message);
|
||||
default:
|
||||
return Error::internal(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
static User mapRowToUser(const SqlRow& row) {
|
||||
User user;
|
||||
user.id = columnValue(row, "id");
|
||||
user.username = columnValue(row, "username");
|
||||
user.email = columnValue(row, "email");
|
||||
user.role = parseUserRole(columnValue(row, "role"));
|
||||
user.created_at = parseTimestamp(columnValue(row, "created_at"));
|
||||
user.updated_at = parseTimestamp(columnValue(row, "updated_at"));
|
||||
return user;
|
||||
}
|
||||
|
||||
static std::string columnValue(const SqlRow& row, const std::string& key) {
|
||||
const auto itr = row.columns.find(key);
|
||||
return itr != row.columns.end() ? itr->second : "";
|
||||
}
|
||||
|
||||
static Timestamp parseTimestamp(const std::string& value) {
|
||||
if (value.empty()) {
|
||||
return std::chrono::system_clock::now();
|
||||
}
|
||||
try {
|
||||
const auto seconds = std::stoll(value);
|
||||
return Timestamp(std::chrono::seconds(seconds));
|
||||
} catch (...) {
|
||||
return std::chrono::system_clock::now();
|
||||
}
|
||||
}
|
||||
|
||||
static UserRole parseUserRole(const std::string& value) {
|
||||
auto lower = value;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
|
||||
if (lower == "admin") return UserRole::Admin;
|
||||
if (lower == "god") return UserRole::God;
|
||||
if (lower == "supergod") return UserRole::SuperGod;
|
||||
return UserRole::User;
|
||||
}
|
||||
|
||||
static std::string userRoleToString(UserRole role) {
|
||||
switch (role) {
|
||||
case UserRole::Admin:
|
||||
return "admin";
|
||||
case UserRole::God:
|
||||
return "god";
|
||||
case UserRole::SuperGod:
|
||||
return "supergod";
|
||||
default:
|
||||
return "user";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string joinFragments(const std::vector<std::string>& fragments, const std::string& separator) {
|
||||
std::ostringstream out;
|
||||
for (size_t i = 0; i < fragments.size(); ++i) {
|
||||
if (i > 0) {
|
||||
out << separator;
|
||||
}
|
||||
out << fragments[i];
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
static std::string userFields() {
|
||||
return "id, username, email, role, created_at, updated_at";
|
||||
}
|
||||
|
||||
std::string placeholder(size_t index) const {
|
||||
if (dialect_ == Dialect::Postgres || dialect_ == Dialect::Prisma) {
|
||||
return "$" + std::to_string(index);
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
SqlPool pool_;
|
||||
Dialect dialect_;
|
||||
};
|
||||
|
||||
class PostgresAdapter : public SqlAdapter {
|
||||
public:
|
||||
explicit PostgresAdapter(const SqlConnectionConfig& config)
|
||||
: SqlAdapter(config, Dialect::Postgres) {}
|
||||
};
|
||||
|
||||
class MySQLAdapter : public SqlAdapter {
|
||||
public:
|
||||
explicit MySQLAdapter(const SqlConnectionConfig& config)
|
||||
: SqlAdapter(config, Dialect::MySQL) {}
|
||||
};
|
||||
|
||||
class PrismaAdapter : public SqlAdapter {
|
||||
public:
|
||||
explicit PrismaAdapter(const SqlConnectionConfig& config)
|
||||
: SqlAdapter(config, Dialect::Prisma) {}
|
||||
};
|
||||
|
||||
class NativePrismaAdapter : public SqlAdapter {
|
||||
public:
|
||||
explicit NativePrismaAdapter(const SqlConnectionConfig& config)
|
||||
: SqlAdapter(config, Dialect::Prisma),
|
||||
requestsClient_(resolveBridgeUrl(config), buildBridgeHeaders(resolveBridgeToken(config))) {}
|
||||
|
||||
std::vector<SqlRow> runQuery(SqlConnection* connection,
|
||||
const std::string& sql,
|
||||
const std::vector<SqlParam>& params) override {
|
||||
(void)connection;
|
||||
const auto payload = buildPayload(sql, params, "query");
|
||||
const auto response = requestsClient_.post("/api/native-prisma", payload.dump());
|
||||
if (response.statusCode != 200) {
|
||||
throw SqlError{SqlError::Code::Unknown, "Native Prisma bridge request failed"};
|
||||
}
|
||||
std::vector<SqlRow> rows;
|
||||
const auto& rowsJson = response.json["rows"];
|
||||
if (!rowsJson.isNull() && rowsJson.isArray()) {
|
||||
for (const auto& entry : rowsJson) {
|
||||
SqlRow row;
|
||||
for (const auto& [key, value] : entry.items()) {
|
||||
row.columns[key] = value.isString() ? value.get<std::string>() : value.dump();
|
||||
}
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
int runNonQuery(SqlConnection* connection,
|
||||
const std::string& sql,
|
||||
const std::vector<SqlParam>& params) override {
|
||||
(void)connection;
|
||||
const auto payload = buildPayload(sql, params, "nonquery");
|
||||
const auto response = requestsClient_.post("/api/native-prisma", payload.dump());
|
||||
if (response.statusCode != 200) {
|
||||
throw SqlError{SqlError::Code::Unknown, "Native Prisma bridge request failed"};
|
||||
}
|
||||
if (response.json.isMember("affected")) {
|
||||
return response.json["affected"].asInt();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string resolveBridgeUrl(const SqlConnectionConfig& config) {
|
||||
if (!config.prisma_bridge_url.empty()) {
|
||||
return config.prisma_bridge_url;
|
||||
}
|
||||
if (const char* env_url = std::getenv("DBAL_NATIVE_PRISMA_URL")) {
|
||||
return std::string(env_url);
|
||||
}
|
||||
return "http://localhost:3000";
|
||||
}
|
||||
|
||||
static std::string resolveBridgeToken(const SqlConnectionConfig& config) {
|
||||
if (!config.prisma_bridge_token.empty()) {
|
||||
return config.prisma_bridge_token;
|
||||
}
|
||||
if (const char* env_token = std::getenv("DBAL_NATIVE_PRISMA_TOKEN")) {
|
||||
return std::string(env_token);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static std::unordered_map<std::string, std::string> buildBridgeHeaders(const std::string& token) {
|
||||
std::unordered_map<std::string, std::string> headers;
|
||||
headers["Content-Type"] = "application/json";
|
||||
if (!token.empty()) {
|
||||
headers["x-dbal-native-prisma-token"] = token;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
runtime::RequestsClient requestsClient_;
|
||||
drogon::Json::Value buildPayload(const std::string& sql,
|
||||
const std::vector<SqlParam>& params,
|
||||
const std::string& type) const {
|
||||
drogon::Json::Value payload;
|
||||
payload["sql"] = sql;
|
||||
payload["type"] = type;
|
||||
payload["params"] = drogon::Json::Value::array();
|
||||
for (const auto& param : params) {
|
||||
payload["params"].push_back(param.value);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,215 +0,0 @@
|
||||
#include "dbal/adapters/adapter.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dbal {
|
||||
namespace adapters {
|
||||
namespace sqlite {
|
||||
|
||||
class SQLiteAdapter : public Adapter {
|
||||
public:
|
||||
explicit SQLiteAdapter(const std::string& db_path) : db_path_(db_path) {}
|
||||
|
||||
~SQLiteAdapter() override {
|
||||
close();
|
||||
}
|
||||
|
||||
Result<User> createUser(const CreateUserInput& input) override {
|
||||
// Stub implementation
|
||||
User user;
|
||||
user.id = "user_" + input.username;
|
||||
user.username = input.username;
|
||||
user.email = input.email;
|
||||
user.role = input.role;
|
||||
user.created_at = std::chrono::system_clock::now();
|
||||
user.updated_at = user.created_at;
|
||||
|
||||
return Result<User>(user);
|
||||
}
|
||||
|
||||
Result<User> getUser(const std::string& id) override {
|
||||
return Error::notFound("User not found: " + id);
|
||||
}
|
||||
|
||||
Result<User> updateUser(const std::string& id, const UpdateUserInput& input) override {
|
||||
return Error::notFound("User not found: " + id);
|
||||
}
|
||||
|
||||
Result<bool> deleteUser(const std::string& id) override {
|
||||
return Result<bool>(true);
|
||||
}
|
||||
|
||||
Result<std::vector<User>> listUsers(const ListOptions& options) override {
|
||||
std::vector<User> users;
|
||||
return Result<std::vector<User>>(users);
|
||||
}
|
||||
|
||||
Result<PageView> createPage(const CreatePageInput& input) override {
|
||||
PageView page;
|
||||
page.id = "page_" + input.slug;
|
||||
page.slug = input.slug;
|
||||
page.title = input.title;
|
||||
page.description = input.description;
|
||||
page.level = input.level;
|
||||
page.layout = input.layout;
|
||||
page.is_active = input.is_active;
|
||||
page.created_at = std::chrono::system_clock::now();
|
||||
page.updated_at = page.created_at;
|
||||
|
||||
return Result<PageView>(page);
|
||||
}
|
||||
|
||||
Result<PageView> getPage(const std::string& id) override {
|
||||
return Error::notFound("Page not found: " + id);
|
||||
}
|
||||
|
||||
Result<PageView> updatePage(const std::string& id, const UpdatePageInput& input) override {
|
||||
return Error::notFound("Page not found: " + id);
|
||||
}
|
||||
|
||||
Result<bool> deletePage(const std::string& id) override {
|
||||
return Result<bool>(true);
|
||||
}
|
||||
|
||||
Result<std::vector<PageView>> listPages(const ListOptions& options) override {
|
||||
std::vector<PageView> pages;
|
||||
return Result<std::vector<PageView>>(pages);
|
||||
}
|
||||
|
||||
Result<Workflow> createWorkflow(const CreateWorkflowInput& input) override {
|
||||
Workflow workflow;
|
||||
workflow.id = "workflow_" + input.name;
|
||||
workflow.name = input.name;
|
||||
workflow.description = input.description;
|
||||
workflow.trigger = input.trigger;
|
||||
workflow.trigger_config = input.trigger_config;
|
||||
workflow.steps = input.steps;
|
||||
workflow.is_active = input.is_active;
|
||||
workflow.created_by = input.created_by;
|
||||
workflow.created_at = std::chrono::system_clock::now();
|
||||
workflow.updated_at = workflow.created_at;
|
||||
|
||||
return Result<Workflow>(workflow);
|
||||
}
|
||||
|
||||
Result<Workflow> getWorkflow(const std::string& id) override {
|
||||
return Error::notFound("Workflow not found: " + id);
|
||||
}
|
||||
|
||||
Result<Workflow> updateWorkflow(const std::string& id, const UpdateWorkflowInput& input) override {
|
||||
return Error::notFound("Workflow not found: " + id);
|
||||
}
|
||||
|
||||
Result<bool> deleteWorkflow(const std::string& id) override {
|
||||
return Result<bool>(true);
|
||||
}
|
||||
|
||||
Result<std::vector<Workflow>> listWorkflows(const ListOptions& options) override {
|
||||
std::vector<Workflow> workflows;
|
||||
return Result<std::vector<Workflow>>(workflows);
|
||||
}
|
||||
|
||||
Result<Session> createSession(const CreateSessionInput& input) override {
|
||||
Session session;
|
||||
session.id = "session_" + input.user_id;
|
||||
session.user_id = input.user_id;
|
||||
session.token = input.token;
|
||||
session.expires_at = input.expires_at;
|
||||
session.created_at = std::chrono::system_clock::now();
|
||||
session.last_activity = session.created_at;
|
||||
return Result<Session>(session);
|
||||
}
|
||||
|
||||
Result<Session> getSession(const std::string& id) override {
|
||||
return Error::notFound("Session not found: " + id);
|
||||
}
|
||||
|
||||
Result<Session> updateSession(const std::string& id, const UpdateSessionInput& input) override {
|
||||
return Error::notFound("Session not found: " + id);
|
||||
}
|
||||
|
||||
Result<bool> deleteSession(const std::string& id) override {
|
||||
return Result<bool>(true);
|
||||
}
|
||||
|
||||
Result<std::vector<Session>> listSessions(const ListOptions& options) override {
|
||||
std::vector<Session> sessions;
|
||||
return Result<std::vector<Session>>(sessions);
|
||||
}
|
||||
|
||||
Result<LuaScript> createLuaScript(const CreateLuaScriptInput& input) override {
|
||||
LuaScript script;
|
||||
script.id = "lua_" + input.name;
|
||||
script.name = input.name;
|
||||
script.description = input.description;
|
||||
script.code = input.code;
|
||||
script.is_sandboxed = input.is_sandboxed;
|
||||
script.allowed_globals = input.allowed_globals;
|
||||
script.timeout_ms = input.timeout_ms;
|
||||
script.created_by = input.created_by;
|
||||
script.created_at = std::chrono::system_clock::now();
|
||||
script.updated_at = script.created_at;
|
||||
return Result<LuaScript>(script);
|
||||
}
|
||||
|
||||
Result<LuaScript> getLuaScript(const std::string& id) override {
|
||||
return Error::notFound("Lua script not found: " + id);
|
||||
}
|
||||
|
||||
Result<LuaScript> updateLuaScript(const std::string& id, const UpdateLuaScriptInput& input) override {
|
||||
return Error::notFound("Lua script not found: " + id);
|
||||
}
|
||||
|
||||
Result<bool> deleteLuaScript(const std::string& id) override {
|
||||
return Result<bool>(true);
|
||||
}
|
||||
|
||||
Result<std::vector<LuaScript>> listLuaScripts(const ListOptions& options) override {
|
||||
std::vector<LuaScript> scripts;
|
||||
return Result<std::vector<LuaScript>>(scripts);
|
||||
}
|
||||
|
||||
Result<Package> createPackage(const CreatePackageInput& input) override {
|
||||
Package package;
|
||||
package.id = "package_" + input.name;
|
||||
package.name = input.name;
|
||||
package.version = input.version;
|
||||
package.description = input.description;
|
||||
package.author = input.author;
|
||||
package.manifest = input.manifest;
|
||||
package.is_installed = input.is_installed;
|
||||
package.installed_at = input.installed_at;
|
||||
package.installed_by = input.installed_by;
|
||||
package.created_at = std::chrono::system_clock::now();
|
||||
package.updated_at = package.created_at;
|
||||
return Result<Package>(package);
|
||||
}
|
||||
|
||||
Result<Package> getPackage(const std::string& id) override {
|
||||
return Error::notFound("Package not found: " + id);
|
||||
}
|
||||
|
||||
Result<Package> updatePackage(const std::string& id, const UpdatePackageInput& input) override {
|
||||
return Error::notFound("Package not found: " + id);
|
||||
}
|
||||
|
||||
Result<bool> deletePackage(const std::string& id) override {
|
||||
return Result<bool>(true);
|
||||
}
|
||||
|
||||
Result<std::vector<Package>> listPackages(const ListOptions& options) override {
|
||||
std::vector<Package> packages;
|
||||
return Result<std::vector<Package>>(packages);
|
||||
}
|
||||
|
||||
void close() override {
|
||||
// Cleanup
|
||||
}
|
||||
|
||||
private:
|
||||
std::string db_path_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
#include "dbal/client.hpp"
|
||||
#include "entities/index.hpp"
|
||||
#include "store/in_memory_store.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace dbal {
|
||||
|
||||
Client::Client(const ClientConfig& config) : config_(config) {
|
||||
if (config.adapter.empty()) {
|
||||
throw std::invalid_argument("Adapter type must be specified");
|
||||
}
|
||||
if (config.database_url.empty()) {
|
||||
throw std::invalid_argument("Database URL must be specified");
|
||||
}
|
||||
}
|
||||
|
||||
Client::~Client() {
|
||||
close();
|
||||
}
|
||||
|
||||
Result<User> Client::createUser(const CreateUserInput& input) {
|
||||
return entities::user::create(getStore(), input);
|
||||
}
|
||||
|
||||
Result<User> Client::getUser(const std::string& id) {
|
||||
return entities::user::get(getStore(), id);
|
||||
}
|
||||
|
||||
Result<User> Client::updateUser(const std::string& id, const UpdateUserInput& input) {
|
||||
return entities::user::update(getStore(), id, input);
|
||||
}
|
||||
|
||||
Result<bool> Client::deleteUser(const std::string& id) {
|
||||
return entities::user::remove(getStore(), id);
|
||||
}
|
||||
|
||||
Result<std::vector<User>> Client::listUsers(const ListOptions& options) {
|
||||
return entities::user::list(getStore(), options);
|
||||
}
|
||||
|
||||
Result<int> Client::batchCreateUsers(const std::vector<CreateUserInput>& inputs) {
|
||||
return entities::user::batchCreate(getStore(), inputs);
|
||||
}
|
||||
|
||||
Result<int> Client::batchUpdateUsers(const std::vector<UpdateUserBatchItem>& updates) {
|
||||
return entities::user::batchUpdate(getStore(), updates);
|
||||
}
|
||||
|
||||
Result<int> Client::batchDeleteUsers(const std::vector<std::string>& ids) {
|
||||
return entities::user::batchDelete(getStore(), ids);
|
||||
}
|
||||
|
||||
Result<std::vector<User>> Client::searchUsers(const std::string& query, int limit) {
|
||||
return entities::user::search(getStore(), query, limit);
|
||||
}
|
||||
|
||||
Result<int> Client::countUsers(const std::optional<UserRole>& role) {
|
||||
return entities::user::count(getStore(), role);
|
||||
}
|
||||
|
||||
Result<int> Client::updateManyUsers(const std::map<std::string, std::string>& filter,
|
||||
const UpdateUserInput& updates) {
|
||||
return entities::user::updateMany(getStore(), filter, updates);
|
||||
}
|
||||
|
||||
Result<int> Client::deleteManyUsers(const std::map<std::string, std::string>& filter) {
|
||||
return entities::user::deleteMany(getStore(), filter);
|
||||
}
|
||||
|
||||
Result<bool> Client::setCredential(const CreateCredentialInput& input) {
|
||||
return entities::credential::set(getStore(), input);
|
||||
}
|
||||
|
||||
Result<bool> Client::verifyCredential(const std::string& username, const std::string& password) {
|
||||
return entities::credential::verify(getStore(), username, password);
|
||||
}
|
||||
|
||||
Result<bool> Client::setCredentialFirstLoginFlag(const std::string& username, bool flag) {
|
||||
return entities::credential::setFirstLogin(getStore(), username, flag);
|
||||
}
|
||||
|
||||
Result<bool> Client::getCredentialFirstLoginFlag(const std::string& username) {
|
||||
return entities::credential::getFirstLogin(getStore(), username);
|
||||
}
|
||||
|
||||
Result<bool> Client::deleteCredential(const std::string& username) {
|
||||
return entities::credential::remove(getStore(), username);
|
||||
}
|
||||
|
||||
Result<PageView> Client::createPage(const CreatePageInput& input) {
|
||||
return entities::page::create(getStore(), input);
|
||||
}
|
||||
|
||||
Result<PageView> Client::getPage(const std::string& id) {
|
||||
return entities::page::get(getStore(), id);
|
||||
}
|
||||
|
||||
Result<PageView> Client::getPageBySlug(const std::string& slug) {
|
||||
return entities::page::getBySlug(getStore(), slug);
|
||||
}
|
||||
|
||||
Result<PageView> Client::updatePage(const std::string& id, const UpdatePageInput& input) {
|
||||
return entities::page::update(getStore(), id, input);
|
||||
}
|
||||
|
||||
Result<bool> Client::deletePage(const std::string& id) {
|
||||
return entities::page::remove(getStore(), id);
|
||||
}
|
||||
|
||||
Result<std::vector<PageView>> Client::listPages(const ListOptions& options) {
|
||||
return entities::page::list(getStore(), options);
|
||||
}
|
||||
|
||||
Result<std::vector<PageView>> Client::searchPages(const std::string& query, int limit) {
|
||||
return entities::page::search(getStore(), query, limit);
|
||||
}
|
||||
|
||||
Result<ComponentHierarchy> Client::createComponent(const CreateComponentHierarchyInput& input) {
|
||||
return entities::component::create(getStore(), input);
|
||||
}
|
||||
|
||||
Result<ComponentHierarchy> Client::getComponent(const std::string& id) {
|
||||
return entities::component::get(getStore(), id);
|
||||
}
|
||||
|
||||
Result<ComponentHierarchy> Client::updateComponent(const std::string& id, const UpdateComponentHierarchyInput& input) {
|
||||
return entities::component::update(getStore(), id, input);
|
||||
}
|
||||
|
||||
Result<bool> Client::deleteComponent(const std::string& id) {
|
||||
return entities::component::remove(getStore(), id);
|
||||
}
|
||||
|
||||
Result<std::vector<ComponentHierarchy>> Client::listComponents(const ListOptions& options) {
|
||||
return entities::component::list(getStore(), options);
|
||||
}
|
||||
|
||||
Result<std::vector<ComponentHierarchy>> Client::getComponentTree(const std::string& page_id) {
|
||||
return entities::component::getTree(getStore(), page_id);
|
||||
}
|
||||
|
||||
Result<bool> Client::reorderComponents(const std::vector<ComponentOrderUpdate>& updates) {
|
||||
return entities::component::reorder(getStore(), updates);
|
||||
}
|
||||
|
||||
Result<ComponentHierarchy> Client::moveComponent(const MoveComponentInput& input) {
|
||||
return entities::component::move(getStore(), input);
|
||||
}
|
||||
|
||||
Result<std::vector<ComponentHierarchy>> Client::searchComponents(const std::string& query,
|
||||
const std::optional<std::string>& page_id,
|
||||
int limit) {
|
||||
return entities::component::search(getStore(), query, page_id, limit);
|
||||
}
|
||||
|
||||
Result<std::vector<ComponentHierarchy>> Client::getComponentChildren(const std::string& parent_id,
|
||||
const std::optional<std::string>& component_type,
|
||||
int limit) {
|
||||
return entities::component::getChildren(getStore(), parent_id, component_type, limit);
|
||||
}
|
||||
|
||||
Result<Workflow> Client::createWorkflow(const CreateWorkflowInput& input) {
|
||||
return entities::workflow::create(getStore(), input);
|
||||
}
|
||||
|
||||
Result<Workflow> Client::getWorkflow(const std::string& id) {
|
||||
return entities::workflow::get(getStore(), id);
|
||||
}
|
||||
|
||||
Result<Workflow> Client::updateWorkflow(const std::string& id, const UpdateWorkflowInput& input) {
|
||||
return entities::workflow::update(getStore(), id, input);
|
||||
}
|
||||
|
||||
Result<bool> Client::deleteWorkflow(const std::string& id) {
|
||||
return entities::workflow::remove(getStore(), id);
|
||||
}
|
||||
|
||||
Result<std::vector<Workflow>> Client::listWorkflows(const ListOptions& options) {
|
||||
return entities::workflow::list(getStore(), options);
|
||||
}
|
||||
|
||||
Result<Session> Client::createSession(const CreateSessionInput& input) {
|
||||
return entities::session::create(getStore(), input);
|
||||
}
|
||||
|
||||
Result<Session> Client::getSession(const std::string& id) {
|
||||
return entities::session::get(getStore(), id);
|
||||
}
|
||||
|
||||
Result<Session> Client::updateSession(const std::string& id, const UpdateSessionInput& input) {
|
||||
return entities::session::update(getStore(), id, input);
|
||||
}
|
||||
|
||||
Result<bool> Client::deleteSession(const std::string& id) {
|
||||
return entities::session::remove(getStore(), id);
|
||||
}
|
||||
|
||||
Result<std::vector<Session>> Client::listSessions(const ListOptions& options) {
|
||||
return entities::session::list(getStore(), options);
|
||||
}
|
||||
|
||||
Result<LuaScript> Client::createLuaScript(const CreateLuaScriptInput& input) {
|
||||
return entities::lua_script::create(getStore(), input);
|
||||
}
|
||||
|
||||
Result<LuaScript> Client::getLuaScript(const std::string& id) {
|
||||
return entities::lua_script::get(getStore(), id);
|
||||
}
|
||||
|
||||
Result<LuaScript> Client::updateLuaScript(const std::string& id, const UpdateLuaScriptInput& input) {
|
||||
return entities::lua_script::update(getStore(), id, input);
|
||||
}
|
||||
|
||||
Result<bool> Client::deleteLuaScript(const std::string& id) {
|
||||
return entities::lua_script::remove(getStore(), id);
|
||||
}
|
||||
|
||||
Result<std::vector<LuaScript>> Client::listLuaScripts(const ListOptions& options) {
|
||||
return entities::lua_script::list(getStore(), options);
|
||||
}
|
||||
|
||||
Result<std::vector<LuaScript>> Client::searchLuaScripts(const std::string& query,
|
||||
const std::optional<std::string>& created_by,
|
||||
int limit) {
|
||||
return entities::lua_script::search(getStore(), query, created_by, limit);
|
||||
}
|
||||
|
||||
Result<Package> Client::createPackage(const CreatePackageInput& input) {
|
||||
return entities::package::create(getStore(), input);
|
||||
}
|
||||
|
||||
Result<Package> Client::getPackage(const std::string& id) {
|
||||
return entities::package::get(getStore(), id);
|
||||
}
|
||||
|
||||
Result<Package> Client::updatePackage(const std::string& id, const UpdatePackageInput& input) {
|
||||
return entities::package::update(getStore(), id, input);
|
||||
}
|
||||
|
||||
Result<bool> Client::deletePackage(const std::string& id) {
|
||||
return entities::package::remove(getStore(), id);
|
||||
}
|
||||
|
||||
Result<std::vector<Package>> Client::listPackages(const ListOptions& options) {
|
||||
return entities::package::list(getStore(), options);
|
||||
}
|
||||
|
||||
Result<int> Client::batchCreatePackages(const std::vector<CreatePackageInput>& inputs) {
|
||||
return entities::package::batchCreate(getStore(), inputs);
|
||||
}
|
||||
|
||||
Result<int> Client::batchUpdatePackages(const std::vector<UpdatePackageBatchItem>& updates) {
|
||||
return entities::package::batchUpdate(getStore(), updates);
|
||||
}
|
||||
|
||||
Result<int> Client::batchDeletePackages(const std::vector<std::string>& ids) {
|
||||
return entities::package::batchDelete(getStore(), ids);
|
||||
}
|
||||
|
||||
void Client::close() {
|
||||
// For in-memory implementation, optionally clear store.
|
||||
}
|
||||
|
||||
} // namespace dbal
|
||||
@@ -1,216 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <csignal>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
// Cross-platform signal handling
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
// Windows doesn't have SIGTERM, use SIGBREAK
|
||||
#ifndef SIGTERM
|
||||
#define SIGTERM SIGBREAK
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Include server
|
||||
#include "server.hpp"
|
||||
#include "dbal/core/client.hpp"
|
||||
|
||||
namespace {
|
||||
std::unique_ptr<dbal::daemon::Server> server_instance;
|
||||
|
||||
void signalHandler(int signal) {
|
||||
if (signal == SIGINT || signal == SIGTERM) {
|
||||
std::cout << "\nShutting down DBAL daemon..." << std::endl;
|
||||
if (server_instance) {
|
||||
server_instance->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::cout << "DBAL Daemon v1.0.0" << std::endl;
|
||||
std::cout << "Copyright (c) 2024 MetaBuilder" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Register signal handlers
|
||||
std::signal(SIGINT, signalHandler);
|
||||
std::signal(SIGTERM, signalHandler);
|
||||
|
||||
// Load defaults from environment variables (can be overridden by CLI args)
|
||||
std::string config_file = "config.yaml";
|
||||
std::string bind_address = "127.0.0.1";
|
||||
int port = 8080;
|
||||
bool development_mode = false;
|
||||
bool daemon_mode = false; // Default to interactive mode
|
||||
|
||||
// Check environment variables
|
||||
const char* env_bind = std::getenv("DBAL_BIND_ADDRESS");
|
||||
if (env_bind) bind_address = env_bind;
|
||||
|
||||
const char* env_port = std::getenv("DBAL_PORT");
|
||||
if (env_port) port = std::stoi(env_port);
|
||||
|
||||
const char* env_mode = std::getenv("DBAL_MODE");
|
||||
if (env_mode) {
|
||||
std::string mode_str = env_mode;
|
||||
development_mode = (mode_str == "development" || mode_str == "dev");
|
||||
}
|
||||
|
||||
const char* env_config = std::getenv("DBAL_CONFIG");
|
||||
if (env_config) config_file = env_config;
|
||||
|
||||
const char* env_daemon = std::getenv("DBAL_DAEMON");
|
||||
if (env_daemon) {
|
||||
std::string daemon_str = env_daemon;
|
||||
daemon_mode = (daemon_str == "true" || daemon_str == "1" || daemon_str == "yes");
|
||||
}
|
||||
|
||||
// Parse command line arguments (override environment variables)
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
if (arg == "--config" && i + 1 < argc) {
|
||||
config_file = argv[++i];
|
||||
} else if (arg == "--bind" && i + 1 < argc) {
|
||||
bind_address = argv[++i];
|
||||
} else if (arg == "--port" && i + 1 < argc) {
|
||||
port = std::stoi(argv[++i]);
|
||||
} else if (arg == "--mode" && i + 1 < argc) {
|
||||
std::string mode = argv[++i];
|
||||
development_mode = (mode == "development" || mode == "dev");
|
||||
} else if (arg == "--daemon" || arg == "-d") {
|
||||
daemon_mode = true;
|
||||
} else if (arg == "--help" || arg == "-h") {
|
||||
std::cout << "Usage: " << argv[0] << " [options]" << std::endl;
|
||||
std::cout << "Options:" << std::endl;
|
||||
std::cout << " --config <file> Configuration file (default: config.yaml)" << std::endl;
|
||||
std::cout << " --bind <address> Bind address (default: 127.0.0.1)" << std::endl;
|
||||
std::cout << " --port <port> Port number (default: 8080)" << std::endl;
|
||||
std::cout << " --mode <mode> Run mode: production, development (default: production)" << std::endl;
|
||||
std::cout << " --daemon, -d Run in daemon mode (default: interactive)" << std::endl;
|
||||
std::cout << " --help, -h Show this help message" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Environment variables (overridden by CLI args):" << std::endl;
|
||||
std::cout << " DBAL_BIND_ADDRESS Bind address" << std::endl;
|
||||
std::cout << " DBAL_PORT Port number" << std::endl;
|
||||
std::cout << " DBAL_MODE Run mode (production/development)" << std::endl;
|
||||
std::cout << " DBAL_CONFIG Configuration file path" << std::endl;
|
||||
std::cout << " DBAL_DAEMON Run in daemon mode (true/false)" << std::endl;
|
||||
std::cout << " DBAL_LOG_LEVEL Log level (trace/debug/info/warn/error/critical)" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Interactive mode (default):" << std::endl;
|
||||
std::cout << " Shows a command prompt with available commands:" << std::endl;
|
||||
std::cout << " status - Show server status" << std::endl;
|
||||
std::cout << " help - Show available commands" << std::endl;
|
||||
std::cout << " stop - Stop the server and exit" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Nginx reverse proxy example:" << std::endl;
|
||||
std::cout << " location /api/ {" << std::endl;
|
||||
std::cout << " proxy_pass http://127.0.0.1:8080/;" << std::endl;
|
||||
std::cout << " proxy_set_header X-Real-IP $remote_addr;" << std::endl;
|
||||
std::cout << " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;" << std::endl;
|
||||
std::cout << " proxy_set_header X-Forwarded-Proto $scheme;" << std::endl;
|
||||
std::cout << " proxy_set_header Host $host;" << std::endl;
|
||||
std::cout << " }" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Configuration: " << config_file << std::endl;
|
||||
std::cout << "Mode: " << (development_mode ? "development" : "production") << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
dbal::ClientConfig client_config;
|
||||
client_config.mode = development_mode ? "development" : "production";
|
||||
const char* adapter_env = std::getenv("DBAL_ADAPTER");
|
||||
client_config.adapter = adapter_env ? adapter_env : "sqlite";
|
||||
const char* database_env = std::getenv("DBAL_DATABASE_URL");
|
||||
if (!database_env) {
|
||||
database_env = std::getenv("DATABASE_URL");
|
||||
}
|
||||
client_config.database_url = database_env ? database_env : ":memory:";
|
||||
client_config.sandbox_enabled = true;
|
||||
const char* endpoint_env = std::getenv("DBAL_ENDPOINT");
|
||||
if (endpoint_env) {
|
||||
client_config.endpoint = endpoint_env;
|
||||
}
|
||||
|
||||
// Create and start HTTP server
|
||||
server_instance = std::make_unique<dbal::daemon::Server>(bind_address, port, client_config);
|
||||
|
||||
if (!server_instance->start()) {
|
||||
std::cerr << "Failed to start server" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "API endpoints:" << std::endl;
|
||||
std::cout << " GET /health - Health check" << std::endl;
|
||||
std::cout << " GET /version - Version information" << std::endl;
|
||||
std::cout << " GET /status - Server status" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
if (daemon_mode) {
|
||||
// Daemon mode: run in background until signal
|
||||
std::cout << "Daemon mode: Running in background. Press Ctrl+C to stop." << std::endl;
|
||||
|
||||
while (server_instance->isRunning()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
} else {
|
||||
// Interactive mode: show command prompt
|
||||
std::cout << "Interactive mode: Type 'help' for available commands, 'stop' to exit." << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
std::string command;
|
||||
while (server_instance->isRunning()) {
|
||||
std::cout << "dbal> ";
|
||||
std::cout.flush();
|
||||
|
||||
if (!std::getline(std::cin, command)) {
|
||||
// EOF or error, exit gracefully
|
||||
break;
|
||||
}
|
||||
|
||||
// Trim whitespace
|
||||
size_t start = command.find_first_not_of(" \t\r\n");
|
||||
size_t end = command.find_last_not_of(" \t\r\n");
|
||||
if (start == std::string::npos) {
|
||||
continue; // Empty line
|
||||
}
|
||||
command = command.substr(start, end - start + 1);
|
||||
|
||||
if (command.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (command == "help" || command == "?") {
|
||||
std::cout << "Available commands:" << std::endl;
|
||||
std::cout << " status - Show server status and statistics" << std::endl;
|
||||
std::cout << " help - Show this help message" << std::endl;
|
||||
std::cout << " stop - Stop the server and exit" << std::endl;
|
||||
std::cout << " exit - Alias for stop" << std::endl;
|
||||
std::cout << " quit - Alias for stop" << std::endl;
|
||||
} else if (command == "status") {
|
||||
std::cout << "Server status:" << std::endl;
|
||||
std::cout << " Address: " << bind_address << ":" << port << std::endl;
|
||||
std::cout << " Mode: " << (development_mode ? "development" : "production") << std::endl;
|
||||
std::cout << " Status: " << (server_instance->isRunning() ? "running" : "stopped") << std::endl;
|
||||
} else if (command == "stop" || command == "exit" || command == "quit") {
|
||||
std::cout << "Stopping server..." << std::endl;
|
||||
server_instance->stop();
|
||||
break;
|
||||
} else {
|
||||
std::cout << "Unknown command: " << command << std::endl;
|
||||
std::cout << "Type 'help' for available commands." << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Daemon stopped." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
#include "rpc_user_actions.hpp"
|
||||
#include "server_helpers.hpp"
|
||||
|
||||
#include "dbal/core/errors.hpp"
|
||||
|
||||
namespace dbal {
|
||||
namespace daemon {
|
||||
namespace rpc {
|
||||
|
||||
void handle_user_list(Client& client,
|
||||
const Json::Value& options,
|
||||
ResponseSender send_success,
|
||||
ErrorSender send_error) {
|
||||
auto list_options = list_options_from_json(options);
|
||||
auto result = client.listUsers(list_options);
|
||||
if (!result.isOk()) {
|
||||
const auto& error = result.error();
|
||||
send_error(error.what(), static_cast<int>(error.code()));
|
||||
return;
|
||||
}
|
||||
send_success(list_response_value(result.value(), list_options));
|
||||
}
|
||||
|
||||
void handle_user_read(Client& client,
|
||||
const std::string& id,
|
||||
ResponseSender send_success,
|
||||
ErrorSender send_error) {
|
||||
if (id.empty()) {
|
||||
send_error("ID is required for read operations", 400);
|
||||
return;
|
||||
}
|
||||
auto result = client.getUser(id);
|
||||
if (!result.isOk()) {
|
||||
const auto& error = result.error();
|
||||
send_error(error.what(), static_cast<int>(error.code()));
|
||||
return;
|
||||
}
|
||||
send_success(user_to_json(result.value()));
|
||||
}
|
||||
|
||||
void handle_user_create(Client& client,
|
||||
const Json::Value& payload,
|
||||
ResponseSender send_success,
|
||||
ErrorSender send_error) {
|
||||
const auto username = payload.get("username", "").asString();
|
||||
const auto email = payload.get("email", "").asString();
|
||||
if (username.empty() || email.empty()) {
|
||||
send_error("Username and email are required for creation", 400);
|
||||
return;
|
||||
}
|
||||
|
||||
CreateUserInput input;
|
||||
input.username = username;
|
||||
input.email = email;
|
||||
if (payload.isMember("role") && payload["role"].isString()) {
|
||||
input.role = normalize_role(payload["role"].asString());
|
||||
}
|
||||
|
||||
auto result = client.createUser(input);
|
||||
if (!result.isOk()) {
|
||||
const auto& error = result.error();
|
||||
send_error(error.what(), static_cast<int>(error.code()));
|
||||
return;
|
||||
}
|
||||
|
||||
send_success(user_to_json(result.value()));
|
||||
}
|
||||
|
||||
void handle_user_update(Client& client,
|
||||
const std::string& id,
|
||||
const Json::Value& payload,
|
||||
ResponseSender send_success,
|
||||
ErrorSender send_error) {
|
||||
if (id.empty()) {
|
||||
send_error("ID is required for updates", 400);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateUserInput updates;
|
||||
bool has_updates = false;
|
||||
if (payload.isMember("username") && payload["username"].isString()) {
|
||||
updates.username = payload["username"].asString();
|
||||
has_updates = true;
|
||||
}
|
||||
if (payload.isMember("email") && payload["email"].isString()) {
|
||||
updates.email = payload["email"].asString();
|
||||
has_updates = true;
|
||||
}
|
||||
if (payload.isMember("role") && payload["role"].isString()) {
|
||||
updates.role = normalize_role(payload["role"].asString());
|
||||
has_updates = true;
|
||||
}
|
||||
|
||||
if (!has_updates) {
|
||||
send_error("At least one update field must be provided", 400);
|
||||
return;
|
||||
}
|
||||
|
||||
auto result = client.updateUser(id, updates);
|
||||
if (!result.isOk()) {
|
||||
const auto& error = result.error();
|
||||
send_error(error.what(), static_cast<int>(error.code()));
|
||||
return;
|
||||
}
|
||||
|
||||
send_success(user_to_json(result.value()));
|
||||
}
|
||||
|
||||
void handle_user_delete(Client& client,
|
||||
const std::string& id,
|
||||
ResponseSender send_success,
|
||||
ErrorSender send_error) {
|
||||
if (id.empty()) {
|
||||
send_error("ID is required for delete operations", 400);
|
||||
return;
|
||||
}
|
||||
|
||||
auto result = client.deleteUser(id);
|
||||
if (!result.isOk()) {
|
||||
const auto& error = result.error();
|
||||
send_error(error.what(), static_cast<int>(error.code()));
|
||||
return;
|
||||
}
|
||||
|
||||
Json::Value body;
|
||||
body["deleted"] = result.value();
|
||||
send_success(body);
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
} // namespace daemon
|
||||
} // namespace dbal
|
||||
@@ -1,46 +0,0 @@
|
||||
#ifndef DBAL_RPC_USER_ACTIONS_HPP
|
||||
#define DBAL_RPC_USER_ACTIONS_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <json/json.h>
|
||||
|
||||
#include "dbal/core/client.hpp"
|
||||
|
||||
namespace dbal {
|
||||
namespace daemon {
|
||||
namespace rpc {
|
||||
|
||||
using ResponseSender = std::function<void(const Json::Value&)>;
|
||||
using ErrorSender = std::function<void(const std::string&, int)>;
|
||||
|
||||
void handle_user_list(Client& client,
|
||||
const Json::Value& options,
|
||||
ResponseSender send_success,
|
||||
ErrorSender send_error);
|
||||
|
||||
void handle_user_read(Client& client,
|
||||
const std::string& id,
|
||||
ResponseSender send_success,
|
||||
ErrorSender send_error);
|
||||
|
||||
void handle_user_create(Client& client,
|
||||
const Json::Value& payload,
|
||||
ResponseSender send_success,
|
||||
ErrorSender send_error);
|
||||
|
||||
void handle_user_update(Client& client,
|
||||
const std::string& id,
|
||||
const Json::Value& payload,
|
||||
ResponseSender send_success,
|
||||
ErrorSender send_error);
|
||||
|
||||
void handle_user_delete(Client& client,
|
||||
const std::string& id,
|
||||
ResponseSender send_success,
|
||||
ErrorSender send_error);
|
||||
|
||||
} // namespace rpc
|
||||
} // namespace daemon
|
||||
} // namespace dbal
|
||||
|
||||
#endif // DBAL_RPC_USER_ACTIONS_HPP
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user