Skip to content

Commit 460cb9b

Browse files
Cloudflare Functions for better 404s (#1733)
1 parent 9b7ad14 commit 460cb9b

File tree

5 files changed

+96
-50
lines changed

5 files changed

+96
-50
lines changed

.github/workflows/ci.yml

+11-10
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ jobs:
161161
##############################################################
162162

163163
DeployProduction:
164-
name: "Deploy: Production"
164+
name: "Deploy: Production ${{ matrix.app.name }}"
165165
if: github.ref == 'refs/heads/main'
166166
runs-on: ubuntu-latest
167167
timeout-minutes: 15
@@ -181,12 +181,13 @@ jobs:
181181
steps:
182182
- uses: actions/download-artifact@v4
183183
name: deploy-prep-dist
184-
- name: Publish ${{ matrix.app.id }}
185-
uses: cloudflare/pages-action@v1.5.0
186-
with:
187-
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
188-
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
189-
projectName: ${{ matrix.app.cloudflareName }}
190-
directory: ./deploy-prep-dist/${{ matrix.app.path }}
191-
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
192-
184+
- name: Publish ${{ matrix.app.name }}
185+
run: |
186+
npx wrangler pages publish \
187+
./deploy-prep-dist/${{ matrix.app.path }} \
188+
--project-name=${{ matrix.app.cloudflareName }} \
189+
--branch=${{ github.event.workflow_run.head_branch }} \
190+
--commit-hash=${{ github.event.workflow_run.head_commit }}
191+
env:
192+
CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
193+
CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}

.github/workflows/deploy-preview.yml

+23-40
Original file line numberDiff line numberDiff line change
@@ -94,56 +94,39 @@ jobs:
9494
- run: echo "${{ github.event.workflow_run.pull_requests[0].number }}"
9595
id: number
9696

97-
DeployPreview_Tutorial:
98-
name: "Deploy: Preview"
97+
# We can know the URL ahead of time:
98+
# https://<SHA>.limber-glimmer-tutorial.pages.dev
99+
# https://<SHA>.limber-glimdown.pages.dev
100+
DeployPreview:
101+
name: "Deploy: Preview ${{ matrix.app.name }}"
99102
runs-on: ubuntu-latest
100103
timeout-minutes: 15
101104
needs: [Build]
102105
permissions:
103106
contents: read
104-
deployments: write
105-
outputs:
106-
tutorialUrl: ${{ steps.deploy.outputs.url }}
107+
strategy:
108+
matrix:
109+
app:
110+
- { path: "./tutorial/dist", cloudflareName: "limber-glimmer-tutorial", name: "tutorial" }
111+
- { path: "./repl/dist", cloudflareName: "limber-glimdown", name: "limber" }
107112
steps:
108113
- uses: actions/download-artifact@v4
109114
name: deploy-prep-dist
110-
- id: deploy
111-
uses: cloudflare/pages-action@v1.5.0
112-
with:
113-
branch: ${{ github.event.workflow_run.head_branch }}
114-
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
115-
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
116-
projectName: limber-glimmer-tutorial
117-
directory: ./deploy-prep-dist/tutorial/dist
118-
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
119-
120-
DeployPreview_Limber:
121-
name: "Deploy: Preview"
122-
runs-on: ubuntu-latest
123-
timeout-minutes: 15
124-
needs: [Build]
125-
permissions:
126-
contents: read
127-
deployments: write
128-
outputs:
129-
limberUrl: ${{ steps.deploy.outputs.url }}
130-
steps:
131-
- uses: actions/download-artifact@v4
132-
name: deploy-prep-dist
133-
- id: deploy
134-
uses: cloudflare/pages-action@v1.5.0
135-
with:
136-
branch: ${{ github.event.workflow_run.head_branch }}
137-
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
138-
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
139-
projectName: limber-glimdown
140-
directory: ./deploy-prep-dist/repl/dist
141-
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
115+
- name: Preview ${{ matrix.app.name }}
116+
run: |
117+
npx wrangler pages deploy \
118+
./deploy-prep-dist/${{ matrix.app.path }} \
119+
--project-name=${{ matrix.app.cloudflareName }} \
120+
--branch=${{ github.event.workflow_run.head_branch }} \
121+
--commit-hash=${{ github.event.workflow_run.head_commit }}
122+
env:
123+
CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
124+
CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}
142125

