1
0
mirror of synced 2026-02-03 18:01:02 -05:00

Compare commits

...

56 Commits

Author SHA1 Message Date
Brandon Bayer
a0b7a08db6 even stricter typing by removing undefined only if suspense & enabled are true 2021-02-10 12:14:10 -05:00
Brian Liu
91d7a8a353 Merge branch 'canary' into use-query-data-type 2021-02-10 13:48:55 +11:00
Brian Liu
aa681ff50c Added TS function overload for return types 2021-02-10 13:05:26 +11:00
Brandon Bayer
1792cc6788 v0.30.0-canary.7 2021-02-09 19:36:42 -05:00
Michael Ford
dc88bc68d2 Change useSession to use suspense by default (#1888)
Co-authored-by: Brandon Bayer <b@bayer.ws> (major)
2021-02-09 19:21:09 -05:00
allcontributors[bot]
37348f2595 docs: add mtford90 as a contributor (#1899)
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-02-09 18:53:25 -05:00
Brandon Bayer
bec3cd6cde Add internal BlitzAppRoot component (meta) (#1898) 2021-02-09 18:45:19 -05:00
Brandon Bayer
a9c1171a14 Fix vercel deployments with 0.30-canary (#1897)
(patch)
2021-02-09 18:26:53 -05:00
Brian Liu
7b7eaeee4b Revert "Fixed useQuery return data type"
This reverts commit cb2b6a1777.
2021-02-10 10:08:56 +11:00
Brian Liu
a96b8735cc Revert "Fixed test fails"
This reverts commit cc64243006.
2021-02-10 10:08:44 +11:00
allcontributors[bot]
b46a245f08 docs: add rodrigoehlers as a contributor (#1896)
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-02-09 17:20:39 -05:00
Rodrigo Ehlers
a5208e2b96 Fix useSession return type to have userId: number | null (#1895)
(patch)
2021-02-09 17:20:24 -05:00
Camilo Gonzalez
4fefbcbbb0 Automatically prompt to run prisma migrate after blitz generate (minor) (#1894)
Co-authored-by: Brandon Bayer <b@bayer.ws>
2021-02-09 12:58:22 -05:00
engelkes-finstreet
258c0491dd Add initialPublicData option to useSession for use with SSR (minor) (#1807)
* Add initial parameter to useSession hook to remove flickering if session is loaded via server side.

* Use better naming.

* Update packages/core/src/supertokens.ts

* fix and cleanup types

Co-authored-by: Brandon Bayer <b@bayer.ws>
2021-02-09 17:12:51 +00:00
Brian Liu
cc64243006 Fixed test fails 2021-02-09 12:25:06 +11:00
Brian Liu
cb2b6a1777 Fixed useQuery return data type 2021-02-09 11:26:42 +11:00
allcontributors[bot]
8ec0d929d8 docs: add lcswillems as a contributor (#1892)
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-02-08 17:24:46 -05:00
Lucas Willems
adfc529852 Fix typo in anti-csrf cookie name (#1889)
(patch)
2021-02-08 17:24:31 -05:00
depfu[bot]
aebc79fe9c Update all Yarn dependencies (2021-02-08) (#1886)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> (patch)
2021-02-08 17:18:38 -05:00
Brandon Bayer
da6393c538 v0.30.0-canary.6 2021-02-06 19:06:28 -05:00
Brandon Bayer
e51a002892 Add SimpleRolesIsAuthorized<RoleType> type so session.$authorize() can type check the roles AND update new app template accordingly (#1883)
* add `SimpleRolesIsAuthorized<RoleType>` type so `session.$authorize()` can type check the roles AND update new app template accordingly (minor)

* fix type
2021-02-06 19:00:08 -05:00
Brandon Bayer
d73750be0c Fix all known resolver.pipe type issues AND update getModelNames template to use resolver.pipe (#1881)
(patch)
2021-02-06 18:23:29 -05:00
allcontributors[bot]
95781eb6ba docs: add alii as a contributor (#1882)
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-02-06 18:06:06 -05:00
Alistair Smith
afcd47569c Fix blitz install [RECIPE] not working (#1880)
(patch)
2021-02-06 18:05:44 -05:00
Brandon Bayer
f405b5b4df update auth example project pages from latest templates
(meta)
2021-02-06 17:07:31 -05:00
Brandon Bayer
a0d7378642 v0.30.0-canary.5 2021-02-05 15:51:24 -05:00
Brandon Bayer
60bba38919 fix dependency issue (ignore) 2021-02-05 15:49:27 -05:00
Stratulat Alexandru
901d1cad7e Add paginate resolver utility and add it to blitz generate template (#1199)
Co-authored-by: Brandon Bayer <b@bayer.ws> (minor)
2021-02-05 15:47:16 -05:00
Brandon Bayer
383034d1fe change jest-preset to use .server.test.ts instead of .test.server.ts (ignore) (#1875)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2021-02-05 14:15:16 -05:00
Brandon Bayer
bd6f37a6b0 (recipe) add gh-action-yarn-postgres recipe which adds a production github action config (#1876) 2021-02-05 11:39:37 -05:00
Brandon Bayer
b1116b6052 end g2i sponsor trial
(ignore)
2021-02-04 21:01:26 -05:00
Brandon Bayer
85c91e2e2e update jest preset with auto client/server environments and ability to use file.test.server.ts (minor) (#1873)
* update jest preset with auto client/server environments and ability to use `file.test.server.ts`

* cleanup
2021-02-05 01:58:20 +00:00
Brandon Bayer
0bad1f181b Massive update to blitz generate templates to generate production ready code (#1870)
(minor)
2021-02-04 20:35:22 -05:00
Brandon Bayer
971e695b30 Change blitz start to blitz dev and blitz start --production to blitz start (#1872)
* Change `blitz start` to `blitz dev` and `blitz start --production` to `blitz start`

* fix alias

* fix help msg (major)
2021-02-04 18:26:29 -05:00
Brandon Bayer
c0e1246dc0 add blitz generate mutation (minor) (#1871) 2021-02-04 18:05:58 -05:00
Brandon Bayer
bce9822d13 Revert "Change blitz start to blitz dev and blitz start --production to blitz start"
This reverts commit e41219944c.
2021-02-04 17:57:19 -05:00
Brandon Bayer
e41219944c Change blitz start to blitz dev and blitz start --production to blitz start 2021-02-04 17:56:30 -05:00
Brandon Bayer
fe0d958368 (newapp) upgrade prisma to 2.16 and change @prisma/cli to new prisma name (#1864)
* (newapp) upgrade prisma to 2.16 and change `@prisma/cli` to new `prisma` name

* fix cli

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2021-02-04 18:27:19 +00:00
depfu[bot]
06248f6005 Upgrade next: 10.0.5 → 10.0.6 and refactor runtime require statements (patch) (#1828)
* Update next to version 10.0.6

* type fixes

* big refactor to fix resolve config

* more refactor and fix

* fix jest

* fixes

* fix import

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Brandon Bayer <b@bayer.ws>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2021-02-04 18:06:15 +00:00
TagawaHirotaka
24b85b0108 Add cypress for e2e cli testing (meta) (#1846)
* install cypress

* change describe

* refactor tsconfig.json

* Move cypress dependency to the very root package.json of the repo

* Move e2e test scripts to packages/cli/package.json

* Remove package.json

* forget to add it

* setting cypress.json

* fix cypress install

Co-authored-by: Brandon Bayer <b@bayer.ws>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2021-02-04 17:31:31 +00:00
Brandon Bayer
7804d3ea77 Add @lcswillems as a contributor 2021-02-04 12:06:51 -05:00
allcontributors[bot]
6dca78a8ed docs: add merodiro as a contributor (#1862)
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-02-04 11:56:38 -05:00
Amr A.Mohammed
92679cfa03 (newapp) Add legacy-peer-deps to .npmrc for npm v7 compatibility (#1859) 2021-02-04 11:56:28 -05:00
allcontributors[bot]
46cb60a962 docs: add wafuwafu13 as a contributor (#1861)
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-02-04 11:46:03 -05:00
depfu[bot]
012d146fd9 Upgrade superjson: 1.5.2 → 1.6.0 (patch) (#1854)
* Update superjson to version 1.6.0

* fix

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Brandon Bayer <b@bayer.ws>
2021-02-03 20:58:01 +00:00
Brandon Bayer
5015693ddd fix db.$reset() error "cached plan must not change result type" (patch) (#1855) 2021-02-03 20:34:47 +00:00
depfu[bot]
e2ab39ed75 Update tslog to version 3.1.1 (#1853)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2021-02-03 16:54:10 +00:00
Andreas Bollig
64ced80f77 fix blitz new for people without global git config (patch) (#1847)
* fix blitz new for people without global git config

* adjusted test to reflect changed git parameters in blitz new

* Update packages/generator/src/generators/app-generator.ts

* Update packages/generator/test/generators/app-generator.test.ts

Co-authored-by: Brandon Bayer <b@bayer.ws>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2021-02-03 16:33:54 +00:00
allcontributors[bot]
050c4f7127 docs: add ajmarkow as a contributor (#1852)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-02-03 11:08:31 -05:00
allcontributors[bot]
86de3303bf docs: add akbo as a contributor (#1851)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-02-03 11:07:16 -05:00
allcontributors[bot]
31724c7b2a docs: add Gim3l as a contributor (#1849)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-02-02 16:30:26 -05:00
Gimel Dick
d7647ad2be Add to new app template (#1844) 2021-02-02 16:30:15 -05:00
depfu[bot]
0a3836b30b Update all dependencies (2021-02-01) (#1839)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> (patch)
2021-02-01 18:15:09 -05:00
t.kuriyama
1fccb1dc19 Fix several compiler bugs like ENOENT, file rename broken, default export not component, etc (#1835)
(patch)
2021-02-01 18:02:41 -05:00
Brandon Bayer
7195aaea66 Add @queq1890 as a contributor 2021-02-01 17:51:23 -05:00
Brandon Bayer
d1c4553ddd (newapp) small tweak to error fallback in _app.tsx 2021-02-01 17:50:31 -05:00
210 changed files with 3437 additions and 2123 deletions

View File

@@ -1882,6 +1882,99 @@
"design",
"code"
]
},
{
"login": "queq1890",
"name": "Yuji Matsumoto",
"avatar_url": "https://avatars.githubusercontent.com/u/32263803?v=4",
"profile": "http://queq1890.info",
"contributions": [
"doc"
]
},
{
"login": "Gim3l",
"name": "Gimel Dick",
"avatar_url": "https://avatars.githubusercontent.com/u/46765702?v=4",
"profile": "https://github.com/Gim3l",
"contributions": [
"code"
]
},
{
"login": "akbo",
"name": "Andreas Bollig",
"avatar_url": "https://avatars.githubusercontent.com/u/1926271?v=4",
"profile": "https://github.com/akbo",
"contributions": [
"code"
]
},
{
"login": "ajmarkow",
"name": "AJ Markow",
"avatar_url": "https://avatars.githubusercontent.com/u/66390428?v=4",
"profile": "https://ajm.codes",
"contributions": [
"test",
"code"
]
},
{
"login": "wafuwafu13",
"name": "TagawaHirotaka",
"avatar_url": "https://avatars.githubusercontent.com/u/50798936?v=4",
"profile": "https://wafuwafu13.hateblo.jp/",
"contributions": [
"code",
"test"
]
},
{
"login": "merodiro",
"name": "Amr A.Mohammed",
"avatar_url": "https://avatars.githubusercontent.com/u/17033502?v=4",
"profile": "https://github.com/merodiro",
"contributions": [
"code"
]
},
{
"login": "lcswillems",
"name": "Lucas Willems",
"avatar_url": "https://avatars.githubusercontent.com/u/5437552?v=4",
"profile": "http://www.lucaswillems.com",
"contributions": [
"doc",
"code"
]
},
{
"login": "alii",
"name": "Alistair Smith",
"avatar_url": "https://avatars.githubusercontent.com/u/25351731?v=4",
"profile": "https://alistair.cloud",
"contributions": [
"code"
]
},
{
"login": "rodrigoehlers",
"name": "Rodrigo Ehlers",
"avatar_url": "https://avatars.githubusercontent.com/u/19683042?v=4",
"profile": "https://rodrigoehlers.com",
"contributions": [
"code"
]
},
{
"login": "mtford90",
"name": "Michael Ford",
"avatar_url": "https://avatars.githubusercontent.com/u/1734057?v=4",
"profile": "https://www.builtopen.com/",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.log
.DS_Store
.idea
.jest-*
lib
node_modules
@@ -24,3 +25,5 @@ dist
.tsbuildinfo
.nvmrc
**/.test*
examples/auth2
.idea

View File

@@ -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-199-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-209-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">
@@ -48,7 +48,7 @@ _You can alternatively use [`npx`](https://www.npmjs.com/package/npx)_
1. `blitz new myAppName`
2. `cd myAppName`
3. `blitz start`
3. `blitz dev`
4. View your baby app at http://localhost:3000
<br><br>
@@ -149,11 +149,9 @@ Your financial contributions help ensure Blitz continues to be developed and mai
### 🏆 Gold Sponsors
<div>
<a aria-label="G2i" href="http://g2i.co/sign-up?utm_source=blitz&utm_medium=referral&utm_campaign=blitz2020">
<img alt="" src="https://files-5oz00y7xp.vercel.app/G2i_Logo_wwords.png" width="160px">
<a aria-label="Your Company" href="#">
<img alt="" src="https://dummyimage.com/1000x330/efe8ff/000000.png&text=Your+Logo+Here" width="300px">
</a>
</div>
### 💎 Diamond Sponsors
@@ -489,6 +487,18 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
<td align="center"><a href="https://github.com/jonasthiesen"><img src="https://avatars.githubusercontent.com/u/23408018?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jonas Thiesen</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jonasthiesen" title="Documentation">📖</a></td>
<td align="center"><a href="https://thakkaryash94.github.io/"><img src="https://avatars.githubusercontent.com/u/7349778?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yash Thakkar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=thakkaryash94" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/rince"><img src="https://avatars.githubusercontent.com/u/933895?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kazuma Suzuki</b></sub></a><br /><a href="#design-rince" title="Design">🎨</a> <a href="https://github.com/blitz-js/blitz/commits?author=rince" title="Code">💻</a></td>
<td align="center"><a href="http://queq1890.info"><img src="https://avatars.githubusercontent.com/u/32263803?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yuji Matsumoto</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=queq1890" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/Gim3l"><img src="https://avatars.githubusercontent.com/u/46765702?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gimel Dick</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Gim3l" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/akbo"><img src="https://avatars.githubusercontent.com/u/1926271?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andreas Bollig</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=akbo" title="Code">💻</a></td>
<td align="center"><a href="https://ajm.codes"><img src="https://avatars.githubusercontent.com/u/66390428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>AJ Markow</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ajmarkow" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=ajmarkow" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://wafuwafu13.hateblo.jp/"><img src="https://avatars.githubusercontent.com/u/50798936?v=4?s=100" width="100px;" alt=""/><br /><sub><b>TagawaHirotaka</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wafuwafu13" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=wafuwafu13" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/merodiro"><img src="https://avatars.githubusercontent.com/u/17033502?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Amr A.Mohammed</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=merodiro" title="Code">💻</a></td>
<td align="center"><a href="http://www.lucaswillems.com"><img src="https://avatars.githubusercontent.com/u/5437552?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lucas Willems</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lcswillems" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=lcswillems" title="Code">💻</a></td>
<td align="center"><a href="https://alistair.cloud"><img src="https://avatars.githubusercontent.com/u/25351731?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alistair Smith</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=alii" title="Code">💻</a></td>
<td align="center"><a href="https://rodrigoehlers.com"><img src="https://avatars.githubusercontent.com/u/19683042?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rodrigo Ehlers</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rodrigoehlers" title="Code">💻</a></td>
<td align="center"><a href="https://www.builtopen.com/"><img src="https://avatars.githubusercontent.com/u/1734057?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Ford</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mtford90" title="Code">💻</a></td>
</tr>
</table>

View File

@@ -20,7 +20,7 @@ blitz prisma migrate dev --preview-feature
3. Start the dev server
```
blitz start
blitz dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

View File

@@ -3,6 +3,7 @@ import db from "db"
import {Strategy as TwitterStrategy} from "passport-twitter"
import {Strategy as GitHubStrategy} from "passport-github2"
import {Strategy as Auth0Strategy} from "passport-auth0"
import {Role} from "types"
function assert(condition: any, message: string): asserts condition {
if (!condition) throw new Error(message)
@@ -91,7 +92,7 @@ export default passportAuth({
const publicData = {
userId: user.id,
roles: [user.role],
roles: [user.role as Role],
source: "github",
githubUsername: profile.username,
}

View File

@@ -1,7 +1,8 @@
import {Link, useMutation, AuthenticationError} from "blitz"
import {LabeledTextField} from "app/components/LabeledTextField"
import {Form, FORM_ERROR} from "app/components/Form"
import login, {LoginInput} from "app/auth/mutations/login"
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
@@ -9,17 +10,19 @@ type LoginFormProps = {
export const LoginForm = (props: LoginFormProps) => {
const [loginMutation] = useMutation(login)
return (
<div>
<h1>Login</h1>
<Form
submitText="Login"
schema={LoginInput}
initialValues={{email: undefined, password: undefined}}
schema={Login}
initialValues={{email: "", password: ""}}
onSubmit={async (values) => {
try {
await loginMutation(values)
props.onSuccess && props.onSuccess()
props.onSuccess?.()
} catch (error) {
if (error instanceof AuthenticationError) {
return {[FORM_ERROR]: "Sorry, those credentials are invalid"}
@@ -34,7 +37,13 @@ export const LoginForm = (props: LoginFormProps) => {
>
<LabeledTextField name="email" label="Email" placeholder="Email" />
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
<div>
<Link href="/forgot-password">
<a>Forgot your password?</a>
</Link>
</div>
</Form>
<div style={{marginTop: "1rem"}}>
Or <Link href="/signup">Sign Up</Link>
</div>

View File

@@ -0,0 +1,44 @@
import React from "react"
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) {
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

View 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)
await db.user.update({
where: {id: user.id},
data: {hashedPassword},
})
return true
},
)

View 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("blitz", () => ({
...jest.requireActual("blitz")!,
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()
})
})

View 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
})

View File

@@ -1,6 +1,7 @@
import {resolver, SecurePassword, AuthenticationError} from "blitz"
import db from "db"
import * as z from "zod"
import {Login} from "../validations"
import {Role} from "types"
export const authenticateUser = async (email: string, password: string) => {
const user = await db.user.findFirst({where: {email}})
@@ -18,16 +19,11 @@ export const authenticateUser = async (email: string, password: string) => {
return rest
}
export const LoginInput = z.object({
email: z.string().email(),
password: z.string(),
})
export default resolver.pipe(resolver.zod(LoginInput), async ({email, password}, {session}) => {
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 session.$create({userId: user.id, roles: [user.role]})
await ctx.session.$create({userId: user.id, roles: [user.role as Role]})
return user
})

View File

@@ -1,5 +1,5 @@
import {Ctx} from "blitz"
export default async function logout(_: any, {session}: Ctx) {
return await session.$revoke()
export default async function logout(_: any, ctx: Ctx) {
return await ctx.session.$revoke()
}

View 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,
)
})
})

View 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)
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
})

View File

@@ -1,20 +1,15 @@
import {resolver, SecurePassword} from "blitz"
import db from "db"
import * as z from "zod"
import {Signup} from "app/auth/validations"
import {Role} from "types"
export const SignupInput = z.object({
email: z.string().email(),
password: z.string().min(10).max(100),
})
export default resolver.pipe(resolver.zod(SignupInput), async ({email, password}, {session}) => {
export default resolver.pipe(resolver.zod(Signup), async ({email, password}, ctx) => {
const hashedPassword = await SecurePassword.hash(password)
const user = await db.user.create({
data: {email, hashedPassword, role: "user"},
data: {email: email.toLowerCase(), hashedPassword, role: "user"},
select: {id: true, name: true, email: true, role: true},
})
await session.$create({userId: user.id, roles: [user.role]})
await ctx.session.$create({userId: user.id, roles: [user.role as Role]})
return user
})

View File

@@ -0,0 +1,47 @@
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) {
return {
[FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.",
}
}
}}
>
<LabeledTextField name="email" label="Email" placeholder="Email" />
</Form>
)}
</div>
)
}
ForgotPasswordPage.getLayout = (page) => <Layout title="Forgot Your Password?">{page}</Layout>
export default ForgotPasswordPage

View File

@@ -1,21 +1,18 @@
import {Head, useRouter, BlitzPage} from "blitz"
import React from "react"
import {useRouter, BlitzPage} from "blitz"
import Layout from "app/core/layouts/Layout"
import {LoginForm} from "app/auth/components/LoginForm"
const SignupPage: BlitzPage = () => {
const LoginPage: BlitzPage = () => {
const router = useRouter()
return (
<>
<Head>
<title>Login</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div>
<LoginForm onSuccess={() => router.push("/")} />
</div>
</>
<div>
<LoginForm onSuccess={() => router.push("/")} />
</div>
)
}
export default SignupPage
LoginPage.getLayout = (page) => <Layout title="Log In">{page}</Layout>
export default LoginPage

View File

@@ -0,0 +1,58 @@
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"
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="/">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) {
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.getLayout = (page) => <Layout title="Reset Your Password">{page}</Layout>
export default ResetPasswordPage

View File

@@ -1,53 +1,19 @@
import {Head, useRouter, BlitzPage, useMutation} from "blitz"
import {Form, FORM_ERROR} from "app/components/Form"
import {LabeledTextField} from "app/components/LabeledTextField"
import signup, {SignupInput} from "app/auth/mutations/signup"
import Layout from "app/core/layouts/Layout"
import {Form, FORM_ERROR} from "app/core/components/Form"
import {LabeledTextField} from "app/core/components/LabeledTextField"
import {SignupForm} from "app/auth/components/SignupForm"
const SignupPage: BlitzPage = () => {
const router = useRouter()
const [signupMutation] = useMutation(signup)
return (
<>
<Head>
<title>Sign Up</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div>
<h1>Create an Account</h1>
<Form
submitText="Create Account"
schema={SignupInput}
onSubmit={async (values) => {
try {
await signupMutation(values)
router.push("/")
} catch (error) {
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]:
"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>
</>
<div>
<SignupForm onSuccess={() => router.push("/")} />
</div>
)
}
SignupPage.getLayout = (page) => <Layout title="Sign Up">{page}</Layout>
export default SignupPage

View File

@@ -0,0 +1,33 @@
import * as 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,
})

View File

View File

@@ -1,17 +1,18 @@
import {ReactNode, PropsWithoutRef} from "react"
import React, {ReactNode, PropsWithoutRef} from "react"
import {Form as FinalForm, FormProps as FinalFormProps} from "react-final-form"
import * as z from "zod"
export {FORM_ERROR} from "final-form"
type FormProps<S extends z.ZodType<any, any>> = {
export interface FormProps<S extends z.ZodType<any, any>>
extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> {
/** All your form fields */
children: ReactNode
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"]
schema?: S
} & Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit">
}
export function Form<S extends z.ZodType<any, any>>({
children,

View File

@@ -1,4 +1,4 @@
import {forwardRef, PropsWithoutRef} from "react"
import React, {PropsWithoutRef} from "react"
import {useField} from "react-final-form"
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
@@ -11,12 +11,16 @@ export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElem
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
}
export const LabeledTextField = forwardRef<HTMLInputElement, LabeledTextFieldProps>(
export const LabeledTextField = React.forwardRef<HTMLInputElement, LabeledTextFieldProps>(
({name, label, outerProps, ...props}, ref) => {
const {
input,
meta: {touched, error, submitError, submitting},
} = useField(name)
} = useField(name, {
parse: props.type === "number" ? Number : undefined,
})
const normalizedError = Array.isArray(error) ? error.join(", ") : error || submitError
return (
<div {...outerProps}>
@@ -25,9 +29,9 @@ export const LabeledTextField = forwardRef<HTMLInputElement, LabeledTextFieldPro
<input {...input} disabled={submitting} {...props} ref={ref} />
</label>
{touched && (error || submitError) && (
{touched && normalizedError && (
<div role="alert" style={{color: "red"}}>
{error || submitError}
{normalizedError}
</div>
)}

View File

@@ -2,7 +2,7 @@ import {useSession, useRouter, useMutation, Head} from "blitz"
import logout from "app/auth/mutations/logout"
export default function Layout({title, children}: {title?: string; children: React.ReactNode}) {
const session = useSession()
const session = useSession({suspense: false})
const router = useRouter()
const [logoutMutation] = useMutation(logout)
return (

View File

@@ -0,0 +1,13 @@
import {resolver} from "blitz"
import db from "db"
import * as z from "zod"
const __Name__ = z
.object({
id: z.number(),
})
.nonstrict()
export default resolver.pipe(resolver.zod(__Name__), resolver.authorize(), async (input) => {
// Do your stuff :)
})

View File

@@ -1,4 +1,5 @@
import {
withBlitzAppRoot,
AppProps,
ErrorComponent,
useRouter,
@@ -10,12 +11,10 @@ import {ErrorBoundary} from "react-error-boundary"
import {queryCache} from "react-query"
import LoginForm from "app/auth/components/LoginForm"
if (typeof window !== "undefined") {
;(window as any)["DEBUG_BLITZ"] = 1
}
export default function App({Component, pageProps}: AppProps) {
const getLayout = Component.getLayout || ((page) => page)
const router = useRouter()
return (
<ErrorBoundary
FallbackComponent={RootErrorFallback}
@@ -26,7 +25,7 @@ export default function App({Component, pageProps}: AppProps) {
queryCache.resetErrorBoundaries()
}}
>
<Component {...pageProps} />
{getLayout(<Component {...pageProps} />)}
</ErrorBoundary>
)
}

View File

@@ -1,20 +1,27 @@
import {render} from "test/utils"
import Home from "./index"
import {useCurrentUser} from "app/hooks/useCurrentUser"
jest.mock("app/hooks/useCurrentUser")
const mockUseCurrentUser = useCurrentUser as jest.MockedFunction<typeof useCurrentUser>
jest.mock("blitz", () => ({
...jest.requireActual("blitz")!,
useQuery: () => [
{
id: 1,
name: "User",
email: "user@email.com",
role: "user",
},
],
}))
test("renders blitz documentation link", () => {
mockUseCurrentUser.mockReturnValue({
id: 1,
name: "User",
email: "user@email.com",
role: "user",
})
// 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()
})

View File

@@ -1,30 +1,21 @@
import {Suspense} from "react"
import {Head, Link, useSession, useRouterQuery, useMutation, invoke} from "blitz"
import {Head, Link, useSession, useRouterQuery, useMutation, invoke, useQuery} from "blitz"
import getUser from "app/users/queries/getUser"
import trackView from "app/users/mutations/trackView"
import Layout from "app/core/layouts/Layout"
import {useCurrentUser} from "app/hooks/useCurrentUser"
// import getUsers from "app/users/queries/getUsers"
const CurrentUserInfo = () => {
const currentUser = useCurrentUser()
const session = useSession()
const [currentUser] = useQuery(getUser, {where: {id: session.userId!}})
return <pre>{JSON.stringify(currentUser, null, 2)}</pre>
}
// const Users = () => {
// const [users] = useQuery(getUsers, {})
//
// return <pre style={{maxWidth: "30rem"}}>{JSON.stringify(users, null, 2)}</pre>
// }
const UserStuff = () => {
const session = useSession()
const query = useRouterQuery()
const [trackViewMutation] = useMutation(trackView)
if (session.isLoading) return <div>Loading...</div>
return (
<div>
{!session.userId && (
@@ -48,11 +39,6 @@ const UserStuff = () => {
<Suspense fallback="Loading...">
<CurrentUserInfo />
</Suspense>
{/*
<Suspense fallback="Loading...">
<Users />
</Suspense>
*/}
<button
onClick={async () => {
try {
@@ -94,7 +80,9 @@ const Home = () => (
<img src="/logo.png" alt="blitz.js" />
</div>
<UserStuff />
<Suspense fallback={"Loading..."}>
<UserStuff />
</Suspense>
</main>
<footer>

View File

@@ -1,5 +1,5 @@
import {Suspense} from "react"
import {Link, useRouter, useQuery, useParam, BlitzPage, useMutation} from "blitz"
import {Head, Link, useRouter, useQuery, useParam, BlitzPage, useMutation} from "blitz"
import Layout from "app/core/layouts/Layout"
import getProject from "app/projects/queries/getProject"
import deleteProject from "app/projects/mutations/deleteProject"
@@ -7,30 +7,39 @@ import deleteProject from "app/projects/mutations/deleteProject"
export const Project = () => {
const router = useRouter()
const projectId = useParam("projectId", "number")
const [project] = useQuery(getProject, {where: {id: projectId}})
const [deleteProjectMutation] = useMutation(deleteProject)
const [deleteProjectMutation, {isSuccess}] = useMutation(deleteProject)
const [project] = useQuery(getProject, {id: projectId}, {enabled: !isSuccess})
if (!project) return null
return (
<div>
<h1>Project {project.id}</h1>
<pre>{JSON.stringify(project, null, 2)}</pre>
<>
<Head>
<title>Project {project.id}</title>
</Head>
<Link href={`/projects/${project.id}/edit`}>
<a>Edit</a>
</Link>
<div>
<h1>Project {project.id}</h1>
<pre>{JSON.stringify(project, null, 2)}</pre>
<button
type="button"
onClick={async () => {
if (window.confirm("This will be deleted")) {
await deleteProjectMutation({where: {id: project.id}})
router.push("/projects")
}
}}
>
Delete
</button>
</div>
<Link href={`/projects/${project.id}/edit`}>
<a>Edit</a>
</Link>
<button
type="button"
onClick={async () => {
if (window.confirm("This will be deleted")) {
await deleteProjectMutation({id: project.id})
router.push("/projects")
}
}}
style={{marginLeft: "0.5rem"}}
>
Delete
</button>
</div>
</>
)
}
@@ -50,6 +59,6 @@ const ShowProjectPage: BlitzPage = () => {
)
}
ShowProjectPage.getLayout = (page) => <Layout title={"Project"}>{page}</Layout>
ShowProjectPage.getLayout = (page) => <Layout>{page}</Layout>
export default ShowProjectPage

View File

@@ -1,39 +1,51 @@
import {Suspense} from "react"
import {Link, useRouter, useQuery, useMutation, useParam, BlitzPage} from "blitz"
import {Head, Link, useRouter, useQuery, useMutation, useParam, BlitzPage} from "blitz"
import Layout from "app/core/layouts/Layout"
import getProject from "app/projects/queries/getProject"
import updateProject from "app/projects/mutations/updateProject"
import ProjectForm from "app/projects/components/ProjectForm"
import {ProjectForm, FORM_ERROR} from "app/projects/components/ProjectForm"
export const EditProject = () => {
const router = useRouter()
const projectId = useParam("projectId", "number")
const [project, {setQueryData}] = useQuery(getProject, {where: {id: projectId}})
const [project, {setQueryData}] = useQuery(getProject, {id: projectId})
const [updateProjectMutation] = useMutation(updateProject)
return (
<div>
<h1>Edit Project {project.id}</h1>
<pre>{JSON.stringify(project)}</pre>
<>
<Head>
<title>Edit Project {project.id}</title>
</Head>
<ProjectForm
initialValues={project}
onSubmit={async () => {
try {
const updated = await updateProjectMutation({
where: {id: project.id},
data: {name: "MyNewName"},
})
await setQueryData(updated)
alert("Success!" + JSON.stringify(updated))
router.push(`/projects/${updated.id}`)
} catch (error) {
console.log(error)
alert("Error editing project " + JSON.stringify(error, null, 2))
}
}}
/>
</div>
<div>
<h1>Edit Project {project.id}</h1>
<pre>{JSON.stringify(project)}</pre>
<ProjectForm
submitText="Update Project"
// TODO use a zod schema for form validation
// - Tip: extract mutation's schema into a shared `validations.ts` file and
// then import and use it here
// schema={UpdateProject}
initialValues={project}
onSubmit={async (values) => {
try {
const updated = await updateProjectMutation({
id: project.id,
...values,
})
await setQueryData(updated)
router.push(`/projects/${updated.id}`)
} catch (error) {
console.error(error)
return {
[FORM_ERROR]: error.toString(),
}
}
}}
/>
</div>
</>
)
}
@@ -53,6 +65,6 @@ const EditProjectPage: BlitzPage = () => {
)
}
EditProjectPage.getLayout = (page) => <Layout title={"Edit Project"}>{page}</Layout>
EditProjectPage.getLayout = (page) => <Layout>{page}</Layout>
export default EditProjectPage

View File

@@ -1,5 +1,5 @@
import {Suspense} from "react"
import {Link, usePaginatedQuery, useRouter, BlitzPage} from "blitz"
import {Head, Link, usePaginatedQuery, useRouter, BlitzPage} from "blitz"
import Layout from "app/core/layouts/Layout"
import getProjects from "app/projects/queries/getProjects"
@@ -41,20 +41,26 @@ export const ProjectsList = () => {
const ProjectsPage: BlitzPage = () => {
return (
<div>
<p>
<Link href="/projects/new">
<a>Create Project</a>
</Link>
</p>
<>
<Head>
<title>Projects</title>
</Head>
<Suspense fallback={<div>Loading...</div>}>
<ProjectsList />
</Suspense>
</div>
<div>
<p>
<Link href="/projects/new">
<a>Create Project</a>
</Link>
</p>
<Suspense fallback={<div>Loading...</div>}>
<ProjectsList />
</Suspense>
</div>
</>
)
}
ProjectsPage.getLayout = (page) => <Layout title={"Projects"}>{page}</Layout>
ProjectsPage.getLayout = (page) => <Layout>{page}</Layout>
export default ProjectsPage

View File

@@ -1,7 +1,7 @@
import {Link, useRouter, useMutation, BlitzPage} from "blitz"
import Layout from "app/core/layouts/Layout"
import createProject from "app/projects/mutations/createProject"
import ProjectForm from "app/projects/components/ProjectForm"
import {ProjectForm, FORM_ERROR} from "app/projects/components/ProjectForm"
const NewProjectPage: BlitzPage = () => {
const router = useRouter()
@@ -12,14 +12,21 @@ const NewProjectPage: BlitzPage = () => {
<h1>Create New Project</h1>
<ProjectForm
initialValues={{}}
onSubmit={async () => {
submitText="Create Project"
// TODO use a zod schema for form validation
// - Tip: extract mutation's schema into a shared `validations.ts` file and
// then import and use it here
// schema={CreateProject}
// initialValues={{}}
onSubmit={async (values) => {
try {
const project = await createProjectMutation({name: "MyName"})
alert("Success!" + JSON.stringify(project))
const project = await createProjectMutation(values)
router.push(`/projects/${project.id}`)
} catch (error) {
alert("Error creating project " + JSON.stringify(error, null, 2))
console.error(error)
return {
[FORM_ERROR]: error.toString(),
}
}
}}
/>

View File

@@ -27,7 +27,7 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async ({req, re
// https://github.com/blitz-js/blitz/issues/794
path.resolve("next.config.js")
path.resolve("blitz.config.js")
path.resolve(".next/__db.js")
path.resolve(".next/blitz/db.js")
// End anti-tree-shaking
const session = await getSessionContext(req, res)

View File

@@ -1,23 +1,12 @@
import React from "react"
import {Form, FormProps} from "app/core/components/Form"
import {LabeledTextField} from "app/core/components/LabeledTextField"
import * as z from "zod"
export {FORM_ERROR} from "app/core/components/Form"
type ProjectFormProps = {
initialValues: any
onSubmit: React.FormEventHandler<HTMLFormElement>
}
const ProjectForm = ({initialValues, onSubmit}: ProjectFormProps) => {
export function ProjectForm<S extends z.ZodType<any, any>>(props: FormProps<S>) {
return (
<form
onSubmit={(event) => {
event.preventDefault()
onSubmit(event)
}}
>
<div>Put your form fields here. But for now, just click submit</div>
<div>{JSON.stringify(initialValues)}</div>
<button>Submit</button>
</form>
<Form<S> {...props}>
<LabeledTextField name="name" label="Name" placeholder="Name" />
</Form>
)
}
export default ProjectForm

View File

@@ -2,24 +2,15 @@ import {resolver} from "blitz"
import db from "db"
import * as z from "zod"
export const CreateProject = z.object({
name: z.string(),
dueDate: z.date().optional(),
const CreateProject = z
.object({
name: z.string(),
})
.nonstrict()
export default resolver.pipe(resolver.zod(CreateProject), resolver.authorize(), async (input) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const project = await db.project.create({data: input})
return project
})
export default resolver.pipe(
resolver.zod(CreateProject),
(input, _ctx) => ({extraFieldForIntegrationTesting: _ctx.session.userId, ...input}),
resolver.authorize(),
// How to set a default input value
(input, _ctx) => ({dueDate: new Date(), ...input}),
async (input, _ctx) => {
console.log("Creating project...")
const project = await db.project.create({
data: input,
})
console.log("Created project")
return project
},
)

View File

@@ -1,12 +1,16 @@
import {Ctx} from "blitz"
import db, {Prisma} from "db"
import {resolver} from "blitz"
import db from "db"
import * as z from "zod"
type DeleteProjectInput = Pick<Prisma.ProjectDeleteArgs, "where">
const DeleteProject = z
.object({
id: z.number(),
})
.nonstrict()
export default async function deleteProject({where}: DeleteProjectInput, ctx: Ctx) {
ctx.session.$authorize()
const project = await db.project.delete({where})
export default resolver.pipe(resolver.zod(DeleteProject), resolver.authorize(), async ({id}) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const project = await db.project.delete({where: {id}})
return project
}
})

View File

@@ -1,12 +1,21 @@
import {Ctx} from "blitz"
import db, {Prisma} from "db"
import {resolver} from "blitz"
import db from "db"
import * as z from "zod"
type UpdateProjectInput = Pick<Prisma.ProjectUpdateArgs, "where" | "data">
const UpdateProject = z
.object({
id: z.number(),
name: z.string(),
})
.nonstrict()
export default async function updateProject({where, data}: UpdateProjectInput, ctx: Ctx) {
ctx.session.$authorize()
export default resolver.pipe(
resolver.zod(UpdateProject),
resolver.authorize(),
async ({id, ...data}) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const project = await db.project.update({where: {id}, data})
const project = await db.project.update({where, data})
return project
}
return project
},
)

View File

@@ -1,14 +1,17 @@
import {Ctx, NotFoundError} from "blitz"
import db, {Prisma} from "db"
import {resolver, NotFoundError} from "blitz"
import db from "db"
import * as z from "zod"
type GetProjectInput = Pick<Prisma.ProjectFindFirstArgs, "where">
const GetProject = z.object({
// This accepts type of undefined, but is required at runtime
id: z.number().optional().refine(Boolean, "Required"),
})
export default async function getProject({where}: GetProjectInput, ctx: Ctx) {
ctx.session.$authorize()
const project = await db.project.findFirst({where})
export default resolver.pipe(resolver.zod(GetProject), resolver.authorize(), async ({id}) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const project = await db.project.findFirst({where: {id}})
if (!project) throw new NotFoundError()
return project
}
})

View File

@@ -1,29 +1,25 @@
import {Ctx} from "blitz"
import {paginate, resolver} from "blitz"
import db, {Prisma} from "db"
type GetProjectsInput = Pick<Prisma.ProjectFindManyArgs, "where" | "orderBy" | "skip" | "take">
interface GetProjectsInput
extends Pick<Prisma.ProjectFindManyArgs, "where" | "orderBy" | "skip" | "take"> {}
export default async function getProjects(
{where, orderBy, skip = 0, take}: GetProjectsInput,
ctx: Ctx,
) {
ctx.session.$authorize()
export default resolver.pipe(
resolver.authorize(),
async ({where, orderBy, skip = 0, take = 100}: GetProjectsInput) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const {items: projects, hasMore, nextPage, count} = await paginate({
skip,
take,
count: () => db.project.count({where}),
query: (paginateArgs) => db.project.findMany({...paginateArgs, where, orderBy}),
})
const projects = await db.project.findMany({
where,
orderBy,
take,
skip,
})
const count = await db.project.count()
const hasMore = typeof take === "number" ? skip + take < count : false
const nextPage = hasMore ? {take, skip: skip + take!} : null
return {
projects,
nextPage,
hasMore,
count,
}
}
return {
projects,
nextPage,
hasMore,
count,
}
},
)

View File

@@ -1,11 +1,11 @@
import {Ctx} from "blitz"
import db from "db"
export default async function getCurrentUser(_ = null, ctx: Ctx) {
if (!ctx.session.userId) return null
export default async function getCurrentUser(_ = null, {session}: Ctx) {
if (!session.userId) return null
const user = await db.user.findFirst({
where: {id: ctx.session.userId},
where: {id: session.userId},
select: {id: true, name: true, email: true, role: true},
})

View File

@@ -6,7 +6,7 @@ type GetUserInput = {
}
export default async function getUser({where}: GetUserInput, ctx: Ctx) {
ctx.session.$authorize()
if (!ctx.session.userId) return null
const user = await db.user.findFirst({where})

View File

@@ -7,14 +7,14 @@ module.exports = withBundleAnalyzer({
middleware: [
sessionMiddleware({
isAuthorized: simpleRolesIsAuthorized,
sessionExpiryMinutes: 4,
// sessionExpiryMinutes: 4,
}),
],
log: {
// level: "trace",
},
experimental: {
isomorphicResolverImports: true,
isomorphicResolverImports: false,
},
/*
webpack: (config, {buildId, dev, isServer, defaultLoaders, webpack}) => {

View File

@@ -29,6 +29,7 @@ describe("index page", () => {
cy.signup(user)
cy.wait(500)
cy.contains("button", "Logout").click()
cy.contains("a", /login/i).click()
@@ -37,6 +38,7 @@ describe("index page", () => {
cy.contains("button", /login/i).click()
cy.location("pathname").should("equal", "/")
cy.wait(500)
cy.contains("button", "Logout")
})

View File

@@ -0,0 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "es5"],
"types": ["cypress"]
}
}

View File

View File

@@ -0,0 +1,15 @@
-- 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 "Token.hashedToken_type_unique" ON "Token"("hashedToken", "type");

View File

@@ -20,18 +20,20 @@ generator client {
// --------------------------------------
model User {
id Int @default(autoincrement()) @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name String?
email String @unique
email String @unique
hashedPassword String?
role String @default("user")
sessions Session[]
role String @default("user")
sessions Session[]
tokens Token[]
}
model Session {
id Int @default(autoincrement()) @id
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
expiresAt DateTime?
@@ -44,10 +46,25 @@ model Session {
privateData String?
}
model Token {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
hashedToken String
type String
expiresAt DateTime
sentTo String
user User @relation(fields: [userId], references: [id])
userId Int
@@unique([hashedToken, type])
}
model Project {
id Int @default(autoincrement()) @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name String
dueDate DateTime?
}

View File

View 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.
*/
import previewEmail from "preview-email"
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
await previewEmail(msg)
}
},
}
}

View File

@@ -1,17 +1,18 @@
{
"name": "@examples/auth",
"version": "0.30.0-canary.4",
"version": "0.30.0-canary.7",
"scripts": {
"dev": "blitz dev",
"build": "blitz build",
"start": "blitz start",
"studio": "blitz prisma studio",
"build": "blitz build",
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
"analyze": "cross-env ANALYZE=true blitz build",
"cy:open": "cypress open",
"cy:run": "cypress run || cypress run",
"test": "prisma generate && yarn test:jest && yarn test:e2e",
"test:jest": "jest",
"test:server": "blitz prisma migrate deploy --preview-feature && blitz build && blitz start --production -p 3099",
"test:server": "cross-env NODE_ENV=test blitz prisma migrate deploy --preview-feature && blitz build && cross-env NODE_ENV=test blitz start -p 3099",
"test:e2e": "cross-env NODE_ENV=test start-server-and-test test:server http://localhost:3099 cy:run"
},
"browserslist": [
@@ -38,13 +39,13 @@
]
},
"dependencies": {
"@prisma/cli": "2.15.0",
"@prisma/client": "2.15.0",
"blitz": "0.30.0-canary.4",
"@prisma/client": "2.16.0",
"blitz": "0.30.0-canary.7",
"final-form": "4.20.1",
"passport-auth0": "1.4.0",
"passport-github2": "0.1.12",
"passport-twitter": "1.0.4",
"prisma": "2.16.0",
"react": "0.0.0-experimental-3310209d0",
"react-dom": "0.0.0-experimental-3310209d0",
"react-error-boundary": "3.1.0",
@@ -53,20 +54,22 @@
},
"devDependencies": {
"@cypress/skip-test": "2.6.0",
"@next/bundle-analyzer": "^10.0.5",
"@next/bundle-analyzer": "^10.0.6",
"@testing-library/react": "11.2.3",
"@testing-library/react-hooks": "4.0.1",
"@types/passport-auth0": "1.0.4",
"@types/passport-github2": "1.2.4",
"@types/passport-twitter": "1.0.36",
"@types/preview-email": "2.0.0",
"@types/react": "17.0.0",
"cross-env": "7.0.3",
"cypress": "6.2.1",
"eslint": "7.17.0",
"husky": "4.3.7",
"eslint": "7.18.0",
"husky": "4.3.8",
"lint-staged": "10.5.3",
"prettier": "2.2.1",
"pretty-quick": "3.1.0",
"preview-email": "3.0.3",
"start-server-and-test": "1.11.7",
"typescript": "4.1.3"
},

View File

@@ -1,2 +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

View File

@@ -1,3 +1,4 @@
import React from "react"
import {RouterContext, BlitzRouter} from "blitz"
import {render as defaultRender} from "@testing-library/react"
import {renderHook as defaultRenderHook} from "@testing-library/react-hooks"
@@ -13,14 +14,6 @@ export * from "@testing-library/react"
// This is the place to add any other context providers you need while testing.
// --------------------------------------------------------------------------------
type DefaultParams = Parameters<typeof defaultRender>
type RenderUI = DefaultParams[0]
type RenderOptions = DefaultParams[1] & {router?: Partial<BlitzRouter>}
type DefaultHookParams = Parameters<typeof defaultRenderHook>
type RenderHook = DefaultHookParams[0]
type RenderHookOptions = DefaultHookParams[1] & {router?: Partial<BlitzRouter>}
// --------------------------------------------------
// render()
// --------------------------------------------------
@@ -87,3 +80,11 @@ export const mockRouter: BlitzRouter = {
},
isFallback: false,
}
type DefaultParams = Parameters<typeof defaultRender>
type RenderUI = DefaultParams[0]
type RenderOptions = DefaultParams[1] & {router?: Partial<BlitzRouter>}
type DefaultHookParams = Parameters<typeof defaultRenderHook>
type RenderHook = DefaultHookParams[0]
type RenderHookOptions = DefaultHookParams[1] & {router?: Partial<BlitzRouter>}

View File

@@ -16,6 +16,6 @@
"isolatedModules": true,
"jsx": "preserve"
},
"exclude": ["node_modules"],
"exclude": ["node_modules", "cypress"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}

View File

@@ -1,16 +1,18 @@
import {DefaultCtx, SessionContext} from "blitz"
import {simpleRolesIsAuthorized} from "@blitzjs/server"
import {SimpleRolesIsAuthorized} from "@blitzjs/server"
import {User} from "db"
export type Role = "ADMIN" | "USER"
declare module "blitz" {
export interface Ctx extends DefaultCtx {
session: SessionContext
}
export interface Session {
isAuthorized: typeof simpleRolesIsAuthorized
isAuthorized: SimpleRolesIsAuthorized<Role>
PublicData: {
userId: User["id"]
roles: string[]
roles: Role[]
views?: number
}
}

View File

@@ -13,10 +13,10 @@
3. Start the dev server
```sh
blitz start
blitz dev
// Or if you want hot-reloading of server.js, use:
yarn start
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
@@ -25,5 +25,5 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the
```sh
blitz build
blitz start --production
blitz start
```

View File

@@ -84,7 +84,7 @@ const Home: BlitzPage = () => {
<code>Ctrl + c</code>
</pre>
<pre>
<code>blitz start</code>
<code>blitz dev</code>
</pre>
<p>
and go to{" "}

View File

@@ -1,9 +1,10 @@
{
"name": "@examples/custom-server",
"version": "0.30.0-canary.4",
"version": "0.30.0-canary.7",
"scripts": {
"start": "nodemon --watch server.js --exec 'blitz start'",
"dev": "nodemon --watch server.js --exec 'blitz dev'",
"build": "blitz build",
"start": "blitz start",
"studio": "blitz prisma studio",
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
"test-watch": "jest --watch",
@@ -11,7 +12,7 @@
"cy-run": "cypress run",
"test:migrate": "prisma generate && blitz prisma migrate deploy --preview-feature",
"test:jest": "jest",
"test-server": "blitz build && blitz start --production",
"test-server": "blitz build && blitz start",
"test:e2e": "cross-env NODE_ENV=test PORT=3099 start-server-and-test test-server http://localhost:3099 cy-run",
"test": "run-s test:*"
},
@@ -39,12 +40,12 @@
]
},
"dependencies": {
"@prisma/cli": "2.15.0",
"@prisma/client": "2.15.0",
"blitz": "0.30.0-canary.4",
"@prisma/client": "2.16.0",
"blitz": "0.30.0-canary.7",
"final-form": "4.20.1",
"react": "0.0.0-experimental-4ead6b530",
"react-dom": "0.0.0-experimental-4ead6b530",
"prisma": "2.16.0",
"react": "0.0.0-experimental-3310209d0",
"react-dom": "0.0.0-experimental-3310209d0",
"react-error-boundary": "2.3.2",
"react-final-form": "6.5.2",
"secure-password": "4.0.0",
@@ -53,7 +54,7 @@
},
"devDependencies": {
"@cypress/skip-test": "2.6.0",
"@testing-library/jest-dom": "5.11.6",
"@testing-library/jest-dom": "5.11.9",
"@testing-library/react": "11.2.3",
"@testing-library/react-hooks": "4.0.1",
"@types/jest": "26.0.20",
@@ -63,7 +64,7 @@
"@typescript-eslint/parser": "4.12.0",
"babel-eslint": "10.1.0",
"cypress": "6.2.1",
"eslint": "7.17.0",
"eslint": "7.18.0",
"eslint-config-react-app": "5.2.1",
"eslint-plugin-cypress": "2.11.1",
"eslint-plugin-flowtype": "5.2.0",
@@ -71,7 +72,7 @@
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.21.5",
"eslint-plugin-react-hooks": "4.2.0",
"husky": "4.3.7",
"husky": "4.3.8",
"jest": "26.6.3",
"jest-environment-jsdom-fourteen": "1.0.1",
"jest-watch-typeahead": "0.6.1",
@@ -82,7 +83,7 @@
"pretty-quick": "3.1.0",
"react-test-renderer": "16.14.0",
"start-server-and-test": "1.11.2",
"ts-jest": "26.4.4"
"ts-jest": "26.5.0"
},
"private": true
}

View File

@@ -31,7 +31,7 @@ FAUNA_SECRET=YOUR_AUTH_KEY
2. Start the dev server
```
yarn blitz start
yarn blitz dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

View File

@@ -1,10 +1,11 @@
{
"name": "@examples/fauna",
"version": "0.30.0-canary.4",
"version": "0.30.0-canary.7",
"scripts": {
"dev": "blitz dev",
"build": "blitz build",
"start": "blitz start",
"studio": "blitz prisma studio",
"build": "blitz build",
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
"test": "echo \"No tests yet\""
},
@@ -27,9 +28,9 @@
]
},
"dependencies": {
"blitz": "0.30.0-canary.4",
"blitz": "0.30.0-canary.7",
"final-form": "4.20.1",
"graphql": "15.4.0",
"graphql": "15.5.0",
"graphql-request": "3.4.0",
"react": "0.0.0-experimental-3310209d0",
"react-dom": "0.0.0-experimental-3310209d0",
@@ -39,7 +40,7 @@
"zod": "1.11.11"
},
"devDependencies": {
"@testing-library/jest-dom": "5.11.8",
"@testing-library/jest-dom": "5.11.9",
"@testing-library/react": "11.2.3",
"@testing-library/react-hooks": "4.0.1",
"@types/jest": "26.0.20",
@@ -48,14 +49,14 @@
"@typescript-eslint/eslint-plugin": "4.12.0",
"@typescript-eslint/parser": "4.12.0",
"babel-eslint": "10.1.0",
"eslint": "7.17.0",
"eslint": "7.18.0",
"eslint-config-react-app": "6.0.0",
"eslint-plugin-flowtype": "5.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"husky": "4.3.7",
"husky": "4.3.8",
"jest": "26.6.3",
"jest-environment-jsdom-fourteen": "1.0.1",
"jest-watch-typeahead": "0.6.1",
@@ -63,7 +64,7 @@
"prettier": "2.2.1",
"pretty-quick": "3.1.0",
"start-server-and-test": "1.11.7",
"ts-jest": "26.4.4",
"ts-jest": "26.5.0",
"typescript": "4.1.3"
},
"private": true

View File

@@ -14,7 +14,7 @@ model Project {
3. Start the dev server
```
blitz start
blitz dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

View File

@@ -1,9 +1,10 @@
{
"name": "no-prisma",
"version": "0.30.0-canary.4",
"version": "0.30.0-canary.7",
"scripts": {
"start": "blitz start",
"dev": "blitz dev",
"build": "blitz build",
"start": "blitz start",
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
"test": "echo \"No tests yet\""
},
@@ -26,7 +27,7 @@
]
},
"dependencies": {
"blitz": "0.30.0-canary.4",
"blitz": "0.30.0-canary.7",
"knex": "0.21.16",
"react": "0.0.0-experimental-3310209d0",
"react-dom": "0.0.0-experimental-3310209d0",
@@ -37,14 +38,14 @@
"@typescript-eslint/eslint-plugin": "4.12.0",
"@typescript-eslint/parser": "4.12.0",
"babel-eslint": "10.1.0",
"eslint": "7.17.0",
"eslint": "7.18.0",
"eslint-config-react-app": "6.0.0",
"eslint-plugin-flowtype": "5.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"husky": "4.3.7",
"husky": "4.3.8",
"lint-staged": "10.5.3",
"prettier": "2.2.1",
"pretty-quick": "3.1.0",

View File

@@ -20,7 +20,7 @@ blitz prisma migrate dev --preview-feature
3. Start the dev server
```
blitz start
blitz dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

View File

@@ -1,9 +1,10 @@
{
"name": "@examples/plain-js",
"version": "0.30.0-canary.4",
"version": "0.30.0-canary.7",
"scripts": {
"start": "blitz start",
"dev": "blitz dev",
"build": "blitz prisma migrate deploy --preview-feature && blitz build",
"start": "blitz start",
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
"test": "echo \"DISABLED\""
},
@@ -29,9 +30,9 @@
]
},
"dependencies": {
"@prisma/cli": "2.15.0",
"@prisma/client": "2.15.0",
"blitz": "0.30.0-canary.4",
"@prisma/client": "2.16.0",
"blitz": "0.30.0-canary.7",
"prisma": "2.16.0",
"react": "0.0.0-experimental-3310209d0",
"react-dom": "0.0.0-experimental-3310209d0"
},
@@ -39,14 +40,14 @@
"@typescript-eslint/eslint-plugin": "4.12.0",
"@typescript-eslint/parser": "4.12.0",
"babel-eslint": "10.1.0",
"eslint": "7.17.0",
"eslint": "7.18.0",
"eslint-config-react-app": "6.0.0",
"eslint-plugin-flowtype": "5.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"husky": "4.3.7",
"husky": "4.3.8",
"lint-staged": "10.5.3",
"prettier": "2.2.1",
"pretty-quick": "3.1.0",

View File

@@ -9,7 +9,7 @@ yarn blitz prisma migrate dev --preview-feature
2. Start the dev server
```
yarn blitz start
yarn blitz dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

View File

@@ -16,7 +16,7 @@ export const getStaticProps = async () => {
const Page: BlitzPage<InferGetStaticPropsType<typeof getStaticProps>> = function ({products}) {
return (
<div>
<h1>Products</h1>
<h1>First 100 Products</h1>
<div id="products">
{products.map((product) => (
<p key={product.id}>

View File

@@ -1,4 +1,4 @@
import {Middleware} from "blitz"
import {Middleware, paginate} from "blitz"
import db, {Prisma, Product} from "db"
import {sum} from "lodash"
@@ -10,15 +10,12 @@ export function averagePrice(products: Product[]) {
type GetProductsInput = {
where?: Prisma.ProductFindManyArgs["where"]
orderBy?: Prisma.ProductFindManyArgs["orderBy"]
skip?: Prisma.ProductFindManyArgs["skip"]
cursor?: Prisma.ProductFindManyArgs["cursor"]
take?: Prisma.ProductFindManyArgs["take"]
// Only available if a model relationship exists
// include?: Prisma.ProductFindManyArgs['include']
skip?: number
take?: number
}
export default async function getProducts(
{where, orderBy, skip = 0, cursor, take}: GetProductsInput,
{where, orderBy, skip = 0, take = 100}: GetProductsInput,
ctx: Record<any, unknown> = {},
) {
if (ctx.referer) {
@@ -27,22 +24,18 @@ export default async function getProducts(
console.log("this line should not be included in the frontend bundle")
const products = await db.product.findMany({
where,
orderBy,
const {items: products, hasMore, nextPage, count} = await paginate({
skip,
cursor,
take,
count: () => db.product.count({where}),
query: (paginateArgs) => db.product.findMany({...paginateArgs, where, orderBy}),
})
const count = await db.product.count()
const hasMore = typeof take === "number" ? skip + take < count : false
const nextPage = hasMore ? {take, skip: skip + take!} : null
return {
products,
nextPage,
hasMore,
nextPage,
count,
}
}

View File

@@ -0,0 +1,9 @@
-- CreateTable
CREATE TABLE "Variant" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"name" TEXT NOT NULL,
"productId" INTEGER NOT NULL,
FOREIGN KEY ("productId") REFERENCES "Product" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

View File

@@ -0,0 +1,2 @@
# Please do not edit this file manually
provider = "sqlite"

View File

@@ -38,4 +38,15 @@ model Product {
name String?
description String?
price Int?
variants Variant[]
}
model Variant {
id Int @default(autoincrement()) @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name String
product Product @relation(fields: [productId], references: [id])
productId Int
}

View File

@@ -1,12 +1,12 @@
{
"name": "@examples/store",
"version": "0.30.0-canary.4",
"version": "0.30.0-canary.7",
"private": true,
"scripts": {
"build": "blitz prisma migrate deploy --preview-feature && blitz build",
"cy:open": "cypress open",
"cy:run": "cypress run || cypress run",
"test:server": "prisma generate && blitz prisma migrate deploy --preview-feature && blitz db seed && blitz build && blitz start --production -p 3099",
"test:server": "prisma generate && blitz prisma migrate deploy --preview-feature && blitz db seed && blitz build && blitz start -p 3099",
"test": "cross-env NODE_ENV=test start-server-and-test test:server http://localhost:3099 cy:run",
"posttest": "node assert-tree-shaking-works.js"
},
@@ -20,10 +20,10 @@
"trailingComma": "all"
},
"dependencies": {
"@prisma/cli": "2.15.0",
"@prisma/client": "2.15.0",
"blitz": "0.30.0-canary.4",
"@prisma/client": "2.16.0",
"blitz": "0.30.0-canary.7",
"final-form": "4.20.1",
"prisma": "2.16.0",
"react": "0.0.0-experimental-3310209d0",
"react-dom": "0.0.0-experimental-3310209d0",
"react-error-boundary": "3.1.0",
@@ -34,7 +34,7 @@
"@types/react": "17.0.0",
"cypress": "6.2.1",
"eslint-plugin-cypress": "2.11.2",
"sass": "1.32.0",
"sass": "1.32.5",
"start-server-and-test": "1.11.7"
}
}

23
jest.config.js Normal file
View File

@@ -0,0 +1,23 @@
const {resolveAliases} = require("@blitzjs/config")
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
moduleFileExtensions: ["ts", "tsx", "js", "json"],
modulePathIgnorePatterns: ["<rootDir>/tmp", "<rootDir>/dist", "<rootDir>/templates"],
moduleNameMapper: {
...resolveAliases.node,
},
coverageReporters: ["json", "lcov", "text", "clover"],
// collectCoverage: !!`Boolean(process.env.CI)`,
collectCoverageFrom: ["src/**/*.ts"],
coveragePathIgnorePatterns: ["/templates/"],
// coverageThreshold: {
// global: {
// branches: 100,
// functions: 100,
// lines: 100,
// statements: 100,
// },
// },
}

View File

@@ -1,5 +1,5 @@
{
"version": "0.30.0-canary.4",
"version": "0.30.0-canary.7",
"packages": ["packages/*"],
"npmClient": "yarn",
"useWorkspaces": true,

View File

@@ -39,12 +39,14 @@
"publish-danger": "lerna publish --canary --pre-dist-tag danger --preid danger.$(git rev-parse --short HEAD) --allow-branch * --force-publish",
"danger:push-all": "git push --no-verify && git push --no-verify --tags"
},
"resolutions-NOTE1": "typescript and ts-jest pinned to here overside tsdx included versions",
"resolutions": {
"typescript": "4.1.3"
"typescript": "4.1.3",
"ts-jest": "26.5.0"
},
"devDependencies": {
"@rollup/pluginutils": "4.1.0",
"@testing-library/jest-dom": "5.11.8",
"@testing-library/jest-dom": "5.11.9",
"@testing-library/react": "11.2.3",
"@testing-library/react-hooks": "4.0.1",
"@types/b64-lite": "1.3.0",
@@ -62,13 +64,14 @@
"@types/gulp-if": "0.0.33",
"@types/ink-spinner": "3.0.0",
"@types/jest": "26.0.20",
"@types/module-alias": "2.0.0",
"@types/jsonwebtoken": "8.5.0",
"@types/lodash": "4.14.166",
"@types/lodash": "4.14.168",
"@types/mem-fs": "1.1.2",
"@types/mem-fs-editor": "7.0.0",
"@types/merge-stream": "1.1.2",
"@types/mock-fs": "4.13.0",
"@types/node": "14.14.20",
"@types/node": "14.14.22",
"@types/node-fetch": "2.5.8",
"@types/parallel-transform": "1.1.0",
"@types/passport": "1.0.5",
@@ -86,7 +89,6 @@
"@types/through2": "2.0.36",
"@types/vinyl": "2.0.4",
"@types/vinyl-fs": "2.4.11",
"@types/webpack": "4.41.26",
"@typescript-eslint/eslint-plugin": "4.12.0",
"@typescript-eslint/parser": "4.12.0",
"@wessberg/cjs-to-esm-transformer": "0.0.22",
@@ -96,10 +98,11 @@
"concurrently": "5.3.0",
"cpy-cli": "3.1.1",
"cross-env": "7.0.3",
"cypress": "6.2.1",
"debug": "4.3.1",
"delay": "4.4.0",
"delay": "4.4.1",
"directory-tree": "2.2.5",
"eslint": "7.17.0",
"eslint": "7.18.0",
"eslint-config-react-app": "6.0.0",
"eslint-plugin-es": "4.1.0",
"eslint-plugin-es5": "1.5.0",
@@ -110,20 +113,21 @@
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-simple-import-sort": "7.0.0",
"eslint-plugin-unicorn": "25.0.1",
"husky": "4.3.7",
"@size-limit/preset-small-lib": "4.9.1",
"size-limit": "4.9.1",
"eslint-plugin-unicorn": "26.0.1",
"husky": "4.3.8",
"@size-limit/preset-small-lib": "4.9.2",
"size-limit": "4.9.2",
"jest": "26.6.3",
"jest-environment-jsdom-sixteen": "1.0.3",
"lerna": "3.22.1",
"lint-staged": "10.5.3",
"mock-fs": "4.13.0",
"nock": "13.0.5",
"nock": "13.0.6",
"npm-run-all": "4.1.5",
"patch-package": "6.2.2",
"postinstall-postinstall": "2.1.0",
"prettier": "2.2.1",
"prettier-plugin-prisma": "0.2.0",
"pretty-quick": "3.1.0",
"prompt": "1.1.0",
"react-test-renderer": "17.0.1",
@@ -138,13 +142,13 @@
"semver": "7.3.4",
"stdout-stderr": "0.1.13",
"test-listen": "1.1.0",
"ts-jest": "26.4.4",
"ts-jest": "26.5.0",
"tsdx": "0.14.1",
"tslib": "2.1.0",
"typescript": "4.1.3",
"wait-on": "5.2.1",
"yalc": "1.0.0-pre.49",
"eslint_d": "9.1.2"
"eslint_d": "10.0.0"
},
"husky": {
"hooks": {

View File

@@ -1,6 +1,6 @@
{
"name": "@blitzjs/babel-preset",
"version": "0.30.0-canary.4",
"version": "0.30.0-canary.7",
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",

View File

@@ -0,0 +1,22 @@
import { PluginObj } from '@babel/core';
import { getFileName, wrapExportDefaultDeclaration } from './utils';
function AddBlitzAppRoot(): PluginObj {
return {
name: 'AddBlitzAppRoot',
visitor: {
ExportDefaultDeclaration(path, state) {
const filePath = getFileName(state);
if (!filePath?.match(/_app\./)) {
return;
}
wrapExportDefaultDeclaration(path, 'withBlitzAppRoot', '@blitzjs/core');
},
},
};
}
// eslint-disable-next-line import/no-default-export
export default AddBlitzAppRoot;

View File

@@ -1,7 +1,9 @@
import AddBlitzAppRoot from './add-blitz-app-root';
// eslint-disable-next-line import/no-default-export
export default function preset(_api: any, options = {}) {
return {
presets: [[require('next/babel'), options]],
plugins: [require('babel-plugin-superjson-next')],
plugins: [require('babel-plugin-superjson-next'), AddBlitzAppRoot],
};
}

9
packages/babel-preset/src/types.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
declare module '@babel/helper-module-imports' {
import { NodePath, types } from '@babel/core';
function addNamed(
path: NodePath,
named: string,
source: string
): types.Identifier;
}

View File

@@ -0,0 +1,77 @@
import { NodePath, PluginPass, types as t } from '@babel/core';
import { addNamed as addNamedImport } from '@babel/helper-module-imports';
export function functionDeclarationToExpression(
declaration: t.FunctionDeclaration
) {
return t.functionExpression(
declaration.id,
declaration.params,
declaration.body,
declaration.generator,
declaration.async
);
}
export function classDeclarationToExpression(declaration: t.ClassDeclaration) {
return t.classExpression(
declaration.id,
declaration.superClass,
declaration.body,
declaration.decorators
);
}
export function getFileName(state: PluginPass) {
const { filename, cwd } = state;
if (!filename) {
return undefined;
}
if (cwd && filename.startsWith(cwd)) {
return filename.slice(cwd.length);
}
return filename;
}
export function wrapExportDefaultDeclaration(
path: NodePath<any>,
HOFName: string,
importFrom: string
) {
function wrapInHOF(path: NodePath<any>, expr: t.Expression) {
return t.callExpression(addNamedImport(path, HOFName, importFrom), [expr]);
}
const { node } = path;
if (
t.isIdentifier(node.declaration) ||
t.isFunctionExpression(node.declaration) ||
t.isCallExpression(node.declaration)
) {
node.declaration = wrapInHOF(path, node.declaration);
} else if (
t.isFunctionDeclaration(node.declaration) ||
t.isClassDeclaration(node.declaration)
) {
if (node.declaration.id) {
path.insertBefore(node.declaration);
node.declaration = wrapInHOF(path, node.declaration.id);
} else {
if (t.isFunctionDeclaration(node.declaration)) {
node.declaration = wrapInHOF(
path,
functionDeclarationToExpression(node.declaration)
);
} else {
node.declaration = wrapInHOF(
path,
classDeclarationToExpression(node.declaration)
);
}
}
}
}

View File

@@ -1,22 +1,23 @@
const path = require("path")
const pkgDir = require("pkg-dir")
const {pathsToModuleNameMapper} = require("ts-jest/utils")
const projectRoot = pkgDir.sync() || process.cwd()
const {getProjectRoot, resolveAliases} = require("@blitzjs/config")
const projectRoot = getProjectRoot()
const {compilerOptions} = require(path.join(projectRoot, "tsconfig"))
module.exports = {
maxWorkers: 1,
const common = {
globalSetup: path.resolve(__dirname, "./jest-preset/global-setup.js"),
setupFilesAfterEnv: [
path.resolve(__dirname, "./jest-preset/setup-after-env.js"),
"<rootDir>/test/setup.ts",
],
// Add type checking to TypeScript test files
preset: "ts-jest",
testEnvironment: "jest-environment-jsdom-fourteen",
// Automatically clear mock calls and instances between every test
clearMocks: true,
testPathIgnorePatterns: ["/node_modules/", "/.blitz/", "/.next/", "<rootDir>/db/migrations"],
testPathIgnorePatterns: [
"/node_modules/",
"/.blitz/",
"/.next/",
"<rootDir>/db/migrations",
"<rootDir>/test/e2e",
"<rootDir>/cypress",
],
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(ts|tsx)$"],
transform: {
"^.+\\.(ts|tsx)$": "babel-jest",
@@ -24,13 +25,9 @@ module.exports = {
// This makes absolute imports work
moduleDirectories: ["node_modules", "<rootDir>"],
// Ignore the build directories
modulePathIgnorePatterns: [
"<rootDir>/.blitz",
"<rootDir>/.next",
"<rootDir>/test/e2e",
"<rootDir>/cypress",
],
modulePathIgnorePatterns: ["<rootDir>/.blitz", "<rootDir>/.next"],
moduleNameMapper: {
...resolveAliases.node,
// This ensures any path aliases in tsconfig also work in jest
...pathsToModuleNameMapper(compilerOptions.paths || {}),
"\\.(css|less|sass|scss)$": path.resolve(__dirname, "./jest-preset/identity-obj-proxy.js"),
@@ -44,3 +41,41 @@ module.exports = {
coverageDirectory: ".coverage",
collectCoverageFrom: ["**/*.{js,jsx,ts,tsx}", "!**/*.d.ts", "!**/node_modules/**"],
}
module.exports = {
// TODO - check on https://github.com/facebook/jest/issues/10936
maxWorkers: 1,
projects: [
{
...common,
name: "client",
displayName: {
name: "CLIENT",
color: "cyan",
},
testEnvironment: "jest-environment-jsdom-fourteen",
testRegex: ["^((?!queries|mutations|api|\\.server\\.).)*\\.(test|spec)\\.(j|t)sx?$"],
setupFilesAfterEnv: [
path.resolve(__dirname, "./jest-preset/client/setup-after-env.js"),
"<rootDir>/test/setup.ts",
],
},
{
...common,
name: "server",
displayName: {
name: "SERVER",
color: "magenta",
},
testEnvironment: "node",
testRegex: [
"\\.server\\.(spec|test)\\.(j|t)sx?$",
"[\\/](queries|mutations|api)[\\/].*\\.(test|spec)\\.(j|t)sx?$",
],
setupFilesAfterEnv: [
path.resolve(__dirname, "./jest-preset/server/setup-after-env.js"),
"<rootDir>/test/setup.ts",
],
},
],
}

View File

@@ -0,0 +1 @@
require("@testing-library/jest-dom")

View File

@@ -3,6 +3,8 @@ require("@testing-library/jest-dom")
afterAll(async () => {
try {
await global._blitz_prismaClient.$disconnect()
// console.log("DISCONNECT")
// await new Promise((resolve) => setTimeout(resolve, 500))
} catch (error) {
// ignore error
}

View File

@@ -1,7 +1,7 @@
{
"name": "blitz",
"description": "Blitz is a Rails-like framework for monolithic, full-stack React apps — built on Next.js",
"version": "0.30.0-canary.4",
"version": "0.30.0-canary.7",
"license": "MIT",
"scripts": {
"clean": "rimraf dist",
@@ -43,20 +43,20 @@
"url": "https://github.com/blitz-js/blitz"
},
"dependencies": {
"@blitzjs/babel-preset": "0.30.0-canary.4",
"@blitzjs/cli": "0.30.0-canary.4",
"@blitzjs/config": "0.30.0-canary.4",
"@blitzjs/core": "0.30.0-canary.4",
"@blitzjs/display": "0.30.0-canary.4",
"@blitzjs/generator": "0.30.0-canary.4",
"@blitzjs/installer": "0.30.0-canary.4",
"@blitzjs/server": "0.30.0-canary.4",
"@blitzjs/babel-preset": "0.30.0-canary.7",
"@blitzjs/cli": "0.30.0-canary.7",
"@blitzjs/config": "0.30.0-canary.7",
"@blitzjs/core": "0.30.0-canary.7",
"@blitzjs/display": "0.30.0-canary.7",
"@blitzjs/generator": "0.30.0-canary.7",
"@blitzjs/installer": "0.30.0-canary.7",
"@blitzjs/server": "0.30.0-canary.7",
"@testing-library/jest-dom": "5.11.9",
"@testing-library/react": "^11.2.3",
"@testing-library/react-hooks": "^4.0.1",
"@types/jest": "^26.0.20",
"envinfo": "^7.7.3",
"eslint-config-blitz": "0.30.0-canary.4",
"eslint-config-blitz": "0.30.0-canary.7",
"jest": "^26.6.3",
"jest-environment-jsdom-fourteen": "^1.0.1",
"jest-watch-typeahead": "^0.6.1",

View File

@@ -14,6 +14,8 @@ const common = {
"next",
"fs",
"path",
"os",
"tty",
],
plugins: [
json(),

View File

@@ -93,7 +93,7 @@ async function printEnvInfo() {
{
System: ["OS", "CPU", "Memory", "Shell"],
Binaries: ["Node", "Yarn", "npm", "Watchman"],
npmPackages: ["blitz", "typescript", "react", "react-dom", "@prisma/cli", "@prisma/client"],
npmPackages: ["blitz", "typescript", "react", "react-dom", "prisma", "@prisma/client"],
},
{showNotFound: true},
)

View File

@@ -33,5 +33,5 @@ blitz build
### Launch the development server
```bash
blitz start
blitz dev
```

View File

@@ -0,0 +1,3 @@
{
"integrationFolder": "test/e2e/cypress/integration"
}

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,21 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

@@ -0,0 +1,20 @@
// ***********************************************************
// 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.js using ES2015 syntax:
import "./commands"
// Alternatively you can use CommonJS syntax:
// require('./commands')

Binary file not shown.

View File

@@ -1,23 +1,9 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
moduleFileExtensions: ["ts", "js", "json"],
coverageReporters: ["json", "lcov", "text", "clover"],
preset: "../../jest.config.js",
// collectCoverage: !!`Boolean(process.env.CI)`,
collectCoverageFrom: ["src/**/*.ts"],
modulePathIgnorePatterns: ["<rootDir>/tmp", "<rootDir>/lib"],
testPathIgnorePatterns: ["src/commands/test.ts"],
testTimeout: 30000,
// TODO enable threshold
// coverageThreshold: {
// global: {
// branches: 100,
// functions: 100,
// lines: 100,
// statements: 100,
// },
// },
globals: {
"ts-jest": {
tsconfig: "test/tsconfig.json",

View File

@@ -1,7 +1,7 @@
{
"name": "@blitzjs/cli",
"description": "Blitz.js CLI",
"version": "0.30.0-canary.4",
"version": "0.30.0-canary.7",
"license": "MIT",
"scripts": {
"b": "./bin/run",
@@ -14,7 +14,9 @@
"dev": "rimraf lib && tsc --watch --preserveWatchOutput",
"build": "rimraf lib && tsc",
"test": "tsdx test",
"test:watch": "jest --watch"
"test:watch": "jest --watch",
"cypress:open": "cypress open",
"cypress:run": "cypress run"
},
"author": {
"name": "Brandon Bayer",
@@ -30,15 +32,15 @@
"/lib"
],
"dependencies": {
"@blitzjs/display": "0.30.0-canary.4",
"@blitzjs/repl": "0.30.0-canary.4",
"@blitzjs/display": "0.30.0-canary.7",
"@blitzjs/repl": "0.30.0-canary.7",
"@oclif/command": "1.8.0",
"@oclif/config": "1.17.0",
"@oclif/plugin-autocomplete": "0.3.0",
"@oclif/plugin-help": "3.2.1",
"@oclif/plugin-not-found": "1.2.4",
"@prisma/sdk": "2.15.0",
"@salesforce/lazy-require": "0.3.4",
"@prisma/sdk": "2.16.0",
"@salesforce/lazy-require": "0.4.0",
"camelcase": "^6.2.0",
"chalk": "4.1.0",
"cross-spawn": "7.0.3",
@@ -51,6 +53,7 @@
"hasbin": "1.2.3",
"import-cwd": "3.0.0",
"minimist": "1.2.5",
"module-alias": "2.2.2",
"p-event": "4.2.0",
"pkg-dir": "^5.0.0",
"pluralize": "^8.0.0",
@@ -61,13 +64,13 @@
"v8-compile-cache": "2.2.0"
},
"devDependencies": {
"@blitzjs/generator": "0.30.0-canary.4",
"@blitzjs/installer": "0.30.0-canary.4",
"@blitzjs/server": "0.30.0-canary.4",
"@blitzjs/generator": "0.30.0-canary.7",
"@blitzjs/installer": "0.30.0-canary.7",
"@blitzjs/server": "0.30.0-canary.7",
"@oclif/dev-cli": "1.26.0",
"@oclif/test": "1.2.8",
"@prisma/cli": "2.15.0",
"nock": "13.0.5",
"nock": "13.0.6",
"prisma": "2.16.0",
"stdout-stderr": "0.1.13"
},
"jest": {

View File

@@ -10,7 +10,7 @@ export function getDbName(connectionString: string): string {
async function runSeed() {
require("../utils/setup-ts-node").setupTsnode()
const projectRoot = require("../utils/get-project-root").projectRoot
const projectRoot = require("@blitzjs/config").getProjectRoot()
const seedPath = require("path").join(projectRoot, "db/seeds")
const dbPath = require("path").join(projectRoot, "db/index")

View File

@@ -0,0 +1,46 @@
import {ServerConfig} from "@blitzjs/server"
import {Command, flags} from "@oclif/command"
export class Dev extends Command {
static description = "Start a development server"
static aliases = ["d"]
static flags = {
help: flags.help({char: "h"}),
port: flags.integer({
char: "p",
description: "Set port number",
}),
hostname: flags.string({
char: "H",
description: "Set server hostname",
}),
inspect: flags.boolean({
description: "Enable the Node.js inspector",
}),
["no-incremental-build"]: flags.boolean({
description: "Disable incremental build and start from a fresh cache",
}),
}
async run() {
const {flags} = this.parse(Dev)
const config: ServerConfig = {
rootFolder: process.cwd(),
port: flags.port,
hostname: flags.hostname,
inspect: flags.inspect,
clean: flags["no-incremental-build"],
env: "dev",
}
try {
const dev = (await import("@blitzjs/server")).dev
await dev(config)
} catch (err) {
console.error(err)
process.exit(1) // clean up?
}
}
}

View File

@@ -2,12 +2,15 @@ import {flags} from "@oclif/command"
import {log} from "@blitzjs/display"
import {
PageGenerator,
MutationsGenerator,
MutationGenerator,
QueriesGenerator,
FormGenerator,
ModelGenerator,
QueryGenerator,
singleCamel,
capitalize,
uncapitalize,
singlePascal,
pluralCamel,
pluralPascal,
@@ -20,17 +23,18 @@ import chalk from "chalk"
const debug = require("debug")("blitz:generate")
const getIsTypeScript = () =>
require("fs").existsSync(
require("path").join(require("../utils/get-project-root").projectRoot, "tsconfig.json"),
require("path").join(require("@blitzjs/config").getProjectRoot(), "tsconfig.json"),
)
enum ResourceType {
All = "all",
Crud = "crud",
Model = "model",
Mutations = "mutations",
Pages = "pages",
Queries = "queries",
Query = "query",
Mutations = "mutations",
Mutation = "mutation",
Resource = "resource",
}
@@ -60,19 +64,20 @@ function ModelNames(input: string = "") {
const generatorMap = {
[ResourceType.All]: [
ModelGenerator,
PageGenerator,
FormGenerator,
QueriesGenerator,
MutationGenerator,
MutationsGenerator,
ModelGenerator,
],
[ResourceType.Crud]: [MutationGenerator, QueriesGenerator],
[ResourceType.Crud]: [MutationsGenerator, QueriesGenerator],
[ResourceType.Model]: [ModelGenerator],
[ResourceType.Mutations]: [MutationGenerator],
[ResourceType.Pages]: [PageGenerator, FormGenerator],
[ResourceType.Queries]: [QueriesGenerator],
[ResourceType.Query]: [QueryGenerator],
[ResourceType.Resource]: [ModelGenerator, QueriesGenerator, MutationGenerator],
[ResourceType.Mutations]: [MutationsGenerator],
[ResourceType.Mutation]: [MutationGenerator],
[ResourceType.Resource]: [QueriesGenerator, MutationsGenerator, ModelGenerator],
}
export class Generate extends Command {
@@ -226,7 +231,8 @@ export class Generate extends Command {
parentModels: modelNames(flags.parent),
ParentModel: ModelName(flags.parent),
ParentModels: ModelNames(flags.parent),
rawInput: model,
name: uncapitalize(model),
Name: capitalize(model),
dryRun: flags["dry-run"],
context: context,
useTs: getIsTypeScript(),

View File

@@ -185,7 +185,7 @@ export class New extends Command {
)
}
postInstallSteps.push("blitz start")
postInstallSteps.push("blitz dev")
this.log("\n" + log.withBrand("Your new Blitz app is ready! Next steps:") + "\n")

View File

@@ -3,8 +3,16 @@ import {Command} from "@oclif/command"
// @blitzjs/server imports react, so we must import the @blitzjs/server version of the
// local app instead of the global.
// import-cwd is required so this works correctly during new app generation
const getPrismaBin = () =>
require("import-cwd")("@blitzjs/server").resolveBinAsync("@prisma/cli", "prisma")
const getPrismaBin = async () => {
let bin: any
try {
bin = require("import-cwd")("@blitzjs/server").resolveBinAsync("prisma", "prisma")
} catch {
// legacy compatability
bin = require("import-cwd")("@blitzjs/server").resolveBinAsync("@prisma/cli", "prisma")
}
return bin
}
let prismaBin: string

Some files were not shown because too many files have changed in this diff Show More