Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c83167fda | ||
|
|
c28684b8e5 | ||
|
|
a590820c14 | ||
|
|
ff3ef58a47 | ||
|
|
e33c81a70f | ||
|
|
dc41f8eedc | ||
|
|
f49aa37892 | ||
|
|
e218d5f84d | ||
|
|
2eb4f791a8 | ||
|
|
fa0850d0a3 | ||
|
|
2d3c6cbfb8 | ||
|
|
6ff7e99d15 | ||
|
|
77ca03af96 | ||
|
|
f56ef9b2c5 | ||
|
|
991b4a9db0 | ||
|
|
0c8edbb8b2 | ||
|
|
2d75a2b7c7 | ||
|
|
b878c6845e | ||
|
|
cb0da6a0cb | ||
|
|
a47c643145 | ||
|
|
945c66a53c | ||
|
|
50421a19ed | ||
|
|
926e5cf10f | ||
|
|
6228366ea3 | ||
|
|
bcb56eb79d | ||
|
|
80f0c81130 | ||
|
|
0847896968 |
3597
.all-contributorsrc
Normal file
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
||||
**/package.json
|
||||
*.d.ts
|
||||
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
5
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
|
||||
|
||||
* @flybayer @beerose
|
||||
|
||||
packages/generator/templates**/* @flybayer
|
||||
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
github: blitz-js
|
||||
custom: ["https://paypal.me/thebayers"]
|
||||
open_collective: blitzjs
|
||||
patreon: flybayer
|
||||
48
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Bug Report
|
||||
description: Something is not working right. Or error messages are unclear.
|
||||
labels: "kind/bug, status/triage"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What is the problem?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Paste all your error logs here:"
|
||||
value: |
|
||||
```
|
||||
PASTE_HERE (leave the ``` marks)
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Paste all relevant code snippets here:"
|
||||
value: |
|
||||
```
|
||||
PASTE_HERE (leave the ``` marks)
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What are detailed steps to reproduce this?
|
||||
value: "1."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Run `blitz -v` and paste the output here:"
|
||||
value: |
|
||||
```
|
||||
PASTE_HERE (leave the ``` marks)
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Please include below any other applicable logs and screenshots that show your problem:"
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Prisma issue?
|
||||
url: https://github.com/prisma/prisma/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc
|
||||
about: All Prisma issues should be opened in the Prisma Github
|
||||
- name: Question, Discussion, Idea?
|
||||
url: https://github.com/blitz-js/blitz/discussions/new
|
||||
about: Ask questions and discuss with other community members
|
||||
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Feature/change request
|
||||
about: Something new or better!
|
||||
title: ""
|
||||
labels: "status/triage"
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
### What do you want and why?
|
||||
|
||||
The more information the better!
|
||||
|
||||
### Possible implementation(s)
|
||||
|
||||
How might we do this?
|
||||
|
||||
### Additional context
|
||||
|
||||
Add any other context or screenshots about the feature request here.
|
||||
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<!--
|
||||
Thanks for opening a PR! Your contribution is much appreciated.
|
||||
To make sure your PR is handled as smoothly as possible please:
|
||||
- Link issue via "Closes #[issue_number]
|
||||
- Choose & follow the right checklist for the change that you're making:
|
||||
-->
|
||||
|
||||
Closes: ?
|
||||
|
||||
### What are the changes and their implications?
|
||||
|
||||
## Bug Checklist
|
||||
|
||||
- [ ] Integration test added (see [test docs](https://blitzjs.com/docs/contributing#running-tests) if needed)
|
||||
|
||||
## Feature Checklist
|
||||
|
||||
- [ ] Integration test added (see [test docs](https://blitzjs.com/docs/contributing#running-tests) if needed)
|
||||
- [ ] Documentation added/updated (submit PR to [blitzjs.com repo `canary` branch](https://github.com/blitz-js/blitzjs.com/tree/canary))
|
||||
38
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# https://github.com/vercel/next.js/commits/canary/.github/workflows/build_test_deploy.yml
|
||||
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
node_version:
|
||||
- 16
|
||||
name: Node ${{ matrix.node_version }} - ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
|
||||
with:
|
||||
version: 6.10.0
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
cache: "pnpm"
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm manypkg check
|
||||
- run: pnpm lint
|
||||
- run: pnpm build
|
||||
- run: pnpm build:apps
|
||||
- run: pnpm test
|
||||
70
.gitignore
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
.next/
|
||||
out/
|
||||
build
|
||||
dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# turbo
|
||||
.turbo
|
||||
|
||||
.log
|
||||
.DS_Store
|
||||
.idea
|
||||
.jest-*
|
||||
node_modules
|
||||
reports
|
||||
*.log
|
||||
.nyc_output
|
||||
**/coverage
|
||||
.yarn
|
||||
.yarnrc
|
||||
tsconfig.tsbuildinfo
|
||||
.blitz
|
||||
.next
|
||||
dist
|
||||
.now
|
||||
# local env files
|
||||
**/.env.local
|
||||
**/.env.*.local
|
||||
**/.envrc
|
||||
.blitz-*
|
||||
.blitz-cli-cache
|
||||
.vscode
|
||||
.tsbuildinfo
|
||||
.nvmrc
|
||||
**/.test*
|
||||
examples/auth2
|
||||
.idea
|
||||
.ultra.cache.json
|
||||
db.sqlite-journal
|
||||
test/integration/**/db.json
|
||||
test/**/*/out
|
||||
test/**/blitz-env.d.ts
|
||||
examples/**/blitz-env.d.ts
|
||||
.blitz**
|
||||
|
||||
1
.husky/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_
|
||||
6
.husky/pre-commit
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
pnpm manypkg check
|
||||
pnpm lint
|
||||
pnpx pretty-quick --staged
|
||||
1
.node-version
Normal file
@@ -0,0 +1 @@
|
||||
16.13.2
|
||||
7
.npmrc
Normal file
@@ -0,0 +1,7 @@
|
||||
save-exact=true
|
||||
|
||||
public-hoist-pattern[]=secure-password
|
||||
public-hoist-pattern[]=*types*
|
||||
public-hoist-pattern[]=*eslint*
|
||||
public-hoist-pattern[]=@prettier/plugin-*
|
||||
public-hoist-pattern[]=*prettier-plugin-*
|
||||
20
.prettierignore
Normal file
@@ -0,0 +1,20 @@
|
||||
**/migrations/**
|
||||
.blitz
|
||||
.next
|
||||
.log
|
||||
.DS_Store
|
||||
.jest-*
|
||||
packages/cli/lib
|
||||
node_modules
|
||||
reports
|
||||
*.log
|
||||
**/.env*
|
||||
.nyc_output
|
||||
**/coverage
|
||||
.yarn
|
||||
.yarnrc
|
||||
tsconfig.tsbuildinfo
|
||||
dist
|
||||
bin
|
||||
.github/ISSUE_TEMPLATE/bug_report.md
|
||||
packages/generator/templates/**
|
||||
3
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# The Blitz Community Code of Conduct
|
||||
|
||||
[Read the Code of Conduct at Blitzjs.com](https://blitzjs.com/docs/code-of-conduct)
|
||||
3
CONTRIBUTING.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Contributing
|
||||
|
||||
[Read the Contributing Guide at Blitzjs.com](https://blitzjs.com/docs/contributing)
|
||||
3
CONTRIBUTOR_STATS.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Contributor over time
|
||||
|
||||
[](https://www.apiseven.com/en/contributor-graph?chart=contributorOverTime&repo=blitz-js/blitz)
|
||||
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Brandon Bayer
|
||||
Copyright (c) 2022 Blitz Revolution Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
753
README.md
@@ -1,2 +1,751 @@
|
||||
# blitz
|
||||
Framework for building monolithic, full-stack, serverless React apps with zero data-fetching and zero client-side state management
|
||||
[](https://blitzjs.com)
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
<p align="center">
|
||||
<a aria-label="Join our Discord Community" href="https://discord.blitzjs.com">
|
||||
<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-381-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">
|
||||
</a>
|
||||
<a aria-label="NPM version" href="https://www.npmjs.com/package/blitz">
|
||||
<img alt="" src="https://img.shields.io/npm/v/blitz.svg?style=for-the-badge&labelColor=000000&color=E65528">
|
||||
</a>
|
||||
</p>
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<br>
|
||||
|
||||
<h1 align="center">The Fullstack React Framework</h1>
|
||||
|
||||
<h5 align="center">"Zero-API" Data Layer — Built on Next.js — Inspired by Ruby on Rails</h3>
|
||||
<h3 align="center"><a href="https://blitzjs.com/docs/get-started" target="_blank">Read the Documentation</a></h3>
|
||||
<br>
|
||||
|
||||
“Zero-API” data layer **lets you import server code directly into your React components** instead of having to manually add API endpoints and do client-side fetching and caching.
|
||||
|
||||
New Blitz apps come with **all the boring stuff already set up for you!** Like ESLint, Prettier, Jest, user sign up, log in, and password reset.
|
||||
|
||||
Provides **helpful defaults and conventions** for things like routing, file structure, and authentication while also being extremely flexible.
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
### Quick Start
|
||||
|
||||
You need Node.js 12 or newer
|
||||
|
||||
#### Install Blitz
|
||||
|
||||
Run `npm install -g blitz` or `yarn global add blitz`
|
||||
|
||||
_You can alternatively use [`npx`](https://www.npmjs.com/package/npx)_
|
||||
|
||||
#### Create a New App
|
||||
|
||||
1. `blitz new myAppName`
|
||||
2. `cd myAppName`
|
||||
3. `blitz dev`
|
||||
4. View your brand new app at http://localhost:3000
|
||||
|
||||
<br><br>
|
||||
|
||||
<a aria-label="Bytes Newsletter" href="https://ui.dev/bytes/?r=blitzjs">
|
||||
<img alt="Bytes Newsletter" src="https://files-8wtskjofb.vercel.app/smarter-16x1.jpg">
|
||||
</a>
|
||||
|
||||
|
||||
<br><br>
|
||||
|
||||
|
||||
|
||||
### The Foundational Principles
|
||||
|
||||
1. Fullstack & Monolithic
|
||||
2. API Not Required
|
||||
3. Convention over Configuration
|
||||
4. Loose Opinions
|
||||
5. Easy to Start, Easy to Scale
|
||||
6. Stability
|
||||
7. Community over Code
|
||||
|
||||
[The Blitz Manifesto](https://blitzjs.com/docs/manifesto) explains these principles in detail.
|
||||
|
||||
<br>
|
||||
|
||||
### What is Blitz Designed For?
|
||||
|
||||
Blitz is designed for tiny to large database-backed applications that have one or more graphical user interfaces.
|
||||
|
||||
While we currently only support web, we are pursuing the dream of a single monolithic application that runs on web and mobile with maximum code sharing and minimal boilerplate.
|
||||
|
||||
<br>
|
||||
|
||||
## Welcome to the Blitz Community 👋
|
||||
|
||||
The Blitz community is warm, safe, diverse, inclusive, and fun! LGBTQ+, women, and minorities are especially welcome. Please read our [Code of Conduct](https://blitzjs.com/docs/code-of-conduct).
|
||||
|
||||
[Join our Discord Community](https://discord.blitzjs.com) where we help each other build Blitz apps. It's also where we collaborate on building Blitz itself.
|
||||
|
||||
For questions and longer form discussions, [post in our forum](https://github.com/blitz-js/blitz/discussions).
|
||||
|
||||
There's still a lot of work to do, so you are especially invited to join us in building Blitz! A good place to start is [The Contributing Guide](https://blitzjs.com/docs/contributing).
|
||||
|
||||
<br>
|
||||
|
||||
## Financial Contributors
|
||||
|
||||
Your financial contributions help ensure Blitz continues to be developed and maintained! We have monthly sponsorship options starting at $5/month.
|
||||
|
||||
👉 View options and contribute at [GitHub Sponsors](https://github.com/sponsors/blitz-js), [PayPal](https://paypal.me/thebayers), or [Open Collective](https://opencollective.com/blitzjs)
|
||||
|
||||
|
||||
### 🌱 Seedling Sponsors
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><a aria-label="Andreas Asprou" href="https://andreas.fyi">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/andreas.jpg" width="40px"/>
|
||||
</a></td>
|
||||
<td><a aria-label="Digas" href="https://digsas.com">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/digsas.svg" width="40px"/>
|
||||
</a></td>
|
||||
<td><a aria-label="userTrack" href="https://www.usertrack.net/?ref=blitzjs">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/usertrack.png" width="40px"/>
|
||||
</a></td>
|
||||
<td><a aria-label="MeetKai" href="https://meetkai.com/?ref=blitzjs">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/meetkai.png" width="40px"/>
|
||||
</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
### 🥉 Bronze Sponsors
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><a aria-label="Render.com" href="https://render.com?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2020">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/render-logo-color2.png" width="200px">
|
||||
</a></td>
|
||||
<td><a aria-label="RIT" href="https://rit-inc.co.jp/?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2021">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/rit_logo.png" width="200px">
|
||||
</a></td>
|
||||
<td><a aria-label="Boostry" href="https://boostry.co.jp/?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2021">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/boostry.svg" width="200px">
|
||||
</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
### 🥈 Silver Sponsors
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<a aria-label="Fauna" href="https://dashboard.fauna.com/accounts/register?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2020">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/Fauna_Logo_Blue.png" width="300px">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 🏆 Gold Sponsors
|
||||
|
||||
<a aria-label="Your Company" href="#">
|
||||
<img alt="" src="https://dummyimage.com/1000x330/efe8ff/000000.png&text=Your+Logo+Here" width="400px">
|
||||
</a>
|
||||
|
||||
### 💎 Diamond Sponsors
|
||||
|
||||
<a aria-label="Your Company" href="#">
|
||||
<img alt="" src="https://dummyimage.com/1000x330/efe8ff/000000.png&text=Your+Logo+Here" width="500px">
|
||||
</a>
|
||||
|
||||
<br>
|
||||
|
||||
## Core Team ✨
|
||||
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://twitter.com/flybayer"><img src="https://avatars3.githubusercontent.com/u/8813276?v=4" width="100px;" alt=""/><br /><sub><b>Brandon Bayer</b></sub></a><br />Creator</td>
|
||||
<td align="center"><a href="http://aleksandra.codes"><img src="https://avatars.githubusercontent.com/u/9019397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aleksandra Sikora</b></sub></a><br />Lead Maintainer</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
## Maintainers (Level 2) ✨
|
||||
|
||||
_Code ownership, pull request approvals and merging, etc_ (see [Maintainers L2](https://blitzjs.com/docs/maintainers#level-2-maintainers))
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="http://simonknott.de"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="100px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br />SuperJSON</td>
|
||||
<td align="center"><a href="https://juanm04.com"><img src="https://avatars0.githubusercontent.com/u/16712703?v=4" width="100px;" alt=""/><br /><sub><b>Juan Martín Seery</b></sub></a><br />Website/Docs</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<br>
|
||||
|
||||
## Maintainers (Level 1) ✨
|
||||
|
||||
_Issue triage, pull request triage, community encouragement and moderation, etc_ (see [Maintainers L1](https://blitzjs.com/docs/maintainers#level-1-maintainers))
|
||||
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="http://jeremyliberman.com/"><img src="https://avatars3.githubusercontent.com/u/2754163?v=4" width="100px;" alt=""/><br /><sub><b>Jeremy Liberman</b></td>
|
||||
<td align="center">
|
||||
<a href="https://mina.ca">
|
||||
<img src="https://avatars.githubusercontent.com/mabadir" width="100px;" alt="Mina Abadir avatar" /><br />
|
||||
<sub>
|
||||
<b>Mina Abadir</b>
|
||||
</sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://builtforfifty.com">
|
||||
<img src="https://avatars.githubusercontent.com/abuuzayr" width="100px;" alt="Abu Uzayr avatar" /><br />
|
||||
<sub>
|
||||
<b>Abu Uzayr</b>
|
||||
</sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://damilolarandolph.com/">
|
||||
<img src="https://avatars.githubusercontent.com/damilolarandolph" width="100px;" alt="Damilola Randolph avatar" /><br />
|
||||
<sub>
|
||||
<b>Damilola Randolph</b>
|
||||
</sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://twitter.com/flybayer"><img src="https://avatars3.githubusercontent.com/u/8813276?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brandon Bayer</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=flybayer" title="Code">💻</a> <a href="#content-flybayer" title="Content">🖋</a> <a href="#ideas-flybayer" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aflybayer" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=flybayer" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=flybayer" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://medium.com/@ryardley"><img src="https://avatars0.githubusercontent.com/u/1256409?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rudi Yardley</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ryardley" title="Code">💻</a> <a href="#ideas-ryardley" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aryardley" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=ryardley" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://merelinguist.me"><img src="https://avatars3.githubusercontent.com/u/24858006?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dylan Brookes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=merelinguist" title="Code">💻</a> <a href="#ideas-merelinguist" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Amerelinguist" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=merelinguist" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=merelinguist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/aem"><img src="https://avatars0.githubusercontent.com/u/1909883?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Adam Markon</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aem" title="Code">💻</a> <a href="#ideas-aem" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aaem" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=aem" title="Tests">⚠️</a> <a href="#maintenance-aem" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://corey-brown.com"><img src="https://avatars1.githubusercontent.com/u/12791148?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Corey Brown</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=coreybrown89" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Acoreybrown89" title="Reviewed Pull Requests">👀</a> <a href="#maintenance-coreybrown89" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://github.com/LoriKarikari"><img src="https://avatars1.githubusercontent.com/u/7902980?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lori Karikari</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=LoriKarikari" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3ALoriKarikari" title="Reviewed Pull Requests">👀</a> <a href="#maintenance-LoriKarikari" title="Maintenance">🚧</a> <a href="https://github.com/blitz-js/blitz/commits?author=LoriKarikari" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/GeggsElias"><img src="https://avatars3.githubusercontent.com/u/22719177?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Elias Johansson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=eliasjohansson" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Aeliasjohansson" title="Reviewed Pull Requests">👀</a> <a href="#maintenance-eliasjohansson" title="Maintenance">🚧</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://fabulas.io"><img src="https://avatars1.githubusercontent.com/u/14793389?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Edelman </b></sub></a><br /><a href="#infra-medelman17" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/blitz-js/blitz/commits?author=medelman17" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.geistinteractive.com"><img src="https://avatars2.githubusercontent.com/u/316792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Todd Geist</b></sub></a><br /><a href="#financial-toddgeist" title="Financial">💵</a> <a href="https://github.com/blitz-js/blitz/commits?author=toddgeist" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://robdrosenberg.com"><img src="https://avatars0.githubusercontent.com/u/20813991?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robert Rosenberg</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=robdrosenberg" title="Code">💻</a> <a href="#maintenance-robdrosenberg" title="Maintenance">🚧</a> <a href="https://github.com/blitz-js/blitz/commits?author=robdrosenberg" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/quirk0o"><img src="https://avatars3.githubusercontent.com/u/5123725?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Beata Obrok</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=quirk0o" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/tsawan"><img src="https://avatars3.githubusercontent.com/u/3263082?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tahir Awan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tsawan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://raluce.com"><img src="https://avatars1.githubusercontent.com/u/2454632?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Camilo Gonzalez</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=camilo86" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://da.nielkempner.com"><img src="https://avatars3.githubusercontent.com/u/2532112?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Kempner</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dkempner" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://gielcobben.com"><img src="https://avatars0.githubusercontent.com/u/2663212?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=gielcobben" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://jeremyliberman.com/"><img src="https://avatars3.githubusercontent.com/u/2754163?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jeremy Liberman</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MrLeebo" title="Code">💻</a> <a href="#maintenance-MrLeebo" title="Maintenance">🚧</a> <a href="https://github.com/blitz-js/blitz/commits?author=MrLeebo" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=MrLeebo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jimthedev.com"><img src="https://avatars0.githubusercontent.com/u/108938?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jim Cummins</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jimthedev" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kristinamatuska.com/"><img src="https://media-exp1.licdn.com/dms/image/C5603AQHVPAjV21gw9g/profile-displayphoto-shrink_200_200/0?e=1591228800&v=beta&t=0MlbmiYhNvGv1xjLD_fOhOFjVDZ7ltNwfGNeJ4DHedQ?s=100" width="100px;" alt=""/><br /><sub><b>Kristina Matuška</b></sub></a><br /><a href="#design" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/jasonblalock"><img src="https://avatars0.githubusercontent.com/u/5899929?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jason Blalock</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jasonblalock" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/aej11a"><img src="https://avatars2.githubusercontent.com/u/10066422?v=4?s=100" width="100px;" alt=""/><br /><sub><b>aej11a</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aej11a" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/marcoseoane"><img src="https://avatars0.githubusercontent.com/u/28088807?v=4?s=100" width="100px;" alt=""/><br /><sub><b>marcoseoane</b></sub></a><br /><a href="#ideas-marcoseoane" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/rishabhpoddar"><img src="https://avatars2.githubusercontent.com/u/2976287?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rishabh Poddar</b></sub></a><br /><a href="#ideas-rishabhpoddar" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://github.com/lorenzorapetti"><img src="https://avatars1.githubusercontent.com/u/2632174?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lorenzo Rapetti</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lorenzorapetti" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/wKovacs64"><img src="https://avatars1.githubusercontent.com/u/1288694?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Justin Hall</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wKovacs64" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=wKovacs64" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sijad"><img src="https://avatars3.githubusercontent.com/u/7693001?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sajjad Hashemian</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sijad" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ETLopes"><img src="https://avatars3.githubusercontent.com/u/34959471?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eduardo Lopes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ETLopes" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mattleff"><img src="https://avatars0.githubusercontent.com/u/120155?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matthew Leffler</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mattleff" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://hew.tools"><img src="https://avatars0.githubusercontent.com/u/3103241?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hew" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/sonnypgs"><img src="https://avatars3.githubusercontent.com/u/1431300?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sonny</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sonnypgs" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Zeko369"><img src="https://avatars3.githubusercontent.com/u/3064377?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fran Zekan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Zeko369" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Zeko369" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://twitter.com/JanBaykara"><img src="https://avatars2.githubusercontent.com/u/237556?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jan Baykara</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=janbaykara" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mikeattara.com"><img src="https://avatars1.githubusercontent.com/u/31483629?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mike Perry Y Attara</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mikeattara" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://devanthe.dev"><img src="https://avatars0.githubusercontent.com/u/354652?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Devan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=DevanB" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jclancy93"><img src="https://avatars2.githubusercontent.com/u/7850202?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jack Clancy</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jclancy93" title="Code">💻</a> <a href="#maintenance-jclancy93" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://github.com/ntgussoni"><img src="https://avatars0.githubusercontent.com/u/10161067?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nicolas Torres</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Antgussoni" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://simonknott.de"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Skn0tt" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Skn0tt" title="Tests">⚠️</a> <a href="#maintenance-Skn0tt" title="Maintenance">🚧</a> <a href="https://github.com/blitz-js/blitz/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jagascript.com"><img src="https://avatars0.githubusercontent.com/u/4562878?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jaga Santagostino</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kandros" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=kandros" title="Documentation">📖</a> <a href="#maintenance-kandros" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="http://www.joaoportela.com"><img src="https://avatars0.githubusercontent.com/u/1010018?v=4?s=100" width="100px;" alt=""/><br /><sub><b>João Portela</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jportela" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://dajin.dev"><img src="https://avatars0.githubusercontent.com/u/7122182?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Da-Jin Chu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dajinchu" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://shinyaigeek.dev/"><img src="https://avatars1.githubusercontent.com/u/42742053?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shinobu Hayashi</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Shinyaigeek" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://karankiri.com"><img src="https://avatars2.githubusercontent.com/u/19989161?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Karan Kiri</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=karankiri" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/fullmetalengineer"><img src="https://avatars2.githubusercontent.com/u/5294903?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alan Long</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fullmetalengineer" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://codingsh.xyz"><img src="https://avatars2.githubusercontent.com/u/57037080?v=4?s=100" width="100px;" alt=""/><br /><sub><b>codingsh</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=developerfred" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://twitter.com/peaonunes"><img src="https://avatars0.githubusercontent.com/u/3356720?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rafael Nunes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Apeaonunes" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=peaonunes" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://simonpeterdebbarma.com"><img src="https://avatars3.githubusercontent.com/u/31207418?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Simon Debbarma</b></sub></a><br /><a href="#design-0ww" title="Design">🎨</a> <a href="#maintenance-0ww" title="Maintenance">🚧</a> <a href="https://github.com/blitz-js/blitz/commits?author=0ww" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/0xflotus"><img src="https://avatars3.githubusercontent.com/u/26602940?v=4?s=100" width="100px;" alt=""/><br /><sub><b>0xflotus</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=0xflotus" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=0xflotus" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dev.to/tmns"><img src="https://avatars3.githubusercontent.com/u/35785003?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tmns</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tmns" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=tmns" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jruharris.com"><img src="https://avatars1.githubusercontent.com/u/8636691?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jru Harris</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=harris1717" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/ivandevp"><img src="https://avatars3.githubusercontent.com/u/9284690?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ivan Medina</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ivandevp" title="Code">💻</a> <a href="#maintenance-ivandevp" title="Maintenance">🚧</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.dwightwatson.com"><img src="https://avatars3.githubusercontent.com/u/1100408?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dwight Watson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dwightwatson" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=dwightwatson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://is2ei.com/"><img src="https://avatars3.githubusercontent.com/u/3948353?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Horie Issei</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=is2ei" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://twitter.com/lednhatkhanh"><img src="https://avatars2.githubusercontent.com/u/9303093?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nhat Khanh</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lednhatkhanh" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://builtforfifty.com"><img src="https://avatars1.githubusercontent.com/u/19371989?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abu Uzayr</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=abuuzayr" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=abuuzayr" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/nabi009"><img src="https://avatars0.githubusercontent.com/u/3170831?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nabiullah elham</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nabi009" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://lachlanjc.com"><img src="https://avatars1.githubusercontent.com/u/5074763?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lachlan Campbell</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lachlanjc" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://enzoferey.com"><img src="https://avatars1.githubusercontent.com/u/10673347?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Enzo Ferey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=enzoferey" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/pgrimaud"><img src="https://avatars1.githubusercontent.com/u/1866496?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pierre Grimaud</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=pgrimaud" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://pixelmord.github.io"><img src="https://avatars2.githubusercontent.com/u/224168?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andreas Adam</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=pixelmord" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://kevo.dev"><img src="https://avatars3.githubusercontent.com/u/15717067?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Tovar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kevotovar" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://anteprimorac.com.hr"><img src="https://avatars0.githubusercontent.com/u/972083?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ante Primorac</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=anteprimorac" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=anteprimorac" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://mykalmachon.dev"><img src="https://avatars1.githubusercontent.com/u/7844994?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mykal Machon</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MykalMachon" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://jamiedavenport.dev"><img src="https://avatars2.githubusercontent.com/u/1329874?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jamie Davenport</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jamiedavenport" title="Code">💻</a> <a href="#maintenance-jamiedavenport" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://cloudnweb.dev/"><img src="https://avatars0.githubusercontent.com/u/17050715?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GaneshMani</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ganeshmani" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://ramonmorcillo.com"><img src="https://avatars3.githubusercontent.com/u/31936665?v=4?s=100" width="100px;" alt=""/><br /><sub><b>reymon359</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=reymon359" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/gregory-vasquez-96413b184/"><img src="https://avatars1.githubusercontent.com/u/36422346?v=4?s=100" width="100px;" alt=""/><br /><sub><b>gvasquez11</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=gvasquez11" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/josemiguelo"><img src="https://avatars1.githubusercontent.com/u/15330034?v=4?s=100" width="100px;" alt=""/><br /><sub><b> José Miguel Ochoa</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=josemiguelo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/osirvent"><img src="https://avatars2.githubusercontent.com/u/5927133?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Oscar Sirvent</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=osirvent" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=osirvent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/donni106"><img src="https://avatars0.githubusercontent.com/u/1942953?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Molnar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=donni106" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=donni106" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/exclipy"><img src="https://avatars1.githubusercontent.com/u/508799?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Wu Won</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=exclipy" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/tehnuge"><img src="https://avatars1.githubusercontent.com/u/1928236?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Duong</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tehnuge" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://noahfleischmann.com"><img src="https://avatars0.githubusercontent.com/u/23707137?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Noah Fleischmann</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fnoah" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/toshi1127"><img src="https://avatars3.githubusercontent.com/u/32378535?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matsumoto Toshi</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=toshi1127" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=toshi1127" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/simonedelmann"><img src="https://avatars2.githubusercontent.com/u/2821076?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Simon Edelmann</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=simonedelmann" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://shaun.church"><img src="https://avatars3.githubusercontent.com/u/571764?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shaun Church</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=shaunchurch" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=shaunchurch" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://styfle.dev"><img src="https://avatars1.githubusercontent.com/u/229881?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/SigurdMW"><img src="https://avatars3.githubusercontent.com/u/6359003?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sigurd Moland Wahl</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=SigurdMW" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://brianandrews.dev/"><img src="https://avatars1.githubusercontent.com/u/6384100?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brian Andrews</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sbardian" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://garrisonsnelling.com"><img src="https://avatars0.githubusercontent.com/u/5100597?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Garrison Snelling</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=garrisons" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/tylangesmith"><img src="https://avatars1.githubusercontent.com/u/22609577?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ty Lange-Smith</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tylangesmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://rubenmoya.dev"><img src="https://avatars3.githubusercontent.com/u/905225?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rubén Moya</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rubenmoya" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=rubenmoya" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/robertgrzonka"><img src="https://avatars0.githubusercontent.com/u/35585466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>robertgrzonka</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=robertgrzonka" title="Code">💻</a> <a href="#infra-robertgrzonka" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center"><a href="https://github.com/agoxlea"><img src="https://avatars3.githubusercontent.com/u/1240841?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alex Orr</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=agoxlea" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://christse.io"><img src="https://avatars1.githubusercontent.com/u/250450?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chris Tse</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=chris-tse" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://twitter.com/nettofarah"><img src="https://avatars1.githubusercontent.com/u/270688?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Netto Farah</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nettofarah" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/rohanjulka19"><img src="https://avatars0.githubusercontent.com/u/19673968?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rohan Julka</b></sub></a><br /><a href="#infra-rohanjulka19" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center"><a href="https://www.ivansantos.me"><img src="https://avatars3.githubusercontent.com/u/301291?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ivan Santos</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=pragmaticivan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://able.bio"><img src="https://avatars0.githubusercontent.com/u/12991390?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Soumyajit Pathak</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=drenther" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.sebastiankurpiel.com"><img src="https://avatars2.githubusercontent.com/u/16307737?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sebastian Kurpiel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=SebastianKurp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/scisteffan"><img src="https://avatars2.githubusercontent.com/u/2676185?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Steffan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=scisteffan" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=scisteffan" title="Documentation">📖</a> <a href="#financial-scisteffan" title="Financial">💵</a></td>
|
||||
<td align="center"><a href="https://github.com/kripod"><img src="https://avatars3.githubusercontent.com/u/14854048?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kristóf Poduszló</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kripod" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Weilbyte"><img src="https://avatars1.githubusercontent.com/u/43392677?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Weilbyte</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Weilbyte" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Weilbyte" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://ricardotrejos.tech"><img src="https://avatars1.githubusercontent.com/u/8602086?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo Trejos</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cardotrejos" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=cardotrejos" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://gkaragkiaouris.tech/"><img src="https://avatars0.githubusercontent.com/u/8822835?v=4?s=100" width="100px;" alt=""/><br /><sub><b>George Karagkiaouris</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=karaggeorge" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=karaggeorge" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/brady-pascoe-3bba6b13a/"><img src="https://avatars0.githubusercontent.com/u/18705892?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brady Pascoe</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=bpas247" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.yeahcoach.com"><img src="https://avatars1.githubusercontent.com/u/761766?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jirka Svoboda</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=svobik7" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/alan2207"><img src="https://avatars3.githubusercontent.com/u/12713315?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alan Alickovic</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=alan2207" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=alan2207" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://yngve.hoiseth.net"><img src="https://avatars0.githubusercontent.com/u/8469540?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yngve Høiseth</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=yhoiseth" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/bruno_crosier"><img src="https://avatars1.githubusercontent.com/u/18399089?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bruno Crosier</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=brunocrosier" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/jschepmans"><img src="https://avatars2.githubusercontent.com/u/5782977?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Johan Schepmans</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jschepmans" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://twitter.com/dillonraphael"><img src="https://avatars0.githubusercontent.com/u/3496193?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dillon Raphael</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dillonraphael" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/clgeoio"><img src="https://avatars2.githubusercontent.com/u/37571416?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cody G</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/madflow"><img src="https://avatars0.githubusercontent.com/u/183248?v=4?s=100" width="100px;" alt=""/><br /><sub><b>madflow</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=madflow" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Code">💻</a> <a href="#maintenance-nitaking" title="Maintenance">🚧</a> <a href="#question-nitaking" title="Answering Questions">💬</a> <a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sirmyron"><img src="https://avatars2.githubusercontent.com/u/1430136?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sirmyron</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sirmyron" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=sirmyron" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/engelkes-finstreet"><img src="https://avatars1.githubusercontent.com/u/36962022?v=4?s=100" width="100px;" alt=""/><br /><sub><b>engelkes-finstreet</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Code">💻</a> <a href="#maintenance-engelkes-finstreet" title="Maintenance">🚧</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://twitter.com/pixelscommander"><img src="https://avatars2.githubusercontent.com/u/810671?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Denis Radin</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3APixelsCommander" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=PixelsCommander" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=PixelsCommander" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/xiaoyu-tamu"><img src="https://avatars3.githubusercontent.com/u/33362998?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Li</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=xiaoyu-tamu" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/yuta0801"><img src="https://avatars2.githubusercontent.com/u/21266306?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yuta0801</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=yuta0801" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Obii-bit"><img src="https://avatars2.githubusercontent.com/u/67339820?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Obadja Ris</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Obii-bit" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jfelix.info"><img src="https://avatars2.githubusercontent.com/u/21092519?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jose Felix </b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=JoseRFelix" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/johncantrell97"><img src="https://avatars3.githubusercontent.com/u/41305919?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Cantrell</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=johncantrell97" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kwuang.me"><img src="https://avatars1.githubusercontent.com/u/10319942?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kwuang Tang</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cktang88" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/johnletey"><img src="https://avatars1.githubusercontent.com/u/62398724?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Letey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=johnletey" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ditorojuan"><img src="https://avatars0.githubusercontent.com/u/22530892?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Juan Di Toro</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ditorojuan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/taylorcjohnson"><img src="https://avatars0.githubusercontent.com/u/10552296?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Taylor Johnson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=taylorcjohnson" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=taylorcjohnson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/tsriram"><img src="https://avatars3.githubusercontent.com/u/450559?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sriram Thiagarajan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tsriram" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://sergiodxa.com"><img src="https://avatars2.githubusercontent.com/u/1312018?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sergio Xalambrí</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sergiodxa" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/doeixd"><img src="https://avatars3.githubusercontent.com/u/13461122?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Patrick G</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=doeixd" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://avinash.com.np"><img src="https://avatars3.githubusercontent.com/u/513457?v=4?s=100" width="100px;" alt=""/><br /><sub><b>अभिनाश (Avinash)</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hardfire" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://enricoschaaf.com"><img src="https://avatars1.githubusercontent.com/u/54645197?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Enrico Schaaf</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=enricoschaaf" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kitze.io"><img src="https://avatars0.githubusercontent.com/u/1160594?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kitze</b></sub></a><br /><a href="#ideas-kitze" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://github.com/drmas"><img src="https://avatars3.githubusercontent.com/u/644440?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mohamed Shaban</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=drmas" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/jorisre"><img src="https://avatars1.githubusercontent.com/u/7545547?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joris</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jorisre" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Kamshak"><img src="https://avatars3.githubusercontent.com/u/337968?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Valentin Funk</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Kamshak" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://lukebennett.com"><img src="https://avatars1.githubusercontent.com/u/135390?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Luke Bennett</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lukebennett" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://haseebmajid.dev"><img src="https://avatars0.githubusercontent.com/u/998807?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Haseeb Majid</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hmajid2301" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/phillippschmedt"><img src="https://avatars0.githubusercontent.com/u/16028406?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Phillipp Schmedt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=phillippschmedt" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://haspar.us"><img src="https://avatars0.githubusercontent.com/u/15332326?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Piotr Monwid-Olechnowicz</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hasparus" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://mizchi.dev"><img src="https://avatars2.githubusercontent.com/u/73962?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kotaro Chikuba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mizchi" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=mizchi" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/konradkalemba"><img src="https://avatars0.githubusercontent.com/u/8682104?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Konrad Kalemba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=konradkalemba" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=konradkalemba" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Alucard17"><img src="https://avatars1.githubusercontent.com/u/26205172?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alucard17</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Alucard17" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Dohxis"><img src="https://avatars2.githubusercontent.com/u/8768909?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Domantas Mauruča</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Dohxis" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=Dohxis" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://sandulat.com/"><img src="https://avatars0.githubusercontent.com/u/7345874?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stratulat Alexandru</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sandulat" title="Code">💻</a> <a href="#maintenance-sandulat" title="Maintenance">🚧</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/aericson"><img src="https://avatars3.githubusercontent.com/u/692542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>André Ericson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aericson" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=aericson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://Cajotafer.com"><img src="https://avatars2.githubusercontent.com/u/41461969?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Carlos Fernández</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cajotafer" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://oesterkilde.dk/"><img src="https://avatars1.githubusercontent.com/u/6379824?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Østerkilde</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Kosai106" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Kosai106" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/aaronfulkerson"><img src="https://avatars0.githubusercontent.com/u/31112737?v=4?s=100" width="100px;" alt=""/><br /><sub><b>aaronfulkerson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aaronfulkerson" title="Code">💻</a> <a href="#question-aaronfulkerson" title="Answering Questions">💬</a></td>
|
||||
<td align="center"><a href="https://github.com/alexnaiman"><img src="https://avatars3.githubusercontent.com/u/25799714?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexandru Naiman</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=alexnaiman" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://davidlutta.github.io/portfolio/"><img src="https://avatars2.githubusercontent.com/u/14890315?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Ezekiel Lutta</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=davidlutta" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/wanjuntham"><img src="https://avatars1.githubusercontent.com/u/49380551?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wanjuntham</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wanjuntham" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.nahuelchaves.xyz"><img src="https://avatars3.githubusercontent.com/u/96837?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Victor Nahuel Chaves</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nahue" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/peter50216"><img src="https://avatars3.githubusercontent.com/u/891109?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Peter Shih</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=peter50216" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://seweryn.kale.mba"><img src="https://avatars3.githubusercontent.com/u/37031328?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Seweryn Kalemba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sewerynkalemba" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://nksaraf.github.io"><img src="https://avatars2.githubusercontent.com/u/11255148?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nikhil Saraf</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nksaraf" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=nksaraf" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://zane.sh"><img src="https://avatars0.githubusercontent.com/u/16865690?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zane</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=zanedb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/dulcehc"><img src="https://avatars1.githubusercontent.com/u/19391835?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dulce Hernández</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dulcehc" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://markhaehnel.de"><img src="https://avatars2.githubusercontent.com/u/1516205?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mark Hähnel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=markhaehnel" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://stackoverflow.com/users/872395/nemesv"><img src="https://avatars0.githubusercontent.com/u/251330?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Viktor Nemes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nemesv" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://gabeoleary.com"><img src="https://avatars1.githubusercontent.com/u/16123225?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gabe O'Leary</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=goleary" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/machadolucasvp"><img src="https://avatars0.githubusercontent.com/u/44952113?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lucas Machado</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=machadolucasvp" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/maciekgrzybek"><img src="https://avatars2.githubusercontent.com/u/16546428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>maciek_grzybek</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=maciekgrzybek" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mweibel"><img src="https://avatars1.githubusercontent.com/u/307427?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Weibel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mweibel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://isoppp.com"><img src="https://avatars0.githubusercontent.com/u/16318727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hiroki Isogai</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=isoppp" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/matamatanot"><img src="https://avatars2.githubusercontent.com/u/39780486?v=4?s=100" width="100px;" alt=""/><br /><sub><b>matamatanot</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=matamatanot" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/ericsakmar"><img src="https://avatars3.githubusercontent.com/u/5620709?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eric Sakmar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ericsakmar" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/leggsimon"><img src="https://avatars2.githubusercontent.com/u/11544418?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Simon Legg</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=leggsimon" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://robsoriano.com"><img src="https://avatars3.githubusercontent.com/u/13049130?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robert Soriano</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wobsoriano" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/benediktms"><img src="https://avatars2.githubusercontent.com/u/48836135?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Benedikt Schnatterbeck</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=benediktms" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://taloranderson.com"><img src="https://avatars2.githubusercontent.com/u/11509865?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Talor Anderson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Talor-A" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Talor-A" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/akirabaruah"><img src="https://avatars2.githubusercontent.com/u/6751517?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akira Baruah</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=akirabaruah" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://chriswray.dev/"><img src="https://avatars0.githubusercontent.com/u/53663762?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christopher Wray</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cwray-tech" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/piotrski"><img src="https://avatars0.githubusercontent.com/u/244174?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Piotrek Tomczewski</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=piotrski" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=piotrski" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://raph.site"><img src="https://avatars3.githubusercontent.com/u/1575946?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Raphaël Huchet</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kattcorp.com"><img src="https://avatars1.githubusercontent.com/u/459267?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alex Johansson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=KATT" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://davidmazza.com"><img src="https://avatars0.githubusercontent.com/u/120893?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Mazza</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dmzza" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/rayandrews"><img src="https://avatars1.githubusercontent.com/u/4437323?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ray Andrew</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rayandrews" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=rayandrews" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://Dal.Design"><img src="https://avatars3.githubusercontent.com/u/43112535?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abdullah Mzaien</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Mzaien" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Mzaien" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://kwao.io"><img src="https://avatars2.githubusercontent.com/u/8839514?v=4?s=100" width="100px;" alt=""/><br /><sub><b>William Kwao</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=williamkwao" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/sakulstra"><img src="https://avatars3.githubusercontent.com/u/4396533?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lukas Strassel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sakulstra" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=sakulstra" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://thibpat.com"><img src="https://avatars3.githubusercontent.com/u/494686?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Thibaut Patel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tpatel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://jonstuebe.com"><img src="https://avatars0.githubusercontent.com/u/156722?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jon Stuebe</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jonstuebe" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://ugogo.dev"><img src="https://avatars2.githubusercontent.com/u/5040476?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ugo Onali</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ugogo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://saintmalik.me"><img src="https://avatars1.githubusercontent.com/u/37118134?v=4?s=100" width="100px;" alt=""/><br /><sub><b>SaintMalik</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=saintmalik" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://khaledgarbaya.net"><img src="https://avatars1.githubusercontent.com/u/1156093?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Khaled Garbaya</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Khaledgarbaya" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://tundera.dev"><img src="https://avatars0.githubusercontent.com/u/61833561?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tundera</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tundera" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=tundera" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=tundera" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/markylaing"><img src="https://avatars2.githubusercontent.com/u/41469221?v=4?s=100" width="100px;" alt=""/><br /><sub><b>markylaing</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=markylaing" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=markylaing" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://akfm.dev/"><img src="https://avatars2.githubusercontent.com/u/25711332?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akifumi Sato</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=AkifumiSato" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/beeplin"><img src="https://avatars3.githubusercontent.com/u/13058150?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Beep LIN</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=beeplin" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://mattwood.tech/"><img src="https://avatars1.githubusercontent.com/u/22530815?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matt Wood</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mattfwood" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://joaquin.axai.mx"><img src="https://avatars1.githubusercontent.com/u/15214?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joaquin Bravo Contreras</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jackbravo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/arjundubey-cr"><img src="https://avatars0.githubusercontent.com/u/40758425?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Arjun Dubey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=arjundubey-cr" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/chanand"><img src="https://avatars0.githubusercontent.com/u/1317789?v=4?s=100" width="100px;" alt=""/><br /><sub><b>chanand</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=chanand" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/phillipkregg"><img src="https://avatars0.githubusercontent.com/u/1066044?v=4?s=100" width="100px;" alt=""/><br /><sub><b>phillipkregg</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=phillipkregg" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://timothyreynolds.co.uk"><img src="https://avatars1.githubusercontent.com/u/168870?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tim Reynolds</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=timReynolds" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://linbudu.top/"><img src="https://avatars0.githubusercontent.com/u/48507806?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Linbudu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=linbudu599" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.superservice-international.com"><img src="https://avatars0.githubusercontent.com/u/6090492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>C Reimers</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=creimers" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/kyken"><img src="https://avatars2.githubusercontent.com/u/20137120?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tsuyoshi Osawa</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kyken" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://rembrandtreyes.com/"><img src="https://avatars1.githubusercontent.com/u/15057964?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rembrandt Reyes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rembrandtreyes" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=rembrandtreyes" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=rembrandtreyes" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://doi-t.net"><img src="https://avatars2.githubusercontent.com/u/5877477?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Toshiya Doi</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=doi-t" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.koolii.net/"><img src="https://avatars1.githubusercontent.com/u/3866581?v=4?s=100" width="100px;" alt=""/><br /><sub><b>t.kuriyama</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=koolii" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/malkomalko"><img src="https://avatars3.githubusercontent.com/u/763?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robert Malko</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=malkomalko" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ranjan-purbey"><img src="https://avatars3.githubusercontent.com/u/6953187?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ranjan Purbey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ranjan-purbey" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/tarunama"><img src="https://avatars3.githubusercontent.com/u/6047881?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tarunama</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tarunama" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.bacongravy.net/"><img src="https://avatars3.githubusercontent.com/u/16848768?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Kramer</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=bacongravy" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://mikeesto.com"><img src="https://avatars1.githubusercontent.com/u/21051488?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Esteban</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mikeesto" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/marina-ki"><img src="https://avatars0.githubusercontent.com/u/54174518?v=4?s=100" width="100px;" alt=""/><br /><sub><b>marina</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=marina-ki" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=marina-ki" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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> <a href="https://github.com/blitz-js/blitz/commits?author=akbo" title="Documentation">📖</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>
|
||||
<td align="center"><a href="https://brianypliu.com"><img src="https://avatars.githubusercontent.com/u/3888780?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brian Liu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=LBrian" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://aleksandra.codes"><img src="https://avatars.githubusercontent.com/u/9019397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aleksandra Sikora</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=beerose" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=beerose" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=beerose" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://juanm04.com"><img src="https://avatars.githubusercontent.com/u/16712703?v=4?s=100" width="100px;" alt=""/><br /><sub><b>JuanM04</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=JuanM04" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=JuanM04" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=JuanM04" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/arenddeboer"><img src="https://avatars.githubusercontent.com/u/7022204?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Arend de Boer</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=arenddeboer" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/fmilani"><img src="https://avatars.githubusercontent.com/u/1580375?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Felipe Milani</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fmilani" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://nxhx.org"><img src="https://avatars.githubusercontent.com/u/13018?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joe Edelman</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jxe" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/garytube"><img src="https://avatars.githubusercontent.com/u/3823504?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gary</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=garytube" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://oliverloops.com"><img src="https://avatars.githubusercontent.com/u/33361399?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Oliver Lopez </b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=oliverloops" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://decadentIpsum.me"><img src="https://avatars.githubusercontent.com/u/32861532?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andreas Zaralis</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=DecadentIpsum" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/davetorbeck"><img src="https://avatars.githubusercontent.com/u/5829885?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Torbeck</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=davetorbeck" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/gusgard"><img src="https://avatars.githubusercontent.com/u/2577356?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gustavo Gard</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=gusgard" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://narrationbox.com"><img src="https://avatars.githubusercontent.com/u/7126128?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Immortalin</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Immortalin" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://cristianbgp.com"><img src="https://avatars.githubusercontent.com/u/8507974?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cristian Granda</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cristianbgp" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://deniseyu.io"><img src="https://avatars.githubusercontent.com/u/8420094?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Denise Yu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=deniseyu" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://dellacorte.me"><img src="https://avatars.githubusercontent.com/u/295683?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrea Della Corte</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=andreadellacorte" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://aditsachde.com"><img src="https://avatars.githubusercontent.com/u/23707194?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Adit Sachde</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aditsachde" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/hirenchauhan2"><img src="https://avatars.githubusercontent.com/u/8999668?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hiren Chauhan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hirenchauhan2" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://remjx.com/"><img src="https://avatars.githubusercontent.com/u/35121685?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mark Jackson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=remjx" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=remjx" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://lewisb.cloud/"><img src="https://avatars.githubusercontent.com/u/51877955?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lewis Blackburn</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lewisblackburn" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/FDiskas"><img src="https://avatars.githubusercontent.com/u/468006?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vytenis</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=FDiskas" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://portfolio.matthieupetit.com"><img src="https://avatars.githubusercontent.com/u/12969089?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matthieu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=matthieu994" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=matthieu994" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/mitchazj"><img src="https://avatars.githubusercontent.com/u/15032956?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mitchell Johnson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mitchazj" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://roshan.page/"><img src="https://avatars.githubusercontent.com/u/31125563?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Roshan Manuel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Roesh" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Roesh" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Roesh" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://kevinlangleyjr.com"><img src="https://avatars.githubusercontent.com/u/877634?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Langley Jr.</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kevinlangleyjr" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=kevinlangleyjr" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://projet-test-99df0.firebaseapp.com/"><img src="https://avatars.githubusercontent.com/u/51029779?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gabriel Picard</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=heavygabriel" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ryanchenkie.com/"><img src="https://avatars.githubusercontent.com/u/1847678?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ryan Chenkie</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=chenkie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sbappan"><img src="https://avatars.githubusercontent.com/u/12586088?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Santhosh B. Appan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sbappan" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://stackoverflow.com/users/5207233/james-moran"><img src="https://avatars.githubusercontent.com/u/10858584?v=4?s=100" width="100px;" alt=""/><br /><sub><b>James Moran</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=james2406" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=james2406" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://fb.me/yz"><img src="https://avatars.githubusercontent.com/u/14841421?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jack Zhao</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=bugzpodder" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/the-red"><img src="https://avatars.githubusercontent.com/u/4494300?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hisaki Akaza</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=the-red" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://flavioander.com/"><img src="https://avatars.githubusercontent.com/u/14948074?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Flavio</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Flavyoo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://bhanuteja.dev/"><img src="https://avatars.githubusercontent.com/u/17903466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bhanu Teja Pachipulusu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=pbteja1998" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://twitter.com/pavestru"><img src="https://avatars.githubusercontent.com/u/10186479?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pavel Struhar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=pavestru" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://in-thepink.com/"><img src="https://avatars.githubusercontent.com/u/42126368?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Reo Ishiyama</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=reo777" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://macwright.com/"><img src="https://avatars.githubusercontent.com/u/32314?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tom MacWright</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tmcw" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://francoisbest.com"><img src="https://avatars.githubusercontent.com/u/1174092?v=4?s=100" width="100px;" alt=""/><br /><sub><b>François Best</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=franky47" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/FarazPatankar"><img src="https://avatars.githubusercontent.com/u/10681116?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Faraz Patankar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=FarazPatankar" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/ericvicenti"><img src="https://avatars.githubusercontent.com/u/1483597?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eric Vicenti</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ericvicenti" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=ericvicenti" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/amdolan"><img src="https://avatars.githubusercontent.com/u/2552275?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alex Dolan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=amdolan" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=amdolan" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=amdolan" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/Maastrich"><img src="https://avatars.githubusercontent.com/u/58431775?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mathis Pinsault</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Maastrich" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/gstranger"><img src="https://avatars.githubusercontent.com/u/36181416?v=4?s=100" width="100px;" alt=""/><br /><sub><b>gstranger</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=gstranger" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=gstranger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://twitter.com/_markeh"><img src="https://avatars.githubusercontent.com/u/1357323?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mark Hughes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=markhughes" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=markhughes" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="www.andrearizzello.work"><img src="https://avatars.githubusercontent.com/u/10348930?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrea Rizzello</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=andrearizzello" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="jahred.com.au"><img src="https://avatars.githubusercontent.com/u/13903378?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jahred Hope</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jahredhope" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="simonelnahas.github.io/"><img src="https://avatars.githubusercontent.com/u/29279201?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Simon El Nahas</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=simonelnahas" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="www.usertrack.net"><img src="https://avatars.githubusercontent.com/u/1384885?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Buleandra Cristian</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Cristy94" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Cristy94" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://palauisaac.me/"><img src="https://avatars.githubusercontent.com/u/12257885?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pedro Enrique Palau Isaac</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=peterpalau" title="Code">💻</a></td>
|
||||
<td align="center"><a href="www.seanbrydon.me"><img src="https://avatars.githubusercontent.com/u/55134778?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sean-brydon</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sean-brydon" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="buonerba.dev"><img src="https://avatars.githubusercontent.com/u/28837891?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alessandro</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Dieman89" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="www.jyutping.org"><img src="https://avatars.githubusercontent.com/u/11172180?v=4?s=100" width="100px;" alt=""/><br /><sub><b>laubonghaudoi</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=laubonghaudoi" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/TommasoBruno99"><img src="https://avatars.githubusercontent.com/u/61512591?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tommaso Bruno</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=TommasoBruno99" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="antonykamp.de"><img src="https://avatars.githubusercontent.com/u/45163503?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Antony</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=antonykamp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://blog.6nok.org"><img src="https://avatars.githubusercontent.com/u/868283?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fatih Altinok</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=frontsideair" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mokshitjain.co/"><img src="https://avatars.githubusercontent.com/u/50412128?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mokshit Jain</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Mokshit06" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://mubaidr.github.io"><img src="https://avatars.githubusercontent.com/u/2222702?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Ubaid Raza</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mubaidr" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=mubaidr" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/silicontwin"><img src="https://avatars.githubusercontent.com/u/121665?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nick Warren</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=silicontwin" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mlabate"><img src="https://avatars.githubusercontent.com/u/17139676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mlabate</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mlabate" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/lumaxis"><img src="https://avatars.githubusercontent.com/u/406937?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lukas Spieß</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lumaxis" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dawnofmidnight.vercel.app"><img src="https://avatars.githubusercontent.com/u/78233879?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DawnOfMidnight</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dawnofmidnight" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/kenzairaki/"><img src="https://avatars.githubusercontent.com/u/17203119?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kenza Iraki</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kirakik" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=kirakik" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/agustif"><img src="https://avatars.githubusercontent.com/u/6601142?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Agusti Fernandez</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=agustif" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Anjianto"><img src="https://avatars.githubusercontent.com/u/61521141?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Anjianto</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Anjianto" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://adrienblanc.com"><img src="https://avatars.githubusercontent.com/u/41756894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Blanc Adrien</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=adblanc" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/meepdeew"><img src="https://avatars.githubusercontent.com/u/43303008?v=4?s=100" width="100px;" alt=""/><br /><sub><b>meepdeew</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=meepdeew" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Hardik3296"><img src="https://avatars.githubusercontent.com/u/20360325?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hardik Gaur</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Hardik3296" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/acornellier"><img src="https://avatars.githubusercontent.com/u/8725423?v=4?s=100" width="100px;" alt=""/><br /><sub><b>acornellier</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=acornellier" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/craigglennie"><img src="https://avatars.githubusercontent.com/u/149281?v=4?s=100" width="100px;" alt=""/><br /><sub><b>craigglennie</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=craigglennie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.fernvillasenor.com"><img src="https://avatars.githubusercontent.com/u/5857808?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fernando Villasenor</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fernvilla" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/swiftgaruda"><img src="https://avatars.githubusercontent.com/u/16741392?v=4?s=100" width="100px;" alt=""/><br /><sub><b>swiftgaruda</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=swiftgaruda" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pplife.home.blog"><img src="https://avatars.githubusercontent.com/u/35653876?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pankaj Patil</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Patil2099" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="minaabadir.ca"><img src="https://avatars.githubusercontent.com/u/3389914?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mina Abadir</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mabadir" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=mabadir" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=mabadir" title="Tests">⚠️</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/frankiesardo"><img src="https://avatars.githubusercontent.com/u/1476561?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Francesco Sardo</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=frankiesardo" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=frankiesardo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/enemycnt"><img src="https://avatars.githubusercontent.com/u/320313?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nikolay</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=enemycnt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dipeshwagle.com"><img src="https://avatars.githubusercontent.com/u/4191022?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dipesh Wagle</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Dipeshwagle" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://codepoet.de"><img src="https://avatars.githubusercontent.com/u/462455?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Benjamin Bender</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=benbender" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://nima.sh"><img src="https://avatars.githubusercontent.com/u/3728170?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nima Shoghi</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nimashoghi" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/chronark"><img src="https://avatars.githubusercontent.com/u/18246773?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andreas Thomas</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=chronark" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/guoqqqi"><img src="https://avatars.githubusercontent.com/u/72343596?v=4?s=100" width="100px;" alt=""/><br /><sub><b>guoqqqi</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=guoqqqi" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/timbooker"><img src="https://avatars.githubusercontent.com/u/612681?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=timbooker" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=timbooker" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="http://orlowski.me/"><img src="https://avatars.githubusercontent.com/u/16357457?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marek Orłowski</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ormarek" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/AntoineGuestin"><img src="https://avatars.githubusercontent.com/u/70888750?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Antoine G</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=AntoineGuestin" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/swinner2"><img src="https://avatars.githubusercontent.com/u/6707308?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sean Winner</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=swinner2" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=swinner2" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=swinner2" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://usman-s.me"><img src="https://avatars.githubusercontent.com/u/51731966?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Max Programming</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=max-programming" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://makemake.sh"><img src="https://avatars.githubusercontent.com/u/353768?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sebastian Hoitz</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sebastianhoitz" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=sebastianhoitz" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/garnerp"><img src="https://avatars.githubusercontent.com/u/737307?v=4?s=100" width="100px;" alt=""/><br /><sub><b>garnerp</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=garnerp" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/kivi"><img src="https://avatars.githubusercontent.com/u/366163?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kivi</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kivi" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://dangreaves.com"><img src="https://avatars.githubusercontent.com/u/1036142?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dan Greaves</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dangreaves" title="Code">💻</a></td>
|
||||
<td align="center"><a href="lksnmnn.com"><img src="https://avatars.githubusercontent.com/u/4983285?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lukas Neumann</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lksnmnn" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=lksnmnn" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=lksnmnn" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="dbachrach.com"><img src="https://avatars.githubusercontent.com/u/45016?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dustin Bachrach</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dbachrach" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=dbachrach" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/ashikka"><img src="https://avatars.githubusercontent.com/u/58368421?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ashikka Gupta</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ashikka" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=ashikka" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/deini"><img src="https://avatars.githubusercontent.com/u/2752665?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Almaguer</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=deini" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.kevinpeters.net/about/"><img src="https://avatars.githubusercontent.com/u/12736734?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Peters</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=igeligel" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://anolilab.de"><img src="https://avatars.githubusercontent.com/u/2716058?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Bannert</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=prisis" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=prisis" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://benjakugler96.github.io/"><img src="https://avatars.githubusercontent.com/u/53273645?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Benja Kugler</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=benjakugler96" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://semeniuc.ml/"><img src="https://avatars.githubusercontent.com/u/3838856?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eric Semeniuc</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=esemeniuc" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=esemeniuc" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ricardo-rp"><img src="https://avatars.githubusercontent.com/u/30808767?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo Romero</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ricardo-rp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="exocortex.anothernode.com"><img src="https://avatars.githubusercontent.com/u/3286144?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Moritz Reiter</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=anothernode" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://msich.dev"><img src="https://avatars.githubusercontent.com/u/38794918?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matt Sichterman</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=msichterman" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/medihack"><img src="https://avatars.githubusercontent.com/u/120626?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kai Schlamp</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=medihack" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://muyiwa.me"><img src="https://avatars.githubusercontent.com/u/6832244?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muyiwa Olu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=muyiwaolu" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://2hr.me/"><img src="https://avatars.githubusercontent.com/u/4346154?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rabbi Hossain</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rabbihossain" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/bravo-kernel"><img src="https://avatars.githubusercontent.com/u/230500?v=4?s=100" width="100px;" alt=""/><br /><sub><b>bravo-kernel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=bravo-kernel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://samholmes.net"><img src="https://avatars.githubusercontent.com/u/8385528?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sam Holmes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sam3d" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://doncicuto.medium.com"><img src="https://avatars.githubusercontent.com/u/30386061?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Miguel Cabrerizo</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=doncicuto" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=doncicuto" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://zackhobson.com/"><img src="https://avatars.githubusercontent.com/u/12092?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zack Hobson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=zenhob" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=zenhob" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.mokhtar.dev"><img src="https://avatars.githubusercontent.com/u/13026820?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mokhtar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=m5r" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/kenkuan"><img src="https://avatars.githubusercontent.com/u/1924968?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ken Kuan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kenkuan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/meehawk"><img src="https://avatars.githubusercontent.com/u/80167324?v=4?s=100" width="100px;" alt=""/><br /><sub><b>meehawk</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=meehawk" title="Code">💻</a></td>
|
||||
<td align="center"><a href="rahulravindran.in"><img src="https://avatars.githubusercontent.com/u/10168946?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rahul Ravindran</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ravindranrahul" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/s-r-x"><img src="https://avatars.githubusercontent.com/u/41614937?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ilya</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=s-r-x" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=s-r-x" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=s-r-x" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/hashimwarren"><img src="https://avatars.githubusercontent.com/u/6027587?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hashim Warren</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hashimwarren" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://damilolarandolph.com"><img src="https://avatars.githubusercontent.com/u/43427949?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Damilola Randolph</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=damilolarandolph" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/mwcampbell"><img src="https://avatars.githubusercontent.com/u/214820?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matt Campbell</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mwcampbell" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/ratson"><img src="https://avatars.githubusercontent.com/u/2682937?v=4?s=100" width="100px;" alt=""/><br /><sub><b>(◕ᴥ◕)</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ratson" title="Code">💻</a></td>
|
||||
<td align="center"><a href="maciejmyslinski.com"><img src="https://avatars.githubusercontent.com/u/11421186?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mat Milbury</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=maciejmyslinski" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://andreas.fyi"><img src="https://avatars.githubusercontent.com/u/8077469?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andreas Asprou</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=andreasasprou" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/kotx"><img src="https://avatars.githubusercontent.com/u/33439542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kot</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kotx" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=kotx" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=kotx" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/isaka1022"><img src="https://avatars.githubusercontent.com/u/28589716?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Amane</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=isaka1022" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="johnleung.com"><img src="https://avatars.githubusercontent.com/u/20699?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Leung</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fuzzthink" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="roettgers.co"><img src="https://avatars.githubusercontent.com/u/29666239?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bruce</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=bcye" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/emilygracekz"><img src="https://avatars.githubusercontent.com/u/57361805?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Emily</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=emilygracekz" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/npverni"><img src="https://avatars.githubusercontent.com/u/3537?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nathan Verni</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=npverni" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://davyengone.io"><img src="https://avatars.githubusercontent.com/u/4896002?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Davy Engone</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=davyengone" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://fedeorlandau.dev/"><img src="https://avatars.githubusercontent.com/u/10283686?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Federico Joel Orlandau</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Fedeorlandau" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Fedeorlandau" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/johnmurphy01"><img src="https://avatars.githubusercontent.com/u/2939548?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Murphy</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=johnmurphy01" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=johnmurphy01" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/martinsaxa"><img src="https://avatars.githubusercontent.com/u/33789474?v=4?s=100" width="100px;" alt=""/><br /><sub><b>martinsaxa</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=martinsaxa" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ajwgeek"><img src="https://avatars.githubusercontent.com/u/2135600?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Austin Walhof</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ajwgeek" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="g3offrey.dev"><img src="https://avatars.githubusercontent.com/u/11151445?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Geoffrey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=g3offrey" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=g3offrey" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=g3offrey" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/keevan"><img src="https://avatars.githubusercontent.com/u/9924643?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Pham</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=keevan" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/kimngan-bui"><img src="https://avatars.githubusercontent.com/u/20723478?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kimngan-bui</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kimngan-bui" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="world.hey.com/bach"><img src="https://avatars.githubusercontent.com/u/11691670?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bahk Chanhee</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=9j" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.afterecon.com/"><img src="https://avatars.githubusercontent.com/u/5559355?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Vandivier</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Vandivier" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Vandivier" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=Vandivier" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://namirsab.github.io"><img src="https://avatars.githubusercontent.com/u/6980777?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Namir</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=namirsab" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=namirsab" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=namirsab" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://twitter.com/scttcper"><img src="https://avatars.githubusercontent.com/u/1400464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Scott Cooper</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=scttcper" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="abduttayyeb.github.io"><img src="https://avatars.githubusercontent.com/u/55306260?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abduttayyeb M.r</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Abduttayyeb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/maybebored"><img src="https://avatars.githubusercontent.com/u/20951181?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mayuran</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=maybebored" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/MuckHub"><img src="https://avatars.githubusercontent.com/u/54979136?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aleksei Vesselko</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MuckHub" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://p-siriphanthong.github.io/"><img src="https://avatars.githubusercontent.com/u/29949429?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Punn Siriphanthong</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=p-siriphanthong" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://my-portfolio-292eb.web.app"><img src="https://avatars.githubusercontent.com/u/83679827?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shawn Fetanat</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=shawn-fetanat" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/mochi-sann"><img src="https://avatars.githubusercontent.com/u/44772513?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Moyuru</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mochi-sann" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=mochi-sann" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=mochi-sann" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/camsloanftc"><img src="https://avatars.githubusercontent.com/u/16295659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cam Sloan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=camsloanftc" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://macieksitkowski.com"><img src="https://avatars.githubusercontent.com/u/58401630?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Maciek Sitkowski</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sitek94" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vivek7405"><img src="https://avatars.githubusercontent.com/u/24492244?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vivek</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=vivek7405" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=vivek7405" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://cj.io"><img src="https://avatars.githubusercontent.com/u/1819?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CJ Lazell</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cj" title="Code">💻</a></td>
|
||||
<td align="center"><a href="robertbroersma.com"><img src="https://avatars.githubusercontent.com/u/4519828?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robert</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=RobertBroersma" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://christianjensen.netlify.com"><img src="https://avatars.githubusercontent.com/u/12374723?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christian Jensen</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cbejensen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/dvnrsn"><img src="https://avatars.githubusercontent.com/u/9112811?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Devin Rasmussen</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dvnrsn" title="Code">💻</a></td>
|
||||
<td align="center"><a href="www.linkedin.com/in/devtom"><img src="https://avatars.githubusercontent.com/u/23282613?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Thomas Brenneur</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=devtombiz" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=devtombiz" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=devtombiz" title="Tests">⚠️</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://lucasvazq.github.io/"><img src="https://avatars.githubusercontent.com/u/38964964?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lucas Vazquez</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lucasvazq" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/chrisj-back2work"><img src="https://avatars.githubusercontent.com/u/68551954?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chris Johnson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=chrisj-back2work" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/thisdotrob"><img src="https://avatars.githubusercontent.com/u/12902589?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rob Stevenson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=thisdotrob" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="www.lucas.computer"><img src="https://avatars.githubusercontent.com/u/1363056?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lucas Heymès</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lovethebomb" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=lovethebomb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/NorfeldtAbtion"><img src="https://avatars.githubusercontent.com/u/53769763?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lasse Norfeldt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=NorfeldtAbtion" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://nyaripeter.hu/"><img src="https://avatars.githubusercontent.com/u/6048614?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Péter Nyári</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=netwarex" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=netwarex" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.holgerfrohloff.de"><img src="https://avatars.githubusercontent.com/u/84148?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Holger Frohloff</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=5minpause" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/basilk76"><img src="https://avatars.githubusercontent.com/u/45275512?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Basil Khan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=basilk76" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://danestves.com/"><img src="https://avatars.githubusercontent.com/u/31737273?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Esteves</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=danestves" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.bitnative.com"><img src="https://avatars.githubusercontent.com/u/1688997?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cory House</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=coryhouse" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://auspham.dev/"><img src="https://avatars.githubusercontent.com/u/16440123?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Austin (Thang Pham)</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rockmanvnx6" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jammeryhq.com"><img src="https://avatars.githubusercontent.com/u/521777?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marcus Reinhardt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=noxify" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=noxify" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/davidchristie"><img src="https://avatars.githubusercontent.com/u/12044333?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Christie</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=davidchristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/ajanth97"><img src="https://avatars.githubusercontent.com/u/50458502?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ajanth</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ajanth97" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/divpreet"><img src="https://avatars.githubusercontent.com/u/2805650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Div</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=divpreet" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/david-arteaga"><img src="https://avatars.githubusercontent.com/u/7199015?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Arteaga</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=david-arteaga" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/MukulKolpe"><img src="https://avatars.githubusercontent.com/u/78664749?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mukul Kolpe</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MukulKolpe" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/skotchpine"><img src="https://avatars.githubusercontent.com/u/13043909?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tyler</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=skotchpine" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/SofianeDjellouli"><img src="https://avatars.githubusercontent.com/u/38258952?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sofiane Djellouli</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=SofianeDjellouli" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/kreako"><img src="https://avatars.githubusercontent.com/u/65113001?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kreako</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kreako" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://sarahdayan.dev"><img src="https://avatars.githubusercontent.com/u/5370675?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sarah Dayan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sarahdayan" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/c-ciobanu"><img src="https://avatars.githubusercontent.com/u/33382714?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cristi Ciobanu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=c-ciobanu" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://arpitdalal.dev"><img src="https://avatars.githubusercontent.com/u/61059807?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Arpit Dalal</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=arpitdalal" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/robertrisch"><img src="https://avatars.githubusercontent.com/u/73828816?v=4?s=100" width="100px;" alt=""/><br /><sub><b>robertrisch</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=robertrisch" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
9
SECURITY.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
TODO
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Email Brandon Bayer at b@bayer.ws to report a vulnerablity, and he will follow up with you asap.
|
||||
1
apps/web/.eslintrc.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require("@blitzjs/config/eslint")
|
||||
4
apps/web/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
.env
|
||||
*.sqlite
|
||||
30
apps/web/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
|
||||
|
||||
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
|
||||
|
||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
11
apps/web/jest.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const nextJest = require("@blitzjs/next/jest")
|
||||
|
||||
const createJestConfig = nextJest({
|
||||
dir: "./",
|
||||
})
|
||||
|
||||
const customJestConfig = {
|
||||
testEnvironment: "jest-environment-jsdom",
|
||||
}
|
||||
|
||||
module.exports = createJestConfig(customJestConfig)
|
||||
5
apps/web/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
7
apps/web/next.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
})
|
||||
|
||||
module.exports = withBundleAnalyzer({
|
||||
reactStrictMode: true,
|
||||
})
|
||||
35
apps/web/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start:dev": "pnpm run prisma:start && next dev",
|
||||
"buildapp": "prisma generate && next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
|
||||
"prisma:start": "prisma generate && prisma migrate deploy",
|
||||
"prisma:studio": "prisma studio",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "workspace:*",
|
||||
"@blitzjs/config": "workspace:*",
|
||||
"@blitzjs/next": "workspace:*",
|
||||
"@prisma/client": "3.9.0",
|
||||
"@types/jest": "27.4.1",
|
||||
"blitz": "workspace:*",
|
||||
"jest": "27.5.1",
|
||||
"next": "12.1.4",
|
||||
"prisma": "3.9.0",
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0",
|
||||
"ts-node": "10.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@types/react": "17.0.43",
|
||||
"eslint": "7.32.0",
|
||||
"typescript": "^4.5.3"
|
||||
}
|
||||
}
|
||||
35
apps/web/pages/_app.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import {ErrorFallbackProps, ErrorComponent, ErrorBoundary} from "@blitzjs/next"
|
||||
import {AuthenticationError, AuthorizationError} from "blitz"
|
||||
import type {AppProps} from "next/app"
|
||||
import React from "react"
|
||||
import {withBlitz} from "../src/client-setup"
|
||||
|
||||
function RootErrorFallback({error}: ErrorFallbackProps) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return <div>Error: You are not authenticated</div>
|
||||
} else if (error instanceof AuthorizationError) {
|
||||
return (
|
||||
<ErrorComponent
|
||||
statusCode={error.statusCode}
|
||||
title="Sorry, you are not authorized to access this"
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ErrorComponent
|
||||
statusCode={(error as any)?.statusCode || 400}
|
||||
title={error.message || error.name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function MyApp({Component, pageProps}: AppProps) {
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={RootErrorFallback}>
|
||||
<Component {...pageProps} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
export default withBlitz(MyApp)
|
||||
19
apps/web/pages/api/get-user.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {SessionContext} from "@blitzjs/auth"
|
||||
import {prisma} from "../../prisma/index"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
const publicData = blitzContext.session.$publicData
|
||||
|
||||
const sessions = await prisma.session.findMany({})
|
||||
const sessionsCount = await prisma.session.count({})
|
||||
|
||||
res.status(200).json({
|
||||
userId: blitzContext.session.userId,
|
||||
publicData: {...publicData},
|
||||
activeSessions: sessions,
|
||||
sessionsCount,
|
||||
})
|
||||
})
|
||||
11
apps/web/pages/api/is-authorized.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
const {session} = ctx
|
||||
|
||||
const authorized = session.$isAuthorized()
|
||||
|
||||
res.status(200).json({
|
||||
authorized,
|
||||
})
|
||||
})
|
||||
9
apps/web/pages/api/logout.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
await blitzContext.session.$revoke()
|
||||
|
||||
res.status(200).json({userId: blitzContext.session.userId})
|
||||
})
|
||||
21
apps/web/pages/api/multiply.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import {NextApiRequest, NextApiResponse} from "next"
|
||||
import {prisma} from "../../prisma/index"
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await prisma.session.findFirst({
|
||||
where: {
|
||||
handle: "test",
|
||||
},
|
||||
})
|
||||
|
||||
const isAuthorized = !!session
|
||||
|
||||
if (!isAuthorized) {
|
||||
res.status(404).json({message: "No access"})
|
||||
return
|
||||
}
|
||||
|
||||
const x = parseInt(req.query.x as string)
|
||||
const y = parseInt(req.query.y as string)
|
||||
res.json({result: x * y})
|
||||
}
|
||||
12
apps/web/pages/api/revoke-all-sessions.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {prisma} from "../../prisma/index"
|
||||
|
||||
export default api(async (_req, res) => {
|
||||
const sessions = await prisma.session.deleteMany()
|
||||
const sessionsCount = await prisma.session.count()
|
||||
|
||||
res.status(200).json({
|
||||
activeSessions: sessions,
|
||||
sessionsCount,
|
||||
})
|
||||
})
|
||||
18
apps/web/pages/api/set-public-data.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {setPublicDataForUser} from "@blitzjs/auth"
|
||||
|
||||
import {api} from "../../src/server-setup"
|
||||
import {prisma} from "../../prisma/index"
|
||||
|
||||
export default api(async (req, res, ctx) => {
|
||||
if (ctx.session.$thisIsAuthorized()) {
|
||||
ctx.session.$publicData
|
||||
|
||||
await prisma.user.update({
|
||||
where: {id: ctx.session.userId as number},
|
||||
data: {role: req.query.role as string},
|
||||
})
|
||||
await setPublicDataForUser(ctx.session.userId, {role: req.query.role as string})
|
||||
}
|
||||
|
||||
res.status(200).json({userId: ctx.session.userId, role: req.query.role as string})
|
||||
})
|
||||
31
apps/web/pages/api/signin.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {prisma} from "../../prisma/index"
|
||||
import {SecurePassword} from "@blitzjs/auth"
|
||||
|
||||
export const authenticateUser = async (email: string, password: string) => {
|
||||
const user = await prisma.user.findFirst({where: {email}})
|
||||
if (!user) throw new Error("Authentication Error")
|
||||
|
||||
const result = await SecurePassword.verify(user.hashedPassword, password)
|
||||
|
||||
if (result === SecurePassword.VALID_NEEDS_REHASH) {
|
||||
// Upgrade hashed password with a more secure hash
|
||||
const improvedHash = await SecurePassword.hash(password)
|
||||
await prisma.user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
|
||||
}
|
||||
|
||||
const {hashedPassword, ...rest} = user
|
||||
return rest
|
||||
}
|
||||
|
||||
export default api(async (req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
const user = await authenticateUser(req.query.email as string, req.query.password as string)
|
||||
|
||||
await blitzContext.session.$create({
|
||||
userId: user.id,
|
||||
})
|
||||
|
||||
res.status(200).json({email: req.query.email, userId: blitzContext.session.userId})
|
||||
})
|
||||
22
apps/web/pages/api/signup.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {prisma} from "../../prisma/index"
|
||||
import {SecurePassword} from "@blitzjs/auth"
|
||||
|
||||
export default api(async (req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
const hashedPassword = await SecurePassword.hash(
|
||||
(req.query.password as string) || "test-password",
|
||||
)
|
||||
const email = (req.query.email as string) || "test" + Math.random() + "@test.com"
|
||||
const user = await prisma.user.create({
|
||||
data: {email, hashedPassword, role: "user"},
|
||||
select: {id: true, name: true, email: true, role: true},
|
||||
})
|
||||
|
||||
await blitzContext.session.$create({
|
||||
userId: user.id,
|
||||
})
|
||||
|
||||
res.status(200).json({userId: blitzContext.session.userId, ...user, email: req.query.email})
|
||||
})
|
||||
17
apps/web/pages/authenticated-page.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
const AuthPage = () => {
|
||||
return (
|
||||
<div>
|
||||
{JSON.stringify(
|
||||
{
|
||||
message: "This is a page that requires authentication",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
AuthPage.authenticate = true
|
||||
|
||||
export default AuthPage
|
||||
11
apps/web/pages/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
export const getServerSideProps = () => {
|
||||
return {props: {}}
|
||||
}
|
||||
|
||||
export default function Web() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Web</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
18
apps/web/pages/page-with-auth-redirect.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
const PageWithRedirect = () => {
|
||||
return (
|
||||
<div>
|
||||
{JSON.stringify(
|
||||
{
|
||||
message:
|
||||
"This is a page that will redirect you to the Home page if you are authenticated",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
PageWithRedirect.redirectAuthenticatedTo = "/"
|
||||
|
||||
export default PageWithRedirect
|
||||
21
apps/web/pages/page-with-gsp.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import {gSP} from "../src/server-setup"
|
||||
|
||||
export const getStaticProps = gSP(async ({ctx}) => {
|
||||
return {
|
||||
props: {
|
||||
data: {
|
||||
// userId: ctx?.session.userId,
|
||||
// session: {
|
||||
// id: session.userId,
|
||||
// publicData: session.$publicData,
|
||||
// },
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
function Page({data}) {
|
||||
return <div>{JSON.stringify(data, null, 2)}</div>
|
||||
}
|
||||
|
||||
export default Page
|
||||
23
apps/web/pages/page-with-gssp.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import {SessionContext} from "@blitzjs/auth"
|
||||
import {gSSP} from "../src/server-setup"
|
||||
|
||||
type Props = {
|
||||
userId: unknown
|
||||
publicData: SessionContext["$publicData"]
|
||||
}
|
||||
|
||||
export const getServerSideProps = gSSP<Props>(async ({ctx}) => {
|
||||
const {session} = ctx
|
||||
return {
|
||||
props: {
|
||||
userId: session.userId,
|
||||
publicData: session.$publicData,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
function Page(props: Props) {
|
||||
return <div>{JSON.stringify(props, null, 2)}</div>
|
||||
}
|
||||
|
||||
export default Page
|
||||
18
apps/web/pages/page-with-redirect.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
const PageWithRedirect = () => {
|
||||
return (
|
||||
<div>
|
||||
{JSON.stringify(
|
||||
{
|
||||
message:
|
||||
"This page requires authentication. It will redirect you to the Home page if you are NOT authenticated",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
PageWithRedirect.authenticate = {redirectTo: "/"}
|
||||
|
||||
export default PageWithRedirect
|
||||
6
apps/web/pages/page-with-use-auth-session.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import {useAuthenticatedSession} from "../src/client-setup"
|
||||
|
||||
export default function PgaeWithUseAuthorizeIf() {
|
||||
useAuthenticatedSession()
|
||||
return <div>This page is using useAuthenticatedSession</div>
|
||||
}
|
||||
6
apps/web/pages/page-with-use-authorize-if.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import {useAuthorizeIf} from "../src/client-setup"
|
||||
|
||||
export default function PgaeWithUseAuthorizeIf() {
|
||||
useAuthorizeIf(Math.random() > 0.5)
|
||||
return <div>This page is using useAuthorizeIf</div>
|
||||
}
|
||||
6
apps/web/pages/page-with-use-authorize.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import {useAuthorize} from "../src/client-setup"
|
||||
|
||||
export default function PgaeWithUseAuthorize() {
|
||||
useAuthorize()
|
||||
return <div>This page is using useAuthorize</div>
|
||||
}
|
||||
6
apps/web/pages/page-with-use-redirect-auth.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import {useRedirectAuthenticated} from "../src/client-setup"
|
||||
|
||||
export default function PgaeWithUseAuthorizeIf() {
|
||||
useRedirectAuthenticated("/")
|
||||
return <div>This page is using useRedirectAuthenticated</div>
|
||||
}
|
||||
6
apps/web/pages/page-with-use-session.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import {useSession} from "../src/client-setup"
|
||||
|
||||
export default function PgaeWithUseSession() {
|
||||
const data = useSession()
|
||||
return <div>{JSON.stringify({data}, null, 2)}</div>
|
||||
}
|
||||
7
apps/web/pages/page-without-flicker.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
const PageWithRedirect = ({data}) => {
|
||||
return <div>{JSON.stringify(data)}</div>
|
||||
}
|
||||
|
||||
PageWithRedirect.suppressFirstRenderFlicker = true
|
||||
|
||||
export default PageWithRedirect
|
||||
8
apps/web/prisma/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {enhancePrisma} from "blitz"
|
||||
import {PrismaClient} from "@prisma/client"
|
||||
|
||||
const EnhancedPrisma = enhancePrisma(PrismaClient)
|
||||
|
||||
export * from "@prisma/client"
|
||||
const prisma = new EnhancedPrisma()
|
||||
export {prisma}
|
||||
47
apps/web/prisma/migrations/20220209111643_init/migration.sql
Normal file
@@ -0,0 +1,47 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT NOT NULL,
|
||||
"hashedPassword" TEXT,
|
||||
"role" TEXT NOT NULL DEFAULT 'user'
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"expiresAt" DATETIME,
|
||||
"handle" TEXT NOT NULL,
|
||||
"userId" INTEGER,
|
||||
"hashedSessionToken" TEXT,
|
||||
"antiCSRFToken" TEXT,
|
||||
"publicData" TEXT,
|
||||
"privateData" TEXT,
|
||||
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Token" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"hashedToken" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
"sentTo" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
CONSTRAINT "Token_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session_handle_key" ON "Session"("handle");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Token_hashedToken_type_key" ON "Token"("hashedToken", "type");
|
||||
3
apps/web/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "sqlite"
|
||||
50
apps/web/prisma/schema.prisma
Normal file
@@ -0,0 +1,50 @@
|
||||
datasource sqlite {
|
||||
provider = "sqlite"
|
||||
url = "file:./db.sqlite"
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
name String?
|
||||
email String @unique
|
||||
hashedPassword String?
|
||||
role String @default("user")
|
||||
|
||||
sessions Session[]
|
||||
tokens Token[]
|
||||
}
|
||||
|
||||
model Session {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
expiresAt DateTime?
|
||||
handle String @unique
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
hashedSessionToken String?
|
||||
antiCSRFToken String?
|
||||
publicData String?
|
||||
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])
|
||||
}
|
||||
26
apps/web/src/client-setup.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {AuthClientPlugin} from "@blitzjs/auth"
|
||||
import {setupClient} from "@blitzjs/next"
|
||||
|
||||
const {
|
||||
withBlitz,
|
||||
useSession,
|
||||
useAuthorize,
|
||||
useAuthorizeIf,
|
||||
useRedirectAuthenticated,
|
||||
useAuthenticatedSession,
|
||||
} = setupClient({
|
||||
plugins: [
|
||||
AuthClientPlugin({
|
||||
cookiePrefix: "webapp-cookie-prefix",
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
export {
|
||||
withBlitz,
|
||||
useSession,
|
||||
useAuthorize,
|
||||
useAuthorizeIf,
|
||||
useRedirectAuthenticated,
|
||||
useAuthenticatedSession,
|
||||
}
|
||||
17
apps/web/src/server-setup.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import {setupBlitz} from "@blitzjs/next"
|
||||
import {AuthServerPlugin, PrismaStorage} from "@blitzjs/auth"
|
||||
import {prisma as db} from "../prisma/index"
|
||||
import {simpleRolesIsAuthorized} from "@blitzjs/auth"
|
||||
|
||||
const {gSSP, gSP, api} = setupBlitz({
|
||||
plugins: [
|
||||
AuthServerPlugin({
|
||||
cookiePrefix: "webapp-cookie-prefix",
|
||||
// TODO fix type
|
||||
storage: PrismaStorage(db as any),
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
export {gSSP, gSP, api}
|
||||
7
apps/web/test/hello.unit.test.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
function sum(a, b) {
|
||||
return a + b
|
||||
}
|
||||
|
||||
test("adds 1 + 2 to equal 3", () => {
|
||||
expect(sum(1, 2)).toBe(3)
|
||||
})
|
||||
0
apps/web/test/setup.ts
Normal file
5
apps/web/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "@blitzjs/config/tsconfig.nextjs.json",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "jest.config.js"],
|
||||
"exclude": ["node_modules", "test"]
|
||||
}
|
||||
BIN
assets/Fauna_Logo_Blue.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
assets/andreas.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
1
assets/boostry.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 143.5 42.09"><defs><style>.cls-1{fill:none;}.cls-2{fill:#d71526;}</style></defs><title>アセット 15</title><g id="レイヤー_2" data-name="レイヤー 2"><g id="レイヤー_1-2" data-name="レイヤー 1"><path d="M21.87,20A13.2,13.2,0,0,1,34.92,6.83c6.44,0,10.88,4.74,10.88,11a13.21,13.21,0,0,1-13,13.21C26.31,31.05,21.87,26.32,21.87,20Zm20.22-2.1c0-4.34-2.9-7.71-7.27-7.71-5.17,0-9.25,4.51-9.25,9.71,0,4.34,2.91,7.71,7.28,7.71A9.52,9.52,0,0,0,42.09,17.94Z"/><path d="M47.67,20a13.2,13.2,0,0,1,13-13.21c6.44,0,10.88,4.74,10.88,11a13.21,13.21,0,0,1-13,13.21C52.11,31.05,47.67,26.32,47.67,20Zm20.22-2.1c0-4.34-2.9-7.71-7.27-7.71-5.17,0-9.25,4.51-9.25,9.71,0,4.34,2.91,7.71,7.28,7.71C63.85,27.65,67.89,23.14,67.89,17.94Z"/><path d="M72.84,24.81a9.7,9.7,0,0,0,6.41,3c2.4,0,4.07-1.4,4.07-3.57,0-1.57-.94-2.64-3.3-3.87l-1.37-.71a6.56,6.56,0,0,1-4.07-6.14c0-3.9,3.2-6.7,7.47-6.7A12.23,12.23,0,0,1,88.57,8.9l-.68,3.51-.23-.18A10.16,10.16,0,0,0,82,10c-2.2,0-3.84,1.4-3.84,3.3,0,1.17.57,2.37,2.51,3.34l1.33.67C85.46,19.07,87,21,87,24c0,4.3-3.11,7.1-7.61,7.1a11.56,11.56,0,0,1-7.45-2.8l.71-3.65Z"/><path d="M106.27,10.63h-5.85l-3.91,20H92.94l3.91-20H91l.66-3.37h15.32Z"/><path d="M115,21.54l5.27,9.08h-4.14l-5.3-9.74-1.91,9.74h-3.57L109.9,7.26h4.77c4.67,0,7.68,2.54,7.68,6.47S119.44,20.74,115,21.54Zm-3.84-2.37h1.33c3.51,0,6.18-2.17,6.18-5.07,0-2.3-1.7-3.7-4.44-3.7h-1.37Z"/><line class="cls-1" x1="72.32" y1="24.47" x2="72.69" y2="24.81"/><path d="M15.4,16.76l-.76-.31.78-.27C19,15,21.06,12.35,21.06,9.23c0-3.92-3.19-6.65-7.75-6.65H6L.5,30.62H9c5.94,0,10.42-3.48,10.42-8.08A6,6,0,0,0,15.4,16.76ZM8.93,5.68H12.3c2.94,0,4.77,1.55,4.77,4,0,3-2.58,5.18-6,5.18H7.13ZM9.1,27.52H4.75L6.59,18h3.82c3.05,0,5,1.7,5,4.32C15.43,25.34,12.76,27.52,9.1,27.52Z"/><polygon class="cls-2" points="134.09 25.12 132.58 21.09 132.54 20.97 132.49 20.86 127.39 7.26 123.39 7.26 130.13 25.3 121.55 42.09 125.56 42.09 131.87 29.85 131.93 29.73 131.98 29.61 134.06 25.58 134.12 25.47 134.18 25.35 134.14 25.24 134.09 25.12"/><polygon points="139.5 7.26 132.65 20.76 132.65 20.76 132.54 20.98 132.58 21.09 134.09 25.12 134.14 25.24 134.18 25.35 143.5 7.26 139.5 7.26"/><rect class="cls-1" width="143" height="42"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
24
assets/digsas.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M79.4831 1.95572C70.2813 -0.81923 55.2811 -0.617415 46.1549 2.4098L18.6125 11.5167C9.48627 14.5313 9.38542 19.7784 18.3856 23.1588L49.9743 35.028C58.9744 38.4084 73.6217 38.194 82.5084 34.5487L110.883 22.9192C119.782 19.2739 119.53 14.0267 110.316 11.2518L79.4831 1.95572Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M84.235 38.6101C75.3357 42.2554 68.0625 53.1029 68.0625 62.727V113.635C68.0625 123.259 74.9071 127.245 83.2644 122.489L109.282 107.706C117.639 102.951 124.912 91.208 125.442 81.6092L127.837 37.8281C128.366 28.2167 121.509 23.3479 112.609 26.9932L84.235 38.6101Z" fill="url(#paint1_linear)"/>
|
||||
<path d="M0.0071345 37.8409C-0.257575 28.2295 6.877 23.1211 15.8771 26.5015L47.4658 38.3707C56.466 41.7511 63.8274 52.3842 63.8274 62.0082V112.916C63.8274 122.54 56.882 126.715 48.386 122.212L17.0998 105.6C8.60392 101.085 1.44415 89.5306 1.16683 79.9192L0.0071345 37.8409Z" fill="url(#paint2_linear)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="63.9326" y1="0" x2="63.9326" y2="124.497" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#036EB8"/>
|
||||
<stop offset="1" stop-color="#469196"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="63.9326" y1="0" x2="63.9326" y2="124.497" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#036EB8"/>
|
||||
<stop offset="1" stop-color="#469196"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="63.9326" y1="0" x2="63.9326" y2="124.497" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#036EB8"/>
|
||||
<stop offset="1" stop-color="#469196"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0">
|
||||
<rect width="128" height="128" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/meetkai.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
assets/render-logo-color2.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/rit_logo.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
assets/usertrack.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
2
integration-tests/auth/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
SESSION_SECRET_KEY=hsdenhJfpLHrGjgdgg3jdF8g2bYD2PaQ
|
||||
HEADLESS=true
|
||||
1
integration-tests/auth/.eslintrc.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require("@blitzjs/config/eslint")
|
||||
3
integration-tests/auth/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
*.sqlite
|
||||
5
integration-tests/auth/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
10
integration-tests/auth/next.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
})
|
||||
|
||||
module.exports = withBundleAnalyzer({
|
||||
reactStrictMode: true,
|
||||
experimental: {
|
||||
esmExternals: "loose",
|
||||
},
|
||||
})
|
||||
49
integration-tests/auth/package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "test-auth",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start:dev": "pnpm run prisma:start && next dev",
|
||||
"test": "pnpm run prisma:start && vitest run",
|
||||
"test-watch": "vitest",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
|
||||
"prisma:start": "prisma generate && prisma migrate deploy",
|
||||
"prisma:studio": "prisma studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "workspace:*",
|
||||
"@blitzjs/config": "workspace:*",
|
||||
"@blitzjs/next": "workspace:*",
|
||||
"@prisma/client": "3.9.0",
|
||||
"blitz": "workspace:*",
|
||||
"lowdb": "3.0.0",
|
||||
"next": "12.1.4",
|
||||
"prisma": "3.9.0",
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/get-port": "4.2.0",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/react": "17.0.43",
|
||||
"@types/rimraf": "3.0.2",
|
||||
"@types/selenium-webdriver": "4.0.18",
|
||||
"b64-lite": "1.4.0",
|
||||
"chromedriver": "100.0.0",
|
||||
"cross-spawn": "7.0.3",
|
||||
"eslint": "7.32.0",
|
||||
"express": "4.17.3",
|
||||
"fs-extra": "10.0.1",
|
||||
"get-port": "6.1.2",
|
||||
"node-fetch": "3.2.3",
|
||||
"rimraf": "3.0.2",
|
||||
"selenium-webdriver": "4.1.1",
|
||||
"tree-kill": "1.2.2",
|
||||
"typescript": "^4.5.3"
|
||||
}
|
||||
}
|
||||
35
integration-tests/auth/pages/_app.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import {ErrorFallbackProps, ErrorComponent, ErrorBoundary} from "@blitzjs/next"
|
||||
import {AuthenticationError, AuthorizationError} from "blitz"
|
||||
import type {AppProps} from "next/app"
|
||||
import React from "react"
|
||||
import {withBlitz} from "../src/client-setup"
|
||||
|
||||
function RootErrorFallback({error}: ErrorFallbackProps) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return <div>Error: You are not authenticated</div>
|
||||
} else if (error instanceof AuthorizationError) {
|
||||
return (
|
||||
<ErrorComponent
|
||||
statusCode={error.statusCode}
|
||||
title="Sorry, you are not authorized to access this"
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ErrorComponent
|
||||
statusCode={(error as any)?.statusCode || 400}
|
||||
title={error.message || error.name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function MyApp({Component, pageProps}: AppProps) {
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={RootErrorFallback}>
|
||||
<Component {...pageProps} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
export default withBlitz(MyApp)
|
||||
9
integration-tests/auth/pages/api/logout.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
await blitzContext.session.$revoke()
|
||||
|
||||
res.status(200).json({userId: blitzContext.session.userId})
|
||||
})
|
||||
5
integration-tests/auth/pages/api/noauth.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
res.status(200).end()
|
||||
})
|
||||
32
integration-tests/auth/pages/api/signin.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {prisma} from "../../prisma/index"
|
||||
import {SecurePassword} from "@blitzjs/auth"
|
||||
|
||||
export const authenticateUser = async (email: string, password: string) => {
|
||||
const user = await prisma.user.findFirst({where: {email}})
|
||||
|
||||
if (!user) throw new Error("Authentication Error")
|
||||
|
||||
const result = await SecurePassword.verify(user.hashedPassword, password)
|
||||
|
||||
if (result === SecurePassword.VALID_NEEDS_REHASH) {
|
||||
// Upgrade hashed password with a more secure hash
|
||||
const improvedHash = await SecurePassword.hash(password)
|
||||
await prisma.user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
|
||||
}
|
||||
|
||||
const {hashedPassword, ...rest} = user
|
||||
return rest
|
||||
}
|
||||
|
||||
export default api(async (req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
const user = await authenticateUser(req.query.email as string, req.query.password as string)
|
||||
|
||||
await blitzContext.session.$create({
|
||||
userId: user.id,
|
||||
})
|
||||
|
||||
res.status(200).json({email: req.query.email, userId: blitzContext.session.userId})
|
||||
})
|
||||
14
integration-tests/auth/pages/authenticated-page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import {useAuthorize} from "../src/client-setup"
|
||||
|
||||
export const getServerSideProps = () => {
|
||||
return {props: {}}
|
||||
}
|
||||
|
||||
export default function AuthenticatedQuery() {
|
||||
useAuthorize()
|
||||
return (
|
||||
<div>
|
||||
<h1>AuthenticatedQuery</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
11
integration-tests/auth/pages/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
export const getServerSideProps = () => {
|
||||
return {props: {}}
|
||||
}
|
||||
|
||||
export default function Web() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Web</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
8
integration-tests/auth/prisma/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {enhancePrisma} from "blitz"
|
||||
import {PrismaClient} from "@prisma/client"
|
||||
|
||||
const EnhancedPrisma = enhancePrisma(PrismaClient)
|
||||
|
||||
export * from "@prisma/client"
|
||||
const prisma = new EnhancedPrisma()
|
||||
export {prisma}
|
||||
@@ -0,0 +1,47 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT NOT NULL,
|
||||
"hashedPassword" TEXT,
|
||||
"role" TEXT NOT NULL DEFAULT 'user'
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"expiresAt" DATETIME,
|
||||
"handle" TEXT NOT NULL,
|
||||
"userId" INTEGER,
|
||||
"hashedSessionToken" TEXT,
|
||||
"antiCSRFToken" TEXT,
|
||||
"publicData" TEXT,
|
||||
"privateData" TEXT,
|
||||
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Token" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"hashedToken" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
"sentTo" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
CONSTRAINT "Token_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session_handle_key" ON "Session"("handle");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Token_hashedToken_type_key" ON "Token"("hashedToken", "type");
|
||||
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "sqlite"
|
||||
50
integration-tests/auth/prisma/schema.prisma
Normal file
@@ -0,0 +1,50 @@
|
||||
datasource sqlite {
|
||||
provider = "sqlite"
|
||||
url = "file:./db.sqlite"
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
name String?
|
||||
email String @unique
|
||||
hashedPassword String?
|
||||
role String @default("user")
|
||||
|
||||
sessions Session[]
|
||||
tokens Token[]
|
||||
}
|
||||
|
||||
model Session {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
expiresAt DateTime?
|
||||
handle String @unique
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
hashedSessionToken String?
|
||||
antiCSRFToken String?
|
||||
publicData String?
|
||||
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])
|
||||
}
|
||||
18
integration-tests/auth/prisma/seed.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {prisma} from "./index"
|
||||
import {SecurePassword} from "@blitzjs/auth"
|
||||
|
||||
const seed = async () => {
|
||||
await prisma.$reset()
|
||||
|
||||
const hashedPassword = await SecurePassword.hash("abcd1234")
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
email: "test@test.com",
|
||||
hashedPassword,
|
||||
role: "user",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export default seed
|
||||
26
integration-tests/auth/src/client-setup.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {AuthClientPlugin} from "@blitzjs/auth"
|
||||
import {setupClient} from "@blitzjs/next"
|
||||
|
||||
const {
|
||||
withBlitz,
|
||||
useSession,
|
||||
useAuthorize,
|
||||
useAuthorizeIf,
|
||||
useRedirectAuthenticated,
|
||||
useAuthenticatedSession,
|
||||
} = setupClient({
|
||||
plugins: [
|
||||
AuthClientPlugin({
|
||||
cookiePrefix: "webapp-cookie-prefix",
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
export {
|
||||
withBlitz,
|
||||
useSession,
|
||||
useAuthorize,
|
||||
useAuthorizeIf,
|
||||
useRedirectAuthenticated,
|
||||
useAuthenticatedSession,
|
||||
}
|
||||
16
integration-tests/auth/src/server-setup.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {setupBlitz} from "@blitzjs/next"
|
||||
import {AuthServerPlugin, PrismaStorage} from "@blitzjs/auth"
|
||||
import {simpleRolesIsAuthorized} from "@blitzjs/auth"
|
||||
import {prisma as db} from "../prisma/index"
|
||||
|
||||
const {gSSP, gSP, api} = setupBlitz({
|
||||
plugins: [
|
||||
AuthServerPlugin({
|
||||
cookiePrefix: "webapp-cookie-prefix",
|
||||
storage: PrismaStorage(db as any),
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
export {gSSP, gSP, api}
|
||||
147
integration-tests/auth/test/index.test.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import {describe, it, expect, beforeAll, afterAll} from "vitest"
|
||||
import {killApp, findPort, launchApp, nextBuild, nextStart} from "./next-test-utils"
|
||||
import webdriver from "./next-webdriver"
|
||||
import {join} from "path"
|
||||
import seed from "../prisma/seed"
|
||||
import fetch from "node-fetch"
|
||||
import {fromBase64} from "b64-lite"
|
||||
|
||||
let app: any
|
||||
let appPort: number
|
||||
const appDir = join(__dirname, "../")
|
||||
const HEADER_CSRF = "anti-csrf"
|
||||
const COOKIE_PUBLIC_DATA_TOKEN = "webapp-cookie-prefix_sPublicDataToken"
|
||||
const COOKIE_SESSION_TOKEN = "webapp-cookie-prefix_sSessionToken"
|
||||
const COOKIE_ANONYMOUS_SESSION_TOKEN = "webapp-cookie-prefix_sAnonymousSessionToken"
|
||||
const COOKIE_REFRESH_TOKEN = "webapp-cookie-prefix_sIdRefreshToken"
|
||||
const HEADER_PUBLIC_DATA_TOKEN = "public-data-token"
|
||||
|
||||
function readCookie(cookieHeader, name) {
|
||||
const setPos = cookieHeader.search(new RegExp("\\b" + name + "="))
|
||||
const stopPos = cookieHeader.indexOf(";", setPos)
|
||||
let res
|
||||
if (!~setPos) return undefined
|
||||
res = decodeURIComponent(
|
||||
cookieHeader.substring(setPos, ~stopPos ? stopPos : undefined).split("=")[1],
|
||||
)
|
||||
return res.charAt(0) === "{" ? JSON.parse(res) : res
|
||||
}
|
||||
|
||||
const runTests = (mode?: string) => {
|
||||
describe("Auth", () => {
|
||||
describe("unauthenticated", () => {
|
||||
it(
|
||||
"should render error for protected query",
|
||||
async () => {
|
||||
const browser = await webdriver(appPort, "/authenticated-page")
|
||||
let errorMsg = await browser.elementByXpath(`//*[@id="__next"]/div`)
|
||||
expect(errorMsg).toMatch(/Error: You are not authenticated/)
|
||||
if (browser) browser.close()
|
||||
},
|
||||
5000 * 60 * 2,
|
||||
)
|
||||
|
||||
it("should render result for open query", async () => {
|
||||
const res = await fetch(`http://localhost:${appPort}/api/noauth`, {
|
||||
method: "GET",
|
||||
headers: {"Content-Type": "application/json; charset=utf-8"},
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
})
|
||||
|
||||
it("sets correct cookie", async () => {
|
||||
const res = await fetch(`http://localhost:${appPort}/api/noauth`, {
|
||||
method: "GET",
|
||||
headers: {"Content-Type": "application/json; charset=utf-8"},
|
||||
})
|
||||
const cookieHeader = res.headers.get("Set-Cookie")
|
||||
const cookie = (name) => readCookie(cookieHeader, name)
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers.get(HEADER_CSRF)).not.toBe(undefined)
|
||||
expect(cookie(COOKIE_ANONYMOUS_SESSION_TOKEN)).not.toBeUndefined()
|
||||
expect(cookie(COOKIE_SESSION_TOKEN)).toBe("")
|
||||
expect(cookie(COOKIE_REFRESH_TOKEN)).toBeUndefined()
|
||||
|
||||
expect(res.headers.get(HEADER_PUBLIC_DATA_TOKEN)).toBe("updated")
|
||||
expect(cookie(COOKIE_PUBLIC_DATA_TOKEN)).not.toBe(undefined)
|
||||
|
||||
const publicDataStr = fromBase64(cookie(COOKIE_PUBLIC_DATA_TOKEN))
|
||||
const publicData = JSON.parse(publicDataStr)
|
||||
expect(publicData.userId).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe("authenticated", () => {
|
||||
it("should login successfully", async () => {
|
||||
const res = await fetch(
|
||||
`http://localhost:${appPort}/api/signin?email=test@test.com&password=abcd1234`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json; charset=utf-8"},
|
||||
},
|
||||
)
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers.get(HEADER_CSRF)).not.toBe(undefined)
|
||||
|
||||
const cookieHeader = res.headers.get("Set-Cookie")
|
||||
const cookie = (name) => readCookie(cookieHeader, name)
|
||||
expect(cookieHeader).not.toBe(undefined)
|
||||
|
||||
const publicDataStr = fromBase64(cookie(COOKIE_PUBLIC_DATA_TOKEN))
|
||||
const publicData = JSON.parse(publicDataStr)
|
||||
expect(publicData.userId).toBe(1)
|
||||
|
||||
expect(readCookie(cookieHeader, COOKIE_SESSION_TOKEN)).not.toBe(undefined)
|
||||
})
|
||||
|
||||
it("does not require CSRF header on HEAD requests", async () => {
|
||||
const res = await fetch(`http://localhost:${appPort}/api/noauth`, {
|
||||
method: "GET",
|
||||
headers: {"Content-Type": "application/json; charset=utf-8"},
|
||||
})
|
||||
const cookieHeader = res.headers.get("Set-Cookie")
|
||||
|
||||
const headRes = await fetch(`http://localhost:${appPort}/api/noauth`, {
|
||||
method: "HEAD",
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
cookie: cookieHeader as string,
|
||||
},
|
||||
})
|
||||
|
||||
expect(headRes.status).toBe(200)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe("dev mode", () => {
|
||||
beforeAll(async () => {
|
||||
try {
|
||||
appPort = await findPort()
|
||||
app = await launchApp(appDir, appPort, {})
|
||||
await seed()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}, 5000 * 60 * 2)
|
||||
afterAll(() => killApp(app))
|
||||
runTests()
|
||||
})
|
||||
|
||||
describe("server mode", () => {
|
||||
beforeAll(async () => {
|
||||
try {
|
||||
appPort = await findPort()
|
||||
await nextBuild(appDir)
|
||||
app = await nextStart(appDir, appPort)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}, 5000 * 60 * 2)
|
||||
afterAll(() => killApp(app))
|
||||
|
||||
runTests()
|
||||
})
|
||||
621
integration-tests/auth/test/next-test-utils.ts
Normal file
@@ -0,0 +1,621 @@
|
||||
import spawn from "cross-spawn"
|
||||
import {ChildProcess} from "child_process"
|
||||
import express from "express"
|
||||
import {existsSync, readFileSync, unlinkSync, writeFileSync, createReadStream} from "fs"
|
||||
import {writeFile} from "fs-extra"
|
||||
import getPort from "get-port"
|
||||
import http from "http"
|
||||
// `next` here is the symlink in `test/node_modules/next` which points to the root directory.
|
||||
// This is done so that requiring from `next` works.
|
||||
// The reason we don't import the relative path `../../dist/<etc>` is that it would lead to inconsistent module singletons
|
||||
import server from "next/dist/server/next"
|
||||
import fetch from "node-fetch"
|
||||
import path from "path"
|
||||
import qs from "querystring"
|
||||
import treeKill from "tree-kill"
|
||||
|
||||
export const nextServer = server
|
||||
|
||||
// polyfill Object.fromEntries for the test/integration/relay-analytics tests
|
||||
// on node 10, this can be removed after we no longer support node 10
|
||||
if (!Object.fromEntries) {
|
||||
Object.fromEntries = require("core-js/features/object/from-entries")
|
||||
}
|
||||
|
||||
export function initNextServerScript(scriptPath, successRegexp, env, failRegexp, opts) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const instance = spawn("node", ["--no-deprecation", scriptPath], {env})
|
||||
|
||||
function handleStdout(data) {
|
||||
const message = data.toString()
|
||||
if (successRegexp.test(message)) {
|
||||
resolve(instance)
|
||||
}
|
||||
process.stdout.write(message)
|
||||
|
||||
if (opts && opts.onStdout) {
|
||||
opts.onStdout(message.toString())
|
||||
}
|
||||
}
|
||||
|
||||
function handleStderr(data) {
|
||||
const message = data.toString()
|
||||
if (failRegexp && failRegexp.test(message)) {
|
||||
instance.kill()
|
||||
return reject(new Error("received failRegexp"))
|
||||
}
|
||||
process.stderr.write(message)
|
||||
|
||||
if (opts && opts.onStderr) {
|
||||
opts.onStderr(message.toString())
|
||||
}
|
||||
}
|
||||
|
||||
instance.stdout?.on("data", handleStdout)
|
||||
instance.stderr?.on("data", handleStderr)
|
||||
|
||||
instance.on("close", () => {
|
||||
instance.stdout?.removeListener("data", handleStdout)
|
||||
instance.stderr?.removeListener("data", handleStderr)
|
||||
})
|
||||
|
||||
instance.on("error", (err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function renderViaAPI(app, pathname, query) {
|
||||
const url = `${pathname}${query ? `?${qs.stringify(query)}` : ""}`
|
||||
return app.renderToHTML({url}, {}, pathname, query)
|
||||
}
|
||||
|
||||
export function renderViaHTTP(appPort, pathname, query, opts) {
|
||||
return fetchViaHTTP(appPort, pathname, query, opts).then((res) => res.text())
|
||||
}
|
||||
|
||||
export function fetchViaHTTP(appPort, pathname, query, opts) {
|
||||
const url = `http://localhost:${appPort}${pathname}${
|
||||
typeof query === "string" ? query : query ? `?${qs.stringify(query)}` : ""
|
||||
}`
|
||||
return fetch(url, opts)
|
||||
}
|
||||
|
||||
export function findPort() {
|
||||
return getPort()
|
||||
}
|
||||
|
||||
interface RunNextCommandOptions {
|
||||
cwd?: string
|
||||
env?: Record<any, any>
|
||||
spawnOptions?: any
|
||||
instance?: any
|
||||
stderr?: boolean
|
||||
stdout?: boolean
|
||||
ignoreFail?: boolean
|
||||
}
|
||||
|
||||
export function runNextCommand(argv: any[], options: RunNextCommandOptions = {}) {
|
||||
const nextDir = path.dirname(require.resolve("next/package"))
|
||||
const nextBin = path.join(nextDir, "dist/bin/next")
|
||||
const cwd = options.cwd || nextDir
|
||||
// Let Next.js decide the environment
|
||||
const env = {
|
||||
...process.env,
|
||||
...options.env,
|
||||
NODE_ENV: "",
|
||||
__NEXT_TEST_MODE: "true",
|
||||
}
|
||||
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
console.log(`Running command "next ${argv.join(" ")}"`)
|
||||
const instance = spawn("node", ["--no-deprecation", nextBin, ...argv], {
|
||||
...options.spawnOptions,
|
||||
cwd,
|
||||
env,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
})
|
||||
|
||||
if (typeof options.instance === "function") {
|
||||
options.instance(instance)
|
||||
}
|
||||
|
||||
let stderrOutput = ""
|
||||
instance.stderr?.on("data", function (chunk) {
|
||||
stderrOutput += chunk
|
||||
})
|
||||
|
||||
let stdoutOutput = ""
|
||||
if (options.stdout) {
|
||||
instance.stdout?.on("data", function (chunk) {
|
||||
stdoutOutput += chunk
|
||||
})
|
||||
}
|
||||
|
||||
instance.on("close", (code, signal) => {
|
||||
if (!options.stderr && !options.stdout && !options.ignoreFail && code !== 0) {
|
||||
console.log(stderrOutput)
|
||||
return reject(new Error(`command failed with code ${code}`))
|
||||
}
|
||||
|
||||
resolve({
|
||||
code,
|
||||
signal,
|
||||
stdout: stdoutOutput,
|
||||
stderr: stderrOutput,
|
||||
})
|
||||
})
|
||||
|
||||
instance.on("error", (err: any) => {
|
||||
console.log(stderrOutput)
|
||||
err.stdout = stdoutOutput
|
||||
err.stderr = stderrOutput
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
interface RunNextCommandDevOptions {
|
||||
cwd?: string
|
||||
env?: Record<any, any>
|
||||
onStdout?: (stdout: string) => void
|
||||
onStderr?: (stderr: string) => void
|
||||
stderr?: boolean
|
||||
stdout?: boolean
|
||||
nodeArgs?: []
|
||||
bootupMarker?: any
|
||||
nextStart?: boolean
|
||||
}
|
||||
|
||||
export function runNextCommandDev(argv, stdOut, opts: RunNextCommandDevOptions = {}) {
|
||||
const nextDir = path.dirname(require.resolve("next/package"))
|
||||
const nextBin = path.join(nextDir, "dist/bin/next")
|
||||
|
||||
const cwd = opts.cwd || nextDir
|
||||
const env = {
|
||||
...process.env,
|
||||
NODE_ENV: opts.nextStart ? ("production" as const) : ("development" as const),
|
||||
__NEXT_TEST_MODE: "true",
|
||||
...opts.env,
|
||||
}
|
||||
|
||||
const nodeArgs = opts.nodeArgs || []
|
||||
return new Promise<void | string | ChildProcess>((resolve, reject) => {
|
||||
const instance = spawn("node", [...nodeArgs, "--no-deprecation", nextBin, ...argv], {
|
||||
cwd,
|
||||
env,
|
||||
})
|
||||
|
||||
let didResolve = false
|
||||
|
||||
function handleStdout(data) {
|
||||
const message = data.toString()
|
||||
|
||||
if (!didResolve) {
|
||||
didResolve = true
|
||||
resolve(instance)
|
||||
}
|
||||
|
||||
if (typeof opts.onStdout === "function") {
|
||||
opts.onStdout(message)
|
||||
}
|
||||
|
||||
if (opts.stdout !== false) {
|
||||
process.stdout.write(message)
|
||||
}
|
||||
}
|
||||
|
||||
function handleStderr(data) {
|
||||
const message = data.toString()
|
||||
if (typeof opts.onStderr === "function") {
|
||||
opts.onStderr(message)
|
||||
}
|
||||
|
||||
if (opts.stderr !== false) {
|
||||
process.stderr.write(message)
|
||||
}
|
||||
}
|
||||
|
||||
instance.stdout?.on("data", handleStdout)
|
||||
instance.stderr?.on("data", handleStderr)
|
||||
|
||||
instance.on("close", () => {
|
||||
instance.stdout?.removeListener("data", handleStdout)
|
||||
instance.stderr?.removeListener("data", handleStderr)
|
||||
if (!didResolve) {
|
||||
didResolve = true
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
||||
instance.on("error", (err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Launch the app in dev mode.
|
||||
export function launchApp(dir, port, opts) {
|
||||
return runNextCommandDev([dir, "-p", port], undefined, opts)
|
||||
}
|
||||
|
||||
export function nextBuild(dir, args = [], opts = {}) {
|
||||
return runNextCommand(["build", ...args], {cwd: dir, ...opts})
|
||||
}
|
||||
|
||||
export function nextExport(dir, {outdir}, opts = {}) {
|
||||
return runNextCommand(["export", dir, "--outdir", outdir], opts)
|
||||
}
|
||||
|
||||
export function nextExportDefault(dir, opts = {}) {
|
||||
return runNextCommand(["export", dir], opts)
|
||||
}
|
||||
|
||||
export function nextLint(dir, args = [], opts = {}) {
|
||||
return runNextCommand(["lint", dir, ...args], opts)
|
||||
}
|
||||
|
||||
export function nextStart(dir, port, opts = {}) {
|
||||
return runNextCommandDev(["start", "-p", port, dir], undefined, {
|
||||
...opts,
|
||||
nextStart: true,
|
||||
})
|
||||
}
|
||||
|
||||
export function buildTS(args = [], cwd, env = {NODE_ENV: "production" as const}) {
|
||||
cwd = cwd || path.dirname(require.resolve("next/package"))
|
||||
env = {...process.env, ...env}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const instance = spawn(
|
||||
"node",
|
||||
["--no-deprecation", require.resolve("typescript/lib/tsc"), ...args],
|
||||
{cwd, env},
|
||||
)
|
||||
let output = ""
|
||||
|
||||
const handleData = (chunk) => {
|
||||
output += chunk.toString()
|
||||
}
|
||||
|
||||
instance.stdout?.on("data", handleData)
|
||||
instance.stderr?.on("data", handleData)
|
||||
|
||||
instance.on("exit", (code) => {
|
||||
if (code) {
|
||||
return reject(new Error("exited with code: " + code + "\n" + output))
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Kill a launched app
|
||||
export async function killApp(instance) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
treeKill(instance.pid, (err) => {
|
||||
if (err) {
|
||||
if (
|
||||
process.platform === "win32" &&
|
||||
typeof err.message === "string" &&
|
||||
(err.message.includes(`no running instance of the task`) ||
|
||||
err.message.includes(`not found`))
|
||||
) {
|
||||
// Windows throws an error if the process is already dead
|
||||
//
|
||||
// Command failed: taskkill /pid 6924 /T /F
|
||||
// ERROR: The process with PID 6924 (child process of PID 6736) could not be terminated.
|
||||
// Reason: There is no running instance of the task.
|
||||
return resolve()
|
||||
}
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function startApp(app: any) {
|
||||
await app.prepare()
|
||||
const handler = app.getRequestHandler()
|
||||
const server = http.createServer(handler)
|
||||
;(server as any).__app = app
|
||||
|
||||
await promiseCall(server, "listen")
|
||||
return server
|
||||
}
|
||||
|
||||
export async function stopApp(server) {
|
||||
if (server.__app) {
|
||||
await server.__app.close()
|
||||
}
|
||||
await promiseCall(server, "close")
|
||||
}
|
||||
|
||||
export function promiseCall(obj, method, ...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const newArgs = [
|
||||
...args,
|
||||
function (err, res) {
|
||||
if (err) return reject(err)
|
||||
resolve(res)
|
||||
},
|
||||
]
|
||||
|
||||
obj[method](...newArgs)
|
||||
})
|
||||
}
|
||||
|
||||
export function waitFor(millis) {
|
||||
return new Promise((resolve) => setTimeout(resolve, millis))
|
||||
}
|
||||
|
||||
export async function startStaticServer(dir, notFoundFile) {
|
||||
const app = express()
|
||||
const server = http.createServer(app)
|
||||
app.use(express.static(dir))
|
||||
|
||||
if (notFoundFile) {
|
||||
app.use((req, res) => {
|
||||
createReadStream(notFoundFile).pipe(res)
|
||||
})
|
||||
}
|
||||
|
||||
await promiseCall(server, "listen")
|
||||
return server
|
||||
}
|
||||
|
||||
export async function startCleanStaticServer(dir) {
|
||||
const app = express()
|
||||
const server = http.createServer(app)
|
||||
app.use(express.static(dir, {extensions: ["html"]}))
|
||||
|
||||
await promiseCall(server, "listen")
|
||||
return server
|
||||
}
|
||||
|
||||
// check for content in 1 second intervals timing out after
|
||||
// 30 seconds
|
||||
export async function check(contentFn, regex, hardError = true) {
|
||||
let content
|
||||
let lastErr
|
||||
|
||||
for (let tries = 0; tries < 30; tries++) {
|
||||
try {
|
||||
content = await contentFn()
|
||||
if (typeof regex === "string") {
|
||||
if (regex === content) {
|
||||
return true
|
||||
}
|
||||
} else if (regex.test(content)) {
|
||||
// found the content
|
||||
return true
|
||||
}
|
||||
await waitFor(1000)
|
||||
} catch (err) {
|
||||
await waitFor(1000)
|
||||
lastErr = err
|
||||
}
|
||||
}
|
||||
console.error("TIMED OUT CHECK: ", {regex, content, lastErr})
|
||||
|
||||
if (hardError) {
|
||||
throw new Error("TIMED OUT: " + regex + "\n\n" + content)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export class File {
|
||||
path: string
|
||||
originalContent: any
|
||||
constructor(path) {
|
||||
this.path = path
|
||||
this.originalContent = existsSync(this.path) ? readFileSync(this.path, "utf8") : null
|
||||
}
|
||||
|
||||
write(content) {
|
||||
if (!this.originalContent) {
|
||||
this.originalContent = content
|
||||
}
|
||||
writeFileSync(this.path, content, "utf8")
|
||||
}
|
||||
|
||||
replace(pattern, newValue) {
|
||||
const currentContent = readFileSync(this.path, "utf8")
|
||||
if (pattern instanceof RegExp) {
|
||||
if (!pattern.test(currentContent)) {
|
||||
throw new Error(
|
||||
`Failed to replace content.\n\nPattern: ${pattern.toString()}\n\nContent: ${currentContent}`,
|
||||
)
|
||||
}
|
||||
} else if (typeof pattern === "string") {
|
||||
if (!currentContent.includes(pattern)) {
|
||||
throw new Error(
|
||||
`Failed to replace content.\n\nPattern: ${pattern}\n\nContent: ${currentContent}`,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unknown replacement attempt type: ${pattern}`)
|
||||
}
|
||||
|
||||
const newContent = currentContent.replace(pattern, newValue)
|
||||
this.write(newContent)
|
||||
}
|
||||
|
||||
delete() {
|
||||
unlinkSync(this.path)
|
||||
}
|
||||
|
||||
restore() {
|
||||
this.write(this.originalContent)
|
||||
}
|
||||
}
|
||||
|
||||
export async function evaluate(browser, input) {
|
||||
if (typeof input === "function") {
|
||||
const result = await browser.executeScript(input)
|
||||
await new Promise((resolve) => setTimeout(resolve, 30))
|
||||
return result
|
||||
} else {
|
||||
throw new Error(`You must pass a function to be evaluated in the browser.`)
|
||||
}
|
||||
}
|
||||
|
||||
export async function retry(fn, duration = 3000, interval = 500, description) {
|
||||
if (duration % interval !== 0) {
|
||||
throw new Error(
|
||||
`invalid duration ${duration} and interval ${interval} mix, duration must be evenly divisible by interval`,
|
||||
)
|
||||
}
|
||||
|
||||
for (let i = duration; i >= 0; i -= interval) {
|
||||
try {
|
||||
return await fn()
|
||||
} catch (err) {
|
||||
if (i === 0) {
|
||||
console.error(`Failed to retry${description ? ` ${description}` : ""} within ${duration}ms`)
|
||||
throw err
|
||||
}
|
||||
console.warn(`Retrying${description ? ` ${description}` : ""} in ${interval}ms`)
|
||||
await waitFor(interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function hasRedbox(browser, expected = true) {
|
||||
let attempts = 30
|
||||
do {
|
||||
const has = await evaluate(browser, () => {
|
||||
return Boolean(
|
||||
[].slice
|
||||
.call(document.querySelectorAll("nextjs-portal"))
|
||||
.find((p) =>
|
||||
p.shadowRoot.querySelector(
|
||||
"#nextjs__container_errors_label, #nextjs__container_build_error_label",
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
if (has) {
|
||||
return true
|
||||
}
|
||||
if (--attempts < 0) {
|
||||
break
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
} while (expected)
|
||||
return false
|
||||
}
|
||||
|
||||
export async function getRedboxHeader(browser) {
|
||||
return retry(
|
||||
() =>
|
||||
evaluate(browser, () => {
|
||||
const portal = [].slice
|
||||
.call(document.querySelectorAll("nextjs-portal"))
|
||||
.find((p) => p.shadowRoot.querySelector("[data-nextjs-dialog-header"))
|
||||
const root = portal.shadowRoot
|
||||
return root
|
||||
.querySelector("[data-nextjs-dialog-header]")
|
||||
.innerText.replace(/__WEBPACK_DEFAULT_EXPORT__/, "Unknown")
|
||||
}),
|
||||
3000,
|
||||
500,
|
||||
"getRedboxHeader",
|
||||
)
|
||||
}
|
||||
|
||||
export async function getRedboxSource(browser) {
|
||||
return retry(
|
||||
() =>
|
||||
evaluate(browser, () => {
|
||||
const portal = [].slice
|
||||
.call(document.querySelectorAll("nextjs-portal"))
|
||||
.find((p) =>
|
||||
p.shadowRoot.querySelector(
|
||||
"#nextjs__container_errors_label, #nextjs__container_build_error_label",
|
||||
),
|
||||
)
|
||||
const root = portal.shadowRoot
|
||||
return root
|
||||
.querySelector("[data-nextjs-codeframe], [data-nextjs-terminal]")
|
||||
.innerText.replace(/__WEBPACK_DEFAULT_EXPORT__/, "Unknown")
|
||||
}),
|
||||
3000,
|
||||
500,
|
||||
"getRedboxSource",
|
||||
)
|
||||
}
|
||||
|
||||
export function getBrowserBodyText(browser) {
|
||||
return browser.eval('document.getElementsByTagName("body")[0].innerText')
|
||||
}
|
||||
|
||||
export function normalizeRegEx(src) {
|
||||
return new RegExp(src).source.replace(/\^\//g, "^\\/")
|
||||
}
|
||||
|
||||
function readJson(path: string) {
|
||||
return JSON.parse(readFileSync(path) as any)
|
||||
}
|
||||
|
||||
export function getBuildManifest(dir) {
|
||||
return readJson(path.join(dir, ".next/build-manifest.json"))
|
||||
}
|
||||
|
||||
export function getPageFileFromBuildManifest(dir, page) {
|
||||
const buildManifest = getBuildManifest(dir)
|
||||
const pageFiles = buildManifest.pages[page]
|
||||
if (!pageFiles) {
|
||||
throw new Error(`No files for page ${page}`)
|
||||
}
|
||||
|
||||
const pageFile = pageFiles.find(
|
||||
(file) => file.endsWith(".js") && file.includes(`pages${page === "" ? "/index" : page}`),
|
||||
)
|
||||
if (!pageFile) {
|
||||
throw new Error(`No page file for page ${page}`)
|
||||
}
|
||||
|
||||
return pageFile
|
||||
}
|
||||
|
||||
export function readNextBuildClientPageFile(appDir, page) {
|
||||
const pageFile = getPageFileFromBuildManifest(appDir, page)
|
||||
return readFileSync(path.join(appDir, ".next", pageFile), "utf8")
|
||||
}
|
||||
|
||||
export function getPagesManifest(dir) {
|
||||
const serverFile = path.join(dir, ".next/server/pages-manifest.json")
|
||||
|
||||
if (existsSync(serverFile)) {
|
||||
return readJson(serverFile)
|
||||
}
|
||||
return readJson(path.join(dir, ".next/serverless/pages-manifest.json"))
|
||||
}
|
||||
|
||||
export function updatePagesManifest(dir, content) {
|
||||
const serverFile = path.join(dir, ".next/server/pages-manifest.json")
|
||||
|
||||
if (existsSync(serverFile)) {
|
||||
return writeFile(serverFile, content)
|
||||
}
|
||||
return writeFile(path.join(dir, ".next/serverless/pages-manifest.json"), content)
|
||||
}
|
||||
|
||||
export function getPageFileFromPagesManifest(dir, page) {
|
||||
const pagesManifest = getPagesManifest(dir)
|
||||
const pageFile = pagesManifest[page]
|
||||
if (!pageFile) {
|
||||
throw new Error(`No file for page ${page}`)
|
||||
}
|
||||
|
||||
return pageFile
|
||||
}
|
||||
|
||||
export function readNextBuildServerPageFile(appDir, page) {
|
||||
const pageFile = getPageFileFromPagesManifest(appDir, page)
|
||||
return readFileSync(path.join(appDir, ".next", "server", pageFile), "utf8")
|
||||
}
|
||||
34
integration-tests/auth/test/next-webdriver.d.ts
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
interface ChainMethods {
|
||||
elementByCss: (selector: string) => Chain<Element>
|
||||
elementById: () => Chain<Element>
|
||||
getValue: () => Chain<any>
|
||||
text: () => Chain<string>
|
||||
type: () => Chain<any>
|
||||
moveTo: () => Chain<any>
|
||||
getComputedCss: () => Chain<any>
|
||||
getAttribute: () => Chain<any>
|
||||
hasElementByCssSelector: () => Chain<any>
|
||||
click: () => Chain<any>
|
||||
elementsByCss: () => Chain<Element[]>
|
||||
waitForElementByCss: (arg: string) => Chain<any>
|
||||
eval: (evalStr: string) => Chain<any>
|
||||
log: () => Chain<any>
|
||||
url: () => Chain<any>
|
||||
back: () => Chain<any>
|
||||
forward: () => Chain<any>
|
||||
refresh: () => Chain<any>
|
||||
setDimensions: (opts: {height: number; width: number}) => Chain<any>
|
||||
close: () => Chain<any>
|
||||
quit: () => Chain<any>
|
||||
}
|
||||
|
||||
interface Chain<T> extends Promise<T & ChainMethods>, ChainMethods {}
|
||||
|
||||
type Browser = {__brand: "Browser"}
|
||||
|
||||
export default function (
|
||||
appPort: number,
|
||||
path: string,
|
||||
waitHydration?: boolean,
|
||||
allowHydrationRetry?: boolean,
|
||||
): Promise<Chain<Browser>>
|
||||
251
integration-tests/auth/test/next-webdriver.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
/// <reference types="./next-webdriver" />
|
||||
import fetch from "node-fetch"
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import {Builder, By} from "selenium-webdriver"
|
||||
import {Options as ChromeOptions} from "selenium-webdriver/chrome"
|
||||
import {Options as FireFoxOptions} from "selenium-webdriver/firefox"
|
||||
import {Options as SafariOptions} from "selenium-webdriver/safari"
|
||||
import Chain from "./wd-chain"
|
||||
|
||||
export function waitFor(millis) {
|
||||
return new Promise((resolve) => setTimeout(resolve, millis))
|
||||
}
|
||||
|
||||
const {
|
||||
BROWSER_NAME: browserName = "chrome",
|
||||
BROWSERSTACK,
|
||||
BROWSERSTACK_USERNAME,
|
||||
BROWSERSTACK_ACCESS_KEY,
|
||||
HEADLESS,
|
||||
CHROME_BIN,
|
||||
LEGACY_SAFARI,
|
||||
CHROMEWEBDRIVER,
|
||||
} = process.env
|
||||
|
||||
let capabilities = {}
|
||||
|
||||
const isChrome = browserName === "chrome"
|
||||
const isSafari = browserName === "safari"
|
||||
const isFirefox = browserName === "firefox"
|
||||
const isIE = browserName === "internet explorer"
|
||||
|
||||
if (CHROMEWEBDRIVER) {
|
||||
console.log("path", process.env.PATH)
|
||||
console.log("comspec", process.env.COMSPEC)
|
||||
console.log("chromedriver", `${CHROMEWEBDRIVER}${path.delimiter}${process.env.PATH}`)
|
||||
}
|
||||
|
||||
if (process.env.ChromeWebDriver) {
|
||||
process.env.PATH = `${process.env.ChromeWebDriver}${path.delimiter}${process.env.PATH}`
|
||||
}
|
||||
|
||||
const isBrowserStack = BROWSERSTACK && BROWSERSTACK_USERNAME && BROWSERSTACK_ACCESS_KEY
|
||||
|
||||
if (isBrowserStack) {
|
||||
const safariOpts = {
|
||||
os: "OS X",
|
||||
os_version: "Mojave",
|
||||
browser: "Safari",
|
||||
}
|
||||
const safariLegacyOpts = {
|
||||
os: "OS X",
|
||||
os_version: "Sierra",
|
||||
browserName: "Safari",
|
||||
browser_version: "10.1",
|
||||
}
|
||||
const ieOpts = {
|
||||
os: "Windows",
|
||||
os_version: "10",
|
||||
browser: "IE",
|
||||
}
|
||||
const firefoxOpts = {
|
||||
os: "Windows",
|
||||
os_version: "10",
|
||||
browser: "Firefox",
|
||||
}
|
||||
const sharedOpts = {
|
||||
"browserstack.local": true,
|
||||
"browserstack.video": false,
|
||||
"browserstack.user": BROWSERSTACK_USERNAME,
|
||||
"browserstack.key": BROWSERSTACK_ACCESS_KEY,
|
||||
"browserstack.localIdentifier": global.browserStackLocalId,
|
||||
}
|
||||
|
||||
capabilities = {
|
||||
...capabilities,
|
||||
...sharedOpts,
|
||||
|
||||
...(isIE ? ieOpts : {}),
|
||||
...(isSafari ? (LEGACY_SAFARI ? safariLegacyOpts : safariOpts) : {}),
|
||||
...(isFirefox ? firefoxOpts : {}),
|
||||
}
|
||||
}
|
||||
|
||||
let chromeOptions = new ChromeOptions()
|
||||
let firefoxOptions = new FireFoxOptions()
|
||||
let safariOptions = new SafariOptions()
|
||||
|
||||
chromeOptions.addArguments("--remote-debugging-port=9222")
|
||||
chromeOptions.addArguments("--headless")
|
||||
chromeOptions.addArguments("--no-sandbox")
|
||||
chromeOptions.addArguments("--disable-gpu")
|
||||
chromeOptions.addArguments("--disable-dev-shm-usage")
|
||||
|
||||
if (HEADLESS) {
|
||||
const screenSize = {width: 1280, height: 720}
|
||||
chromeOptions = chromeOptions.headless().windowSize(screenSize)
|
||||
firefoxOptions = firefoxOptions.headless().windowSize(screenSize)
|
||||
}
|
||||
|
||||
if (CHROME_BIN) {
|
||||
console.log("chrome bin", CHROME_BIN)
|
||||
chromeOptions.setChromeBinaryPath(path.resolve(CHROME_BIN))
|
||||
}
|
||||
|
||||
let seleniumServer
|
||||
|
||||
if (isBrowserStack) {
|
||||
seleniumServer = "http://hub-cloud.browserstack.com/wd/hub"
|
||||
} else if (global.seleniumServerPort) {
|
||||
seleniumServer = `http://localhost:${global.seleniumServerPort}/wd/hub`
|
||||
}
|
||||
|
||||
let browser = new Builder()
|
||||
.usingServer(seleniumServer)
|
||||
.withCapabilities(capabilities)
|
||||
.forBrowser(browserName)
|
||||
.setChromeOptions(chromeOptions)
|
||||
.setFirefoxOptions(firefoxOptions)
|
||||
.setSafariOptions(safariOptions)
|
||||
.build()
|
||||
|
||||
global.wd = browser
|
||||
|
||||
let initialWindow
|
||||
let deviceIP = "localhost"
|
||||
|
||||
const getDeviceIP = async () => {
|
||||
const networkIntfs = os.networkInterfaces()
|
||||
// find deviceIP to use with BrowserStack
|
||||
for (const intf of Object.keys(networkIntfs)) {
|
||||
const addresses = networkIntfs[intf]
|
||||
|
||||
for (const {internal, address, family} of addresses || []) {
|
||||
if (family !== "IPv4" || internal) continue
|
||||
try {
|
||||
const res = await fetch(`http://${address}:${global._newTabPort}`)
|
||||
if (res.ok) {
|
||||
deviceIP = address
|
||||
break
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const freshWindow = async (appPort) => {
|
||||
// First we close all extra windows left over
|
||||
let allWindows = await browser.getAllWindowHandles()
|
||||
|
||||
for (const win of allWindows) {
|
||||
if (win === initialWindow) continue
|
||||
try {
|
||||
await browser.switchTo().window(win)
|
||||
await browser.close()
|
||||
} catch (_) {}
|
||||
}
|
||||
await browser.switchTo().window(initialWindow)
|
||||
|
||||
// now we open a fresh window
|
||||
await browser.get(`http://${deviceIP}:${global._newTabPort}`)
|
||||
|
||||
// await browser.executeScript(`window.location.href = "http://${deviceIP}:${appPort}"`)
|
||||
|
||||
const newTabLink = await browser.findElement(By.css("#new"))
|
||||
await newTabLink.click()
|
||||
|
||||
allWindows = await browser.getAllWindowHandles()
|
||||
const newWindow = allWindows.find((win) => win !== initialWindow)
|
||||
await browser.switchTo().window(newWindow!)
|
||||
}
|
||||
|
||||
export default async function webdriver(
|
||||
appPort,
|
||||
path,
|
||||
waitHydration = true,
|
||||
allowHydrationRetry = false,
|
||||
) {
|
||||
if (!initialWindow) {
|
||||
initialWindow = await browser.getWindowHandle()
|
||||
}
|
||||
if (isBrowserStack && deviceIP === "localhost" && !LEGACY_SAFARI) {
|
||||
await getDeviceIP()
|
||||
}
|
||||
// browser.switchTo().window() fails with `missing field `handle``
|
||||
// in safari and firefox so disabling freshWindow since our
|
||||
// tests shouldn't rely on it
|
||||
// if (isChrome) {
|
||||
// await freshWindow(appPort)
|
||||
// }
|
||||
|
||||
const url = `http://${deviceIP}:${appPort}${path}`
|
||||
;(browser as any).initUrl = url
|
||||
console.log(`> Loading browser with ${url}`)
|
||||
|
||||
await browser.get(url)
|
||||
console.log(`> Loaded browser with ${url}`)
|
||||
|
||||
// Wait for application to hydrate
|
||||
if (waitHydration) {
|
||||
// console.log(`\n> Waiting hydration for ${url}\n`)
|
||||
|
||||
const checkHydrated = async () => {
|
||||
await browser.executeAsyncScript(function () {
|
||||
var callback = arguments[arguments.length - 1]
|
||||
|
||||
// if it's not a Next.js app return
|
||||
if (document.documentElement.innerHTML.indexOf("__NEXT_DATA__") === -1) {
|
||||
callback()
|
||||
}
|
||||
|
||||
if ((window as any).__NEXT_HYDRATED) {
|
||||
callback()
|
||||
} else {
|
||||
var timeout = setTimeout(callback, 10 * 1000)
|
||||
;(window as any).__NEXT_HYDRATED_CB = function () {
|
||||
clearTimeout(timeout)
|
||||
callback()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await checkHydrated()
|
||||
} catch (err) {
|
||||
if (allowHydrationRetry) {
|
||||
// re-try in case the page reloaded during check
|
||||
await waitFor(2000)
|
||||
await checkHydrated()
|
||||
} else {
|
||||
console.error("failed to check hydration")
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(`\n> Hydration complete for ${url}\n`)
|
||||
}
|
||||
|
||||
const promiseProp = new Set(["then", "catch", "finally"])
|
||||
|
||||
return new Proxy(new Chain(browser), {
|
||||
get(obj, prop) {
|
||||
if (obj[prop] || promiseProp.has(prop as string)) {
|
||||
return obj[prop]
|
||||
}
|
||||
return browser[prop]
|
||||
},
|
||||
})
|
||||
}
|
||||
142
integration-tests/auth/test/wd-chain.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
// @ts-nocheck
|
||||
import {until, By} from "selenium-webdriver"
|
||||
|
||||
export default class Chain {
|
||||
constructor(browser) {
|
||||
this.browser = browser
|
||||
}
|
||||
|
||||
updateChain(nextCall) {
|
||||
if (!this.promise) {
|
||||
this.promise = Promise.resolve()
|
||||
}
|
||||
this.promise = this.promise.then(nextCall)
|
||||
this.then = (...args) => this.promise.then(...args)
|
||||
this.catch = (...args) => this.promise.catch(...args)
|
||||
return this
|
||||
}
|
||||
|
||||
elementByCss(sel) {
|
||||
return this.updateChain(() =>
|
||||
this.browser.findElement(By.css(sel)).then((el) => {
|
||||
el.sel = sel
|
||||
el.text = () => el.getText()
|
||||
el.getComputedCss = (prop) => el.getCssValue(prop)
|
||||
el.type = (text) => el.sendKeys(text)
|
||||
el.getValue = () =>
|
||||
this.browser.executeScript(`return document.querySelector('${sel}').value`)
|
||||
return el
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
elementByXpath(sel) {
|
||||
return this.updateChain(() => {
|
||||
return this.browser.findElement(By.xpath(sel)).getText()
|
||||
})
|
||||
}
|
||||
|
||||
elementById(sel) {
|
||||
return this.elementByCss(`#${sel}`)
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.updateChain((el) =>
|
||||
this.browser.executeScript(`return document.querySelector('${el.sel}').value`),
|
||||
)
|
||||
}
|
||||
|
||||
text() {
|
||||
return this.updateChain((el) => el.getText())
|
||||
}
|
||||
|
||||
type(text) {
|
||||
return this.updateChain((el) => el.sendKeys(text))
|
||||
}
|
||||
|
||||
moveTo() {
|
||||
return this.updateChain((el) => {
|
||||
return this.browser
|
||||
.actions()
|
||||
.move({origin: el})
|
||||
.perform()
|
||||
.then(() => el)
|
||||
})
|
||||
}
|
||||
|
||||
getComputedCss(prop) {
|
||||
return this.updateChain((el) => {
|
||||
return el.getCssValue(prop)
|
||||
})
|
||||
}
|
||||
|
||||
getAttribute(attr) {
|
||||
return this.updateChain((el) => el.getAttribute(attr))
|
||||
}
|
||||
|
||||
hasElementByCssSelector(sel) {
|
||||
return this.eval(`document.querySelector('${sel}')`)
|
||||
}
|
||||
|
||||
click() {
|
||||
return this.updateChain((el) => {
|
||||
return el.click().then(() => el)
|
||||
})
|
||||
}
|
||||
|
||||
elementsByCss(sel) {
|
||||
return this.updateChain(() => this.browser.findElements(By.css(sel)))
|
||||
}
|
||||
|
||||
waitForElementByCss(sel, timeout) {
|
||||
return this.updateChain(() => this.browser.wait(until.elementLocated(By.css(sel), timeout)))
|
||||
}
|
||||
|
||||
waitForCondition(condition) {
|
||||
return this.updateChain(() =>
|
||||
this.browser.wait(async (driver) => {
|
||||
return driver.executeScript("return " + condition).catch(() => false)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
eval(snippet) {
|
||||
if (typeof snippet === "string" && !snippet.startsWith("return")) {
|
||||
snippet = `return ${snippet}`
|
||||
}
|
||||
return this.updateChain(() => this.browser.executeScript(snippet))
|
||||
}
|
||||
|
||||
log(type) {
|
||||
return this.updateChain(() => this.browser.manage().logs().get(type))
|
||||
}
|
||||
|
||||
url() {
|
||||
return this.updateChain(() => this.browser.getCurrentUrl())
|
||||
}
|
||||
|
||||
back() {
|
||||
return this.updateChain(() => this.browser.navigate().back())
|
||||
}
|
||||
|
||||
forward() {
|
||||
return this.updateChain(() => this.browser.navigate().forward())
|
||||
}
|
||||
|
||||
refresh() {
|
||||
return this.updateChain(() => this.browser.navigate().refresh())
|
||||
}
|
||||
|
||||
setDimensions({height, width}) {
|
||||
return this.updateChain(() =>
|
||||
this.browser.manage().window().setRect({width, height, x: 0, y: 0}),
|
||||
)
|
||||
}
|
||||
|
||||
close() {
|
||||
return this.updateChain(() => Promise.resolve())
|
||||
}
|
||||
quit() {
|
||||
return this.close()
|
||||
}
|
||||
}
|
||||
11
integration-tests/auth/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "@blitzjs/config/tsconfig.nextjs.json",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"react": ["./node_modules/@types/react"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"baseUrl": "."
|
||||
}
|
||||
39
package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "blitz-root",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*",
|
||||
"integration-tests/*"
|
||||
],
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepare": "husky install",
|
||||
"build": "turbo run build",
|
||||
"build:apps": "turbo run buildapp",
|
||||
"dev": "turbo run dev --no-cache --parallel",
|
||||
"lint": "turbo run lint",
|
||||
"test": "turbo run test",
|
||||
"clean": "turbo run clean && rm -rf node_modules",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/manypkg": "0.19.1",
|
||||
"eslint": "7.32.0",
|
||||
"husky": "7.0.4",
|
||||
"jsdom": "19.0.0",
|
||||
"lint-staged": "12.1.7",
|
||||
"next": "12.1.4",
|
||||
"only-allow": "1.1.0",
|
||||
"patch-package": "6.4.7",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier-plugin-prisma": "3.8.0",
|
||||
"pretty-quick": "3.1.3",
|
||||
"turbo": "1.1.5",
|
||||
"vitest": "0.8.2",
|
||||
"wait-on": "6.0.1"
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"packageManager": "pnpm@6.21.0"
|
||||
}
|
||||
1
packages/blitz-auth/.eslintrc.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require("@blitzjs/config/eslint")
|
||||
11
packages/blitz-auth/build.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import {BuildConfig} from "unbuild"
|
||||
|
||||
const config: BuildConfig = {
|
||||
entries: ["./src/index-browser", "./src/index-server"],
|
||||
externals: ["index-browser.cjs", "index-browser.mjs", "react", "next"],
|
||||
declaration: true,
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
},
|
||||
}
|
||||
export default config
|
||||
55
packages/blitz-auth/package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@blitzjs/auth",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"dev": "pnpm run predev && watch unbuild src --wait=0.2",
|
||||
"predev": "wait-on -d 250 ../blitz/dist/index-server.d.ts",
|
||||
"lint": "eslint . --fix",
|
||||
"test": "vitest run",
|
||||
"test-watch": "vitest",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
||||
},
|
||||
"main": "./dist/index-server.cjs",
|
||||
"module": "./dist/index-server.mjs",
|
||||
"browser": "./dist/index-browser.mjs",
|
||||
"types": "./dist/index-server.d.ts",
|
||||
"sideEffects": false,
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@blitzjs/config": "workspace:*",
|
||||
"@testing-library/react": "13.0.0",
|
||||
"@testing-library/react-hooks": "7.0.2",
|
||||
"@types/cookie": "0.4.1",
|
||||
"@types/jsonwebtoken": "8.5.8",
|
||||
"@types/react": "17.0.43",
|
||||
"@types/react-dom": "17.0.14",
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0",
|
||||
"typescript": "^4.5.3",
|
||||
"unbuild": "0.6.9",
|
||||
"watch": "1.0.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/b64-lite": "1.3.0",
|
||||
"@types/debug": "4.1.7",
|
||||
"@types/secure-password": "3.1.1",
|
||||
"b64-lite": "1.4.0",
|
||||
"bad-behavior": "1.0.1",
|
||||
"blitz": "workspace:*",
|
||||
"cookie": "0.4.1",
|
||||
"debug": "4.3.3",
|
||||
"http": "0.0.1-security",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"nanoid": "3.2.0",
|
||||
"path": "0.12.7",
|
||||
"secure-password": "4.0.0",
|
||||
"url": "0.11.0"
|
||||
}
|
||||
}
|
||||
171
packages/blitz-auth/src/client/auth-client.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
|
||||
import {vi, expect, describe, it, beforeAll, afterAll, spyOn, SpyInstance} from "vitest"
|
||||
import {parsePublicDataToken, getPublicDataStore, useSession} from "./index"
|
||||
import {COOKIE_PUBLIC_DATA_TOKEN} from "../shared"
|
||||
import {toBase64} from "b64-lite"
|
||||
import {act} from "@testing-library/react"
|
||||
import {renderHook} from "@testing-library/react-hooks"
|
||||
|
||||
vi.mock("blitz", async () => {
|
||||
const blitz = await vi.importActual("blitz")
|
||||
//@ts-ignore
|
||||
return {...blitz}
|
||||
})
|
||||
|
||||
import * as stdlib from "blitz"
|
||||
|
||||
beforeAll(() => {
|
||||
globalThis.__BLITZ_SESSION_COOKIE_PREFIX = "blitz-test"
|
||||
})
|
||||
afterAll(() => {
|
||||
globalThis.__BLITZ_SESSION_COOKIE_PREFIX = undefined
|
||||
})
|
||||
|
||||
describe("parsePublicDataToken", () => {
|
||||
it("throws if token is empty", () => {
|
||||
const ret = () => parsePublicDataToken("")
|
||||
expect(ret).toThrow("[parsePublicDataToken] Failed: token is empty")
|
||||
})
|
||||
|
||||
it("throws if the token cannot be parsed", () => {
|
||||
const invalidJSON = "{"
|
||||
const ret = () => parsePublicDataToken(toBase64(invalidJSON))
|
||||
|
||||
expect(ret).toThrowError("[parsePublicDataToken] Failed to parse publicDataStr: {")
|
||||
})
|
||||
|
||||
it("parses the public data", () => {
|
||||
const validJSON = '{"foo": "bar"}'
|
||||
expect(parsePublicDataToken(toBase64(validJSON))).toEqual({
|
||||
publicData: {foo: "bar"},
|
||||
})
|
||||
})
|
||||
|
||||
it("parses the public data containing unicode chars", () => {
|
||||
const data = '"foo-κόσμε-żółć-平仮名"'
|
||||
expect(parsePublicDataToken(toBase64(data))).toEqual({
|
||||
publicData: "foo-κόσμε-żółć-平仮名",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("publicDataStore", () => {
|
||||
it("calls readCookie token on init", () => {
|
||||
const spy = spyOn(stdlib, "readCookie")
|
||||
getPublicDataStore()
|
||||
expect(spy).toHaveBeenCalledWith(COOKIE_PUBLIC_DATA_TOKEN())
|
||||
spy.mockRestore()
|
||||
})
|
||||
|
||||
describe("updateState", () => {
|
||||
let localStorageSpy: SpyInstance
|
||||
|
||||
beforeAll(() => {
|
||||
localStorageSpy = spyOn(Storage.prototype, "setItem")
|
||||
})
|
||||
|
||||
it("sets local storage", () => {
|
||||
getPublicDataStore().updateState()
|
||||
expect(localStorageSpy).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("publishes data on observable", () => {
|
||||
let ret: any = null
|
||||
getPublicDataStore().observable.subscribe((data) => {
|
||||
ret = data
|
||||
})
|
||||
getPublicDataStore().updateState()
|
||||
expect(ret).not.toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe("clear", () => {
|
||||
it("clears the cookie", () => {
|
||||
const spy = spyOn(stdlib, "deleteCookie")
|
||||
getPublicDataStore().clear()
|
||||
expect(spy).toHaveBeenCalledWith(COOKIE_PUBLIC_DATA_TOKEN())
|
||||
})
|
||||
it("clears the cache", () => {
|
||||
getPublicDataStore().clear()
|
||||
})
|
||||
|
||||
it("publishes empty data", () => {
|
||||
let ret: any = null
|
||||
getPublicDataStore().observable.subscribe((data) => {
|
||||
ret = data
|
||||
})
|
||||
getPublicDataStore().clear()
|
||||
expect(ret).toEqual({userId: null})
|
||||
})
|
||||
})
|
||||
|
||||
describe("getData", () => {
|
||||
describe("when the cookie is falsy", () => {
|
||||
it("returns empty data if cookie is falsy", () => {
|
||||
const ret = getPublicDataStore().getData()
|
||||
|
||||
expect(ret).toEqual({userId: null})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("useSession", () => {
|
||||
it("returns empty at when no value is set", () => {
|
||||
const {result} = renderHook(() => useSession())
|
||||
|
||||
expect(result.current).toEqual({
|
||||
isLoading: false,
|
||||
userId: null,
|
||||
})
|
||||
})
|
||||
|
||||
it("subscribes to the public data store", () => {
|
||||
const {result} = renderHook(() => useSession())
|
||||
|
||||
act(() => {
|
||||
getPublicDataStore().updateState({roles: ["foo"], userId: "bar"} as any)
|
||||
})
|
||||
|
||||
expect(result.current).toEqual({
|
||||
isLoading: false,
|
||||
userId: "bar",
|
||||
roles: ["foo"],
|
||||
})
|
||||
|
||||
act(() => {
|
||||
getPublicDataStore().updateState({roles: ["baz"], userId: "boo"} as any)
|
||||
})
|
||||
|
||||
expect(result.current).toEqual({
|
||||
isLoading: false,
|
||||
userId: "boo",
|
||||
roles: ["baz"],
|
||||
})
|
||||
})
|
||||
|
||||
it("un-subscribes from the public data store on unmount", () => {
|
||||
const {result, unmount} = renderHook(() => useSession())
|
||||
|
||||
act(() => {
|
||||
getPublicDataStore().updateState({roles: ["foo"], userId: "bar"} as any)
|
||||
})
|
||||
|
||||
act(() => {
|
||||
unmount()
|
||||
})
|
||||
|
||||
act(() => {
|
||||
getPublicDataStore().updateState({roles: ["baz"], userId: "boo"} as any)
|
||||
})
|
||||
|
||||
expect(result.current).toEqual({
|
||||
isLoading: false,
|
||||
userId: "bar",
|
||||
roles: ["foo"],
|
||||
})
|
||||
})
|
||||
})
|
||||
328
packages/blitz-auth/src/client/index.tsx
Normal file
@@ -0,0 +1,328 @@
|
||||
import {fromBase64} from "b64-lite"
|
||||
import _BadBehavior from "bad-behavior"
|
||||
import {useEffect, useState} from "react"
|
||||
import {UrlObject} from "url"
|
||||
import React, {ComponentPropsWithoutRef} from "react"
|
||||
import {
|
||||
assert,
|
||||
deleteCookie,
|
||||
readCookie,
|
||||
isServer,
|
||||
isClient,
|
||||
createClientPlugin,
|
||||
AuthenticationError,
|
||||
RedirectError,
|
||||
} from "blitz"
|
||||
import {
|
||||
COOKIE_CSRF_TOKEN,
|
||||
COOKIE_PUBLIC_DATA_TOKEN,
|
||||
LOCALSTORAGE_CSRF_TOKEN,
|
||||
LOCALSTORAGE_PREFIX,
|
||||
LOCALSTORAGE_PUBLIC_DATA_TOKEN,
|
||||
PublicData,
|
||||
EmptyPublicData,
|
||||
AuthenticatedClientSession,
|
||||
ClientSession,
|
||||
} from "../shared"
|
||||
import _debug from "debug"
|
||||
import {formatWithValidation} from "../shared/url-utils"
|
||||
import {Ctx} from "blitz"
|
||||
import {ComponentType} from "react"
|
||||
import {ComponentProps} from "react"
|
||||
|
||||
const BadBehavior: typeof _BadBehavior =
|
||||
"default" in _BadBehavior ? (_BadBehavior as any).default : _BadBehavior
|
||||
|
||||
const debug = _debug("blitz:auth-client")
|
||||
|
||||
export const parsePublicDataToken = (token: string) => {
|
||||
assert(token, "[parsePublicDataToken] Failed: token is empty")
|
||||
|
||||
const publicDataStr = fromBase64(token)
|
||||
try {
|
||||
const publicData: PublicData = JSON.parse(publicDataStr)
|
||||
return {
|
||||
publicData,
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`[parsePublicDataToken] Failed to parse publicDataStr: ${publicDataStr}`)
|
||||
}
|
||||
}
|
||||
|
||||
const emptyPublicData: EmptyPublicData = {userId: null}
|
||||
|
||||
class PublicDataStore {
|
||||
private eventKey = `${LOCALSTORAGE_PREFIX}publicDataUpdated`
|
||||
readonly observable = BadBehavior<PublicData | EmptyPublicData>()
|
||||
|
||||
constructor() {
|
||||
if (typeof window !== "undefined") {
|
||||
// Set default value & prevent infinite loop
|
||||
this.updateState(undefined, {suppressEvent: true})
|
||||
window.addEventListener("storage", (event) => {
|
||||
if (event.key === this.eventKey) {
|
||||
// Prevent infinite loop
|
||||
this.updateState(undefined, {suppressEvent: true})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
updateState(value?: PublicData | EmptyPublicData, opts?: {suppressEvent: boolean}) {
|
||||
// We use localStorage as a message bus between tabs.
|
||||
// Setting the current time in ms will cause other tabs to receive the `storage` event
|
||||
if (!opts?.suppressEvent) {
|
||||
// Prevent infinite loop
|
||||
try {
|
||||
localStorage.setItem(this.eventKey, Date.now().toString())
|
||||
} catch (err) {
|
||||
console.error("LocalStorage is not available", err)
|
||||
}
|
||||
}
|
||||
this.observable.next(value ?? this.getData())
|
||||
}
|
||||
|
||||
clear() {
|
||||
deleteCookie(COOKIE_PUBLIC_DATA_TOKEN())
|
||||
localStorage.removeItem(LOCALSTORAGE_PUBLIC_DATA_TOKEN())
|
||||
this.updateState(emptyPublicData)
|
||||
}
|
||||
|
||||
getData() {
|
||||
const publicDataToken = this.getToken()
|
||||
if (!publicDataToken) {
|
||||
return emptyPublicData
|
||||
}
|
||||
|
||||
const {publicData} = parsePublicDataToken(publicDataToken)
|
||||
return publicData
|
||||
}
|
||||
|
||||
private getToken() {
|
||||
const cookieValue = readCookie(COOKIE_PUBLIC_DATA_TOKEN())
|
||||
if (cookieValue) {
|
||||
localStorage.setItem(LOCALSTORAGE_PUBLIC_DATA_TOKEN(), cookieValue)
|
||||
return cookieValue
|
||||
} else {
|
||||
return localStorage.getItem(LOCALSTORAGE_PUBLIC_DATA_TOKEN())
|
||||
}
|
||||
}
|
||||
}
|
||||
export const getPublicDataStore = (): PublicDataStore => {
|
||||
if (!(window as any).__publicDataStore) {
|
||||
;(window as any).__publicDataStore = new PublicDataStore()
|
||||
}
|
||||
return (window as any).__publicDataStore
|
||||
}
|
||||
|
||||
export const getAntiCSRFToken = () => {
|
||||
const cookieValue = readCookie(COOKIE_CSRF_TOKEN())
|
||||
if (cookieValue) {
|
||||
localStorage.setItem(LOCALSTORAGE_CSRF_TOKEN(), cookieValue)
|
||||
return cookieValue
|
||||
} else {
|
||||
return localStorage.getItem(LOCALSTORAGE_CSRF_TOKEN())
|
||||
}
|
||||
}
|
||||
|
||||
export interface UseSessionOptions {
|
||||
initialPublicData?: PublicData
|
||||
suspense?: boolean | null
|
||||
}
|
||||
|
||||
export const useSession = (options: UseSessionOptions = {}): ClientSession => {
|
||||
const suspense = options?.suspense ?? Boolean(process.env.__BLITZ_SUSPENSE_ENABLED)
|
||||
|
||||
let initialState: ClientSession
|
||||
if (options.initialPublicData) {
|
||||
initialState = {...options.initialPublicData, isLoading: false}
|
||||
} else if (suspense) {
|
||||
if (isServer) {
|
||||
throw new Promise((_) => {})
|
||||
} else {
|
||||
initialState = {...getPublicDataStore().getData(), isLoading: false}
|
||||
}
|
||||
} else {
|
||||
initialState = {...emptyPublicData, isLoading: true}
|
||||
}
|
||||
|
||||
const [session, setSession] = useState(initialState)
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize on mount
|
||||
setSession({...getPublicDataStore().getData(), isLoading: false})
|
||||
const subscription = getPublicDataStore().observable.subscribe((data) =>
|
||||
setSession({...data, isLoading: false}),
|
||||
)
|
||||
return subscription.unsubscribe
|
||||
}, [])
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
export const useAuthorizeIf = (condition?: boolean) => {
|
||||
if (isClient && condition && !getPublicDataStore().getData().userId) {
|
||||
const error = new AuthenticationError()
|
||||
error.stack = null!
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export const useAuthorize = () => {
|
||||
useAuthorizeIf(true)
|
||||
}
|
||||
|
||||
export const useAuthenticatedSession = (
|
||||
options: UseSessionOptions = {},
|
||||
): AuthenticatedClientSession => {
|
||||
useAuthorize()
|
||||
return useSession(options) as AuthenticatedClientSession
|
||||
}
|
||||
|
||||
export const useRedirectAuthenticated = (to: UrlObject | string) => {
|
||||
if (isClient && getPublicDataStore().getData().userId) {
|
||||
const error = new RedirectError(to)
|
||||
error.stack = null!
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export interface RouteUrlObject extends Pick<UrlObject, "pathname" | "query"> {
|
||||
pathname: string
|
||||
}
|
||||
|
||||
export type RedirectAuthenticatedTo = string | RouteUrlObject | false
|
||||
export type RedirectAuthenticatedToFnCtx = {
|
||||
session: Ctx["session"]["$publicData"]
|
||||
}
|
||||
export type RedirectAuthenticatedToFn = (
|
||||
args: RedirectAuthenticatedToFnCtx,
|
||||
) => RedirectAuthenticatedTo
|
||||
|
||||
export type BlitzPage<P = {}> = React.ComponentType<P> & {
|
||||
getLayout?: (component: JSX.Element) => JSX.Element
|
||||
authenticate?: boolean | {redirectTo?: string}
|
||||
suppressFirstRenderFlicker?: boolean
|
||||
redirectAuthenticatedTo?: RedirectAuthenticatedTo | RedirectAuthenticatedToFn
|
||||
}
|
||||
|
||||
export function getAuthValues<TProps = any>(
|
||||
Page: ComponentType<TProps> | BlitzPage,
|
||||
props: ComponentPropsWithoutRef<BlitzPage>,
|
||||
) {
|
||||
if (!Page) return {}
|
||||
|
||||
let authenticate = "authenticate" in Page && Page.authenticate
|
||||
let redirectAuthenticatedTo = "redirectAuthenticatedTo" in Page && Page.redirectAuthenticatedTo
|
||||
|
||||
if (authenticate === undefined && redirectAuthenticatedTo === undefined) {
|
||||
const layout = "getLayout" in Page && Page.getLayout?.(<Page {...props} />)
|
||||
|
||||
if (layout) {
|
||||
let currentElement = layout
|
||||
while (true) {
|
||||
const type = layout.type
|
||||
|
||||
if (type.authenticate !== undefined || type.redirectAuthenticatedTo !== undefined) {
|
||||
authenticate = type.authenticate
|
||||
redirectAuthenticatedTo = type.redirectAuthenticatedTo
|
||||
break
|
||||
}
|
||||
|
||||
if (currentElement.props?.children) {
|
||||
currentElement = currentElement.props?.children
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {authenticate, redirectAuthenticatedTo}
|
||||
}
|
||||
|
||||
function withBlitzAuthPlugin<TProps = any>(Page: ComponentType<TProps> | BlitzPage<TProps>) {
|
||||
const AuthRoot = (props: ComponentProps<any>) => {
|
||||
useSession({suspense: false})
|
||||
|
||||
let {authenticate, redirectAuthenticatedTo} = getAuthValues(Page, props)
|
||||
|
||||
useAuthorizeIf(authenticate === true)
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const publicData = getPublicDataStore().getData()
|
||||
|
||||
// We read directly from publicData.userId instead of useSession
|
||||
// so we can access userId on first render. useSession is always empty on first render
|
||||
if (publicData.userId) {
|
||||
debug("[BlitzAuthInnerRoot] logged in")
|
||||
|
||||
if (typeof redirectAuthenticatedTo === "function") {
|
||||
redirectAuthenticatedTo = redirectAuthenticatedTo({
|
||||
session: publicData,
|
||||
})
|
||||
}
|
||||
|
||||
if (redirectAuthenticatedTo) {
|
||||
const redirectUrl =
|
||||
typeof redirectAuthenticatedTo === "string"
|
||||
? redirectAuthenticatedTo
|
||||
: formatWithValidation(redirectAuthenticatedTo)
|
||||
|
||||
debug("[BlitzAuthInnerRoot] redirecting to", redirectUrl)
|
||||
const error = new RedirectError(redirectUrl)
|
||||
error.stack = null!
|
||||
throw error
|
||||
}
|
||||
} else {
|
||||
debug("[BlitzAuthInnerRoot] logged out")
|
||||
if (authenticate && typeof authenticate === "object" && authenticate.redirectTo) {
|
||||
let {redirectTo} = authenticate
|
||||
if (typeof redirectTo !== "string") {
|
||||
redirectTo = formatWithValidation(redirectTo)
|
||||
}
|
||||
|
||||
const url = new URL(redirectTo, window.location.href)
|
||||
url.searchParams.append("next", window.location.pathname)
|
||||
debug("[BlitzAuthInnerRoot] redirecting to", url.toString())
|
||||
const error = new RedirectError(url.toString())
|
||||
error.stack = null!
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <Page {...props} />
|
||||
}
|
||||
|
||||
for (let [key, value] of Object.entries(Page)) {
|
||||
// @ts-ignore
|
||||
AuthRoot[key] = value
|
||||
}
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
AuthRoot.displayName = `BlitzAuthInnerRoot`
|
||||
}
|
||||
|
||||
return AuthRoot
|
||||
}
|
||||
|
||||
export interface AuthPluginClientOptions {
|
||||
cookiePrefix: string
|
||||
}
|
||||
|
||||
export const AuthClientPlugin = createClientPlugin((options: AuthPluginClientOptions) => {
|
||||
globalThis.__BLITZ_SESSION_COOKIE_PREFIX = options.cookiePrefix || "blitz"
|
||||
return {
|
||||
withProvider: withBlitzAuthPlugin,
|
||||
events: {},
|
||||
middleware: {},
|
||||
exports: () => ({
|
||||
useSession,
|
||||
useAuthorize,
|
||||
useAuthorizeIf,
|
||||
useRedirectAuthenticated,
|
||||
useAuthenticatedSession,
|
||||
getAntiCSRFToken,
|
||||
}),
|
||||
}
|
||||
})
|
||||
6
packages/blitz-auth/src/global.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import {SessionConfig} from "./shared/types"
|
||||
|
||||
declare global {
|
||||
var sessionConfig: SessionConfig
|
||||
var __BLITZ_SESSION_COOKIE_PREFIX: string | undefined
|
||||
}
|
||||
10
packages/blitz-auth/src/index-browser.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import "./global"
|
||||
|
||||
export * from "./client"
|
||||
export type {
|
||||
SessionContextBase,
|
||||
SessionContext,
|
||||
AuthenticatedSessionContext,
|
||||
ClientSession,
|
||||
AuthenticatedClientSession,
|
||||
} from "./shared/types"
|
||||