1
0
mirror of synced 2026-02-04 12:08:33 -05:00

Compare commits

...

7 Commits

Author SHA1 Message Date
github-actions[bot]
107254ece9 Version Packages (#4425)
* Version Packages

* pnpm lock update

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
2025-02-24 13:38:29 +00:00
Siddharth Suresh
816330b9d0 fix: Overriden custom cookies used inside withBlitzAuth (#4424)
* fix: use append rather than set

* Create ninety-stingrays-double.md
2025-02-24 18:47:50 +05:30
github-actions[bot]
18decd1558 Version Packages (#4411)
* Version Packages

* pnpm lock update

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
2025-02-21 13:09:32 +05:30
Siddharth Suresh
1610c73f99 breaking change: remove recipes (#4422)
* remove recipies

* missing change

* fixes

* Create wet-drinks-wait.md

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-02-14 15:10:42 +00:00
Siddharth Suresh
0a257e9150 fix: Pages router template (#4423)
* fixes

* fix

* Create tall-waves-marry.md
2025-02-14 15:02:08 +00:00
Blitz.js Bot
2661bcd98d (meta) added @Daidalos117 as contributor 2025-02-07 06:51:33 -06:00
Roman Rajchert
11c9f00eb9 Fix/4407 set query data use inifinite query (#4413)
* fix(4407): setQueryData with useInifiniteQuery

* chore(4407): merge develop

* fix(4407): typing

* chore(4407): add changeset

* chore(4407): remove prefetch on infninite mutate page

* chore(4407): recreate changeset

* fix(4407): mutating data in infinite

* fix(4407): remove web from changeset

---------

Co-authored-by: Roman Rajchert <roman.rajchert@vodafone.com>
2025-02-07 18:21:28 +05:30
227 changed files with 1711 additions and 9206 deletions

View File

@@ -4134,6 +4134,16 @@
"doc",
"code"
]
},
{
"login": "Daidalos117",
"name": "Roman Rajchert",
"avatar_url": "https://avatars.githubusercontent.com/u/15905269?v=4",
"profile": "https://github.com/Daidalos117",
"contributions": [
"doc",
"code"
]
}
],
"contributorsPerLine": 7,

View File

@@ -7,5 +7,5 @@
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["web", "test-*", "toolkit-*", "@blitzjs/recipe-*"]
"ignore": ["web", "test-*", "toolkit-*", "next-blitz-auth"]
}

View File

@@ -1,11 +0,0 @@
---
"@blitzjs/next": major
"@blitzjs/rpc": major
"blitz": major
"@blitzjs/auth": major
"@blitzjs/codemod": major
"@blitzjs/config": major
"@blitzjs/generator": major
---
TODO: Upgrade @tanstack/react-query to v5.1.1

View File

@@ -6,7 +6,7 @@
<img alt="" src="https://img.shields.io/badge/Join%20our%20community-6700EB.svg?style=for-the-badge&labelColor=000000&logoWidth=20&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ9SURBVHgB7d3dVdtAEIbhcSpICUoH0IEogQqSVBBSAU4FSSpIOoAORAfQgSghHXzZ1U/YcMD4R9rZmf2ec3y448LyiNf27iLiGIAmPLrweC9Un3DhrzG6EarLNP09nlwJ1SOZ/lQr5N80/S/p2QMVCBf5N17XCfm1Y/rBHqjAG9PPHvBsz+mf9WAP+HLA9M/YA14cOP2payH7jpj+VCtk1wnTP+vj7xCy6cTpn7EHLMLp059iD1iD8eveJbVCNsSLheX1YA/YgOWnf8YeKB3Wmf7Ud6Fy4f/FHmtpxbl3YlC4MJ/Cj0bWdwPnPbARg+L0S54XQHS32WwuxClzd4CM0z9rPfeAuTtA5ulPXYQ7wZ04Y+oOoDD9KZc9YOoOoDj9s4dwFzgXR6w1wIPoOvPWA9buAHEJ173o3gWiy3AnuBUHLEbgmYwvAk1/wuM8vAgexThzbwPDkx7/DHwVXfFOxP2GmsKd4Ab6zPeAyU8CI7AHFmH2BRCBPXAyk18GzUrqAXCTiR4ssyj0VFw/oCU8+e+RZ33AWz6KMaYbIIWxB+JSLs1bsbkeMN0AqakHvoku9oA2sAfqBvbAQdw0QArsgb25aYBUQT3QgT2gB+yBuqGcHij2UCqXDZACe2Anlw2QYg/QAOyBuoE98CL3DZDCuK4/rh/Q7oGL6U+TOvcNkJoijN8X1C48+T+g75eQDrAH/qmqAVJgDwyqaoAUe4AGYA/UDZX3QLUNkEIZPRCd5+6BahsgVUgPROwBTSijB7jpVAvGHriHvmw9wAZ4BpX1ABvgmakHtPcbRuwBTWAPULgAV9D/jKDY9YRvwvgEaurD44uQHvAol7qBW7WKluVtIHiUS7GyvA0s6CiXDnxrpQfsgbqBS7GKk/2jYHCrVlGyfxTMrVo0ALdq1Q3sgSKofh0M9oA61a+D2QM0AHugbmAPqClmSRjK2apVVQ8UsySsoK1aHdgDesCtWnUDeyCrIpeFg1u3sylyWTi3btMA7IG6gT2wuuK3hoE9sKrit4YVslWLPaAN7IG6ocKt2zmY2h4O9sDiTG0PZw/QANy6XTewBxZj9ogYVHy025LMHhEz9cBn0We6B0yfERReBLfhx0/R1YQHPx/QBPbA0VwcEwf2wNFcHBPHHjiem3MC2QPHcXdSaJjA+KfgTPQ8hhfjBzHC40mhlzJ+Xq9lK4a4PCs43AVaGTed5mZq+iOXZwWHi3AnOj2wFWNcnxYe7gTxLtBKHuamP/J+Wnh8a5irB7ZC5Yk9gPX1QuXC+usHWqGyhYvUYR0a7zboUOFCNVhnk0krZAOW7wFOvzXhom2xnEbIHizTA1wEYhWW6YFGyC6c1gOcfg9wfA80Qj7g8B7g9HuCww+haIR8wf49wOn3Cvv9k8tGyC/s7gFOv3fY3QONkH+v9MBWqB7PeqDn9FcIT//kcitUn6kHOu/T/xfWzlQy3dEHhwAAAABJRU5ErkJggg==">
</a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-435-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-436-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<a aria-label="License" href="https://github.com/blitz-js/blitz/blob/main/LICENSE">
<img alt="" src="https://img.shields.io/npm/l/blitz.svg?style=for-the-badge&labelColor=000000&color=blue">
@@ -766,6 +766,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
</tr>
<tr>
<td align="center"><a href="https://www.drupal.org/u/kksandr"><img src="https://avatars.githubusercontent.com/u/132560756?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ksandr</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kksandr7" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=kksandr7" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Daidalos117"><img src="https://avatars.githubusercontent.com/u/15905269?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Roman Rajchert</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Daidalos117" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Daidalos117" title="Code">💻</a></td>
</tr>
</table>

View File

@@ -12,15 +12,15 @@
"schema": "prisma/schema.prisma"
},
"dependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/config": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/config": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10",
"@prisma/client": "^4.5.0",
"@tanstack/react-query": "5.51.1",
"blitz": "2.2.1",
"blitz": "3.0.1",
"flatted": "3.2.7",
"next": "15.0.1",
"prisma": "^4.5.0",

View File

@@ -23,14 +23,14 @@
]
},
"dependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/config": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/config": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10",
"@prisma/client": "6.1.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"next": "15.0.1",
"openid-client": "5.2.1",
"prisma": "6.1.0",

View File

@@ -24,14 +24,14 @@
]
},
"dependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/config": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/config": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10",
"@prisma/client": "6.1.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"next": "15.0.1",
"next-auth": "4.24.7",
"prisma": "6.1.0",

View File

@@ -16,14 +16,14 @@
"schema": "./db/schema.prisma"
},
"dependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/config": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/config": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0",
"@types/jest": "29.2.2",
"@types/passport-twitter": "1.0.37",
"blitz": "2.2.1",
"blitz": "3.0.1",
"jest": "29.3.0",
"jest-environment-jsdom": "29.3.0",
"next": "15.0.1",

View File

@@ -0,0 +1,90 @@
import {useSuspenseInfiniteQuery} from "@blitzjs/rpc"
import getInfiniteUsers from "src/queries/getInfiniteUsers"
import {useActionState} from "react"
function PageWithInfiniteQueryMutate(props) {
const [usersPages, extraInfo] = useSuspenseInfiniteQuery(
getInfiniteUsers,
(page = {take: 3, skip: 0}) => page,
{
getNextPageParam: (lastPage) => lastPage.nextPage,
initialPageParam: {take: 3, skip: 0},
},
)
const {isFetchingNextPage, fetchNextPage, hasNextPage, setQueryData} = extraInfo
const onOnContactSave = async (previousState, formData: FormData) => {
const name = formData.get("name") as string | null
await setQueryData(
(oldData) => {
if (!oldData) {
return {
pages: [],
pageParams: [],
}
}
return {
...oldData,
pages: oldData.pages.map((page, index) => {
if (index === 0) {
return {
...page,
users: [
{
id: Math.random(),
name,
role: "user",
email: `${name}@yopmail.com`,
createdAt: new Date(),
updatedAt: new Date(),
hashedPassword: "alsdklaskdoaskdokdo",
},
...page.users,
],
}
}
return page
}),
}
},
{refetch: false},
)
}
const [, formAction] = useActionState(onOnContactSave, {name: ""})
return (
<div>
<form action={formAction}>
<input type="text" name="name" placeholder="User name" />
<button type="submit">Add user</button>
</form>
{usersPages.map((usersPage) => (
<>
{usersPage?.users.map((u) => (
<div key={u.name}>
<p>name: {u.name}</p>
<p>role: {u.role}</p>
<p>email: {u.email}</p>
<hr />
</div>
))}
{usersPage.hasMore && (
<button onClick={() => fetchNextPage()} disabled={!hasNextPage || !!isFetchingNextPage}>
{isFetchingNextPage
? "Loading more..."
: hasNextPage
? "Load More"
: "Nothing more to load"}
</button>
)}
</>
))}
</div>
)
}
export default PageWithInfiniteQueryMutate

View File

@@ -17,14 +17,14 @@
"prisma:studio": "prisma studio"
},
"dependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/config": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/config": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10",
"@prisma/client": "6.1.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"delay": "5.0.0",
"next": "15.0.1",
"prisma": "6.1.0",

View File

@@ -17,11 +17,11 @@
"prisma:studio": "prisma studio"
},
"dependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/config": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/config": "3.0.1",
"@blitzjs/next": "3.0.1",
"@prisma/client": "6.1.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"lowdb": "3.0.0",
"next": "15.0.1",
"prisma": "6.1.0",

View File

@@ -16,11 +16,11 @@
"schema": "db/schema.prisma"
},
"dependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"lowdb": "2.1.0",
"next": "15.0.1",
"prisma": "6.1.0",
@@ -28,7 +28,7 @@
"react-dom": "19.0.0"
},
"devDependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/config": "3.0.1",
"@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",

View File

@@ -11,10 +11,10 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
},
"dependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"blitz": "2.2.1",
"@blitzjs/config": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"blitz": "3.0.1",
"next": "15.0.1",
"react": "19.0.0",
"react-dom": "19.0.0"

View File

@@ -17,12 +17,12 @@
"prisma:studio": "prisma studio"
},
"dependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/config": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/config": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"lowdb": "2.1.0",
"next": "15.0.1",
"prisma": "6.1.0",

View File

@@ -16,11 +16,11 @@
"prisma:studio": "prisma studio"
},
"dependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"lowdb": "3.0.0",
"next": "15.0.1",
"prisma": "6.1.0",
@@ -28,7 +28,7 @@
"react-dom": "19.0.0"
},
"devDependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/config": "3.0.1",
"@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",

View File

@@ -8,13 +8,13 @@
"clean": "rm -rf .turbo && rm -rf node_modules"
},
"dependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/config": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/config": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0",
"@tanstack/react-query": "5.51.1",
"blitz": "2.2.1",
"blitz": "3.0.1",
"next": "15.0.1",
"prisma": "6.1.0",
"react": "19.0.0",

View File

@@ -16,10 +16,10 @@
"schema": "db/schema.prisma"
},
"dependencies": {
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"lowdb": "3.0.0",
"next": "15.0.1",
"prisma": "6.1.0",
@@ -27,7 +27,7 @@
"react-dom": "19.0.0"
},
"devDependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/config": "3.0.1",
"@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",

View File

@@ -7,10 +7,10 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
},
"dependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"blitz": "2.2.1",
"@blitzjs/config": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"blitz": "3.0.1",
"next": "15.0.1",
"react": "19.0.0",
"react-dom": "19.0.0"

View File

@@ -7,10 +7,10 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
},
"dependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"blitz": "2.2.1",
"@blitzjs/config": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"blitz": "3.0.1",
"next": "15.0.1",
"react": "19.0.0",
"react-dom": "19.0.0"

View File

@@ -16,11 +16,11 @@
"schema": "db/schema.prisma"
},
"dependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"lowdb": "3.0.0",
"next": "15.0.1",
"prisma": "6.1.0",
@@ -28,7 +28,7 @@
"react-dom": "19.0.0"
},
"devDependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/config": "3.0.1",
"@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",

View File

@@ -3,9 +3,9 @@
"version": "0.0.0",
"private": true,
"devDependencies": {
"@blitzjs/config": "workspace:2.2.1",
"@blitzjs/next": "workspace:2.2.1",
"@blitzjs/rpc": "workspace:2.2.1",
"@blitzjs/config": "workspace:3.0.1",
"@blitzjs/next": "workspace:3.0.1",
"@blitzjs/rpc": "workspace:3.0.1",
"@tanstack/react-query": "4.13.0",
"@testing-library/react": "16.0.1",
"@types/express": "4.17.13",

View File

@@ -1,5 +1,24 @@
# @blitzjs/auth
## 3.0.1
### Patch Changes
- 816330b9d: fix: Overriden custom cookies used inside `withBlitzAuth`
- blitz@3.0.1
## 3.0.0
### Major Changes
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
### Patch Changes
- Updated dependencies [ce1a603b2]
- Updated dependencies [1610c73f9]
- blitz@3.0.0
## 2.2.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@blitzjs/auth",
"version": "2.2.1",
"version": "3.0.1",
"homepage": "https://blitzjs.com/",
"repository": {
"type": "git",
@@ -50,7 +50,7 @@
"url": "0.11.0"
},
"peerDependencies": {
"blitz": "2.2.1",
"blitz": "3.0.1",
"next": "*",
"next-auth": "*",
"secure-password": "4.0.0"
@@ -67,14 +67,14 @@
}
},
"devDependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/config": "3.0.1",
"@testing-library/react": "16.0.1",
"@types/cookie": "0.4.1",
"@types/debug": "4.1.7",
"@types/jsonwebtoken": "8.5.8",
"@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "npm:types-react-dom@19.0.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"next": "15.0.1",
"next-auth": "4.24.7",
"react": "19.0.0",

View File

