Compare commits
258 Commits
next11-1-1
...
canary
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa6e96731a | ||
|
|
f59dfda878 | ||
|
|
e15c62a8d8 | ||
|
|
f97c123ddb | ||
|
|
7fd287f004 | ||
|
|
f0bbdf561d | ||
|
|
a9c6bcc63f | ||
|
|
e1fc695d24 | ||
|
|
525e436b9d | ||
|
|
a98f972bef | ||
|
|
1c2fdfee37 | ||
|
|
8102086400 | ||
|
|
0366729a95 | ||
|
|
352d2af366 | ||
|
|
9b8c4039f0 | ||
|
|
262b647855 | ||
|
|
03017046f7 | ||
|
|
28d2671ddb | ||
|
|
86da1612da | ||
|
|
919465c9b6 | ||
|
|
b84ed74496 | ||
|
|
dc86c78bd1 | ||
|
|
45b38cc836 | ||
|
|
186532ef2d | ||
|
|
77858c5558 | ||
|
|
e9a98ffe3d | ||
|
|
2c06dd6f18 | ||
|
|
881d686233 | ||
|
|
a7deaafbc7 | ||
|
|
7c698f0b35 | ||
|
|
09a8c94392 | ||
|
|
35b9ded76e | ||
|
|
85c10b9d3f | ||
|
|
2675942c58 | ||
|
|
b3ad310f6f | ||
|
|
d41385b592 | ||
|
|
dfbab3919d | ||
|
|
e5c607164e | ||
|
|
49d1e91a64 | ||
|
|
19ea640705 | ||
|
|
f2d814813f | ||
|
|
f99ebf5f37 | ||
|
|
ef2c0cdd67 | ||
|
|
d658af7407 | ||
|
|
0f8f15f31c | ||
|
|
600467035a | ||
|
|
d25aee1313 | ||
|
|
0cc2a3aab7 | ||
|
|
75e6b9fb74 | ||
|
|
6e8a65fa7f | ||
|
|
01dce6b09a | ||
|
|
ceeed729c6 | ||
|
|
7f35af5bd2 | ||
|
|
c603195004 | ||
|
|
bac35e06c8 | ||
|
|
7add9cad87 | ||
|
|
16dae9b081 | ||
|
|
9f52f36c18 | ||
|
|
621d87fdb7 | ||
|
|
871e709076 | ||
|
|
1e3ae5d445 | ||
|
|
a344c1f89e | ||
|
|
ee742530d9 | ||
|
|
f102f4c873 | ||
|
|
eebeeee6b7 | ||
|
|
18842fd56f | ||
|
|
ea1e75b355 | ||
|
|
34ed51a0db | ||
|
|
4ce4bb92bb | ||
|
|
75ed86eafb | ||
|
|
a4bcfb4737 | ||
|
|
bfd3be403e | ||
|
|
bfd75229c1 | ||
|
|
91172b82d9 | ||
|
|
c9f416713a | ||
|
|
11eb4208e6 | ||
|
|
fbd387a30c | ||
|
|
018e2405f2 | ||
|
|
c631f1ef15 | ||
|
|
3ce4f97de7 | ||
|
|
77ad2c02ce | ||
|
|
ecf5a29e19 | ||
|
|
ec8632a3e9 | ||
|
|
98f91e24c1 | ||
|
|
49c5a1e1be | ||
|
|
9a00412049 | ||
|
|
03fd49bb29 | ||
|
|
8b607b9463 | ||
|
|
e3472171a5 | ||
|
|
5f1c6a4571 | ||
|
|
d05f00c0a8 | ||
|
|
0715bb7a02 | ||
|
|
00d1ddff0b | ||
|
|
0f99de91ae | ||
|
|
9cff223033 | ||
|
|
1d6021dd36 | ||
|
|
c4df5d8d04 | ||
|
|
9019f1cf27 | ||
|
|
d92b7efe00 | ||
|
|
daea45eeb2 | ||
|
|
8dcc5ebe23 | ||
|
|
d36138361d | ||
|
|
831b0d4bcd | ||
|
|
5d9fcd22be | ||
|
|
1c51ab403b | ||
|
|
c3c9e89302 | ||
|
|
e842c9f224 | ||
|
|
abeb0c5d14 | ||
|
|
18d1a685a9 | ||
|
|
9255050eee | ||
|
|
915f5fa479 | ||
|
|
24dcac6978 | ||
|
|
adef11ba8e | ||
|
|
4233caa909 | ||
|
|
8f5cad91d4 | ||
|
|
ac5121beda | ||
|
|
5460fbb484 | ||
|
|
ad71e15290 | ||
|
|
4aba0d31f6 | ||
|
|
19ad91709e | ||
|
|
74efbad6d0 | ||
|
|
0fd6a4d5e3 | ||
|
|
1c12da5495 | ||
|
|
c4c90477c5 | ||
|
|
a9aaeec047 | ||
|
|
8420f6ddb8 | ||
|
|
3a1ea75a55 | ||
|
|
b23c2cdc36 | ||
|
|
12c081b830 | ||
|
|
11492dcfbd | ||
|
|
de874ca20e | ||
|
|
92ab9c3867 | ||
|
|
12c1b7331b | ||
|
|
c98788425d | ||
|
|
a279fb90af | ||
|
|
4f868ad2ac | ||
|
|
522da1a3d7 | ||
|
|
e767f51182 | ||
|
|
3479686f44 | ||
|
|
fafab00edc | ||
|
|
fe7a3c3a85 | ||
|
|
252c9b11fe | ||
|
|
9ddc85dbe3 | ||
|
|
ee07fc3b3f | ||
|
|
18c6981958 | ||
|
|
f85b0efc33 | ||
|
|
da8925ab51 | ||
|
|
23f85d6c27 | ||
|
|
58d566983a | ||
|
|
6b59d21e2d | ||
|
|
f2b2e5a414 | ||
|
|
862e54b453 | ||
|
|
32983cb454 | ||
|
|
47c3c947ef | ||
|
|
20e7c16237 | ||
|
|
c512b7a29e | ||
|
|
5870251839 | ||
|
|
457015ae5b | ||
|
|
a19c480c19 | ||
|
|
2f397f0e99 | ||
|
|
c081599547 | ||
|
|
e3876b387e | ||
|
|
937e4061d6 | ||
|
|
3312b4316b | ||
|
|
6653f31b27 | ||
|
|
e109813b67 | ||
|
|
4384ce0514 | ||
|
|
a1d8008cb5 | ||
|
|
919e9f637d | ||
|
|
d2f2e4b315 | ||
|
|
c91de5134e | ||
|
|
8fa479a991 | ||
|
|
6d93450ae0 | ||
|
|
c089980fca | ||
|
|
f4cf751b96 | ||
|
|
46b8103b5f | ||
|
|
9fc06a2279 | ||
|
|
731bf5451d | ||
|
|
e660b7b89d | ||
|
|
046587d025 | ||
|
|
2dd578a2db | ||
|
|
afd6e3f914 | ||
|
|
e183fb4afb | ||
|
|
aa008866cc | ||
|
|
b388a50352 | ||
|
|
fa78180768 | ||
|
|
f8e9d75afd | ||
|
|
797618e338 | ||
|
|
52d569e47e | ||
|
|
f85ae04953 | ||
|
|
2fc6c07133 | ||
|
|
feba2b6ea9 | ||
|
|
51d91c3c9d | ||
|
|
a5d3d02275 | ||
|
|
5fcd1d5b23 | ||
|
|
a808d3c120 | ||
|
|
8fc8800088 | ||
|
|
4d1c997a56 | ||
|
|
90d96552be | ||
|
|
7c4e9c81a0 | ||
|
|
40a6c7d0aa | ||
|
|
60b9f444c0 | ||
|
|
4d95e31013 | ||
|
|
eb4097be96 | ||
|
|
f1c000e8f1 | ||
|
|
13b7e6ab0b | ||
|
|
6ac61768d9 | ||
|
|
f4f6fd91a9 | ||
|
|
8f653d9eda | ||
|
|
291e8b904f | ||
|
|
6f37571dab | ||
|
|
4e6f82910b | ||
|
|
5fc4cbbdb2 | ||
|
|
dc05944074 | ||
|
|
e2490b9528 | ||
|
|
3c62425418 | ||
|
|
c29735fab8 | ||
|
|
8a5596f9b6 | ||
|
|
1457ec8635 | ||
|
|
71be602fbd | ||
|
|
6a539a30ee | ||
|
|
df2c1dd6da | ||
|
|
3d17eae8b8 | ||
|
|
3db0f77463 | ||
|
|
93c03ea36e | ||
|
|
fa7ac116f7 | ||
|
|
c2e69b5de8 | ||
|
|
f280035fa7 | ||
|
|
48f762cb7a | ||
|
|
f3f85b6831 | ||
|
|
cfdfb69c45 | ||
|
|
9e405aa8b8 | ||
|
|
853254df2c | ||
|
|
350a98da5f | ||
|
|
3473cec6d4 | ||
|
|
baeefff47a | ||
|
|
bdc560a1d4 | ||
|
|
5b4413dd87 | ||
|
|
cf448a801e | ||
|
|
c4f6f78dde | ||
|
|
9e9c63d896 | ||
|
|
e45822407f | ||
|
|
ac4f9f3702 | ||
|
|
b6adb749ce | ||
|
|
ea566b2c3a | ||
|
|
87eefafb22 | ||
|
|
6056ed85d9 | ||
|
|
8fef7b7407 | ||
|
|
25cdbbbf53 | ||
|
|
db2c28d014 | ||
|
|
39e089bac2 | ||
|
|
17853ee542 | ||
|
|
9dd420c313 | ||
|
|
4d930541d9 | ||
|
|
eaf4862915 | ||
|
|
3bcfda462d | ||
|
|
ef362299c6 | ||
|
|
d64cf5323a |
@@ -309,7 +309,8 @@
|
||||
"profile": "https://github.com/Zeko369",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
"doc",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -625,7 +626,8 @@
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/17050715?v=4",
|
||||
"profile": "https://cloudnweb.dev/",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -970,7 +972,9 @@
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/3496193?v=4",
|
||||
"profile": "https://twitter.com/dillonraphael",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"test",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1547,7 +1551,8 @@
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/244174?v=4",
|
||||
"profile": "https://github.com/piotrski",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2005,7 +2010,8 @@
|
||||
"profile": "http://aleksandra.codes",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
"doc",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2962,7 +2968,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/12092?v=4",
|
||||
"profile": "http://zackhobson.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3063,7 +3070,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/8077469?v=4",
|
||||
"profile": "https://andreas.fyi",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3175,7 +3183,9 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/11151445?v=4",
|
||||
"profile": "g3offrey.dev",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3186,6 +3196,655 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kimngan-bui",
|
||||
"name": "kimngan-bui",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/20723478?v=4",
|
||||
"profile": "https://github.com/kimngan-bui",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "9j",
|
||||
"name": "Bahk Chanhee",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/11691670?v=4",
|
||||
"profile": "world.hey.com/bach",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Vandivier",
|
||||
"name": "John Vandivier",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5559355?v=4",
|
||||
"profile": "http://www.afterecon.com/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"test",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "namirsab",
|
||||
"name": "Namir",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6980777?v=4",
|
||||
"profile": "http://namirsab.github.io",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "scttcper",
|
||||
"name": "Scott Cooper",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1400464?v=4",
|
||||
"profile": "https://twitter.com/scttcper",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Abduttayyeb",
|
||||
"name": "Abduttayyeb M.r",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/55306260?v=4",
|
||||
"profile": "abduttayyeb.github.io",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "maybebored",
|
||||
"name": "Mayuran",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/20951181?v=4",
|
||||
"profile": "https://github.com/maybebored",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MuckHub",
|
||||
"name": "Aleksei Vesselko",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/54979136?v=4",
|
||||
"profile": "https://github.com/MuckHub",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "p-siriphanthong",
|
||||
"name": "Punn Siriphanthong",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/29949429?v=4",
|
||||
"profile": "https://p-siriphanthong.github.io/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "shawn-fetanat",
|
||||
"name": "Shawn Fetanat",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/83679827?v=4",
|
||||
"profile": "https://my-portfolio-292eb.web.app",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mochi-sann",
|
||||
"name": "Moyuru",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/44772513?v=4",
|
||||
"profile": "https://github.com/mochi-sann",
|
||||
"contributions": [
|
||||
"code",
|
||||
"test",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "camsloanftc",
|
||||
"name": "Cam Sloan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/16295659?v=4",
|
||||
"profile": "https://github.com/camsloanftc",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sitek94",
|
||||
"name": "Maciek Sitkowski",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/58401630?v=4",
|
||||
"profile": "https://macieksitkowski.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "vivek7405",
|
||||
"name": "Vivek",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/24492244?v=4",
|
||||
"profile": "https://github.com/vivek7405",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "cj",
|
||||
"name": "CJ Lazell",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1819?v=4",
|
||||
"profile": "http://cj.io",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "RobertBroersma",
|
||||
"name": "Robert",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4519828?v=4",
|
||||
"profile": "robertbroersma.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "cbejensen",
|
||||
"name": "Christian Jensen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/12374723?v=4",
|
||||
"profile": "https://christianjensen.netlify.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dvnrsn",
|
||||
"name": "Devin Rasmussen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/9112811?v=4",
|
||||
"profile": "https://github.com/dvnrsn",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "devtombiz",
|
||||
"name": "Thomas Brenneur",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/23282613?v=4",
|
||||
"profile": "www.linkedin.com/in/devtom",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lucasvazq",
|
||||
"name": "Lucas Vazquez",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/38964964?v=4",
|
||||
"profile": "https://lucasvazq.github.io/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chrisj-back2work",
|
||||
"name": "Chris Johnson",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/68551954?v=4",
|
||||
"profile": "https://github.com/chrisj-back2work",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "thisdotrob",
|
||||
"name": "Rob Stevenson",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/12902589?v=4",
|
||||
"profile": "https://github.com/thisdotrob",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lovethebomb",
|
||||
"name": "Lucas Heymès",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1363056?v=4",
|
||||
"profile": "www.lucas.computer",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "NorfeldtAbtion",
|
||||
"name": "Lasse Norfeldt",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/53769763?v=4",
|
||||
"profile": "https://github.com/NorfeldtAbtion",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "netwarex",
|
||||
"name": "Péter Nyári",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6048614?v=4",
|
||||
"profile": "https://nyaripeter.hu/",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "5minpause",
|
||||
"name": "Holger Frohloff",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/84148?v=4",
|
||||
"profile": "https://www.holgerfrohloff.de",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "basilk76",
|
||||
"name": "Basil Khan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/45275512?v=4",
|
||||
"profile": "https://github.com/basilk76",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "danestves",
|
||||
"name": "Daniel Esteves",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/31737273?v=4",
|
||||
"profile": "https://danestves.com/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "coryhouse",
|
||||
"name": "Cory House",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1688997?v=4",
|
||||
"profile": "http://www.bitnative.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rockmanvnx6",
|
||||
"name": "Austin (Thang Pham)",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/16440123?v=4",
|
||||
"profile": "https://auspham.dev/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "noxify",
|
||||
"name": "Marcus Reinhardt",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/521777?v=4",
|
||||
"profile": "https://jammeryhq.com",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "davidchristie",
|
||||
"name": "David Christie",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/12044333?v=4",
|
||||
"profile": "https://github.com/davidchristie",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ajanth97",
|
||||
"name": "Ajanth",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/50458502?v=4",
|
||||
"profile": "https://github.com/ajanth97",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "divpreet",
|
||||
"name": "Div",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2805650?v=4",
|
||||
"profile": "https://github.com/divpreet",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "david-arteaga",
|
||||
"name": "David Arteaga",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7199015?v=4",
|
||||
"profile": "https://github.com/david-arteaga",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MukulKolpe",
|
||||
"name": "Mukul Kolpe",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/78664749?v=4",
|
||||
"profile": "https://github.com/MukulKolpe",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "skotchpine",
|
||||
"name": "tyler",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/13043909?v=4",
|
||||
"profile": "https://github.com/skotchpine",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "SofianeDjellouli",
|
||||
"name": "Sofiane Djellouli",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/38258952?v=4",
|
||||
"profile": "https://github.com/SofianeDjellouli",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kreako",
|
||||
"name": "kreako",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/65113001?v=4",
|
||||
"profile": "https://github.com/kreako",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sarahdayan",
|
||||
"name": "Sarah Dayan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5370675?v=4",
|
||||
"profile": "https://sarahdayan.dev",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "c-ciobanu",
|
||||
"name": "Cristi Ciobanu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/33382714?v=4",
|
||||
"profile": "https://github.com/c-ciobanu",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "arpitdalal",
|
||||
"name": "Arpit Dalal",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/61059807?v=4",
|
||||
"profile": "https://arpitdalal.dev",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "robertrisch",
|
||||
"name": "robertrisch",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/73828816?v=4",
|
||||
"profile": "https://github.com/robertrisch",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dineshgadge",
|
||||
"name": "Dinesh Gadge",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/186976?v=4",
|
||||
"profile": "https://github.com/dineshgadge",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "maltekiessling",
|
||||
"name": "Malte Kießling",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/30420110?v=4",
|
||||
"profile": "https://github.com/maltekiessling",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ospfranco",
|
||||
"name": "Oscar Franco",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1634213?v=4",
|
||||
"profile": "ospfranco.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Nfinished",
|
||||
"name": "Adam Trager",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1719791?v=4",
|
||||
"profile": "adamtrager.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "shellord",
|
||||
"name": "saheenshoukath",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2632896?v=4",
|
||||
"profile": "https://saheen.codes",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "husnuljahneer",
|
||||
"name": "Husnul Jahneer",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/54552763?v=4",
|
||||
"profile": "https://jahneer.me",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ReykCS",
|
||||
"name": "Reyk",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/40463716?v=4",
|
||||
"profile": "https://github.com/ReykCS",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Lokprakash-babu",
|
||||
"name": "Lokprakash Babu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/60031382?v=4",
|
||||
"profile": "https://github.com/Lokprakash-babu",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "eai04191",
|
||||
"name": "eai04191",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3516343?v=4",
|
||||
"profile": "https://mizle.net",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "numanaral",
|
||||
"name": "Numan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/25233323?v=4",
|
||||
"profile": "https://numanaral.github.io/?ref=github",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jscyo",
|
||||
"name": "Joel Coutinho",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6310783?v=4",
|
||||
"profile": "https://github.com/jscyo",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "davidbarker",
|
||||
"name": "David Barker",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1597139?v=4",
|
||||
"profile": "https://github.com/davidbarker",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "timfee",
|
||||
"name": "Tim Feeley",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3246342?v=4",
|
||||
"profile": "http://www.timfeeley.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Caslus",
|
||||
"name": "lucas philippe",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22855640?v=4",
|
||||
"profile": "https://github.com/Caslus",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "the-bayer",
|
||||
"name": "Blake Bayer",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/94391693?v=4",
|
||||
"profile": "https://github.com/the-bayer",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rmassie",
|
||||
"name": "R Massie",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7375518?v=4",
|
||||
"profile": "https://github.com/rmassie",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "paulm17",
|
||||
"name": "Paul",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/387463?v=4",
|
||||
"profile": "https://github.com/paulm17",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "minho42",
|
||||
"name": "Min ho Kim",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/15278512?v=4",
|
||||
"profile": "https://minho42.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "webdeb",
|
||||
"name": "webdeb",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/14992140?v=4",
|
||||
"profile": "https://github.com/webdeb",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "iDavidB",
|
||||
"name": "David",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/32268383?v=4",
|
||||
"profile": "https://github.com/iDavidB",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jakedee",
|
||||
"name": "Jake Dowie",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5058625?v=4",
|
||||
"profile": "https://jdlt.co.uk",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "datner",
|
||||
"name": "Datner",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22598347?v=4",
|
||||
"profile": "https://github.com/datner",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "remlse",
|
||||
"name": "remlse",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/54984957?v=4",
|
||||
"profile": "https://github.com/remlse",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sergous",
|
||||
"name": "Sergei Smirnov",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/545151?v=4",
|
||||
"profile": "https://github.com/sergous",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Trancever",
|
||||
"name": "Dawid Urbaniak",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/18584155?v=4",
|
||||
"profile": "https://twitter.com/trensik",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "SerekKiri",
|
||||
"name": "Kacper Potyrała",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/29735836?v=4",
|
||||
"profile": "kiri.dev",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "iojcde",
|
||||
"name": "Jeeho Ahn",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/31413538?v=4",
|
||||
"profile": "jcde.xyz",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"tool"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
4
.github/CODEOWNERS
vendored
@@ -1,8 +1,8 @@
|
||||
# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
|
||||
|
||||
* @flybayer @beerose
|
||||
* @beerose
|
||||
|
||||
# packages/cli/**/* @aem, @flybayer
|
||||
# packages/generator/**/* @aem @flybayer
|
||||
packages/generator/templates**/* @flybayer
|
||||
# packages/generator/templates**/* @flybayer
|
||||
# packages/installer/**/* @aem @flybayer
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -16,4 +16,4 @@ Closes: ?
|
||||
## Feature Checklist
|
||||
|
||||
- [ ] Integration test added (see [test docs](https://blitzjs.com/docs/contributing#running-tests) if needed)
|
||||
- [ ] Documentation added/updated (submit PR to [blitzjs.com repo](https://github.com/blitz-js/blitzjs.com))
|
||||
- [ ] Documentation added/updated (submit PR to [blitzjs.com repo `canary` branch](https://github.com/blitz-js/blitzjs.com/tree/canary))
|
||||
|
||||
4
.github/workflows/compressed.yml
vendored
@@ -15,6 +15,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- name: Count size
|
||||
uses: preactjs/compressed-size-action@v2
|
||||
with:
|
||||
|
||||
65
.github/workflows/main.yml
vendored
@@ -30,9 +30,9 @@ jobs:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }}
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v13-
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v14-
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --silent
|
||||
env:
|
||||
@@ -74,9 +74,9 @@ jobs:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }}
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v13-
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v14-
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- name: Build Packages
|
||||
run: yarn build
|
||||
@@ -98,6 +98,10 @@ jobs:
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- name: Setup kernel to increase watchers
|
||||
if: runner.os == 'Linux'
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
@@ -106,6 +110,33 @@ jobs:
|
||||
env:
|
||||
CI: true
|
||||
|
||||
testNextPackages:
|
||||
name: Next - Test Packages
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nextjs
|
||||
needs: build-linux
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- name: Setup kernel to increase watchers
|
||||
if: runner.os == 'Linux'
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Test Next Packages
|
||||
run: yarn testonly:packages
|
||||
env:
|
||||
CI: true
|
||||
|
||||
testBlitzExamples:
|
||||
timeout-minutes: 30
|
||||
name: Blitz - Test Example Apps (ubuntu-latest)
|
||||
@@ -120,6 +151,10 @@ jobs:
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
# Needed to get cypress binary
|
||||
- run: yarn cypress install
|
||||
- name: Install sass
|
||||
@@ -157,9 +192,9 @@ jobs:
|
||||
# path: |
|
||||
# ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
# **/node_modules
|
||||
# key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }}
|
||||
# key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-
|
||||
# ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- name: Build Packages
|
||||
run: yarn build
|
||||
@@ -222,6 +257,10 @@ jobs:
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
|
||||
# TODO: remove after we fix watchpack watching too much
|
||||
- name: Setup kernel to increase watchers
|
||||
@@ -256,9 +295,9 @@ jobs:
|
||||
# path: |
|
||||
# ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
# **/node_modules
|
||||
# key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }}
|
||||
# key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-
|
||||
# ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- name: Build Packages
|
||||
run: yarn build
|
||||
@@ -285,7 +324,10 @@ jobs:
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
# TODO: remove after we fix watchpack watching too much
|
||||
- name: Setup kernel to increase watchers
|
||||
if: runner.os == 'Linux'
|
||||
@@ -318,9 +360,9 @@ jobs:
|
||||
# path: |
|
||||
# ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
# **/node_modules
|
||||
# key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }}
|
||||
# key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-
|
||||
# ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- name: Build Packages
|
||||
run: yarn build
|
||||
@@ -363,6 +405,7 @@ jobs:
|
||||
testIntegrationBlitzWin,
|
||||
testUnit,
|
||||
testBlitzPackages,
|
||||
testNextPackages,
|
||||
testBlitzExamples,
|
||||
testBlitzExamplesWin,
|
||||
]
|
||||
|
||||
@@ -1 +1 @@
|
||||
12.20.0
|
||||
14.18.1
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
|
||||
## Notes For Core Team
|
||||
|
||||
### To Publish a new NPM Package under `@blitzjs/` namespace
|
||||
|
||||
1. cd into the package directory
|
||||
2. Run `npm publish --tag danger --access public`
|
||||
- `--access public` is required because scoped packages are set to private by default
|
||||
|
||||
### Syncing Next.js Fork
|
||||
|
||||
1. Run `yarn push-nextjs`
|
||||
|
||||
181
README.md
@@ -6,7 +6,7 @@
|
||||
<img alt="" src="https://img.shields.io/badge/Join%20our%20community-6700EB.svg?style=for-the-badge&labelColor=000000&logoWidth=20&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ9SURBVHgB7d3dVdtAEIbhcSpICUoH0IEogQqSVBBSAU4FSSpIOoAORAfQgSghHXzZ1U/YcMD4R9rZmf2ec3y448LyiNf27iLiGIAmPLrweC9Un3DhrzG6EarLNP09nlwJ1SOZ/lQr5N80/S/p2QMVCBf5N17XCfm1Y/rBHqjAG9PPHvBsz+mf9WAP+HLA9M/YA14cOP2payH7jpj+VCtk1wnTP+vj7xCy6cTpn7EHLMLp059iD1iD8eveJbVCNsSLheX1YA/YgOWnf8YeKB3Wmf7Ud6Fy4f/FHmtpxbl3YlC4MJ/Cj0bWdwPnPbARg+L0S54XQHS32WwuxClzd4CM0z9rPfeAuTtA5ulPXYQ7wZ04Y+oOoDD9KZc9YOoOoDj9s4dwFzgXR6w1wIPoOvPWA9buAHEJ173o3gWiy3AnuBUHLEbgmYwvAk1/wuM8vAgexThzbwPDkx7/DHwVXfFOxP2GmsKd4Ab6zPeAyU8CI7AHFmH2BRCBPXAyk18GzUrqAXCTiR4ssyj0VFw/oCU8+e+RZ33AWz6KMaYbIIWxB+JSLs1bsbkeMN0AqakHvoku9oA2sAfqBvbAQdw0QArsgb25aYBUQT3QgT2gB+yBuqGcHij2UCqXDZACe2Anlw2QYg/QAOyBuoE98CL3DZDCuK4/rh/Q7oGL6U+TOvcNkJoijN8X1C48+T+g75eQDrAH/qmqAVJgDwyqaoAUe4AGYA/UDZX3QLUNkEIZPRCd5+6BahsgVUgPROwBTSijB7jpVAvGHriHvmw9wAZ4BpX1ABvgmakHtPcbRuwBTWAPULgAV9D/jKDY9YRvwvgEaurD44uQHvAol7qBW7WKluVtIHiUS7GyvA0s6CiXDnxrpQfsgbqBS7GKk/2jYHCrVlGyfxTMrVo0ALdq1Q3sgSKofh0M9oA61a+D2QM0AHugbmAPqClmSRjK2apVVQ8UsySsoK1aHdgDesCtWnUDeyCrIpeFg1u3sylyWTi3btMA7IG6gT2wuuK3hoE9sKrit4YVslWLPaAN7IG6ocKt2zmY2h4O9sDiTG0PZw/QANy6XTewBxZj9ogYVHy025LMHhEz9cBn0We6B0yfERReBLfhx0/R1YQHPx/QBPbA0VwcEwf2wNFcHBPHHjiem3MC2QPHcXdSaJjA+KfgTPQ8hhfjBzHC40mhlzJ+Xq9lK4a4PCs43AVaGTed5mZq+iOXZwWHi3AnOj2wFWNcnxYe7gTxLtBKHuamP/J+Wnh8a5irB7ZC5Yk9gPX1QuXC+usHWqGyhYvUYR0a7zboUOFCNVhnk0krZAOW7wFOvzXhom2xnEbIHizTA1wEYhWW6YFGyC6c1gOcfg9wfA80Qj7g8B7g9HuCww+haIR8wf49wOn3Cvv9k8tGyC/s7gFOv3fY3QONkH+v9MBWqB7PeqDn9FcIT//kcitUn6kHOu/T/xfWzlQy3dEHhwAAAABJRU5ErkJggg==">
|
||||
</a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-338-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
|
||||
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-408-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
<a aria-label="License" href="https://github.com/blitz-js/blitz/blob/canary/LICENSE">
|
||||
<img alt="" src="https://img.shields.io/npm/l/blitz.svg?style=for-the-badge&labelColor=000000&color=blue">
|
||||
@@ -107,18 +107,21 @@ Your financial contributions help ensure Blitz continues to be developed and mai
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><a aria-label="Andreas Asprou" href="https://andreas.fyi">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/andreas.jpg" width="40px"/>
|
||||
</a></td>
|
||||
<td><a aria-label="Robert Malko" href="https://github.com/malkomalko">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/rob_blitz.jpg" width="40px"/>
|
||||
</a></td>
|
||||
<td><a aria-label="Digas" href="https://digsas.com">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/digsas.svg" width="40px"/>
|
||||
</a></td>
|
||||
<td><a aria-label="userTrack" href="https://www.usertrack.net/?ref=blitzjs">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/usertrack.png" width="40px"/>
|
||||
</a></td>
|
||||
<td>
|
||||
<a aria-label="Andreas Asprou" href="https://andreas.fyi">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/andreas.jpg" width="40px"/>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a aria-label="MeetKai" href="https://meetkai.com/?ref=blitzjs">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/meetkai.png" width="40px"/>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a aria-label="JDLT" href="https://jdlt.co.uk/?ref=blitzjs">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/jdlt.png" width="40px"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -149,11 +152,6 @@ Your financial contributions help ensure Blitz continues to be developed and mai
|
||||
<a aria-label="Fauna" href="https://dashboard.fauna.com/accounts/register?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2020">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/Fauna_Logo_Blue.png" width="300px">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a aria-label="GraphCMS" href="https://graphcms.com/?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2021">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/graphcms.png" width="300px">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -215,21 +213,6 @@ _Issue triage, pull request triage, community encouragement and moderation, etc_
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="http://jeremyliberman.com/"><img src="https://avatars3.githubusercontent.com/u/2754163?v=4" width="100px;" alt=""/><br /><sub><b>Jeremy Liberman</b></td>
|
||||
<td align="center"><a href="https://github.com/engelkes-finstreet"><img src="https://avatars0.githubusercontent.com/u/36962022?s=460&u=34cfc4a3d6da0a87026f6068c371779c68daa3a2&v=4" width="100px;" alt=""/><br /><sub><b>Patrick Engelkes</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/myrondavis"><img src="https://avatars2.githubusercontent.com/u/1430136?v=4" width="100px;" alt=""/><br /><sub><b>Myron Davis</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/NaReto1125_"><img src="https://avatars.githubusercontent.com/reo777" width="100px;" alt=""/><br /><sub><b>Reo Ishiyama</b></sub></a></td>
|
||||
<td align="center">
|
||||
<a href="https://kevinlangleyjr.com">
|
||||
<img
|
||||
src="https://avatars.githubusercontent.com/u/877634?v=4"
|
||||
width="100px;"
|
||||
alt=""
|
||||
/><br />
|
||||
<sub>
|
||||
<b>Kevin Langley Jr.</b>
|
||||
</sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://mina.ca">
|
||||
<img src="https://avatars.githubusercontent.com/mabadir" width="100px;" alt="Mina Abadir avatar" /><br />
|
||||
@@ -238,8 +221,6 @@ _Issue triage, pull request triage, community encouragement and moderation, etc_
|
||||
</sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://builtforfifty.com">
|
||||
<img src="https://avatars.githubusercontent.com/abuuzayr" width="100px;" alt="Abu Uzayr avatar" /><br />
|
||||
@@ -248,6 +229,30 @@ _Issue triage, pull request triage, community encouragement and moderation, etc_
|
||||
</sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://damilolarandolph.com/">
|
||||
<img src="https://avatars.githubusercontent.com/damilolarandolph" width="100px;" alt="Damilola Randolph avatar" /><br />
|
||||
<sub>
|
||||
<b>Damilola Randolph</b>
|
||||
</sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://www.saheen.me/">
|
||||
<img src="https://avatars.githubusercontent.com/shellord" width="100px;" alt="Saheen Shoukath avatar" /><br />
|
||||
<sub>
|
||||
<b>Saheen Shoukath</b>
|
||||
</sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://jahneer.me">
|
||||
<img src="https://avatars.githubusercontent.com/husnuljahneer" width="100px;" alt="Husnul Jahneer avatar" /><br />
|
||||
<sub>
|
||||
<b>Husnul Jahneer</b>
|
||||
</sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
@@ -302,7 +307,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/sonnypgs"><img src="https://avatars3.githubusercontent.com/u/1431300?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sonny</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sonnypgs" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Zeko369"><img src="https://avatars3.githubusercontent.com/u/3064377?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fran Zekan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Zeko369" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Zeko369" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Zeko369"><img src="https://avatars3.githubusercontent.com/u/3064377?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fran Zekan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Zeko369" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Zeko369" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Zeko369" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="http://twitter.com/JanBaykara"><img src="https://avatars2.githubusercontent.com/u/237556?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jan Baykara</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=janbaykara" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mikeattara.com"><img src="https://avatars1.githubusercontent.com/u/31483629?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mike Perry Y Attara</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mikeattara" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://devanthe.dev"><img src="https://avatars0.githubusercontent.com/u/354652?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Devan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=DevanB" title="Documentation">📖</a></td>
|
||||
@@ -343,7 +348,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<td align="center"><a href="http://anteprimorac.com.hr"><img src="https://avatars0.githubusercontent.com/u/972083?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ante Primorac</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=anteprimorac" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=anteprimorac" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://mykalmachon.dev"><img src="https://avatars1.githubusercontent.com/u/7844994?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mykal Machon</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MykalMachon" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://jamiedavenport.dev"><img src="https://avatars2.githubusercontent.com/u/1329874?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jamie Davenport</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jamiedavenport" title="Code">💻</a> <a href="#maintenance-jamiedavenport" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://cloudnweb.dev/"><img src="https://avatars0.githubusercontent.com/u/17050715?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GaneshMani</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ganeshmani" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://cloudnweb.dev/"><img src="https://avatars0.githubusercontent.com/u/17050715?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GaneshMani</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ganeshmani" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=ganeshmani" title="Tests">⚠️</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://ramonmorcillo.com"><img src="https://avatars3.githubusercontent.com/u/31936665?v=4?s=100" width="100px;" alt=""/><br /><sub><b>reymon359</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=reymon359" title="Code">💻</a></td>
|
||||
@@ -392,7 +397,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/jschepmans"><img src="https://avatars2.githubusercontent.com/u/5782977?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Johan Schepmans</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jschepmans" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://twitter.com/dillonraphael"><img src="https://avatars0.githubusercontent.com/u/3496193?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dillon Raphael</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dillonraphael" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://twitter.com/dillonraphael"><img src="https://avatars0.githubusercontent.com/u/3496193?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dillon Raphael</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dillonraphael" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=dillonraphael" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=dillonraphael" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/clgeoio"><img src="https://avatars2.githubusercontent.com/u/37571416?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cody G</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/madflow"><img src="https://avatars0.githubusercontent.com/u/183248?v=4?s=100" width="100px;" alt=""/><br /><sub><b>madflow</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=madflow" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Code">💻</a> <a href="#maintenance-nitaking" title="Maintenance">🚧</a> <a href="#question-nitaking" title="Answering Questions">💬</a> <a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Documentation">📖</a></td>
|
||||
@@ -472,7 +477,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<td align="center"><a href="https://chriswray.dev/"><img src="https://avatars0.githubusercontent.com/u/53663762?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christopher Wray</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cwray-tech" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/piotrski"><img src="https://avatars0.githubusercontent.com/u/244174?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Piotrek Tomczewski</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=piotrski" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/piotrski"><img src="https://avatars0.githubusercontent.com/u/244174?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Piotrek Tomczewski</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=piotrski" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=piotrski" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://raph.site"><img src="https://avatars3.githubusercontent.com/u/1575946?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Raphaël Huchet</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kattcorp.com"><img src="https://avatars1.githubusercontent.com/u/459267?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alex Johansson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=KATT" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://davidmazza.com"><img src="https://avatars0.githubusercontent.com/u/120893?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Mazza</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dmzza" title="Code">💻</a></td>
|
||||
@@ -535,7 +540,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<td align="center"><a href="https://brianypliu.com"><img src="https://avatars.githubusercontent.com/u/3888780?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brian Liu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=LBrian" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://aleksandra.codes"><img src="https://avatars.githubusercontent.com/u/9019397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aleksandra Sikora</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=beerose" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=beerose" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://aleksandra.codes"><img src="https://avatars.githubusercontent.com/u/9019397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aleksandra Sikora</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=beerose" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=beerose" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=beerose" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://juanm04.com"><img src="https://avatars.githubusercontent.com/u/16712703?v=4?s=100" width="100px;" alt=""/><br /><sub><b>JuanM04</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=JuanM04" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=JuanM04" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=JuanM04" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/arenddeboer"><img src="https://avatars.githubusercontent.com/u/7022204?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Arend de Boer</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=arenddeboer" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/fmilani"><img src="https://avatars.githubusercontent.com/u/1580375?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Felipe Milani</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fmilani" title="Documentation">📖</a></td>
|
||||
@@ -666,7 +671,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<td align="center"><a href="https://github.com/bravo-kernel"><img src="https://avatars.githubusercontent.com/u/230500?v=4?s=100" width="100px;" alt=""/><br /><sub><b>bravo-kernel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=bravo-kernel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://samholmes.net"><img src="https://avatars.githubusercontent.com/u/8385528?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sam Holmes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sam3d" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://doncicuto.medium.com"><img src="https://avatars.githubusercontent.com/u/30386061?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Miguel Cabrerizo</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=doncicuto" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=doncicuto" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://zackhobson.com/"><img src="https://avatars.githubusercontent.com/u/12092?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zack Hobson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=zenhob" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://zackhobson.com/"><img src="https://avatars.githubusercontent.com/u/12092?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zack Hobson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=zenhob" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=zenhob" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.mokhtar.dev"><img src="https://avatars.githubusercontent.com/u/13026820?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mokhtar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=m5r" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -681,7 +686,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/ratson"><img src="https://avatars.githubusercontent.com/u/2682937?v=4?s=100" width="100px;" alt=""/><br /><sub><b>(◕ᴥ◕)</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ratson" title="Code">💻</a></td>
|
||||
<td align="center"><a href="maciejmyslinski.com"><img src="https://avatars.githubusercontent.com/u/11421186?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mat Milbury</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=maciejmyslinski" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://andreas.fyi"><img src="https://avatars.githubusercontent.com/u/8077469?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andreas Asprou</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=andreasasprou" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://andreas.fyi"><img src="https://avatars.githubusercontent.com/u/8077469?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andreas Asprou</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=andreasasprou" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=andreasasprou" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/kotx"><img src="https://avatars.githubusercontent.com/u/33439542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kot</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kotx" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=kotx" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=kotx" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/isaka1022"><img src="https://avatars.githubusercontent.com/u/28589716?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Amane</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=isaka1022" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="johnleung.com"><img src="https://avatars.githubusercontent.com/u/20699?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Leung</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fuzzthink" title="Documentation">📖</a></td>
|
||||
@@ -697,8 +702,98 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<td align="center"><a href="https://github.com/ajwgeek"><img src="https://avatars.githubusercontent.com/u/2135600?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Austin Walhof</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ajwgeek" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="g3offrey.dev"><img src="https://avatars.githubusercontent.com/u/11151445?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Geoffrey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=g3offrey" title="Code">💻</a></td>
|
||||
<td align="center"><a href="g3offrey.dev"><img src="https://avatars.githubusercontent.com/u/11151445?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Geoffrey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=g3offrey" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=g3offrey" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=g3offrey" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/keevan"><img src="https://avatars.githubusercontent.com/u/9924643?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Pham</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=keevan" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/kimngan-bui"><img src="https://avatars.githubusercontent.com/u/20723478?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kimngan-bui</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kimngan-bui" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="world.hey.com/bach"><img src="https://avatars.githubusercontent.com/u/11691670?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bahk Chanhee</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=9j" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.afterecon.com/"><img src="https://avatars.githubusercontent.com/u/5559355?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Vandivier</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Vandivier" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Vandivier" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=Vandivier" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://namirsab.github.io"><img src="https://avatars.githubusercontent.com/u/6980777?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Namir</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=namirsab" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=namirsab" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=namirsab" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://twitter.com/scttcper"><img src="https://avatars.githubusercontent.com/u/1400464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Scott Cooper</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=scttcper" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="abduttayyeb.github.io"><img src="https://avatars.githubusercontent.com/u/55306260?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abduttayyeb M.r</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Abduttayyeb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/maybebored"><img src="https://avatars.githubusercontent.com/u/20951181?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mayuran</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=maybebored" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/MuckHub"><img src="https://avatars.githubusercontent.com/u/54979136?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aleksei Vesselko</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MuckHub" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://p-siriphanthong.github.io/"><img src="https://avatars.githubusercontent.com/u/29949429?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Punn Siriphanthong</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=p-siriphanthong" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://my-portfolio-292eb.web.app"><img src="https://avatars.githubusercontent.com/u/83679827?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shawn Fetanat</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=shawn-fetanat" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/mochi-sann"><img src="https://avatars.githubusercontent.com/u/44772513?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Moyuru</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mochi-sann" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=mochi-sann" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=mochi-sann" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/camsloanftc"><img src="https://avatars.githubusercontent.com/u/16295659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cam Sloan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=camsloanftc" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://macieksitkowski.com"><img src="https://avatars.githubusercontent.com/u/58401630?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Maciek Sitkowski</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sitek94" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vivek7405"><img src="https://avatars.githubusercontent.com/u/24492244?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vivek</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=vivek7405" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=vivek7405" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://cj.io"><img src="https://avatars.githubusercontent.com/u/1819?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CJ Lazell</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cj" title="Code">💻</a></td>
|
||||
<td align="center"><a href="robertbroersma.com"><img src="https://avatars.githubusercontent.com/u/4519828?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robert</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=RobertBroersma" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://christianjensen.netlify.com"><img src="https://avatars.githubusercontent.com/u/12374723?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christian Jensen</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cbejensen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/dvnrsn"><img src="https://avatars.githubusercontent.com/u/9112811?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Devin Rasmussen</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dvnrsn" title="Code">💻</a></td>
|
||||
<td align="center"><a href="www.linkedin.com/in/devtom"><img src="https://avatars.githubusercontent.com/u/23282613?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Thomas Brenneur</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=devtombiz" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=devtombiz" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=devtombiz" title="Tests">⚠️</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://lucasvazq.github.io/"><img src="https://avatars.githubusercontent.com/u/38964964?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lucas Vazquez</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lucasvazq" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/chrisj-back2work"><img src="https://avatars.githubusercontent.com/u/68551954?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chris Johnson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=chrisj-back2work" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/thisdotrob"><img src="https://avatars.githubusercontent.com/u/12902589?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rob Stevenson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=thisdotrob" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="www.lucas.computer"><img src="https://avatars.githubusercontent.com/u/1363056?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lucas Heymès</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lovethebomb" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=lovethebomb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/NorfeldtAbtion"><img src="https://avatars.githubusercontent.com/u/53769763?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lasse Norfeldt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=NorfeldtAbtion" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://nyaripeter.hu/"><img src="https://avatars.githubusercontent.com/u/6048614?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Péter Nyári</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=netwarex" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=netwarex" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.holgerfrohloff.de"><img src="https://avatars.githubusercontent.com/u/84148?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Holger Frohloff</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=5minpause" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/basilk76"><img src="https://avatars.githubusercontent.com/u/45275512?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Basil Khan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=basilk76" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://danestves.com/"><img src="https://avatars.githubusercontent.com/u/31737273?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Esteves</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=danestves" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.bitnative.com"><img src="https://avatars.githubusercontent.com/u/1688997?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cory House</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=coryhouse" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://auspham.dev/"><img src="https://avatars.githubusercontent.com/u/16440123?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Austin (Thang Pham)</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rockmanvnx6" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jammeryhq.com"><img src="https://avatars.githubusercontent.com/u/521777?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marcus Reinhardt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=noxify" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=noxify" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/davidchristie"><img src="https://avatars.githubusercontent.com/u/12044333?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Christie</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=davidchristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/ajanth97"><img src="https://avatars.githubusercontent.com/u/50458502?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ajanth</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ajanth97" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/divpreet"><img src="https://avatars.githubusercontent.com/u/2805650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Div</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=divpreet" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/david-arteaga"><img src="https://avatars.githubusercontent.com/u/7199015?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Arteaga</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=david-arteaga" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/MukulKolpe"><img src="https://avatars.githubusercontent.com/u/78664749?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mukul Kolpe</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MukulKolpe" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/skotchpine"><img src="https://avatars.githubusercontent.com/u/13043909?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tyler</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=skotchpine" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/SofianeDjellouli"><img src="https://avatars.githubusercontent.com/u/38258952?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sofiane Djellouli</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=SofianeDjellouli" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/kreako"><img src="https://avatars.githubusercontent.com/u/65113001?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kreako</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kreako" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://sarahdayan.dev"><img src="https://avatars.githubusercontent.com/u/5370675?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sarah Dayan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sarahdayan" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/c-ciobanu"><img src="https://avatars.githubusercontent.com/u/33382714?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cristi Ciobanu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=c-ciobanu" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://arpitdalal.dev"><img src="https://avatars.githubusercontent.com/u/61059807?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Arpit Dalal</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=arpitdalal" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/robertrisch"><img src="https://avatars.githubusercontent.com/u/73828816?v=4?s=100" width="100px;" alt=""/><br /><sub><b>robertrisch</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=robertrisch" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/dineshgadge"><img src="https://avatars.githubusercontent.com/u/186976?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dinesh Gadge</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dineshgadge" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/maltekiessling"><img src="https://avatars.githubusercontent.com/u/30420110?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Malte Kießling</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=maltekiessling" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="ospfranco.com"><img src="https://avatars.githubusercontent.com/u/1634213?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Oscar Franco</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ospfranco" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="adamtrager.com"><img src="https://avatars.githubusercontent.com/u/1719791?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Adam Trager</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Nfinished" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://saheen.codes"><img src="https://avatars.githubusercontent.com/u/2632896?v=4?s=100" width="100px;" alt=""/><br /><sub><b>saheenshoukath</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=shellord" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jahneer.me"><img src="https://avatars.githubusercontent.com/u/54552763?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Husnul Jahneer</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=husnuljahneer" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/ReykCS"><img src="https://avatars.githubusercontent.com/u/40463716?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Reyk</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ReykCS" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Lokprakash-babu"><img src="https://avatars.githubusercontent.com/u/60031382?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lokprakash Babu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Lokprakash-babu" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mizle.net"><img src="https://avatars.githubusercontent.com/u/3516343?v=4?s=100" width="100px;" alt=""/><br /><sub><b>eai04191</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=eai04191" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://numanaral.github.io/?ref=github"><img src="https://avatars.githubusercontent.com/u/25233323?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Numan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=numanaral" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jscyo"><img src="https://avatars.githubusercontent.com/u/6310783?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joel Coutinho</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jscyo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/davidbarker"><img src="https://avatars.githubusercontent.com/u/1597139?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Barker</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=davidbarker" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.timfeeley.com/"><img src="https://avatars.githubusercontent.com/u/3246342?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tim Feeley</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=timfee" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Caslus"><img src="https://avatars.githubusercontent.com/u/22855640?v=4?s=100" width="100px;" alt=""/><br /><sub><b>lucas philippe</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Caslus" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/the-bayer"><img src="https://avatars.githubusercontent.com/u/94391693?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Blake Bayer</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=the-bayer" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=the-bayer" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/rmassie"><img src="https://avatars.githubusercontent.com/u/7375518?v=4?s=100" width="100px;" alt=""/><br /><sub><b>R Massie</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rmassie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/paulm17"><img src="https://avatars.githubusercontent.com/u/387463?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Paul</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=paulm17" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://minho42.com"><img src="https://avatars.githubusercontent.com/u/15278512?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Min ho Kim</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=minho42" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/webdeb"><img src="https://avatars.githubusercontent.com/u/14992140?v=4?s=100" width="100px;" alt=""/><br /><sub><b>webdeb</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=webdeb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/iDavidB"><img src="https://avatars.githubusercontent.com/u/32268383?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=iDavidB" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=iDavidB" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=iDavidB" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://jdlt.co.uk"><img src="https://avatars.githubusercontent.com/u/5058625?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jake Dowie</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jakedee" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/datner"><img src="https://avatars.githubusercontent.com/u/22598347?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Datner</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=datner" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=datner" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=datner" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/remlse"><img src="https://avatars.githubusercontent.com/u/54984957?v=4?s=100" width="100px;" alt=""/><br /><sub><b>remlse</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=remlse" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sergous"><img src="https://avatars.githubusercontent.com/u/545151?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sergei Smirnov</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sergous" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/trensik"><img src="https://avatars.githubusercontent.com/u/18584155?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dawid Urbaniak</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Trancever" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Trancever" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="kiri.dev"><img src="https://avatars.githubusercontent.com/u/29735836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kacper Potyrała</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=SerekKiri" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="jcde.xyz"><img src="https://avatars.githubusercontent.com/u/31413538?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jeeho Ahn</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=iojcde" title="Documentation">📖</a> <a href="#tool-iojcde" title="Tools">🔧</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@ TODO
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Email Brandon Bayer at b@bayer.ws to report a vulnerablity, and he will follow up with you asap.
|
||||
Email Brandon Bayer at b@bayer.ws to report a vulnerability, and he will follow up with you asap.
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M79.4831 1.95572C70.2813 -0.81923 55.2811 -0.617415 46.1549 2.4098L18.6125 11.5167C9.48627 14.5313 9.38542 19.7784 18.3856 23.1588L49.9743 35.028C58.9744 38.4084 73.6217 38.194 82.5084 34.5487L110.883 22.9192C119.782 19.2739 119.53 14.0267 110.316 11.2518L79.4831 1.95572Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M84.235 38.6101C75.3357 42.2554 68.0625 53.1029 68.0625 62.727V113.635C68.0625 123.259 74.9071 127.245 83.2644 122.489L109.282 107.706C117.639 102.951 124.912 91.208 125.442 81.6092L127.837 37.8281C128.366 28.2167 121.509 23.3479 112.609 26.9932L84.235 38.6101Z" fill="url(#paint1_linear)"/>
|
||||
<path d="M0.0071345 37.8409C-0.257575 28.2295 6.877 23.1211 15.8771 26.5015L47.4658 38.3707C56.466 41.7511 63.8274 52.3842 63.8274 62.0082V112.916C63.8274 122.54 56.882 126.715 48.386 122.212L17.0998 105.6C8.60392 101.085 1.44415 89.5306 1.16683 79.9192L0.0071345 37.8409Z" fill="url(#paint2_linear)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="63.9326" y1="0" x2="63.9326" y2="124.497" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#036EB8"/>
|
||||
<stop offset="1" stop-color="#469196"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="63.9326" y1="0" x2="63.9326" y2="124.497" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#036EB8"/>
|
||||
<stop offset="1" stop-color="#469196"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="63.9326" y1="0" x2="63.9326" y2="124.497" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#036EB8"/>
|
||||
<stop offset="1" stop-color="#469196"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0">
|
||||
<rect width="128" height="128" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 78 KiB |
BIN
assets/jdlt.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
assets/meetkai.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 30 KiB |
@@ -1,7 +1,7 @@
|
||||
import {useSession, useRouter, useMutation, Head} from "blitz"
|
||||
import {useSession, useRouter, useMutation, Head, BlitzLayout} from "blitz"
|
||||
import logout from "app/auth/mutations/logout"
|
||||
|
||||
export default function Layout({title, children}: {title?: string; children: React.ReactNode}) {
|
||||
const Layout: BlitzLayout<{title?: string}> = ({title, children}) => {
|
||||
const session = useSession({suspense: false})
|
||||
const router = useRouter()
|
||||
const [logoutMutation] = useMutation(logout)
|
||||
@@ -28,3 +28,5 @@ export default function Layout({title, children}: {title?: string; children: Rea
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {render} from "test/utils"
|
||||
import Home from "./index"
|
||||
|
||||
jest.mock("@blitzjs/core", () => ({
|
||||
...jest.requireActual<object>("@blitzjs/core")!,
|
||||
jest.mock("next/data-client", () => ({
|
||||
...jest.requireActual<object>("next/data-client")!,
|
||||
useQuery: () => [
|
||||
{
|
||||
id: 1,
|
||||
@@ -24,4 +24,4 @@ test("renders blitz documentation link", () => {
|
||||
const element = getByText(/powered by blitz/i)
|
||||
// @ts-ignore
|
||||
expect(element).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,12 +6,14 @@ import {
|
||||
useRouterQuery,
|
||||
useMutation,
|
||||
invoke,
|
||||
Image,
|
||||
useQuery,
|
||||
BlitzPage,
|
||||
} from "blitz"
|
||||
import getUser from "app/users/queries/getUser"
|
||||
import trackView from "app/users/mutations/trackView"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import logo from "public/logo.png"
|
||||
|
||||
import {Routes} from ".blitz"
|
||||
|
||||
@@ -88,7 +90,7 @@ const Home: BlitzPage = () => (
|
||||
|
||||
<main>
|
||||
<div className="logo">
|
||||
<img src="/logo.png" alt="blitz.js" />
|
||||
<Image src={logo} alt="blitz.js" width={400} height={200} />
|
||||
</div>
|
||||
|
||||
<Suspense fallback={"Loading..."}>
|
||||
|
||||
13
examples/auth/app/queries/users/currentUserQuery.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import {Ctx} from "blitz"
|
||||
import db from "db"
|
||||
|
||||
export default async function currentUserQuery(_ = null, {session}: Ctx) {
|
||||
if (!session.userId) return null
|
||||
|
||||
const user = await db.user.findFirst({
|
||||
where: {id: session.userId},
|
||||
select: {id: true, name: true, email: true, role: true},
|
||||
})
|
||||
|
||||
return user
|
||||
}
|
||||
@@ -16,6 +16,9 @@ module.exports = withBundleAnalyzer({
|
||||
cli: {
|
||||
clearConsoleOnBlitzDev: false,
|
||||
},
|
||||
codegen: {
|
||||
templateDir: "./my-templates",
|
||||
},
|
||||
log: {
|
||||
// level: "trace",
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* and then export it. That way you can import here and anywhere else
|
||||
* and use it straight away.
|
||||
*/
|
||||
import previewEmail from "preview-email"
|
||||
|
||||
type ResetPasswordMailer = {
|
||||
to: string
|
||||
@@ -38,6 +37,7 @@ export function forgotPasswordMailer({to, token}: ResetPasswordMailer) {
|
||||
throw new Error("No production email implementation in mailers/forgotPasswordMailer")
|
||||
} else {
|
||||
// Preview email in the browser
|
||||
const previewEmail = (await import("preview-email")).default
|
||||
await previewEmail(msg)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"dev": "blitz dev",
|
||||
"build": "blitz build",
|
||||
"start": "blitz start",
|
||||
"console": "blitz console",
|
||||
"studio": "blitz prisma studio",
|
||||
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
|
||||
"analyze": "cross-env ANALYZE=true blitz build",
|
||||
@@ -29,7 +30,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "2.24.1",
|
||||
"blitz": "0.40.0",
|
||||
"blitz": "0.45.4",
|
||||
"final-form": "4.20.1",
|
||||
"passport-auth0": "1.4.0",
|
||||
"passport-github2": "0.1.12",
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import {ReactNode} from "react"
|
||||
import {Head} from "blitz"
|
||||
import {Head, BlitzLayout} from "blitz"
|
||||
|
||||
type LayoutProps = {
|
||||
title?: string
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const Layout = ({title, children}: LayoutProps) => {
|
||||
const Layout: BlitzLayout<{title?: string}> = ({title, children}) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {Link, BlitzPage, useMutation} from "blitz"
|
||||
import {Link, BlitzPage, useMutation, Image} from "blitz"
|
||||
import Layout from "app/layouts/Layout"
|
||||
import logout from "app/auth/mutations/logout"
|
||||
import {useCurrentUser} from "app/hooks/useCurrentUser"
|
||||
import {Suspense} from "react"
|
||||
import logo from "public/logo.png"
|
||||
|
||||
/*
|
||||
* This file is just for a pleasant getting started page for your new app.
|
||||
@@ -54,7 +55,7 @@ const Home: BlitzPage = () => {
|
||||
<div className="container">
|
||||
<main>
|
||||
<div className="logo">
|
||||
<img src="/logo.png" alt="blitz.js" />
|
||||
<Image src={logo} alt="blitz.js" />
|
||||
</div>
|
||||
<p>
|
||||
<strong>Congrats!</strong> Your app is ready, including user sign-up and log-in.
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
* This seed function is executed when you run `blitz db seed`.
|
||||
*
|
||||
* Probably you want to use a library like https://chancejs.com
|
||||
* or https://github.com/Marak/Faker.js to easily generate
|
||||
* realistic data.
|
||||
* to easily generate realistic data.
|
||||
*/
|
||||
const seed = async () => {
|
||||
// for (let i = 0; i < 5; i++) {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "2.24.1",
|
||||
"blitz": "0.40.0",
|
||||
"blitz": "0.45.4",
|
||||
"final-form": "4.20.1",
|
||||
"prisma": "2.24.1",
|
||||
"react": "0.0.0-experimental-6a589ad71",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import blitz from "blitz/custom-server"
|
||||
import {createServer} from "http"
|
||||
import {parse} from "url"
|
||||
import {log} from "@blitzjs/display"
|
||||
import {log} from "next/dist/server/lib/logging"
|
||||
|
||||
const {PORT = "3000"} = process.env
|
||||
const dev = process.env.NODE_ENV !== "production"
|
||||
|
||||
11
examples/cypress/.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
# https://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
3
examples/cypress/.env
Normal file
@@ -0,0 +1,3 @@
|
||||
# This env file should be checked into source control
|
||||
# This is the place for default values for all environments
|
||||
# Values in `.env.local` and `.env.production` will override these values
|
||||
3
examples/cypress/.eslintrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ["blitz"],
|
||||
}
|
||||
56
examples/cypress/.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
.npm
|
||||
web_modules/
|
||||
|
||||
# blitz
|
||||
/.blitz/
|
||||
/.next/
|
||||
*.sqlite
|
||||
*.sqlite-journal
|
||||
.now
|
||||
.blitz**
|
||||
blitz-log.log
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
.envrc
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Testing
|
||||
.coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
lib-cov
|
||||
|
||||
# Caches
|
||||
*.tsbuildinfo
|
||||
.eslintcache
|
||||
.node_repl_history
|
||||
.yarn-integrity
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
7
examples/cypress/.npmrc
Normal file
@@ -0,0 +1,7 @@
|
||||
save-exact=true
|
||||
legacy-peer-deps=true
|
||||
|
||||
public-hoist-pattern[]=next
|
||||
public-hoist-pattern[]=secure-password
|
||||
public-hoist-pattern[]=*jest*
|
||||
public-hoist-pattern[]=@testing-library/*
|
||||
9
examples/cypress/.prettierignore
Normal file
@@ -0,0 +1,9 @@
|
||||
.gitkeep
|
||||
.env*
|
||||
*.ico
|
||||
*.lock
|
||||
db/migrations
|
||||
.next
|
||||
.blitz
|
||||
.yarn
|
||||
.pnp*
|
||||
25
examples/cypress/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# **cypress**
|
||||
|
||||
This is an example [Cypress](https://www.cypress.io/) integration.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Migrate the database:
|
||||
|
||||
```
|
||||
blitz prisma migrate dev
|
||||
```
|
||||
|
||||
Run e2e tests:
|
||||
|
||||
```
|
||||
yarn test:e2e
|
||||
```
|
||||
|
||||
Open Cypress dashboard:
|
||||
|
||||
```
|
||||
yarn cypress:open
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
49
examples/cypress/app/auth/components/LoginForm.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { AuthenticationError, Link, useMutation } from "blitz"
|
||||
import { LabeledTextField } from "app/core/components/LabeledTextField"
|
||||
import { Form, FORM_ERROR } from "app/core/components/Form"
|
||||
import login from "app/auth/mutations/login"
|
||||
import { Login } from "app/auth/validations"
|
||||
|
||||
type LoginFormProps = {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const LoginForm = (props: LoginFormProps) => {
|
||||
const [loginMutation] = useMutation(login)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Login</h1>
|
||||
|
||||
<Form
|
||||
submitText="Login"
|
||||
schema={Login}
|
||||
initialValues={{ email: "", password: "" }}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await loginMutation(values)
|
||||
props.onSuccess?.()
|
||||
} catch (error: any) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return { [FORM_ERROR]: "Sorry, those credentials are invalid" }
|
||||
} else {
|
||||
return {
|
||||
[FORM_ERROR]:
|
||||
"Sorry, we had an unexpected error. Please try again. - " + error.toString(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
|
||||
</Form>
|
||||
|
||||
<div style={{ marginTop: "1rem" }}>
|
||||
Or <Link href="signup">Sign Up</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginForm
|
||||
43
examples/cypress/app/auth/components/SignupForm.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useMutation } from "blitz"
|
||||
import { LabeledTextField } from "app/core/components/LabeledTextField"
|
||||
import { Form, FORM_ERROR } from "app/core/components/Form"
|
||||
import signup from "app/auth/mutations/signup"
|
||||
import { Signup } from "app/auth/validations"
|
||||
|
||||
type SignupFormProps = {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const SignupForm = (props: SignupFormProps) => {
|
||||
const [signupMutation] = useMutation(signup)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Create an Account</h1>
|
||||
|
||||
<Form
|
||||
submitText="Create Account"
|
||||
schema={Signup}
|
||||
initialValues={{ email: "", password: "" }}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await signupMutation(values)
|
||||
props.onSuccess?.()
|
||||
} catch (error: any) {
|
||||
if (error.code === "P2002" && error.meta?.target?.includes("email")) {
|
||||
// This error comes from Prisma
|
||||
return { email: "This email is already being used" }
|
||||
} else {
|
||||
return { [FORM_ERROR]: error.toString() }
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SignupForm
|
||||
23
examples/cypress/app/auth/mutations/changePassword.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { NotFoundError, SecurePassword, resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { authenticateUser } from "./login"
|
||||
import { ChangePassword } from "../validations"
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(ChangePassword),
|
||||
resolver.authorize(),
|
||||
async ({ currentPassword, newPassword }, ctx) => {
|
||||
const user = await db.user.findFirst({ where: { id: ctx.session.userId! } })
|
||||
if (!user) throw new NotFoundError()
|
||||
|
||||
await authenticateUser(user.email, currentPassword)
|
||||
|
||||
const hashedPassword = await SecurePassword.hash(newPassword.trim())
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { hashedPassword },
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
)
|
||||
56
examples/cypress/app/auth/mutations/forgotPassword.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { hash256, Ctx } from "blitz"
|
||||
import forgotPassword from "./forgotPassword"
|
||||
import db from "db"
|
||||
import previewEmail from "preview-email"
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.$reset()
|
||||
})
|
||||
|
||||
const generatedToken = "plain-token"
|
||||
jest.mock("next/stdlib-server", () => ({
|
||||
...jest.requireActual<object>("next/stdlib-server")!,
|
||||
generateToken: () => generatedToken,
|
||||
}))
|
||||
jest.mock("preview-email", () => jest.fn())
|
||||
|
||||
describe("forgotPassword mutation", () => {
|
||||
it("does not throw error if user doesn't exist", async () => {
|
||||
await expect(forgotPassword({ email: "no-user@email.com" }, {} as Ctx)).resolves.not.toThrow()
|
||||
})
|
||||
|
||||
it("works correctly", async () => {
|
||||
// Create test user
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email: "user@example.com",
|
||||
tokens: {
|
||||
// Create old token to ensure it's deleted
|
||||
create: {
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: "token",
|
||||
expiresAt: new Date(),
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
include: { tokens: true },
|
||||
})
|
||||
|
||||
// Invoke the mutation
|
||||
await forgotPassword({ email: user.email }, {} as Ctx)
|
||||
|
||||
const tokens = await db.token.findMany({ where: { userId: user.id } })
|
||||
const token = tokens[0]
|
||||
|
||||
// delete's existing tokens
|
||||
expect(tokens.length).toBe(1)
|
||||
|
||||
expect(token.id).not.toBe(user.tokens[0].id)
|
||||
expect(token.type).toBe("RESET_PASSWORD")
|
||||
expect(token.sentTo).toBe(user.email)
|
||||
expect(token.hashedToken).toBe(hash256(generatedToken))
|
||||
expect(token.expiresAt > new Date()).toBe(true)
|
||||
expect(previewEmail).toBeCalled()
|
||||
})
|
||||
})
|
||||
41
examples/cypress/app/auth/mutations/forgotPassword.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { resolver, generateToken, hash256 } from "blitz"
|
||||
import db from "db"
|
||||
import { forgotPasswordMailer } from "mailers/forgotPasswordMailer"
|
||||
import { ForgotPassword } from "../validations"
|
||||
|
||||
const RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS = 4
|
||||
|
||||
export default resolver.pipe(resolver.zod(ForgotPassword), async ({ email }) => {
|
||||
// 1. Get the user
|
||||
const user = await db.user.findFirst({ where: { email: email.toLowerCase() } })
|
||||
|
||||
// 2. Generate the token and expiration date.
|
||||
const token = generateToken()
|
||||
const hashedToken = hash256(token)
|
||||
const expiresAt = new Date()
|
||||
expiresAt.setHours(expiresAt.getHours() + RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS)
|
||||
|
||||
// 3. If user with this email was found
|
||||
if (user) {
|
||||
// 4. Delete any existing password reset tokens
|
||||
await db.token.deleteMany({ where: { type: "RESET_PASSWORD", userId: user.id } })
|
||||
// 5. Save this new token in the database.
|
||||
await db.token.create({
|
||||
data: {
|
||||
user: { connect: { id: user.id } },
|
||||
type: "RESET_PASSWORD",
|
||||
expiresAt,
|
||||
hashedToken,
|
||||
sentTo: user.email,
|
||||
},
|
||||
})
|
||||
// 6. Send the email
|
||||
await forgotPasswordMailer({ to: user.email, token }).send()
|
||||
} else {
|
||||
// 7. If no user found wait the same time so attackers can't tell the difference
|
||||
await new Promise((resolve) => setTimeout(resolve, 750))
|
||||
}
|
||||
|
||||
// 8. Return the same result whether a password reset email was sent or not
|
||||
return
|
||||
})
|
||||
31
examples/cypress/app/auth/mutations/login.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { resolver, SecurePassword, AuthenticationError } from "blitz"
|
||||
import db from "db"
|
||||
import { Login } from "../validations"
|
||||
import { Role } from "types"
|
||||
|
||||
export const authenticateUser = async (rawEmail: string, rawPassword: string) => {
|
||||
const email = rawEmail.toLowerCase().trim()
|
||||
const password = rawPassword.trim()
|
||||
const user = await db.user.findFirst({ where: { email } })
|
||||
if (!user) throw new AuthenticationError()
|
||||
|
||||
const result = await SecurePassword.verify(user.hashedPassword, password)
|
||||
|
||||
if (result === SecurePassword.VALID_NEEDS_REHASH) {
|
||||
// Upgrade hashed password with a more secure hash
|
||||
const improvedHash = await SecurePassword.hash(password)
|
||||
await db.user.update({ where: { id: user.id }, data: { hashedPassword: improvedHash } })
|
||||
}
|
||||
|
||||
const { hashedPassword, ...rest } = user
|
||||
return rest
|
||||
}
|
||||
|
||||
export default resolver.pipe(resolver.zod(Login), async ({ email, password }, ctx) => {
|
||||
// This throws an error if credentials are invalid
|
||||
const user = await authenticateUser(email, password)
|
||||
|
||||
await ctx.session.$create({ userId: user.id, role: user.role as Role })
|
||||
|
||||
return user
|
||||
})
|
||||
5
examples/cypress/app/auth/mutations/logout.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Ctx } from "blitz"
|
||||
|
||||
export default async function logout(_: any, ctx: Ctx) {
|
||||
return await ctx.session.$revoke()
|
||||
}
|
||||
82
examples/cypress/app/auth/mutations/resetPassword.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import resetPassword from "./resetPassword"
|
||||
import db from "db"
|
||||
import { hash256, SecurePassword } from "blitz"
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.$reset()
|
||||
})
|
||||
|
||||
const mockCtx: any = {
|
||||
session: {
|
||||
$create: jest.fn,
|
||||
},
|
||||
}
|
||||
|
||||
describe("resetPassword mutation", () => {
|
||||
it("works correctly", async () => {
|
||||
expect(true).toBe(true)
|
||||
|
||||
// Create test user
|
||||
const goodToken = "randomPasswordResetToken"
|
||||
const expiredToken = "expiredRandomPasswordResetToken"
|
||||
const future = new Date()
|
||||
future.setHours(future.getHours() + 4)
|
||||
const past = new Date()
|
||||
past.setHours(past.getHours() - 4)
|
||||
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email: "user@example.com",
|
||||
tokens: {
|
||||
// Create old token to ensure it's deleted
|
||||
create: [
|
||||
{
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: hash256(expiredToken),
|
||||
expiresAt: past,
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
{
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: hash256(goodToken),
|
||||
expiresAt: future,
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
include: { tokens: true },
|
||||
})
|
||||
|
||||
const newPassword = "newPassword"
|
||||
|
||||
// Non-existent token
|
||||
await expect(
|
||||
resetPassword({ token: "no-token", password: "", passwordConfirmation: "" }, mockCtx)
|
||||
).rejects.toThrowError()
|
||||
|
||||
// Expired token
|
||||
await expect(
|
||||
resetPassword(
|
||||
{ token: expiredToken, password: newPassword, passwordConfirmation: newPassword },
|
||||
mockCtx
|
||||
)
|
||||
).rejects.toThrowError()
|
||||
|
||||
// Good token
|
||||
await resetPassword(
|
||||
{ token: goodToken, password: newPassword, passwordConfirmation: newPassword },
|
||||
mockCtx
|
||||
)
|
||||
|
||||
// Delete's the token
|
||||
const numberOfTokens = await db.token.count({ where: { userId: user.id } })
|
||||
expect(numberOfTokens).toBe(0)
|
||||
|
||||
// Updates user's password
|
||||
const updatedUser = await db.user.findFirst({ where: { id: user.id } })
|
||||
expect(await SecurePassword.verify(updatedUser!.hashedPassword, newPassword)).toBe(
|
||||
SecurePassword.VALID
|
||||
)
|
||||
})
|
||||
})
|
||||
47
examples/cypress/app/auth/mutations/resetPassword.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { resolver, SecurePassword, hash256 } from "blitz"
|
||||
import db from "db"
|
||||
import { ResetPassword } from "../validations"
|
||||
import login from "./login"
|
||||
|
||||
export class ResetPasswordError extends Error {
|
||||
name = "ResetPasswordError"
|
||||
message = "Reset password link is invalid or it has expired."
|
||||
}
|
||||
|
||||
export default resolver.pipe(resolver.zod(ResetPassword), async ({ password, token }, ctx) => {
|
||||
// 1. Try to find this token in the database
|
||||
const hashedToken = hash256(token)
|
||||
const possibleToken = await db.token.findFirst({
|
||||
where: { hashedToken, type: "RESET_PASSWORD" },
|
||||
include: { user: true },
|
||||
})
|
||||
|
||||
// 2. If token not found, error
|
||||
if (!possibleToken) {
|
||||
throw new ResetPasswordError()
|
||||
}
|
||||
const savedToken = possibleToken
|
||||
|
||||
// 3. Delete token so it can't be used again
|
||||
await db.token.delete({ where: { id: savedToken.id } })
|
||||
|
||||
// 4. If token has expired, error
|
||||
if (savedToken.expiresAt < new Date()) {
|
||||
throw new ResetPasswordError()
|
||||
}
|
||||
|
||||
// 5. Since token is valid, now we can update the user's password
|
||||
const hashedPassword = await SecurePassword.hash(password.trim())
|
||||
const user = await db.user.update({
|
||||
where: { id: savedToken.userId },
|
||||
data: { hashedPassword },
|
||||
})
|
||||
|
||||
// 6. Revoke all existing login sessions for this user
|
||||
await db.session.deleteMany({ where: { userId: user.id } })
|
||||
|
||||
// 7. Now log the user in with the new credentials
|
||||
await login({ email: user.email, password }, ctx)
|
||||
|
||||
return true
|
||||
})
|
||||
15
examples/cypress/app/auth/mutations/signup.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { resolver, SecurePassword } from "blitz"
|
||||
import db from "db"
|
||||
import { Signup } from "app/auth/validations"
|
||||
import { Role } from "types"
|
||||
|
||||
export default resolver.pipe(resolver.zod(Signup), async ({ email, password }, ctx) => {
|
||||
const hashedPassword = await SecurePassword.hash(password.trim())
|
||||
const user = await db.user.create({
|
||||
data: { email: email.toLowerCase().trim(), hashedPassword, role: "USER" },
|
||||
select: { id: true, name: true, email: true, role: true },
|
||||
})
|
||||
|
||||
await ctx.session.$create({ userId: user.id, role: user.role as Role })
|
||||
return user
|
||||
})
|
||||
48
examples/cypress/app/auth/pages/forgot-password.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { BlitzPage, useMutation } from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import { LabeledTextField } from "app/core/components/LabeledTextField"
|
||||
import { Form, FORM_ERROR } from "app/core/components/Form"
|
||||
import { ForgotPassword } from "app/auth/validations"
|
||||
import forgotPassword from "app/auth/mutations/forgotPassword"
|
||||
|
||||
const ForgotPasswordPage: BlitzPage = () => {
|
||||
const [forgotPasswordMutation, { isSuccess }] = useMutation(forgotPassword)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Forgot your password?</h1>
|
||||
|
||||
{isSuccess ? (
|
||||
<div>
|
||||
<h2>Request Submitted</h2>
|
||||
<p>
|
||||
If your email is in our system, you will receive instructions to reset your password
|
||||
shortly.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Form
|
||||
submitText="Send Reset Password Instructions"
|
||||
schema={ForgotPassword}
|
||||
initialValues={{ email: "" }}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await forgotPasswordMutation(values)
|
||||
} catch (error: any) {
|
||||
return {
|
||||
[FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.",
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ForgotPasswordPage.redirectAuthenticatedTo = "/"
|
||||
ForgotPasswordPage.getLayout = (page) => <Layout title="Forgot Your Password?">{page}</Layout>
|
||||
|
||||
export default ForgotPasswordPage
|
||||
23
examples/cypress/app/auth/pages/login.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useRouter, BlitzPage } from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import { LoginForm } from "app/auth/components/LoginForm"
|
||||
|
||||
const LoginPage: BlitzPage = () => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LoginForm
|
||||
onSuccess={() => {
|
||||
const next = router.query.next ? decodeURIComponent(router.query.next as string) : "/"
|
||||
router.push(next)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
LoginPage.redirectAuthenticatedTo = "/"
|
||||
LoginPage.getLayout = (page) => <Layout title="Log In">{page}</Layout>
|
||||
|
||||
export default LoginPage
|
||||
61
examples/cypress/app/auth/pages/reset-password.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { BlitzPage, useRouterQuery, Link, useMutation } from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import { LabeledTextField } from "app/core/components/LabeledTextField"
|
||||
import { Form, FORM_ERROR } from "app/core/components/Form"
|
||||
import { ResetPassword } from "app/auth/validations"
|
||||
import resetPassword from "app/auth/mutations/resetPassword"
|
||||
|
||||
import { Routes } from ".blitz"
|
||||
|
||||
const ResetPasswordPage: BlitzPage = () => {
|
||||
const query = useRouterQuery()
|
||||
const [resetPasswordMutation, { isSuccess }] = useMutation(resetPassword)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Set a New Password</h1>
|
||||
|
||||
{isSuccess ? (
|
||||
<div>
|
||||
<h2>Password Reset Successfully</h2>
|
||||
<p>
|
||||
Go to the <Link href={Routes.Home()}>homepage</Link>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Form
|
||||
submitText="Reset Password"
|
||||
schema={ResetPassword}
|
||||
initialValues={{ password: "", passwordConfirmation: "", token: query.token as string }}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await resetPasswordMutation(values)
|
||||
} catch (error: any) {
|
||||
if (error.name === "ResetPasswordError") {
|
||||
return {
|
||||
[FORM_ERROR]: error.message,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
[FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.",
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="password" label="New Password" type="password" />
|
||||
<LabeledTextField
|
||||
name="passwordConfirmation"
|
||||
label="Confirm New Password"
|
||||
type="password"
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ResetPasswordPage.redirectAuthenticatedTo = "/"
|
||||
ResetPasswordPage.getLayout = (page) => <Layout title="Reset Your Password">{page}</Layout>
|
||||
|
||||
export default ResetPasswordPage
|
||||
20
examples/cypress/app/auth/pages/signup.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useRouter, BlitzPage } from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import { SignupForm } from "app/auth/components/SignupForm"
|
||||
|
||||
import { Routes } from ".blitz"
|
||||
|
||||
const SignupPage: BlitzPage = () => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SignupForm onSuccess={() => router.push(Routes.Home())} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
SignupPage.redirectAuthenticatedTo = "/"
|
||||
SignupPage.getLayout = (page) => <Layout title="Sign Up">{page}</Layout>
|
||||
|
||||
export default SignupPage
|
||||
33
examples/cypress/app/auth/validations.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { z } from "zod"
|
||||
|
||||
const password = z.string().min(10).max(100)
|
||||
|
||||
export const Signup = z.object({
|
||||
email: z.string().email(),
|
||||
password,
|
||||
})
|
||||
|
||||
export const Login = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
})
|
||||
|
||||
export const ForgotPassword = z.object({
|
||||
email: z.string().email(),
|
||||
})
|
||||
|
||||
export const ResetPassword = z
|
||||
.object({
|
||||
password: password,
|
||||
passwordConfirmation: password,
|
||||
token: z.string(),
|
||||
})
|
||||
.refine((data) => data.password === data.passwordConfirmation, {
|
||||
message: "Passwords don't match",
|
||||
path: ["passwordConfirmation"], // set the path of the error
|
||||
})
|
||||
|
||||
export const ChangePassword = z.object({
|
||||
currentPassword: z.string(),
|
||||
newPassword: password,
|
||||
})
|
||||
53
examples/cypress/app/core/components/Form.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { ReactNode, PropsWithoutRef } from "react"
|
||||
import { Form as FinalForm, FormProps as FinalFormProps } from "react-final-form"
|
||||
import { z } from "zod"
|
||||
import { validateZodSchema } from "blitz"
|
||||
export { FORM_ERROR } from "final-form"
|
||||
|
||||
export interface FormProps<S extends z.ZodType<any, any>>
|
||||
extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> {
|
||||
/** All your form fields */
|
||||
children?: ReactNode
|
||||
/** Text to display in the submit button */
|
||||
submitText?: string
|
||||
schema?: S
|
||||
onSubmit: FinalFormProps<z.infer<S>>["onSubmit"]
|
||||
initialValues?: FinalFormProps<z.infer<S>>["initialValues"]
|
||||
}
|
||||
|
||||
export function Form<S extends z.ZodType<any, any>>({
|
||||
children,
|
||||
submitText,
|
||||
schema,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
...props
|
||||
}: FormProps<S>) {
|
||||
return (
|
||||
<FinalForm
|
||||
initialValues={initialValues}
|
||||
validate={validateZodSchema(schema)}
|
||||
onSubmit={onSubmit}
|
||||
render={({ handleSubmit, submitting, submitError }) => (
|
||||
<form onSubmit={handleSubmit} className="form" {...props}>
|
||||
{/* Form fields supplied as children are rendered here */}
|
||||
{children}
|
||||
|
||||
{submitError && (
|
||||
<div role="alert" style={{ color: "red" }}>
|
||||
{submitError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{submitText && (
|
||||
<button type="submit" disabled={submitting}>
|
||||
{submitText}
|
||||
</button>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Form
|
||||
49
examples/cypress/app/core/components/LabeledTextField.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { forwardRef, ComponentPropsWithoutRef, PropsWithoutRef } from "react"
|
||||
import { useField, UseFieldConfig } from "react-final-form"
|
||||
|
||||
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
|
||||
/** Field name. */
|
||||
name: string
|
||||
/** Field label. */
|
||||
label: string
|
||||
/** Field type. Doesn't include radio buttons and checkboxes */
|
||||
type?: "text" | "password" | "email" | "number"
|
||||
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
|
||||
labelProps?: ComponentPropsWithoutRef<"label">
|
||||
fieldProps?: UseFieldConfig<string>
|
||||
}
|
||||
|
||||
export const LabeledTextField = forwardRef<HTMLInputElement, LabeledTextFieldProps>(
|
||||
({ name, label, outerProps, fieldProps, labelProps, ...props }, ref) => {
|
||||
const {
|
||||
input,
|
||||
meta: { touched, error, submitError, submitting },
|
||||
} = useField(name, {
|
||||
parse:
|
||||
props.type === "number"
|
||||
? (Number as any)
|
||||
: // Converting `""` to `null` ensures empty values will be set to null in the DB
|
||||
(v) => (v === "" ? null : v),
|
||||
...fieldProps,
|
||||
})
|
||||
|
||||
const normalizedError = Array.isArray(error) ? error.join(", ") : error || submitError
|
||||
|
||||
return (
|
||||
<div {...outerProps}>
|
||||
<label {...labelProps}>
|
||||
{label}
|
||||
<input {...input} disabled={submitting} {...props} ref={ref} />
|
||||
</label>
|
||||
|
||||
{touched && normalizedError && (
|
||||
<div role="alert" style={{ color: "red" }}>
|
||||
{normalizedError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default LabeledTextField
|
||||
7
examples/cypress/app/core/hooks/useCurrentUser.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { useQuery } from "blitz"
|
||||
import getCurrentUser from "app/users/queries/getCurrentUser"
|
||||
|
||||
export const useCurrentUser = () => {
|
||||
const [user] = useQuery(getCurrentUser, null)
|
||||
return user
|
||||
}
|
||||
22
examples/cypress/app/core/layouts/Layout.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ReactNode } from "react"
|
||||
import { Head } from "blitz"
|
||||
|
||||
type LayoutProps = {
|
||||
title?: string
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const Layout = ({ title, children }: LayoutProps) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{title || "cypress"}</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout
|
||||
19
examples/cypress/app/pages/404.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Head, ErrorComponent } from "blitz"
|
||||
|
||||
// ------------------------------------------------------
|
||||
// This page is rendered if a route match is not found
|
||||
// ------------------------------------------------------
|
||||
export default function Page404() {
|
||||
const statusCode = 404
|
||||
const title = "This page could not be found"
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>
|
||||
{statusCode}: {title}
|
||||
</title>
|
||||
</Head>
|
||||
<ErrorComponent statusCode={statusCode} title={title} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
40
examples/cypress/app/pages/_app.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
AppProps,
|
||||
ErrorBoundary,
|
||||
ErrorComponent,
|
||||
AuthenticationError,
|
||||
AuthorizationError,
|
||||
ErrorFallbackProps,
|
||||
useQueryErrorResetBoundary,
|
||||
} from "blitz"
|
||||
import LoginForm from "app/auth/components/LoginForm"
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const getLayout = Component.getLayout || ((page) => page)
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={RootErrorFallback}
|
||||
onReset={useQueryErrorResetBoundary().reset}
|
||||
>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
function RootErrorFallback({ error, resetErrorBoundary }: ErrorFallbackProps) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return <LoginForm onSuccess={resetErrorBoundary} />
|
||||
} else if (error instanceof AuthorizationError) {
|
||||
return (
|
||||
<ErrorComponent
|
||||
statusCode={error.statusCode}
|
||||
title="Sorry, you are not authorized to access this"
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ErrorComponent statusCode={error.statusCode || 400} title={error.message || error.name} />
|
||||
)
|
||||
}
|
||||
}
|
||||
23
examples/cypress/app/pages/_document.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Document, Html, DocumentHead, Main, BlitzScript /*DocumentContext*/ } from "blitz"
|
||||
|
||||
class MyDocument extends Document {
|
||||
// Only uncomment if you need to customize this behaviour
|
||||
// static async getInitialProps(ctx: DocumentContext) {
|
||||
// const initialProps = await Document.getInitialProps(ctx)
|
||||
// return {...initialProps}
|
||||
// }
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<DocumentHead />
|
||||
<body>
|
||||
<Main />
|
||||
<BlitzScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument
|
||||
27
examples/cypress/app/pages/index.test.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { render } from "test/utils"
|
||||
import Home from "./index"
|
||||
|
||||
jest.mock("next/data-client", () => ({
|
||||
...jest.requireActual<object>("next/data-client")!,
|
||||
useQuery: () => [
|
||||
{
|
||||
id: 1,
|
||||
name: "User",
|
||||
email: "user@email.com",
|
||||
role: "user",
|
||||
},
|
||||
],
|
||||
}))
|
||||
|
||||
test("renders blitz documentation link", () => {
|
||||
// This is an example of how to ensure a specific item is in the document
|
||||
// But it's disabled by default (by test.skip) so the test doesn't fail
|
||||
// when you remove the the default content from the page
|
||||
|
||||
// This is an example on how to mock api hooks when testing
|
||||
|
||||
const { getByText } = render(<Home />)
|
||||
const element = getByText(/powered by blitz/i)
|
||||
// @ts-ignore
|
||||
expect(element).toBeInTheDocument()
|
||||
})
|
||||
274
examples/cypress/app/pages/index.tsx
Normal file
@@ -0,0 +1,274 @@
|
||||
import { Suspense } from "react"
|
||||
import { Image, Link, BlitzPage, useMutation } from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import { useCurrentUser } from "app/core/hooks/useCurrentUser"
|
||||
import logout from "app/auth/mutations/logout"
|
||||
import logo from "public/logo.png"
|
||||
|
||||
import { Routes } from ".blitz"
|
||||
|
||||
/*
|
||||
* This file is just for a pleasant getting started page for your new app.
|
||||
* You can delete everything in here and start from scratch if you like.
|
||||
*/
|
||||
|
||||
const UserInfo = () => {
|
||||
const currentUser = useCurrentUser()
|
||||
const [logoutMutation] = useMutation(logout)
|
||||
|
||||
if (currentUser) {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="button small"
|
||||
onClick={async () => {
|
||||
await logoutMutation()
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
<div>
|
||||
User id: <code>{currentUser.id}</code>
|
||||
<br />
|
||||
User role: <code>{currentUser.role}</code>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<Link href={Routes.SignupPage()}>
|
||||
<a className="button small">
|
||||
<strong>Sign Up</strong>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={Routes.LoginPage()}>
|
||||
<a className="button small">
|
||||
<strong>Login</strong>
|
||||
</a>
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const Home: BlitzPage = () => {
|
||||
return (
|
||||
<div className="container">
|
||||
<main>
|
||||
<div className="logo">
|
||||
<Image src={logo} alt="blitzjs" />
|
||||
</div>
|
||||
<p>
|
||||
<strong>Congrats!</strong> Your app is ready, including user sign-up and log-in.
|
||||
</p>
|
||||
<div className="buttons" style={{ marginTop: "1rem", marginBottom: "1rem" }}>
|
||||
<Suspense fallback="Loading...">
|
||||
<UserInfo />
|
||||
</Suspense>
|
||||
</div>
|
||||
<p>
|
||||
<strong>
|
||||
To add a new model to your app, <br />
|
||||
run the following in your terminal:
|
||||
</strong>
|
||||
</p>
|
||||
<pre>
|
||||
<code>blitz generate all project name:string</code>
|
||||
</pre>
|
||||
<div style={{ marginBottom: "1rem" }}>(And select Yes to run prisma migrate)</div>
|
||||
<div>
|
||||
<p>
|
||||
Then <strong>restart the server</strong>
|
||||
</p>
|
||||
<pre>
|
||||
<code>Ctrl + c</code>
|
||||
</pre>
|
||||
<pre>
|
||||
<code>blitz dev</code>
|
||||
</pre>
|
||||
<p>
|
||||
and go to{" "}
|
||||
<Link href="/projects">
|
||||
<a>/projects</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
<div className="buttons" style={{ marginTop: "5rem" }}>
|
||||
<a
|
||||
className="button"
|
||||
href="https://blitzjs.com/docs/getting-started?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
<a
|
||||
className="button-outline"
|
||||
href="https://github.com/blitz-js/blitz"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Github Repo
|
||||
</a>
|
||||
<a
|
||||
className="button-outline"
|
||||
href="https://discord.blitzjs.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Discord Community
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<a
|
||||
href="https://blitzjs.com?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Powered by Blitz.js
|
||||
</a>
|
||||
</footer>
|
||||
|
||||
<style jsx global>{`
|
||||
@import url("https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;700&display=swap");
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 5rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main p {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
border-top: 1px solid #eaeaea;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #45009d;
|
||||
}
|
||||
|
||||
footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #f4f4f4;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-gap: 0.5rem;
|
||||
}
|
||||
.button {
|
||||
font-size: 1rem;
|
||||
background-color: #6700eb;
|
||||
padding: 1rem 2rem;
|
||||
color: #f4f4f4;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button.small {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #45009d;
|
||||
}
|
||||
|
||||
.button-outline {
|
||||
border: 2px solid #6700eb;
|
||||
padding: 1rem 2rem;
|
||||
color: #6700eb;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button-outline:hover {
|
||||
border-color: #45009d;
|
||||
color: #45009d;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
code {
|
||||
font-size: 0.9rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
max-width: 800px;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Home.suppressFirstRenderFlicker = true
|
||||
Home.getLayout = (page) => <Layout title="Home">{page}</Layout>
|
||||
|
||||
export default Home
|
||||
13
examples/cypress/app/users/queries/getCurrentUser.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Ctx } from "blitz"
|
||||
import db from "db"
|
||||
|
||||
export default async function getCurrentUser(_ = null, { session }: Ctx) {
|
||||
if (!session.userId) return null
|
||||
|
||||
const user = await db.user.findFirst({
|
||||
where: { id: session.userId },
|
||||
select: { id: true, name: true, email: true, role: true },
|
||||
})
|
||||
|
||||
return user
|
||||
}
|
||||
4
examples/cypress/babel.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ["blitz/babel"],
|
||||
plugins: [],
|
||||
}
|
||||
19
examples/cypress/blitz.config.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { BlitzConfig, sessionMiddleware, simpleRolesIsAuthorized } from "blitz"
|
||||
|
||||
const config: BlitzConfig = {
|
||||
middleware: [
|
||||
sessionMiddleware({
|
||||
cookiePrefix: "cypress-example",
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
}),
|
||||
],
|
||||
/* Uncomment this to customize the webpack config
|
||||
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
|
||||
// Note: we provide webpack above so you should not `require` it
|
||||
// Perform customizations to webpack config
|
||||
// Important: return the modified config
|
||||
return config
|
||||
},
|
||||
*/
|
||||
}
|
||||
module.exports = config
|
||||
11
examples/cypress/cypress.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3099",
|
||||
"integrationFolder": "test/e2e",
|
||||
"fixturesFolder": false,
|
||||
"nodeVersion": "system",
|
||||
"defaultCommandTimeout": 12000,
|
||||
"pageLoadTimeout": 120000,
|
||||
"ignoreTestFiles": ["tsconfig.json"],
|
||||
"chromeWebSecurity": false,
|
||||
"video": false
|
||||
}
|
||||
10
examples/cypress/cypress/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
plugins: ["cypress"],
|
||||
env: {
|
||||
"cypress/globals": true,
|
||||
},
|
||||
extends: ["plugin:cypress/recommended"],
|
||||
rules: {
|
||||
"cypress/no-unnecessary-waiting": "off",
|
||||
},
|
||||
}
|
||||
22
examples/cypress/cypress/global.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
declare namespace Cypress {
|
||||
export interface Chainable {
|
||||
// task for totally resetting the database
|
||||
task(event: "db:reset", options?: Partial<Loggable & Timeoutable>): Chainable<boolean>
|
||||
// task for deleting db data without dropping tables
|
||||
task(event: "db:clear", options?: Partial<Loggable & Timeoutable>): Chainable<boolean>
|
||||
// task for seeding db with essential data for tests
|
||||
task(event: "db:seed", options?: Partial<Loggable & Timeoutable>): Chainable<boolean>
|
||||
|
||||
// task for creating a new factory
|
||||
task(
|
||||
event: "factory",
|
||||
args: { name: string; attrs?: any },
|
||||
options?: Partial<Loggable & Timeoutable>
|
||||
): Chainable<any>
|
||||
|
||||
/**
|
||||
* Logs-in user by using API request
|
||||
*/
|
||||
login({ email: string, password: string }): Chainable<Response>
|
||||
}
|
||||
}
|
||||
55
examples/cypress/cypress/plugins/index.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
process.env.NODE_ENV = "test"
|
||||
|
||||
import { loadEnvConfig } from "@blitzjs/env"
|
||||
loadEnvConfig()
|
||||
|
||||
import "./register-ts-paths"
|
||||
import db from "db"
|
||||
import seed from "db/seeds"
|
||||
import { factory } from "test/factories"
|
||||
|
||||
let dbSetup = false
|
||||
|
||||
const pluginConfig: Cypress.PluginConfig = (on, _config) => {
|
||||
on("task", {
|
||||
factory,
|
||||
"db:reset": async () => {
|
||||
if (!dbSetup) {
|
||||
try {
|
||||
// Only need to do this once at startup
|
||||
console.log("Resetting database...")
|
||||
await db.$reset()
|
||||
console.log("Database reset.")
|
||||
dbSetup = true
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw new Error("Failed to set up database in cypress/plugins/index.ts")
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
// for Postgres
|
||||
// "db:clear": () => {
|
||||
// Delete all data without dropping tables, so migration isn't required
|
||||
// await db.$executeRaw`
|
||||
// do
|
||||
// $$
|
||||
// declare
|
||||
// l_stmt text;
|
||||
// begin
|
||||
// select 'truncate ' || string_agg(format('%I.%I', schemaname, tablename), ',')
|
||||
// into l_stmt from pg_tables
|
||||
// where schemaname in ('public');
|
||||
// execute l_stmt;
|
||||
// end;
|
||||
// $$
|
||||
// `
|
||||
// return true
|
||||
// },
|
||||
"db:seed": async () => {
|
||||
await seed()
|
||||
return true
|
||||
},
|
||||
})
|
||||
}
|
||||
export default pluginConfig
|
||||
2
examples/cypress/cypress/plugins/register-ts-paths.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// @ts-ignore
|
||||
require("tsconfig-paths").register()
|
||||
10
examples/cypress/cypress/support/commands.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import "@testing-library/cypress/add-commands"
|
||||
|
||||
Cypress.Commands.add("login", ({ email, password }) => {
|
||||
return cy.request("POST", `/api/rpc/login`, {
|
||||
params: {
|
||||
email,
|
||||
password,
|
||||
},
|
||||
})
|
||||
})
|
||||
24
examples/cypress/cypress/support/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
import "./commands"
|
||||
|
||||
before(() => {
|
||||
cy.task("db:reset")
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// you can clear the database here
|
||||
cy.task("db:seed")
|
||||
})
|
||||
10
examples/cypress/cypress/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["cypress", "@types/testing-library__cypress"],
|
||||
"isolatedModules": false,
|
||||
"tsBuildInfoFile": ".tsbuildinfo.test"
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
7
examples/cypress/db/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { enhancePrisma } from "blitz"
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
|
||||
const EnhancedPrisma = enhancePrisma(PrismaClient)
|
||||
|
||||
export * from "@prisma/client"
|
||||
export default new EnhancedPrisma()
|
||||
@@ -0,0 +1,47 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT NOT NULL,
|
||||
"hashedPassword" TEXT,
|
||||
"role" TEXT NOT NULL DEFAULT 'USER'
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"expiresAt" DATETIME,
|
||||
"handle" TEXT NOT NULL,
|
||||
"hashedSessionToken" TEXT,
|
||||
"antiCSRFToken" TEXT,
|
||||
"publicData" TEXT,
|
||||
"privateData" TEXT,
|
||||
"userId" INTEGER,
|
||||
FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Token" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"hashedToken" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
"sentTo" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session.handle_unique" ON "Session"("handle");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Token.hashedToken_type_unique" ON "Token"("hashedToken", "type");
|
||||
3
examples/cypress/db/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "sqlite"
|
||||
65
examples/cypress/db/schema.prisma
Normal file
@@ -0,0 +1,65 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = "file:./db.sqlite"
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
name String?
|
||||
email String @unique
|
||||
hashedPassword String?
|
||||
role String @default("USER")
|
||||
|
||||
tokens Token[]
|
||||
sessions Session[]
|
||||
}
|
||||
|
||||
model Session {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
expiresAt DateTime?
|
||||
handle String @unique
|
||||
hashedSessionToken String?
|
||||
antiCSRFToken String?
|
||||
publicData String?
|
||||
privateData String?
|
||||
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
}
|
||||
|
||||
model Token {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
hashedToken String
|
||||
type String
|
||||
// See note below about TokenType enum
|
||||
// type TokenType
|
||||
expiresAt DateTime
|
||||
sentTo String
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
|
||||
@@unique([hashedToken, type])
|
||||
}
|
||||
|
||||
// NOTE: It's highly recommended to use an enum for the token type
|
||||
// but enums only work in Postgres.
|
||||
// See: https://blitzjs.com/docs/database-overview#switch-to-postgre-sql
|
||||
// enum TokenType {
|
||||
// RESET_PASSWORD
|
||||
// }
|
||||
15
examples/cypress/db/seeds.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// import db from "./index"
|
||||
|
||||
/*
|
||||
* This seed function is executed when you run `blitz db seed`.
|
||||
*
|
||||
* Probably you want to use a library like https://chancejs.com
|
||||
* to easily generate realistic data.
|
||||
*/
|
||||
const seed = async () => {
|
||||
// for (let i = 0; i < 5; i++) {
|
||||
// await db.project.create({ data: { name: "Project " + i } })
|
||||
// }
|
||||
}
|
||||
|
||||
export default seed
|
||||
7
examples/cypress/jest.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { Config } from "@jest/types"
|
||||
|
||||
const config: Config.InitialOptions = {
|
||||
preset: "blitz",
|
||||
}
|
||||
|
||||
export default config
|
||||
45
examples/cypress/mailers/forgotPasswordMailer.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/* TODO - You need to add a mailer integration in `integrations/` and import here.
|
||||
*
|
||||
* The integration file can be very simple. Instantiate the email client
|
||||
* and then export it. That way you can import here and anywhere else
|
||||
* and use it straight away.
|
||||
*/
|
||||
|
||||
type ResetPasswordMailer = {
|
||||
to: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export function forgotPasswordMailer({ to, token }: ResetPasswordMailer) {
|
||||
// In production, set APP_ORIGIN to your production server origin
|
||||
const origin = process.env.APP_ORIGIN || process.env.BLITZ_DEV_SERVER_ORIGIN
|
||||
const resetUrl = `${origin}/reset-password?token=${token}`
|
||||
|
||||
const msg = {
|
||||
from: "TODO@example.com",
|
||||
to,
|
||||
subject: "Your Password Reset Instructions",
|
||||
html: `
|
||||
<h1>Reset Your Password</h1>
|
||||
<h3>NOTE: You must set up a production email integration in mailers/forgotPasswordMailer.ts</h3>
|
||||
|
||||
<a href="${resetUrl}">
|
||||
Click here to set a new password
|
||||
</a>
|
||||
`,
|
||||
}
|
||||
|
||||
return {
|
||||
async send() {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
// TODO - send the production email, like this:
|
||||
// await postmark.sendEmail(msg)
|
||||
throw new Error("No production email implementation in mailers/forgotPasswordMailer")
|
||||
} else {
|
||||
// Preview email in the browser
|
||||
const previewEmail = (await import("preview-email")).default
|
||||
await previewEmail(msg)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
62
examples/cypress/package.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@examples/cypress",
|
||||
"version": "0.41.1",
|
||||
"scripts": {
|
||||
"dev": "blitz dev",
|
||||
"build": "blitz build",
|
||||
"start": "blitz start",
|
||||
"studio": "blitz prisma studio",
|
||||
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
|
||||
"cy:open": "cypress open",
|
||||
"cy:run": "cypress run",
|
||||
"test:server": "blitz prisma migrate deploy && blitz build && blitz start -p 3099",
|
||||
"test:e2e": "cross-env NODE_ENV=test start-server-and-test test:server http://localhost:3099 cy:run",
|
||||
"test:jest": "jest --passWithNoTests",
|
||||
"test": "blitz codegen && prisma generate && yarn test:jest && yarn test:e2e"
|
||||
},
|
||||
"prisma": {
|
||||
"schema": "db/schema.prisma"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 100
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,tsx}": [
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "tsc && lint-staged && pretty-quick --staged",
|
||||
"pre-push": "npm run lint && npm run test"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "2.24.1",
|
||||
"blitz": "0.45.4",
|
||||
"final-form": "4.20.1",
|
||||
"react": "0.0.0-experimental-6a589ad71",
|
||||
"react-dom": "0.0.0-experimental-6a589ad71",
|
||||
"react-final-form": "6.5.2",
|
||||
"zod": "3.10.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/cypress": "8.0.1",
|
||||
"@types/preview-email": "2.0.0",
|
||||
"@types/react": "17.0.2",
|
||||
"@types/testing-library__cypress": "5.0.9",
|
||||
"chance": "1.1.8",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "6.2.1",
|
||||
"eslint": "7.21.0",
|
||||
"husky": "5.1.2",
|
||||
"lint-staged": "10.5.4",
|
||||
"prettier": "2.2.1",
|
||||
"pretty-quick": "3.1.0",
|
||||
"preview-email": "3.0.3",
|
||||
"prisma": "2.24.1",
|
||||
"start-server-and-test": "1.11.7"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
BIN
examples/cypress/public/favicon.ico
Executable file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
examples/cypress/public/logo.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
3
examples/cypress/test/e2e/.eslintrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ["../../cypress/.eslintrc.js"],
|
||||
}
|
||||
35
examples/cypress/test/e2e/login.e2e.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { User } from "test/factories"
|
||||
|
||||
describe("Login", () => {
|
||||
describe("with an email that doesnt exist", () => {
|
||||
it("shows an error", () => {
|
||||
const email = "nowayshouldIexist@example.com"
|
||||
const password = "test1234"
|
||||
|
||||
cy.visit("/login").wait(100)
|
||||
|
||||
cy.findByLabelText(/email/i).type(email)
|
||||
cy.findByLabelText(/password/i).type(password)
|
||||
cy.findAllByRole("button", { name: /login/i }).click()
|
||||
|
||||
cy.findByText(/invalid/i).should("exist")
|
||||
})
|
||||
})
|
||||
|
||||
describe("with valid credentials", () => {
|
||||
it("logs in", () => {
|
||||
const attrs = { password: "superstrongpassword" }
|
||||
|
||||
cy.visit("/login").wait(100)
|
||||
|
||||
cy.task("factory", { name: "user", attrs }).then((user: User) => {
|
||||
cy.findByLabelText(/email/i).type(user.email)
|
||||
cy.findByLabelText(/password/i).type(attrs.password)
|
||||
cy.findAllByRole("button", { name: /login/i }).click()
|
||||
|
||||
cy.location("pathname").should("equal", "/")
|
||||
cy.findByText(/logout/i).should("exist")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
14
examples/cypress/test/e2e/signup.e2e.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
describe("Signup", () => {
|
||||
it("creates new account", () => {
|
||||
const attrs = { email: "blitz@example.com", password: "superstrongpassword" }
|
||||
|
||||
cy.visit("/signup").wait(100)
|
||||
|
||||
cy.findByLabelText(/email/i).type(attrs.email)
|
||||
cy.findByLabelText(/password/i).type(attrs.password)
|
||||
cy.findAllByRole("button", { name: /create account/i }).click()
|
||||
|
||||
cy.location("pathname").should("equal", "/")
|
||||
cy.findByText(/logout/i).should("exist")
|
||||
})
|
||||
})
|
||||
9
examples/cypress/test/e2e/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["cypress", "@types/testing-library__cypress"],
|
||||
"isolatedModules": false
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["**/*.ts", "../../cypress/**/*.ts"]
|
||||
}
|
||||
16
examples/cypress/test/factories/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { user } from "./user"
|
||||
export * from "./user"
|
||||
|
||||
export const Factories = {
|
||||
user,
|
||||
}
|
||||
|
||||
export function factory<T extends keyof typeof Factories>({
|
||||
name,
|
||||
attrs,
|
||||
}: {
|
||||
name: T
|
||||
attrs: Parameters<typeof Factories[T]>[0]
|
||||
}) {
|
||||
return Factories[name](attrs)
|
||||
}
|
||||
22
examples/cypress/test/factories/user.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { PromiseReturnType } from "next/types/utils"
|
||||
import signup from "app/auth/mutations/signup"
|
||||
import Chance from "chance"
|
||||
|
||||
const chance = new Chance()
|
||||
|
||||
const ctx = {
|
||||
session: { $create: () => {} },
|
||||
}
|
||||
|
||||
type UserAttributes = {
|
||||
email?: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export const user = async ({ email = chance.email(), password }: UserAttributes) => {
|
||||
const user = await signup({ email, password }, ctx as any)
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
export type User = PromiseReturnType<typeof user>
|
||||
4
examples/cypress/test/setup.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This is the jest 'setupFilesAfterEnv' setup file
|
||||
// It's a good place to set globals, add global before/after hooks, etc
|
||||
|
||||
export {} // so TS doesn't complain
|
||||
105
examples/cypress/test/utils.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { RouterContext, BlitzRouter, BlitzProvider } from "blitz"
|
||||
import { render as defaultRender } from "@testing-library/react"
|
||||
import { renderHook as defaultRenderHook } from "@testing-library/react-hooks"
|
||||
|
||||
export * from "@testing-library/react"
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// This file customizes the render() and renderHook() test functions provided
|
||||
// by React testing library. It adds a router context wrapper with a mocked router.
|
||||
//
|
||||
// You should always import `render` and `renderHook` from this file
|
||||
//
|
||||
// This is the place to add any other context providers you need while testing.
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// --------------------------------------------------
|
||||
// render()
|
||||
// --------------------------------------------------
|
||||
// Override the default test render with our own
|
||||
//
|
||||
// You can override the router mock like this:
|
||||
//
|
||||
// const { baseElement } = render(<MyComponent />, {
|
||||
// router: { pathname: '/my-custom-pathname' },
|
||||
// });
|
||||
// --------------------------------------------------
|
||||
export function render(
|
||||
ui: RenderUI,
|
||||
{ wrapper, router, dehydratedState, ...options }: RenderOptions = {}
|
||||
) {
|
||||
if (!wrapper) {
|
||||
// Add a default context wrapper if one isn't supplied from the test
|
||||
wrapper = ({ children }) => (
|
||||
<BlitzProvider dehydratedState={dehydratedState}>
|
||||
<RouterContext.Provider value={{ ...mockRouter, ...router }}>
|
||||
{children}
|
||||
</RouterContext.Provider>
|
||||
</BlitzProvider>
|
||||
)
|
||||
}
|
||||
return defaultRender(ui, { wrapper, ...options })
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// renderHook()
|
||||
// --------------------------------------------------
|
||||
// Override the default test renderHook with our own
|
||||
//
|
||||
// You can override the router mock like this:
|
||||
//
|
||||
// const result = renderHook(() => myHook(), {
|
||||
// router: { pathname: '/my-custom-pathname' },
|
||||
// });
|
||||
// --------------------------------------------------
|
||||
export function renderHook(
|
||||
hook: RenderHook,
|
||||
{ wrapper, router, dehydratedState, ...options }: RenderHookOptions = {}
|
||||
) {
|
||||
if (!wrapper) {
|
||||
// Add a default context wrapper if one isn't supplied from the test
|
||||
wrapper = ({ children }) => (
|
||||
<BlitzProvider dehydratedState={dehydratedState}>
|
||||
<RouterContext.Provider value={{ ...mockRouter, ...router }}>
|
||||
{children}
|
||||
</RouterContext.Provider>
|
||||
</BlitzProvider>
|
||||
)
|
||||
}
|
||||
return defaultRenderHook(hook, { wrapper, ...options })
|
||||
}
|
||||
|
||||
export const mockRouter: BlitzRouter = {
|
||||
basePath: "",
|
||||
pathname: "/",
|
||||
route: "/",
|
||||
asPath: "/",
|
||||
params: {},
|
||||
query: {},
|
||||
isReady: true,
|
||||
isLocaleDomain: false,
|
||||
isPreview: false,
|
||||
push: jest.fn(),
|
||||
replace: jest.fn(),
|
||||
reload: jest.fn(),
|
||||
back: jest.fn(),
|
||||
prefetch: jest.fn(),
|
||||
beforePopState: jest.fn(),
|
||||
events: {
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
},
|
||||
isFallback: false,
|
||||
}
|
||||
|
||||
type DefaultParams = Parameters<typeof defaultRender>
|
||||
type RenderUI = DefaultParams[0]
|
||||
type RenderOptions = DefaultParams[1] & { router?: Partial<BlitzRouter>; dehydratedState?: unknown }
|
||||
|
||||
type DefaultHookParams = Parameters<typeof defaultRenderHook>
|
||||
type RenderHook = DefaultHookParams[0]
|
||||
type RenderHookOptions = DefaultHookParams[1] & {
|
||||
router?: Partial<BlitzRouter>
|
||||
dehydratedState?: unknown
|
||||
}
|
||||
25
examples/cypress/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"types": ["jest", "@testing-library/react", "@testing-library/jest-dom"],
|
||||
"baseUrl": "./",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": ".tsbuildinfo"
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.e2e.ts", "cypress"],
|
||||
"include": ["blitz-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
25
examples/cypress/types.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { DefaultCtx, SessionContext, SimpleRolesIsAuthorized } from "blitz"
|
||||
import { User } from "db"
|
||||
|
||||
export type Role = "ADMIN" | "USER"
|
||||
|
||||
declare module "blitz" {
|
||||
export interface Ctx extends DefaultCtx {
|
||||
session: SessionContext
|
||||
}
|
||||
export interface Session {
|
||||
isAuthorized: SimpleRolesIsAuthorized<Role>
|
||||
PublicData: {
|
||||
userId: User["id"]
|
||||
role: Role
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should not be needed. Usually it isn't but for some reason in this example it is
|
||||
declare module "react" {
|
||||
interface StyleHTMLAttributes<T> extends React.HTMLAttributes<T> {
|
||||
jsx?: boolean
|
||||
global?: boolean
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PropsWithoutRef } from "react"
|
||||
import React, { PropsWithoutRef } from "react"
|
||||
import { useField } from "react-final-form"
|
||||
|
||||
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { ReactNode } from "react"
|
||||
import { Head } from "blitz"
|
||||
import { Head, BlitzLayout } from "blitz"
|
||||
|
||||
type LayoutProps = {
|
||||
title?: string
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const Layout = ({ title, children }: LayoutProps) => {
|
||||
const Layout: BlitzLayout<{ title?: string }> = ({ title, children }) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
* This seed function is executed when you run `blitz db seed`.
|
||||
*
|
||||
* Probably you want to use a library like https://chancejs.com
|
||||
* or https://github.com/Marak/Faker.js to easily generate
|
||||
* realistic data.
|
||||
* to easily generate realistic data.
|
||||
*/
|
||||
const seed = async () => {
|
||||
// for (let i = 0; i < 5; i++) {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"blitz": "0.40.0",
|
||||
"blitz": "0.45.4",
|
||||
"final-form": "4.20.1",
|
||||
"graphql": "15.5.0",
|
||||
"graphql-request": "3.4.0",
|
||||
|
||||
@@ -3,4 +3,6 @@
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import "@testing-library/jest-dom/extend-expect"
|
||||
require("dotenv-flow").config({ silent: true })
|
||||
import { loadEnvConfig } from "@blitzjs/env"
|
||||
|
||||
loadEnvConfig()
|
||||
|
||||
11
examples/i18n-next-rosetta/.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
# https://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
3
examples/i18n-next-rosetta/.env
Normal file
@@ -0,0 +1,3 @@
|
||||
# This env file should be checked into source control
|
||||
# This is the place for default values for all environments
|
||||
# Values in `.env.local` and `.env.production` will override these values
|
||||
3
examples/i18n-next-rosetta/.eslintrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ["blitz"],
|
||||
}
|
||||
56
examples/i18n-next-rosetta/.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
.npm
|
||||
web_modules/
|
||||
|
||||
# blitz
|
||||
/.blitz/
|
||||
/.next/
|
||||
*.sqlite
|
||||
*.sqlite-journal
|
||||
.now
|
||||
.blitz**
|
||||
blitz-log.log
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
.envrc
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Testing
|
||||
.coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
lib-cov
|
||||
|
||||
# Caches
|
||||
*.tsbuildinfo
|
||||
.eslintcache
|
||||
.node_repl_history
|
||||
.yarn-integrity
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
5
examples/i18n-next-rosetta/.husky/pre-commit
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
npx pretty-quick --staged
|
||||