Compare commits
7 Commits
@blitzjs/a
...
blitz@3.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
107254ece9 | ||
|
|
816330b9d0 | ||
|
|
18decd1558 | ||
|
|
1610c73f99 | ||
|
|
0a257e9150 | ||
|
|
2661bcd98d | ||
|
|
11c9f00eb9 |
@@ -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,
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"access": "restricted",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["web", "test-*", "toolkit-*", "@blitzjs/recipe-*"]
|
||||
"ignore": ["web", "test-*", "toolkit-*", "next-blitz-auth"]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
90
apps/web/src/pages/page-with-inf-mutate.tsx
Normal file
90
apps/web/src/pages/page-with-inf-mutate.tsx
Normal 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
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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!))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
1
packages/blitz/installer.d.ts
vendored
1
packages/blitz/installer.d.ts
vendored
@@ -1 +0,0 @@
|
||||
export * from "./dist/installer"
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = require("./dist/installer.cjs")
|
||||
@@ -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",
|
||||
|
||||
@@ -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}
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from "../src/installer/index"
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
@@ -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} />
|
||||
}
|
||||
@@ -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)}
|
||||
/>
|
||||
)
|
||||
@@ -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>
|
||||
⎪ {executor.stepName} ⎪
|
||||
</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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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} />
|
||||
)
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
})
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import {useInput} from "ink"
|
||||
|
||||
export function useEnterToContinue(cb: Function, additionalCondition: boolean = true) {
|
||||
useInput((_input, key) => {
|
||||
if (additionalCondition && key.return) {
|
||||
cb()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import {useStdin} from "ink"
|
||||
import {RecipeCLIFlags} from "../types"
|
||||
|
||||
export function useUserInput(cliFlags: RecipeCLIFlags) {
|
||||
const {isRawModeSupported} = useStdin()
|
||||
return isRawModeSupported && !cliFlags.yesToAll
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
2167
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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
Reference in New Issue
Block a user