Compare commits
10 Commits
@blitzjs/a
...
@blitzjs/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b18f81873e | ||
|
|
83b6be7ad5 | ||
|
|
0ef8de04b3 | ||
|
|
107254ece9 | ||
|
|
816330b9d0 | ||
|
|
18decd1558 | ||
|
|
1610c73f99 | ||
|
|
0a257e9150 | ||
|
|
2661bcd98d | ||
|
|
11c9f00eb9 |
@@ -4134,6 +4134,16 @@
|
|||||||
"doc",
|
"doc",
|
||||||
"code"
|
"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,
|
"contributorsPerLine": 7,
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
"access": "restricted",
|
"access": "restricted",
|
||||||
"baseBranch": "main",
|
"baseBranch": "main",
|
||||||
"updateInternalDependencies": "patch",
|
"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
|
|
||||||
26
.github/workflows/main.yml
vendored
26
.github/workflows/main.yml
vendored
@@ -16,12 +16,12 @@ jobs:
|
|||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v5
|
||||||
- uses: pnpm/action-setup@v4.0.0
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 8.6.6
|
version: 8.6.6
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
@@ -37,12 +37,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Build
|
name: Build
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v5
|
||||||
- uses: pnpm/action-setup@v4.0.0
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 8.6.6
|
version: 8.6.6
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
@@ -65,15 +65,15 @@ jobs:
|
|||||||
NODE_VERSION: 18
|
NODE_VERSION: 18
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup PNPM
|
- name: Setup PNPM
|
||||||
uses: pnpm/action-setup@v4.0.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 8.6.6
|
version: 8.6.6
|
||||||
|
|
||||||
- name: Setup node@16
|
- name: Setup node@16
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
@@ -100,7 +100,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
folders: ${{ steps.set-matrix.outputs.folders }}
|
folders: ${{ steps.set-matrix.outputs.folders }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v5
|
||||||
- id: set-matrix
|
- id: set-matrix
|
||||||
name: "Find all folders"
|
name: "Find all folders"
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -125,17 +125,17 @@ jobs:
|
|||||||
- run: echo ${{matrix.folder}}
|
- run: echo ${{matrix.folder}}
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest'
|
if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup PNPM
|
- name: Setup PNPM
|
||||||
if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest'
|
if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest'
|
||||||
uses: pnpm/action-setup@v4.0.0
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 8.6.6
|
version: 8.6.6
|
||||||
|
|
||||||
- name: Setup node@18
|
- name: Setup node@18
|
||||||
if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest'
|
if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest'
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|||||||
@@ -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==">
|
<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>
|
</a>
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- 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 -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
<a aria-label="License" href="https://github.com/blitz-js/blitz/blob/main/LICENSE">
|
<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">
|
<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>
|
||||||
<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://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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@@ -12,15 +12,15 @@
|
|||||||
"schema": "prisma/schema.prisma"
|
"schema": "prisma/schema.prisma"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@hookform/error-message": "2.0.0",
|
"@hookform/error-message": "2.0.0",
|
||||||
"@hookform/resolvers": "2.9.10",
|
"@hookform/resolvers": "2.9.10",
|
||||||
"@prisma/client": "^4.5.0",
|
"@prisma/client": "^4.5.0",
|
||||||
"@tanstack/react-query": "5.51.1",
|
"@tanstack/react-query": "5.51.1",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"flatted": "3.2.7",
|
"flatted": "3.2.7",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"prisma": "^4.5.0",
|
"prisma": "^4.5.0",
|
||||||
|
|||||||
@@ -23,14 +23,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@hookform/error-message": "2.0.0",
|
"@hookform/error-message": "2.0.0",
|
||||||
"@hookform/resolvers": "2.9.10",
|
"@hookform/resolvers": "2.9.10",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"openid-client": "5.2.1",
|
"openid-client": "5.2.1",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
|
|||||||
@@ -24,14 +24,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@hookform/error-message": "2.0.0",
|
"@hookform/error-message": "2.0.0",
|
||||||
"@hookform/resolvers": "2.9.10",
|
"@hookform/resolvers": "2.9.10",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"next-auth": "4.24.7",
|
"next-auth": "4.24.7",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"schema": "./db/schema.prisma"
|
"schema": "./db/schema.prisma"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"@types/jest": "29.2.2",
|
"@types/jest": "29.2.2",
|
||||||
"@types/passport-twitter": "1.0.37",
|
"@types/passport-twitter": "1.0.37",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"jest": "29.3.0",
|
"jest": "29.3.0",
|
||||||
"jest-environment-jsdom": "29.3.0",
|
"jest-environment-jsdom": "29.3.0",
|
||||||
"next": "15.0.1",
|
"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"
|
"prisma:studio": "prisma studio"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@hookform/error-message": "2.0.0",
|
"@hookform/error-message": "2.0.0",
|
||||||
"@hookform/resolvers": "2.9.10",
|
"@hookform/resolvers": "2.9.10",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"delay": "5.0.0",
|
"delay": "5.0.0",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
"prisma:studio": "prisma studio"
|
"prisma:studio": "prisma studio"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"lowdb": "3.0.0",
|
"lowdb": "3.0.0",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
|
|||||||
@@ -16,11 +16,11 @@
|
|||||||
"schema": "db/schema.prisma"
|
"schema": "db/schema.prisma"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"lowdb": "2.1.0",
|
"lowdb": "2.1.0",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@next/bundle-analyzer": "12.0.8",
|
"@next/bundle-analyzer": "12.0.8",
|
||||||
"@types/express": "4.17.13",
|
"@types/express": "4.17.13",
|
||||||
"@types/fs-extra": "9.0.13",
|
"@types/fs-extra": "9.0.13",
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
|
|||||||
@@ -17,12 +17,12 @@
|
|||||||
"prisma:studio": "prisma studio"
|
"prisma:studio": "prisma studio"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"lowdb": "2.1.0",
|
"lowdb": "2.1.0",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
|
|||||||
@@ -16,11 +16,11 @@
|
|||||||
"prisma:studio": "prisma studio"
|
"prisma:studio": "prisma studio"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"lowdb": "3.0.0",
|
"lowdb": "3.0.0",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@next/bundle-analyzer": "12.0.8",
|
"@next/bundle-analyzer": "12.0.8",
|
||||||
"@types/express": "4.17.13",
|
"@types/express": "4.17.13",
|
||||||
"@types/fs-extra": "9.0.13",
|
"@types/fs-extra": "9.0.13",
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
"clean": "rm -rf .turbo && rm -rf node_modules"
|
"clean": "rm -rf .turbo && rm -rf node_modules"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"@tanstack/react-query": "5.51.1",
|
"@tanstack/react-query": "5.51.1",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
|
|||||||
@@ -16,10 +16,10 @@
|
|||||||
"schema": "db/schema.prisma"
|
"schema": "db/schema.prisma"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"lowdb": "3.0.0",
|
"lowdb": "3.0.0",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@next/bundle-analyzer": "12.0.8",
|
"@next/bundle-analyzer": "12.0.8",
|
||||||
"@types/express": "4.17.13",
|
"@types/express": "4.17.13",
|
||||||
"@types/fs-extra": "9.0.13",
|
"@types/fs-extra": "9.0.13",
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
|
|||||||
@@ -16,11 +16,11 @@
|
|||||||
"schema": "db/schema.prisma"
|
"schema": "db/schema.prisma"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/next": "2.2.1",
|
"@blitzjs/next": "3.0.2",
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"lowdb": "3.0.0",
|
"lowdb": "3.0.0",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@next/bundle-analyzer": "12.0.8",
|
"@next/bundle-analyzer": "12.0.8",
|
||||||
"@types/express": "4.17.13",
|
"@types/express": "4.17.13",
|
||||||
"@types/fs-extra": "9.0.13",
|
"@types/fs-extra": "9.0.13",
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blitzjs/config": "workspace:2.2.1",
|
"@blitzjs/config": "workspace:3.0.2",
|
||||||
"@blitzjs/next": "workspace:2.2.1",
|
"@blitzjs/next": "workspace:3.0.2",
|
||||||
"@blitzjs/rpc": "workspace:2.2.1",
|
"@blitzjs/rpc": "workspace:3.0.2",
|
||||||
"@tanstack/react-query": "4.13.0",
|
"@tanstack/react-query": "4.13.0",
|
||||||
"@testing-library/react": "16.0.1",
|
"@testing-library/react": "16.0.1",
|
||||||
"@types/express": "4.17.13",
|
"@types/express": "4.17.13",
|
||||||
|
|||||||
@@ -1,5 +1,32 @@
|
|||||||
# @blitzjs/auth
|
# @blitzjs/auth
|
||||||
|
|
||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 83b6be7ad: Upgrade cookie-session dependency to v2.1.1
|
||||||
|
- 0ef8de04b: fix: Overriden custom cookies with pages router
|
||||||
|
- blitz@3.0.2
|
||||||
|
|
||||||
|
## 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
|
## 2.2.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@blitzjs/auth",
|
"name": "@blitzjs/auth",
|
||||||
"version": "2.2.1",
|
"version": "3.0.2",
|
||||||
"homepage": "https://blitzjs.com/",
|
"homepage": "https://blitzjs.com/",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -28,14 +28,14 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/b64-lite": "1.3.0",
|
"@types/b64-lite": "1.3.0",
|
||||||
"@types/cookie-session": "2.0.44",
|
"@types/cookie-session": "2.0.49",
|
||||||
"@types/oauth": "0.9.1",
|
"@types/oauth": "0.9.1",
|
||||||
"@types/passport": "1.0.7",
|
"@types/passport": "1.0.7",
|
||||||
"@types/secure-password": "3.1.1",
|
"@types/secure-password": "3.1.1",
|
||||||
"b64-lite": "1.4.0",
|
"b64-lite": "1.4.0",
|
||||||
"bad-behavior": "1.0.1",
|
"bad-behavior": "1.0.1",
|
||||||
"cookie": "0.4.1",
|
"cookie": "0.4.1",
|
||||||
"cookie-session": "2.0.0",
|
"cookie-session": "2.1.1",
|
||||||
"debug": "4.3.3",
|
"debug": "4.3.3",
|
||||||
"find-up": "4.1.0",
|
"find-up": "4.1.0",
|
||||||
"http": "0.0.1-security",
|
"http": "0.0.1-security",
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
"url": "0.11.0"
|
"url": "0.11.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"next": "*",
|
"next": "*",
|
||||||
"next-auth": "*",
|
"next-auth": "*",
|
||||||
"secure-password": "4.0.0"
|
"secure-password": "4.0.0"
|
||||||
@@ -67,14 +67,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@testing-library/react": "16.0.1",
|
"@testing-library/react": "16.0.1",
|
||||||
"@types/cookie": "0.4.1",
|
"@types/cookie": "0.4.1",
|
||||||
"@types/debug": "4.1.7",
|
"@types/debug": "4.1.7",
|
||||||
"@types/jsonwebtoken": "8.5.8",
|
"@types/jsonwebtoken": "8.5.8",
|
||||||
"@types/react": "npm:types-react@19.0.0",
|
"@types/react": "npm:types-react@19.0.0",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"next-auth": "4.24.7",
|
"next-auth": "4.24.7",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
|
|||||||
226
packages/blitz-auth/src/server/auth-sessions.test.ts
Normal file
226
packages/blitz-auth/src/server/auth-sessions.test.ts
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
import {expect, describe, it, beforeEach} from "vitest"
|
||||||
|
import {ServerResponse} from "http"
|
||||||
|
import {Writable} from "stream"
|
||||||
|
import {append} from "./auth-sessions"
|
||||||
|
|
||||||
|
class MockServerResponse extends Writable {
|
||||||
|
private headers: Map<string, string | string[]> = new Map()
|
||||||
|
|
||||||
|
getHeader(name: string) {
|
||||||
|
return this.headers.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
setHeader(name: string, value: string | string[]) {
|
||||||
|
this.headers.set(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeaders() {
|
||||||
|
return Object.fromEntries(this.headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
_write(_chunk: unknown, _encoding: string, callback: (error?: Error | null) => void): void {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("append", () => {
|
||||||
|
let res: ServerResponse
|
||||||
|
const COOKIE_PREFIX = "auth-tests-cookie-prefix_s"
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
res = new MockServerResponse() as unknown as ServerResponse
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Blitz Auth Flows", () => {
|
||||||
|
const anonymousSessionCookie = `${COOKIE_PREFIX}AnonymousSessionToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJibGl0empzIjp7ImlzQW5vbnltb3VzIjp0cnVlLCJoYW5kbGUiOiJEVjk4OVZadFpra0lpWHFSOFRPX3Fvem44MHBwWFBnaDphand0IiwicHVibGljRGF0YSI6eyJ1c2VySWQiOm51bGx9LCJhbnRpQ1NSRlRva2VuIjoiM25BdDBZWVI0b0xDNnAtTm1fQW1CeFQxRmJmVmpiaXMifSwiaWF0IjoxNzQwODA0NTE4LCJhdWQiOiJibGl0empzIiwiaXNzIjoiYmxpdHpqcyIsInN1YiI6ImFub255bW91cyJ9.ZpMxWh3Yq2Qe4BXzZ61d4V0YGV2luswF7ovE90DxURM; Path=/; Expires=Thu, 28 Feb 2030 04:48:38 GMT; HttpOnly; SameSite=Lax`
|
||||||
|
const antiCsrfCookie = `${COOKIE_PREFIX}AntiCsrfToken=3nAt0YYR4oLC6p-Nm_AmBxT1FbfVjbis; Path=/; Expires=Thu, 28 Feb 2030 04:48:38 GMT; SameSite=Lax`
|
||||||
|
const publicDataCookie = `${COOKIE_PREFIX}PublicDataToken=eyJ1c2VySWQiOm51bGx9; Path=/; Expires=Thu, 28 Feb 2030 04:48:38 GMT; SameSite=Lax`
|
||||||
|
|
||||||
|
const expiredSessionCookie = `${COOKIE_PREFIX}SessionToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SameSite=Lax`
|
||||||
|
const expiredAnonymousCookie = `${COOKIE_PREFIX}AnonymousSessionToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SameSite=Lax`
|
||||||
|
|
||||||
|
// Login cookies
|
||||||
|
const loginAntiCsrfCookie = `${COOKIE_PREFIX}AntiCsrfToken=1s3yaYs0yThO-DwOuiepJLzycvN090tO; Path=/; Expires=Mon, 31 Mar 2025 04:48:38 GMT; SameSite=Lax`
|
||||||
|
const loginPublicDataCookie = `${COOKIE_PREFIX}PublicDataToken=eyJ1c2VySWQiOjEsInJvbGUiOiJ1c2VyIn0%3D; Path=/; Expires=Mon, 31 Mar 2025 04:48:38 GMT; SameSite=Lax`
|
||||||
|
const loginSessionCookie = `${COOKIE_PREFIX}SessionToken=aGNjc0o5anJ5eTF4bDdqRE5VN09LeEx5QUJoR2toUjc6b3RzO1NaWC1la3YydGR4UGNjWVp6QkM0SlBQbUdWWmZEMlpFOzhhYWU1MDI2M2Q0YmUyNDIxZWYwNDBmMmFhZGI2MDk4YTNiNjhjMTAyZjlmNmNjYTQ4NzUzMGZiYjc0ZTdhYmI7djA%3D; Path=/; Expires=Mon, 31 Mar 2025 04:48:38 GMT; HttpOnly; SameSite=Lax`
|
||||||
|
|
||||||
|
it("should handle anonymous session cookies", () => {
|
||||||
|
append(res, "Set-Cookie", [anonymousSessionCookie, antiCsrfCookie, publicDataCookie])
|
||||||
|
|
||||||
|
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
expect(cookies).toHaveLength(3)
|
||||||
|
expect(cookies[0]).toBe(anonymousSessionCookie)
|
||||||
|
expect(cookies[1]).toBe(antiCsrfCookie)
|
||||||
|
expect(cookies[2]).toBe(publicDataCookie)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should deduplicate cookies when the same one is set twice", () => {
|
||||||
|
append(res, "Set-Cookie", anonymousSessionCookie)
|
||||||
|
append(res, "Set-Cookie", anonymousSessionCookie)
|
||||||
|
|
||||||
|
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
expect(cookies).toHaveLength(1)
|
||||||
|
expect(cookies[0]).toBe(anonymousSessionCookie)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should replace cookies with same name when values change", () => {
|
||||||
|
append(res, "Set-Cookie", anonymousSessionCookie)
|
||||||
|
|
||||||
|
const updatedAnonymousCookie = `${COOKIE_PREFIX}AnonymousSessionToken=NEW_TOKEN_VALUE; Path=/; SameSite=Lax`
|
||||||
|
append(res, "Set-Cookie", updatedAnonymousCookie)
|
||||||
|
|
||||||
|
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
expect(cookies).toHaveLength(1)
|
||||||
|
expect(cookies[0]).toBe(updatedAnonymousCookie)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle session expiration", () => {
|
||||||
|
// First add anonymous session
|
||||||
|
append(res, "Set-Cookie", [anonymousSessionCookie, antiCsrfCookie, publicDataCookie])
|
||||||
|
|
||||||
|
append(res, "Set-Cookie", [expiredSessionCookie, expiredAnonymousCookie])
|
||||||
|
|
||||||
|
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
expect(cookies).toHaveLength(4)
|
||||||
|
|
||||||
|
expect(cookies.find((c) => c === expiredSessionCookie)).toBeDefined()
|
||||||
|
expect(cookies.find((c) => c === expiredAnonymousCookie)).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle login flow cookies", () => {
|
||||||
|
// First anonymous session
|
||||||
|
append(res, "Set-Cookie", [anonymousSessionCookie, antiCsrfCookie, publicDataCookie])
|
||||||
|
|
||||||
|
// Then login, which expires anonymous and sets new session
|
||||||
|
append(res, "Set-Cookie", [
|
||||||
|
expiredAnonymousCookie,
|
||||||
|
loginSessionCookie,
|
||||||
|
loginAntiCsrfCookie,
|
||||||
|
loginPublicDataCookie,
|
||||||
|
])
|
||||||
|
|
||||||
|
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
|
||||||
|
// Should have 4 cookies:
|
||||||
|
// - Original antiCsrf cookie (should be replaced by login one)
|
||||||
|
// - Expired anonymous cookie
|
||||||
|
// - Login session cookie
|
||||||
|
// - Login publicData cookie
|
||||||
|
expect(cookies).toHaveLength(4)
|
||||||
|
|
||||||
|
// Check proper replacement by extracting cookie names
|
||||||
|
const cookieNames = cookies.map((c) => {
|
||||||
|
const namePart = c.substring(0, c.indexOf("="))
|
||||||
|
return namePart
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}AntiCsrfToken`)).toHaveLength(1)
|
||||||
|
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}PublicDataToken`)).toHaveLength(1)
|
||||||
|
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}SessionToken`)).toHaveLength(1)
|
||||||
|
// the expired cookie
|
||||||
|
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}AnonymousSessionToken`)).toHaveLength(
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should properly combine multiple append calls with different cookie groups", () => {
|
||||||
|
append(res, "Set-Cookie", [anonymousSessionCookie, antiCsrfCookie])
|
||||||
|
|
||||||
|
append(res, "Set-Cookie", [publicDataCookie, loginAntiCsrfCookie])
|
||||||
|
|
||||||
|
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
|
||||||
|
expect(cookies).toHaveLength(3)
|
||||||
|
|
||||||
|
const antiCsrfCookies = cookies.filter((c) => c.includes(`${COOKIE_PREFIX}AntiCsrfToken`))
|
||||||
|
expect(antiCsrfCookies).toHaveLength(1)
|
||||||
|
expect(antiCsrfCookies[0]).toBe(loginAntiCsrfCookie)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle the full session flow", () => {
|
||||||
|
append(res, "Set-Cookie", [anonymousSessionCookie, antiCsrfCookie, publicDataCookie])
|
||||||
|
|
||||||
|
const initialCookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
expect(initialCookies).toHaveLength(3)
|
||||||
|
|
||||||
|
append(res, "Set-Cookie", [
|
||||||
|
expiredAnonymousCookie,
|
||||||
|
loginSessionCookie,
|
||||||
|
loginAntiCsrfCookie,
|
||||||
|
loginPublicDataCookie,
|
||||||
|
])
|
||||||
|
const loginCookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
expect(loginCookies).toHaveLength(4)
|
||||||
|
|
||||||
|
append(res, "Set-Cookie", [
|
||||||
|
expiredSessionCookie,
|
||||||
|
anonymousSessionCookie,
|
||||||
|
antiCsrfCookie,
|
||||||
|
publicDataCookie,
|
||||||
|
])
|
||||||
|
const logoutCookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
expect(logoutCookies).toHaveLength(4)
|
||||||
|
|
||||||
|
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
|
||||||
|
const cookieNames = cookies.map((c) => c.substring(0, c.indexOf("=")))
|
||||||
|
|
||||||
|
const counts = cookieNames.reduce((acc, name) => {
|
||||||
|
acc[name] = (acc[name] || 0) + 1
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, number>)
|
||||||
|
|
||||||
|
expect(Object.keys(counts).length).toBe(4)
|
||||||
|
|
||||||
|
Object.values(counts).forEach((count) => {
|
||||||
|
expect(count).toBeLessThanOrEqual(3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle cookies with quoted values and special characters", () => {
|
||||||
|
const specialCookie = `${COOKIE_PREFIX}PublicDataToken="eyJ1c2VySWQiOjEsIm5hbWUiOiJKb2huIERvZSwgSnIuIn0%3D"; Path=/; SameSite=Lax`
|
||||||
|
append(res, "Set-Cookie", specialCookie)
|
||||||
|
|
||||||
|
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
expect(cookies).toHaveLength(1)
|
||||||
|
expect(cookies[0]).toBe(specialCookie)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should properly merge with existing custom cookies already in the response", () => {
|
||||||
|
const customCookie1 = "custom1=value1; Path=/; HttpOnly"
|
||||||
|
const customCookie2 = "custom2=value2; Path=/; HttpOnly"
|
||||||
|
const existingAuthCookie = `${COOKIE_PREFIX}AntiCsrfToken=old-token; Path=/; SameSite=Lax`
|
||||||
|
|
||||||
|
res.setHeader("Set-Cookie", [customCookie1, customCookie2, existingAuthCookie])
|
||||||
|
|
||||||
|
// login
|
||||||
|
append(res, "Set-Cookie", [anonymousSessionCookie, loginAntiCsrfCookie, publicDataCookie])
|
||||||
|
|
||||||
|
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||||
|
|
||||||
|
expect(cookies).toHaveLength(5)
|
||||||
|
|
||||||
|
// Custom cookies should be preserved
|
||||||
|
expect(cookies).toContain(customCookie1)
|
||||||
|
expect(cookies).toContain(customCookie2)
|
||||||
|
|
||||||
|
// Auth cookies should be correctly applied, with antiCsrf being updated
|
||||||
|
expect(cookies).toContain(anonymousSessionCookie)
|
||||||
|
expect(cookies).toContain(loginAntiCsrfCookie)
|
||||||
|
expect(cookies).toContain(publicDataCookie)
|
||||||
|
|
||||||
|
// The old auth cookie should be replaced
|
||||||
|
expect(cookies).not.toContain(existingAuthCookie)
|
||||||
|
|
||||||
|
// Verify we have the right counts of each cookie type
|
||||||
|
const cookieNames = cookies.map((c) => c.substring(0, c.indexOf("=")))
|
||||||
|
expect(cookieNames.filter((n) => n === "custom1")).toHaveLength(1)
|
||||||
|
expect(cookieNames.filter((n) => n === "custom2")).toHaveLength(1)
|
||||||
|
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}AnonymousSessionToken`)).toHaveLength(
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}AntiCsrfToken`)).toHaveLength(1)
|
||||||
|
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}PublicDataToken`)).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -466,9 +466,9 @@ export class SessionContextClass implements SessionContext {
|
|||||||
}
|
}
|
||||||
const cookieHeaders = this._headers.get("set-cookie")
|
const cookieHeaders = this._headers.get("set-cookie")
|
||||||
if (response instanceof Response) {
|
if (response instanceof Response) {
|
||||||
response.headers.set("Set-Cookie", cookieHeaders!)
|
response.headers.append("Set-Cookie", cookieHeaders!)
|
||||||
} else {
|
} else {
|
||||||
response.setHeader("Set-Cookie", splitCookiesString(cookieHeaders!))
|
append(response, "Set-Cookie", splitCookiesString(cookieHeaders!))
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = this._headers.entries()
|
const headers = this._headers.entries()
|
||||||
@@ -1249,12 +1249,31 @@ export async function setPublicDataForUser(userId: PublicData["userId"], data: R
|
|||||||
* @param {string} field
|
* @param {string} field
|
||||||
* @param {string| string[]} val
|
* @param {string| string[]} val
|
||||||
*/
|
*/
|
||||||
function append(res: ServerResponse, field: string, val: string | string[]) {
|
export function append(res: ServerResponse, field: string, val: string | string[]) {
|
||||||
let prev: string | string[] | undefined = res.getHeader(field) as string | string[] | undefined
|
let prev: string | string[] | undefined = res.getHeader(field) as string | string[] | undefined
|
||||||
let value = val
|
let value = val
|
||||||
|
|
||||||
if (prev !== undefined) {
|
if (field.toLowerCase() === "set-cookie") {
|
||||||
// concat the new and prev vals
|
const prevCookies = prev ? (Array.isArray(prev) ? prev : [prev]) : []
|
||||||
|
const newCookies = Array.isArray(val) ? val : [val]
|
||||||
|
|
||||||
|
const allCookies = [...prevCookies, ...newCookies].reduce((acc: string[], cookieHeader) => {
|
||||||
|
return acc.concat(splitCookiesString(cookieHeader))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const cookieMap = new Map()
|
||||||
|
allCookies.forEach((cookieStr) => {
|
||||||
|
const firstSemicolon = cookieStr.indexOf(";")
|
||||||
|
const cookieNameValue = firstSemicolon > -1 ? cookieStr.slice(0, firstSemicolon) : cookieStr
|
||||||
|
const parsed = cookie.parse(cookieNameValue)
|
||||||
|
const name = Object.keys(parsed)[0]
|
||||||
|
if (name) {
|
||||||
|
cookieMap.set(name, cookieStr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
value = Array.from(cookieMap.values())
|
||||||
|
} else if (prev !== undefined) {
|
||||||
value = Array.isArray(prev)
|
value = Array.isArray(prev)
|
||||||
? prev.concat(val)
|
? prev.concat(val)
|
||||||
: Array.isArray(val)
|
: Array.isArray(val)
|
||||||
@@ -1263,7 +1282,6 @@ function append(res: ServerResponse, field: string, val: string | string[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
value = Array.isArray(value) ? value.map(String) : String(value)
|
value = Array.isArray(value) ? value.map(String) : String(value)
|
||||||
|
|
||||||
res.setHeader(field, value)
|
res.setHeader(field, value)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,33 @@
|
|||||||
# @blitzjs/next
|
# @blitzjs/next
|
||||||
|
|
||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @blitzjs/rpc@3.0.2
|
||||||
|
- blitz@3.0.2
|
||||||
|
|
||||||
|
## 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
|
## 2.2.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@blitzjs/next",
|
"name": "@blitzjs/next",
|
||||||
"version": "2.2.1",
|
"version": "3.0.2",
|
||||||
"homepage": "https://blitzjs.com/",
|
"homepage": "https://blitzjs.com/",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"eslint.js"
|
"eslint.js"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/rpc": "2.2.1",
|
"@blitzjs/rpc": "3.0.2",
|
||||||
"@types/hoist-non-react-statics": "3.3.1",
|
"@types/hoist-non-react-statics": "3.3.1",
|
||||||
"copy-webpack-plugin": "11.0.0",
|
"copy-webpack-plugin": "11.0.0",
|
||||||
"debug": "4.3.3",
|
"debug": "4.3.3",
|
||||||
@@ -39,13 +39,13 @@
|
|||||||
"supports-color": "8.1.1"
|
"supports-color": "8.1.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"next": "*",
|
"next": "*",
|
||||||
"react": "*",
|
"react": "*",
|
||||||
"tslog": "4.9.0"
|
"tslog": "4.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@testing-library/dom": "8.13.0",
|
"@testing-library/dom": "8.13.0",
|
||||||
"@testing-library/jest-dom": "5.16.3",
|
"@testing-library/jest-dom": "5.16.3",
|
||||||
"@testing-library/react": "16.0.1",
|
"@testing-library/react": "16.0.1",
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
"@types/react": "npm:types-react@19.0.0",
|
"@types/react": "npm:types-react@19.0.0",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
||||||
"@types/testing-library__react-hooks": "4.0.0",
|
"@types/testing-library__react-hooks": "4.0.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"cross-spawn": "7.0.3",
|
"cross-spawn": "7.0.3",
|
||||||
"find-up": "4.1.0",
|
"find-up": "4.1.0",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
|
|||||||
@@ -1,5 +1,30 @@
|
|||||||
# @blitzjs/rpc
|
# @blitzjs/rpc
|
||||||
|
|
||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- blitz@3.0.2
|
||||||
|
|
||||||
|
## 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
|
## 2.2.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@blitzjs/rpc",
|
"name": "@blitzjs/rpc",
|
||||||
"version": "2.2.1",
|
"version": "3.0.2",
|
||||||
"homepage": "https://blitzjs.com/",
|
"homepage": "https://blitzjs.com/",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -37,18 +37,18 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tanstack/query-core": "5.51.1",
|
"@tanstack/query-core": "5.51.1",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"next": "*",
|
"next": "*",
|
||||||
"react": "*"
|
"react": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blitzjs/auth": "2.2.1",
|
"@blitzjs/auth": "3.0.2",
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@tanstack/query-core": "5.51.1",
|
"@tanstack/query-core": "5.51.1",
|
||||||
"@types/debug": "4.1.7",
|
"@types/debug": "4.1.7",
|
||||||
"@types/react": "npm:types-react@19.0.0",
|
"@types/react": "npm:types-react@19.0.0",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"next": "15.0.1",
|
"next": "15.0.1",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
sanitizeQuery,
|
sanitizeQuery,
|
||||||
sanitizeMutation,
|
sanitizeMutation,
|
||||||
getInfiniteQueryKey,
|
getInfiniteQueryKey,
|
||||||
|
QueryType,
|
||||||
} from "../utils"
|
} from "../utils"
|
||||||
import {useRouter} from "next/compat/router"
|
import {useRouter} from "next/compat/router"
|
||||||
|
|
||||||
@@ -282,7 +283,7 @@ export function usePaginatedQuery<
|
|||||||
// -------------------------
|
// -------------------------
|
||||||
export interface RestInfiniteResult<TResult, TError>
|
export interface RestInfiniteResult<TResult, TError>
|
||||||
extends Omit<UseInfiniteQueryResult<TResult, TError>, "data">,
|
extends Omit<UseInfiniteQueryResult<TResult, TError>, "data">,
|
||||||
QueryCacheFunctions<TResult> {
|
QueryCacheFunctions<InfiniteData<TResult>> {
|
||||||
pageParams: any
|
pageParams: any
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,7 +356,7 @@ export function useInfiniteQuery<
|
|||||||
|
|
||||||
const rest = {
|
const rest = {
|
||||||
...queryRest,
|
...queryRest,
|
||||||
...getQueryCacheFunctions<FirstParam<T>, TResult, T>(queryFn, getQueryParams),
|
...getQueryCacheFunctions<FirstParam<T>, InfiniteData<TResult>, T>(queryFn, getQueryParams),
|
||||||
pageParams: infiniteQueryData?.pageParams,
|
pageParams: infiniteQueryData?.pageParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +368,7 @@ export function useInfiniteQuery<
|
|||||||
// -------------------------
|
// -------------------------
|
||||||
export interface RestInfiniteResult<TResult, TError>
|
export interface RestInfiniteResult<TResult, TError>
|
||||||
extends Omit<UseInfiniteQueryResult<TResult, TError>, "data">,
|
extends Omit<UseInfiniteQueryResult<TResult, TError>, "data">,
|
||||||
QueryCacheFunctions<TResult> {
|
QueryCacheFunctions<InfiniteData<TResult>> {
|
||||||
pageParams: any
|
pageParams: any
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,7 +450,11 @@ export function useSuspenseInfiniteQuery<
|
|||||||
|
|
||||||
const rest = {
|
const rest = {
|
||||||
...queryRest,
|
...queryRest,
|
||||||
...getQueryCacheFunctions<FirstParam<T>, TResult, T>(queryFn, getQueryParams),
|
...getQueryCacheFunctions<FirstParam<T>, InfiniteData<TResult>, T>(
|
||||||
|
queryFn,
|
||||||
|
getQueryParams,
|
||||||
|
QueryType.INFINITE,
|
||||||
|
),
|
||||||
pageParams: infiniteQueryData?.pageParams,
|
pageParams: infiniteQueryData?.pageParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,9 +65,10 @@ export interface QueryCacheFunctions<T> {
|
|||||||
export const getQueryCacheFunctions = <TInput, TResult, T extends AsyncFunc>(
|
export const getQueryCacheFunctions = <TInput, TResult, T extends AsyncFunc>(
|
||||||
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
|
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
|
||||||
params: TInput,
|
params: TInput,
|
||||||
|
queryType: QueryType = QueryType.STANDARD,
|
||||||
): QueryCacheFunctions<TResult> => ({
|
): QueryCacheFunctions<TResult> => ({
|
||||||
setQueryData: (newData, opts = {refetch: true}) => {
|
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>(
|
export function setQueryData<TInput, TResult, T extends AsyncFunc>(
|
||||||
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
|
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
|
||||||
params: TInput,
|
params: TInput,
|
||||||
newData: TResult | ((oldData: TResult | undefined) => TResult | undefined),
|
newData: TResult | ((oldData: TResult | undefined) => TResult | undefined),
|
||||||
opts: MutateOptions = {refetch: true},
|
opts: MutateOptions = {refetch: true},
|
||||||
|
queryType: QueryType = QueryType.STANDARD,
|
||||||
): Promise<void | ReturnType<ReturnType<typeof getQueryClient>["invalidateQueries"]>> {
|
): Promise<void | ReturnType<ReturnType<typeof getQueryClient>["invalidateQueries"]>> {
|
||||||
if (typeof resolver === "undefined") {
|
if (typeof resolver === "undefined") {
|
||||||
throw new Error("setQueryData is missing the first argument - it must be a resolver function")
|
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) => {
|
return new Promise((res) => {
|
||||||
getQueryClient().setQueryData(queryKey, newData)
|
getQueryClient().setQueryData(queryKey, newData)
|
||||||
|
|||||||
@@ -1,5 +1,30 @@
|
|||||||
# blitz
|
# blitz
|
||||||
|
|
||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @blitzjs/generator@3.0.2
|
||||||
|
|
||||||
|
## 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
|
## 2.2.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {BuildConfig} from "unbuild"
|
import {BuildConfig} from "unbuild"
|
||||||
|
|
||||||
const config: BuildConfig = {
|
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"],
|
externals: ["index-browser.cjs", "index-browser.mjs", "index.cjs", "zod", "react"],
|
||||||
declaration: true,
|
declaration: true,
|
||||||
rollup: {
|
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",
|
"name": "blitz",
|
||||||
"version": "2.2.1",
|
"version": "3.0.2",
|
||||||
"homepage": "https://blitzjs.com/",
|
"homepage": "https://blitzjs.com/",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"files": [
|
"files": [
|
||||||
"installer.*",
|
|
||||||
"dist/**",
|
"dist/**",
|
||||||
"bin/**"
|
"bin/**"
|
||||||
],
|
],
|
||||||
@@ -30,7 +29,7 @@
|
|||||||
"blitz": "bin/blitz"
|
"blitz": "bin/blitz"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzjs/generator": "2.2.1",
|
"@blitzjs/generator": "3.0.2",
|
||||||
"@mrleebo/prisma-ast": "0.4.1",
|
"@mrleebo/prisma-ast": "0.4.1",
|
||||||
"@types/global-agent": "2.1.1",
|
"@types/global-agent": "2.1.1",
|
||||||
"arg": "5.0.1",
|
"arg": "5.0.1",
|
||||||
@@ -80,7 +79,7 @@
|
|||||||
"watchpack": "2.1.1"
|
"watchpack": "2.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@types/cookie": "0.4.1",
|
"@types/cookie": "0.4.1",
|
||||||
"@types/cross-spawn": "6.0.2",
|
"@types/cross-spawn": "6.0.2",
|
||||||
"@types/debug": "4.1.7",
|
"@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),
|
generate: () => import("./commands/generate").then((i) => i.generate),
|
||||||
codegen: () => import("./commands/codegen").then((i) => i.codegen),
|
codegen: () => import("./commands/codegen").then((i) => i.codegen),
|
||||||
db: () => import("./commands/db").then((i) => i.db),
|
db: () => import("./commands/db").then((i) => i.db),
|
||||||
install: () => import("./commands/install").then((i) => i.install),
|
|
||||||
console: () => import("./commands/console").then((i) => i.consoleREPL),
|
console: () => import("./commands/console").then((i) => i.consoleREPL),
|
||||||
routes: () => import("./commands/routes").then((i) => i.routes),
|
routes: () => import("./commands/routes").then((i) => i.routes),
|
||||||
}
|
}
|
||||||
@@ -47,7 +46,6 @@ const aliases: Record<string, keyof typeof commands> = {
|
|||||||
e: "export",
|
e: "export",
|
||||||
n: "new",
|
n: "new",
|
||||||
g: "generate",
|
g: "generate",
|
||||||
i: "install",
|
|
||||||
c: "console",
|
c: "console",
|
||||||
r: "routes",
|
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,33 @@
|
|||||||
# @blitzjs/codemod
|
# @blitzjs/codemod
|
||||||
|
|
||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @blitzjs/generator@3.0.2
|
||||||
|
- blitz@3.0.2
|
||||||
|
|
||||||
|
## 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
|
## 2.2.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@blitzjs/codemod",
|
"name": "@blitzjs/codemod",
|
||||||
"version": "2.2.1",
|
"version": "3.0.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "unbuild",
|
"build": "unbuild",
|
||||||
"dev": "watch unbuild src --wait=0.2",
|
"dev": "watch unbuild src --wait=0.2",
|
||||||
@@ -25,9 +25,9 @@
|
|||||||
"@babel/plugin-proposal-class-properties": "7.17.12",
|
"@babel/plugin-proposal-class-properties": "7.17.12",
|
||||||
"@babel/plugin-syntax-jsx": "7.17.12",
|
"@babel/plugin-syntax-jsx": "7.17.12",
|
||||||
"@babel/plugin-syntax-typescript": "7.17.12",
|
"@babel/plugin-syntax-typescript": "7.17.12",
|
||||||
"@blitzjs/generator": "2.2.1",
|
"@blitzjs/generator": "3.0.2",
|
||||||
"arg": "5.0.1",
|
"arg": "5.0.1",
|
||||||
"blitz": "2.2.1",
|
"blitz": "3.0.2",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"cross-spawn": "7.0.3",
|
"cross-spawn": "7.0.3",
|
||||||
"debug": "4.3.3",
|
"debug": "4.3.3",
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "7.12.10",
|
"@babel/preset-env": "7.12.10",
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@types/jscodeshift": "0.11.2",
|
"@types/jscodeshift": "0.11.2",
|
||||||
"@types/node": "18.11.9",
|
"@types/node": "18.11.9",
|
||||||
"ast-types": "0.14.2",
|
"ast-types": "0.14.2",
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
# @blitzjs/config
|
# @blitzjs/config
|
||||||
|
|
||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
## 3.0.1
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
### Major Changes
|
||||||
|
|
||||||
|
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
|
||||||
|
|
||||||
## 2.2.1
|
## 2.2.1
|
||||||
|
|
||||||
## 2.2.0
|
## 2.2.0
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@blitzjs/config",
|
"name": "@blitzjs/config",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.2.1",
|
"version": "3.0.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "5.42.1",
|
"@typescript-eslint/eslint-plugin": "5.42.1",
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
# @blitzjs/generator
|
# @blitzjs/generator
|
||||||
|
|
||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
## 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
|
## 2.2.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@blitzjs/generator",
|
"name": "@blitzjs/generator",
|
||||||
"version": "2.2.1",
|
"version": "3.0.2",
|
||||||
"homepage": "https://blitzjs.com/",
|
"homepage": "https://blitzjs.com/",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"zod": "3.23.8"
|
"zod": "3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@juanm04/cpx": "2.0.1",
|
"@juanm04/cpx": "2.0.1",
|
||||||
"@types/babel__core": "7.1.19",
|
"@types/babel__core": "7.1.19",
|
||||||
"@types/diff": "5.0.2",
|
"@types/diff": "5.0.2",
|
||||||
|
|||||||
@@ -68,18 +68,28 @@ export class AppGenerator extends Generator<AppGeneratorOptions> {
|
|||||||
this.destinationPath("package.json"),
|
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"
|
this.options.useTs ? "ts" : "js"
|
||||||
}`
|
}`
|
||||||
if (this.fs.exists(rpcEndpointPath)) {
|
if (this.fs.exists(appRpcEndpointPath)) {
|
||||||
this.fs.move(
|
this.fs.move(
|
||||||
this.destinationPath(rpcEndpointPath),
|
this.destinationPath(appRpcEndpointPath),
|
||||||
this.destinationPath(
|
this.destinationPath(
|
||||||
`src/app/api/rpc/[[...blitz]]/route.${this.options.useTs ? "ts" : "js"}`,
|
`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") {
|
if (this.options.template.path === "app") {
|
||||||
const pathsToMove = [
|
const pathsToMove = [
|
||||||
"src/app/auth/components/LoginForm.tsx",
|
"src/app/auth/components/LoginForm.tsx",
|
||||||
|
|||||||
@@ -27,10 +27,10 @@
|
|||||||
"@blitzjs/rpc": "latest",
|
"@blitzjs/rpc": "latest",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"blitz": "latest",
|
"blitz": "latest",
|
||||||
"next": "14.2.15",
|
"next": "15.0.1",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
"react": "18.2.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "19.0.0",
|
||||||
"secure-password": "4.0.0",
|
"secure-password": "4.0.0",
|
||||||
"zod": "3.23.8"
|
"zod": "3.23.8"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,10 +27,10 @@
|
|||||||
"@blitzjs/rpc": "latest",
|
"@blitzjs/rpc": "latest",
|
||||||
"@prisma/client": "6.1.0",
|
"@prisma/client": "6.1.0",
|
||||||
"blitz": "latest",
|
"blitz": "latest",
|
||||||
"next": "14.2.15",
|
"next": "15.0.1",
|
||||||
"prisma": "6.1.0",
|
"prisma": "6.1.0",
|
||||||
"react": "18.2.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "19.0.0",
|
||||||
"secure-password": "4.0.0",
|
"secure-password": "4.0.0",
|
||||||
"zod": "3.23.8"
|
"zod": "3.23.8"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"@typescript-eslint/parser": "5.9.1"
|
"@typescript-eslint/parser": "5.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blitzjs/config": "2.2.1",
|
"@blitzjs/config": "3.0.2",
|
||||||
"@types/react": "npm:types-react@19.0.0",
|
"@types/react": "npm:types-react@19.0.0",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
|
|||||||
2278
pnpm-lock.yaml
generated
2278
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>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user