@@ -466,7 +466,7 @@ export class SessionContextClass implements SessionContext {
}
const cookieHeaders = this._headers.get("set-cookie")
if (response instanceof Response) {
response.headers.set("Set-Cookie", cookieHeaders!)
response.headers.append("Set-Cookie", cookieHeaders!)
} else {
response.setHeader("Set-Cookie", splitCookiesString(cookieHeaders!))
}

View File

@@ -1,5 +1,26 @@
# @blitzjs/next
## 3.0.1
### Patch Changes
- @blitzjs/rpc@3.0.1
- blitz@3.0.1
## 3.0.0
### Major Changes
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
### Patch Changes
- Updated dependencies [11c9f00eb]
- Updated dependencies [ce1a603b2]
- Updated dependencies [1610c73f9]
- @blitzjs/rpc@3.0.0
- blitz@3.0.0
## 2.2.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@blitzjs/next",
"version": "2.2.1",
"version": "3.0.1",
"homepage": "https://blitzjs.com/",
"repository": {
"type": "git",
@@ -29,7 +29,7 @@
"eslint.js"
],
"dependencies": {
"@blitzjs/rpc": "2.2.1",
"@blitzjs/rpc": "3.0.1",
"@types/hoist-non-react-statics": "3.3.1",
"copy-webpack-plugin": "11.0.0",
"debug": "4.3.3",
@@ -39,13 +39,13 @@
"supports-color": "8.1.1"
},
"peerDependencies": {
"blitz": "2.2.1",
"blitz": "3.0.1",
"next": "*",
"react": "*",
"tslog": "4.9.0"
},
"devDependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/config": "3.0.1",
"@testing-library/dom": "8.13.0",
"@testing-library/jest-dom": "5.16.3",
"@testing-library/react": "16.0.1",
@@ -55,7 +55,7 @@
"@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "npm:types-react-dom@19.0.0",
"@types/testing-library__react-hooks": "4.0.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"cross-spawn": "7.0.3",
"find-up": "4.1.0",
"next": "15.0.1",

View File

@@ -1,5 +1,24 @@
# @blitzjs/rpc
## 3.0.1
### Patch Changes
- blitz@3.0.1
## 3.0.0
### Major Changes
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
### Patch Changes
- 11c9f00eb: fix(4407): setQueryData with useInfiniteQuery
- Updated dependencies [ce1a603b2]
- Updated dependencies [1610c73f9]
- blitz@3.0.0
## 2.2.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@blitzjs/rpc",
"version": "2.2.1",
"version": "3.0.1",
"homepage": "https://blitzjs.com/",
"repository": {
"type": "git",
@@ -37,18 +37,18 @@
},
"peerDependencies": {
"@tanstack/query-core": "5.51.1",
"blitz": "2.2.1",
"blitz": "3.0.1",
"next": "*",
"react": "*"
},
"devDependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/config": "2.2.1",
"@blitzjs/auth": "3.0.1",
"@blitzjs/config": "3.0.1",
"@tanstack/query-core": "5.51.1",
"@types/debug": "4.1.7",
"@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "npm:types-react-dom@19.0.0",
"blitz": "2.2.1",
"blitz": "3.0.1",
"next": "15.0.1",
"react": "19.0.0",
"react-dom": "19.0.0",

View File

