-
Notifications
You must be signed in to change notification settings - Fork 174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
examples: add public deployment of LinearLite #1929
Changes from 30 commits
7f0b44e
b5eb8b7
366684a
2820dee
aa0fca7
6ea7568
1c7fed1
9c3c9a4
cf2e819
7eb1a1b
3d3793c
1bdb044
bd907e6
c0727fb
4107784
db69979
561bd6d
79efa3a
2415a16
f720944
88a8207
06601e8
256f5ad
b21179c
397af11
82471a3
d7c7356
6d12488
b06f8b1
1f68c74
8df6331
43fe5e3
67a5832
688ff1d
0fc164b
23a54d3
4031da7
1841bac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
name: Deploy Examples | ||
|
||
on: | ||
push: | ||
branches: ['main'] | ||
pull_request: | ||
paths: ['examples/*/**'] | ||
|
||
concurrency: | ||
group: ${{ github.event_name == 'push' && 'prod-deploy-group' || format('examples-pr-{0}', github.event.number) }} | ||
|
||
jobs: | ||
deploy-examples: | ||
name: Deploy Examples | ||
environment: ${{ github.event_name == 'push' && 'Production' || 'Pull request' }} | ||
runs-on: ubuntu-latest | ||
|
||
env: | ||
DEPLOY_ENV: ${{ github.event_name == 'push' && 'production' || format('pr-{0}', github.event.number) }} | ||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_DEFAULT_ACCOUNT_ID }} | ||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
NEON_API_KEY: ${{ secrets.NEON_API_KEY }} | ||
NEON_PROJECT_ID: ${{ secrets.NEON_PROJECT_ID }} | ||
ELECTRIC_API: ${{ secrets.ELECTRIC_API }} | ||
ELECTRIC_ADMIN_API: ${{ secrets.ELECTRIC_ADMIN_API }} | ||
# HONEYCOMB_API_KEY: ${{ secrets.HONEYCOMB_API_KEY }} TODO | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: pnpm/action-setup@v4 | ||
with: | ||
version: 9 | ||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version: 20 | ||
cache: 'pnpm' | ||
|
||
- name: Install dependencies | ||
run: pnpm install --frozen-lockfile | ||
|
||
- name: Cache SST state | ||
uses: actions/cache@v4 | ||
with: | ||
path: .sst | ||
key: sst-cache-main-${{ runner.os }} | ||
restore-keys: | | ||
sst-cache-main-${{ runner.os }} | ||
|
||
- name: Deploy Linearlite | ||
working-directory: examples/linearlite | ||
run: pnpm sst deploy --stage ${{ env.DEPLOY_ENV }} | ||
|
||
- name: Deploy NextJs example | ||
working-directory: examples/nextjs-example | ||
run: pnpm sst deploy --stage ${{ env.DEPLOY_ENV }} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
name: Teardown Examples PR stack | ||
|
||
on: | ||
pull_request: | ||
paths: ['examples/*/**'] | ||
types: [closed] | ||
|
||
concurrency: | ||
group: examples-pr-${{ github.event.number }} | ||
|
||
jobs: | ||
teardown-pr-stack: | ||
name: Teardown Examples PR stack | ||
environment: Pull request | ||
runs-on: ubuntu-latest | ||
|
||
env: | ||
DEPLOY_ENV: ${{ github.event_name == 'push' && 'production' || format('pr-{0}', github.event.number) }} | ||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_DEFAULT_ACCOUNT_ID }} | ||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
NEON_API_KEY: ${{ secrets.NEON_API_KEY }} | ||
NEON_PROJECT_ID: ${{ secrets.NEON_PROJECT_ID }} | ||
ELECTRIC_API: ${{ secrets.ELECTRIC_API }} | ||
ELECTRIC_ADMIN_API: ${{ secrets.ELECTRIC_ADMIN_API }} | ||
# HONEYCOMB_API_KEY: ${{ secrets.HONEYCOMB_API_KEY }} TODO | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: pnpm/action-setup@v4 | ||
with: | ||
version: 9 | ||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version: 20 | ||
cache: 'pnpm' | ||
|
||
- name: Install dependencies | ||
run: pnpm install --frozen-lockfile | ||
|
||
- name: Cache SST state | ||
uses: actions/cache@v4 | ||
with: | ||
path: .sst | ||
key: sst-cache-${{ github.event.number }}-${{ runner.os }} | ||
restore-keys: | | ||
sst-cache-${{ runner.os }} | ||
|
||
- name: Remove Linearlite | ||
working-directory: examples/linearlite | ||
run: | | ||
export PR_NUMBER=${{ github.event.number }} | ||
echo "Removing stage pr-$PR_NUMBER" | ||
pnpm sst remove --stage "pr-$PR_NUMBER" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use $DEPLOY_ENV here to be consistent with other workflow? |
||
|
||
- name: Remove NextJs example | ||
working-directory: examples/nextjs-example | ||
run: | | ||
export PR_NUMBER=${{ github.event.number }} | ||
echo "Removing stage pr-$PR_NUMBER" | ||
pnpm sst remove --stage "pr-$PR_NUMBER" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
dist | ||
.env.local | ||
db/data/ | ||
db/data/ | ||
.sst/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
export const baseUrl = import.meta.env.ELECTRIC_URL ?? `http://localhost:3000` | ||
export const baseUrl = import.meta.env.VITE_ELECTRIC_URL | ||
? new URL(import.meta.env.VITE_ELECTRIC_URL).origin | ||
: `http://localhost:3000` | ||
export const token = import.meta.env.VITE_ELECTRIC_TOKEN ?? `` | ||
export const databaseId = import.meta.env.VITE_DATABASE_ID ?? `` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,11 @@ | ||
import { baseUrl } from './electric' | ||
import { ShapeStreamOptions } from '@electric-sql/client' | ||
import { baseUrl, databaseId, token } from './electric' | ||
|
||
export const issueShape = { | ||
url: `${baseUrl}/v1/shape`, | ||
export const issueShape: ShapeStreamOptions = { | ||
url: `${baseUrl}/v1/shape/`, | ||
table: `issue`, | ||
databaseId, | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* This file is auto-generated by SST. Do not edit. */ | ||
/* tslint:disable */ | ||
/* eslint-disable */ | ||
/// <reference types="vite/client" /> | ||
interface ImportMetaEnv { | ||
readonly VITE_ELECTRIC_URL: string | ||
readonly VITE_ELECTRIC_TOKEN: string | ||
readonly VITE_DATABASE_ID: string | ||
} | ||
interface ImportMeta { | ||
readonly env: ImportMetaEnv | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/* This file is auto-generated by SST. Do not edit. */ | ||
/* tslint:disable */ | ||
/* eslint-disable */ | ||
/* deno-fmt-ignore-file */ | ||
import "sst" | ||
export {} | ||
declare module "sst" { | ||
export interface Resource { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// eslint-disable-next-line @typescript-eslint/triple-slash-reference | ||
/// <reference path="./.sst/platform/config.d.ts" /> | ||
|
||
import { execSync } from 'child_process' | ||
|
||
export default $config({ | ||
app(input) { | ||
return { | ||
name: `linearlite`, | ||
removal: input?.stage === `production` ? `retain` : `remove`, | ||
home: `aws`, | ||
providers: { | ||
cloudflare: `5.42.0`, | ||
aws: { | ||
version: `6.57.0`, | ||
}, | ||
neon: `0.6.3`, | ||
}, | ||
} | ||
}, | ||
async run() { | ||
const project = neon.getProjectOutput({ id: process.env.NEON_PROJECT_ID! }) | ||
const base = { | ||
projectId: project.id, | ||
branchId: project.defaultBranchId, | ||
} | ||
|
||
const db = new neon.Database(`linearlite`, { | ||
...base, | ||
ownerName: `neondb_owner`, | ||
}) | ||
|
||
const databaseUri = getNeonDbUri(project, db) | ||
try { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to be defensive? I actually haven't tested the exception path because everything is working now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah I'd say so until we have our own Provider that handles errors correctly |
||
databaseUri.apply(applyMigrations) | ||
|
||
const electricInfo = databaseUri.apply((uri) => | ||
addDatabaseToElectric(uri) | ||
) | ||
|
||
const website = deployLinearLite(electricInfo) | ||
return { | ||
databaseUri, | ||
database_id: electricInfo.id, | ||
electric_token: electricInfo.token, | ||
website: website.url, | ||
} | ||
} catch (e) { | ||
console.error('Failed to deploy linearlite stack', e) | ||
} | ||
} | ||
}) | ||
|
||
function applyMigrations(uri: string) { | ||
execSync(`pnpm exec pg-migrations apply --directory ./db/migrations`, { | ||
env: { | ||
...process.env, | ||
DATABASE_URL: uri, | ||
}, | ||
}) | ||
} | ||
|
||
function deployLinearLite( | ||
electricInfo: $util.Output<{ id: string; token: string }> | ||
) { | ||
return new sst.aws.StaticSite(`linearlite`, { | ||
environment: { | ||
VITE_ELECTRIC_URL: process.env.ELECTRIC_API!, | ||
VITE_ELECTRIC_TOKEN: electricInfo.token, | ||
VITE_DATABASE_ID: electricInfo.id, | ||
}, | ||
build: { | ||
command: `pnpm run --filter @electric-sql/client --filter @electric-sql/react --filter @electric-examples/linearlite build`, | ||
output: `dist`, | ||
}, | ||
domain: { | ||
name: `linearlite${$app.stage === `production` ? `` : `-stage-${$app.stage}`}.electric-sql.com`, | ||
dns: sst.cloudflare.dns(), | ||
}, | ||
}) | ||
} | ||
|
||
function getNeonDbUri( | ||
project: $util.Output<neon.GetProjectResult>, | ||
db: neon.Database | ||
) { | ||
const passwordOutput = neon.getBranchRolePasswordOutput({ | ||
projectId: project.id, | ||
branchId: project.defaultBranchId, | ||
roleName: db.ownerName, | ||
}) | ||
|
||
return $interpolate`postgresql://${passwordOutput.roleName}:${passwordOutput.password}@${project.databaseHost}/${db.name}?sslmode=require` | ||
} | ||
|
||
async function addDatabaseToElectric( | ||
uri: string | ||
): Promise<{ id: string; token: string }> { | ||
const adminApi = process.env.ELECTRIC_ADMIN_API | ||
|
||
const result = await fetch(`${adminApi}/v1/databases`, { | ||
method: `PUT`, | ||
headers: { 'Content-Type': `application/json` }, | ||
body: JSON.stringify({ | ||
database_url: uri, | ||
region: `us-east-1`, | ||
}), | ||
}) | ||
|
||
if (!result.ok) { | ||
throw new Error( | ||
`Could not add database to Electric (${result.status}): ${await result.text()}` | ||
) | ||
} | ||
|
||
return await result.json() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create a pr deployment when one of the examples is modified. Because we need to teardown the PR stack and it breaks with Neon, maybe we comment this out for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah good call — too much overhead right now — we can run a site locally for a PR if we want to test it.