143126
PostComment:
144127
name: Post Preview URL as comment to PR
145128
runs-on: ubuntu-latest
146-
needs: [DeployPreview_Limber, DeployPreview_Tutorial, determinePR]
129+
needs: [DeployPreview, determinePR]
147130
permissions:
148131
pull-requests: write
149132
steps:
@@ -154,7 +137,7 @@ jobs:
154137
message: |+
155138
| Project | Preview URL |
156139
| ------- | ----------- |
157-
| Limber | ${{ needs.DeployPreview_Limber.outputs.limberUrl }} |
158-
| Tutorial | ${{ needs.DeployPreview_Tutorial.outputs.tutorialUrl }} |
140+
| Limber | https://${{ github.event.workflow_run.head_commit }}.limber-glimdown.pages.dev |
141+
| Tutorial | https://${{ github.event.workflow_run.head_commit }}.limber-glimmer-tutorial.pages.dev |
159142
160143
[Logs](https://github.com/NullVoxPopuli/limber/actions/runs/${{ github.run_id }})

apps/tutorial/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/node_modules/
1010

1111
# misc
12+
.wrangler/
1213
/.env*
1314
/.pnp*
1415
/.sass-cache

apps/tutorial/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"lint": "pnpm -w exec lint",
1616
"lint:types": "glint",
1717
"lint:fix": "pnpm -w exec lint fix",
18+
"cf": "cd dist && npx wrangler pages dev ./",
1819
"start": "concurrently 'ember serve' 'pnpm _syncPnpm --watch' --names 'serve,sync deps'",
1920
"test:ember": "ember test --test-port 0",
2021
"_syncPnpm": "pnpm sync-dependencies-meta-injected",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
export async function onRequest(context) {
2+
try {
3+
return errorIfNotFound(context);
4+
} catch (err) {
5+
return new Response(`${err.message}\n${err.stack}`, { status: 500 });
6+
}
7+
}
8+
9+
async function errorIfNotFound(context) {
10+
const { request, next } = context;
11+
const url = new URL(request.url);
12+
13+
/**
14+
* If the extension ends with gjs
15+
* or md, we check disk.
16+
* otherwise we fallback to default behavior.
17+
*/
18+
let isGJS = url.pathname.endsWith('.gjs');
19+
let isGTS = url.pathname.endsWith('.gts');
20+
let isMD = url.pathname.endsWith('.md');
21+
22+
let shouldCheck = isGJS || isGTS || isMD;
23+
24+
if (!shouldCheck) {
25+
// let Cloudflare do its thing
26+
return await next();
27+
}
28+
29+
let defaultResponse = await next();
30+
let { status, headers } = defaultResponse;
31+
32+
/**
33+
* Server thinks things are not modified, there is nothing we can do.
34+
* (So we need to make sure we return the correct thing the first time)
35+
*/
36+
if (status === 304) {
37+
return defaultResponse;
38+
}
39+
40+
console.log(url, status, headers, headers.get('content-type'));
41+
/**
42+
* Cloudflare (because of how single-page-apps work)
43+
* returns our index.html as a 200 whenever any URL is requested.
44+
* Normally this is good, but because we have file extensions
45+
* in our URLs, we *only* want to return those files.
46+
* Not the index HTML
47+
*/
48+
if (status <= 400 && headers.get('content-type').includes('text/html')) {
49+
/**
50+
* We return a 404
51+
*/
52+
return new Response(JSON.stringify({
53+
'intercepted-by': 'public/functions/_middleware.js',
54+
}), {
55+
status: 404,
56+
});
57+
}
58+
59+
return defaultResponse;
60+
}

0 commit comments

Comments
 (0)