@@ -39,6 +39,7 @@ import {
sanitizeQuery,
sanitizeMutation,
getInfiniteQueryKey,
QueryType,
} from "../utils"
import {useRouter} from "next/compat/router"
@@ -282,7 +283,7 @@ export function usePaginatedQuery<
// -------------------------
export interface RestInfiniteResult<TResult, TError>
extends Omit<UseInfiniteQueryResult<TResult, TError>, "data">,
QueryCacheFunctions<TResult> {
QueryCacheFunctions<InfiniteData<TResult>> {
pageParams: any
}
@@ -355,7 +356,7 @@ export function useInfiniteQuery<
const rest = {
...queryRest,
...getQueryCacheFunctions<FirstParam<T>, TResult, T>(queryFn, getQueryParams),
...getQueryCacheFunctions<FirstParam<T>, InfiniteData<TResult>, T>(queryFn, getQueryParams),
pageParams: infiniteQueryData?.pageParams,
}
@@ -367,7 +368,7 @@ export function useInfiniteQuery<
// -------------------------
export interface RestInfiniteResult<TResult, TError>
extends Omit<UseInfiniteQueryResult<TResult, TError>, "data">,
QueryCacheFunctions<TResult> {
QueryCacheFunctions<InfiniteData<TResult>> {
pageParams: any
}
@@ -449,7 +450,11 @@ export function useSuspenseInfiniteQuery<
const rest = {
...queryRest,
...getQueryCacheFunctions<FirstParam<T>, TResult, T>(queryFn, getQueryParams),
...getQueryCacheFunctions<FirstParam<T>, InfiniteData<TResult>, T>(
queryFn,
getQueryParams,
QueryType.INFINITE,
),
pageParams: infiniteQueryData?.pageParams,
}

View File

@@ -65,9 +65,10 @@ export interface QueryCacheFunctions<T> {
export const getQueryCacheFunctions = <TInput, TResult, T extends AsyncFunc>(
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
params: TInput,
queryType: QueryType = QueryType.STANDARD,
): QueryCacheFunctions<TResult> => ({
setQueryData: (newData, opts = {refetch: true}) => {
return setQueryData(resolver, params, newData, opts)
return setQueryData(resolver, params, newData, opts, queryType)
},
})
@@ -170,16 +171,22 @@ export const invalidateQuery: InvalidateQuery = (resolver = undefined, ...params
})
}
export enum QueryType {
STANDARD = "STANDARD",
INFINITE = "INFINITE",
}
export function setQueryData<TInput, TResult, T extends AsyncFunc>(
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
params: TInput,
newData: TResult | ((oldData: TResult | undefined) => TResult | undefined),
opts: MutateOptions = {refetch: true},
queryType: QueryType = QueryType.STANDARD,
): Promise<void | ReturnType<ReturnType<typeof getQueryClient>["invalidateQueries"]>> {
if (typeof resolver === "undefined") {
throw new Error("setQueryData is missing the first argument - it must be a resolver function")
}
const queryKey = getQueryKey(resolver, params)
const getQueryKeyFn = queryType === QueryType.STANDARD ? getQueryKey : getInfiniteQueryKey
const queryKey = getQueryKeyFn(resolver, params)
return new Promise((res) => {
getQueryClient().setQueryData(queryKey, newData)

View File

@@ -1,5 +1,24 @@
# blitz
## 3.0.1
### Patch Changes
- @blitzjs/generator@3.0.1
## 3.0.0
### Major Changes
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
### Patch Changes
- 1610c73f9: Due to increased maintenance required to keep recipes up to date. Removing them from the codebase from v3
- Updated dependencies [ce1a603b2]
- Updated dependencies [0a257e915]
- @blitzjs/generator@3.0.0
## 2.2.1
### Patch Changes

View File

@@ -1,7 +1,7 @@
import {BuildConfig} from "unbuild"
const config: BuildConfig = {
entries: ["./src/index-browser", "./src/index-server", "./src/cli/index", "./src/installer"],
entries: ["./src/index-browser", "./src/index-server", "./src/cli/index"],
externals: ["index-browser.cjs", "index-browser.mjs", "index.cjs", "zod", "react"],
declaration: true,
rollup: {

View File

@@ -1 +0,0 @@
export * from "./dist/installer"

View File

@@ -1 +0,0 @@
module.exports = require("./dist/installer.cjs")

View File

@@ -1,6 +1,6 @@
{
"name": "blitz",
"version": "2.2.1",
"version": "3.0.1",
"homepage": "https://blitzjs.com/",
"repository": {
"type": "git",
@@ -22,7 +22,6 @@
"sideEffects": false,
"license": "MIT",
"files": [
"installer.*",
"dist/**",
"bin/**"
],
@@ -30,7 +29,7 @@
"blitz": "bin/blitz"
},
"dependencies": {
"@blitzjs/generator": "2.2.1",
"@blitzjs/generator": "3.0.1",
"@mrleebo/prisma-ast": "0.4.1",
"@types/global-agent": "2.1.1",
"arg": "5.0.1",
@@ -80,7 +79,7 @@
"watchpack": "2.1.1"
},
"devDependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/config": "3.0.1",
"@types/cookie": "0.4.1",
"@types/cross-spawn": "6.0.2",
"@types/debug": "4.1.7",

View File

@@ -1,325 +0,0 @@
import arg from "arg"
import {CliCommand} from "../index"
import prompts from "prompts"
import {bootstrap} from "global-agent"
import {baseLogger, log} from "../../logging"
import Debug from "debug"
const debug = Debug("blitz:cli")
import {join, resolve, dirname} from "path"
import {Stream} from "stream"
import {promisify} from "util"
import {RecipeCLIFlags, RecipeExecutor} from "../../installer"
import {setupTsnode} from "../utils/setup-ts-node"
import {isInternalBlitzMonorepoDevelopment} from "../utils/helpers"
import findUp from "find-up"
import resolveFrom from "resolve-from"
import {findNodeModulesRoot} from "../utils/find-node-modules"
interface GlobalAgent {
HTTP_PROXY?: string
HTTPS_PROXY?: string
NO_PROXY?: string
}
declare global {
var GLOBAL_AGENT: GlobalAgent
}
const args = arg(
{
// Types
"--help": Boolean,
"--env": String,
"--yes": Boolean,
// Aliases
"-e": "--env",
"-y": "--yes",
},
{
permissive: true,
},
)
const pipeline = promisify(Stream.pipeline)
const got = async (url: string) => {
return require("got")(url).catch((e: any) => {
if (e.response.statusCode === 403) {
baseLogger().error(e.response.body)
} else {
return e
}
})
}
const gotJSON = async (url: string) => {
debug("[gotJSON] Downloading json from ", url)
const res = await got(url)
return JSON.parse(res.body)
}
const isUrlValid = async (url: string) => {
return (await got(url).catch((e) => e)).statusCode === 200
}
const requireJSON = (file: string) => {
return JSON.parse(require("fs-extra").readFileSync(file).toString("utf-8"))
}
const checkLockFileExists = async (filename: string) => {
const dotBlitz = join(await findNodeModulesRoot(process.cwd()), ".blitz")
return require("fs-extra").existsSync(resolve(join(dotBlitz, "..", "..", filename)))
}
const GH_ROOT = "https://github.com/"
const API_ROOT = "https://api.github.com/repos/"
const RAW_ROOT = "https://raw.githubusercontent.com/"
const CODE_ROOT = "https://codeload.github.com/"
export enum RecipeLocation {
Local,
Remote,
}
interface RecipeMeta {
path: string
subdirectory?: string
location: RecipeLocation
}
interface Tree {
path: string
mode: string
type: string
sha: string
size: number
url: string
}
interface GithubRepoAPITrees {
sha: string
url: string
tree: Tree[]
truncated: boolean
}
const getOfficialRecipeList = async (): Promise<string[]> => {
return await gotJSON(`${API_ROOT}blitz-js/blitz/git/trees/main?recursive=1`).then(
(release: GithubRepoAPITrees) =>
release.tree.reduce((recipesList: string[], item) => {
const filePath = item.path.split("/")
const [directory, recipeName] = filePath
if (
directory === "recipes" &&
filePath.length === 2 &&
item.type === "tree" &&
recipeName
) {
recipesList.push(recipeName)
}
return recipesList
}, []),
)
}
const normalizeRecipePath = (recipeArg: string): RecipeMeta => {
const isNativeRecipe = /^([\w\-_]*)$/.test(recipeArg)
const isUrlRecipe = recipeArg.startsWith(GH_ROOT)
const isGitHubShorthandRecipe = /^([\w-_]*)\/([\w-_]*)$/.test(recipeArg)
if (isNativeRecipe || isUrlRecipe || isGitHubShorthandRecipe) {
let repoUrl
let subdirectory
switch (true) {
case isUrlRecipe:
repoUrl = recipeArg
break
case isNativeRecipe:
repoUrl = `${GH_ROOT}blitz-js/blitz`
subdirectory = `recipes/${recipeArg}`
break
case isGitHubShorthandRecipe:
repoUrl = `${GH_ROOT}${recipeArg}`
break
default:
throw new Error(
"should be impossible, the 3 cases are the only way to get into this switch",
)
}
return {
path: repoUrl,
subdirectory,
location: RecipeLocation.Remote,
}
} else {
return {
path: recipeArg,
location: RecipeLocation.Local,
}
}
}
const cloneRepo = async (
repoFullName: string,
defaultBranch: string,
subdirectory?: string,
): Promise<string> => {
debug("[cloneRepo] starting...")
const dotBlitz = join(await findNodeModulesRoot(process.cwd()), ".blitz")
const recipeDir = join(dotBlitz, "..", "..", "recipe-install")
// clean up from previous run in case of error
require("rimraf").sync(recipeDir)
require("fs-extra").mkdirsSync(recipeDir)
process.chdir(recipeDir)
debug("Extracting recipe to ", recipeDir)
const repoName = repoFullName.split("/")[1]
// `tar` top-level filter is `${repoName}-${defaultBranch}`, and then we want to get our recipe path
// within that folder
const extractPath = subdirectory ? [`${repoName}-${defaultBranch}/${subdirectory}`] : undefined
const depth = subdirectory ? subdirectory.split("/").length + 1 : 1
await pipeline(
require("got").stream(`${CODE_ROOT}${repoFullName}/tar.gz/${defaultBranch}`),
require("tar").extract({strip: depth}, extractPath),
)
return recipeDir
}
const installRecipeAtPath = async (
recipePath: string,
...runArgs: Parameters<RecipeExecutor<any>["run"]>
) => {
const recipe = require(recipePath).default as RecipeExecutor<any>
await recipe.run(...runArgs)
}
const setupProxySupport = async () => {
const httpProxy = process.env.http_proxy || process.env.HTTP_PROXY
const httpsProxy = process.env.https_proxy || process.env.HTTPS_PROXY
const noProxy = process.env.no_proxy || process.env.NO_PROXY
if (httpProxy || httpsProxy) {
globalThis.GLOBAL_AGENT = {
HTTP_PROXY: httpProxy,
HTTPS_PROXY: httpsProxy,
NO_PROXY: noProxy,
}
bootstrap()
}
}
const install: CliCommand = async () => {
setupTsnode()
let selectedRecipe: string | null = args._[1] ? `${args._[1]}` : null
await setupProxySupport()
if (!selectedRecipe) {
const officialRecipeList = await getOfficialRecipeList()
const res = await prompts({
type: "select",
name: "recipeName",
message: "Select a recipe to install",
choices: officialRecipeList.map((r) => {
return {title: r, value: r}
}),
})
selectedRecipe = res.recipeName
}
if (selectedRecipe) {
const recipeInfo = normalizeRecipePath(selectedRecipe)
// Take all the args after the recipe string
//
// ['material-ui', '--yes', 'prop=true']
// --> ['material-ui', 'prop=true']
// --> ['prop=true']
// --> { prop: 'true' }
const cliArgs = args._.filter((arg) => !arg.startsWith("--"))
.slice(2)
.reduce(
(acc, arg) => ({
...acc,
[`${arg.split("=")[0]}`]: arg.split("=")[1] ? JSON.parse(`"${arg.split("=")[1]}"`) : true, // if no value is provided, assume it's a boolean flag
}),
{},
)
const cliFlags: RecipeCLIFlags = {
yesToAll: args["--yes"] || false,
}
const chalk = (await import("chalk")).default
if (recipeInfo.location === RecipeLocation.Remote) {
const apiUrl = recipeInfo.path.replace(GH_ROOT, API_ROOT)
const rawUrl = recipeInfo.path.replace(GH_ROOT, RAW_ROOT)
const repoInfo = await gotJSON(apiUrl)
const packageJsonPath = join(
`${rawUrl}`,
repoInfo.default_branch,
recipeInfo.subdirectory ?? "",
"package.json",
)
if (!(await isUrlValid(packageJsonPath))) {
debug("Url is invalid for ", packageJsonPath)
baseLogger().error(`Could not find recipe "${args._[1]}"\n`)
console.log(`${chalk.bold("Please provide one of the following:")}
1. The name of a recipe to install (e.g. "tailwind")
${chalk.dim("- Available recipes listed at https://github.com/blitz-js/blitz/tree/main/recipes")}
2. The full name of a GitHub repository (e.g. "blitz-js/example-recipe"),
3. A full URL to a Github repository (e.g. "https://github.com/blitz-js/example-recipe"), or
4. A file path to a locally-written recipe.\n`)
process.exit(1)
} else {
let spinner = log.spinner(`Cloning GitHub repository for ${selectedRecipe} recipe`).start()
const recipeRepoPath = await cloneRepo(
repoInfo.full_name,
repoInfo.default_branch,
recipeInfo.subdirectory,
)
spinner.stop()
spinner = log.spinner("Installing package.json dependencies").start()
let pkgManager = "npm"
let installArgs = ["install", "--legacy-peer-deps", "--ignore-scripts"]
if (await checkLockFileExists("yarn.lock")) {
pkgManager = "yarn"
installArgs = ["install", "--ignore-scripts"]
} else if (await checkLockFileExists("pnpm-lock.yaml")) {
pkgManager = "pnpm"
installArgs = ["install", "--ignore-scripts"]
}
await new Promise((resolve) => {
const installProcess = require("cross-spawn")(pkgManager, installArgs)
installProcess.on("exit", resolve)
})
spinner.stop()
const recipePackageMain = requireJSON("./package.json").main
const recipeEntry = resolve(recipePackageMain)
process.chdir(join(process.cwd(), ".."))
await installRecipeAtPath(recipeEntry, cliArgs, cliFlags)
require("rimraf").sync(recipeRepoPath)
}
} else {
try {
await installRecipeAtPath(resolve(`${args._[1]}`), cliArgs, cliFlags)
} catch (err) {
if (err instanceof Error) {
throw new Error(err.message)
}
console.log(err)
}
}
}
}
export {install}

View File

@@ -35,7 +35,6 @@ const commands = {
generate: () => import("./commands/generate").then((i) => i.generate),
codegen: () => import("./commands/codegen").then((i) => i.codegen),
db: () => import("./commands/db").then((i) => i.db),
install: () => import("./commands/install").then((i) => i.install),
console: () => import("./commands/console").then((i) => i.consoleREPL),
routes: () => import("./commands/routes").then((i) => i.routes),
}
@@ -47,7 +46,6 @@ const aliases: Record<string, keyof typeof commands> = {
e: "export",
n: "new",
g: "generate",
i: "install",
c: "console",
r: "routes",
}

View File

@@ -1 +0,0 @@
export * from "../src/installer/index"

View File

@@ -1,12 +0,0 @@
import {Text} from "ink"
import * as React from "react"
import {Newline} from "./newline"
export const EnterToContinue: React.FC<{message?: string}> = ({
message = "Press ENTER to continue",
}) => (
<>
<Newline />
<Text bold>{message}</Text>
</>
)

View File

@@ -1,6 +0,0 @@
import {Box} from "ink"
import * as React from "react"
export const Newline: React.FC<{count?: number}> = ({count = 1}) => {
return <Box paddingBottom={count} />
}

View File

@@ -1,198 +0,0 @@
import {spawn} from "cross-spawn"
import * as fs from "fs-extra"
import {Box, Text} from "ink"
import Spinner from "ink-spinner"
import * as path from "path"
import * as React from "react"
import {Newline} from "../components/newline"
import {RecipeCLIArgs} from "../types"
import {useEnterToContinue} from "../utils/use-enter-to-continue"
import {useUserInput} from "../utils/use-user-input"
import {IExecutor, executorArgument, ExecutorConfig, getExecutorArgument} from "./executor"
interface NpmPackage {
name: string
// defaults to latest published
version?: string
// defaults to false
isDevDep?: boolean
}
export interface Config extends ExecutorConfig {
packages: executorArgument<NpmPackage[]>
}
export function isAddDependencyExecutor(executor: ExecutorConfig): executor is Config {
return (executor as Config).packages !== undefined
}
export const type = "add-dependency"
function Package({pkg, loading}: {pkg: NpmPackage; loading: boolean}) {
return (
<Text>
{` `}
{loading ? <Spinner /> : "📦"}
{` ${pkg.name}@${pkg.version}`}
</Text>
)
}
const DependencyList = ({
lede = "Hang tight! Installing dependencies...",
depsLoading = false,
devDepsLoading = false,
packages,
}: {
lede?: string
depsLoading?: boolean
devDepsLoading?: boolean
packages: NpmPackage[]
}) => {
const prodPackages = packages.filter((p) => !p.isDevDep)
const devPackages = packages.filter((p) => p.isDevDep)
return (
<Box flexDirection="column">
<Text>{lede}</Text>
<Newline />
{prodPackages.length ? <Text>Dependencies to be installed:</Text> : null}
{prodPackages.map((pkg) => (
<Package key={pkg.name} pkg={pkg} loading={depsLoading} />
))}
<Newline />
{devPackages.length ? <Text>Dev Dependencies to be installed:</Text> : null}
{devPackages.map((pkg) => (
<Package key={pkg.name} pkg={pkg} loading={devDepsLoading} />
))}
</Box>
)
}
/**
* Exported for unit testing purposes
*/
export function getPackageManager() {
if (fs.existsSync(path.resolve("yarn.lock"))) {
return "yarn"
} else if (fs.existsSync(path.resolve("pnpm-lock.yaml"))) {
return "pnpm"
} else {
return "npm"
}
}
/**
* Exported for unit testing purposes
*/
export async function installPackages(packages: NpmPackage[], isDev = false) {
const packageManager = getPackageManager()
const isNPM = packageManager === "npm"
const pkgInstallArg = isNPM ? "install" : "add"
const args: string[] = [pkgInstallArg]
if (isDev) {
args.push(isNPM ? "--save-dev" : "-D")
}
packages.forEach((pkg) => {
pkg.version ? args.push(`${pkg.name}@${pkg.version}`) : args.push(pkg.name)
})
await new Promise((resolve) => {
const cp = spawn(packageManager, args, {
stdio: ["inherit", "pipe", "pipe"],
})
cp.on("exit", resolve)
})
}
export const Commit: IExecutor["Commit"] = ({cliArgs, cliFlags, step, onChangeCommitted}) => {
const userInput = useUserInput(cliFlags)
const [depsInstalled, setDepsInstalled] = React.useState(false)
const [devDepsInstalled, setDevDepsInstalled] = React.useState(false)
const handleChangeCommitted = React.useCallback(() => {
const packages = (step as Config).packages
const dependencies = packages.length === 1 ? "dependency" : "dependencies"
onChangeCommitted(`Installed ${packages.length} ${dependencies}`)
}, [onChangeCommitted, step])
React.useEffect(() => {
async function installDeps() {
const packagesToInstall = getExecutorArgument((step as Config).packages, cliArgs).filter(
(p) => !p.isDevDep,
)
await installPackages(packagesToInstall)
setDepsInstalled(true)
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
installDeps()
}, [cliArgs, step])
React.useEffect(() => {
if (!depsInstalled) return
async function installDevDeps() {
const packagesToInstall = getExecutorArgument((step as Config).packages, cliArgs).filter(
(p) => p.isDevDep,
)
await installPackages(packagesToInstall, true)
setDevDepsInstalled(true)
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
installDevDeps()
}, [cliArgs, depsInstalled, step])
React.useEffect(() => {
if (depsInstalled && devDepsInstalled) {
handleChangeCommitted()
}
}, [depsInstalled, devDepsInstalled, handleChangeCommitted])
if (!isAddDependencyExecutor(step)) {
onChangeCommitted()
return null
}
const childProps: CommitChildProps = {
depsInstalled,
devDepsInstalled,
handleChangeCommitted,
step,
cliArgs,
}
if (userInput) return <CommitWithInput {...childProps} />
else return <CommitWithoutInput {...childProps} />
}
interface CommitChildProps {
depsInstalled: boolean
devDepsInstalled: boolean
handleChangeCommitted: () => void
step: Config
cliArgs: RecipeCLIArgs
}
const CommitWithInput = ({
depsInstalled,
devDepsInstalled,
handleChangeCommitted,
step,
cliArgs,
}: CommitChildProps) => {
useEnterToContinue(handleChangeCommitted, depsInstalled && devDepsInstalled)
return (
<DependencyList
depsLoading={!depsInstalled}
devDepsLoading={!devDepsInstalled}
packages={getExecutorArgument(step.packages, cliArgs)}
/>
)
}
const CommitWithoutInput = ({depsInstalled, devDepsInstalled, step, cliArgs}: CommitChildProps) => (
<DependencyList
depsLoading={!depsInstalled}
devDepsLoading={!devDepsInstalled}
packages={getExecutorArgument(step.packages, cliArgs)}
/>
)

View File

@@ -1,71 +0,0 @@
import {Box, Text} from "ink"
import * as React from "react"
import {Newline} from "../components/newline"
import {RecipeCLIArgs, RecipeCLIFlags} from "../types"
export interface ExecutorConfig {
successIcon?: string
stepId: string | number
stepName: string
stepType: string
// a bit to display to the user to give context to the change
explanation: string
}
export interface IExecutor {
type: string
Propose?: React.FC<{
step: ExecutorConfig
onProposalAccepted: (data?: any) => void
cliArgs: RecipeCLIArgs
cliFlags: RecipeCLIFlags
}>
Commit: React.FC<{
step: ExecutorConfig
proposalData?: any
onChangeCommitted: (data?: any) => void
cliArgs: RecipeCLIArgs
cliFlags: RecipeCLIFlags
}>
}
type dynamicExecutorArgument<T> = (cliArgs: RecipeCLIArgs) => T
function isDynamicExecutorArgument<T>(
input: executorArgument<T>,
): input is dynamicExecutorArgument<T> {
return typeof (input as dynamicExecutorArgument<T>) === "function"
}
export type executorArgument<T> = T | dynamicExecutorArgument<T>
export function Frontmatter({executor}: {executor: ExecutorConfig}) {
const lineLength = executor.stepName.length + 6
const verticalBorder = `+${new Array(lineLength).fill("").join("")}+`
return (
<Box flexDirection="column" paddingBottom={1}>
<Newline />
<Box flexDirection="column">
<Text color="#8a3df0" bold>
{verticalBorder}
</Text>
<Text color="#8a3df0" bold>
&nbsp;&nbsp;&nbsp;{executor.stepName}&nbsp;&nbsp;&nbsp;
</Text>
<Text color="#8a3df0" bold>
{verticalBorder}
</Text>
</Box>
<Text color="gray" italic>
{executor.explanation}
</Text>
</Box>
)
}
export function getExecutorArgument<T>(input: executorArgument<T>, cliArgs: RecipeCLIArgs): T {
if (isDynamicExecutorArgument(input)) {
return input(cliArgs)
}
return input
}

View File

@@ -1,41 +0,0 @@
// import { prompt as enquirer } from 'enquirer'
import prompts from "prompts"
enum SearchType {
file,
directory,
}
interface FilePromptOptions {
globFilter?: string
getChoices?(context: any): string[]
searchType?: SearchType
context: any
}
async function getMatchingFiles(filter: string = ""): Promise<string[]> {
let {globby} = await import("globby")
return globby(filter, {expandDirectories: true})
}
export async function filePrompt(options: FilePromptOptions): Promise<string> {
const choices = options.getChoices
? options.getChoices(options.context)
: await getMatchingFiles(options.globFilter)
if (choices.length === 1) {
return `${choices[0]}`
}
const results: {file: string} = await prompts({
type: "autocomplete",
name: "file",
message: "Select the target file",
// @ts-ignore
limit: 10,
choices: choices.map((choice) => {
return {title: choice, value: choice}
}),
})
return results.file
}

View File

@@ -1,183 +0,0 @@
import {createPatch} from "diff"
import * as fs from "fs-extra"
import {Box, Text} from "ink"
import Spinner from "ink-spinner"
import * as React from "react"
import {EnterToContinue} from "../components/enter-to-continue"
import {RecipeCLIArgs} from "../types"
import {
processFile,
stringProcessFile,
StringTransformer,
transform,
Transformer,
TransformStatus,
} from "../utils/transform"
import {useEnterToContinue} from "../utils/use-enter-to-continue"
import {useUserInput} from "../utils/use-user-input"
import {IExecutor, executorArgument, ExecutorConfig, getExecutorArgument} from "./executor"
import {filePrompt} from "./file-prompt"
export interface Config extends ExecutorConfig {
selectTargetFiles?(cliArgs: RecipeCLIArgs): any[]
singleFileSearch?: executorArgument<string>
transform?: Transformer
transformPlain?: StringTransformer
}
export function isFileTransformExecutor(executor: ExecutorConfig): executor is Config {
return (
(executor as Config).transform !== undefined ||
(executor as Config).transformPlain !== undefined
)
}
export const type = "file-transform"
export const Propose: IExecutor["Propose"] = ({cliArgs, cliFlags, onProposalAccepted, step}) => {
const userInput = useUserInput(cliFlags)
const [diff, setDiff] = React.useState<string | null>(null)
const [error, setError] = React.useState<Error | null>(null)
const [filePath, setFilePath] = React.useState("")
const [proposalAccepted, setProposalAccepted] = React.useState(false)
const acceptProposal = React.useCallback(() => {
setProposalAccepted(true)
onProposalAccepted(filePath)
}, [onProposalAccepted, filePath])
React.useEffect(() => {
async function generateDiff() {
const fileToTransform: string = await filePrompt({
context: cliArgs,
globFilter: getExecutorArgument((step as Config).singleFileSearch, cliArgs),
getChoices: (step as Config).selectTargetFiles,
})
setFilePath(fileToTransform)
const originalFile = fs.readFileSync(fileToTransform).toString("utf-8")
const newFile = await ((step as Config).transformPlain
? stringProcessFile(originalFile, (step as Config).transformPlain!)
: processFile(originalFile, (step as Config).transform!))
return createPatch(fileToTransform, originalFile, newFile)
}
generateDiff().then(setDiff, setError)
}, [cliArgs, step])
// Let the renderer deal with errors from file transformers, otherwise the
// process would just hang.
if (error) throw error
if (!diff) {
return (
<Box>
<Text>
<Spinner />
Generating file diff...
</Text>
</Box>
)
}
const childProps: ProposeChildProps = {
diff,
filePath,
proposalAccepted,
acceptProposal,
}
if (userInput) return <ProposeWithInput {...childProps} />
else return <ProposeWithoutInput {...childProps} />
}
interface ProposeChildProps {
diff: string
filePath: string
proposalAccepted: boolean
acceptProposal: () => void
}
const Diff = ({diff}: {diff: string}) => (
<>
{diff
.split("\n")
.slice(2)
.map((line, idx) => {
let styleProps: any = {}
if (line.startsWith("-") && !line.startsWith("---")) {
styleProps.bold = true
styleProps.color = "red"
} else if (line.startsWith("+") && !line.startsWith("+++")) {
styleProps.bold = true
styleProps.color = "green"
}
return (
<Text {...styleProps} key={idx}>
{line}
</Text>
)
})}
</>
)
const ProposeWithInput = ({
diff,
filePath,
proposalAccepted,
acceptProposal,
}: ProposeChildProps) => {
useEnterToContinue(acceptProposal, filePath !== "" && !proposalAccepted)
return (
<Box flexDirection="column">
<Diff diff={diff} />
<EnterToContinue message="The above changes will be made. Press ENTER to continue" />
</Box>
)
}
const ProposeWithoutInput = ({
diff,
filePath,
proposalAccepted,
acceptProposal,
}: ProposeChildProps) => {
React.useEffect(() => {
if (filePath !== "" && !proposalAccepted) {
acceptProposal()
}
}, [acceptProposal, filePath, proposalAccepted])
return (
<Box flexDirection="column">
<Diff diff={diff} />
</Box>
)
}
export const Commit: IExecutor["Commit"] = ({onChangeCommitted, proposalData: filePath, step}) => {
React.useEffect(() => {
void (async function () {
const results = await transform(
async (original) =>
await ((step as Config).transformPlain
? stringProcessFile(original, (step as Config).transformPlain!)
: processFile(original, (step as Config).transform!)),
[filePath],
)
if (results.some((r) => r.status === TransformStatus.Failure)) {
console.error(results)
}
onChangeCommitted(`Modified file: ${filePath}`)
})()
}, [filePath, onChangeCommitted, step])
return (
<Box>
<Spinner />
<Text>Applying file changes</Text>
</Box>
)
}

View File

@@ -1,136 +0,0 @@
import {Generator, GeneratorOptions, SourceRootType} from "@blitzjs/generator"
import {Box, Text} from "ink"
import {useEffect, useState} from "react"
import * as React from "react"
import {EnterToContinue} from "../components/enter-to-continue"
import {useEnterToContinue} from "../utils/use-enter-to-continue"
import {useUserInput} from "../utils/use-user-input"
import {IExecutor, executorArgument, ExecutorConfig, getExecutorArgument} from "./executor"
export interface Config extends ExecutorConfig {
targetDirectory?: executorArgument<string>
templatePath: executorArgument<string>
templateValues: executorArgument<{[key: string]: string}>
destinationPathPrompt?: executorArgument<string>
}
export function isNewFileExecutor(executor: ExecutorConfig): executor is Config {
return (executor as Config).templatePath !== undefined
}
export const type = "new-file"
interface TempGeneratorOptions extends GeneratorOptions {
targetDirectory?: string
templateRoot: string
templateValues: any
run?: any
}
class TempGenerator extends Generator<TempGeneratorOptions> {
sourceRoot: SourceRootType
targetDirectory: string
templateValues: any
returnResults = true
constructor(options: TempGeneratorOptions) {
super(options)
this.sourceRoot = {type: "absolute", path: options.templateRoot}
this.templateValues = options.templateValues
this.targetDirectory = options.targetDirectory || "."
}
getTemplateValues() {
return this.templateValues
}
getTargetDirectory() {
return this.targetDirectory
}
}
export const Commit: IExecutor["Commit"] = ({cliArgs, cliFlags, onChangeCommitted, step}) => {
const userInput = useUserInput(cliFlags)
const generatorArgs = React.useMemo(
() => ({
destinationRoot: ".",
targetDirectory: getExecutorArgument((step as Config).targetDirectory, cliArgs),
templateRoot: getExecutorArgument((step as Config).templatePath, cliArgs),
templateValues: getExecutorArgument((step as Config).templateValues, cliArgs),
}),
[cliArgs, step],
)
const [fileCreateOutput, setFileCreateOutput] = useState("")
const [changeCommited, setChangeCommited] = useState(false)
const fileCreateLines = fileCreateOutput.split("\n")
const handleChangeCommitted = React.useCallback(() => {
setChangeCommited(true)
onChangeCommitted(
`Successfully created ${fileCreateLines
.map((l) => l.split(" ").slice(1).join("").trim())
.join(", ")}`,
)
}, [fileCreateLines, onChangeCommitted])
useEffect(() => {
async function createNewFiles() {
if (!fileCreateOutput) {
const generator = new TempGenerator(generatorArgs)
const results = (await generator.run()) as unknown as string
setFileCreateOutput(results)
}
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
createNewFiles()
}, [fileCreateOutput, generatorArgs])
const childProps: CommitChildProps = {
changeCommited,
fileCreateOutput,
handleChangeCommitted,
}
if (userInput) return <CommitWithInput {...childProps} />
else return <CommitWithoutInput {...childProps} />
}
interface CommitChildProps {
changeCommited: boolean
fileCreateOutput: string
handleChangeCommitted: () => void
}
const CommitWithInput = ({
changeCommited,
fileCreateOutput,
handleChangeCommitted,
}: CommitChildProps) => {
useEnterToContinue(handleChangeCommitted, !changeCommited && fileCreateOutput !== "")
return (
<Box flexDirection="column">
{fileCreateOutput !== "" && (
<>
<Text>{fileCreateOutput}</Text>
<EnterToContinue />
</>
)}
</Box>
)
}
const CommitWithoutInput = ({
changeCommited,
fileCreateOutput,
handleChangeCommitted,
}: CommitChildProps) => {
React.useEffect(() => {
if (!changeCommited && fileCreateOutput !== "") {
handleChangeCommitted()
}
}, [changeCommited, fileCreateOutput, handleChangeCommitted])
return (
<Box flexDirection="column">{fileCreateOutput !== "" && <Text>{fileCreateOutput}</Text>}</Box>
)
}

View File

@@ -1,77 +0,0 @@
import {Box, Text} from "ink"
import * as React from "react"
import {EnterToContinue} from "../components/enter-to-continue"
import {useEnterToContinue} from "../utils/use-enter-to-continue"
import {useUserInput} from "../utils/use-user-input"
import {IExecutor, executorArgument, ExecutorConfig, getExecutorArgument} from "./executor"
export interface Config extends ExecutorConfig {
message: executorArgument<string>
}
export const type = "print-message"
export const Commit: IExecutor["Commit"] = ({cliArgs, cliFlags, onChangeCommitted, step}) => {
const userInput = useUserInput(cliFlags)
const generatorArgs = React.useMemo(
() => ({
message: getExecutorArgument((step as Config).message, cliArgs),
stepName: getExecutorArgument((step as Config).stepName, cliArgs),
}),
[cliArgs, step],
)
const [changeCommited, setChangeCommited] = React.useState(false)
const handleChangeCommitted = React.useCallback(() => {
setChangeCommited(true)
onChangeCommitted(generatorArgs.stepName)
}, [onChangeCommitted, generatorArgs])
const childProps: CommitChildProps = {
changeCommited,
generatorArgs,
handleChangeCommitted,
}
if (userInput) return <CommitWithInput {...childProps} />
else return <CommitWithoutInput {...childProps} />
}
interface CommitChildProps {
changeCommited: boolean
generatorArgs: {message: string; stepName: string}
handleChangeCommitted: () => void
}
const CommitWithInput = ({
changeCommited,
generatorArgs,
handleChangeCommitted,
}: CommitChildProps) => {
useEnterToContinue(handleChangeCommitted, !changeCommited)
return (
<Box flexDirection="column">
<Text>{generatorArgs.message}</Text>
<EnterToContinue />
</Box>
)
}
const CommitWithoutInput = ({
changeCommited,
generatorArgs,
handleChangeCommitted,
}: CommitChildProps) => {
React.useEffect(() => {
if (!changeCommited) {
handleChangeCommitted()
}
}, [changeCommited, handleChangeCommitted])
return (
<Box flexDirection="column">
<Text>{generatorArgs.message}</Text>
</Box>
)
}

View File

@@ -1,136 +0,0 @@
import {spawn} from "cross-spawn"
import {Box, Text} from "ink"
import Spinner from "ink-spinner"
import * as React from "react"
import {Newline} from "../components/newline"
import {RecipeCLIArgs} from "../types"
import {useEnterToContinue} from "../utils/use-enter-to-continue"
import {useUserInput} from "../utils/use-user-input"
import {IExecutor, ExecutorConfig, getExecutorArgument} from "./executor"
export type CliCommand = string | [string, ...string[]]
export interface Config extends ExecutorConfig {
command: CliCommand
}
export interface CommitChildProps {
commandInstalled: boolean
handleChangeCommitted: () => void
command: CliCommand
cliArgs: RecipeCLIArgs
step: Config
}
export const type = "run-command"
function Command({command, loading}: {command: CliCommand; loading: boolean}) {
return (
<Text>
{` `}
{loading ? <Spinner /> : "✅"}
{` ${typeof command === "string" ? command : command.join(" ")}`}
</Text>
)
}
const CommandList = ({
lede = "Hang tight! Running...",
commandLoading = false,
step,
command,
}: {
lede?: string
commandLoading?: boolean
step: Config
command: CliCommand
}) => {
return (
<Box flexDirection="column">
<Text>{lede}</Text>
<Newline />
<Command key={step.stepId} command={command} loading={commandLoading} />
</Box>
)
}
/**
* INFO: Exported for unit testing purposes
*
* This function calls the defined command with their optional arguments if defined
*
* @param {CliCommand} input The Command and arguments
* @return Promise<void>
*
* @example await executeCommand("ls")
* @example await executeCommand(["ls"])
* @example await executeCommand(["ls", ...["-a", "-l"]])
*/
export async function executeCommand(input: CliCommand): Promise<void> {
// from https://stackoverflow.com/a/43766456/9950655
const argsRegex =
/("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|\/[^/\\]*(?:\\[\S\s][^/\\]*)*\/[gimy]*(?=\s|$)|(?:\\\s|\S)+)/g
const command: string[] = Array.isArray(input) ? input : input.match(argsRegex) || []
if (command.length === 0) {
throw new Error(`The command is too short: \`${JSON.stringify(input)}\``)
}
await new Promise((resolve) => {
const cp = spawn(`${command[0]}`, command.slice(1), {
stdio: ["inherit", "pipe", "pipe"],
})
cp.on("exit", resolve)
cp.stdout.on("data", () => {})
})
}
export const Commit: IExecutor["Commit"] = ({cliArgs, cliFlags, step, onChangeCommitted}) => {
const userInput = useUserInput(cliFlags)
const [commandInstalled, setCommandInstalled] = React.useState(false)
const executorCommand = getExecutorArgument((step as Config).command, cliArgs)
const handleChangeCommitted = React.useCallback(() => {
onChangeCommitted(`Executed command ${executorCommand}`)
}, [executorCommand, onChangeCommitted])
React.useEffect(() => {
async function runCommand() {
await executeCommand(executorCommand)
setCommandInstalled(true)
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
runCommand()
}, [cliArgs, step, executorCommand])
React.useEffect(() => {
if (commandInstalled) {
handleChangeCommitted()
}
}, [commandInstalled, handleChangeCommitted])
const childProps: CommitChildProps = {
commandInstalled,
handleChangeCommitted,
command: executorCommand,
cliArgs,
step: step as Config,
}
if (userInput) return <CommitWithInput {...childProps} />
else return <CommitWithoutInput {...childProps} />
}
const CommitWithInput = ({
commandInstalled,
handleChangeCommitted,
command,
step,
}: CommitChildProps) => {
useEnterToContinue(handleChangeCommitted, commandInstalled)
return <CommandList commandLoading={!commandInstalled} step={step} command={command} />
}
const CommitWithoutInput = ({commandInstalled, command, step}: CommitChildProps) => (
<CommandList commandLoading={!commandInstalled} step={step} command={command} />
)

View File

@@ -1,12 +0,0 @@
export * from "./recipe-executor"
export * from "./recipe-builder"
export * from "./executors/executor"
export {type as AddDependencyType} from "./executors/add-dependency-executor"
export {type as FileTransformType} from "./executors/file-transform-executor"
export {type as NewFileType} from "./executors/new-file-executor"
export {type as PrintMessageType} from "./executors/print-message-executor"
export * from "./utils/paths"
export * from "./transforms"
export {customTsParser} from "./utils/transform"
export type {Program, RecipeCLIArgs, RecipeCLIFlags} from "./types"

View File

@@ -1,85 +0,0 @@
import * as AddDependencyExecutor from "./executors/add-dependency-executor"
import * as TransformFileExecutor from "./executors/file-transform-executor"
import * as NewFileExecutor from "./executors/new-file-executor"
import * as PrintMessageExecutor from "./executors/print-message-executor"
import * as RunCommandExecutor from "./executors/run-command-executor"
import {ExecutorConfigUnion, RecipeExecutor} from "./recipe-executor"
import {RecipeMeta} from "./types"
export interface IRecipeBuilder {
setName(name: string): IRecipeBuilder
setDescription(description: string): IRecipeBuilder
printMessage(
step: Omit<Omit<PrintMessageExecutor.Config, "stepType">, "explanation">,
): IRecipeBuilder
setOwner(owner: string): IRecipeBuilder
setRepoLink(repoLink: string): IRecipeBuilder
addAddDependenciesStep(step: Omit<AddDependencyExecutor.Config, "stepType">): IRecipeBuilder
addNewFilesStep(step: Omit<NewFileExecutor.Config, "stepType">): IRecipeBuilder
addTransformFilesStep(step: Omit<TransformFileExecutor.Config, "stepType">): IRecipeBuilder
addRunCommandStep(step: Omit<RunCommandExecutor.Config, "stepType">): IRecipeBuilder
build(): RecipeExecutor<any>
}
export function RecipeBuilder(): IRecipeBuilder {
const steps: ExecutorConfigUnion[] = []
const meta: Partial<RecipeMeta> = {}
return {
setName(name: string) {
meta.name = name
return this
},
setDescription(description: string) {
meta.description = description
return this
},
printMessage(step: Omit<PrintMessageExecutor.Config, "stepType">) {
steps.push({
stepType: PrintMessageExecutor.type,
...step,
})
return this
},
setOwner(owner: string) {
meta.owner = owner
return this
},
setRepoLink(repoLink: string) {
meta.repoLink = repoLink
return this
},
addAddDependenciesStep(step: Omit<AddDependencyExecutor.Config, "stepType">) {
steps.push({
stepType: AddDependencyExecutor.type,
...step,
})
return this
},
addNewFilesStep(step: Omit<NewFileExecutor.Config, "stepType">) {
steps.push({
stepType: NewFileExecutor.type,
...step,
})
return this
},
addTransformFilesStep(step: Omit<TransformFileExecutor.Config, "stepType">) {
steps.push({
stepType: TransformFileExecutor.type,
...step,
})
return this
},
addRunCommandStep(step: Omit<RunCommandExecutor.Config, "stepType">) {
steps.push({
stepType: RunCommandExecutor.type,
...step,
})
return this
},
build() {
return new RecipeExecutor(meta as RecipeMeta, steps)
},
}
}

View File

@@ -1,50 +0,0 @@
import {render} from "ink"
import {baseLogger} from "../logging"
import React from "react"
import * as AddDependencyExecutor from "./executors/add-dependency-executor"
import * as FileTransformExecutor from "./executors/file-transform-executor"
import * as NewFileExecutor from "./executors/new-file-executor"
import * as PrintMessageExecutor from "./executors/print-message-executor"
import {RecipeRenderer} from "./recipe-renderer"
import {RecipeCLIArgs, RecipeCLIFlags, RecipeMeta} from "./types"
// const debug = require('debug')("blitz:installer")
type ExecutorConfig =
| AddDependencyExecutor.Config
| FileTransformExecutor.Config
| NewFileExecutor.Config
| PrintMessageExecutor.Config
export type {ExecutorConfig as ExecutorConfigUnion}
export class RecipeExecutor<Options extends RecipeMeta> {
private readonly steps: ExecutorConfig[]
private readonly options: Options
constructor(options: Options, steps: ExecutorConfig[]) {
this.options = options
this.steps = steps
}
async run(
cliArgs: RecipeCLIArgs = {},
cliFlags: RecipeCLIFlags = {yesToAll: false},
): Promise<void> {
try {
const {waitUntilExit} = render(
<RecipeRenderer
cliArgs={cliArgs}
cliFlags={cliFlags}
steps={this.steps}
recipeMeta={this.options}
/>,
{exitOnCtrlC: false},
)
await waitUntilExit()
baseLogger().info(`\n🎉 The ${this.options.name} recipe has been installed!\n`)
} catch (e) {
baseLogger().error(e as any)
return
}
}
}

View File

@@ -1,310 +0,0 @@
import {Box, Text, useApp, useInput} from "ink"
import React from "react"
import {assert} from "../index-server"
import {EnterToContinue} from "./components/enter-to-continue"
import {Newline} from "./components/newline"
import * as AddDependencyExecutor from "./executors/add-dependency-executor"
import {ExecutorConfig, Frontmatter, IExecutor} from "./executors/executor"
import * as FileTransformExecutor from "./executors/file-transform-executor"
import * as NewFileExecutor from "./executors/new-file-executor"
import * as PrintMessageExecutor from "./executors/print-message-executor"
import * as RunCommandExecutor from "./executors/run-command-executor"
import {RecipeCLIArgs, RecipeCLIFlags, RecipeMeta} from "./types"
import {useEnterToContinue} from "./utils/use-enter-to-continue"
import {useUserInput} from "./utils/use-user-input"
enum Action {
SkipStep,
ProposeChange,
ApplyChange,
CommitApproved,
CompleteChange,
}
enum Status {
Pending,
Proposed,
ReadyToCommit,
Committing,
Committed,
}
const ExecutorMap: {[key: string]: IExecutor} = {
[AddDependencyExecutor.type]: AddDependencyExecutor,
[NewFileExecutor.type]: NewFileExecutor,
[PrintMessageExecutor.type]: PrintMessageExecutor,
[FileTransformExecutor.type]: FileTransformExecutor,
[RunCommandExecutor.type]: RunCommandExecutor,
} as const
interface State {
steps: {
executor: ExecutorConfig
status: Status
proposalData?: any
successMsg: string
}[]
current: number
}
function recipeReducer(state: State, action: {type: Action; data?: any}) {
const newState = {...state}
const step = newState.steps[newState.current]
switch (action.type) {
case Action.ProposeChange:
assert(step, "Step is empty in recipeReducer function")
step.status = Status.Proposed
break
case Action.CommitApproved:
assert(step, "Step is empty in recipeReducer function")
step.status = Status.ReadyToCommit
step.proposalData = action.data
break
case Action.ApplyChange:
assert(step, "Step is empty in recipeReducer function")
step.status = Status.Committing
break
case Action.CompleteChange:
assert(step, "Step is empty in recipeReducer function")
step.status = Status.Committed
step.successMsg = action.data as string
newState.current = Math.min(newState.current + 1, newState.steps.length - 1)
break
case Action.SkipStep:
newState.current += 1
break
}
return newState
}
interface RecipeProps {
cliArgs: RecipeCLIArgs
cliFlags: RecipeCLIFlags
steps: ExecutorConfig[]
recipeMeta: RecipeMeta
}
const DispatchContext = React.createContext<React.Dispatch<{type: Action; data?: any}>>(() => {})
function WelcomeMessage({
recipeMeta,
enterToContinue = true,
}: {
recipeMeta: RecipeMeta
enterToContinue?: boolean
}) {
return (
<Box flexDirection="column">
<Text color="#8a3df0" bold>
Recipe: {recipeMeta.name}
</Text>
<Newline />
<Text color="gray">
<Text italic>{recipeMeta.description}</Text>
</Text>
<Newline />
<Text color="gray">
Repo: <Text italic>{recipeMeta.repoLink}</Text>
</Text>
<Text color="gray">
Author: <Text italic>{recipeMeta.owner}</Text>
</Text>
{enterToContinue && <EnterToContinue />}
</Box>
)
}
function StepMessages({state}: {state: State}) {
const messages = state.steps
.map((step) => ({
msg: step.successMsg,
icon: step.executor.successIcon ?? "✅",
}))
.filter((s) => s.msg)
return (
<>
{messages.map(({msg, icon}, index) => (
<Text key={msg + index} color="green">
{msg === "\n" ? "" : icon} {msg}
</Text>
))}
</>
)
}
function StepExecutor({
cliArgs,
cliFlags,
proposalData,
step,
status,
}: {
step: ExecutorConfig
status: Status
cliArgs: RecipeCLIArgs
cliFlags: RecipeCLIFlags
proposalData?: any
}) {
const executor = ExecutorMap[step.stepType]
assert(executor, `Executor not found for ${step.stepType}`)
const {Propose, Commit}: IExecutor = executor
const dispatch = React.useContext(DispatchContext)
const handleProposalAccepted = React.useCallback(
(msg: any) => {
dispatch({type: Action.CommitApproved, data: msg})
},
[dispatch],
)
const handleChangeCommitted = React.useCallback(
(msg: any) => {
dispatch({type: Action.CompleteChange, data: msg})
},
[dispatch],
)
React.useEffect(() => {
if (status === Status.Pending) {
dispatch({type: Action.ProposeChange})
} else if (status === Status.ReadyToCommit) {
dispatch({type: Action.ApplyChange})
}
if (status === Status.Proposed && !Propose) {
dispatch({type: Action.CommitApproved})
}
}, [dispatch, status, Propose])
return (
<Box flexDirection="column">
{status !== Status.Committed ? <Frontmatter executor={step} /> : null}
{[Status.Proposed].includes(status) && Propose ? (
<Propose
cliArgs={cliArgs}
cliFlags={cliFlags}
step={step}
onProposalAccepted={handleProposalAccepted}
/>
) : null}
{[Status.Committing].includes(status) ? (
<Commit
cliArgs={cliArgs}
cliFlags={cliFlags}
proposalData={proposalData}
step={step}
onChangeCommitted={handleChangeCommitted}
/>
) : null}
</Box>
)
}
export function RecipeRenderer({cliArgs, cliFlags, steps, recipeMeta}: RecipeProps) {
const userInput = useUserInput(cliFlags)
const {exit} = useApp()
const mappedSteps = steps.map((e) => ({
executor: e,
status: Status.Pending,
successMsg: "",
}))
if (steps.length === 0) {
exit(new Error("This recipe has no steps"))
}
const [state, dispatch] = React.useReducer(recipeReducer, {
current: userInput ? -1 : 0,
steps: mappedSteps,
})
React.useEffect(() => {
if (
state.current === state.steps.length - 1 &&
state.steps[state.current]?.status === Status.Committed
) {
exit()
}
})
return (
<DispatchContext.Provider value={dispatch}>
{userInput ? (
<RecipeRendererWithInput
cliArgs={cliArgs}
cliFlags={cliFlags}
state={state}
recipeMeta={recipeMeta}
/>
) : (
<RecipeRendererWithoutInput
cliArgs={cliArgs}
cliFlags={cliFlags}
state={state}
recipeMeta={recipeMeta}
/>
)}
</DispatchContext.Provider>
)
}
function RecipeRendererWithInput({
cliArgs,
cliFlags,
recipeMeta,
state,
}: Omit<RecipeProps, "steps"> & {state: State}) {
const {exit} = useApp()
const dispatch = React.useContext(DispatchContext)
const step = state.steps[state.current]
useInput((input, key) => {
if (input === "c" && key.ctrl) {
exit(new Error("You aborted installation"))
return
}
})
useEnterToContinue(() => dispatch({type: Action.SkipStep}), state.current === -1)
return (
<>
<StepMessages state={state} />
{state.current === -1 ? (
<WelcomeMessage recipeMeta={recipeMeta} />
) : step ? (
<StepExecutor
cliArgs={cliArgs}
cliFlags={cliFlags}
proposalData={state.steps[state.current]?.proposalData}
step={step.executor}
status={step.status}
/>
) : (
new Error("Step not found in RecipeRendererWithInput")
)}
</>
)
}
function RecipeRendererWithoutInput({
cliArgs,
cliFlags,
recipeMeta,
state,
}: Omit<RecipeProps, "steps"> & {state: State}) {
return (
<>
<WelcomeMessage recipeMeta={recipeMeta} enterToContinue={false} />
<StepMessages state={state} />
<StepExecutor
cliArgs={cliArgs}
cliFlags={cliFlags}
proposalData={state.steps[state.current]?.proposalData}
step={state.steps[state.current]?.executor as ExecutorConfig}
status={state.steps[state.current]?.status as Status}
/>
</>
)
}

View File

@@ -1,20 +0,0 @@
import type {ExpressionKind} from "ast-types/gen/kinds"
import j from "jscodeshift"
import {Program} from "../types"
import {addImport} from "./add-import"
export const addBlitzMiddleware = (program: Program, middleware: ExpressionKind): Program => {
const pluginArray = program.find(j.Identifier, (node) => node.name === "plugins")
pluginArray.get().parentPath.value.value.elements = [
...pluginArray.get().parentPath.value.value.elements,
j.template.expression`BlitzServerMiddleware(${middleware})`,
]
const blitzServerMiddleWare = j.importDeclaration(
[j.importSpecifier(j.identifier("BlitzServerMiddleware"))],
j.literal("blitz"),
)
addImport(program, blitzServerMiddleWare)
return program
}

View File

@@ -1,17 +0,0 @@
import j from "jscodeshift"
import {Program} from "../types"
export function addImport(program: Program, importToAdd: j.ImportDeclaration): Program {
const importStatementCount = program.find(j.ImportDeclaration).length
if (importStatementCount === 0) {
program.find(j.Statement).at(0).insertBefore(importToAdd)
return program
}
program.find<j.ImportDeclaration>(j.ImportDeclaration).forEach((stmt, idx) => {
const node = stmt.node
if (idx === importStatementCount - 1) {
stmt.replace(node, importToAdd)
}
})
return program
}

View File

@@ -1,15 +0,0 @@
import j from "jscodeshift"
import {Program} from "../types"
export const findModuleExportsExpressions = (program: Program) =>
program.find<j.AssignmentExpression>(j.AssignmentExpression).filter((path) => {
const {left, right} = path.value
return (
left.type === "MemberExpression" &&
left.object.type === "Identifier" &&
left.property.type === "Identifier" &&
left.property.name === "exports" &&
right.type === "ObjectExpression"
)
})

View File

@@ -1,9 +0,0 @@
export * from "./add-import"
export * from "./add-blitz-middleware"
export * from "./find-module-exports-expressions"
export * from "./prisma"
export * from "./transform-next-config"
export * from "./with-utilities"
export * from "./wrap-blitz-config"
export * from "./update-babel-config"
export * from "./wrap-app-return-statement"

View File

@@ -1,27 +0,0 @@
import {Enum} from "@mrleebo/prisma-ast"
import {produceSchema} from "./produce-schema"
/**
* Adds an enum to your schema.prisma data model.
*
* @param source - schema.prisma source file contents
* @param enumProps - the enum to add
* @returns The modified schema.prisma source
* @example Usage
* ```
* addPrismaEnum(source, {
type: "enum",
name: "Role",
enumerators: [
{type: "enumerator", name: "USER"},
{type: "enumerator", name: "ADMIN"},
],
})
* ```
*/
export function addPrismaEnum(source: string, enumProps: Enum): Promise<string> {
return produceSchema(source, (schema) => {
const existing = schema.list.find((x) => x.type === "enum" && x.name === enumProps.name)
existing ? Object.assign(existing, enumProps) : schema.list.push(enumProps)
})
}

View File

@@ -1,34 +0,0 @@
import {Field, Model} from "@mrleebo/prisma-ast"
import {produceSchema} from "./produce-schema"
/**
* Adds a field to a model in your schema.prisma data model.
*
* @param source - schema.prisma source file contents
* @param modelName - name of the model to add a field to
* @param fieldProps - the field to add
* @returns The modified schema.prisma source
* @example Usage
* ```
* addPrismaField(source, "Project", {
type: "field",
name: "name",
fieldType: "String",
optional: false,
attributes: [{type: "attribute", kind: "field", name: "unique"}],
})
* ```
*/
export function addPrismaField(
source: string,
modelName: string,
fieldProps: Field,
): Promise<string> {
return produceSchema(source, (schema) => {
const model = schema.list.find((x) => x.type === "model" && x.name === modelName) as Model
if (!model) return
const existing = model.properties.find((x) => x.type === "field" && x.name === fieldProps.name)
existing ? Object.assign(existing, fieldProps) : model.properties.push(fieldProps)
})
}

View File

@@ -1,26 +0,0 @@
import {Generator} from "@mrleebo/prisma-ast"
import {produceSchema} from "./produce-schema"
/**
* Adds a generator to your schema.prisma data model.
*
* @param source - schema.prisma source file contents
* @param generatorProps - the generator to add
* @returns The modified schema.prisma source
* @example Usage
* ```
* addPrismaGenerator(source, {
type: "generator",
name: "nexusPrisma",
assignments: [{type: "assignment", key: "provider", value: '"nexus-prisma"'}],
})
* ```
*/
export function addPrismaGenerator(source: string, generatorProps: Generator): Promise<string> {
return produceSchema(source, (schema) => {
const existing = schema.list.find(
(x) => x.type === "generator" && x.name === generatorProps.name,
) as Generator
existing ? Object.assign(existing, generatorProps) : schema.list.push(generatorProps)
})
}

View File

@@ -1,37 +0,0 @@
import {Model, ModelAttribute} from "@mrleebo/prisma-ast"
import {produceSchema} from "./produce-schema"
/**
* Adds a field to a model in your schema.prisma data model.
*
* @remarks Not ready for actual use
* @param source - schema.prisma source file contents
* @param modelName - name of the model to add a field to
* @param attributeProps - the model attribute (such as an index) to add
* @returns The modified schema.prisma source
* @example Usage
* ```
* addPrismaModelAttribute(source, "Project", {
* type: "attribute",
* kind: "model",
* name: "index",
* args: [{ type: "attributeArgument", value: { type: "array", args: ["name"] } }]
* });
* ```
*/
export function addPrismaModelAttribute(
source: string,
modelName: string,
attributeProps: ModelAttribute,
): Promise<string> {
return produceSchema(source, (schema) => {
const model = schema.list.find((x) => x.type === "model" && x.name === modelName) as Model
if (!model) return
const existing = model.properties.find(
(x) => x.type === "attribute" && x.name === attributeProps.name,
)
existing ? Object.assign(existing, attributeProps) : model.properties.push(attributeProps)
})
}

View File

@@ -1,24 +0,0 @@
import {Model} from "@mrleebo/prisma-ast"
import {produceSchema} from "./produce-schema"
/**
* Adds an enum to your schema.prisma data model.
*
* @param source - schema.prisma source file contents
* @param modelProps - the model to add
* @returns The modified schema.prisma source
* @example Usage
* ```
* addPrismaModel(source, {
type: "model",
name: "Project",
properties: [{type: "field", name: "id", fieldType: "String"}],
})
* ```
*/
export function addPrismaModel(source: string, modelProps: Model): Promise<string> {
return produceSchema(source, (schema) => {
const existing = schema.list.find((x) => x.type === "model" && x.name === modelProps.name)
existing ? Object.assign(existing, modelProps) : schema.list.push(modelProps)
})
}

View File

@@ -1,7 +0,0 @@
export * from "./add-prisma-enum"
export * from "./add-prisma-field"
export * from "./add-prisma-generator"
export * from "./add-prisma-model-attribute"
export * from "./add-prisma-model"
export * from "./produce-schema"
export * from "./set-prisma-data-source"

View File

@@ -1,14 +0,0 @@
import {printSchema as printer, Schema} from "@mrleebo/prisma-ast"
/**
* Takes the schema.prisma document parsed from @mrleebo/prisma-ast and
* serializes it back to a schema.prisma source string. To ensure consistent
* formatting and prettify the document, we also execute the
* IntrospectionEngine from @prisma/sdk.
*
* @param schema - the parsed prisma schema
* @returns the schema.prisma source string
*/
export function printSchema(schema: Schema): string {
return printer(schema)
}

View File

@@ -1,19 +0,0 @@
import {getSchema, printSchema, Schema} from "@mrleebo/prisma-ast"
/**
* A file transformer that parses a schema.prisma string, offers you a callback
* of the parsed document object, then takes your changes to the document and
* writes out a new schema.prisma string with the changes applied.
*
* @param source - schema.prisma source file contents
* @param producer - a callback function that can mutate the parsed data model
* @returns The modified schema.prisma source
*/
export async function produceSchema(
source: string,
producer: (schema: Schema) => void,
): Promise<string> {
const schema = await getSchema(source)
producer(schema)
return printSchema(schema)
}

View File

@@ -1,31 +0,0 @@
import {Datasource} from "@mrleebo/prisma-ast"
import {produceSchema} from "./produce-schema"
/**
* Modify the prisma datasource metadata to use the provider and url specified.
*
* @param source - schema.prisma source file contents
* @param datasourceProps - datasource object to assign to the schema
* @returns The modified schema.prisma source
* @example Usage
* ```
* setPrismaDataSource(source, {
type: "datasource",
name: "db",
assignments: [
{type: "assignment", key: "provider", value: '"postgresql"'},
{
type: "assignment",
key: "url",
value: {type: "function", name: "env", params: ['"DATABASE_URL"']},
},
],
})
* ```
*/
export function setPrismaDataSource(source: string, datasourceProps: Datasource): Promise<string> {
return produceSchema(source, (schema) => {
const existing = schema.list.find((x) => x.type === "datasource")
existing ? Object.assign(existing, datasourceProps) : schema.list.push(datasourceProps)
})
}

View File

@@ -1,91 +0,0 @@
import type {ExpressionKind} from "ast-types/gen/kinds"
import j from "jscodeshift"
import {Program} from "../types"
function recursiveConfigSearch(
program: Program,
obj: ExpressionKind,
): j.ObjectExpression | undefined {
// Identifier being a variable name
if (obj.type === "Identifier") {
const {node} = j(obj).get()
// Get the definition of the variable
const identifier: j.ASTPath<j.VariableDeclarator> = program
.find(j.VariableDeclarator, {
id: {name: node.name},
})
.get()
// Return what is after the `=`
return identifier.value.init ? recursiveConfigSearch(program, identifier.value.init) : undefined
} else if (obj.type === "CallExpression") {
// If it's an function call (like `withBundleAnalyzer`), get the first argument
if (obj.arguments.length === 0) {
// If it has no arguments, create an empty object: `{}`
let config = j.objectExpression([])
obj.arguments.push(config)
return config
} else {
const arg = obj.arguments[0]
if (arg) {
if (arg.type === "SpreadElement") return undefined
else return recursiveConfigSearch(program, arg)
}
}
} else if (obj.type === "ObjectExpression") {
// If it's an object, return it
return obj
} else {
return undefined
}
}
export type TransformBlitzConfigCallback = (config: j.ObjectExpression) => j.ObjectExpression
export function transformBlitzConfig(
program: Program,
transform: TransformBlitzConfigCallback,
): Program {
let moduleExportsExpressions = program.find(j.AssignmentExpression, {
operator: "=",
left: {object: {name: "module"}, property: {name: "exports"}},
right: {},
})
// If there isn't any `module.exports = ...`, create one
if (moduleExportsExpressions.length === 0) {
let config = j.objectExpression([])
config = transform(config)
let moduleExportExpression = j.expressionStatement(
j.assignmentExpression(
"=",
j.memberExpression(j.identifier("module"), j.identifier("exports")),
config,
),
)
program.get().node.program.body.push(moduleExportExpression)
} else if (moduleExportsExpressions.length === 1) {
let moduleExportsExpression: j.ASTPath<j.AssignmentExpression> = moduleExportsExpressions.get()
let config: j.ObjectExpression | undefined = recursiveConfigSearch(
program,
moduleExportsExpression.value.right,
)
if (config) {
config = transform(config)
} else {
console.warn(
"The configuration couldn't be found, but there is a 'module.exports' inside `blitz.config.js`",
)
}
} else {
console.warn("There are multiple 'module.exports' inside 'blitz.config.js'")
}
return program
}

View File

@@ -1,69 +0,0 @@
import j from "jscodeshift"
import {assert} from "../../index-server"
import {Program} from "../types"
export function transformNextConfig(program: Program): {
program: Program
configObj: []
pushToConfig: (property: j.ObjectProperty) => void
wrapConfig: (func: string | j.CallExpression) => {
withBlitz: j.Identifier | j.CallExpression
}
addRequireStatement: (identifier: string, packageName: string) => void
} {
const configObj = program
.find<j.VariableDeclarator>(j.VariableDeclarator, (filter) => filter.id.name === "config")
.get().parentPath.value[0].init.properties
assert(configObj, "Config object not found")
const pushToConfig = (property: j.ObjectProperty) => {
configObj.push(property)
}
const wrapConfig = (
func: string | j.CallExpression,
): {
withBlitz: j.Identifier | j.CallExpression
} => {
const withBlitz = program
.find(j.CallExpression, (filter) => filter.callee.name === "withBlitz")
.get().value.arguments
assert(withBlitz, "withBlitz wrapper not found")
if (typeof func === "string") {
withBlitz.push(j.template.expression`${func}(${withBlitz})`)
withBlitz.splice(0, 1)
} else {
withBlitz.push(func)
withBlitz.splice(0, 1)
}
return {
withBlitz,
}
}
const addRequireStatement = (identifier: string, packageName: string) => {
program
.get()
.value.program.body.unshift(
j.expressionStatement(
j.assignmentExpression(
"=",
j.identifier(identifier),
j.callExpression(j.identifier("require"), [j.identifier(`"${packageName}"`)]),
),
),
)
}
return {
program,
configObj,
pushToConfig,
wrapConfig,
addRequireStatement,
}
}

View File

@@ -1,113 +0,0 @@
import type {ExpressionKind} from "ast-types/gen/kinds"
import {namedTypes} from "ast-types"
import j, {ASTPath} from "jscodeshift"
import {JsonObject, JsonValue} from "../types"
import {Program} from "../types"
import {findModuleExportsExpressions} from "./find-module-exports-expressions"
type AddBabelItemDefinition = string | [name: string, options: JsonObject]
const jsonValueToExpression = (value: JsonValue): ExpressionKind =>
typeof value === "string"
? j.stringLiteral(value)
: typeof value === "number"
? j.numericLiteral(value)
: typeof value === "boolean"
? j.booleanLiteral(value)
: value === null
? j.nullLiteral()
: Array.isArray(value)
? j.arrayExpression(value.map(jsonValueToExpression))
: j.objectExpression(
Object.entries(value)
.filter((entry): entry is [string, JsonValue] => entry[1] !== undefined)
.map(([key, value]) =>
j.objectProperty(j.stringLiteral(key), jsonValueToExpression(value)),
),
)
function updateBabelConfig(program: Program, item: AddBabelItemDefinition, key: string): Program {
findModuleExportsExpressions(program).forEach((moduleExportsExpression) => {
const foundExpression: Program = j(moduleExportsExpression)
foundExpression
.find<j.ObjectProperty>(j.ObjectProperty, {key: {name: key}})
.forEach((items) => {
// Don't add it again if it already exists,
// that what this code does. For simplicity,
// all the examples will be with key = 'presets'
const itemName = Array.isArray(item) ? item[0] : item
if (items.node.value.type === "Literal" || items.node.value.type === "StringLiteral") {
// {
// presets: "this-preset"
// }
if (itemName !== items.node.value.value) {
items.node.value = j.arrayExpression([items.node.value, jsonValueToExpression(item)])
}
} else if (items.node.value.type === "ArrayExpression") {
// {
// presets: ["this-preset", "maybe-another", ...]
// }
// Here, it will return if it find the preset inside the
// array, so the last line doesn't push a duplicated preset
for (const [i, element] of items.node.value.elements.entries()) {
if (!element) continue
if (element.type === "Literal" || element.type === "StringLiteral") {
// {
// presets: [..., "this-preset", ...]
// }
if (element.value === itemName) return
} else if (element.type === "ArrayExpression") {
// {
// presets: [..., ["this-preset"], ...]
// }
if (
(element.elements[0]?.type === "Literal" ||
element.elements[0]?.type === "StringLiteral") &&
element.elements[0].value === itemName
) {
if (
element.elements[1]?.type === "ObjectExpression" &&
element.elements[1].properties.length > 0
) {
// The preset has a config.
// ["this-preset", {...}]
if (Array.isArray(item)) {
// If it has an adittional config, add the new keys
// (don't matter if they already exists, let the user handle it later by themself)
let obj = element.elements[1]
for (const key in item[1]) {
const value = item[1][key]
if (value === undefined) continue
obj.properties.push(
j.objectProperty(j.stringLiteral(key), jsonValueToExpression(value)),
)
}
items.node.value.elements[i] = obj
}
} else {
// The preset has no config.
// Its ["this-preset"]
items.node.value.elements[i] = jsonValueToExpression(item)
}
return
}
}
}
items.node.value.elements.push(jsonValueToExpression(item))
}
})
})
return program
}
export const addBabelPreset = (program: Program, preset: AddBabelItemDefinition): Program =>
updateBabelConfig(program, preset, "presets")
export const addBabelPlugin = (program: Program, plugin: AddBabelItemDefinition): Program =>
updateBabelConfig(program, plugin, "plugins")

View File

@@ -1,20 +0,0 @@
import {CommentKind, TypeAnnotationKind, TSTypeAnnotationKind} from "ast-types/gen/kinds"
import j from "jscodeshift"
export function withComments<
Node extends {
comments?: CommentKind[] | null
},
>(node: Node, comments: CommentKind[]): Node {
node.comments = comments
return node
}
export function withTypeAnnotation<
Node extends {
typeAnnotation?: TypeAnnotationKind | TSTypeAnnotationKind | null
},
>(node: Node, type: Parameters<typeof j.tsTypeAnnotation>[0]): Node {
node.typeAnnotation = j.tsTypeAnnotation(type)
return node
}

View File

@@ -1,33 +0,0 @@
import {NodePath} from "ast-types/lib/node-path"
import j, {JSXAttribute} from "jscodeshift"
import {assert} from "../../index-server"
import {Program} from "../types"
export function wrapAppWithProvider(
program: Program,
element: string,
attributes?: string[],
): Program {
const findMyApp = program.find(j.FunctionDeclaration, (node) => node.id.name === "MyApp")
assert(findMyApp.length, "MyApp function not found")
findMyApp.forEach((path: NodePath) => {
const statement = path.value.body.body.filter(
(b: j.ReturnStatement) => b.type === "ReturnStatement",
)[0]
const argument = statement.argument
let attrs: JSXAttribute[] = []
if (attributes) {
attrs = attributes.map((i) => j.jsxAttribute(j.jsxIdentifier(i)))
}
statement.argument = j.jsxElement(
j.jsxOpeningElement(j.jsxIdentifier(element), attrs),
j.jsxClosingElement(j.jsxIdentifier(element)),
[j.jsxText("\n"), argument, j.jsxText("\n")],
)
})
return program
}

View File

@@ -1,33 +0,0 @@
import j from "jscodeshift"
import {Program} from "../types"
export function wrapBlitzConfig(program: Program, functionName: string): Program {
let moduleExportsExpressions = program.find(j.AssignmentExpression, {
operator: "=",
left: {object: {name: "module"}, property: {name: "exports"}},
right: {},
})
// If there isn't any `module.exports = ...`, create one
if (moduleExportsExpressions.length === 0) {
let moduleExportExpression = j.expressionStatement(
j.assignmentExpression(
"=",
j.memberExpression(j.identifier("module"), j.identifier("exports")),
j.callExpression(j.identifier(functionName), [j.objectExpression([])]),
),
)
program.get().node.program.body.push(moduleExportExpression)
} else if (moduleExportsExpressions.length === 1) {
let moduleExportsExpression: j.ASTPath<j.AssignmentExpression> = moduleExportsExpressions.get()
moduleExportsExpression.value.right = j.callExpression(j.identifier(functionName), [
moduleExportsExpression.value.right,
])
} else {
console.warn("There are multiple 'module.exports' inside 'blitz.config.js'")
}
return program
}

View File

@@ -1,41 +0,0 @@
import type * as j from "jscodeshift"
export interface RecipeMeta {
name: string
description: string
owner: string
repoLink: string
}
export type RecipeCLIArgs = {[Key in string]?: string | true}
export interface RecipeCLIFlags {
yesToAll: boolean
}
export type Program = j.Collection<j.Program>
/**
Matches a JSON object.
This type can be useful to enforce some input to be JSON-compatible or as a super-type to be extended from. Don't use this as a direct return type as the user would have to double-cast it: `jsonObject as unknown as CustomResponse`. Instead, you could extend your CustomResponse type from it to ensure your type only uses JSON-compatible types: `interface CustomResponse extends JsonObject { … }`.
@see https://github.com/sindresorhus/type-fest
*/
export type JsonObject = {[Key in string]?: JsonValue}
/**
Matches a JSON array.
@see https://github.com/sindresorhus/type-fest
*/
export type JsonArray = JsonValue[]
/**
Matches any valid JSON primitive value.
@see https://github.com/sindresorhus/type-fest
*/
export type JsonPrimitive = string | number | boolean | null
/**
Matches any valid JSON value.
@see https://github.com/sindresorhus/type-fest
*/
export type JsonValue = JsonPrimitive | JsonObject | JsonArray

View File

@@ -1,77 +0,0 @@
import * as fs from "fs-extra"
import * as path from "path"
function ext(jsx = false) {
return fs.existsSync(path.resolve("tsconfig.json")) ? (jsx ? ".tsx" : ".ts") : ".js"
}
function getBlitzPath(type: string) {
const appPath = `app/blitz-${type}${ext(false)}`
const srcPath = `src/blitz-${type}${ext(false)}`
const appDir = fs.existsSync(path.resolve(appPath))
const srcDir = fs.existsSync(path.resolve(srcPath))
if (appDir) {
return appPath
} else if (srcDir) {
return srcPath
}
}
function getAppSourceDir() {
const srcPath = "src/pages"
const srcDir = fs.existsSync(path.resolve(srcPath))
if (srcDir) {
return "src"
} else {
return "app"
}
}
function findPageDir() {
const srcPagePath = `src/pages`
const srcPage = getAppSourceDir()
switch (srcPage) {
case "src": {
return srcPagePath
}
default: {
return `pages`
}
}
}
export const paths = {
document() {
return `${findPageDir()}/_document${ext(true)}`
},
app() {
return `${findPageDir()}/_app${ext(true)}`
},
appSrcDirectory() {
return getAppSourceDir()
},
blitzServer() {
return getBlitzPath("server")
},
blitzClient() {
return getBlitzPath("client")
},
entry() {
return `${findPageDir()}/index${ext(true)}`
},
nextConfig() {
return `next.config.js`
},
babelConfig() {
return `babel.config.js`
},
packageJson() {
return "package.json"
},
prismaSchema() {
return "db/schema.prisma"
},
}

View File

@@ -1,73 +0,0 @@
import * as fs from "fs-extra"
import j from "jscodeshift"
import getBabelOptions, {Overrides} from "recast/parsers/_babel_options"
import * as babel from "recast/parsers/babel"
import {Program} from "../types"
export const customTsParser: {} = {
parse(source: string, options?: Overrides) {
const babelOptions = getBabelOptions(options)
babelOptions.plugins.push("typescript")
babelOptions.plugins.push("jsx")
return babel.parser.parse(source, babelOptions)
},
}
export enum TransformStatus {
Success = "success",
Failure = "failure",
}
export interface TransformResult {
status: TransformStatus
filename: string
error?: Error
}
export type StringTransformer = (program: string) => string | Promise<string>
export type Transformer = (program: Program) => Program | Promise<Program>
export function stringProcessFile(
original: string,
transformerFn: StringTransformer,
): string | Promise<string> {
return transformerFn(original)
}
export async function processFile(original: string, transformerFn: Transformer): Promise<string> {
const program = j(original, {parser: customTsParser})
return (await transformerFn(program)).toSource()
}
export async function transform(
processFile: (original: string) => Promise<string>,
targetFilePaths: string[],
): Promise<TransformResult[]> {
const results: TransformResult[] = []
for (const filePath of targetFilePaths) {
if (!fs.existsSync(filePath)) {
results.push({
status: TransformStatus.Failure,
filename: filePath,
error: new Error(`Error: ${filePath} not found`),
})
}
try {
const fileBuffer = fs.readFileSync(filePath)
const fileSource = fileBuffer.toString("utf-8")
const transformedCode = await processFile(fileSource)
fs.writeFileSync(filePath, transformedCode)
results.push({
status: TransformStatus.Success,
filename: filePath,
})
} catch (err) {
results.push({
status: TransformStatus.Failure,
filename: filePath,
error: err as any,
})
}
}
return results
}

View File

@@ -1,9 +0,0 @@
import {useInput} from "ink"
export function useEnterToContinue(cb: Function, additionalCondition: boolean = true) {
useInput((_input, key) => {
if (additionalCondition && key.return) {
cb()
}
})
}

View File

@@ -1,7 +0,0 @@
import {useStdin} from "ink"
import {RecipeCLIFlags} from "../types"
export function useUserInput(cliFlags: RecipeCLIFlags) {
const {isRawModeSupported} = useStdin()
return isRawModeSupported && !cliFlags.yesToAll
}

View File

@@ -1,5 +1,26 @@
# @blitzjs/codemod
## 3.0.1
### Patch Changes
- @blitzjs/generator@3.0.1
- blitz@3.0.1
## 3.0.0
### Major Changes
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
### Patch Changes
- Updated dependencies [ce1a603b2]
- Updated dependencies [0a257e915]
- Updated dependencies [1610c73f9]
- blitz@3.0.0
- @blitzjs/generator@3.0.0
## 2.2.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@blitzjs/codemod",
"version": "2.2.1",
"version": "3.0.1",
"scripts": {
"build": "unbuild",
"dev": "watch unbuild src --wait=0.2",
@@ -25,9 +25,9 @@
"@babel/plugin-proposal-class-properties": "7.17.12",
"@babel/plugin-syntax-jsx": "7.17.12",
"@babel/plugin-syntax-typescript": "7.17.12",
"@blitzjs/generator": "2.2.1",
"@blitzjs/generator": "3.0.1",
"arg": "5.0.1",
"blitz": "2.2.1",
"blitz": "3.0.1",
"chalk": "^4.1.0",
"cross-spawn": "7.0.3",
"debug": "4.3.3",
@@ -38,7 +38,7 @@
},
"devDependencies": {
"@babel/preset-env": "7.12.10",
"@blitzjs/config": "2.2.1",
"@blitzjs/config": "3.0.1",
"@types/jscodeshift": "0.11.2",
"@types/node": "18.11.9",
"ast-types": "0.14.2",

View File

@@ -1,5 +1,13 @@
# @blitzjs/config
## 3.0.1
## 3.0.0
### Major Changes
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
## 2.2.1
## 2.2.0

View File

@@ -1,7 +1,7 @@
{
"name": "@blitzjs/config",
"private": true,
"version": "2.2.1",
"version": "3.0.1",
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "5.42.1",

View File

@@ -1,5 +1,17 @@
# @blitzjs/generator
## 3.0.1
## 3.0.0
### Major Changes
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
### Patch Changes
- 0a257e915: fix: Pages router blitz rpc api not generated in the correct format
## 2.2.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@blitzjs/generator",
"version": "2.2.1",
"version": "3.0.1",
"homepage": "https://blitzjs.com/",
"repository": {
"type": "git",
@@ -54,7 +54,7 @@
"zod": "3.23.8"
},
"devDependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/config": "3.0.1",
"@juanm04/cpx": "2.0.1",
"@types/babel__core": "7.1.19",
"@types/diff": "5.0.2",

View File

@@ -68,18 +68,28 @@ export class AppGenerator extends Generator<AppGeneratorOptions> {
this.destinationPath("package.json"),
)
const rpcEndpointPath = `src/app/api/rpc/blitzrpcroute/route.${
const appRpcEndpointPath = `src/app/api/rpc/blitzrpcroute/route.${
this.options.useTs ? "ts" : "js"
}`
if (this.fs.exists(rpcEndpointPath)) {
if (this.fs.exists(appRpcEndpointPath)) {
this.fs.move(
this.destinationPath(rpcEndpointPath),
this.destinationPath(appRpcEndpointPath),
this.destinationPath(
`src/app/api/rpc/[[...blitz]]/route.${this.options.useTs ? "ts" : "js"}`,
),
)
}
const pagesRpcEndpointPath = `src/pages/api/rpc/blitzrpcroute.${
this.options.useTs ? "ts" : "js"
}`
if (this.fs.exists(pagesRpcEndpointPath)) {
this.fs.move(
this.destinationPath(pagesRpcEndpointPath),
this.destinationPath(`src/pages/api/rpc/[[...blitz]].${this.options.useTs ? "ts" : "js"}`),
)
}
if (this.options.template.path === "app") {
const pathsToMove = [
"src/app/auth/components/LoginForm.tsx",

View File

@@ -27,10 +27,10 @@
"@blitzjs/rpc": "latest",
"@prisma/client": "6.1.0",
"blitz": "latest",
"next": "14.2.15",
"next": "15.0.1",
"prisma": "6.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"secure-password": "4.0.0",
"zod": "3.23.8"
},

View File

@@ -27,10 +27,10 @@
"@blitzjs/rpc": "latest",
"@prisma/client": "6.1.0",
"blitz": "latest",
"next": "14.2.15",
"next": "15.0.1",
"prisma": "6.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"secure-password": "4.0.0",
"zod": "3.23.8"
},

View File

@@ -25,7 +25,7 @@
"@typescript-eslint/parser": "5.9.1"
},
"devDependencies": {
"@blitzjs/config": "2.2.1",
"@blitzjs/config": "3.0.1",
"@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "npm:types-react-dom@19.0.0",
"react": "19.0.0",

2167
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +0,0 @@
# @blitzjs/recipe-base-web
## 2.0.0-beta.11
### Patch Changes
- Updated dependencies [1476a577]
- blitz@2.0.0-beta.11
## 2.0.0-beta.10
### Patch Changes
- Updated dependencies [9db6c885]
- Updated dependencies [d98e4bac]
- Updated dependencies [9fe0cc54]
- Updated dependencies [af58e2b2]
- Updated dependencies [2ade7268]
- Updated dependencies [0edeaa37]
- Updated dependencies [430f6ec7]
- Updated dependencies [15d22af2]
- Updated dependencies [aa34661f]
- Updated dependencies [8e0c9d76]
- Updated dependencies [e2c18895]
- blitz@2.0.0-beta.5

View File

@@ -1,12 +0,0 @@
## base-web
The Blitz Recipe for installing Base Web
## more information
- [Base Web Homepage](https://baseweb.design/)
- [Github page](https://github.com/uber/baseweb)
## contributors
- Konrad Kalemba <konrad@kale.mba>

View File

@@ -1,355 +0,0 @@
import {addImport, paths, RecipeBuilder} from "blitz/installer"
import j from "jscodeshift"
import {join} from "path"
export default RecipeBuilder()
.setName("Base Web")
.setDescription(`This will install all necessary dependencies and configure Base Web for use.`)
.setOwner("Konrad Kalemba <konrad@kale.mba>")
.setRepoLink("https://github.com/blitz-js/blitz/")
.addAddDependenciesStep({
stepId: "addDeps",
stepName: "Add dependencies",
explanation: `Add 'baseui' and Styletron as a dependency too -- it's a toolkit for CSS in JS styling which Base Web relies on.`,
packages: [
{name: "baseui", version: "^10.5.0"},
{name: "styletron-engine-atomic", version: "^1.4.8"},
{name: "styletron-react", version: "^6.0.2"},
{name: "@types/styletron-engine-atomic", version: "^1.1.1"},
{name: "@types/styletron-react", version: "^5.0.3"},
],
})
.addNewFilesStep({
stepId: "addStyletronUtil",
stepName: "Add Styletron util file",
explanation: `Next, we need to add a util file that will help us to make Styletron work both client- and server-side.`,
targetDirectory: "./utils",
templatePath: join(__dirname, "templates", "utils"),
templateValues: {},
})
.addTransformFilesStep({
stepId: "addStyletronAndBaseProvidersToApp",
stepName: "Import required providers and wrap the root of the app with them",
explanation: `Additionally we supply StyletronProvider with 'value' and 'debug' props. BaseProvider requires a 'theme' prop we set with default Base Web's light theme.`,
singleFileSearch: paths.app(),
transform(program) {
const styletronProviderImport = j.importDeclaration(
[j.importSpecifier(j.identifier("Provider"), j.identifier("StyletronProvider"))],
j.literal("styletron-react"),
)
const styletronAndDebugImport = j.importDeclaration(
[j.importSpecifier(j.identifier("styletron")), j.importSpecifier(j.identifier("debug"))],
j.literal("utils/styletron"),
)
const themeAndBaseProviderImport = j.importDeclaration(
[
j.importSpecifier(j.identifier("LightTheme")),
j.importSpecifier(j.identifier("BaseProvider")),
],
j.literal("baseui"),
)
addImport(program, styletronProviderImport)
addImport(program, styletronAndDebugImport)
addImport(program, themeAndBaseProviderImport)
program
.find(j.FunctionDeclaration, (node) => node.id.name === "MyApp")
.forEach((path) => {
const statement = path.value.body.body.filter(
(b) => b.type === "ReturnStatement",
)[0] as j.ReturnStatement
const argument = statement?.argument as j.JSXElement
statement.argument = j.jsxElement(
j.jsxOpeningElement(j.jsxIdentifier("StyletronProvider"), [
j.jsxAttribute(
j.jsxIdentifier("value"),
j.jsxExpressionContainer(j.identifier("styletron")),
),
j.jsxAttribute(
j.jsxIdentifier("debug"),
j.jsxExpressionContainer(j.identifier("debug")),
),
j.jsxAttribute(j.jsxIdentifier("debugAfterHydration")),
]),
j.jsxClosingElement(j.jsxIdentifier("StyletronProvider")),
[
j.literal("\n"),
j.jsxElement(
j.jsxOpeningElement(j.jsxIdentifier("BaseProvider"), [
j.jsxAttribute(
j.jsxIdentifier("theme"),
j.jsxExpressionContainer(j.identifier("LightTheme")),
),
]),
j.jsxClosingElement(j.jsxIdentifier("BaseProvider")),
[j.literal("\n"), argument, j.literal("\n")],
),
j.literal("\n"),
],
)
})
return program
},
})
.addTransformFilesStep({
stepId: "modifyGetInitialPropsAndAddStylesheetsToDocument",
stepName: "Modify getInitialProps method and add stylesheets to Document",
explanation: `To make Styletron work server-side we need to modify getInitialProps method of custom Document class. We also have to put Styletron's generated stylesheets in DocumentHead.`,
singleFileSearch: paths.document(),
transform(program) {
const styletronProviderImport = j.importDeclaration(
[j.importSpecifier(j.identifier("Provider"), j.identifier("StyletronProvider"))],
j.literal("styletron-react"),
)
const styletronServerAndSheetImport = j.importDeclaration(
[j.importSpecifier(j.identifier("Server")), j.importSpecifier(j.identifier("Sheet"))],
j.literal("styletron-engine-atomic"),
)
const styletronImport = j.importDeclaration(
[j.importSpecifier(j.identifier("styletron"))],
j.literal("utils/styletron"),
)
addImport(program, styletronProviderImport)
addImport(program, styletronServerAndSheetImport)
addImport(program, styletronImport)
program
.find(j.ImportDeclaration, {source: {value: "next/document"}})
.forEach((nextDocumentImportPath) => {
let specifiers = nextDocumentImportPath.value.specifiers || []
if (
!specifiers
.filter((spec) => j.ImportSpecifier.check(spec))
.some((node) => (node as j.ImportSpecifier)?.imported?.name === "DocumentContext")
) {
specifiers.push(j.importSpecifier(j.identifier("DocumentContext")))
}
})
program.find(j.ClassDeclaration).forEach((path) => {
const props = j.typeAlias(
j.identifier("MyDocumentProps"),
null,
j.objectTypeAnnotation([
j.objectTypeProperty(
j.identifier("stylesheets"),
j.arrayTypeAnnotation(j.genericTypeAnnotation(j.identifier("Sheet"), null)),
false,
),
]),
)
path.insertBefore(props)
path.value.superTypeParameters = j.typeParameterInstantiation([
j.genericTypeAnnotation(j.identifier("MyDocumentProps"), null),
])
})
program.find(j.ClassBody).forEach((path) => {
const {node} = path
const ctxParam = j.identifier("ctx")
ctxParam.typeAnnotation = j.tsTypeAnnotation(
j.tsTypeReference(j.identifier("DocumentContext")),
)
const stylesheetsObjectProperty = j.objectProperty(
j.identifier("stylesheets"),
j.identifier("stylesheets"),
)
stylesheetsObjectProperty.shorthand = true
const getInitialPropsBody = j.blockStatement([
j.variableDeclaration("const", [
j.variableDeclarator(
j.identifier("originalRenderPage"),
j.memberExpression(j.identifier("ctx"), j.identifier("renderPage")),
),
]),
j.expressionStatement(
j.assignmentExpression(
"=",
j.memberExpression(j.identifier("ctx"), j.identifier("renderPage")),
j.arrowFunctionExpression(
[],
j.callExpression(j.identifier("originalRenderPage"), [
j.objectExpression([
j.objectProperty(
j.identifier("enhanceApp"),
j.arrowFunctionExpression(
[j.identifier("App")],
j.arrowFunctionExpression(
[j.identifier("props")],
j.jsxElement(
j.jsxOpeningElement(j.jsxIdentifier("StyletronProvider"), [
j.jsxAttribute(
j.jsxIdentifier("value"),
j.jsxExpressionContainer(j.identifier("styletron")),
),
]),
j.jsxClosingElement(j.jsxIdentifier("StyletronProvider")),
[
j.literal("\n"),
j.jsxElement(
j.jsxOpeningElement(
j.jsxIdentifier("App"),
[j.jsxSpreadAttribute(j.identifier("props"))],
true,
),
),
j.literal("\n"),
],
),
),
),
),
]),
]),
),
),
),
j.variableDeclaration("const", [
j.variableDeclarator(
j.identifier("initialProps"),
j.awaitExpression(
j.callExpression(
j.memberExpression(j.identifier("Document"), j.identifier("getInitialProps")),
[j.identifier("ctx")],
),
),
),
]),
j.variableDeclaration("const", [
j.variableDeclarator(
j.identifier("stylesheets"),
j.logicalExpression(
"||",
j.callExpression(
j.memberExpression(
j.parenthesizedExpression(
j.tsAsExpression(
j.identifier("styletron"),
j.tsTypeReference(j.identifier("Server")),
),
),
j.identifier("getStylesheets"),
),
[],
),
j.arrayExpression([]),
),
),
]),
j.returnStatement(
j.objectExpression([
j.spreadElement(j.identifier("initialProps")),
stylesheetsObjectProperty,
]),
),
])
const getInitialPropsMethod = j.classMethod(
"method",
j.identifier("getInitialProps"),
[ctxParam],
getInitialPropsBody,
false,
true,
)
getInitialPropsMethod.async = true
// TODO: better way will be to check if the method already exists and modify it or else add it
// currently it gets added assuming it did not exist before
node.body.splice(0, 0, getInitialPropsMethod)
})
program.find(j.JSXElement, {openingElement: {name: {name: "Head"}}}).forEach((path) => {
const {node} = path
path.replace(
j.jsxElement(
j.jsxOpeningElement(j.jsxIdentifier("Head")),
j.jsxClosingElement(j.jsxIdentifier("Head")),
[
...(node.children || []),
j.literal("\n"),
j.jsxExpressionContainer(
j.callExpression(
j.memberExpression(
j.memberExpression(
j.memberExpression(j.thisExpression(), j.identifier("props")),
j.identifier("stylesheets"),
),
j.identifier("map"),
),
[
j.arrowFunctionExpression(
[j.identifier("sheet"), j.identifier("i")],
j.jsxElement(
j.jsxOpeningElement(
j.jsxIdentifier("style"),
[
j.jsxAttribute(
j.jsxIdentifier("className"),
j.literal("_styletron_hydrate_"),
),
j.jsxAttribute(
j.jsxIdentifier("dangerouslySetInnerHTML"),
j.jsxExpressionContainer(
j.objectExpression([
j.objectProperty(
j.identifier("__html"),
j.memberExpression(j.identifier("sheet"), j.identifier("css")),
),
]),
),
),
j.jsxAttribute(
j.jsxIdentifier("media"),
j.jsxExpressionContainer(
j.memberExpression(
j.memberExpression(j.identifier("sheet"), j.identifier("attrs")),
j.identifier("media"),
),
),
),
j.jsxAttribute(
j.jsxIdentifier("data-hydrate"),
j.jsxExpressionContainer(
j.memberExpression(
j.memberExpression(j.identifier("sheet"), j.identifier("attrs")),
j.stringLiteral("data-hydrate"),
true,
),
),
),
j.jsxAttribute(
j.jsxIdentifier("key"),
j.jsxExpressionContainer(j.jsxIdentifier("i")),
),
],
true,
),
),
),
],
),
),
j.literal("\n"),
],
),
)
})
return program
},
})
.build()

View File

@@ -1,32 +0,0 @@
{
"name": "@blitzjs/recipe-base-web",
"private": true,
"version": "2.0.0-beta.11",
"description": "The Blitz Recipe for installing Base Web",
"main": "index.ts",
"scripts": {
"test": "echo \"No tests yet\""
},
"repository": {
"type": "git",
"url": "https://github.com/blitz-js/blitz.git"
},
"keywords": [
"blitz",
"blitzjs",
"base-web"
],
"author": "Konrad Kalemba <konrad@kale.mba>",
"license": "MIT",
"bugs": {
"url": "https://github.com/blitz-js/blitz/issues"
},
"homepage": "https://github.com/blitz-js/blitz#readme",
"dependencies": {
"blitz": "2.2.1",
"jscodeshift": "0.13.0"
},
"devDependencies": {
"@types/jscodeshift": "0.11.2"
}
}

View File

@@ -1,14 +0,0 @@
import {Client, Server} from "styletron-engine-atomic"
import {DebugEngine} from "styletron-react"
const getHydrateClass = () =>
document.getElementsByClassName("_styletron_hydrate_") as HTMLCollectionOf<HTMLStyleElement>
export const styletron =
typeof window === "undefined"
? new Server()
: new Client({
hydrate: getHydrateClass(),
})
export const debug = process.env.NODE_ENV === "production" ? void 0 : new DebugEngine()

View File

@@ -1,25 +0,0 @@
# @blitzjs/recipe-bulma
## 2.0.0-beta.11
### Patch Changes
- Updated dependencies [1476a577]
- blitz@2.0.0-beta.11
## 2.0.0-beta.10
### Patch Changes
- Updated dependencies [9db6c885]
- Updated dependencies [d98e4bac]
- Updated dependencies [9fe0cc54]
- Updated dependencies [af58e2b2]
- Updated dependencies [2ade7268]
- Updated dependencies [0edeaa37]
- Updated dependencies [430f6ec7]
- Updated dependencies [15d22af2]
- Updated dependencies [aa34661f]
- Updated dependencies [8e0c9d76]
- Updated dependencies [e2c18895]
- blitz@2.0.0-beta.5

View File

@@ -1,17 +0,0 @@
## bulma
The Blitz Recipe for installing Bulma CSS
```
blitz install bulma
```
## More information
- [How to use receipes in Blitz](https://blitzjs.com/docs/using-recipes)
- [Learn about Bulma CSS](https://bulma.io/)
- [Bulma CSS's Github](https://github.com/jgthms/bulma)
## Contributors
- vivek <vivek7405@hey.com>

View File

@@ -1,37 +0,0 @@
import {addImport, paths, RecipeBuilder} from "blitz/installer"
import j from "jscodeshift"
import {join} from "path"
export default RecipeBuilder()
.setName("Bulma CSS")
.setDescription(`This will install all necessary dependencies and configure Bulma for use.`)
.setOwner("vivek7405@hey.com")
.setRepoLink("https://github.com/blitz-js/blitz/")
.addAddDependenciesStep({
stepId: "addDeps",
stepName: "npm dependencies",
explanation: `Bulma CSS requires a couple of dependencies including sass for converting sass and scss to css`,
packages: [
{name: "bulma", version: "0.9.x", isDevDep: true},
{name: "sass", version: "1.43.x", isDevDep: true},
],
})
.addNewFilesStep({
stepId: "addStyles",
stepName: "Stylesheet",
explanation: `Adds a root CSS stylesheet where Bulma is imported and where you can add global styles`,
targetDirectory: "./app/core/styles",
templatePath: join(__dirname, "templates", "styles"),
templateValues: {},
})
.addTransformFilesStep({
stepId: "importStyles",
stepName: "Import stylesheets",
explanation: `Imports the stylesheet we just added into your app`,
singleFileSearch: paths.app(),
transform(program) {
const stylesImport = j.importDeclaration([], j.literal("app/core/styles/index.scss"))
return addImport(program, stylesImport)
},
})
.build()

View File

@@ -1,31 +0,0 @@
{
"name": "@blitzjs/recipe-bulma",
"private": true,
"version": "2.0.0-beta.11",
"description": "The Blitz Recipe for installing Bulma CSS",
"main": "index.ts",
"scripts": {
"test": "echo \"No tests yet\""
},
"repository": {
"type": "git",
"url": "https://github.com/blitz-js/blitz.git"
},
"keywords": [
"blitz",
"blitzjs"
],
"author": "vivek <vivek7405@hey.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/blitz-js/blitz/issues"
},
"homepage": "https://github.com/blitz-js/blitz#readme",
"dependencies": {
"blitz": "2.2.1",
"jscodeshift": "0.13.0"
},
"devDependencies": {
"@types/jscodeshift": "0.11.2"
}
}

View File

@@ -1,29 +0,0 @@
@charset "utf-8";
// Customization
// You can easily customize Bulma with your own variables.
// Just uncomment the following block to see the result.
/*
1. Import the initial variables
@import "node_modules/bulma/sass/utilities/initial-variables";
2. Set your own initial variables
Update the blue shade, used for links
$blue: #06bcef;
Add pink and its invert
$pink: #ff8080;
$pink-invert: #fff;
Update the sans-serif font family
$family-sans-serif: "Helvetica", "Arial", sans-serif;
3. Set the derived variables
Use the new pink as the primary color
$primary: $pink;
$primary-invert: $pink-invert;
4. Import the rest of Bulma
*/
@import "node_modules/bulma/bulma.sass";

View File

@@ -1,25 +0,0 @@
# @blitzjs/recipe-bumbag-ui
## 2.0.0-beta.11
### Patch Changes
- Updated dependencies [1476a577]
- blitz@2.0.0-beta.11
## 2.0.0-beta.10
### Patch Changes
- Updated dependencies [9db6c885]
- Updated dependencies [d98e4bac]
- Updated dependencies [9fe0cc54]
- Updated dependencies [af58e2b2]
- Updated dependencies [2ade7268]
- Updated dependencies [0edeaa37]
- Updated dependencies [430f6ec7]
- Updated dependencies [15d22af2]
- Updated dependencies [aa34661f]
- Updated dependencies [8e0c9d76]
- Updated dependencies [e2c18895]
- blitz@2.0.0-beta.5

View File

@@ -1,12 +0,0 @@
## bumbag-ui
The Blitz Recipe for installing Bumbag UI
## more information
- [Bumbag UI Homepage](https://www.bumbag.style/)
- [Github page](https://github.com/jxom/bumbag-ui)
## contributors
- Agusti Fernandez Pardo <agusti@cruceritis.com>

View File

@@ -1,121 +0,0 @@
import {addImport, paths, Program, RecipeBuilder} from "blitz/installer"
import j from "jscodeshift"
function wrapComponentWithBumbagProvider(program: Program) {
program
.find(j.FunctionDeclaration, (node) => node.id.name === "MyApp")
.forEach((path) => {
const statement = path.value.body.body.filter(
(b) => b.type === "ReturnStatement",
)[0] as j.ReturnStatement
const argument = statement?.argument as j.JSXElement
try {
statement.argument = j.jsxElement(
j.jsxOpeningElement(j.jsxIdentifier("BumbagProvider isSSR")),
j.jsxClosingElement(j.jsxIdentifier("BumbagProvider")),
[j.jsxText("\n"), argument, j.jsxText("\n")],
)
} catch {
console.error("Already installed recipe")
}
})
return program
}
function injectInitializeColorModeAndExtractCritical(program: Program) {
// Finds body element and injects InitializeColorMode before it.
program.find(j.JSXElement, {openingElement: {name: {name: "body"}}}).forEach((path) => {
const {node} = path
path.replace(
j.jsxElement(
j.jsxOpeningElement(j.jsxIdentifier("body")),
j.jsxClosingElement(j.jsxIdentifier("body")),
[
j.literal("\n"),
j.jsxElement(j.jsxOpeningElement(j.jsxIdentifier("InitializeColorMode"), [], true)),
...(node.children || []),
],
),
)
})
// Find ClassDeclaration and insert extractCritical on getInitialProps
program
.find(j.ClassDeclaration)
.at(0)
.get()
.insertAfter(
`
MyDocument.getInitialProps = async (ctx) => {
const initialProps = await Document.getInitialProps(ctx)
const styles = extractCritical(initialProps.html)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
<style
data-emotion-css={styles.ids.join(' ')}
dangerouslySetInnerHTML={{ __html: styles.css }}
/>
</>
),
}
}
`,
)
return program
}
export default RecipeBuilder()
.setName("Bumbag UI")
.setDescription(
`This will install all necessary dependencies and configure BumbagProvider in your _app and _document`,
)
.setOwner("me@agusti.me")
.setRepoLink("https://github.com/blitz-js/blitz/")
.addAddDependenciesStep({
stepId: "addDeps",
stepName: "npm dependencies",
explanation: `Bumbag UI requires both "bumbag" and "bumbag-server" (SSR) npm packages`,
packages: [
{name: "bumbag", version: "2.x"},
{name: "bumbag-server", version: "2.x"},
],
})
.addTransformFilesStep({
stepId: "importBmumbagProvider",
stepName: "Import BumbagProvider",
explanation: `Import bumbag Provider as BumbagProvider into _app`,
singleFileSearch: paths.app(),
transform(program) {
const stylesImport = j.importDeclaration(
[j.importSpecifier(j.identifier("Provider as BumbagProvider"))],
j.literal("bumbag"),
)
addImport(program, stylesImport)
return wrapComponentWithBumbagProvider(program)
},
})
.addTransformFilesStep({
stepId: "ImportExtractCriticalAndInitializeColorMode",
stepName: "ImportExtractCritical & initializeColorMode",
explanation: `Import InitializeColorMode from bumbag, and extractCritical into _document`,
singleFileSearch: paths.document(),
transform(program) {
const initializeColorModeImport = j.importDeclaration(
[j.importSpecifier(j.identifier("InitializeColorMode"))],
j.literal("bumbag"),
)
const exctractCriticalImport = j.importDeclaration(
[j.importSpecifier(j.identifier("extractCritical"))],
j.literal("bumbag-server"),
)
addImport(program, initializeColorModeImport)
addImport(program, exctractCriticalImport)
return injectInitializeColorModeAndExtractCritical(program)
},
})
.build()

View File

@@ -1,34 +0,0 @@
{
"name": "@blitzjs/recipe-bumbag-ui",
"private": true,
"version": "2.0.0-beta.11",
"description": "The Blitz Recipe for installing Bumbag UI",
"main": "index.ts",
"scripts": {
"test": "echo \"No tests yet\""
},
"repository": {
"type": "git",
"url": "https://github.com/blitz-js/blitz.git"
},
"keywords": [
"blitz",
"blitzjs",
"chakra",
"bumbag-ui"
],
"author": "Agusti Fernandez Pardo <agusti@cruceritis.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/blitz-js/blitz/issues"
},
"homepage": "https://github.com/blitz-js/blitz#readme",
"dependencies": {
"blitz": "2.2.1",
"jscodeshift": "0.13.0"
},
"devDependencies": {
"@types/jscodeshift": "0.11.2",
"ast-types": "0.14.2"
}
}

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