Compare commits
106 Commits
blitz@2.0.
...
v0.0.2-can
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b749884ddc | ||
|
|
0b18df1390 | ||
|
|
256387cbbd | ||
|
|
f80071913d | ||
|
|
c05afe8bf6 | ||
|
|
95713e627b | ||
|
|
4184a4fe5d | ||
|
|
5933b6189e | ||
|
|
2d545688cb | ||
|
|
c3dee2271e | ||
|
|
93865f4431 | ||
|
|
be1c57b345 | ||
|
|
5d22f9b2cc | ||
|
|
1ea4398216 | ||
|
|
bb93ed8843 | ||
|
|
e2eed221e0 | ||
|
|
6719104cb3 | ||
|
|
d103345b77 | ||
|
|
ae99dc4a55 | ||
|
|
4658f616f1 | ||
|
|
48861cbf25 | ||
|
|
c82de5cfd0 | ||
|
|
64185b884d | ||
|
|
9bdc4350f9 | ||
|
|
81735c4dec | ||
|
|
53eab985fd | ||
|
|
e16d66a4c5 | ||
|
|
bc3aa30929 | ||
|
|
9d8edb6ead | ||
|
|
25ff55291a | ||
|
|
2a9ac48a72 | ||
|
|
925098534f | ||
|
|
277c704be8 | ||
|
|
046b2ed300 | ||
|
|
ef579daf1c | ||
|
|
e44785bcff | ||
|
|
72697f25f9 | ||
|
|
fa6067eee7 | ||
|
|
6879be005e | ||
|
|
52e93a608b | ||
|
|
bce5a4bd37 | ||
|
|
1dad620368 | ||
|
|
f09b968e27 | ||
|
|
7f81d6291d | ||
|
|
f48a776e99 | ||
|
|
3e63287fa2 | ||
|
|
828c8d2f23 | ||
|
|
d8c2f696b1 | ||
|
|
4f21628365 | ||
|
|
0521b595fe | ||
|
|
f0159a05ae | ||
|
|
4a1e7e361f | ||
|
|
b1f620a579 | ||
|
|
dfbcb5bf67 | ||
|
|
0677a16e75 | ||
|
|
968b507570 | ||
|
|
e773b26f5f | ||
|
|
54e4759791 | ||
|
|
df0e5d3539 | ||
|
|
32944666f4 | ||
|
|
b5e0d7afed | ||
|
|
4bb86dc8b8 | ||
|
|
93a5fb057e | ||
|
|
3c044fd4d0 | ||
|
|
b430c87b65 | ||
|
|
903644b628 | ||
|
|
d26be24cb3 | ||
|
|
949e7eb83f | ||
|
|
97bb455cc4 | ||
|
|
cdb5ff2133 | ||
|
|
b1aee93e2d | ||
|
|
667566e341 | ||
|
|
c51443bf5d | ||
|
|
ab4a3d2748 | ||
|
|
6d6a689557 | ||
|
|
16c2031d2a | ||
|
|
590b20f12e | ||
|
|
c82c0b3689 | ||
|
|
18d38d79e7 | ||
|
|
4113124ec4 | ||
|
|
8f6d0e03ac | ||
|
|
c48fd8925b | ||
|
|
66af983955 | ||
|
|
55b735086c | ||
|
|
4e64784749 | ||
|
|
3ee2ef0b42 | ||
|
|
eaa6fc8802 | ||
|
|
ef6bf61c5b | ||
|
|
5f5b589a7f | ||
|
|
82ae27841c | ||
|
|
8623d5a817 | ||
|
|
250c49c7bd | ||
|
|
7739c3e951 | ||
|
|
8a7f7931f4 | ||
|
|
6579e85a96 | ||
|
|
1195f5225e | ||
|
|
9b206a9831 | ||
|
|
dae1db73e9 | ||
|
|
d63f59d2fa | ||
|
|
bbf9cb0d2b | ||
|
|
a44b1d93c1 | ||
|
|
3dab930a75 | ||
|
|
939fad20f6 | ||
|
|
2e06ef8637 | ||
|
|
12ab14bc57 | ||
|
|
45000493e0 |
219
.all-contributorsrc
Normal file
219
.all-contributorsrc
Normal file
@@ -0,0 +1,219 @@
|
||||
{
|
||||
"projectName": "blitz",
|
||||
"projectOwner": "blitz-js",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"imageSize": 100,
|
||||
"commit": true,
|
||||
"commitConvention": "none",
|
||||
"contributors": [
|
||||
{
|
||||
"login": "flybayer",
|
||||
"name": "Brandon Bayer",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/8813276?v=4",
|
||||
"profile": "https://twitter.com/flybayer",
|
||||
"contributions": [
|
||||
"code",
|
||||
"content",
|
||||
"ideas",
|
||||
"review"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "medelman17",
|
||||
"name": "Michael Edelman ",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/14793389?v=4",
|
||||
"profile": "https://fabulas.io",
|
||||
"contributions": [
|
||||
"infra"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "merelinguist",
|
||||
"name": "Dylan Brookes",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/24858006?v=4",
|
||||
"profile": "https://merelinguist.now.sh",
|
||||
"contributions": [
|
||||
"ideas",
|
||||
"review",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ryardley",
|
||||
"name": "Rudi Yardley",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/1256409?v=4",
|
||||
"profile": "https://medium.com/@ryardley",
|
||||
"contributions": [
|
||||
"code",
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "toddgeist",
|
||||
"name": "Todd Geist",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/316792?v=4",
|
||||
"profile": "http://www.geistinteractive.com",
|
||||
"contributions": [
|
||||
"financial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "quirk0o",
|
||||
"name": "Beata Obrok",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/5123725?v=4",
|
||||
"profile": "https://github.com/quirk0o",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "tsawan",
|
||||
"name": "Tahir Awan",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/3263082?v=4",
|
||||
"profile": "https://github.com/tsawan",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "camilo86",
|
||||
"name": "Camilo Gonzalez",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/2454632?v=4",
|
||||
"profile": "https://raluce.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dkempner",
|
||||
"name": "Daniel Kempner",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/2532112?v=4",
|
||||
"profile": "http://da.nielkempner.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "gielcobben",
|
||||
"name": "Giel",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/2663212?v=4",
|
||||
"profile": "http://gielcobben.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MrLeebo",
|
||||
"name": "Jeremy Liberman",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/2754163?v=4",
|
||||
"profile": "http://jeremyliberman.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jimthedev",
|
||||
"name": "Jim Cummins",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/108938?v=4",
|
||||
"profile": "https://jimthedev.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Kristina Matuška",
|
||||
"avatar_url": "https://media-exp1.licdn.com/dms/image/C5603AQHVPAjV21gw9g/profile-displayphoto-shrink_200_200/0?e=1591228800&v=beta&t=0MlbmiYhNvGv1xjLD_fOhOFjVDZ7ltNwfGNeJ4DHedQ",
|
||||
"profile": "http://kristinamatuska.com/",
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "robdrosenberg",
|
||||
"name": "Robert Rosenberg",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/20813991?v=4",
|
||||
"profile": "http://robdrosenberg.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jasonblalock",
|
||||
"name": "Jason Blalock",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/5899929?v=4",
|
||||
"profile": "https://github.com/jasonblalock",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "coreybrown89",
|
||||
"name": "Corey Brown",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/12791148?v=4",
|
||||
"profile": "https://corey-brown.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "aej11a",
|
||||
"name": "aej11a",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/10066422?v=4",
|
||||
"profile": "https://github.com/aej11a",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "marcoseoane",
|
||||
"name": "marcoseoane",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/28088807?v=4",
|
||||
"profile": "https://github.com/marcoseoane",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rishabhpoddar",
|
||||
"name": "Rishabh Poddar",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/2976287?v=4",
|
||||
"profile": "https://github.com/rishabhpoddar",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "aem",
|
||||
"name": "Adam Markon",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/1909883?v=4",
|
||||
"profile": "https://github.com/aem",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lorenzorapetti",
|
||||
"name": "Lorenzo Rapetti",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/2632174?v=4",
|
||||
"profile": "https://github.com/lorenzorapetti",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "wKovacs64",
|
||||
"name": "Justin Hall",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/1288694?v=4",
|
||||
"profile": "https://github.com/wKovacs64",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
"skipCi": true
|
||||
}
|
||||
5
.github/CODEOWNERS
vendored
Normal file
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
|
||||
|
||||
packages/server/* @ryardley
|
||||
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
github: blitz-js
|
||||
custom: ['https://paypal.me/thebayers']
|
||||
open_collective: blitzjs
|
||||
patreon: flybayer
|
||||
41
.github/workflows/main.yml
vendored
Normal file
41
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build & Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.16.1'
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Cache Node.js modules
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --silent
|
||||
env:
|
||||
CI: true
|
||||
- name: Test Blitz Packages
|
||||
run: yarn test
|
||||
env:
|
||||
CI: true
|
||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
.log
|
||||
.DS_Store
|
||||
.jest-*
|
||||
lib
|
||||
node_modules
|
||||
reports
|
||||
*.log
|
||||
**/.env*
|
||||
.nyc_output
|
||||
**/coverage
|
||||
.yarn
|
||||
.yarnrc
|
||||
tsconfig.tsbuildinfo
|
||||
.blitz
|
||||
.next
|
||||
dist
|
||||
.now
|
||||
16
.npmignore
Normal file
16
.npmignore
Normal file
@@ -0,0 +1,16 @@
|
||||
.DS_Store
|
||||
.prettierrc
|
||||
.nyc_output
|
||||
.travis.yml
|
||||
coverage
|
||||
coverage.lcov
|
||||
bench
|
||||
docs
|
||||
src
|
||||
examples
|
||||
babel.config.js
|
||||
test
|
||||
CONTRIBUTING.md
|
||||
CODE_OF_CONDUCT.md
|
||||
*.ts
|
||||
!*.d.ts
|
||||
18
.prettierignore
Normal file
18
.prettierignore
Normal file
@@ -0,0 +1,18 @@
|
||||
**/migrations/**
|
||||
.blitz
|
||||
.next
|
||||
.log
|
||||
.DS_Store
|
||||
.jest-*
|
||||
lib
|
||||
node_modules
|
||||
reports
|
||||
*.log
|
||||
**/.env*
|
||||
.nyc_output
|
||||
**/coverage
|
||||
.yarn
|
||||
.yarnrc
|
||||
tsconfig.tsbuildinfo
|
||||
dist
|
||||
bin
|
||||
60
CODE_OF_CONDUCT.md
Normal file
60
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# The Blitz Community Code of Conduct
|
||||
|
||||
The Blitz core members take this CoC very serious. All members, contributors and volunteers in this community are required to act according to the following Code of Conduct to keep Blitz a positive, growing project and community and help us provide and ensure a safe environment for everyone.
|
||||
|
||||
The Blitz community
|
||||
|
||||
## When Something Happens
|
||||
|
||||
If you see a Code of Conduct violation, follow these steps:
|
||||
|
||||
1. Let the person know that what they did is not appropriate and ask them to stop and/or edit their message(s).
|
||||
2. That person should immediately stop the behavior and correct the issue.
|
||||
3. If this doesn’t happen, or if you’re uncomfortable speaking up, contact Brandon Bayer ([Twitter](https://twitter.com/flybayer) | [Email](mailto:b@bayer.ws)).
|
||||
|
||||
When reporting, please include any relevant details, links, screenshots, context, or other information that may be used to better understand and resolve the situation.
|
||||
|
||||
The core members will prioritize the well-being and comfort of the recipients of the violation over the comfort of the violator.
|
||||
|
||||
## What We Believe and How We Act
|
||||
|
||||
- We are committed to providing a friendly, safe and welcoming environment for everyone, regardless of age, body size, culture, ethnicity, gender expression, gender identity, level of experience, nationality, personal ability or disability, physical appearance, physical or mental difference, race, religion, set of skills, sexual orientation, socio-economic status, and subculture. We welcome people regardless of these or other attributes.
|
||||
- We are better together. We are more alike than different.
|
||||
- Our community is based on mutual respect, tolerance, and encouragement.
|
||||
- We believe that a diverse community where people treat each other with respect is stronger, more vibrant and has more potential contributors and more sources for ideas. We aim for more diversity.
|
||||
- We are kind, welcoming and courteous to everyone.
|
||||
- We’re respectful of others, their positions, their skills, their commitments and their efforts.
|
||||
- We’re attentive in our communications, whether in person or online, and we’re tactful when approaching differing views.
|
||||
- We are aware that language shapes reality. Thus, we use inclusive, gender-neutral language in the documents we provide and when we talk to people. When referring to a group of people, we aim to use gender-neutral terms like “team”, “folks”, “everyone”. (For details, we recommend [this post](https://modelviewculture.com/pieces/gendered-language-feature-or-bug-in-software-documentation)).
|
||||
- We respect that people have differences of opinion and criticize constructively.
|
||||
- We value people over code.
|
||||
|
||||
## Don'ts
|
||||
|
||||
- Don’t discriminate against anyone.
|
||||
- Sexism and racism of any kind (including sexist and racist “jokes”), demeaning or insulting behaviour and harassment are seen as direct violations to this Code of Conduct. Harassment includes offensive verbal comments related to age, body size, culture, ethnicity, gender expression, gender identity, level of experience, nationality, personal ability or disability, physical appearance, physical or mental difference, race, religion, set of skills, sexual orientation, socio-economic status, and subculture. Harassment also includes sexual images in public spaces, deliberate intimidation, stalking, following, harassing photography or recording, inappropriate physical contact, and unwelcome sexual attention.
|
||||
- On Slack and other online or offline communications channels, don't use overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all.
|
||||
- Don’t be mean or rude.
|
||||
- Respect that some individuals and cultures consider the casual use of profanity offensive and off-putting.
|
||||
- Unwelcome / non-consensual sexual advances over Slack or any other channels related with this community are not okay.
|
||||
- Derailing, tone arguments and otherwise playing on people’s desires to be nice are not welcome, especially in discussions about violations to this Code of Conduct.
|
||||
- Please avoid unstructured critique.
|
||||
- Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
|
||||
- Sponsors of Blitz are also subject to this Code of Conduct. In particular, sponsors are required to not use sexualized images, activities, or other material which is not according to this Code of Conduct.
|
||||
|
||||
## Consequences for Violations to this Code of Conduct
|
||||
|
||||
If a participant engages in any behavior violating this Code of Conduct, the core members of this community will take any action they deem appropriate, starting with a gentle warning and then escalating as needed to expulsion from the community, exclusion from any interaction and loss of all rights in this community.
|
||||
|
||||
## Decisions About Consequences of Violations
|
||||
|
||||
Decisions about consequences of violations of this Code of Conduct are made by this community’s core members and may not be discussed with the person responsible for the violation.
|
||||
|
||||
## For Questions or Feedback
|
||||
|
||||
If you have any questions or feedback on this Code of Conduct, we’re happy to hear from you.
|
||||
|
||||
## Thanks for the Inspiration To
|
||||
|
||||
- [Hood.ie](http://hood.ie/code-of-conduct/)
|
||||
- [WeAllJS](https://wealljs.org/code-of-conduct)
|
||||
65
CONTRIBUTING.md
Normal file
65
CONTRIBUTING.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# How to Contribute to Blitz.js
|
||||
|
||||
We're so excited you're interested in helping with Blitz! We happy to help you get started, even if you don't have any previous open-source experience :)
|
||||
|
||||
### First Things First
|
||||
|
||||
1. Familiarize yourself with the [Blitz Code of Conduct](https://github.com/blitz-js/blitz/blob/canary/CODE_OF_CONDUCT.md)
|
||||
2. Join the [Blitz Slack Community](https://slack.blitzjs.com)
|
||||
3. Install the [Zenhub browser extension](https://www.zenhub.com/extension)
|
||||
4. View open issues and their progress [on the Zenhub repo tab](https://github.com/blitz-js/blitz#zenhub)
|
||||
|
||||
### What to Work On?
|
||||
|
||||
Issues with the label `ready to work on | help wanted` are the best place to start. If you find one that looks interesting and no one else is already working on it, comment in the issue that you are going to work on it. Please ask as many questions as you need, either directly in the issue or in Slack. We're happy to help!
|
||||
|
||||
After you contribute in any way, please add yourself as a contributor via the [@all-contributors bot](https://allcontributors.org/docs/en/bot/usage)!
|
||||
|
||||
## Development Setup
|
||||
|
||||
#### Repo Setup
|
||||
|
||||
**1.** Clone the repo
|
||||
|
||||
```
|
||||
git clone git@github.com:blitz-js/blitz.git
|
||||
cd blitz
|
||||
```
|
||||
|
||||
**2.** Install dependencies
|
||||
|
||||
```
|
||||
yarn
|
||||
```
|
||||
|
||||
**3.** Start the package server. This must be running for any package development or example development
|
||||
|
||||
```
|
||||
yarn dev
|
||||
```
|
||||
|
||||
#### Develop a Blitz `package`
|
||||
|
||||
**1.** Change to a package directory
|
||||
|
||||
```
|
||||
cd packages/core
|
||||
```
|
||||
|
||||
**2.** Start the test runner
|
||||
|
||||
```
|
||||
yarn test:watch
|
||||
```
|
||||
|
||||
#### Run a Blitz `example`
|
||||
|
||||
**NOTE:** There are currently no examples for the new architecture in the pending RFC.
|
||||
|
||||
**1.** Change to an example directory
|
||||
|
||||
```
|
||||
cd examples/first-demo
|
||||
```
|
||||
|
||||
**2.** Follow instructions in the example's README
|
||||
76
MANIFESTO.md
Normal file
76
MANIFESTO.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# The Blitz.js Manifesto
|
||||
|
||||
## Background
|
||||
|
||||
Technology follows a repeating cycle of bundling and unbundling. Created in 2005, Ruby on Rails became a major bundling force. It made web application development easier and more accessible than ever before. This benefited everyone, from those learning programming to seniors building production systems.
|
||||
|
||||
A major unbundling happened in 2013 with the release of React because it is hyper focused on the rendering layer. As React grew in popularity, so did the choices for all the other parts, leaving developers with hundreds of decisions to make when starting a new app. While this has contributed to JavaScript Fatigue, it's been a powerful driving force for rapid frontend innovation.
|
||||
|
||||
Now, in 2020, is the perfect time for another major bundling. Developers are yearning for an easier, simpler way to build web applications. Beginners want a guiding hand for building a robust app. And seniors want a framework that removes mundane tasks and provides a highly scalable architecture.
|
||||
|
||||
Hence the creation of Blitz.
|
||||
|
||||
## What is Blitz For?
|
||||
|
||||
Blitz is for building tiny to large fullstack database-backed applications that have one or more graphical user interfaces like web or mobile apps.
|
||||
|
||||
## 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
|
||||
|
||||
### 1. Fullstack & Monolithic
|
||||
|
||||
A fullstack, monolithic application is simpler than an application where frontend and backend are developed and deployed separately. Monolithic doesn't mean it will be slow or hard to scale to large teams. Monolithic doesn't mean there isn't separation of concerns. Monolithic means you can reason about your app as a single entity.
|
||||
|
||||
### 2. API Not Required
|
||||
|
||||
Until now, choosing React for your view layer required you to have a REST or GraphQL API even if it wasn't used by third-parties. This additional complexity is a significant drawback not shared by traditional server rendered apps like Ruby on Rails.
|
||||
|
||||
Contrary to popular opinion, most apps don't need an API! If you are building a unicorn startup, then yes you'll need an API at some point. But unicorn startups are 1% of all applications. Most applications are much smaller, faithfully serving a constrained set of use cases for small to medium businesses that don't need Internet Scale.
|
||||
|
||||
This is akin to the microservices vs monolith debate. Huge companies are absolutely going to need a more microservice oriented architecture, but the vast majority of apps are much better served by a single monolith.
|
||||
|
||||
### 3. Convention over Configuration
|
||||
|
||||
Starting a new fullstack React app is currently too hard. You have to spend days on things like configuring eslint, prettier, husky, jest, cypress, typescript, deciding on a file structure, setting up a database, adding authentication and authorization, setting up a router, defining routing conventions, and setting up your styling library.
|
||||
|
||||
Blitz will make as many decisions and do as much work for you as possible. This makes it lightning fast to start real development. It also greatly benefits the community. Common project structure and architectural patterns make it easy to move from Blitz app to Blitz app and immediately feel at home.
|
||||
|
||||
Convention over configuration doesn't mean no configuration. It means configuration is optional. Blitz will provide all the escape hatches you need for bespoke customization.
|
||||
|
||||
### 4. Loose Opinions
|
||||
|
||||
Blitz is opinionated. The out-of-the-box experience guides you on a path perfect for most applications. However, Blitz isn't arrogant. It understands there are very good reasons for deviating from convention, and it allows you to do so. For example, Blitz has a conventional file structure, but, with few exceptions, doesn't _enforce_ it.
|
||||
|
||||
And when there's not community consensus, `blitz new` will prompt you to choose.
|
||||
|
||||
### 5. Easy to Start, Easy to Scale
|
||||
|
||||
A framework that's only easy for one end of an application lifecycle is not a good framework. Both starting and scaling must be easy.
|
||||
|
||||
Easy to start includes being easy for beginners and being easy to migrate existing Next.js apps to Blitz.
|
||||
|
||||
Scaling is important in all forms: lines of code, number of people working in the codebase, and code execution.
|
||||
|
||||
### 6. Stability
|
||||
|
||||
In the fast-paced world of Javascript, a stable, predictable release cycle is a breath of fresh air. A stable release cycle ensures minimal breaking changes, and it ensures you know exactly what and when a breaking change will occur. It also minimizes bugs in stable releases by ensuring features are in beta for a minimum amount of time. [Ember is the model citizen](https://emberjs.com/releases/) in this regard.
|
||||
|
||||
The exact details of the Blitz release cycle are to be determined, but we'll follow a pattern similar to Ember which strictly follows SemVer with stable releases every 6 weeks and LTS releases every 6 months.
|
||||
|
||||
Blitz will follow a public RFC (request for comments) process so all users and companies can participate in proposing and evaluating new features.
|
||||
|
||||
If a Blitz API needs to be removed, it will be deprecated in a minor release. Major releases will simply remove APIs already deprecated in a previous release.
|
||||
|
||||
### 7. Community over Code
|
||||
|
||||
The Blitz community is the most important aspect of the framework, by far.
|
||||
We have a comprehensive [Code of Conduct](https://github.com/blitz-js/blitz/blob/canary/CODE_OF_CONDUCT.md). LGBTQ+, women, and minorities are especially welcome.
|
||||
|
||||
We are all in this together, from the youngest to the oldest. We are all more similar than we are different. We can and should solve problems together. We should learn from other communities, not compete against them.
|
||||
100
README.md
100
README.md
@@ -1,2 +1,98 @@
|
||||
# blitz
|
||||
Framework for building monolithic, full-stack, serverless React apps with zero data-fetching and zero client-side state management
|
||||
# Blitz.js ⚡️
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[](#contributors-)
|
||||
[](https://actions-badge.atrox.dev/blitz-js/blitz/goto?ref=canary)
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
Blitz is a Rails-like framework for monolithic, full-stack React apps without an API.
|
||||
|
||||
The central thesis is that most apps don’t need a REST or GraphQL API. Blitz brings back the simplicity of server rendered frameworks like Ruby on Rails while preserving everything we love about React.
|
||||
|
||||
Additionally, Blitz is bringing other Rails goodness that’s missing in the React ecosystem like file structure and routing conventions, a really nice console REPL, intelligent code-scaffolding, and a fine-tuned out-of-the-box setup with Prettier, Typescript, ESlint, Jest, Cypress, etc.
|
||||
|
||||
### What is Blitz Designed For?
|
||||
|
||||
Blitz is designed for tiny to large database-backed applications that have one or more graphical user interfaces.
|
||||
|
||||
Web support will be released first, followed by React Native. We are pursuing the dream of a single monolithic application that runs on web and mobile with maximum code sharing and minimal boilerplate.
|
||||
|
||||
### What are 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://github.com/blitz-js/blitz/blob/canary/MANIFESTO.md) explains these principles in detail.
|
||||
|
||||
### 👉 [View the Architecture RFC](https://github.com/blitz-js/blitz/pull/73) for exact details on what a Blitz app looks like 👈
|
||||
|
||||
---
|
||||
|
||||
## 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://github.com/blitz-js/blitz/blob/canary/CODE_OF_CONDUCT.md).
|
||||
|
||||
### You are invited to join us — let’s build the future of web dev together! 🤝
|
||||
|
||||
1. [Join the Blitz Slack Community](https://slack.blitzjs.com)
|
||||
2. If you're interested in helping, read [The Contributing Guide](CONTRIBUTING.md)
|
||||
|
||||
## Sponsors and Donations
|
||||
|
||||
- Contribute via [GitHub Sponsors](https://github.com/sponsors/blitz-js)
|
||||
- Contribute via [PayPal](https://paypal.me/thebayers)
|
||||
- Contribute via [Open Collective](https://opencollective.com/blitzjs)
|
||||
- Contribute via [Patreon](https://patreon.com/flybayer)
|
||||
|
||||
_Sponsor Blitz and display your logo and hiring status here. This is a great way to get in front of early adopters! [See options on Open Collective](https://opencollective.com/blitzjs)_
|
||||
|
||||
## 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" 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></td>
|
||||
<td align="center"><a href="https://fabulas.io"><img src="https://avatars1.githubusercontent.com/u/14793389?v=4" width="100px;" alt=""/><br /><sub><b>Michael Edelman </b></sub></a><br /><a href="#infra-medelman17" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center"><a href="https://merelinguist.now.sh"><img src="https://avatars3.githubusercontent.com/u/24858006?v=4" width="100px;" alt=""/><br /><sub><b>Dylan Brookes</b></sub></a><br /><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="Code">💻</a></td>
|
||||
<td align="center"><a href="https://medium.com/@ryardley"><img src="https://avatars0.githubusercontent.com/u/1256409?v=4" 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></td>
|
||||
<td align="center"><a href="http://www.geistinteractive.com"><img src="https://avatars2.githubusercontent.com/u/316792?v=4" width="100px;" alt=""/><br /><sub><b>Todd Geist</b></sub></a><br /><a href="#financial-toddgeist" title="Financial">💵</a></td>
|
||||
<td align="center"><a href="https://github.com/quirk0o"><img src="https://avatars3.githubusercontent.com/u/5123725?v=4" 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" 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>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://raluce.com"><img src="https://avatars1.githubusercontent.com/u/2454632?v=4" 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" 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>
|
||||
<td align="center"><a href="http://gielcobben.com"><img src="https://avatars0.githubusercontent.com/u/2663212?v=4" 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" 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></td>
|
||||
<td align="center"><a href="https://jimthedev.com"><img src="https://avatars0.githubusercontent.com/u/108938?v=4" 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" 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="http://robdrosenberg.com"><img src="https://avatars0.githubusercontent.com/u/20813991?v=4" 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></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/jasonblalock"><img src="https://avatars0.githubusercontent.com/u/5899929?v=4" 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://corey-brown.com"><img src="https://avatars1.githubusercontent.com/u/12791148?v=4" 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></td>
|
||||
<td align="center"><a href="https://github.com/aej11a"><img src="https://avatars2.githubusercontent.com/u/10066422?v=4" 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" width="100px;" alt=""/><br /><sub><b>marcoseoane</b></sub></a><br /><a href="#ideas-marcoseoane" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://github.com/rishabhpoddar"><img src="https://avatars2.githubusercontent.com/u/2976287?v=4" 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/aem"><img src="https://avatars0.githubusercontent.com/u/1909883?v=4" width="100px;" alt=""/><br /><sub><b>Adam Markon</b></sub></a><br /><a href="#ideas-aem" 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" 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>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/wKovacs64"><img src="https://avatars1.githubusercontent.com/u/1288694?v=4" 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></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- 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!
|
||||
|
||||
3
__mocks__/fs.js
Normal file
3
__mocks__/fs.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const {fs} = require('memfs')
|
||||
|
||||
module.exports = fs
|
||||
33
examples/store/.gitignore
vendored
Normal file
33
examples/store/.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
/.blitz/
|
||||
*.sqlite
|
||||
|
||||
# production
|
||||
/build
|
||||
.now
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
22
examples/store/README.md
Normal file
22
examples/store/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
## Getting Started
|
||||
|
||||
1. Ensure you have Postgres installed locally
|
||||
2. Set the `DATABASE_URL` environment variable, something like this:
|
||||
|
||||
```
|
||||
DATABASE_URL=postgresql://<your_computer_username>@localhost:5432/blitz-example-store
|
||||
```
|
||||
|
||||
3. DB migrate
|
||||
|
||||
```
|
||||
yarn blitz db migrate
|
||||
```
|
||||
|
||||
4. Start the dev server
|
||||
|
||||
```
|
||||
yarn blitz start
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
16
examples/store/app/admin/pages/admin/index.tsx
Normal file
16
examples/store/app/admin/pages/admin/index.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Link} from '@blitzjs/core'
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<div>
|
||||
<h1>Store Admin</h1>
|
||||
<div>
|
||||
<p>
|
||||
<Link href="/admin/products">
|
||||
<a>Manage Products</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
30
examples/store/app/admin/pages/admin/products/[id].tsx
Normal file
30
examples/store/app/admin/pages/admin/products/[id].tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import {Suspense} from 'react'
|
||||
import {Link, useRouter, useQuery} from '@blitzjs/core'
|
||||
import getProduct from 'app/products/queries/getProduct'
|
||||
import ProductForm from 'app/products/components/ProductForm'
|
||||
|
||||
function Product() {
|
||||
const router = useRouter()
|
||||
const id = parseInt(router.query.id as string)
|
||||
const [product] = useQuery(getProduct, {where: {id}})
|
||||
|
||||
return <ProductForm product={product} onSuccess={() => router.push('/admin/products')} />
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<div>
|
||||
<h1>Edit Product</h1>
|
||||
<p>
|
||||
<Link href="/admin/products">
|
||||
<a>Manage Products</a>
|
||||
</Link>
|
||||
</p>
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Product />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
40
examples/store/app/admin/pages/admin/products/index.tsx
Normal file
40
examples/store/app/admin/pages/admin/products/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import {Suspense} from 'react'
|
||||
import {useQuery, Link} from '@blitzjs/core'
|
||||
import getProducts from 'app/products/queries/getProducts'
|
||||
|
||||
function ProductsList() {
|
||||
const [products] = useQuery(getProducts)
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{products.map((product) => (
|
||||
<li key={product.id}>
|
||||
<Link href="/admin/products/[id]" as={`/admin/products/${product.id}`}>
|
||||
<a>{product.name}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<div>
|
||||
<h1>Products</h1>
|
||||
|
||||
<p>
|
||||
<Link href="/admin/products/new">
|
||||
<a>Create Product</a>
|
||||
</Link>
|
||||
<Link href="/admin">
|
||||
<a style={{marginLeft: 16}}>Admin</a>
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<ProductsList />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
19
examples/store/app/admin/pages/admin/products/new.tsx
Normal file
19
examples/store/app/admin/pages/admin/products/new.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import {Link, useRouter} from '@blitzjs/core'
|
||||
import ProductForm from 'app/products/components/ProductForm'
|
||||
|
||||
export default function () {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div>
|
||||
<h1>Create a New Product</h1>
|
||||
<p>
|
||||
<Link href="/admin/products">
|
||||
<a>Manage Products</a>
|
||||
</Link>
|
||||
</p>
|
||||
<div>
|
||||
<ProductForm onSuccess={() => router.push('/admin/products')} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
19
examples/store/app/components/ErrorBoundary.tsx
Normal file
19
examples/store/app/components/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
|
||||
export default class ErrorBoundary extends React.Component<{fallback: (error: any) => React.ReactNode}> {
|
||||
state = {hasError: false, error: null}
|
||||
|
||||
static getDerivedStateFromError(error: any) {
|
||||
return {
|
||||
hasError: true,
|
||||
error,
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback(this.state.error)
|
||||
}
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
15
examples/store/app/pages/_app.tsx
Normal file
15
examples/store/app/pages/_app.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import ErrorBoundary from 'app/components/ErrorBoundary'
|
||||
|
||||
export default function MyApp({Component, pageProps}) {
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallback={(error) => (
|
||||
<div>
|
||||
<h1>Unhandled Error</h1>
|
||||
<pre>{JSON.stringify(error, null, 2)}</pre>
|
||||
</div>
|
||||
)}>
|
||||
<Component {...pageProps} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
174
examples/store/app/pages/index.tsx
Normal file
174
examples/store/app/pages/index.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
import Head from 'next/head'
|
||||
import {Link} from '@blitzjs/core'
|
||||
|
||||
const Home = () => (
|
||||
<div className="container">
|
||||
<Head>
|
||||
<title>Blitz Example Store</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<main>
|
||||
<h1 className="title" style={{marginBottom: 24}}>
|
||||
Blitz Store Example
|
||||
</h1>
|
||||
<p>
|
||||
<Link href="/products">
|
||||
<a>View Static Public Product Listings</a>
|
||||
</Link>
|
||||
</p>
|
||||
<p>
|
||||
<Link href="/admin">
|
||||
<a>View Dynamic Admin Section</a>
|
||||
</Link>
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<a
|
||||
href="https://zeit.co?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Powered by <img src="/zeit.svg" alt="ZEIT Logo" />
|
||||
</a>
|
||||
</footer>
|
||||
|
||||
<style jsx>{`
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
padding: 0 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 5rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
border-top: 1px solid #eaeaea;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
footer img {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.title a:hover,
|
||||
.title a:focus,
|
||||
.title a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.title,
|
||||
.description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
line-height: 1.5;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
max-width: 800px;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1rem;
|
||||
flex-basis: 45%;
|
||||
padding: 1.5rem;
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 10px;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus,
|
||||
.card:active {
|
||||
color: #0070f3;
|
||||
border-color: #0070f3;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<style jsx global>{`
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans,
|
||||
Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Home
|
||||
74
examples/store/app/products/components/ProductForm.tsx
Normal file
74
examples/store/app/products/components/ProductForm.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import {Form, Field} from 'react-final-form'
|
||||
import {Product, ProductCreateInput, ProductUpdateInput} from 'db'
|
||||
import createProduct from 'app/products/mutations/createProduct'
|
||||
import updateProduct from 'app/products/mutations/updateProduct'
|
||||
|
||||
type ProductInput = ProductCreateInput | ProductUpdateInput
|
||||
|
||||
function isNew(product: ProductInput): product is ProductCreateInput {
|
||||
return (product as ProductUpdateInput).id === undefined
|
||||
}
|
||||
|
||||
type ProductFormProps = {
|
||||
product?: ProductUpdateInput
|
||||
style?: React.CSSProperties
|
||||
onSuccess: (product: Product) => any
|
||||
}
|
||||
|
||||
export default function ({product, style, onSuccess, ...props}: ProductFormProps) {
|
||||
return (
|
||||
<Form
|
||||
initialValues={product || {name: null, handle: null, description: null, price: null}}
|
||||
onSubmit={async (data: ProductInput) => {
|
||||
if (isNew(data)) {
|
||||
try {
|
||||
const product = await createProduct({data})
|
||||
onSuccess(product)
|
||||
} catch (error) {
|
||||
alert('Error creating product ' + JSON.stringify(error, null, 2))
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const product = await updateProduct({where: {id: data.id}, data})
|
||||
onSuccess(product)
|
||||
} catch (error) {
|
||||
alert('Error updating product ' + JSON.stringify(error, null, 2))
|
||||
}
|
||||
}
|
||||
}}
|
||||
render={({handleSubmit}) => (
|
||||
<form onSubmit={handleSubmit} style={{maxWidth: 400, ...style}} {...props}>
|
||||
<div style={{marginBottom: 16}}>
|
||||
<label style={{display: 'flex', flexDirection: 'column'}}>
|
||||
Product Name
|
||||
<Field name="name" component="input" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 16}}>
|
||||
<label style={{display: 'flex', flexDirection: 'column'}}>
|
||||
Handle
|
||||
<Field name="handle" component="input" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 16}}>
|
||||
<label style={{display: 'flex', flexDirection: 'column'}}>
|
||||
Description
|
||||
<Field name="description" component="textarea" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 16}}>
|
||||
<label style={{display: 'flex', flexDirection: 'column'}}>
|
||||
Price
|
||||
<Field name="price" component="input" parse={(value) => (value ? parseInt(value) : null)} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button>{product ? 'Update' : 'Create'} Product</button>
|
||||
</form>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
7
examples/store/app/products/mutations/createProduct.ts
Normal file
7
examples/store/app/products/mutations/createProduct.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import db, {ProductCreateArgs} from 'db'
|
||||
|
||||
export default async function createProduct(args: ProductCreateArgs) {
|
||||
const product = await db.product.create(args)
|
||||
|
||||
return product
|
||||
}
|
||||
7
examples/store/app/products/mutations/deleteProduct.ts
Normal file
7
examples/store/app/products/mutations/deleteProduct.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import db, {ProductDeleteArgs} from 'db'
|
||||
|
||||
export default async function deleteProduct(args: ProductDeleteArgs) {
|
||||
const product = await db.product.delete(args)
|
||||
|
||||
return product
|
||||
}
|
||||
10
examples/store/app/products/mutations/updateProduct.ts
Normal file
10
examples/store/app/products/mutations/updateProduct.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import db, {ProductUpdateArgs} from 'db'
|
||||
|
||||
export default async function updateProduct(args: ProductUpdateArgs) {
|
||||
// Don't allow updating ID
|
||||
delete args.data.id
|
||||
|
||||
const product = await db.product.update(args)
|
||||
|
||||
return product
|
||||
}
|
||||
43
examples/store/app/products/pages/products/[handle].tsx
Normal file
43
examples/store/app/products/pages/products/[handle].tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import {Link, BlitzPage, GetStaticProps, GetStaticPaths} from '@blitzjs/core'
|
||||
import getProduct from 'app/products/queries/getProduct'
|
||||
import getProducts from 'app/products/queries/getProducts'
|
||||
import {Product} from 'db'
|
||||
|
||||
type StaticProps = {
|
||||
product: Product
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps<StaticProps> = async (ctx) => {
|
||||
const product = await getProduct({where: {handle: ctx.params.handle as string}})
|
||||
|
||||
return {
|
||||
props: {product},
|
||||
revalidate: 1,
|
||||
}
|
||||
}
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
const paths = (await getProducts()).map(({handle}) => ({params: {handle}}))
|
||||
return {
|
||||
paths,
|
||||
fallback: true,
|
||||
}
|
||||
}
|
||||
|
||||
const Page: BlitzPage<StaticProps> = function ({product}) {
|
||||
if (!product) {
|
||||
return <div>Building Page...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{product.name}</h1>
|
||||
<p>{product.description}</p>
|
||||
<p>Price: ${product.price}</p>
|
||||
|
||||
<Link href="/products">
|
||||
<a>All Products</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Page
|
||||
34
examples/store/app/products/pages/products/index.tsx
Normal file
34
examples/store/app/products/pages/products/index.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import {Link, BlitzPage, GetStaticProps} from '@blitzjs/core'
|
||||
import getProducts from 'app/products/queries/getProducts'
|
||||
import {Product} from 'db'
|
||||
|
||||
type StaticProps = {
|
||||
products: Product[]
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps<StaticProps> = async () => {
|
||||
const products = await getProducts()
|
||||
|
||||
return {
|
||||
props: {products},
|
||||
revalidate: 1,
|
||||
}
|
||||
}
|
||||
|
||||
const Page: BlitzPage<StaticProps> = function ({products}) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Products</h1>
|
||||
<div>
|
||||
{products.map((product) => (
|
||||
<p key={product.id}>
|
||||
<Link href="/products/[handle]" as={`/products/${product.handle}`}>
|
||||
<a>{product.name}</a>
|
||||
</Link>
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Page
|
||||
7
examples/store/app/products/queries/getProduct.ts
Normal file
7
examples/store/app/products/queries/getProduct.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import db, {FindOneProductArgs} from 'db'
|
||||
|
||||
export default async function getProduct(args: FindOneProductArgs) {
|
||||
const product = await db.product.findOne(args)
|
||||
|
||||
return product
|
||||
}
|
||||
7
examples/store/app/products/queries/getProducts.ts
Normal file
7
examples/store/app/products/queries/getProducts.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import db, {FindManyProductArgs} from 'db'
|
||||
|
||||
export default async function getProducts(args?: FindManyProductArgs) {
|
||||
const products = await db.product.findMany(args)
|
||||
|
||||
return products
|
||||
}
|
||||
6
examples/store/db/index.ts
Normal file
6
examples/store/db/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import {PrismaClient} from '@prisma/client'
|
||||
export * from '@prisma/client'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export default prisma
|
||||
72
examples/store/db/migrations/20200414170933-init/README.md
Normal file
72
examples/store/db/migrations/20200414170933-init/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Migration `20200414170933-init`
|
||||
|
||||
This migration has been generated by Brandon Bayer at 4/14/2020, 5:09:33 PM.
|
||||
You can check out the [state of the schema](./schema.prisma) after the migration.
|
||||
|
||||
## Database Steps
|
||||
|
||||
```sql
|
||||
CREATE TABLE "public"."User" (
|
||||
"email" text NOT NULL ,
|
||||
"id" SERIAL,
|
||||
"name" text ,
|
||||
"role" text ,
|
||||
"storeId" integer ,
|
||||
PRIMARY KEY ("id")
|
||||
)
|
||||
|
||||
CREATE TABLE "public"."Product" (
|
||||
"description" text ,
|
||||
"handle" text NOT NULL ,
|
||||
"id" SERIAL,
|
||||
"name" text ,
|
||||
"price" integer ,
|
||||
PRIMARY KEY ("id")
|
||||
)
|
||||
|
||||
CREATE UNIQUE INDEX "User.email" ON "public"."User"("email")
|
||||
|
||||
CREATE UNIQUE INDEX "Product.handle" ON "public"."Product"("handle")
|
||||
```
|
||||
|
||||
## Changes
|
||||
|
||||
```diff
|
||||
diff --git schema.prisma schema.prisma
|
||||
migration ..20200414170933-init
|
||||
--- datamodel.dml
|
||||
+++ datamodel.dml
|
||||
@@ -1,0 +1,30 @@
|
||||
+// This is your Prisma schema file,
|
||||
+// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
+
|
||||
+datasource postgresql {
|
||||
+ provider = "postgresql"
|
||||
+ url = env("DATABASE_URL")
|
||||
+}
|
||||
+
|
||||
+generator client {
|
||||
+ provider = "prisma-client-js"
|
||||
+}
|
||||
+
|
||||
+
|
||||
+// --------------------------------------
|
||||
+
|
||||
+model User {
|
||||
+ id Int @default(autoincrement()) @id
|
||||
+ email String @unique
|
||||
+ name String?
|
||||
+ role String?
|
||||
+ storeId Int?
|
||||
+}
|
||||
+
|
||||
+model Product {
|
||||
+ id Int @default(autoincrement()) @id
|
||||
+ handle String @unique
|
||||
+ name String?
|
||||
+ description String?
|
||||
+ price Int?
|
||||
+}
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
datasource postgresql {
|
||||
provider = "postgresql"
|
||||
url = "***"
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
model User {
|
||||
id Int @default(autoincrement()) @id
|
||||
email String @unique
|
||||
name String?
|
||||
role String?
|
||||
storeId Int?
|
||||
}
|
||||
|
||||
model Product {
|
||||
id Int @default(autoincrement()) @id
|
||||
handle String @unique
|
||||
name String?
|
||||
description String?
|
||||
price Int?
|
||||
}
|
||||
199
examples/store/db/migrations/20200414170933-init/steps.json
Normal file
199
examples/store/db/migrations/20200414170933-init/steps.json
Normal file
@@ -0,0 +1,199 @@
|
||||
{
|
||||
"version": "0.3.14-fixed",
|
||||
"steps": [
|
||||
{
|
||||
"tag": "CreateSource",
|
||||
"source": "postgresql"
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Source",
|
||||
"source": "postgresql"
|
||||
},
|
||||
"argument": "provider",
|
||||
"value": "\"postgresql\""
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Source",
|
||||
"source": "postgresql"
|
||||
},
|
||||
"argument": "url",
|
||||
"value": "env(\"DATABASE_URL\")"
|
||||
},
|
||||
{
|
||||
"tag": "CreateModel",
|
||||
"model": "User"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "id",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "autoincrement()"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "email",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "User",
|
||||
"field": "email"
|
||||
},
|
||||
"directive": "unique"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "name",
|
||||
"type": "String",
|
||||
"arity": "Optional"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "role",
|
||||
"type": "String",
|
||||
"arity": "Optional"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "User",
|
||||
"field": "storeId",
|
||||
"type": "Int",
|
||||
"arity": "Optional"
|
||||
},
|
||||
{
|
||||
"tag": "CreateModel",
|
||||
"model": "Product"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Product",
|
||||
"field": "id",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Product",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Product",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "autoincrement()"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Product",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Product",
|
||||
"field": "handle",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Product",
|
||||
"field": "handle"
|
||||
},
|
||||
"directive": "unique"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Product",
|
||||
"field": "name",
|
||||
"type": "String",
|
||||
"arity": "Optional"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Product",
|
||||
"field": "description",
|
||||
"type": "String",
|
||||
"arity": "Optional"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Product",
|
||||
"field": "price",
|
||||
"type": "Int",
|
||||
"arity": "Optional"
|
||||
}
|
||||
]
|
||||
}
|
||||
6
examples/store/db/migrations/migrate.lock
Normal file
6
examples/store/db/migrations/migrate.lock
Normal file
@@ -0,0 +1,6 @@
|
||||
# IF THERE'S A GIT CONFLICT IN THIS FILE, DON'T SOLVE IT MANUALLY!
|
||||
# INSTEAD EXECUTE `prisma migrate fix`
|
||||
# Prisma Migrate lockfile v1
|
||||
# Read more about conflict resolution here: TODO
|
||||
|
||||
20200414170933-init
|
||||
30
examples/store/db/schema.prisma
Normal file
30
examples/store/db/schema.prisma
Normal file
@@ -0,0 +1,30 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
datasource postgresql {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
model User {
|
||||
id Int @default(autoincrement()) @id
|
||||
email String @unique
|
||||
name String?
|
||||
role String?
|
||||
storeId Int?
|
||||
}
|
||||
|
||||
model Product {
|
||||
id Int @default(autoincrement()) @id
|
||||
handle String @unique
|
||||
name String?
|
||||
description String?
|
||||
price Int?
|
||||
}
|
||||
3
examples/store/next.config.js
Normal file
3
examples/store/next.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const {withBlitz} = require('@blitzjs/server')
|
||||
|
||||
module.exports = withBlitz({})
|
||||
10
examples/store/now.json
Normal file
10
examples/store/now.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"env": {
|
||||
"DATABASE_URL": "@store-example-db"
|
||||
},
|
||||
"build": {
|
||||
"env": {
|
||||
"DATABASE_URL": "@store-example-db"
|
||||
}
|
||||
}
|
||||
}
|
||||
24
examples/store/package.json
Normal file
24
examples/store/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "store",
|
||||
"version": "0.0.2-canary.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "blitz db migrate && blitz build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/cli": "0.0.2-canary.0",
|
||||
"@blitzjs/core": "0.0.2-canary.0",
|
||||
"@blitzjs/server": "0.0.2-canary.0",
|
||||
"@prisma/cli": "2.0.0-beta.2",
|
||||
"@prisma/client": "2.0.0-beta.2",
|
||||
"final-form": "4.19.1",
|
||||
"react": "0.0.0-experimental-e5d06e34b",
|
||||
"react-dom": "0.0.0-experimental-e5d06e34b",
|
||||
"react-final-form": "6.4.0",
|
||||
"typescript": "3.8.3"
|
||||
},
|
||||
"NOTE": "Next.js dependency is only required for deploying to zeit, for now",
|
||||
"devDependencies": {
|
||||
"next": "9.3.4"
|
||||
}
|
||||
}
|
||||
BIN
examples/store/public/favicon.ico
Normal file
BIN
examples/store/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
10
examples/store/public/zeit.svg
Normal file
10
examples/store/public/zeit.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="82" height="16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="url(#prefix__paint0_linear)" d="M9.018 0l9.019 16H0L9.018 0z"/>
|
||||
<path fill="#333" fill-rule="evenodd" d="M51.634 12.028h-6.492V2.052h6.492v1.256H46.61v3.007h4.37V7.57h-4.37v3.202h5.024v1.255zm-14.063 0h-7.235v-1.096l5.342-7.624h-5.253V2.052h7.058v1.097l-5.342 7.623h5.43v1.256zm21.88 0h6.333v-1.256h-2.423V3.308h2.423V2.052h-6.332v1.256h2.441v7.465h-2.441v1.255zm18.22 0h-1.468v-8.72h-3.36V2.052h8.225v1.256H77.67v8.72z" clip-rule="evenodd"/>
|
||||
<defs>
|
||||
<linearGradient id="prefix__paint0_linear" x1="28.022" x2="16.189" y1="22.991" y2="8.569" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 794 B |
20
examples/store/tsconfig.json
Normal file
20
examples/store/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"baseUrl": "./",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
4
examples/vanilla-next/.gitignore
vendored
Normal file
4
examples/vanilla-next/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.next
|
||||
.blitz
|
||||
node_modules
|
||||
dist
|
||||
1
examples/vanilla-next/README.md
Normal file
1
examples/vanilla-next/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This is an example of a simple next app being run using the blitz cli
|
||||
3
examples/vanilla-next/app/posts/controller.ts
Normal file
3
examples/vanilla-next/app/posts/controller.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function greet(who) {
|
||||
return `Hello ${who}`
|
||||
}
|
||||
2
examples/vanilla-next/next-env.d.ts
vendored
Normal file
2
examples/vanilla-next/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
3
examples/vanilla-next/next.config.js
Normal file
3
examples/vanilla-next/next.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const {withBlitz} = require('@blitzjs/server')
|
||||
|
||||
module.exports = withBlitz({})
|
||||
20
examples/vanilla-next/package.json
Normal file
20
examples/vanilla-next/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": "0.0.2-canary.0",
|
||||
"name": "vanilla-next",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"author": "BlitzJS",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "blitz start",
|
||||
"build": "blitz build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/cli": "0.0.2-canary.0",
|
||||
"@blitzjs/core": "0.0.2-canary.0",
|
||||
"@blitzjs/server": "0.0.2-canary.0",
|
||||
"next": "^9.3.0",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1"
|
||||
}
|
||||
}
|
||||
13
examples/vanilla-next/pages/index.tsx
Normal file
13
examples/vanilla-next/pages/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<h1>
|
||||
I am home.{' '}
|
||||
<Link href="/posts/5">
|
||||
<a>Click here</a>
|
||||
</Link>
|
||||
</h1>
|
||||
)
|
||||
}
|
||||
17
examples/vanilla-next/pages/posts/[...id].tsx
Normal file
17
examples/vanilla-next/pages/posts/[...id].tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import {greet} from 'app/posts/controller'
|
||||
|
||||
// TEST: Typical server rendering from params example
|
||||
export const getServerSideProps = (ctx: any) => {
|
||||
const {res} = ctx
|
||||
|
||||
const stringId = ctx.query && (Array.isArray(ctx.query.id) ? ctx.query.id[0] : ctx.query.id)
|
||||
|
||||
const id = isNaN(parseInt(stringId)) ? null : parseInt(stringId)
|
||||
|
||||
if (res?.status) res?.status(200)
|
||||
return Promise.resolve({props: {id}})
|
||||
}
|
||||
|
||||
export default function Page({id}) {
|
||||
return <h1>{greet(`${id}`)}</h1>
|
||||
}
|
||||
34
examples/vanilla-next/tsconfig.json
Normal file
34
examples/vanilla-next/tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules", ".blitz"],
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"importHelpers": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"rootDir": "../../",
|
||||
"baseUrl": "./",
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"downlevelIteration": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"noImplicitAny": false
|
||||
}
|
||||
}
|
||||
16
lerna.json
Normal file
16
lerna.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "0.0.2-canary.0",
|
||||
"packages": ["packages/*"],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"command": {
|
||||
"version": {
|
||||
"exact": true
|
||||
},
|
||||
"publish": {
|
||||
"npmClient": "npm",
|
||||
"allowBranch": ["master", "canary"],
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
}
|
||||
}
|
||||
53
package.json
Normal file
53
package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"private": true,
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*",
|
||||
"examples/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"husky",
|
||||
"**/@prisma",
|
||||
"**/@prisma/**"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"yarn": "^1.19.1",
|
||||
"node": ">=12.16.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "lerna run start --scope @blitzjs/* --stream --parallel",
|
||||
"build": "lerna run build --scope @blitzjs/* --stream",
|
||||
"prepublishOnly": "lerna run build --scope @blitzjs/* && lerna run test --parallel",
|
||||
"pretest": "lerna run build --scope @blitzjs/*",
|
||||
"test": "lerna run test --parallel"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/jest": "^25.1.3",
|
||||
"@types/node": "^13.7.4",
|
||||
"cross-env": "^7.0.0",
|
||||
"debug": "^4.1.1",
|
||||
"husky": "^4.2.3",
|
||||
"jest": "24.9.0",
|
||||
"lerna": "^3.20.2",
|
||||
"lint-staged": "^10.0.8",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.0.4",
|
||||
"pretty-quick": "2.0.1",
|
||||
"ts-jest": "24.3.0",
|
||||
"tsdx": "^0.13.1",
|
||||
"tslib": "^1.10.0",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "pretty-quick --staged",
|
||||
"pre-push": "yarn test"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"jest": "24.9.0",
|
||||
"ts-jest": "24.3.0"
|
||||
}
|
||||
}
|
||||
13
packages/cli/.gitignore
vendored
Normal file
13
packages/cli/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
node_modules
|
||||
.rts2_cache_cjs
|
||||
.rts2_cache_esm
|
||||
.rts2_cache_umd
|
||||
.rts2_cache_system
|
||||
dist
|
||||
tmp
|
||||
.blitz
|
||||
|
||||
# good directory to use for testing app generation
|
||||
_app
|
||||
160
packages/cli/README.md
Normal file
160
packages/cli/README.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# blitz-cli
|
||||
|
||||
Blitz CLI
|
||||
|
||||
[](https://oclif.io)
|
||||
[](https://npmjs.org/package/blitz-cli)
|
||||
[](https://npmjs.org/package/blitz-cli)
|
||||
[](https://github.com/mabadir/blitz-cli/blob/master/package.json)
|
||||
|
||||
## Contributing
|
||||
|
||||
Run `yarn` from the monorepo root
|
||||
|
||||
**Run locally from this directory:**
|
||||
`yarn b [COMMAND]`
|
||||
|
||||
**Run tests:**
|
||||
`yarn test`
|
||||
|
||||
**Build package:**
|
||||
`yarn build`
|
||||
|
||||
<!-- toc -->
|
||||
* [blitz-cli](#blitz-cli)
|
||||
* [Usage](#usage)
|
||||
* [Commands](#commands)
|
||||
<!-- tocstop -->
|
||||
|
||||
# Usage
|
||||
|
||||
<!-- usage -->
|
||||
```sh-session
|
||||
$ npm install -g @blitzjs/cli
|
||||
$ blitz COMMAND
|
||||
running command...
|
||||
$ blitz (-v|--version|version)
|
||||
@blitzjs/cli/0.0.1-canary.1 darwin-x64 node-v12.16.1
|
||||
$ blitz --help [COMMAND]
|
||||
USAGE
|
||||
$ blitz COMMAND
|
||||
...
|
||||
```
|
||||
<!-- usagestop -->
|
||||
|
||||
# Commands
|
||||
|
||||
<!-- commands -->
|
||||
* [`blitz build`](#blitz-build)
|
||||
* [`blitz console`](#blitz-console)
|
||||
* [`blitz db COMMAND`](#blitz-db-command)
|
||||
* [`blitz help [COMMAND]`](#blitz-help-command)
|
||||
* [`blitz new [PATH]`](#blitz-new-path)
|
||||
* [`blitz start`](#blitz-start)
|
||||
* [`blitz test [WATCH]`](#blitz-test-watch)
|
||||
|
||||
## `blitz build`
|
||||
|
||||
Create a production build
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ blitz build
|
||||
|
||||
ALIASES
|
||||
$ blitz b
|
||||
```
|
||||
|
||||
## `blitz console`
|
||||
|
||||
Run project REPL
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ blitz console
|
||||
|
||||
ALIASES
|
||||
$ blitz c
|
||||
```
|
||||
|
||||
## `blitz db COMMAND`
|
||||
|
||||
Run project database commands
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ blitz db COMMAND
|
||||
|
||||
ARGUMENTS
|
||||
COMMAND Run specific db command
|
||||
```
|
||||
|
||||
_See code: [lib/commands/db.js](https://github.com/blitz-js/blitz/blob/v0.0.1-canary.1/lib/commands/db.js)_
|
||||
|
||||
## `blitz help [COMMAND]`
|
||||
|
||||
display help for blitz
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ blitz help [COMMAND]
|
||||
|
||||
ARGUMENTS
|
||||
COMMAND command to show help for
|
||||
|
||||
OPTIONS
|
||||
--all see all commands in CLI
|
||||
```
|
||||
|
||||
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v2.2.3/src/commands/help.ts)_
|
||||
|
||||
## `blitz new [PATH]`
|
||||
|
||||
Create a new Blitz project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ blitz new [PATH]
|
||||
|
||||
ARGUMENTS
|
||||
PATH path to the new project, defaults to the current directory
|
||||
|
||||
OPTIONS
|
||||
-h, --help show CLI help
|
||||
-t, --[no-]ts generate a TypeScript project
|
||||
--dry-run show what files will be created without writing them to disk
|
||||
--[no-]yarn use Yarn as the package manager
|
||||
```
|
||||
|
||||
## `blitz start`
|
||||
|
||||
Start a development server
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ blitz start
|
||||
|
||||
OPTIONS
|
||||
-p, --production Create and start a production server
|
||||
|
||||
ALIASES
|
||||
$ blitz s
|
||||
```
|
||||
|
||||
## `blitz test [WATCH]`
|
||||
|
||||
Run project tests
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ blitz test [WATCH]
|
||||
|
||||
ARGUMENTS
|
||||
WATCH Run test:watch
|
||||
|
||||
ALIASES
|
||||
$ blitz t
|
||||
```
|
||||
|
||||
_See code: [lib/commands/test.js](https://github.com/blitz-js/blitz/blob/v0.0.1-canary.1/lib/commands/test.js)_
|
||||
<!-- commandsstop -->
|
||||
3
packages/cli/bin/run
Executable file
3
packages/cli/bin/run
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('@oclif/command').run().then(require('@oclif/command/flush')).catch(require('@oclif/errors/handle'))
|
||||
3
packages/cli/bin/run.cmd
Normal file
3
packages/cli/bin/run.cmd
Normal file
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
|
||||
node "%~dp0\run" %*
|
||||
26
packages/cli/jest.config.js
Normal file
26
packages/cli/jest.config.js
Normal file
@@ -0,0 +1,26 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
moduleFileExtensions: ['ts', 'js', 'json'],
|
||||
coverageReporters: ['json', 'lcov', 'text', 'clover'],
|
||||
// collectCoverage: !!`Boolean(process.env.CI)`,
|
||||
collectCoverageFrom: ['src/**/*.ts'],
|
||||
coveragePathIgnorePatterns: ['/templates/'],
|
||||
modulePathIgnorePatterns: ['tmp', 'lib'],
|
||||
testPathIgnorePatterns: ['src/commands/test.ts'],
|
||||
// TODO enable threshold
|
||||
// coverageThreshold: {
|
||||
// global: {
|
||||
// branches: 100,
|
||||
// functions: 100,
|
||||
// lines: 100,
|
||||
// statements: 100,
|
||||
// },
|
||||
// },
|
||||
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsConfig: 'test/tsconfig.json',
|
||||
},
|
||||
},
|
||||
}
|
||||
76
packages/cli/package.json
Normal file
76
packages/cli/package.json
Normal file
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "@blitzjs/cli",
|
||||
"description": "Blitz CLI",
|
||||
"version": "0.0.2-canary.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"b": "./bin/run",
|
||||
"build": "rimraf lib && tsc && cp -r templates lib/templates",
|
||||
"test": "jest --coverage",
|
||||
"test:watch": "jest --watch"
|
||||
},
|
||||
"author": {
|
||||
"name": "Brandon Bayer",
|
||||
"email": "b@bayer.ws",
|
||||
"url": "https://twitter.com/flybayer"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"bin": {
|
||||
"blitz": "./bin/run"
|
||||
},
|
||||
"files": [
|
||||
"/bin",
|
||||
"/lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"@blitzjs/server": "0.0.2-canary.0",
|
||||
"@oclif/command": "^1.5.19",
|
||||
"@oclif/config": "^1.14.0",
|
||||
"@oclif/plugin-help": "^2.2.3",
|
||||
"@oclif/plugin-not-found": "^1.2.3",
|
||||
"chalk": "^4.0.0",
|
||||
"chokidar": "^3.3.1",
|
||||
"diff": "^4.0.2",
|
||||
"enquirer": "^2.3.4",
|
||||
"execa": "^4.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"has-yarn": "^2.1.0",
|
||||
"mem-fs": "^1.1.3",
|
||||
"mem-fs-editor": "^6.0.0",
|
||||
"vinyl": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@oclif/dev-cli": "^1.22.2",
|
||||
"@oclif/test": "^1.2.5",
|
||||
"@types/diff": "^4.0.2",
|
||||
"@types/fs-extra": "^8.1.0",
|
||||
"@types/mem-fs": "^1.1.2",
|
||||
"@types/mem-fs-editor": "^5.1.1",
|
||||
"@types/vinyl": "^2.0.4",
|
||||
"chai": "^4.2.0",
|
||||
"globby": "^11.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^8.6.2"
|
||||
},
|
||||
"oclif": {
|
||||
"commands": "./lib/src/commands",
|
||||
"bin": "blitz",
|
||||
"plugins": [
|
||||
"@oclif/plugin-help",
|
||||
"@oclif/plugin-not-found"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"yarn": "^1.19.1",
|
||||
"node": ">=12.16.1"
|
||||
},
|
||||
"keywords": [
|
||||
"blitz",
|
||||
"cli"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/blitz-js/blitz"
|
||||
},
|
||||
"gitHead": "6719104cb3e78948e7f06aa948ff72bbb84cb682"
|
||||
}
|
||||
8
packages/cli/src/command.ts
Normal file
8
packages/cli/src/command.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {Command as OclifCommand} from '@oclif/command'
|
||||
import Enquirer = require('enquirer')
|
||||
|
||||
abstract class Command extends OclifCommand {
|
||||
protected enquirer = new Enquirer()
|
||||
}
|
||||
|
||||
export default Command
|
||||
15
packages/cli/src/commands/build.ts
Normal file
15
packages/cli/src/commands/build.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {Command} from '@oclif/command'
|
||||
import {build} from '@blitzjs/server'
|
||||
|
||||
export default class Build extends Command {
|
||||
static description = 'Create a production build'
|
||||
static aliases = ['b']
|
||||
|
||||
async run() {
|
||||
const config = {
|
||||
rootFolder: process.cwd(),
|
||||
}
|
||||
|
||||
await build(config)
|
||||
}
|
||||
}
|
||||
70
packages/cli/src/commands/console.ts
Normal file
70
packages/cli/src/commands/console.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import {Command} from '@oclif/command'
|
||||
import * as REPL from 'repl'
|
||||
import {REPLCommand, REPLServer} from 'repl'
|
||||
import {watch} from 'chokidar'
|
||||
|
||||
import {loadDependencies} from '../utils/load-dependencies'
|
||||
import {BLITZ_MODULE_PATHS, loadBlitz} from '../utils/load-blitz'
|
||||
|
||||
export default class Console extends Command {
|
||||
static description = 'Run project REPL'
|
||||
static aliases = ['c']
|
||||
static message = `Welcome to Blitz.js v0.0.1
|
||||
Type ".help" for more information.`
|
||||
|
||||
static replOptions = {
|
||||
prompt: '⚡️>',
|
||||
useColors: true,
|
||||
}
|
||||
|
||||
static commands = {
|
||||
reload: {
|
||||
help: 'Reload all modules',
|
||||
action(this: REPLServer) {
|
||||
this.clearBufferedCommand()
|
||||
console.log('Reloading all modules...')
|
||||
Console.loadModules(this)
|
||||
this.displayPrompt()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
async run() {
|
||||
this.log(Console.message)
|
||||
|
||||
const repl = Console.initializeRepl()
|
||||
|
||||
const watchers = [
|
||||
watch('package.json').on('change', () => Console.loadDependencies(repl)),
|
||||
watch(BLITZ_MODULE_PATHS).on('all', () => Console.loadBlitz(repl)),
|
||||
]
|
||||
|
||||
repl
|
||||
.on('reset', () => Console.loadModules(repl))
|
||||
.on('exit', () => watchers.forEach((watcher) => watcher.close()))
|
||||
}
|
||||
|
||||
private static initializeRepl() {
|
||||
const repl = REPL.start(Console.replOptions)
|
||||
Console.defineCommands(repl, Console.commands)
|
||||
Console.loadModules(repl)
|
||||
return repl
|
||||
}
|
||||
|
||||
private static loadModules(repl: REPLServer) {
|
||||
// Console.loadDependencies(repl)
|
||||
Console.loadBlitz(repl)
|
||||
}
|
||||
|
||||
private static loadDependencies(repl: REPLServer) {
|
||||
Object.assign(repl.context, loadDependencies(process.cwd()))
|
||||
}
|
||||
|
||||
private static loadBlitz(repl: REPLServer) {
|
||||
Object.assign(repl.context, loadBlitz())
|
||||
}
|
||||
|
||||
private static defineCommands(repl: REPLServer, commands: Record<string, REPLCommand>) {
|
||||
Object.entries(commands).forEach(([keyword, cmd]) => repl.defineCommand(keyword, cmd))
|
||||
}
|
||||
}
|
||||
48
packages/cli/src/commands/db.ts
Normal file
48
packages/cli/src/commands/db.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import {platform} from 'os'
|
||||
import {spawn} from 'child_process'
|
||||
import {Command} from '@oclif/command'
|
||||
import * as path from 'path'
|
||||
|
||||
export default class Db extends Command {
|
||||
static description = 'Run project database commands'
|
||||
|
||||
static args = [
|
||||
{
|
||||
name: 'command',
|
||||
description: 'Run specific db command',
|
||||
required: true,
|
||||
},
|
||||
]
|
||||
|
||||
async run() {
|
||||
const {args} = this.parse(Db)
|
||||
const command = args['command']
|
||||
|
||||
const prismaBinaryName = platform() === 'win32' ? 'prisma.cmd' : 'prisma'
|
||||
const prismaBinary = path.join(process.cwd(), 'node_modules/.bin', prismaBinaryName)
|
||||
|
||||
const schemaArg = `--schema=${path.join(process.cwd(), 'db', 'schema.prisma')}`
|
||||
|
||||
if (command === 'migrate' || command === 'm') {
|
||||
const cp = spawn(prismaBinary, ['migrate', 'save', schemaArg, '--create-db', '--experimental'], {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
cp.on('exit', (code: number) => {
|
||||
if (code == 0) {
|
||||
const cp = spawn(prismaBinary, ['migrate', 'up', schemaArg, '--create-db', '--experimental'], {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
cp.on('exit', (code: number) => {
|
||||
if (code == 0) {
|
||||
spawn(prismaBinary, ['generate', schemaArg], {stdio: 'inherit'})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
} else if (command === 'init' || command === 'i') {
|
||||
spawn(prismaBinary, ['init'], {stdio: 'inherit'})
|
||||
} else {
|
||||
this.log('Missing command')
|
||||
}
|
||||
}
|
||||
}
|
||||
63
packages/cli/src/commands/new.ts
Normal file
63
packages/cli/src/commands/new.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import * as path from 'path'
|
||||
import {flags} from '@oclif/command'
|
||||
import Command from '../command'
|
||||
import AppGenerator from '../generators/app'
|
||||
const debug = require('debug')('blitz:new')
|
||||
|
||||
import PromptAbortedError from '../errors/prompt-aborted'
|
||||
|
||||
export interface Flags {
|
||||
ts: boolean
|
||||
yarn: boolean
|
||||
}
|
||||
|
||||
export default class New extends Command {
|
||||
static description = 'Create a new Blitz project'
|
||||
|
||||
static args = [
|
||||
{
|
||||
name: 'path',
|
||||
required: false,
|
||||
description: 'path to the new project, defaults to the current directory',
|
||||
},
|
||||
]
|
||||
|
||||
static flags = {
|
||||
help: flags.help({char: 'h'}),
|
||||
ts: flags.boolean({
|
||||
char: 't',
|
||||
description: 'generate a TypeScript project',
|
||||
default: true,
|
||||
allowNo: true,
|
||||
}),
|
||||
yarn: flags.boolean({description: 'use Yarn as the package manager', default: true, allowNo: true}),
|
||||
'dry-run': flags.boolean({description: 'show what files will be created without writing them to disk'}),
|
||||
}
|
||||
|
||||
async run() {
|
||||
const {args, flags} = this.parse(New)
|
||||
debug('args: ', args)
|
||||
debug('flags: ', flags)
|
||||
|
||||
const destinationRoot = args?.path ? path.resolve(args?.path) : process.cwd()
|
||||
const appName = path.basename(destinationRoot)
|
||||
|
||||
const generator = new AppGenerator({
|
||||
sourceRoot: path.join(__dirname, '../../templates/app'),
|
||||
destinationRoot,
|
||||
appName,
|
||||
dryRun: flags['dry-run'],
|
||||
install: true,
|
||||
yarn: flags.yarn,
|
||||
})
|
||||
|
||||
try {
|
||||
await generator.run()
|
||||
this.log('App Created!')
|
||||
} catch (err) {
|
||||
if (err instanceof PromptAbortedError) this.exit(0)
|
||||
|
||||
this.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
28
packages/cli/src/commands/start.ts
Normal file
28
packages/cli/src/commands/start.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {Command, flags} from '@oclif/command'
|
||||
import {dev, prod} from '@blitzjs/server'
|
||||
|
||||
export default class Start extends Command {
|
||||
static description = 'Start a development server'
|
||||
static aliases = ['s']
|
||||
|
||||
static flags = {
|
||||
production: flags.boolean({
|
||||
char: 'p',
|
||||
description: 'Create and start a production server',
|
||||
}),
|
||||
}
|
||||
|
||||
async run() {
|
||||
const {flags} = this.parse(Start)
|
||||
|
||||
const config = {
|
||||
rootFolder: process.cwd(),
|
||||
}
|
||||
|
||||
if (flags.production) {
|
||||
await prod(config)
|
||||
} else {
|
||||
await dev(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
31
packages/cli/src/commands/test.ts
Normal file
31
packages/cli/src/commands/test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {platform} from 'os'
|
||||
import {spawn} from 'child_process'
|
||||
import {Command} from '@oclif/command'
|
||||
import hasYarn from 'has-yarn'
|
||||
|
||||
export default class Test extends Command {
|
||||
static description = 'Run project tests'
|
||||
static aliases = ['t']
|
||||
|
||||
static args = [
|
||||
{
|
||||
name: 'watch',
|
||||
description: 'Run test:watch',
|
||||
},
|
||||
]
|
||||
|
||||
async run() {
|
||||
const {args} = this.parse(Test)
|
||||
let watchMode: boolean = false
|
||||
const watch = args['watch']
|
||||
if (watch) {
|
||||
watchMode = watch === 'watch' || watch === 'w'
|
||||
}
|
||||
const yarnBinary = platform() === 'win32' ? 'yarn.cmd' : 'yarn'
|
||||
const npmBinary = platform() === 'win32' ? 'npm.cmd' : 'npm'
|
||||
const packageManager = hasYarn() ? yarnBinary : npmBinary
|
||||
|
||||
if (watchMode) spawn(packageManager, ['test:watch'], {stdio: 'inherit'})
|
||||
else spawn(packageManager, ['test'], {stdio: 'inherit'})
|
||||
}
|
||||
}
|
||||
5
packages/cli/src/errors/prompt-aborted.ts
Normal file
5
packages/cli/src/errors/prompt-aborted.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default class PromptAbortedError extends Error {
|
||||
constructor() {
|
||||
super('Prompt aborted')
|
||||
}
|
||||
}
|
||||
87
packages/cli/src/generator.ts
Normal file
87
packages/cli/src/generator.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import * as fs from 'fs-extra'
|
||||
import * as path from 'path'
|
||||
import {EventEmitter} from 'events'
|
||||
import {create as createStore, Store} from 'mem-fs'
|
||||
import {create as createEditor, Editor} from 'mem-fs-editor'
|
||||
import Enquirer = require('enquirer')
|
||||
import execa = require('execa')
|
||||
|
||||
import ConflictChecker from './transforms/conflict-checker'
|
||||
|
||||
export interface GeneratorOptions {
|
||||
sourceRoot: string
|
||||
destinationRoot?: string
|
||||
yarn?: boolean
|
||||
install?: boolean
|
||||
dryRun?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* The base generator class.
|
||||
* Every generator must extend this class.
|
||||
*/
|
||||
abstract class Generator<T extends GeneratorOptions = GeneratorOptions> extends EventEmitter {
|
||||
private readonly store: Store
|
||||
|
||||
protected readonly fs: Editor
|
||||
protected readonly enquirer: Enquirer
|
||||
|
||||
private performedActions: string[] = []
|
||||
|
||||
constructor(protected readonly options: T) {
|
||||
super()
|
||||
|
||||
this.store = createStore()
|
||||
this.fs = createEditor(this.store)
|
||||
this.enquirer = new Enquirer()
|
||||
if (!this.options.destinationRoot) this.options.destinationRoot = process.cwd()
|
||||
}
|
||||
|
||||
abstract async write(): Promise<void>
|
||||
|
||||
sourcePath(...paths: string[]): string {
|
||||
return path.join(this.options.sourceRoot, ...paths)
|
||||
}
|
||||
|
||||
destinationPath(...paths: string[]): string {
|
||||
return path.join(this.options.destinationRoot!, ...paths)
|
||||
}
|
||||
|
||||
async install() {
|
||||
await execa(this.options.yarn ? 'yarn' : 'npm', ['install'])
|
||||
}
|
||||
|
||||
async run() {
|
||||
if (!this.options.dryRun) {
|
||||
await fs.ensureDir(this.options.destinationRoot!)
|
||||
process.chdir(this.options.destinationRoot!)
|
||||
}
|
||||
|
||||
await this.write()
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const conflictChecker = new ConflictChecker({
|
||||
dryRun: this.options.dryRun,
|
||||
})
|
||||
conflictChecker.on('error', (err) => {
|
||||
reject(err)
|
||||
})
|
||||
conflictChecker.on('fileStatus', (data: string) => {
|
||||
this.performedActions.push(data)
|
||||
})
|
||||
|
||||
this.fs.commit([conflictChecker], (err) => {
|
||||
if (err) reject(err)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
this.performedActions.forEach((action) => {
|
||||
console.log(action)
|
||||
})
|
||||
|
||||
if (this.options.install && !this.options.dryRun) await this.install()
|
||||
}
|
||||
}
|
||||
|
||||
export default Generator
|
||||
35
packages/cli/src/generators/app.ts
Normal file
35
packages/cli/src/generators/app.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import Generator, {GeneratorOptions} from '../generator'
|
||||
|
||||
export interface AppGeneratorOptions extends GeneratorOptions {
|
||||
appName: string
|
||||
}
|
||||
|
||||
class AppGenerator extends Generator<AppGeneratorOptions> {
|
||||
packageJson() {
|
||||
return {
|
||||
name: this.options.appName,
|
||||
version: '0.0.1',
|
||||
private: true,
|
||||
scripts: {
|
||||
dev: 'next dev',
|
||||
build: 'next build',
|
||||
start: 'next start',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async write() {
|
||||
this.fs.copyTpl(this.sourcePath('README.md.ejs'), this.destinationPath('README.md'), {
|
||||
name: 'Hello',
|
||||
})
|
||||
this.fs.copyTpl(this.sourcePath('pages/index.js.ejs'), this.destinationPath('pages/index.js'), {
|
||||
name: 'Hello',
|
||||
})
|
||||
|
||||
this.fs.writeJSON(this.destinationPath('package.json'), this.packageJson())
|
||||
|
||||
this.fs.copy(this.sourcePath('gitignore'), this.destinationPath('.gitignore'))
|
||||
}
|
||||
}
|
||||
|
||||
export default AppGenerator
|
||||
1
packages/cli/src/index.ts
Normal file
1
packages/cli/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {run} from '@oclif/command'
|
||||
142
packages/cli/src/transforms/conflict-checker.ts
Normal file
142
packages/cli/src/transforms/conflict-checker.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import {Transform, TransformCallback} from 'stream'
|
||||
import * as path from 'path'
|
||||
import File from 'vinyl'
|
||||
import {diffLines, Change} from 'diff'
|
||||
import * as fs from 'fs-extra'
|
||||
import chalk = require('chalk')
|
||||
import enquirer = require('enquirer')
|
||||
|
||||
import PromptAbortedError from '../errors/prompt-aborted'
|
||||
|
||||
interface PromptAnswer {
|
||||
action: 'overwrite' | 'skip' | 'show'
|
||||
}
|
||||
|
||||
type PromptActions = 'create' | 'overwrite' | 'skip' | 'identical'
|
||||
|
||||
interface ConflictCheckerOptions {
|
||||
dryRun?: boolean
|
||||
}
|
||||
|
||||
export default class ConflictChecker extends Transform {
|
||||
private _destroyed = false
|
||||
|
||||
constructor(private readonly options?: ConflictCheckerOptions) {
|
||||
super({
|
||||
objectMode: true,
|
||||
})
|
||||
}
|
||||
|
||||
_transform(file: File, _encoding: string, cb: TransformCallback): void {
|
||||
if (file.state === null) {
|
||||
cb()
|
||||
return
|
||||
}
|
||||
|
||||
// If the file doesn't exists yet there isn't any diff to perform
|
||||
const filePath = path.resolve(file.path)
|
||||
if (!fs.existsSync(filePath)) {
|
||||
this.handlePush(file, 'create')
|
||||
cb()
|
||||
return
|
||||
}
|
||||
|
||||
this.checkDiff(file)
|
||||
.then((status) => {
|
||||
if (status !== 'skip') {
|
||||
this.handlePush(file, status)
|
||||
} else {
|
||||
this.fileStatusString(file, status)
|
||||
}
|
||||
|
||||
cb()
|
||||
})
|
||||
.catch((err) => {
|
||||
// If the error is an empty string, it means that the user has
|
||||
// stopped the prompt with ctrl-c so we return PromptAbortedError
|
||||
// to end the program without writing anything to disk
|
||||
cb(err || new PromptAbortedError())
|
||||
})
|
||||
}
|
||||
|
||||
destroy(err?: Error): void {
|
||||
if (this._destroyed) return
|
||||
this._destroyed = true
|
||||
|
||||
process.nextTick(() => {
|
||||
if (err) this.emit('err', err)
|
||||
this.emit('close')
|
||||
})
|
||||
}
|
||||
|
||||
handlePush(file: File, status: PromptActions): void {
|
||||
if (!this.options?.dryRun) this.push(file)
|
||||
|
||||
this.emit('fileStatus', this.fileStatusString(file, status))
|
||||
}
|
||||
|
||||
private async checkDiff(file: File): Promise<PromptActions> {
|
||||
let newFileContents = file.contents?.toString() ?? ''
|
||||
const oldFileContents = fs.readFileSync(path.resolve(file.path)).toString()
|
||||
|
||||
const diff = diffLines(oldFileContents, newFileContents)
|
||||
|
||||
const conflict = diff.some((line) => line.added || line.removed)
|
||||
|
||||
if (conflict) {
|
||||
let answer = null
|
||||
do {
|
||||
answer = await enquirer.prompt<PromptAnswer>({
|
||||
type: 'select',
|
||||
name: 'action',
|
||||
message: `The file "${file.path}" has conflicts. What do you want to do?`, // Maybe color file.path
|
||||
choices: [
|
||||
{name: 'overwrite', message: 'Overwrite', value: 'overwrite'},
|
||||
{name: 'skip', message: 'Skip', value: 'skip'},
|
||||
{name: 'show', message: 'Show changes', value: 'show'},
|
||||
],
|
||||
})
|
||||
|
||||
if (answer?.action === 'show') this.printDiff(diff)
|
||||
} while (answer?.action === 'show')
|
||||
|
||||
return answer.action
|
||||
}
|
||||
|
||||
return 'identical'
|
||||
}
|
||||
|
||||
private printDiff(diff: Change[]) {
|
||||
console.log('\n')
|
||||
diff.forEach((line) => {
|
||||
const value = line.value.replace('\n', '')
|
||||
if (line.added) {
|
||||
console.log(chalk.green(`+ ${value}`))
|
||||
} else if (line.removed) {
|
||||
console.log(chalk.red(`- ${value}`))
|
||||
} else {
|
||||
console.log(value)
|
||||
}
|
||||
})
|
||||
console.log('\n')
|
||||
}
|
||||
|
||||
private fileStatusString(file: File, status: PromptActions) {
|
||||
let statusLog = null
|
||||
switch (status) {
|
||||
case 'create':
|
||||
statusLog = chalk.green('CREATE ')
|
||||
break
|
||||
case 'overwrite':
|
||||
statusLog = chalk.cyan('OVERWRITE')
|
||||
break
|
||||
case 'skip':
|
||||
statusLog = chalk.blue('SKIP ')
|
||||
break
|
||||
case 'identical':
|
||||
statusLog = chalk.gray('IDENTICAL')
|
||||
}
|
||||
|
||||
return `${statusLog} ${file.relative}`
|
||||
}
|
||||
}
|
||||
7
packages/cli/src/utils/load-blitz.ts
Normal file
7
packages/cli/src/utils/load-blitz.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import {forceRequire} from './module'
|
||||
|
||||
export const BLITZ_MODULE_PATHS = ['@prisma/client']
|
||||
|
||||
export const loadBlitz = () => {
|
||||
return Object.assign({}, ...BLITZ_MODULE_PATHS.map(forceRequire))
|
||||
}
|
||||
42
packages/cli/src/utils/load-dependencies.ts
Normal file
42
packages/cli/src/utils/load-dependencies.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import * as path from 'path'
|
||||
import {forceRequire} from './module'
|
||||
|
||||
const modulePath = (module: string) => {
|
||||
try {
|
||||
return require.resolve(module)
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to load module '${module}'`)
|
||||
}
|
||||
}
|
||||
|
||||
const isFunction = (functionToCheck: any): functionToCheck is Function =>
|
||||
typeof functionToCheck === 'function'
|
||||
|
||||
const toCamelCase = (name: string) =>
|
||||
name
|
||||
.replace(/[_-]([a-z])/g, (_match: string, group: string) => group.toUpperCase())
|
||||
.replace(/@[a-z]+\//, '')
|
||||
|
||||
const functionModuleName = (moduleName: string, fun: Function) => {
|
||||
if (fun.name && fun.name !== 'anonymous') return fun.name
|
||||
return toCamelCase(moduleName)
|
||||
}
|
||||
|
||||
export const loadDependencies = (pkgRoot: string) => {
|
||||
const pkg = forceRequire(path.join(pkgRoot, 'package.json'))
|
||||
|
||||
const modules = Object.keys(pkg.dependencies || {})
|
||||
.map((name) => [name, modulePath(name)])
|
||||
.filter(([_name, path]) => path !== null)
|
||||
.map(([name, path]) => [name, forceRequire(path)])
|
||||
.map(([name, module]) => {
|
||||
if (isFunction(module)) return {[functionModuleName(name, module)]: module}
|
||||
const defaultExport = module.default
|
||||
if (!defaultExport) return module
|
||||
if (isFunction(defaultExport)) return {[functionModuleName(name, defaultExport)]: defaultExport}
|
||||
return {
|
||||
[toCamelCase(defaultExport)]: defaultExport,
|
||||
}
|
||||
})
|
||||
return Object.assign({}, ...modules)
|
||||
}
|
||||
8
packages/cli/src/utils/module.ts
Normal file
8
packages/cli/src/utils/module.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
const invalidateCache = (module: string) => {
|
||||
delete require.cache[require.resolve(module)]
|
||||
}
|
||||
|
||||
export const forceRequire = (module: string) => {
|
||||
invalidateCache(module)
|
||||
return require(module)
|
||||
}
|
||||
1
packages/cli/templates/app/README.md.ejs
Normal file
1
packages/cli/templates/app/README.md.ejs
Normal file
@@ -0,0 +1 @@
|
||||
# <%= name %>
|
||||
25
packages/cli/templates/app/gitignore
Normal file
25
packages/cli/templates/app/gitignore
Normal file
@@ -0,0 +1,25 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env*
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
15
packages/cli/templates/app/pages/index.js.ejs
Normal file
15
packages/cli/templates/app/pages/index.js.ejs
Normal file
@@ -0,0 +1,15 @@
|
||||
import Head from 'next/head'
|
||||
|
||||
const Home = () => (
|
||||
<div className="container">
|
||||
<Head>
|
||||
<title><%= name %></title>
|
||||
</Head>
|
||||
|
||||
<main>
|
||||
<h1>Welcome to <%= name %>!</h1>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Home
|
||||
20
packages/cli/test/commands/build.test.ts
Normal file
20
packages/cli/test/commands/build.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
const build = jest.fn(() => {})
|
||||
jest.mock('@blitzjs/server', () => ({build}))
|
||||
|
||||
import BuildCmd from '../../src/commands/build'
|
||||
import {resolve} from 'path'
|
||||
|
||||
describe('Build command', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
const options = {
|
||||
rootFolder: resolve(__dirname, '../../'),
|
||||
}
|
||||
|
||||
it('runs the build script', async () => {
|
||||
await BuildCmd.run([])
|
||||
expect(build).toBeCalledWith(options)
|
||||
})
|
||||
})
|
||||
49
packages/cli/test/commands/console.test.ts
Normal file
49
packages/cli/test/commands/console.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as repl from 'repl'
|
||||
import * as chokidar from 'chokidar'
|
||||
import ConsoleCmd from '../../src/commands/console'
|
||||
import {BLITZ_MODULE_PATHS} from '../../src/utils/load-blitz'
|
||||
import {REPLServer} from 'repl'
|
||||
import {FSWatcher} from 'chokidar'
|
||||
|
||||
const mockRepl = ({
|
||||
defineCommand: jest.fn(),
|
||||
on: jest.fn(),
|
||||
context: {},
|
||||
} as any) as REPLServer
|
||||
const mockWatcher = ({
|
||||
on: jest.fn(),
|
||||
} as any) as FSWatcher
|
||||
|
||||
jest.mock('repl')
|
||||
jest.mock('chokidar')
|
||||
jest.mock(`${process.cwd()}/package.json`, () => ({
|
||||
dependencies: {
|
||||
ramda: '1.0.0',
|
||||
},
|
||||
}))
|
||||
jest.mock('../../src/utils/load-dependencies')
|
||||
jest.mock('../../src/utils/load-blitz')
|
||||
|
||||
describe('Console command', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks()
|
||||
})
|
||||
|
||||
it('runs REPL', async () => {
|
||||
jest.spyOn(ConsoleCmd.prototype, 'log')
|
||||
jest.spyOn(repl, 'start').mockReturnValue(mockRepl)
|
||||
jest.spyOn(chokidar, 'watch').mockReturnValue(mockWatcher)
|
||||
jest.spyOn(mockRepl, 'on').mockReturnValue(mockRepl)
|
||||
|
||||
await ConsoleCmd.prototype.run()
|
||||
|
||||
expect(repl.start).toBeCalledWith(ConsoleCmd.replOptions)
|
||||
expect(mockRepl.defineCommand).toBeCalledWith('reload', ConsoleCmd.commands.reload)
|
||||
|
||||
expect(chokidar.watch).toBeCalledWith('package.json')
|
||||
expect(chokidar.watch).toBeCalledWith(BLITZ_MODULE_PATHS)
|
||||
|
||||
expect(ConsoleCmd.prototype.log).toHaveBeenCalledWith(`Welcome to Blitz.js v0.0.1
|
||||
Type ".help" for more information.`)
|
||||
})
|
||||
})
|
||||
78
packages/cli/test/commands/db.test.ts
Normal file
78
packages/cli/test/commands/db.test.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import * as path from 'path'
|
||||
import {platform} from 'os'
|
||||
|
||||
let onSpy: jest.Mock
|
||||
const spawn = jest.fn(() => {
|
||||
onSpy = jest.fn(function on(_: string, callback: (_: number) => {}) {
|
||||
callback(0)
|
||||
})
|
||||
return {on: onSpy}
|
||||
})
|
||||
|
||||
jest.doMock('child_process', () => ({spawn}))
|
||||
|
||||
import DbCmd from '../../src/commands/db'
|
||||
|
||||
const prismaBinaryName = platform() === 'win32' ? 'prisma.cmd' : 'prisma'
|
||||
const prismaBinary = path.join(process.cwd(), 'node_modules/.bin', prismaBinaryName)
|
||||
const schemaArg = `--schema=${path.join(process.cwd(), 'db', 'schema.prisma')}`
|
||||
|
||||
const initParams = [prismaBinary, ['init'], {stdio: 'inherit'}]
|
||||
const migrateSaveParams = [
|
||||
prismaBinary,
|
||||
['migrate', 'save', schemaArg, '--create-db', '--experimental'],
|
||||
{stdio: 'inherit'},
|
||||
]
|
||||
const migrateUpParams = [
|
||||
prismaBinary,
|
||||
['migrate', 'up', schemaArg, '--create-db', '--experimental'],
|
||||
{stdio: 'inherit'},
|
||||
]
|
||||
|
||||
describe('Db command', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('runs db init', async () => {
|
||||
await DbCmd.run(['init'])
|
||||
|
||||
expect(spawn).toHaveBeenCalledWith(...initParams)
|
||||
})
|
||||
|
||||
it('runs db init (alias)', async () => {
|
||||
await DbCmd.run(['i'])
|
||||
|
||||
expect(spawn).toHaveBeenCalledWith(...initParams)
|
||||
})
|
||||
|
||||
it('runs db migrate', async () => {
|
||||
await DbCmd.run(['migrate'])
|
||||
|
||||
expect(spawn).toBeCalledWith(...migrateSaveParams)
|
||||
expect(spawn.mock.calls.length).toBe(3)
|
||||
|
||||
// following expection is not working
|
||||
//expect(onSpy).toHaveBeenCalledWith(0);
|
||||
|
||||
expect(spawn).toBeCalledWith(...migrateUpParams)
|
||||
})
|
||||
|
||||
it('runs db migrate (alias)', async () => {
|
||||
await DbCmd.run(['m'])
|
||||
|
||||
expect(spawn).toBeCalledWith(...migrateSaveParams)
|
||||
expect(spawn.mock.calls.length).toBe(3)
|
||||
|
||||
// following expection is not working
|
||||
//expect(onSpy).toHaveBeenCalledWith(0);
|
||||
|
||||
expect(spawn).toBeCalledWith(...migrateUpParams)
|
||||
})
|
||||
|
||||
it('does not run db in case of invalid command', async () => {
|
||||
await DbCmd.run(['invalid'])
|
||||
|
||||
expect(spawn.mock.calls.length).toBe(0)
|
||||
})
|
||||
})
|
||||
3
packages/cli/test/commands/new.test.ts
Normal file
3
packages/cli/test/commands/new.test.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
jest.mock('fs')
|
||||
|
||||
test('Mock inquirer.js and test the command', () => {})
|
||||
27
packages/cli/test/commands/start.test.ts
Normal file
27
packages/cli/test/commands/start.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
const dev = jest.fn(() => {})
|
||||
const prod = jest.fn(() => {})
|
||||
|
||||
jest.mock('@blitzjs/server', () => ({dev, prod}))
|
||||
|
||||
import StartCmd from '../../src/commands/start'
|
||||
import {resolve} from 'path'
|
||||
|
||||
describe('Start command', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
const options = {
|
||||
rootFolder: resolve(__dirname, '../../'),
|
||||
}
|
||||
|
||||
it('runs the dev script', async () => {
|
||||
await StartCmd.run([])
|
||||
expect(dev).toBeCalledWith(options)
|
||||
})
|
||||
|
||||
it('runs the prod script when passed the production flag', async () => {
|
||||
await StartCmd.run(['--production'])
|
||||
expect(prod).toBeCalledWith(options)
|
||||
})
|
||||
})
|
||||
55
packages/cli/test/commands/test.test.ts
Normal file
55
packages/cli/test/commands/test.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import childProcess from 'child_process'
|
||||
import hasYarn from 'has-yarn'
|
||||
import TestCmd from '../../src/commands/test'
|
||||
|
||||
jest.mock('child_process')
|
||||
jest.mock('has-yarn')
|
||||
|
||||
const testParams = [['test'], {stdio: 'inherit'}]
|
||||
const testWatchParams = [['test:watch'], {stdio: 'inherit'}]
|
||||
|
||||
describe('Test command', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('runs yarn test script', async () => {
|
||||
jest.spyOn(hasYarn, 'default').mockReturnValue(true)
|
||||
|
||||
await TestCmd.run([])
|
||||
|
||||
expect(childProcess.spawn).toBeCalledWith('yarn', ...testParams)
|
||||
})
|
||||
|
||||
it('runs npm test script', async () => {
|
||||
jest.spyOn(hasYarn, 'default').mockReturnValue(false)
|
||||
|
||||
await TestCmd.run([])
|
||||
|
||||
expect(childProcess.spawn).toBeCalledWith('npm', ...testParams)
|
||||
})
|
||||
|
||||
it('runs yarn test:watch script', async () => {
|
||||
jest.spyOn(hasYarn, 'default').mockReturnValue(true)
|
||||
|
||||
await TestCmd.run(['watch'])
|
||||
|
||||
expect(childProcess.spawn).toBeCalledWith('yarn', ...testWatchParams)
|
||||
})
|
||||
|
||||
it('runs yarn test:watch script with alias', async () => {
|
||||
jest.spyOn(hasYarn, 'default').mockReturnValue(true)
|
||||
|
||||
await TestCmd.run(['w'])
|
||||
|
||||
expect(childProcess.spawn).toBeCalledWith('yarn', ...testWatchParams)
|
||||
})
|
||||
|
||||
it('runs yarn test and ignores invalid argument', async () => {
|
||||
jest.spyOn(hasYarn, 'default').mockReturnValue(true)
|
||||
|
||||
await TestCmd.run(['invalid'])
|
||||
|
||||
expect(childProcess.spawn).toBeCalledWith('yarn', ...testParams)
|
||||
})
|
||||
})
|
||||
9
packages/cli/test/tsconfig.json
Normal file
9
packages/cli/test/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"types": ["jest"],
|
||||
"target": "es6"
|
||||
},
|
||||
"references": [{"path": ".."}]
|
||||
}
|
||||
15
packages/cli/tsconfig.json
Normal file
15
packages/cli/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "es2015",
|
||||
"baseUrl": "./",
|
||||
"module": "commonjs",
|
||||
"rootDir": ".",
|
||||
"outDir": "lib",
|
||||
"declaration": false,
|
||||
"sourceMap": false,
|
||||
"types": []
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
2721
packages/cli/yarn.lock
Normal file
2721
packages/cli/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
8
packages/core/.gitignore
vendored
Normal file
8
packages/core/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
node_modules
|
||||
.rts2_cache_cjs
|
||||
.rts2_cache_esm
|
||||
.rts2_cache_umd
|
||||
.rts2_cache_system
|
||||
dist
|
||||
1
packages/core/jest.setup.js
Normal file
1
packages/core/jest.setup.js
Normal file
@@ -0,0 +1 @@
|
||||
require('@testing-library/jest-dom')
|
||||
70
packages/core/package.json
Normal file
70
packages/core/package.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "@blitzjs/core",
|
||||
"description": "Framework for building monolithic, full-stack, serverless React apps with zero data-fetching and zero client-side state management",
|
||||
"version": "0.0.2-canary.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "tsdx watch",
|
||||
"build": "tsdx build",
|
||||
"test": "tsdx test",
|
||||
"test:watch": "tsdx test --watch",
|
||||
"lint": "tsdx lint"
|
||||
},
|
||||
"author": {
|
||||
"name": "Brandon Bayer",
|
||||
"email": "b@bayer.ws",
|
||||
"url": "https://twitter.com/flybayer"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Michael Edelman",
|
||||
"email": "michael@fabulas.io",
|
||||
"url": "https://twitter.com/edelman215"
|
||||
}
|
||||
],
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/core.esm.js",
|
||||
"types": "dist/packages/core/src/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "tsdx lint"
|
||||
}
|
||||
},
|
||||
"jest": {
|
||||
"setupFilesAfterEnv": [
|
||||
"<rootDir>/jest.setup.js"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"yarn": "^1.19.1",
|
||||
"node": ">=12.16.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/blitz-js/blitz"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@prisma/client": "2.0.0-beta.2",
|
||||
"@testing-library/jest-dom": "5.5.0",
|
||||
"@testing-library/react": "10.0.2",
|
||||
"@types/jest": "^25.1.3",
|
||||
"@types/node": "^13.7.4",
|
||||
"@types/react": "16.9.34",
|
||||
"cross-env": "^7.0.0",
|
||||
"husky": "^4.2.3",
|
||||
"lint-staged": "^10.0.8",
|
||||
"next": "^9.3.0",
|
||||
"ts-jest": "24.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/react-query": "1.1.2",
|
||||
"@types/serialize-error": "4.0.1",
|
||||
"pretty-ms": "6.0.1",
|
||||
"react-query": "1.2.1",
|
||||
"serialize-error": "6.0.0"
|
||||
},
|
||||
"gitHead": "6719104cb3e78948e7f06aa948ff72bbb84cb682"
|
||||
}
|
||||
14
packages/core/src/index.ts
Normal file
14
packages/core/src/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export {
|
||||
GetStaticProps,
|
||||
GetStaticPaths,
|
||||
GetServerSideProps,
|
||||
NextPage as BlitzPage,
|
||||
NextApiRequest as BlitzApiRequest,
|
||||
NextApiResponse as BlitzApiResponse,
|
||||
} from 'next'
|
||||
|
||||
export {default as Link} from 'next/link'
|
||||
export {default as Router, useRouter, withRouter} from 'next/router'
|
||||
|
||||
export * from './useQuery'
|
||||
export * from './rpc'
|
||||
95
packages/core/src/rpc.ts
Normal file
95
packages/core/src/rpc.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import {BlitzApiRequest, BlitzApiResponse} from '.'
|
||||
import {serializeError, deserializeError} from 'serialize-error'
|
||||
|
||||
export async function rpc(url: string, params: any) {
|
||||
if (typeof window === 'undefined') return
|
||||
const result = await window.fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
redirect: 'follow',
|
||||
body: JSON.stringify({params}),
|
||||
})
|
||||
|
||||
const json = await result.json()
|
||||
|
||||
if (json.result) {
|
||||
return json.result
|
||||
} else {
|
||||
throw deserializeError(json.error)
|
||||
}
|
||||
}
|
||||
|
||||
rpc.warm = (url: string) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.fetch(url, {method: 'HEAD'})
|
||||
}
|
||||
}
|
||||
|
||||
export function isomorphicRpc(resolver: any, cacheKey: string) {
|
||||
if (typeof window !== 'undefined') {
|
||||
const url = cacheKey.replace(/^app\/_rpc/, '/api')
|
||||
const rpcFn: any = (params: any) => rpc(url, params)
|
||||
rpcFn.cacheKey = url
|
||||
|
||||
// Warm the lambda
|
||||
rpc.warm(url)
|
||||
return rpcFn
|
||||
} else {
|
||||
return resolver
|
||||
}
|
||||
}
|
||||
|
||||
export function rpcHandler(
|
||||
type: string,
|
||||
name: string,
|
||||
resolver: (...args: any) => Promise<any>,
|
||||
connectDb?: () => any,
|
||||
) {
|
||||
return async function (req: BlitzApiRequest, res: BlitzApiResponse) {
|
||||
const logPrefix = `[${type}:${name}]`
|
||||
console.log(`${logPrefix} ${req.method} ${JSON.stringify(req.body)} `)
|
||||
|
||||
if (req.method === 'HEAD') {
|
||||
// Warm the lamda and connect to DB
|
||||
if (typeof connectDb === 'function') {
|
||||
connectDb()
|
||||
}
|
||||
console.log(`${logPrefix} SUCCESS 200`)
|
||||
return res.status(200).end()
|
||||
} else if (req.method === 'POST') {
|
||||
// Handle RPC call
|
||||
|
||||
if (typeof req.body.params === 'undefined') {
|
||||
const error = {message: "Request body is missing the 'params' key"}
|
||||
console.log(`${logPrefix} ERROR: ${JSON.stringify(error)}`)
|
||||
return res.status(400).json({
|
||||
result: null,
|
||||
error,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await resolver(req.body.params)
|
||||
|
||||
console.log(`${logPrefix} SUCCESS ${JSON.stringify(result)}`)
|
||||
return res.json({
|
||||
result,
|
||||
error: null,
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(`${logPrefix} ERROR: ${error}`)
|
||||
return res.json({
|
||||
result: null,
|
||||
error: serializeError(error),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Everything else is error
|
||||
console.log(`${logPrefix} ERROR: 404`)
|
||||
return res.status(404).end()
|
||||
}
|
||||
}
|
||||
}
|
||||
25
packages/core/src/useQuery.ts
Normal file
25
packages/core/src/useQuery.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {useQuery as useReactQuery} from 'react-query'
|
||||
import {PromiseReturnType} from '@prisma/client'
|
||||
// TODO: why doesn't the type work properly from ../types?
|
||||
// import {PromiseReturnType} from '../types'
|
||||
|
||||
type QueryFn = (...args: any) => Promise<any>
|
||||
|
||||
// interface QueryFn {
|
||||
// (...args: any): Promise<any>
|
||||
// cacheKey: string
|
||||
// }
|
||||
|
||||
export function useQuery<T extends QueryFn>(
|
||||
queryFn: T,
|
||||
params?: any,
|
||||
options: any = {},
|
||||
): [PromiseReturnType<T>, Record<any, any>] {
|
||||
const {data, ...rest} = useReactQuery([(queryFn as any).cacheKey, params], (_, params) => queryFn(params), {
|
||||
suspense: true,
|
||||
retry: process.env.NODE_ENV === 'production' ? 3 : false,
|
||||
...options,
|
||||
})
|
||||
|
||||
return [data as PromiseReturnType<T>, rest]
|
||||
}
|
||||
1
packages/core/src/utils/index.ts
Normal file
1
packages/core/src/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const isServer = typeof window === 'undefined'
|
||||
3
packages/core/test/todo.test.ts
Normal file
3
packages/core/test/todo.test.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
it('todo', async () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
13
packages/core/tsconfig.json
Normal file
13
packages/core/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src", "types", "test"],
|
||||
"exclude": ["node_modules"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"declarationDir": "./dist",
|
||||
"downlevelIteration": true,
|
||||
"paths": {
|
||||
"*": ["src/*", "node_modules/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
6
packages/core/tsdx.config.js
Normal file
6
packages/core/tsdx.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
rollup(config, options) {
|
||||
config.external('@prisma/client')
|
||||
return config
|
||||
},
|
||||
}
|
||||
15
packages/core/types/index.d.ts
vendored
Normal file
15
packages/core/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// declare module '@prisma/client' {
|
||||
// export class PrismaClient {
|
||||
// constructor(args: any)
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Get the type of the value, that the Promise holds.
|
||||
*/
|
||||
export type PromiseType<T extends PromiseLike<any>> = T extends PromiseLike<infer U> ? U : T
|
||||
|
||||
/**
|
||||
* Get the return type of a function which returns a Promise.
|
||||
*/
|
||||
export type PromiseReturnType<T extends (...args: any) => Promise<any>> = PromiseType<ReturnType<T>>
|
||||
4
packages/server/.gitignore
vendored
Normal file
4
packages/server/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
5
packages/server/bin/next-patched
Executable file
5
packages/server/bin/next-patched
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict'
|
||||
|
||||
require('@blitzjs/server/register')
|
||||
require('next/dist/bin/next')
|
||||
61
packages/server/package.json
Normal file
61
packages/server/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"version": "0.0.2-canary.0",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
"next-patched": "./bin/next-patched"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "tsdx watch",
|
||||
"build": "tsdx build",
|
||||
"test": "tsdx test",
|
||||
"lint": "tsdx lint"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "tsdx lint"
|
||||
}
|
||||
},
|
||||
"name": "@blitzjs/server",
|
||||
"author": {
|
||||
"name": "Brandon Bayer",
|
||||
"email": "b@bayer.ws",
|
||||
"url": "https://twitter.com/flybayer"
|
||||
},
|
||||
"module": "dist/webpack.esm.js",
|
||||
"types": "dist/packages/server/src/index.d.ts",
|
||||
"devDependencies": {
|
||||
"@types/gulp-if": "^0.0.33",
|
||||
"@types/jest": "^25.1.3",
|
||||
"@types/path-is-absolute": "^1.0.0",
|
||||
"@types/readable-stream": "^2.3.5",
|
||||
"@types/through2": "^2.0.34",
|
||||
"@types/vinyl-fs": "^2.4.11",
|
||||
"directory-tree": "^2.2.4",
|
||||
"husky": "^4.2.3",
|
||||
"tsdx": "^0.13.1",
|
||||
"tslib": "^1.11.1",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"fast-glob": "^3.2.2",
|
||||
"gulp-if": "^3.0.0",
|
||||
"next": "9.3.4",
|
||||
"next-compose-plugins": "2.2.0",
|
||||
"next-transpile-modules": "3.2.0",
|
||||
"node-pty": "^0.9.0",
|
||||
"null-loader": "^3.0.0",
|
||||
"path-is-absolute": "^2.0.0",
|
||||
"pirates": "^4.0.1",
|
||||
"readable-stream": "^3.6.0",
|
||||
"resolve-bin": "^0.4.0",
|
||||
"through2": "^3.0.1",
|
||||
"vinyl": "^2.2.0",
|
||||
"vinyl-file": "^3.0.0",
|
||||
"vinyl-fs": "^3.0.3"
|
||||
},
|
||||
"gitHead": "6719104cb3e78948e7f06aa948ff72bbb84cb682"
|
||||
}
|
||||
16
packages/server/register/index.js
Normal file
16
packages/server/register/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const addHook = require('pirates').addHook
|
||||
|
||||
addHook(
|
||||
function (code) {
|
||||
const wrapCode =
|
||||
'\n;if(_launchEditorFn) { module.exports = require("@blitzjs/server/register/launch-editor").enhance(_launchEditorFn); }'
|
||||
return code.replace(/module\.exports\s?=/, 'const _launchEditorFn =') + wrapCode
|
||||
},
|
||||
{
|
||||
exts: ['.js'],
|
||||
matcher: function (filename) {
|
||||
return filename.match(/launch-editor\/index/)
|
||||
},
|
||||
ignoreNodeModules: false,
|
||||
},
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user