├── .dockerignore ├── .githooks └── pre-push ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── bun.lock ├── go ├── go.mod ├── go.sum └── handler.go ├── infra ├── alerts.ts ├── api.ts ├── auth.ts ├── autodeploy.ts ├── billing.ts ├── bus.ts ├── cluster.ts ├── connect.ts ├── dns.ts ├── email.ts ├── event.ts ├── issues.ts ├── network.ts ├── opencontrol.ts ├── planetscale.ts ├── postgres.ts ├── regions.ts ├── secret.ts ├── stage.ts ├── storage.ts ├── util.ts ├── web.ts ├── websocket.ts └── zero.ts ├── package.json ├── packages ├── backend │ ├── Dockerfile │ ├── package.json │ ├── src │ │ ├── api │ │ │ ├── account.ts │ │ │ ├── agent.ts │ │ │ ├── auth.ts │ │ │ ├── billing.ts │ │ │ ├── debug.ts │ │ │ ├── event.ts │ │ │ ├── github.ts │ │ │ ├── index.ts │ │ │ ├── ingest.ts │ │ │ ├── lambda.ts │ │ │ ├── link.ts │ │ │ ├── local.ts │ │ │ ├── log.ts │ │ │ ├── replicache.ts │ │ │ ├── slack.ts │ │ │ ├── webhook.ts │ │ │ └── workspace.ts │ │ ├── function │ │ │ ├── api.ts │ │ │ ├── auth │ │ │ │ ├── issuer.ts │ │ │ │ └── websocket.ts │ │ │ ├── billing │ │ │ │ ├── cron.ts │ │ │ │ └── fetch-usage.ts │ │ │ ├── connect.ts │ │ │ ├── error.ts │ │ │ ├── events │ │ │ │ ├── event.ts │ │ │ │ ├── runner-updated-external.ts │ │ │ │ └── stack-updated-external.ts │ │ │ ├── issues │ │ │ │ ├── cleanup.ts │ │ │ │ ├── detected.ts │ │ │ │ ├── subscriber-self-hosted.ts │ │ │ │ └── subscriber.ts │ │ │ ├── migrator.ts │ │ │ ├── opencontrol │ │ │ │ └── server.ts │ │ │ └── run │ │ │ │ ├── config-parser.ts │ │ │ │ ├── monitor.ts │ │ │ │ └── runner-remover.ts │ │ ├── index.ts │ │ ├── log-polyfill.ts │ │ ├── replicache │ │ │ ├── dummy │ │ │ │ ├── data.ts │ │ │ │ └── pull.ts │ │ │ ├── framework.ts │ │ │ └── server.ts │ │ └── subjects.ts │ ├── sst-env.d.ts │ └── tsconfig.json ├── build │ ├── buildspec │ │ └── index.mjs │ ├── image │ │ ├── Dockerfile │ │ ├── Dockerfile.codebuild │ │ └── index.mjs │ ├── package.json │ └── sst-env.d.ts ├── cdc │ ├── Dockerfile │ ├── debezium │ │ ├── .dockerignore │ │ ├── .gitignore │ │ ├── Dockerfile │ │ └── planetscale.jar │ ├── package.json │ ├── src │ │ └── index.ts │ ├── sst-env.d.ts │ └── tsconfig.json ├── core │ ├── .sst.config.1719338843207.mjs │ ├── drizzle.config.ts │ ├── drizzle.pg.ts │ ├── migrations-pg │ │ ├── 0000_parched_cargill.sql │ │ ├── 0001_wealthy_infant_terrible.sql │ │ ├── 0002_late_tiger_shark.sql │ │ ├── 0003_aberrant_komodo.sql │ │ ├── 0004_uneven_serpent_society.sql │ │ ├── 0005_tiny_lockjaw.sql │ │ ├── 0006_mighty_omega_flight.sql │ │ ├── 0007_skinny_centennial.sql │ │ ├── 0008_lyrical_shatterstar.sql │ │ ├── 0009_loud_tyrannus.sql │ │ ├── 0010_slippery_zeigeist.sql │ │ └── meta │ │ │ ├── 0000_snapshot.json │ │ │ ├── 0001_snapshot.json │ │ │ ├── 0002_snapshot.json │ │ │ ├── 0003_snapshot.json │ │ │ ├── 0004_snapshot.json │ │ │ ├── 0005_snapshot.json │ │ │ ├── 0006_snapshot.json │ │ │ ├── 0007_snapshot.json │ │ │ ├── 0008_snapshot.json │ │ │ ├── 0009_snapshot.json │ │ │ ├── 0010_snapshot.json │ │ │ └── _journal.json │ ├── migrations │ │ ├── 0000_tricky_rhodey.sql │ │ ├── 0001_shocking_wind_dancer.sql │ │ ├── 0002_empty_preak.sql │ │ ├── 0003_absurd_cammi.sql │ │ ├── 0004_wild_talos.sql │ │ ├── 0005_groovy_chamber.sql │ │ ├── 0005_mute_loners.sql │ │ ├── 0006_real_marvel_zombies.sql │ │ ├── 0007_sloppy_multiple_man.sql │ │ ├── 0008_chubby_doomsday.sql │ │ ├── 0009_cynical_wallow.sql │ │ ├── 0010_tiresome_maximus.sql │ │ ├── 0011_boring_lenny_balinger.sql │ │ ├── 0012_workable_rumiko_fujikawa.sql │ │ ├── 0013_moaning_chameleon.sql │ │ ├── 0014_workable_nightshade.sql │ │ ├── 0015_living_skaar.sql │ │ ├── 0016_swift_killer_shrike.sql │ │ ├── 0017_quick_silvermane.sql │ │ ├── 0018_curly_zaladane.sql │ │ ├── 0019_chubby_lord_tyger.sql │ │ ├── 0020_outstanding_the_spike.sql │ │ ├── 0021_mean_domino.sql │ │ ├── 0022_goofy_sebastian_shaw.sql │ │ ├── 0023_closed_komodo.sql │ │ ├── 0024_flawless_zuras.sql │ │ ├── 0025_early_vulture.sql │ │ ├── 0026_ancient_wrecker.sql │ │ ├── 0027_lively_mystique.sql │ │ ├── 0028_lowly_marvel_boy.sql │ │ ├── 0029_fast_scarlet_witch.sql │ │ ├── 0030_bored_vivisector.sql │ │ ├── 0031_friendly_patch.sql │ │ ├── 0032_mean_butterfly.sql │ │ ├── 0033_whole_goliath.sql │ │ ├── 0034_thin_night_nurse.sql │ │ ├── 0035_fantastic_dorian_gray.sql │ │ ├── 0036_fresh_the_santerians.sql │ │ ├── 0037_slow_makkari.sql │ │ ├── 0038_illegal_sharon_carter.sql │ │ ├── 0039_fantastic_inhumans.sql │ │ ├── 0040_panoramic_sentry.sql │ │ ├── 0041_jittery_landau.sql │ │ ├── 0042_concerned_rick_jones.sql │ │ ├── 0043_premium_bushwacker.sql │ │ ├── 0044_remarkable_blade.sql │ │ ├── 0045_lethal_quicksilver.sql │ │ ├── 0046_brown_starfox.sql │ │ ├── 0047_melodic_franklin_richards.sql │ │ ├── 0048_wise_surge.sql │ │ ├── 0049_acoustic_baron_strucker.sql │ │ ├── 0050_aberrant_edwin_jarvis.sql │ │ ├── 0051_ancient_molly_hayes.sql │ │ ├── 0052_classy_invisible_woman.sql │ │ ├── 0053_stiff_stephen_strange.sql │ │ ├── 0054_fresh_salo.sql │ │ ├── 0055_steady_micromax.sql │ │ ├── 0056_nostalgic_omega_red.sql │ │ ├── 0057_familiar_chat.sql │ │ ├── 0058_melted_zaran.sql │ │ ├── 0059_mute_tiger_shark.sql │ │ ├── 0060_chubby_black_tarantula.sql │ │ ├── 0061_tough_maestro.sql │ │ ├── 0062_known_darkhawk.sql │ │ ├── 0063_large_robbie_robertson.sql │ │ ├── 0064_flowery_toad.sql │ │ ├── 0065_hesitant_kid_colt.sql │ │ ├── 0066_clever_reaper.sql │ │ ├── 0067_chubby_lilandra.sql │ │ ├── 0068_familiar_cerebro.sql │ │ ├── 0069_famous_magneto.sql │ │ ├── 0070_useful_lady_ursula.sql │ │ ├── 0071_bumpy_deathstrike.sql │ │ ├── 0072_busy_morgan_stark.sql │ │ └── meta │ │ │ ├── 0000_snapshot.json │ │ │ ├── 0001_snapshot.json │ │ │ ├── 0002_snapshot.json │ │ │ ├── 0003_snapshot.json │ │ │ ├── 0004_snapshot.json │ │ │ ├── 0005_snapshot.json │ │ │ ├── 0006_snapshot.json │ │ │ ├── 0007_snapshot.json │ │ │ ├── 0008_snapshot.json │ │ │ ├── 0009_snapshot.json │ │ │ ├── 0010_snapshot.json │ │ │ ├── 0011_snapshot.json │ │ │ ├── 0012_snapshot.json │ │ │ ├── 0013_snapshot.json │ │ │ ├── 0014_snapshot.json │ │ │ ├── 0015_snapshot.json │ │ │ ├── 0016_snapshot.json │ │ │ ├── 0017_snapshot.json │ │ │ ├── 0018_snapshot.json │ │ │ ├── 0019_snapshot.json │ │ │ ├── 0020_snapshot.json │ │ │ ├── 0021_snapshot.json │ │ │ ├── 0022_snapshot.json │ │ │ ├── 0023_snapshot.json │ │ │ ├── 0024_snapshot.json │ │ │ ├── 0025_snapshot.json │ │ │ ├── 0026_snapshot.json │ │ │ ├── 0027_snapshot.json │ │ │ ├── 0028_snapshot.json │ │ │ ├── 0029_snapshot.json │ │ │ ├── 0030_snapshot.json │ │ │ ├── 0031_snapshot.json │ │ │ ├── 0032_snapshot.json │ │ │ ├── 0033_snapshot.json │ │ │ ├── 0034_snapshot.json │ │ │ ├── 0035_snapshot.json │ │ │ ├── 0036_snapshot.json │ │ │ ├── 0037_snapshot.json │ │ │ ├── 0038_snapshot.json │ │ │ ├── 0039_snapshot.json │ │ │ ├── 0040_snapshot.json │ │ │ ├── 0041_snapshot.json │ │ │ ├── 0042_snapshot.json │ │ │ ├── 0043_snapshot.json │ │ │ ├── 0044_snapshot.json │ │ │ ├── 0045_snapshot.json │ │ │ ├── 0046_snapshot.json │ │ │ ├── 0047_snapshot.json │ │ │ ├── 0048_snapshot.json │ │ │ ├── 0049_snapshot.json │ │ │ ├── 0050_snapshot.json │ │ │ ├── 0051_snapshot.json │ │ │ ├── 0052_snapshot.json │ │ │ ├── 0053_snapshot.json │ │ │ ├── 0054_snapshot.json │ │ │ ├── 0055_snapshot.json │ │ │ ├── 0056_snapshot.json │ │ │ ├── 0057_snapshot.json │ │ │ ├── 0058_snapshot.json │ │ │ ├── 0059_snapshot.json │ │ │ ├── 0060_snapshot.json │ │ │ ├── 0061_snapshot.json │ │ │ ├── 0062_snapshot.json │ │ │ ├── 0063_snapshot.json │ │ │ ├── 0064_snapshot.json │ │ │ ├── 0065_snapshot.json │ │ │ ├── 0066_snapshot.json │ │ │ ├── 0067_snapshot.json │ │ │ ├── 0068_snapshot.json │ │ │ ├── 0069_snapshot.json │ │ │ ├── 0070_snapshot.json │ │ │ ├── 0071_snapshot.json │ │ │ ├── 0072_snapshot.json │ │ │ └── _journal.json │ ├── package.json │ ├── src │ │ ├── account │ │ │ ├── account.sql.ts │ │ │ └── index.ts │ │ ├── actor.ts │ │ ├── agent │ │ │ └── agent.pg.ts │ │ ├── alert │ │ │ ├── alert.sql.ts │ │ │ └── index.ts │ │ ├── app │ │ │ ├── app.pg.ts │ │ │ ├── app.sql.ts │ │ │ ├── index.ts │ │ │ ├── repo.ts │ │ │ ├── resource.ts │ │ │ └── stage.ts │ │ ├── aws │ │ │ ├── account.ts │ │ │ ├── aws.sql.ts │ │ │ ├── bootstrap.ts │ │ │ └── index.ts │ │ ├── billing │ │ │ ├── billing.sql.ts │ │ │ ├── index.ts │ │ │ └── stripe.ts │ │ ├── context.ts │ │ ├── drizzle │ │ │ ├── index.ts │ │ │ └── postgres.ts │ │ ├── email-octopus │ │ │ └── index.ts │ │ ├── event.ts │ │ ├── git │ │ │ ├── git.sql.ts │ │ │ └── github.ts │ │ ├── issue │ │ │ ├── extract.ts │ │ │ ├── index.ts │ │ │ ├── issue.sql.ts │ │ │ └── send.ts │ │ ├── lambda │ │ │ ├── index.ts │ │ │ └── lambda.sql.ts │ │ ├── log │ │ │ ├── error.ts │ │ │ ├── index.ts │ │ │ ├── lambda.ts │ │ │ ├── log.sql.ts │ │ │ ├── poller.ts │ │ │ └── search.ts │ │ ├── pulumi │ │ │ └── index.ts │ │ ├── realtime │ │ │ └── index.ts │ │ ├── replicache │ │ │ ├── index.ts │ │ │ └── replicache.sql.ts │ │ ├── run │ │ │ ├── codebuild-runner.ts │ │ │ ├── config.ts │ │ │ ├── index.ts │ │ │ ├── run.pg.ts │ │ │ └── run.sql.ts │ │ ├── slack │ │ │ ├── index.ts │ │ │ └── slack.sql.ts │ │ ├── state │ │ │ ├── index.ts │ │ │ ├── pg.ts │ │ │ ├── state.pg.ts │ │ │ └── state.sql.ts │ │ ├── storage │ │ │ └── index.ts │ │ ├── stripe │ │ │ └── index.ts │ │ ├── user │ │ │ ├── index.ts │ │ │ ├── user.pg.ts │ │ │ └── user.sql.ts │ │ ├── util │ │ │ ├── aws.ts │ │ │ ├── benchmark.ts │ │ │ ├── compress.ts │ │ │ ├── disposable.ts │ │ │ ├── error.ts │ │ │ ├── json.ts │ │ │ ├── lazy.ts │ │ │ ├── log.ts │ │ │ ├── object.ts │ │ │ ├── pulumi.ts │ │ │ ├── queue.ts │ │ │ ├── retry.ts │ │ │ ├── sql.pg.ts │ │ │ ├── sql.ts │ │ │ ├── string.ts │ │ │ ├── transaction.ts │ │ │ └── zod.ts │ │ ├── warning │ │ │ ├── index.ts │ │ │ └── warning.sql.ts │ │ ├── websocket │ │ │ └── index.ts │ │ └── workspace │ │ │ ├── index.ts │ │ │ ├── workspace.pg.ts │ │ │ └── workspace.sql.ts │ ├── sst-env.d.ts │ ├── test │ │ ├── __snapshots__ │ │ │ └── log.test.ts.snap │ │ ├── account.test.ts │ │ ├── log.test.ts │ │ ├── log │ │ │ ├── __snapshots__ │ │ │ │ ├── error.test.ts.snap │ │ │ │ └── lambda.test.ts.snap │ │ │ ├── error.test.ts │ │ │ └── lambda.test.ts │ │ └── util │ │ │ └── disposable.test.ts │ └── tsconfig.json ├── mail │ ├── emails │ │ ├── components.tsx │ │ ├── styles.ts │ │ └── templates │ │ │ ├── AutodeployEmail.tsx │ │ │ ├── InviteEmail.tsx │ │ │ ├── IssueEmail.tsx │ │ │ ├── IssueRateLimitEmail.tsx │ │ │ └── static │ │ │ ├── ibm-plex-mono-latin-400.woff2 │ │ │ ├── ibm-plex-mono-latin-500.woff2 │ │ │ ├── ibm-plex-mono-latin-600.woff2 │ │ │ ├── ibm-plex-mono-latin-700.woff2 │ │ │ ├── rubik-latin.woff2 │ │ │ └── sst-logo.png │ ├── package.json │ └── sst-env.d.ts ├── scripts │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── apply-coupon.ts │ │ ├── backfill-run-stage-name.ts │ │ ├── backfill-stripe-price-id.ts │ │ ├── backfill-workspace-stripe-customer-id.ts │ │ ├── common.ts │ │ ├── create-user.ts │ │ ├── create-workspace.ts │ │ ├── grant-trial.ts │ │ ├── integrate.ts │ │ ├── recreate-invoices.ts │ │ ├── subscribe-issues.ts │ │ ├── sync-aws-accounts.ts │ │ ├── sync-stages.ts │ │ └── sync-usage.ts │ ├── sst-env.d.ts │ └── tsconfig.json ├── web │ ├── package.json │ ├── sst-env.d.ts │ └── workspace │ │ ├── .gitignore │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── public │ │ ├── email │ │ ├── og.png │ │ └── screenshot.png │ │ ├── src │ │ ├── App.tsx │ │ ├── assets │ │ │ └── favicon.ico │ │ ├── common │ │ │ ├── context.tsx │ │ │ ├── format.ts │ │ │ ├── hash.ts │ │ │ ├── invocation.tsx │ │ │ ├── keyboard-navigator.tsx │ │ │ ├── pulumi.ts │ │ │ ├── resource-icon.ts │ │ │ └── url-builder.ts │ │ ├── data │ │ │ ├── app.ts │ │ │ ├── aws │ │ │ │ ├── account.ts │ │ │ │ └── index.ts │ │ │ ├── issue.ts │ │ │ ├── lambda-payload.ts │ │ │ ├── log-poller.ts │ │ │ ├── log-search.ts │ │ │ ├── log.ts │ │ │ ├── resource.ts │ │ │ ├── stage.ts │ │ │ ├── store.ts │ │ │ ├── usage.ts │ │ │ ├── user.ts │ │ │ ├── warning.ts │ │ │ └── workspace.ts │ │ ├── index.tsx │ │ ├── logo.svg │ │ ├── pages │ │ │ ├── auth │ │ │ │ └── index.tsx │ │ │ ├── debug │ │ │ │ └── index.tsx │ │ │ ├── design │ │ │ │ └── index.tsx │ │ │ ├── local.tsx │ │ │ ├── not-found.tsx │ │ │ ├── workspace-create.tsx │ │ │ └── workspace │ │ │ │ ├── account.tsx │ │ │ │ ├── app │ │ │ │ ├── autodeploy │ │ │ │ │ ├── detail.tsx │ │ │ │ │ ├── dialog-deploy.tsx │ │ │ │ │ ├── dialog-redeploy.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── list.tsx │ │ │ │ │ └── not-found.tsx │ │ │ │ ├── context.tsx │ │ │ │ ├── header.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── overview.tsx │ │ │ │ ├── settings │ │ │ │ │ └── index.tsx │ │ │ │ └── warning.tsx │ │ │ │ ├── command-bar.tsx │ │ │ │ ├── context.ts │ │ │ │ ├── debug.tsx │ │ │ │ ├── header.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── overview-next.tsx │ │ │ │ ├── overview.tsx │ │ │ │ ├── settings │ │ │ │ ├── alerts.tsx │ │ │ │ ├── aws.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── stage │ │ │ │ ├── agent │ │ │ │ │ ├── chat.tsx │ │ │ │ │ ├── components │ │ │ │ │ │ ├── system.txt │ │ │ │ │ │ └── tool.ts │ │ │ │ │ └── index.tsx │ │ │ │ ├── context.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── issues │ │ │ │ │ ├── common.ts │ │ │ │ │ ├── detail.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── list.tsx │ │ │ │ ├── logs │ │ │ │ │ ├── aws │ │ │ │ │ │ ├── dialog-range.tsx │ │ │ │ │ │ └── next.tsx │ │ │ │ │ ├── dialog-payload-manage.tsx │ │ │ │ │ ├── dialog-payload-save.tsx │ │ │ │ │ ├── dialog-range.tsx │ │ │ │ │ ├── dummy.ts │ │ │ │ │ ├── error.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── invoke.tsx │ │ │ │ │ └── list.tsx │ │ │ │ ├── resources │ │ │ │ │ ├── detail.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── list.tsx │ │ │ │ └── updates │ │ │ │ │ ├── detail.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── list.tsx │ │ │ │ ├── user.tsx │ │ │ │ └── zero.tsx │ │ ├── providers │ │ │ ├── account.tsx │ │ │ ├── bus.tsx │ │ │ ├── dummy.tsx │ │ │ ├── flags.tsx │ │ │ ├── freshpaint.ts │ │ │ ├── invocation.tsx │ │ │ ├── local.tsx │ │ │ ├── realtime.ts │ │ │ ├── replicache-status.ts │ │ │ └── replicache.tsx │ │ ├── sst-env.d.ts │ │ └── ui │ │ │ ├── alert.tsx │ │ │ ├── avatar-icon.tsx │ │ │ ├── button.tsx │ │ │ ├── dropdown.tsx │ │ │ ├── form.tsx │ │ │ ├── histogram.tsx │ │ │ ├── icons │ │ │ ├── custom.tsx │ │ │ └── index.tsx │ │ │ ├── layout.tsx │ │ │ ├── loader.tsx │ │ │ ├── modal.tsx │ │ │ ├── select.tsx │ │ │ ├── splash.tsx │ │ │ ├── switch.tsx │ │ │ ├── tag.tsx │ │ │ ├── text.ts │ │ │ ├── theme.ts │ │ │ └── utility.ts │ │ ├── sst-env.d.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts └── zero │ ├── .gitignore │ ├── package.json │ ├── schema.ts │ ├── sst-env.d.ts │ └── zero-schema.json ├── patches └── @macaron-css%2Fsolid@1.5.3.patch ├── scripts ├── mysql.ts └── psql.ts ├── sst-env.d.ts ├── sst.config.ts ├── sst.v2.config.ts ├── tsconfig.json └── turbo.json /.dockerignore: -------------------------------------------------------------------------------- 1 | 2 | # sst 3 | .sst -------------------------------------------------------------------------------- /.githooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | bun typecheck 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vim 2 | node_modules 3 | .sst 4 | *.DS_Store 5 | *.tsbuildinfo 6 | .turbo 7 | packages/core/src/workflow/ 8 | packages/scripts/src/workflow.ts 9 | .env 10 | .termai 11 | .opencode 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.trailingComma": "all" 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SST Console 2 | 3 | The SST Console is a web based dashboard for managing your SST apps with your team — [**console.sst.dev**](https://console.sst.dev) 4 | 5 | ![SST Console homescreen](packages/web/workspace/public/screenshot.png) 6 | 7 | [Learn more](https://sst.dev/docs/console) about it over on our docs. 8 | -------------------------------------------------------------------------------- /go/go.mod: -------------------------------------------------------------------------------- 1 | module console.sst.dev/testing 2 | 3 | go 1.21.0 4 | 5 | require github.com/aws/aws-lambda-go v1.41.0 6 | -------------------------------------------------------------------------------- /go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-lambda-go v1.41.0 h1:l/5fyVb6Ud9uYd411xdHZzSf2n86TakxzpvIoz7l+3Y= 2 | github.com/aws/aws-lambda-go v1.41.0/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= 8 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | -------------------------------------------------------------------------------- /go/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | 7 | "github.com/aws/aws-lambda-go/lambda" 8 | ) 9 | 10 | func main() { 11 | lambda.Start(func () (*string, error) { 12 | log.Println("Hello, World!") 13 | log.Println("Another log") 14 | return nil, errors.New("This is an error") 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /infra/alerts.ts: -------------------------------------------------------------------------------- 1 | const alerts = new sst.aws.SnsTopic("Alerts"); 2 | 3 | new aws.sns.TopicSubscription("AlertsSubscription", { 4 | topic: alerts.arn, 5 | protocol: "email", 6 | endpoint: 7 | "alert-sst-aaaanfxph6mglwqxacgpdhpbrq@anomaly-innovations.slack.com", 8 | }); 9 | 10 | // Alarm for high concurrent Lambda executions 11 | new aws.cloudwatch.MetricAlarm("AlarmLambda", { 12 | comparisonOperator: "GreaterThanThreshold", 13 | evaluationPeriods: 1, 14 | metricName: "ConcurrentExecutions", 15 | namespace: "AWS/Lambda", 16 | period: 30, 17 | statistic: "Maximum", 18 | threshold: 100, 19 | alarmDescription: "Alarm when concurrent Lambda executions exceed 100", 20 | alarmActions: [alerts.arn], 21 | insufficientDataActions: [], 22 | }); 23 | 24 | export {}; 25 | -------------------------------------------------------------------------------- /infra/auth.ts: -------------------------------------------------------------------------------- 1 | import { domain } from "./dns"; 2 | import { email } from "./email"; 3 | import { database } from "./planetscale"; 4 | 5 | export const auth = new sst.aws.Auth("OpenAuth", { 6 | domain: "openauth." + domain, 7 | issuer: { 8 | dev: false, 9 | link: [database, email], 10 | handler: "packages/backend/src/function/auth/issuer.handler", 11 | environment: { 12 | AUTH_FRONTEND_URL: $dev ? "http://localhost:3000" : "https://" + domain, 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /infra/billing.ts: -------------------------------------------------------------------------------- 1 | import { bus } from "./bus"; 2 | import { database } from "./planetscale"; 3 | import { allSecrets, assumable } from "./secret"; 4 | 5 | const queue = new sst.aws.Queue("BillingQueue", { 6 | fifo: true, 7 | visibilityTimeout: "180 seconds", 8 | }); 9 | 10 | queue.subscribe( 11 | { 12 | link: [database, ...allSecrets], 13 | handler: "packages/backend/src/function/billing/fetch-usage.handler", 14 | permissions: [assumable], 15 | timeout: "3 minutes", 16 | }, 17 | { 18 | batch: { 19 | size: 10, 20 | }, 21 | }, 22 | ); 23 | 24 | new sst.aws.Cron("BillingCron", { 25 | schedule: "cron(0 5 * * ? *)", 26 | job: { 27 | handler: "packages/backend/src/function/billing/cron.handler", 28 | timeout: "900 seconds", 29 | permissions: [assumable], 30 | link: [bus, database, queue, ...allSecrets], 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /infra/bus.ts: -------------------------------------------------------------------------------- 1 | export const bus = new sst.aws.Bus("Bus"); 2 | 3 | new aws.cloudwatch.EventBusPolicy("BusPolicy", { 4 | eventBusName: bus.name, 5 | policy: $jsonStringify({ 6 | Version: "2012-10-17", 7 | Statement: [ 8 | { 9 | Sid: "AllowEvents", 10 | Effect: "Allow", 11 | Principal: { 12 | AWS: "*", 13 | Service: "s3.amazonaws.com", 14 | }, 15 | Action: "events:PutEvents", 16 | Resource: bus.nodes.bus.arn, 17 | }, 18 | ], 19 | }), 20 | }); 21 | -------------------------------------------------------------------------------- /infra/cluster.ts: -------------------------------------------------------------------------------- 1 | import { vpc } from "./network"; 2 | 3 | export const cluster = new sst.aws.Cluster("Cluster", { 4 | vpc, 5 | }); 6 | -------------------------------------------------------------------------------- /infra/dns.ts: -------------------------------------------------------------------------------- 1 | const PRODUCTION = "console.sst.dev"; 2 | const DEV = "dev.console.sst.dev"; 3 | 4 | export const { zone, domain } = (() => { 5 | if ($app.stage === "production") 6 | return { 7 | zone: new aws.route53.Zone( 8 | "Zone", 9 | { 10 | name: PRODUCTION, 11 | }, 12 | { 13 | retainOnDelete: true, 14 | import: "Z001790632MKQEXQUOINJ", 15 | }, 16 | ), 17 | domain: PRODUCTION, 18 | }; 19 | 20 | if ($app.stage === "dev") 21 | return { 22 | zone: new aws.route53.Zone( 23 | "Zone", 24 | { 25 | name: DEV, 26 | }, 27 | { 28 | import: "Z04733193GHYW3SIO6DKT", 29 | ignoreChanges: ["*"], 30 | }, 31 | ), 32 | domain: DEV, 33 | }; 34 | 35 | return { 36 | zone: aws.route53.Zone.get("Zone", "Z04733193GHYW3SIO6DKT"), 37 | domain: `${$app.stage}.${DEV}`, 38 | }; 39 | })(); 40 | -------------------------------------------------------------------------------- /infra/email.ts: -------------------------------------------------------------------------------- 1 | import { domain, zone } from "./dns"; 2 | 3 | export const email = new sst.aws.Email("Email", { 4 | sender: domain, 5 | dns: sst.aws.dns({ 6 | override: true, 7 | }), 8 | }); 9 | 10 | // export const email = new sst.Linkable("Email", { 11 | // properties: { 12 | // sender: domain, 13 | // }, 14 | // }); 15 | 16 | // new aws.route53.Record("MX", { 17 | // name: domain, 18 | // zoneId: zone.zoneId, 19 | // type: "MX", 20 | // ttl: 60, 21 | // records: [ 22 | // "aspmx.l.google.com.", 23 | // "alt1.aspmx.l.google.com.", 24 | // "alt2.aspmx.l.google.com.", 25 | // "alt3.aspmx.l.google.com.", 26 | // "alt4.aspmx.l.google.com.", 27 | // ], 28 | // }); 29 | -------------------------------------------------------------------------------- /infra/network.ts: -------------------------------------------------------------------------------- 1 | export const vpc = ["dev", "production"].includes($app.stage) 2 | ? new sst.aws.Vpc("VPC", { 3 | bastion: true, 4 | nat: "managed", 5 | }) 6 | : sst.aws.Vpc.get("VPC", "vpc-069d2d529d3288945"); 7 | -------------------------------------------------------------------------------- /infra/opencontrol.ts: -------------------------------------------------------------------------------- 1 | import { database } from "./planetscale"; 2 | import { allSecrets } from "./secret"; 3 | import { domain } from "./dns"; 4 | 5 | const opencontrol = new sst.aws.OpenControl("OpenControl", { 6 | server: { 7 | handler: "packages/backend/src/function/opencontrol/server.handler", 8 | timeout: "3 minutes", 9 | link: [database, ...allSecrets], 10 | transform: { 11 | role: (args) => { 12 | args.managedPolicyArns = $output(args.managedPolicyArns).apply((v) => [ 13 | ...(v ?? []), 14 | "arn:aws:iam::aws:policy/ReadOnlyAccess", 15 | ]); 16 | }, 17 | }, 18 | }, 19 | }); 20 | 21 | new sst.aws.Router("OpenControlRouter", { 22 | routes: { 23 | "/*": opencontrol.url, 24 | }, 25 | domain: "opencontrol." + domain, 26 | }); 27 | -------------------------------------------------------------------------------- /infra/planetscale.ts: -------------------------------------------------------------------------------- 1 | const mysql = planetscale.Database.get("Database", "sst,sst"); 2 | 3 | const branch = 4 | $app.stage === "production" 5 | ? planetscale.Branch.get("DatabaseBranch", "sst,sst,production") 6 | : new planetscale.Branch( 7 | "DatabaseBranch", 8 | { 9 | database: mysql.name, 10 | organization: mysql.organization, 11 | name: $app.stage, 12 | parentBranch: "production", 13 | production: $app.stage === "production", 14 | }, 15 | {}, 16 | ); 17 | 18 | const password = new planetscale.Password("DatabasePassword", { 19 | database: mysql.name, 20 | organization: mysql.organization, 21 | branch: branch.name, 22 | role: "admin", 23 | name: `${$app.name}-${$app.stage}-password`, 24 | }); 25 | 26 | export const database = new sst.Linkable("Database", { 27 | properties: { 28 | host: branch.mysqlAddress, 29 | username: password.username, 30 | database: password.database, 31 | password: password.plaintext, 32 | port: 3306, 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /infra/postgres.ts: -------------------------------------------------------------------------------- 1 | import { vpc } from "./network"; 2 | import { database } from "./planetscale"; 3 | import { isPermanent } from "./stage"; 4 | 5 | export const postgres = new sst.aws.Aurora("Postgres", { 6 | vpc, 7 | engine: "postgres", 8 | scaling: isPermanent 9 | ? undefined 10 | : { 11 | min: "0 ACU", 12 | max: "1 ACU", 13 | }, 14 | transform: { 15 | clusterParameterGroup: { 16 | parameters: [ 17 | { 18 | name: "rds.logical_replication", 19 | value: "1", 20 | applyMethod: "pending-reboot", 21 | }, 22 | { 23 | name: "max_slot_wal_keep_size", 24 | value: "10240", 25 | applyMethod: "pending-reboot", 26 | }, 27 | ], 28 | }, 29 | }, 30 | }); 31 | 32 | new sst.x.DevCommand("Studio", { 33 | link: [postgres], 34 | dev: { 35 | command: "bun pg studio", 36 | directory: "packages/core", 37 | autostart: true, 38 | }, 39 | }); 40 | 41 | const migrator = new sst.aws.Function("DatabaseMigrator", { 42 | handler: "packages/backend/src/function/migrator.handler", 43 | link: [postgres, database], 44 | vpc, 45 | copyFiles: [ 46 | { 47 | from: "packages/core/migrations-pg", 48 | to: "./migrations-pg", 49 | }, 50 | ], 51 | }); 52 | 53 | if (!$dev) { 54 | new aws.lambda.Invocation("DatabaseMigratorInvocation", { 55 | input: Date.now().toString(), 56 | functionName: migrator.name, 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /infra/regions.ts: -------------------------------------------------------------------------------- 1 | export const regions = ["production"].includes($app.stage) 2 | ? await aws.getRegions().then((r) => r.names) 3 | : ["us-east-1"]; 4 | 5 | const providers = {} as Record; 6 | for (const region of regions) { 7 | if (region === "us-east-1") { 8 | providers[region] = undefined; 9 | } 10 | providers[region] = new aws.Provider("Aws_" + region, { 11 | region: region as any, 12 | }); 13 | } 14 | 15 | export function multiregion( 16 | cb: (region: string, provider?: aws.Provider) => T, 17 | ): Record { 18 | const result = {} as Record; 19 | for (const [region, provider] of Object.entries(providers)) { 20 | result[region] = cb(region, provider); 21 | } 22 | return result; 23 | } 24 | -------------------------------------------------------------------------------- /infra/secret.ts: -------------------------------------------------------------------------------- 1 | export const secret = { 2 | AnthropicKey: new sst.Secret("AnthropicKey"), 3 | GeminiKey: new sst.Secret("GeminiKey"), 4 | StripeSecretKey: new sst.Secret("StripeSecretKey"), 5 | StripeOpenControlSecretKey: new sst.Secret("StripeOpenControlSecretKey"), 6 | StripeWebhookSigningSecret: new sst.Secret("StripeWebhookSigningSecret"), 7 | EmailOctopusSecret: new sst.Secret("EmailOctopusSecret", "disabled"), 8 | StripeInvocationsPriceID: new sst.Secret( 9 | "StripeInvocationsPriceID", 10 | $app.stage === "production" 11 | ? "price_1NlZmAEAHP8a0ogpglxmSac1" 12 | : "price_1NgB4oEAHP8a0ogpxqUXHKee", 13 | ), 14 | StripeResourcesPriceID: new sst.Secret( 15 | "StripeResourcesPriceID", 16 | $app.stage === "production" 17 | ? "price_1QhwLAEAHP8a0ogpjRV91Yl8" 18 | : "price_1Qi4QzEAHP8a0ogpDvPDu8Bm", 19 | ), 20 | StripeCoupon50ID: new sst.Secret( 21 | "StripeCoupon50ID", 22 | $app.stage === "production" ? "SQfanxGc" : "O6e5LLnW", 23 | ), 24 | StripeCoupon80ID: new sst.Secret( 25 | "StripeCoupon80ID", 26 | $app.stage === "production" ? "iZuY8E7x" : "xihoZNwb", 27 | ), 28 | SlackClientID: new sst.Secret("SlackClientID"), 29 | SlackClientSecret: new sst.Secret("SlackClientSecret"), 30 | GithubAppID: new sst.Secret("GithubAppID"), 31 | GithubPrivateKey: new sst.Secret("GithubPrivateKey"), 32 | GithubWebhookSecret: new sst.Secret("GithubWebhookSecret"), 33 | BotpoisonSecretKey: new sst.Secret("BotpoisonSecretKey"), 34 | }; 35 | 36 | export const allSecrets = [...Object.values(secret)]; 37 | 38 | export const assumable = { actions: ["sts:*"], resources: ["*"] }; 39 | -------------------------------------------------------------------------------- /infra/stage.ts: -------------------------------------------------------------------------------- 1 | export const isPermanent = ["dev", "production"].includes($app.stage); 2 | -------------------------------------------------------------------------------- /infra/storage.ts: -------------------------------------------------------------------------------- 1 | import { multiregion } from "./regions"; 2 | 3 | export const storage = new sst.aws.Bucket("Storage", { 4 | transform: { 5 | publicAccessBlock: { 6 | blockPublicAcls: false, 7 | blockPublicPolicy: false, 8 | ignorePublicAcls: false, 9 | restrictPublicBuckets: false, 10 | }, 11 | }, 12 | }); 13 | 14 | new aws.s3.BucketOwnershipControls("ownership-controls", { 15 | bucket: storage.name, 16 | rule: { 17 | objectOwnership: "ObjectWriter", 18 | }, 19 | }); 20 | 21 | // export const storageAccess = new aws.s3.BucketPublicAccessBlock( 22 | // "StorageAccess", 23 | // { 24 | // bucket: storage.name, 25 | // blockPublicAcls: false, 26 | // blockPublicPolicy: false, 27 | // ignorePublicAcls: false, 28 | // restrictPublicBuckets: false, 29 | // }, 30 | // ); 31 | 32 | new aws.s3.BucketLifecycleConfigurationV2("StorageLifecycle", { 33 | bucket: storage.name, 34 | rules: [ 35 | { 36 | id: "daily", 37 | status: "Enabled", 38 | filter: { 39 | prefix: "temporary/daily/", 40 | }, 41 | expiration: { 42 | days: 1, 43 | }, 44 | }, 45 | { 46 | id: "weekly", 47 | status: "Enabled", 48 | filter: { 49 | prefix: "temporary/weekly/", 50 | }, 51 | expiration: { 52 | days: 7, 53 | }, 54 | }, 55 | { 56 | id: "monthly", 57 | status: "Enabled", 58 | filter: { 59 | prefix: "temporary/monthly/", 60 | }, 61 | expiration: { 62 | days: 30, 63 | }, 64 | }, 65 | ], 66 | }); 67 | 68 | export const publicStorage = multiregion((region, provider) => { 69 | const bucket = new sst.aws.Bucket( 70 | "PublicStorage_" + region, 71 | { 72 | access: "public", 73 | transform: { 74 | bucket(args, opts) { 75 | args.bucket = `sst-public-${$app.stage}-${region}`; 76 | // opts.import = args.bucket; 77 | // opts.ignoreChanges = ["*"]; 78 | }, 79 | }, 80 | }, 81 | { 82 | provider, 83 | }, 84 | ); 85 | 86 | return bucket; 87 | }); 88 | -------------------------------------------------------------------------------- /infra/util.ts: -------------------------------------------------------------------------------- 1 | export const ALL_REGIONS = aws.getRegionsOutput().names; 2 | -------------------------------------------------------------------------------- /infra/web.ts: -------------------------------------------------------------------------------- 1 | import { backend } from "./api"; 2 | import { auth } from "./auth"; 3 | import { zero } from "./zero"; 4 | import { connectTemplateUrl } from "./connect"; 5 | import { domain } from "./dns"; 6 | import { issues } from "./issues"; 7 | import { websocket } from "./websocket"; 8 | 9 | new sst.aws.StaticSite("Workspace", { 10 | path: "./packages/web/workspace", 11 | build: { 12 | output: "./dist", 13 | command: "bun run build", 14 | }, 15 | domain: { 16 | name: domain, 17 | dns: sst.aws.dns({ 18 | override: true, 19 | }), 20 | }, 21 | environment: { 22 | VITE_API_URL: backend.url.apply((x) => x.replace(/\/$/, "")), 23 | VITE_AUTH_URL: auth.url, 24 | VITE_STAGE: $app.stage, 25 | VITE_ZERO_URL: zero.url, 26 | VITE_CONNECT_URL: connectTemplateUrl, 27 | VITE_ISSUES_URL: issues.properties.cfn, 28 | VITE_WEBSOCKET_HTTP: websocket.properties.http, 29 | VITE_WEBSOCKET_REALTIME: websocket.properties.realtime, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /infra/websocket.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "./auth"; 2 | import { database } from "./planetscale"; 3 | 4 | const websocketAuthorizer = new sst.aws.Function("WebsocketAuthorizer", { 5 | handler: "packages/backend/src/function/auth/websocket.handler", 6 | link: [database, auth], 7 | }); 8 | 9 | export const websocket = new sst.Linkable("Websocket", { 10 | properties: 11 | $app.stage === "production" 12 | ? { 13 | http: "il74c3crpfbydaoqjni56kv6ky.appsync-api.us-east-1.amazonaws.com", 14 | realtime: 15 | "il74c3crpfbydaoqjni56kv6ky.appsync-realtime-api.us-east-1.amazonaws.com", 16 | token: new sst.Secret("WebsocketToken").value, 17 | } 18 | : { 19 | http: "oyq6tqbrczd5xfovlyvcsd3xtu.appsync-api.us-east-1.amazonaws.com", 20 | realtime: 21 | "oyq6tqbrczd5xfovlyvcsd3xtu.appsync-realtime-api.us-east-1.amazonaws.com", 22 | token: new sst.Secret("WebsocketToken").value, 23 | }, 24 | }); 25 | export {}; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "console", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "engines": { 7 | "node": ">=20.0.0" 8 | }, 9 | "packageManager": "bun@1.2.1", 10 | "workspaces": [ 11 | "packages/*", 12 | "packages/web/*" 13 | ], 14 | "scripts": { 15 | "env": "env", 16 | "prepare": "git config --local core.hooksPath .githooks", 17 | "dev": "sst dev", 18 | "sso": "aws sso login --sso-session=sst --no-browser --use-device-code", 19 | "build": "sst build", 20 | "deploy": "sst deploy", 21 | "remove": "sst remove", 22 | "console": "sst console", 23 | "typecheck": "turbo typecheck" 24 | }, 25 | "devDependencies": { 26 | "@openauthjs/solid": "0.0.0-20250311201457", 27 | "@tsconfig/node16": "^16.1.0", 28 | "@tsconfig/node20": "^20.1.4", 29 | "@types/bun": "^1.1.18", 30 | "constructs": "10.3.0", 31 | "sst": "3.12.5", 32 | "turbo": "2.3.4", 33 | "typescript": "5.5.4" 34 | }, 35 | "patchedDependencies": { 36 | "@macaron-css/solid@1.5.3": "patches/@macaron-css%2Fsolid@1.5.3.patch" 37 | }, 38 | "trustedDependencies": [ 39 | "@rocicorp/zero-sqlite3", 40 | "protobufjs" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /packages/backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mirror.gcr.io/oven/bun:1.2 2 | 3 | ADD ./package.json . 4 | ADD ./bun.lock . 5 | ADD ./packages/core/package.json ./packages/core/package.json 6 | ADD ./packages/backend/package.json ./packages/backend/package.json 7 | ADD ./packages/mail/package.json ./packages/mail/package.json 8 | ADD ./patches ./patches 9 | RUN bun install --ignore-scripts 10 | 11 | ADD ./packages/backend ./packages/backend 12 | ADD ./packages/core ./packages/core 13 | ADD ./packages/mail ./packages/mail 14 | 15 | WORKDIR ./packages/backend 16 | CMD ["bun", "run", "./src/index.ts"] 17 | 18 | -------------------------------------------------------------------------------- /packages/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@console/backend", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "sideEffects": false, 6 | "scripts": { 7 | "typecheck": "tsc --noEmit --incremental", 8 | "dev": "bun run --watch ./src/index.ts" 9 | }, 10 | "exports": { 11 | "./*": "./src/*.ts" 12 | }, 13 | "devDependencies": { 14 | "@types/aws-lambda": "^8.10.114", 15 | "@types/luxon": "3.4.2", 16 | "@types/node": "^22.10.1", 17 | "sst": "3.12.5", 18 | "typescript": "5.5.4" 19 | }, 20 | "dependencies": { 21 | "@ai-sdk/anthropic": "1.2.1", 22 | "@ai-sdk/google": "1.2.8", 23 | "@aws-crypto/sha256-js": "5.2.0", 24 | "@aws-sdk/client-cloudformation": "3.699.0", 25 | "@aws-sdk/client-cloudwatch": "3.600.0", 26 | "@aws-sdk/client-cloudwatch-logs": "3.699.0", 27 | "@aws-sdk/client-lambda": "3.699.0", 28 | "@aws-sdk/client-s3": "3.701.0", 29 | "@aws-sdk/client-scheduler": "3.699.0", 30 | "@aws-sdk/client-sesv2": "3.699.0", 31 | "@aws-sdk/client-sqs": "3.699.0", 32 | "@aws-sdk/client-sts": "3.699.0", 33 | "@aws-sdk/s3-request-presigner": "3.701.0", 34 | "@aws-sdk/signature-v4": "^3.374.0", 35 | "@aws-sdk/util-format-url": "^3.731.0", 36 | "@botpoison/node": "^0.1.10", 37 | "@console/core": "workspace:*", 38 | "@console/mail": "workspace:*", 39 | "@esbuild/linux-arm64": "0.21.4", 40 | "@hono/zod-validator": "0.4.3", 41 | "@modelcontextprotocol/sdk": "1.10.2", 42 | "@openauthjs/openauth": "0.0.0-20250322224806", 43 | "@paralleldrive/cuid2": "2.2.2", 44 | "aws-sdk": "2.1692.0", 45 | "esbuild": "0.21.4", 46 | "fast-jwt": "^2.2.1", 47 | "hono": "4.6.5", 48 | "luxon": "3.5.0", 49 | "oauth4webapi": "^3.1.2", 50 | "octokit": "^4.0.2", 51 | "opencontrol": "0.0.16", 52 | "remeda": "^2.17.3", 53 | "replicache": "14.2.2", 54 | "source-map": "^0.7.4", 55 | "strip-ansi": "^7.1.0", 56 | "undici": "^5.22.0", 57 | "valibot": "^1.0.0-rc.1", 58 | "zod": "3.24.1" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/backend/src/api/account.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { notPublic } from "./auth"; 3 | import { assertActor, withActor } from "@console/core/actor"; 4 | import { Account } from "@console/core/account/index"; 5 | import { zValidator } from "@hono/zod-validator"; 6 | import { Workspace } from "@console/core/workspace/index"; 7 | import { User } from "@console/core/user/index"; 8 | import { VisibleError } from "@console/core/util/error"; 9 | 10 | export const AccountRoute = new Hono() 11 | .use(notPublic) 12 | .get("/", async (c) => { 13 | const actor = assertActor("account"); 14 | return c.json({ 15 | id: actor.properties.accountID, 16 | email: actor.properties.email, 17 | workspaces: await Account.workspaces(), 18 | }); 19 | }) 20 | .post( 21 | "/workspace", 22 | zValidator("json", Workspace.create.schema), 23 | async (c) => { 24 | const actor = assertActor("account"); 25 | const body = c.req.valid("json"); 26 | try { 27 | const workspaceID = await Workspace.create(body); 28 | const workspace = await Workspace.fromID(workspaceID); 29 | await withActor( 30 | { 31 | type: "system", 32 | properties: { 33 | workspaceID, 34 | }, 35 | }, 36 | () => 37 | User.create({ 38 | email: actor.properties.email, 39 | first: true, 40 | }), 41 | ); 42 | return c.json(workspace); 43 | } catch { 44 | throw new VisibleError( 45 | "workspace.slug", 46 | "Workspace slug already exists", 47 | ); 48 | } 49 | }, 50 | ); 51 | -------------------------------------------------------------------------------- /packages/backend/src/api/billing.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { notPublic } from "./auth"; 3 | import { Billing } from "@console/core/billing/index"; 4 | import { stripe } from "@console/core/stripe/index"; 5 | import { DateTime } from "luxon"; 6 | import { Resource } from "sst"; 7 | 8 | export const BillingRoute = new Hono() 9 | .use(notPublic) 10 | .post("/checkout", async (c) => { 11 | const body = await c.req.json(); 12 | 13 | const item = await Billing.Stripe.get(); 14 | if (!item?.customerID) { 15 | throw new Error("No stripe customer ID"); 16 | } 17 | 18 | const session = await stripe.checkout.sessions.create({ 19 | mode: "subscription", 20 | line_items: [ 21 | { 22 | price: Resource.StripeResourcesPriceID.value, 23 | }, 24 | ], 25 | customer: item.customerID, 26 | success_url: body.return_url, 27 | cancel_url: body.return_url, 28 | subscription_data: { 29 | proration_behavior: "none", 30 | billing_cycle_anchor: getAnchorDate().toUnixInteger(), 31 | }, 32 | }); 33 | 34 | return c.json({ 35 | url: session.url, 36 | }); 37 | }) 38 | .post("/portal", async (c) => { 39 | const body = await c.req.json(); 40 | 41 | const item = await Billing.Stripe.get(); 42 | if (!item?.customerID) { 43 | throw new Error("No stripe customer ID"); 44 | } 45 | 46 | const session = await stripe.billingPortal.sessions.create({ 47 | customer: item.customerID, 48 | return_url: body.return_url, 49 | }); 50 | 51 | return c.json({ 52 | url: session.url, 53 | }); 54 | }); 55 | 56 | function getAnchorDate() { 57 | const now = DateTime.now(); 58 | 59 | // check if falls in current month's anchor date 60 | // ie. Current time: Nov 1, 5am UTC 61 | // Anchor date: Nov 1, 12pm UTC 62 | const anchor = now.toUTC().startOf("month").plus({ hour: 12 }); 63 | if (anchor.toUnixInteger() > now.toUnixInteger()) return anchor; 64 | 65 | // ie. Current time: Nov 2, 5am UTC 66 | // Anchor date: Dec 1, 12pm UTC 67 | return now.toUTC().plus({ month: 1 }).startOf("month").plus({ hour: 12 }); 68 | } 69 | -------------------------------------------------------------------------------- /packages/backend/src/api/debug.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { notPublic } from "./auth"; 3 | 4 | export const DebugRoute = new Hono().use(notPublic).get("/", async (c) => {}); 5 | -------------------------------------------------------------------------------- /packages/backend/src/api/lambda.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { notPublic } from "./auth"; 3 | import { zValidator } from "@hono/zod-validator"; 4 | import { Lambda } from "@console/core/lambda/index"; 5 | 6 | export const LambdaRoute = new Hono() 7 | .use(notPublic) 8 | .post("/invoke", zValidator("json", Lambda.invoke.schema), async (c) => { 9 | const requestID = await Lambda.invoke(c.req.valid("json")); 10 | return c.json({ 11 | requestID, 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/backend/src/api/local.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { notPublic } from "./auth"; 3 | import { assertActor } from "@console/core/actor"; 4 | import { zValidator } from "@hono/zod-validator"; 5 | import { z } from "zod"; 6 | import { and, db, eq } from "@console/core/drizzle/index"; 7 | import { workspace } from "@console/core/workspace/workspace.sql"; 8 | import { user } from "@console/core/user/user.sql"; 9 | import { stage, app } from "@console/core/app/app.sql"; 10 | 11 | export const LocalRoute = new Hono().use(notPublic).get( 12 | "/", 13 | zValidator( 14 | "query", 15 | z.object({ 16 | app: z.string(), 17 | stage: z.string(), 18 | }), 19 | ), 20 | async (c) => { 21 | const query = c.req.valid("query"); 22 | const actor = assertActor("account"); 23 | 24 | const result = await db 25 | .select({ 26 | workspace: workspace.slug, 27 | }) 28 | .from(user) 29 | .innerJoin(workspace, eq(workspace.id, user.workspaceID)) 30 | .innerJoin(stage, eq(stage.workspaceID, workspace.id)) 31 | .innerJoin( 32 | app, 33 | and(eq(app.id, stage.appID), eq(workspace.id, app.workspaceID)), 34 | ) 35 | .where( 36 | and( 37 | eq(user.email, actor.properties.email), 38 | eq(app.name, query.app), 39 | eq(stage.name, query.stage), 40 | ), 41 | ); 42 | 43 | return c.json(result.map((item) => item.workspace)); 44 | }, 45 | ); 46 | -------------------------------------------------------------------------------- /packages/backend/src/api/workspace.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { notPublic } from "./auth"; 3 | import { Workspace } from "@console/core/workspace/index"; 4 | 5 | export const WorkspaceRoute = new Hono() 6 | .use(notPublic) 7 | .delete("/:workspaceID", async (c) => { 8 | const workspaceID = c.req.param("workspaceID"); 9 | await Workspace.remove(workspaceID); 10 | return c.json({}); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/backend/src/function/auth/websocket.ts: -------------------------------------------------------------------------------- 1 | import { withActor } from "@console/core/actor"; 2 | import { User } from "@console/core/user/index"; 3 | import { db, eq, sql } from "@console/core/drizzle/index"; 4 | import { user } from "@console/core/user/user.sql"; 5 | import { createClient } from "@openauthjs/openauth/client"; 6 | import { Resource } from "sst"; 7 | import { subjects } from "../../subjects"; 8 | 9 | const client = createClient({ 10 | issuer: Resource.OpenAuth.url, 11 | clientID: "socket", 12 | subjects, 13 | }); 14 | 15 | export async function handler(event: any) { 16 | const token = event.authorizationToken; 17 | const verified = await client.verify(token); 18 | if (verified.err) return { isAuthorized: false }; 19 | if (verified.subject.type !== "account") return { isAuthorized: false }; 20 | if (event.requestContext.operation === "EVENT_CONNECT") { 21 | await db 22 | .update(user) 23 | .set({ 24 | timeSeen: sql`now()`, 25 | }) 26 | .where(eq(user.email, verified.subject.properties.email)) 27 | .execute(); 28 | return { isAuthorized: true }; 29 | } 30 | const workspaceID = event.requestContext.channel.split("/").at(2)!; 31 | const email = verified.subject.properties.email; 32 | return withActor( 33 | { 34 | type: "system", 35 | properties: { 36 | workspaceID, 37 | }, 38 | }, 39 | async () => { 40 | const user = await User.fromEmail(email); 41 | if (!user || user.timeDeleted) { 42 | return { isAuthorized: false }; 43 | } 44 | return { isAuthorized: true }; 45 | }, 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /packages/backend/src/function/error.ts: -------------------------------------------------------------------------------- 1 | export const handler = async () => { 2 | console.log("started", new Date()); 3 | console.error(new Error("logged a different error")); 4 | return { 5 | statusCode: 200, 6 | body: "ok", 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/backend/src/function/issues/cleanup.ts: -------------------------------------------------------------------------------- 1 | import { Issue } from "@console/core/issue/index"; 2 | import { Log } from "@console/core/log/index"; 3 | 4 | export async function handler() { 5 | await Issue.cleanup(); 6 | await Log.Search.cleanup(); 7 | } 8 | -------------------------------------------------------------------------------- /packages/backend/src/function/issues/detected.ts: -------------------------------------------------------------------------------- 1 | import { withActor } from "@console/core/actor"; 2 | import { Issue } from "@console/core/issue/index"; 3 | import { SQSHandler } from "aws-lambda"; 4 | 5 | export const handler: SQSHandler = async (event) => { 6 | for (const record of event.Records) { 7 | const parsed = JSON.parse( 8 | record.body, 9 | ) as typeof Issue.Events.IssueDetected.$payload; 10 | await withActor(parsed.metadata.actor, async () => { 11 | await Issue.Send.triggerIssue(parsed.properties); 12 | }); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /packages/backend/src/function/issues/subscriber.ts: -------------------------------------------------------------------------------- 1 | import { Issue } from "@console/core/issue/index"; 2 | import { unzipSync } from "zlib"; 3 | import { withActor } from "@console/core/actor"; 4 | import { KinesisStreamEvent } from "aws-lambda"; 5 | import { queue } from "@console/core/util/queue"; 6 | 7 | export const handler = async (event: KinesisStreamEvent) => 8 | withActor( 9 | { 10 | type: "public", 11 | properties: {}, 12 | }, 13 | async () => { 14 | console.log("got", event.Records.length, "records"); 15 | const incomplete: string[] = event.Records.map( 16 | (r) => r.eventID, 17 | ).reverse(); 18 | await queue(5, event.Records, async (record) => { 19 | console.log( 20 | "arrival", 21 | new Date( 22 | record.kinesis.approximateArrivalTimestamp * 1000, 23 | ).toISOString(), 24 | new Date().toISOString(), 25 | "diff", 26 | Date.now() - record.kinesis.approximateArrivalTimestamp * 1000, 27 | ); 28 | if ( 29 | Date.now() - record.kinesis.approximateArrivalTimestamp * 1000 > 30 | 1000 * 60 * 5 31 | ) { 32 | incomplete.pop(); 33 | console.log("too old"); 34 | return; 35 | } 36 | const decoded = JSON.parse( 37 | unzipSync(Buffer.from(record.kinesis.data, "base64")).toString(), 38 | ); 39 | if (decoded.messageType !== "DATA_MESSAGE") { 40 | incomplete.pop(); 41 | return; 42 | } 43 | try { 44 | await Issue.extract(decoded); 45 | incomplete.pop(); 46 | } catch (ex) { 47 | console.error(ex); 48 | } 49 | }); 50 | 51 | console.log("incomplete", incomplete.length); 52 | const response = { 53 | batchItemFailures: incomplete.map((id) => ({ 54 | itemIdentifier: id, 55 | })), 56 | }; 57 | 58 | return response; 59 | }, 60 | ); 61 | -------------------------------------------------------------------------------- /packages/backend/src/function/migrator.ts: -------------------------------------------------------------------------------- 1 | import { postgres } from "@console/core/drizzle/postgres"; 2 | import { migrate } from "drizzle-orm/postgres-js/migrator"; 3 | 4 | export const handler = async (event: any) => { 5 | await migrate(postgres, { 6 | migrationsFolder: "./migrations-pg", 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /packages/backend/src/function/run/monitor.ts: -------------------------------------------------------------------------------- 1 | import { withActor } from "@console/core/actor"; 2 | import { Run } from "@console/core/run/index"; 3 | 4 | export async function handler(evt: Run.RunTimeoutMonitorEvent) { 5 | const { workspaceID, runID } = evt; 6 | await withActor( 7 | { 8 | type: "system", 9 | properties: { 10 | workspaceID, 11 | }, 12 | }, 13 | async () => { 14 | await Run.markRunCompleted({ 15 | runID, 16 | error: { 17 | type: "run_failed", 18 | properties: { message: "Build timed out" }, 19 | }, 20 | }); 21 | }, 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/backend/src/function/run/runner-remover.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from "aws-lambda"; 2 | import { withActor } from "@console/core/actor"; 3 | import { AWS } from "@console/core/aws/index"; 4 | import { Run } from "@console/core/run/index"; 5 | 6 | export async function handler(evt: Run.RunnerRemoverEvent, context: Context) { 7 | const { workspaceID, runnerID, removeIfNotUsedAfter } = evt; 8 | await withActor( 9 | { 10 | type: "system", 11 | properties: { 12 | workspaceID, 13 | }, 14 | }, 15 | async () => { 16 | const runner = await Run.getRunnerByID(runnerID); 17 | if (!runner) return; 18 | 19 | // In use => Re-schedule 20 | if ((runner.timeRun?.getTime() ?? 0) > removeIfNotUsedAfter) { 21 | process.env.RUNNER_REMOVER_FUNCTION_ARN = context.invokedFunctionArn; 22 | await Run.scheduleRunnerRemover(runnerID); 23 | return; 24 | } 25 | 26 | // Not in use => Remove 27 | const awsAccount = await AWS.Account.fromID(runner.awsAccountID); 28 | if (!awsAccount) return; 29 | const credentials = await AWS.assumeRole(awsAccount?.accountID!); 30 | if (!credentials) return; 31 | await Run.removeRunner({ runner, credentials }); 32 | }, 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /packages/backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import { app } from "./api"; 2 | import { patchLogger } from "./log-polyfill"; 3 | 4 | patchLogger(); 5 | 6 | export default { 7 | port: 3001, 8 | fetch: app.fetch, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/backend/src/log-polyfill.ts: -------------------------------------------------------------------------------- 1 | import { format } from "util"; 2 | 3 | export function patchLogger() { 4 | const log = 5 | (level: "INFO" | "WARN" | "TRACE" | "DEBUG" | "ERROR") => 6 | (msg: string, ...rest: any[]) => { 7 | let line = `${level}\t${format(msg, ...rest)}`; 8 | line = line.replace(/\n/g, "\r"); 9 | process.stdout.write(line + "\n"); 10 | }; 11 | console.log = log("INFO"); 12 | console.warn = log("WARN"); 13 | console.error = log("ERROR"); 14 | console.trace = log("TRACE"); 15 | console.debug = log("DEBUG"); 16 | } 17 | -------------------------------------------------------------------------------- /packages/backend/src/subjects.ts: -------------------------------------------------------------------------------- 1 | import { createSubjects } from "@openauthjs/openauth/subject"; 2 | import { z } from "zod"; 3 | 4 | export const subjects = createSubjects({ 5 | account: z.object({ 6 | accountID: z.string(), 7 | email: z.string(), 8 | }), 9 | user: z.object({ 10 | userID: z.string(), 11 | workspaceID: z.string(), 12 | }), 13 | }); 14 | -------------------------------------------------------------------------------- /packages/backend/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /* This file is auto-generated by SST. Do not edit. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | /* deno-fmt-ignore-file */ 5 | 6 | /// 7 | 8 | import "sst" 9 | export {} -------------------------------------------------------------------------------- /packages/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react", 5 | "noUncheckedIndexedAccess": true, 6 | "module": "esnext", 7 | "moduleResolution": "bundler", 8 | "baseUrl": "." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/build/image/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ARC 2 | 3 | FROM public.ecr.aws/codebuild/amazonlinux-${ARC}-lambda-standard:nodejs20 4 | 5 | # configure npm 6 | ENV NPM_CONFIG_CACHE=/tmp/.npm 7 | 8 | # configure pnpm 9 | RUN npm config --global delete prefix \ 10 | && npm install -g pnpm@9.1.2 \ 11 | && npm config --global set prefix /tmp/opt/npm \ 12 | && pnpm config set store-dir /tmp/pnpm 13 | 14 | # install SST Ion 15 | RUN touch /tmp/.bashrc \ 16 | && curl -fsSL https://ion.sst.dev/install | bash \ 17 | && mv /tmp/.sst/bin/sst /usr/local/bin/sst \ 18 | && sst version 19 | 20 | # Cleanup 21 | RUN rm -fr /tmp/* 22 | 23 | COPY index.mjs ${LAMBDA_TASK_ROOT} 24 | 25 | ENTRYPOINT [ "/lambda-entrypoint.sh" ] 26 | CMD ["index.handler"] 27 | 28 | # how to build 29 | # $ docker logout public.ecr.aws 30 | # $ AWS_PROFILE=sst-dev aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 917397401067.dkr.ecr.us-east-1.amazonaws.com 31 | # $ docker build --build-arg="ARC=x86_64" --platform linux/amd64 --tag 917397401067.dkr.ecr.us-east-1.amazonaws.com/console-images:x86_64-1 . 32 | # $ docker build --build-arg="ARC=aarch64" --platform linux/arm64 --tag 917397401067.dkr.ecr.us-east-1.amazonaws.com/console-images:arm64-1 . 33 | # $ docker push 917397401067.dkr.ecr.us-east-1.amazonaws.com/console-images:x86_64-1 34 | # $ docker push 917397401067.dkr.ecr.us-east-1.amazonaws.com/console-images:arm64-1 -------------------------------------------------------------------------------- /packages/build/image/Dockerfile.codebuild: -------------------------------------------------------------------------------- 1 | ARG FUNCTION_DIR="/function" 2 | 3 | FROM public.ecr.aws/codebuild/amazonlinux2-x86_64-standard:5.0-24.05.15 as build-image 4 | ARG FUNCTION_DIR 5 | #RUN apt-get update && \ 6 | # apt-get install -y \ 7 | # g++ \ 8 | # make \ 9 | # cmake \ 10 | # unzip \ 11 | # libcurl4-openssl-dev 12 | # Download, build and install cmake 13 | WORKDIR /tmp 14 | RUN wget https://cmake.org/files/v3.18/cmake-3.18.0.tar.gz 15 | RUN tar -xvzf cmake-3.18.0.tar.gz 16 | WORKDIR /tmp/cmake-3.18.0 17 | RUN ./bootstrap 18 | RUN make 19 | RUN make install 20 | WORKDIR ${FUNCTION_DIR} 21 | RUN mkdir -p ${FUNCTION_DIR} 22 | COPY package.json ${FUNCTION_DIR} 23 | RUN npm install 24 | 25 | FROM public.ecr.aws/codebuild/amazonlinux2-x86_64-standard:5.0-24.05.15 26 | ARG FUNCTION_DIR 27 | WORKDIR ${FUNCTION_DIR} 28 | # install AWS Lambda RIC 29 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 30 | #COPY package.json ${FUNCTION_DIR} 31 | COPY index.mjs ${FUNCTION_DIR} 32 | #RUN npm install 33 | # configure npm 34 | ENV NPM_CONFIG_CACHE=/tmp/.npm 35 | # configure pnpm 36 | RUN npm install -g pnpm@9.1.2 37 | RUN pnpm config set store-dir /tmp/pnpm 38 | # install SST Ion 39 | RUN touch /root/.bashrc 40 | RUN curl -fsSL https://ion.sst.dev/install | bash 41 | ENV PATH="/root/.sst/bin/:$PATH" 42 | # install Node.js 20 43 | RUN n 20.11.1 44 | 45 | ENTRYPOINT ["/usr/local/bin/npx", "aws-lambda-ric"] 46 | CMD ["index.handler"] 47 | 48 | # how to build 49 | # $ docker logout public.ecr.aws 50 | # $ docker build --platform linux/amd64 --tag 917397401067.dkr.ecr.us-east-1.amazonaws.com/images:x86_64 . 51 | # $ AWS_PROFILE=sst-dev aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 917397401067.dkr.ecr.us-east-1.amazonaws.com 52 | # $ AWS_PROFILE=sst-dev docker push public.ecr.aws/w4j3p5y3/images:x86_64 53 | 54 | # $ docker build --platform linux/arm64 -t sst-ci . 55 | # $ docker run -v ~/Sites/fwang/aws-lambda-rie:/aws-lambda -p 9000:8080 --rm --entrypoint /aws-lambda/aws-lambda-rie sst-ci /usr/local/bin/npx aws-lambda-ric index.handler 56 | # $ curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}' 57 | # or manually login 58 | # $ docker run --rm --entrypoint bash sst-ci -------------------------------------------------------------------------------- /packages/build/image/index.mjs: -------------------------------------------------------------------------------- 1 | /** @typedef {import("../../core/src/run").RunnerEvent} RunnerEvent */ 2 | /** @typedef {import("aws-lambda").Context} Context */ 3 | import fs from "fs"; 4 | import path from "path"; 5 | import https from "https"; 6 | 7 | /** @type {string} */ 8 | let currentVersion; 9 | 10 | /** @type {any} */ 11 | let buildspec; 12 | 13 | /** 14 | * @param {RunnerEvent} event 15 | * @param {Context} context 16 | */ 17 | export async function handler(event, context) { 18 | console.log(event); 19 | const version = event.buildspec.version; 20 | const bucket = event.buildspec.bucket; 21 | console.log("buildspec version:", version); 22 | 23 | if (version !== currentVersion) { 24 | await download( 25 | `https://${bucket}.s3.amazonaws.com/buildspec/${version}/index.mjs`, 26 | `/tmp/buildspec/${version}/index.mjs` 27 | ); 28 | buildspec = await import(`/tmp/buildspec/${version}/index.mjs`); 29 | currentVersion = version; 30 | } 31 | 32 | await buildspec.handler(event, context); 33 | } 34 | 35 | /** 36 | * @param {string} url 37 | * @param {string} filePath 38 | */ 39 | async function download(url, filePath) { 40 | console.log("download buildspec from", url, "to", filePath); 41 | const fileDir = path.dirname(filePath); 42 | fs.rmSync(fileDir, { force: true, recursive: true }); 43 | fs.mkdirSync(fileDir, { recursive: true }); 44 | 45 | return new Promise((resolve, reject) => { 46 | const file = fs.createWriteStream(filePath); 47 | 48 | https 49 | .get(url, (response) => { 50 | if (response.statusCode !== 200) { 51 | reject(new Error(`Failed to get '${url}' (${response.statusCode})`)); 52 | return; 53 | } 54 | 55 | response.pipe(file); 56 | 57 | file.on("finish", () => { 58 | file.close(() => { 59 | resolve("Download completed!"); 60 | }); 61 | }); 62 | 63 | file.on("error", (err) => { 64 | fs.unlink(filePath, () => reject(err)); 65 | }); 66 | }) 67 | .on("error", (err) => { 68 | fs.unlink(filePath, () => reject(err)); 69 | }); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /packages/build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@console/build", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "devDependencies": { 6 | "@types/aws-lambda": "^8.10.114" 7 | }, 8 | "dependencies": { 9 | "@aws-sdk/client-eventbridge": "3.699.0" 10 | }, 11 | "main": "index.js", 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "description": "" 18 | } 19 | -------------------------------------------------------------------------------- /packages/build/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /* This file is auto-generated by SST. Do not edit. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | /* deno-fmt-ignore-file */ 5 | 6 | /// 7 | 8 | import "sst" 9 | export {} -------------------------------------------------------------------------------- /packages/cdc/Dockerfile: -------------------------------------------------------------------------------- 1 | from mirror.gcr.io/oven/bun:1.2.2 2 | 3 | copy ./package.json . 4 | copy ./packages/core/package.json ./packages/core/ 5 | copy ./packages/cdc/package.json ./packages/cdc/ 6 | copy ./packages/mail/package.json ./packages/mail/ 7 | copy ./patches/*.patch ./patches/ 8 | 9 | # do not run scripts 10 | run bun install --frozen-lockfile --ignore-scripts 11 | 12 | copy ./packages/core/src ./packages/core/src 13 | copy ./packages/cdc/src ./packages/cdc/src 14 | 15 | entrypoint ["bun", "run", "./packages/cdc/src/index.ts"] 16 | 17 | -------------------------------------------------------------------------------- /packages/cdc/debezium/.dockerignore: -------------------------------------------------------------------------------- 1 | 2 | # sst 3 | .sst -------------------------------------------------------------------------------- /packages/cdc/debezium/.gitignore: -------------------------------------------------------------------------------- 1 | application.properties 2 | -------------------------------------------------------------------------------- /packages/cdc/debezium/Dockerfile: -------------------------------------------------------------------------------- 1 | from mirror.gcr.io/debezium/server:2.4.1.Final 2 | 3 | copy application.properties /debezium/conf/application.properties 4 | copy planetscale.jar /debezium/lib/planetscale.jar 5 | 6 | -------------------------------------------------------------------------------- /packages/cdc/debezium/planetscale.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sst/console/53d4c9161b99d1d8c8d8e6e7e0d404c5aba24a3f/packages/cdc/debezium/planetscale.jar -------------------------------------------------------------------------------- /packages/cdc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@console/cdc", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "dependencies": { 6 | "@console/core": "workspace:*", 7 | "drizzle-orm": "0.39.1", 8 | "planetscale-stream-ts": "^1.1.0", 9 | "sst": "3.12.5" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cdc/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /* This file is auto-generated by SST. Do not edit. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | /* deno-fmt-ignore-file */ 5 | 6 | /// 7 | 8 | import "sst" 9 | export {} -------------------------------------------------------------------------------- /packages/cdc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react", 5 | "noUncheckedIndexedAccess": true, 6 | "module": "esnext", 7 | "moduleResolution": "bundler", 8 | "baseUrl": "." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit"; 2 | import { Resource } from "sst"; 3 | 4 | const connection = { 5 | user: Resource.Database.username, 6 | password: Resource.Database.password, 7 | host: Resource.Database.host, 8 | }; 9 | console.log(connection); 10 | export default defineConfig({ 11 | out: "./migrations/", 12 | strict: true, 13 | schema: "./src/**/*.sql.ts", 14 | verbose: true, 15 | dialect: "mysql", 16 | dbCredentials: { 17 | url: `mysql://${connection.user}:${connection.password}@${connection.host}/sst?ssl={"rejectUnauthorized":true}`, 18 | ssl: { 19 | rejectUnauthorized: true, 20 | }, 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /packages/core/drizzle.pg.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit"; 2 | import { Resource } from "sst"; 3 | 4 | const connection = { 5 | user: Resource.Postgres.username, 6 | password: Resource.Postgres.password, 7 | host: Resource.Postgres.host, 8 | }; 9 | 10 | console.log(connection); 11 | 12 | export default defineConfig({ 13 | out: "./migrations-pg/", 14 | strict: true, 15 | schema: "./src/**/*.pg.ts", 16 | verbose: true, 17 | dialect: "postgresql", 18 | dbCredentials: { 19 | url: `postgres://${connection.user}:${connection.password}@${connection.host}/console`, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /packages/core/migrations-pg/0000_parched_cargill.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION moddatetime; 2 | 3 | -- Function to add trigger only to tables with a time_updated column 4 | CREATE OR REPLACE FUNCTION sync_time_updated() 5 | RETURNS event_trigger AS $$ 6 | DECLARE 7 | tbl_name TEXT; 8 | trigger_exists BOOLEAN; 9 | BEGIN 10 | FOR tbl_name IN 11 | SELECT table_name 12 | FROM information_schema.columns 13 | WHERE column_name = 'time_updated' 14 | AND table_schema = 'public' 15 | LOOP 16 | -- Check if the trigger already exists for this table 17 | SELECT EXISTS ( 18 | SELECT 1 19 | FROM information_schema.triggers 20 | WHERE event_object_schema = 'public' 21 | AND event_object_table = tbl_name 22 | AND trigger_name = 'time_updated_handle' 23 | ) INTO trigger_exists; 24 | 25 | -- Add trigger only if the table has a time_updated column and the trigger doesn't exist 26 | IF NOT trigger_exists THEN 27 | EXECUTE format( 28 | 'CREATE TRIGGER time_updated_handle BEFORE UPDATE ON %I 29 | FOR EACH ROW EXECUTE FUNCTION moddatetime(time_updated)', 30 | tbl_name 31 | ); 32 | END IF; 33 | END LOOP; 34 | END; 35 | $$ LANGUAGE plpgsql; 36 | 37 | CREATE EVENT TRIGGER time_updated_ensure 38 | ON ddl_command_end 39 | WHEN TAG IN ('CREATE TABLE') 40 | EXECUTE FUNCTION sync_time_updated(); 41 | 42 | -------------------------------------------------------------------------------- /packages/core/migrations-pg/0002_late_tiger_shark.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "state_event" DROP CONSTRAINT "state_event_workspace_id_stage_id_update_id_sequence_unique";--> statement-breakpoint 2 | ALTER TABLE "state_event" ADD COLUMN "urn" varchar(512) NOT NULL;--> statement-breakpoint 3 | ALTER TABLE "state_event" ADD COLUMN "action" varchar(255) NOT NULL;--> statement-breakpoint 4 | ALTER TABLE "state_event" ADD COLUMN "outputs" json NOT NULL;--> statement-breakpoint 5 | ALTER TABLE "state_event" ADD COLUMN "inputs" json NOT NULL;--> statement-breakpoint 6 | ALTER TABLE "state_event" ADD COLUMN "parent" varchar(512);--> statement-breakpoint 7 | ALTER TABLE "state_event" ADD COLUMN "custom" boolean NOT NULL;--> statement-breakpoint 8 | ALTER TABLE "state_event" ADD COLUMN "time_state_created" timestamp with time zone;--> statement-breakpoint 9 | ALTER TABLE "state_event" ADD COLUMN "time_state_modified" timestamp with time zone;--> statement-breakpoint 10 | ALTER TABLE "state_event" DROP COLUMN "sequence";--> statement-breakpoint 11 | ALTER TABLE "state_event" DROP COLUMN "timestamp";--> statement-breakpoint 12 | ALTER TABLE "state_event" DROP COLUMN "data";--> statement-breakpoint 13 | ALTER TABLE "state_event" ADD CONSTRAINT "urn_uniq" UNIQUE("workspace_id","stage_id","update_id","urn"); -------------------------------------------------------------------------------- /packages/core/migrations-pg/0003_aberrant_komodo.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "state_event" DROP CONSTRAINT "urn_uniq";--> statement-breakpoint 2 | ALTER TABLE "state_event" ADD COLUMN "timestamp" timestamp with time zone;--> statement-breakpoint 3 | ALTER TABLE "state_event" ADD COLUMN "event" json;--> statement-breakpoint 4 | ALTER TABLE "state_event" DROP COLUMN "type";--> statement-breakpoint 5 | ALTER TABLE "state_event" DROP COLUMN "urn";--> statement-breakpoint 6 | ALTER TABLE "state_event" DROP COLUMN "action";--> statement-breakpoint 7 | ALTER TABLE "state_event" DROP COLUMN "outputs";--> statement-breakpoint 8 | ALTER TABLE "state_event" DROP COLUMN "inputs";--> statement-breakpoint 9 | ALTER TABLE "state_event" DROP COLUMN "parent";--> statement-breakpoint 10 | ALTER TABLE "state_event" DROP COLUMN "custom";--> statement-breakpoint 11 | ALTER TABLE "state_event" DROP COLUMN "time_state_created";--> statement-breakpoint 12 | ALTER TABLE "state_event" DROP COLUMN "time_state_modified";--> statement-breakpoint 13 | ALTER TABLE "state_event" ADD CONSTRAINT "urn_uniq" UNIQUE("workspace_id","stage_id","update_id","timestamp"); -------------------------------------------------------------------------------- /packages/core/migrations-pg/0004_uneven_serpent_society.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "state_event" DROP CONSTRAINT "urn_uniq";--> statement-breakpoint 2 | ALTER TABLE "state_event" ADD COLUMN "type" varchar(255) NOT NULL;--> statement-breakpoint 3 | ALTER TABLE "state_event" ADD COLUMN "urn" varchar(255) NOT NULL;--> statement-breakpoint 4 | ALTER TABLE "state_event" ADD COLUMN "parent" varchar(255);--> statement-breakpoint 5 | ALTER TABLE "state_event" ADD COLUMN "inputs" json;--> statement-breakpoint 6 | ALTER TABLE "state_event" ADD COLUMN "outputs" json;--> statement-breakpoint 7 | ALTER TABLE "state_event" ADD COLUMN "logs" json NOT NULL;--> statement-breakpoint 8 | ALTER TABLE "state_event" ADD COLUMN "error" text;--> statement-breakpoint 9 | ALTER TABLE "state_event" ADD COLUMN "time_started" timestamp with time zone NOT NULL;--> statement-breakpoint 10 | ALTER TABLE "state_event" ADD COLUMN "time_completed" timestamp with time zone NOT NULL;--> statement-breakpoint 11 | ALTER TABLE "state_event" DROP COLUMN "event";--> statement-breakpoint 12 | ALTER TABLE "state_event" ADD CONSTRAINT "urn_uniq" UNIQUE("workspace_id","stage_id","update_id","urn","type"); -------------------------------------------------------------------------------- /packages/core/migrations-pg/0005_tiny_lockjaw.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "state_event" DROP CONSTRAINT "urn_uniq";--> statement-breakpoint 2 | ALTER TABLE "state_event" ADD COLUMN "action" varchar(255);--> statement-breakpoint 3 | ALTER TABLE "state_event" ADD CONSTRAINT "urn_uniq" UNIQUE("workspace_id","stage_id","update_id","urn","action"); -------------------------------------------------------------------------------- /packages/core/migrations-pg/0006_mighty_omega_flight.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "state_event" DROP COLUMN "timestamp"; -------------------------------------------------------------------------------- /packages/core/migrations-pg/0007_skinny_centennial.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "state_update" ADD COLUMN "outputs" json DEFAULT '{}'::json;--> statement-breakpoint 2 | ALTER TABLE "state_update" ADD COLUMN "hints" json DEFAULT '{}'::json; -------------------------------------------------------------------------------- /packages/core/migrations-pg/0008_lyrical_shatterstar.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "state_event" ALTER COLUMN "urn" SET DATA TYPE varchar(512);--> statement-breakpoint 2 | ALTER TABLE "state_event" ALTER COLUMN "parent" SET DATA TYPE varchar(512); -------------------------------------------------------------------------------- /packages/core/migrations-pg/0009_loud_tyrannus.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "state_update" ADD COLUMN "version" varchar(255); -------------------------------------------------------------------------------- /packages/core/migrations-pg/0010_slippery_zeigeist.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "agent_usage" ( 2 | "id" varchar(24) NOT NULL, 3 | "workspace_id" varchar(24) NOT NULL, 4 | "time_created" timestamp with time zone DEFAULT now() NOT NULL, 5 | "time_deleted" timestamp with time zone, 6 | "time_updated" timestamp with time zone DEFAULT now() NOT NULL, 7 | "request_id" varchar(255), 8 | "model" varchar(255) NOT NULL, 9 | "input_tokens" integer NOT NULL, 10 | "output_tokens" integer NOT NULL, 11 | "cost" bigint NOT NULL, 12 | CONSTRAINT "agent_usage_workspace_id_id_pk" PRIMARY KEY("workspace_id","id") 13 | ); 14 | -------------------------------------------------------------------------------- /packages/core/migrations-pg/meta/0000_snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "800508ee-a6c8-44e6-bbb2-354e7b8ee130", 3 | "prevId": "00000000-0000-0000-0000-000000000000", 4 | "version": "7", 5 | "dialect": "postgresql", 6 | "tables": {}, 7 | "enums": {}, 8 | "schemas": {}, 9 | "views": {}, 10 | "sequences": {}, 11 | "roles": {}, 12 | "policies": {}, 13 | "_meta": { 14 | "columns": {}, 15 | "schemas": {}, 16 | "tables": {} 17 | } 18 | } -------------------------------------------------------------------------------- /packages/core/migrations-pg/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "7", 3 | "dialect": "postgresql", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "7", 8 | "when": 1738346547295, 9 | "tag": "0000_parched_cargill", 10 | "breakpoints": true 11 | }, 12 | { 13 | "idx": 1, 14 | "version": "7", 15 | "when": 1738346656520, 16 | "tag": "0001_wealthy_infant_terrible", 17 | "breakpoints": true 18 | }, 19 | { 20 | "idx": 2, 21 | "version": "7", 22 | "when": 1739413062832, 23 | "tag": "0002_late_tiger_shark", 24 | "breakpoints": true 25 | }, 26 | { 27 | "idx": 3, 28 | "version": "7", 29 | "when": 1739413949009, 30 | "tag": "0003_aberrant_komodo", 31 | "breakpoints": true 32 | }, 33 | { 34 | "idx": 4, 35 | "version": "7", 36 | "when": 1739548671615, 37 | "tag": "0004_uneven_serpent_society", 38 | "breakpoints": true 39 | }, 40 | { 41 | "idx": 5, 42 | "version": "7", 43 | "when": 1739550100552, 44 | "tag": "0005_tiny_lockjaw", 45 | "breakpoints": true 46 | }, 47 | { 48 | "idx": 6, 49 | "version": "7", 50 | "when": 1739550997096, 51 | "tag": "0006_mighty_omega_flight", 52 | "breakpoints": true 53 | }, 54 | { 55 | "idx": 7, 56 | "version": "7", 57 | "when": 1739822221683, 58 | "tag": "0007_skinny_centennial", 59 | "breakpoints": true 60 | }, 61 | { 62 | "idx": 8, 63 | "version": "7", 64 | "when": 1740753518758, 65 | "tag": "0008_lyrical_shatterstar", 66 | "breakpoints": true 67 | }, 68 | { 69 | "idx": 9, 70 | "version": "7", 71 | "when": 1743133536198, 72 | "tag": "0009_loud_tyrannus", 73 | "breakpoints": true 74 | }, 75 | { 76 | "idx": 10, 77 | "version": "7", 78 | "when": 1745347821663, 79 | "tag": "0010_slippery_zeigeist", 80 | "breakpoints": true 81 | } 82 | ] 83 | } -------------------------------------------------------------------------------- /packages/core/migrations/0001_shocking_wind_dancer.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` ADD `time_state_created` timestamp;--> statement-breakpoint 2 | ALTER TABLE `state_resource` ADD `time_state_modified` timestamp; -------------------------------------------------------------------------------- /packages/core/migrations/0002_empty_preak.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `state_event` ( 2 | `id` char(24) NOT NULL, 3 | `workspace_id` char(24) NOT NULL, 4 | `stage_id` char(24) NOT NULL, 5 | `update_id` char(24) NOT NULL, 6 | `type` varchar(255) NOT NULL, 7 | `urn` varchar(255) NOT NULL, 8 | `outputs` json NOT NULL, 9 | `action` enum('created','updated','deleted') NOT NULL, 10 | `inputs` json NOT NULL, 11 | `parent` varchar(255), 12 | `custom` boolean NOT NULL, 13 | `time_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 14 | `time_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 15 | `time_deleted` timestamp, 16 | `time_state_created` timestamp, 17 | `time_state_modified` timestamp, 18 | CONSTRAINT `state_event_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`), 19 | CONSTRAINT `urn` UNIQUE(`workspace_id`,`stage_id`,`update_id`,`urn`) 20 | ); 21 | --> statement-breakpoint 22 | ALTER TABLE `state_event` ADD CONSTRAINT `state_event_workspace_id_workspace_id_fk` FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint 23 | ALTER TABLE `state_event` ADD CONSTRAINT `state_event_stage_id` FOREIGN KEY (`workspace_id`,`stage_id`) REFERENCES `stage`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint 24 | ALTER TABLE `state_event` ADD CONSTRAINT `state_event_update_id` FOREIGN KEY (`workspace_id`,`update_id`) REFERENCES `state_update`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action; -------------------------------------------------------------------------------- /packages/core/migrations/0003_absurd_cammi.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_update` DROP FOREIGN KEY `state_update_workspace_id_stage_id_stage_workspace_id_id_fk`; 2 | --> statement-breakpoint 3 | ALTER TABLE `state_update` ADD `index` bigint;--> statement-breakpoint 4 | ALTER TABLE `state_update` ADD CONSTRAINT `state_update_stage_id` FOREIGN KEY (`workspace_id`,`stage_id`) REFERENCES `stage`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action; -------------------------------------------------------------------------------- /packages/core/migrations/0004_wild_talos.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` DROP COLUMN `action`; -------------------------------------------------------------------------------- /packages/core/migrations/0005_groovy_chamber.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` DROP FOREIGN KEY `update_id`; 2 | -------------------------------------------------------------------------------- /packages/core/migrations/0005_mute_loners.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` DROP INDEX `urn`;--> statement-breakpoint 2 | ALTER TABLE `state_resource` DROP FOREIGN KEY `update_id`; 3 | -------------------------------------------------------------------------------- /packages/core/migrations/0006_real_marvel_zombies.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` DROP FOREIGN KEY `state_resource_workspace_id_stage_id_stage_workspace_id_id_fk`; 2 | -------------------------------------------------------------------------------- /packages/core/migrations/0007_sloppy_multiple_man.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` DROP INDEX `urn`;--> statement-breakpoint 2 | ALTER TABLE `state_resource` ADD CONSTRAINT `urn` UNIQUE(`workspace_id`,`stage_id`,`urn`); -------------------------------------------------------------------------------- /packages/core/migrations/0008_chubby_doomsday.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` ADD CONSTRAINT `state_resource_stage_id` FOREIGN KEY (`workspace_id`,`stage_id`) REFERENCES `stage`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint 2 | ALTER TABLE `state_resource` ADD CONSTRAINT `state_resource_update_id` FOREIGN KEY (`workspace_id`,`update_id`) REFERENCES `state_update`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action; -------------------------------------------------------------------------------- /packages/core/migrations/0009_cynical_wallow.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` DROP COLUMN `time_created`;--> statement-breakpoint 2 | ALTER TABLE `state_resource` DROP COLUMN `time_updated`;--> statement-breakpoint 3 | ALTER TABLE `state_resource` DROP COLUMN `time_deleted`; -------------------------------------------------------------------------------- /packages/core/migrations/0010_tiresome_maximus.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` ADD `time_created` timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL;--> statement-breakpoint 2 | ALTER TABLE `state_resource` ADD `time_updated` timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP;--> statement-breakpoint 3 | ALTER TABLE `state_resource` ADD `time_deleted` timestamp; -------------------------------------------------------------------------------- /packages/core/migrations/0011_boring_lenny_balinger.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` DROP FOREIGN KEY `state_resource_update_id`; 2 | -------------------------------------------------------------------------------- /packages/core/migrations/0012_workable_rumiko_fujikawa.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` ADD `update_created_id` char(24);--> statement-breakpoint 2 | ALTER TABLE `state_resource` ADD `update_modified_id` char(24);--> statement-breakpoint 3 | ALTER TABLE `state_resource` ADD CONSTRAINT `state_resource_update_created_id` FOREIGN KEY (`workspace_id`,`update_created_id`) REFERENCES `state_update`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint 4 | ALTER TABLE `state_resource` ADD CONSTRAINT `state_resource_update_modified_id` FOREIGN KEY (`workspace_id`,`update_modified_id`) REFERENCES `state_update`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action; -------------------------------------------------------------------------------- /packages/core/migrations/0013_moaning_chameleon.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_update` MODIFY COLUMN `errors` json; -------------------------------------------------------------------------------- /packages/core/migrations/0014_workable_nightshade.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_event` MODIFY COLUMN `urn` varchar(512) NOT NULL;--> statement-breakpoint 2 | ALTER TABLE `state_event` MODIFY COLUMN `parent` varchar(512);--> statement-breakpoint 3 | ALTER TABLE `state_resource` MODIFY COLUMN `urn` varchar(512) NOT NULL;--> statement-breakpoint 4 | ALTER TABLE `state_resource` MODIFY COLUMN `parent` varchar(512); 5 | -------------------------------------------------------------------------------- /packages/core/migrations/0016_swift_killer_shrike.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE `run_runner`;--> statement-breakpoint 2 | CREATE TABLE `run_runner` ( 3 | `id` char(24) NOT NULL, 4 | `workspace_id` char(24) NOT NULL, 5 | `time_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | `time_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 7 | `time_deleted` timestamp, 8 | `aws_account_id` char(24) NOT NULL, 9 | `region` varchar(255) NOT NULL, 10 | `architecture` enum('x86_64','arm64') NOT NULL, 11 | `image` varchar(255) NOT NULL, 12 | `resource` json NOT NULL, 13 | CONSTRAINT `run_runner_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`) 14 | ); 15 | --> statement-breakpoint 16 | ALTER TABLE `run_runner` ADD CONSTRAINT `run_runner_workspace_id_workspace_id_fk` FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint 17 | ALTER TABLE `run_runner` ADD CONSTRAINT `workspace_id_aws_account_id_fk` FOREIGN KEY (`workspace_id`,`aws_account_id`) REFERENCES `aws_account`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action; -------------------------------------------------------------------------------- /packages/core/migrations/0017_quick_silvermane.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run_runner` ADD `time_run` timestamp;--> statement-breakpoint 2 | ALTER TABLE `run_runner` MODIFY `resource` json; -------------------------------------------------------------------------------- /packages/core/migrations/0018_curly_zaladane.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run_env` ADD CONSTRAINT `run_env_workspace_id_workspace_id_fk` FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON DELETE no action ON UPDATE no action; -------------------------------------------------------------------------------- /packages/core/migrations/0019_chubby_lord_tyger.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `runner` ( 2 | `id` char(24) NOT NULL, 3 | `workspace_id` char(24) NOT NULL, 4 | `time_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | `time_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 6 | `time_deleted` timestamp, 7 | `time_run` timestamp, 8 | `aws_account_id` char(24) NOT NULL, 9 | `region` varchar(255) NOT NULL, 10 | `architecture` enum('x86_64','arm64') NOT NULL, 11 | `image` varchar(255) NOT NULL, 12 | `resource` json, 13 | CONSTRAINT `runner_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`) 14 | ); 15 | --> statement-breakpoint 16 | DROP TABLE `run_runner`;--> statement-breakpoint 17 | ALTER TABLE `runner` ADD CONSTRAINT `runner_workspace_id_workspace_id_fk` FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint 18 | ALTER TABLE `runner` ADD CONSTRAINT `workspace_id_aws_account_id_fk` FOREIGN KEY (`workspace_id`,`aws_account_id`) REFERENCES `aws_account`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action; -------------------------------------------------------------------------------- /packages/core/migrations/0020_outstanding_the_spike.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `runner_usage` ( 2 | `id` char(24) NOT NULL, 3 | `workspace_id` char(24) NOT NULL, 4 | `time_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | `time_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 6 | `time_deleted` timestamp, 7 | `runner_id` char(24) NOT NULL, 8 | `stage_id` char(24) NOT NULL, 9 | `time_run` timestamp, 10 | CONSTRAINT `runner_usage_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`), 11 | CONSTRAINT `runner_id_stage_id_unique` UNIQUE(`workspace_id`,`runner_id`,`stage_id`) 12 | ); 13 | --> statement-breakpoint 14 | ALTER TABLE `runner` ADD `app_repo_id` char(24) NOT NULL;--> statement-breakpoint 15 | ALTER TABLE `runner_usage` ADD CONSTRAINT `runner_usage_workspace_id_workspace_id_fk` FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint 16 | ALTER TABLE `runner_usage` ADD CONSTRAINT `runner_id_fk` FOREIGN KEY (`workspace_id`,`runner_id`) REFERENCES `runner`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint 17 | ALTER TABLE `runner_usage` ADD CONSTRAINT `stage_id_fk` FOREIGN KEY (`workspace_id`,`stage_id`) REFERENCES `stage`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint 18 | ALTER TABLE `runner` ADD CONSTRAINT `repo_id_fk` FOREIGN KEY (`workspace_id`,`app_repo_id`) REFERENCES `app_repo`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action; -------------------------------------------------------------------------------- /packages/core/migrations/0021_mean_domino.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `runner` ADD `warmer` varchar(255); -------------------------------------------------------------------------------- /packages/core/migrations/0022_goofy_sebastian_shaw.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run` ADD `state_update_id` char(24) NOT NULL;--> statement-breakpoint 2 | ALTER TABLE `run` ADD `config` json NOT NULL; -------------------------------------------------------------------------------- /packages/core/migrations/0023_closed_komodo.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `github_repo` DROP FOREIGN KEY `github_repo_workspace_id_org_id`; 2 | --> statement-breakpoint 3 | ALTER TABLE `github_repo` ADD `last_event` json;--> statement-breakpoint 4 | ALTER TABLE `github_repo` ADD `time_last_event` timestamp; -------------------------------------------------------------------------------- /packages/core/migrations/0024_flawless_zuras.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `github_repo` DROP INDEX `repo`; -------------------------------------------------------------------------------- /packages/core/migrations/0025_early_vulture.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `github_repo` ADD `github_org_id` char(24) NOT NULL;--> statement-breakpoint 2 | ALTER TABLE `github_repo` ADD CONSTRAINT `unique_repo_id` UNIQUE(`workspace_id`,`github_org_id`,`repo_id`);--> statement-breakpoint 3 | ALTER TABLE `github_repo` ADD CONSTRAINT `fk_github_org_id` FOREIGN KEY (`workspace_id`,`github_org_id`) REFERENCES `github_org`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint 4 | ALTER TABLE `github_repo` DROP COLUMN `org_id`; 5 | -------------------------------------------------------------------------------- /packages/core/migrations/0026_ancient_wrecker.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `app_repo` ADD `last_event` json;--> statement-breakpoint 2 | ALTER TABLE `app_repo` ADD `last_event_error` text;--> statement-breakpoint 3 | ALTER TABLE `app_repo` ADD `time_last_event` timestamp;--> statement-breakpoint 4 | ALTER TABLE `github_repo` DROP COLUMN `last_event`;--> statement-breakpoint 5 | ALTER TABLE `github_repo` DROP COLUMN `time_last_event`; -------------------------------------------------------------------------------- /packages/core/migrations/0027_lively_mystique.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run` ADD `active` boolean;--> statement-breakpoint 2 | ALTER TABLE `run` ADD CONSTRAINT `unique_active` UNIQUE(`workspace_id`,`stage_id`,`active`); -------------------------------------------------------------------------------- /packages/core/migrations/0028_lowly_marvel_boy.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `app_repo` ADD `last_event_id` char(24);--> statement-breakpoint 2 | ALTER TABLE `app_repo` ADD `last_event_status` text;--> statement-breakpoint 3 | ALTER TABLE `app_repo` ADD `last_event_state_update_id` char(24);--> statement-breakpoint 4 | ALTER TABLE `app_repo` DROP COLUMN `last_event_error`; -------------------------------------------------------------------------------- /packages/core/migrations/0029_fast_scarlet_witch.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `app_repo` DROP COLUMN `last_event_state_update_id`; -------------------------------------------------------------------------------- /packages/core/migrations/0030_bored_vivisector.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `runner` ADD `engine` enum('lambda','codebuild') NOT NULL; -------------------------------------------------------------------------------- /packages/core/migrations/0031_friendly_patch.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `run_config` ( 2 | `id` char(24) NOT NULL, 3 | `workspace_id` char(24) NOT NULL, 4 | `time_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | `time_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 6 | `time_deleted` timestamp, 7 | `app_id` char(24) NOT NULL, 8 | `stage_pattern` varchar(255) NOT NULL, 9 | `aws_account_id` char(24) NOT NULL, 10 | `env` json, 11 | CONSTRAINT `run_config_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`), 12 | CONSTRAINT `unique_stage_pattern` UNIQUE(`workspace_id`,`app_id`,`stage_pattern`) 13 | ); 14 | --> statement-breakpoint 15 | ALTER TABLE `run_config` ADD CONSTRAINT `run_config_workspace_id_workspace_id_fk` FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint 16 | ALTER TABLE `run_config` ADD CONSTRAINT `run_config_workspace_id_app_id_app_workspace_id_id_fk` FOREIGN KEY (`workspace_id`,`app_id`) REFERENCES `app`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action; -------------------------------------------------------------------------------- /packages/core/migrations/0032_mean_butterfly.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run_config` ADD `aws_account_external_id` varchar(12) NOT NULL;--> statement-breakpoint 2 | ALTER TABLE `run_config` DROP COLUMN `aws_account_id`; -------------------------------------------------------------------------------- /packages/core/migrations/0034_thin_night_nurse.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX `external_repo_id` ON `github_repository` (`external_repo_id`); -------------------------------------------------------------------------------- /packages/core/migrations/0035_fantastic_dorian_gray.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `runner` RENAME COLUMN `image` TO `type`;--> statement-breakpoint 2 | ALTER TABLE `runner` DROP COLUMN `architecture`; -------------------------------------------------------------------------------- /packages/core/migrations/0036_fresh_the_santerians.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_update` ADD `run_id` char(24);--> statement-breakpoint 2 | ALTER TABLE `state_update` ADD CONSTRAINT `state_update_run_id` FOREIGN KEY (`workspace_id`,`run_id`) REFERENCES `run`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action; -------------------------------------------------------------------------------- /packages/core/migrations/0037_slow_makkari.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run` ADD `app_id` char(24);--> statement-breakpoint 2 | ALTER TABLE `run` ADD CONSTRAINT `workspace_id_app_id_fk` FOREIGN KEY (`workspace_id`,`app_id`) REFERENCES `app`(`workspace_id`,`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint 3 | ALTER TABLE `run` MODIFY COLUMN `stage_id` char(24) NULL;--> statement-breakpoint 4 | ALTER TABLE `run` MODIFY COLUMN `config` json NULL;--> statement-breakpoint 5 | ALTER TABLE `run` DROP COLUMN `state_update_id`; -------------------------------------------------------------------------------- /packages/core/migrations/0038_illegal_sharon_carter.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run` RENAME COLUMN `git_context` TO `trigger`; -------------------------------------------------------------------------------- /packages/core/migrations/0039_fantastic_inhumans.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run` DROP COLUMN `error`;--> statement-breakpoint 2 | ALTER TABLE `run` ADD COLUMN `error` json; 3 | -------------------------------------------------------------------------------- /packages/core/migrations/0040_panoramic_sentry.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `issue_alert` ADD `event` enum('issue','autodeploy','autodeploy.error') DEFAULT 'issue'; -------------------------------------------------------------------------------- /packages/core/migrations/0041_jittery_landau.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE `app_repo`;--> statement-breakpoint 2 | DROP TABLE `github_repo`;--> statement-breakpoint 3 | DROP TABLE `github_org`;--> statement-breakpoint 4 | DROP TABLE `run_env`;--> statement-breakpoint 5 | ALTER TABLE `run` MODIFY COLUMN `app_id` char(24) NOT NULL;--> statement-breakpoint 6 | ALTER TABLE `app_repository` DROP COLUMN `last_event`;--> statement-breakpoint 7 | ALTER TABLE `app_repository` DROP COLUMN `last_event_id`;--> statement-breakpoint 8 | ALTER TABLE `app_repository` DROP COLUMN `last_event_status`;--> statement-breakpoint 9 | ALTER TABLE `app_repository` DROP COLUMN `time_last_event`;--> statement-breakpoint 10 | ALTER TABLE `state_update` DROP COLUMN `source`; -------------------------------------------------------------------------------- /packages/core/migrations/0042_concerned_rick_jones.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `app_repository` ADD `path` text; -------------------------------------------------------------------------------- /packages/core/migrations/0043_premium_bushwacker.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `runner` MODIFY COLUMN `engine` enum('codebuild') NOT NULL;--> statement-breakpoint 2 | ALTER TABLE `run` ADD `retrier` json;--> statement-breakpoint 3 | ALTER TABLE `run` ADD `force` boolean; -------------------------------------------------------------------------------- /packages/core/migrations/0044_remarkable_blade.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run` ADD `stage_name` varchar(255);--> statement-breakpoint 2 | ALTER TABLE `run` ADD `region` varchar(255);--> statement-breakpoint 3 | ALTER TABLE `run` ADD `aws_account_external_id` varchar(12); -------------------------------------------------------------------------------- /packages/core/migrations/0045_lethal_quicksilver.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run` DROP INDEX `unique_active`;--> statement-breakpoint 2 | ALTER TABLE `run` ADD CONSTRAINT `unique_stage_active` UNIQUE(`workspace_id`,`stage_name`,`region`,`aws_account_external_id`,`active`); -------------------------------------------------------------------------------- /packages/core/migrations/0046_brown_starfox.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE `runner_usage`; -------------------------------------------------------------------------------- /packages/core/migrations/0047_melodic_franklin_richards.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run` DROP FOREIGN KEY `workspace_id_stage_id_fk_aswdrwp7k61d33zx6hg0zhic2`; 2 | -------------------------------------------------------------------------------- /packages/core/migrations/0048_wise_surge.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run` DROP COLUMN `stage_id`; -------------------------------------------------------------------------------- /packages/core/migrations/0049_acoustic_baron_strucker.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `state_count` ( 2 | `id` char(24) NOT NULL, 3 | `workspace_id` char(24) NOT NULL, 4 | `month` date NOT NULL, 5 | `stage_id` char(24) NOT NULL, 6 | `count` int NOT NULL, 7 | CONSTRAINT `state_count_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`), 8 | CONSTRAINT `month` UNIQUE(`workspace_id`,`stage_id`,`month`) 9 | ); 10 | --> statement-breakpoint 11 | ALTER TABLE `state_count` ADD CONSTRAINT `state_count_workspace_id_workspace_id_fk` FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON DELETE no action ON UPDATE no action; -------------------------------------------------------------------------------- /packages/core/migrations/0050_aberrant_edwin_jarvis.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `stripe` ADD `price_id` varchar(255); -------------------------------------------------------------------------------- /packages/core/migrations/0051_ancient_molly_hayes.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_count` ADD `time_created` timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL;--> statement-breakpoint 2 | ALTER TABLE `state_count` ADD `time_updated` timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP;--> statement-breakpoint 3 | ALTER TABLE `state_count` ADD `time_deleted` timestamp; -------------------------------------------------------------------------------- /packages/core/migrations/0052_classy_invisible_woman.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `workspace` ADD `setting_issue` boolean DEFAULT true NOT NULL; -------------------------------------------------------------------------------- /packages/core/migrations/0053_stiff_stephen_strange.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `github_repository` ADD `time_disconnected` timestamp; 2 | -------------------------------------------------------------------------------- /packages/core/migrations/0054_fresh_salo.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `run` DROP INDEX `unique_stage_active`;--> statement-breakpoint 2 | ALTER TABLE `run` ADD CONSTRAINT `unique_stage_active` UNIQUE(`workspace_id`,`app_id`,`stage_name`,`region`,`aws_account_external_id`,`active`); -------------------------------------------------------------------------------- /packages/core/migrations/0055_steady_micromax.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX `id` ON `state_update` (`id`); -------------------------------------------------------------------------------- /packages/core/migrations/0056_nostalgic_omega_red.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_update` ADD `slug` char(6);--> statement-breakpoint 2 | ALTER TABLE `state_update` ADD CONSTRAINT `slug` UNIQUE(`slug`); -------------------------------------------------------------------------------- /packages/core/migrations/0057_familiar_chat.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_update` DROP INDEX `slug`;--> statement-breakpoint 2 | CREATE INDEX `slug` ON `state_update` (`slug`); -------------------------------------------------------------------------------- /packages/core/migrations/0058_melted_zaran.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `stripe` ADD `promo_code` varchar(255); -------------------------------------------------------------------------------- /packages/core/migrations/0059_mute_tiger_shark.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `stripe` ADD `coupon_id` varchar(255);--> statement-breakpoint 2 | ALTER TABLE `stripe` DROP COLUMN `promo_code`; -------------------------------------------------------------------------------- /packages/core/migrations/0060_chubby_black_tarantula.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_update` MODIFY COLUMN `slug` varchar(8); 2 | -------------------------------------------------------------------------------- /packages/core/migrations/0061_tough_maestro.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_event` DROP FOREIGN KEY `state_event_stage_id_7bwbtg0cumicy0oqvwj0nt4cs`; -- statement 2 | 3 | -------------------------------------------------------------------------------- /packages/core/migrations/0062_known_darkhawk.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_event` DROP INDEX `urn`; -- statement 2 | -------------------------------------------------------------------------------- /packages/core/migrations/0063_large_robbie_robertson.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_event` ADD CONSTRAINT `urn` UNIQUE(`workspace_id`, `stage_id`, `update_id`, `action`, `urn`); -- statement 2 | -------------------------------------------------------------------------------- /packages/core/migrations/0064_flowery_toad.sql: -------------------------------------------------------------------------------- 1 | -- Custom SQL migration file, put your code below! -- 2 | ALTER TABLE app MODIFY name VARCHAR(255) NOT NULL COLLATE utf8mb4_bin; 3 | -------------------------------------------------------------------------------- /packages/core/migrations/0065_hesitant_kid_colt.sql: -------------------------------------------------------------------------------- 1 | -- Custom SQL migration file, put your code below! -- 2 | -- update zero."schemaVersions" SET "maxSupportedVersion" = 2; 3 | -------------------------------------------------------------------------------- /packages/core/migrations/0066_clever_reaper.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX `time_updated` ON `state_resource` (`time_updated`); -------------------------------------------------------------------------------- /packages/core/migrations/0067_chubby_lilandra.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `workspace` ADD `old_slug` varchar(255); -------------------------------------------------------------------------------- /packages/core/migrations/0068_familiar_cerebro.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_resource` DROP FOREIGN KEY `state_resource_stage_id_4dfxicfeblzigckqzul3jn47f`; 2 | --> statement-breakpoint 3 | ALTER TABLE `state_resource` DROP FOREIGN KEY `state_resource_update_created_id_3p3iue7jumucr0rzyt5x1ra1i`; 4 | --> statement-breakpoint 5 | ALTER TABLE `state_resource` DROP FOREIGN KEY `state_resource_update_modified_id_eglmolv4lxtiywooq6pgejxx4`; 6 | -------------------------------------------------------------------------------- /packages/core/migrations/0069_famous_magneto.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_update` ADD `version` varchar(255); -------------------------------------------------------------------------------- /packages/core/migrations/0070_useful_lady_ursula.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `aws_account` DROP INDEX `account_id`;--> statement-breakpoint 2 | CREATE INDEX `account_id` ON `aws_account` (`account_id`); -------------------------------------------------------------------------------- /packages/core/migrations/0071_bumpy_deathstrike.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX `account_id` ON `aws_account`;--> statement-breakpoint 2 | ALTER TABLE `aws_account` ADD CONSTRAINT `account_id` UNIQUE(`workspace_id`,`account_id`);--> statement-breakpoint 3 | CREATE INDEX `account_id_idx` ON `aws_account` (`account_id`); -------------------------------------------------------------------------------- /packages/core/migrations/0072_busy_morgan_stark.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `state_update` MODIFY COLUMN `command` enum('deploy','refresh','remove','edit','unknown') NOT NULL; -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@console/core", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "sideEffects": false, 6 | "scripts": { 7 | "test": "sst shell vitest", 8 | "typecheck": "tsc --noEmit --incremental", 9 | "db": "sst shell drizzle-kit", 10 | "pg": "sst shell -- drizzle-kit --config drizzle.pg.ts" 11 | }, 12 | "exports": { 13 | "./*": "./src/*.ts" 14 | }, 15 | "devDependencies": { 16 | "@types/luxon": "3.4.2", 17 | "@types/node": "^22.10.1", 18 | "@types/react": "18.0.25", 19 | "@types/relaxed-json": "^1.0.1", 20 | "drizzle-kit": "0.30.4", 21 | "sst": "3.12.5", 22 | "typescript": "5.5.4", 23 | "vitest": "^0.25.3" 24 | }, 25 | "dependencies": { 26 | "@aws-sdk/client-cloudformation": "3.699.0", 27 | "@aws-sdk/client-cloudwatch-logs": "3.699.0", 28 | "@aws-sdk/client-codebuild": "3.699.0", 29 | "@aws-sdk/client-ec2": "3.701.0", 30 | "@aws-sdk/client-eventbridge": "3.699.0", 31 | "@aws-sdk/client-iam": "3.699.0", 32 | "@aws-sdk/client-iot": "3.699.0", 33 | "@aws-sdk/client-iot-data-plane": "3.699.0", 34 | "@aws-sdk/client-lambda": "3.699.0", 35 | "@aws-sdk/client-s3": "3.701.0", 36 | "@aws-sdk/client-scheduler": "3.699.0", 37 | "@aws-sdk/client-sesv2": "3.699.0", 38 | "@aws-sdk/client-sfn": "3.699.0", 39 | "@aws-sdk/client-sqs": "3.699.0", 40 | "@aws-sdk/client-ssm": "3.699.0", 41 | "@aws-sdk/client-sts": "3.699.0", 42 | "@aws-sdk/middleware-retry": "3.374.0", 43 | "@aws-sdk/s3-request-presigner": "3.701.0", 44 | "@console/mail": "workspace:*", 45 | "@jsx-email/render": "^1.1.0", 46 | "@openauthjs/openevent": "0.0.27", 47 | "@paralleldrive/cuid2": "2.2.2", 48 | "@planetscale/database": "^1.7.0", 49 | "@slack/web-api": "^6.9.0", 50 | "drizzle-orm": "0.39.1", 51 | "drizzle-zod": "0.5.1", 52 | "hono": "4.6.5", 53 | "luxon": "3.5.0", 54 | "minimatch": "^9.0.4", 55 | "octokit": "^4.0.2", 56 | "postgres": "^3.4.5", 57 | "react": "18.2.0", 58 | "relaxed-json": "^1.0.3", 59 | "remeda": "^2.17.3", 60 | "source-map": "^0.7.4", 61 | "stripe": "16.8.0", 62 | "undici": "^5.22.0", 63 | "zod": "3.24.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/core/src/account/account.sql.ts: -------------------------------------------------------------------------------- 1 | import { mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"; 2 | import { id, timestamps } from "../util/sql"; 3 | 4 | export const account = mysqlTable( 5 | "account", 6 | { 7 | ...id, 8 | ...timestamps, 9 | email: varchar("email", { length: 255 }).notNull(), 10 | }, 11 | (user) => ({ 12 | email: uniqueIndex("email").on(user.email), 13 | }), 14 | ); 15 | -------------------------------------------------------------------------------- /packages/core/src/account/index.ts: -------------------------------------------------------------------------------- 1 | export * as Account from "./"; 2 | 3 | import { createSelectSchema } from "drizzle-zod"; 4 | import { z } from "zod"; 5 | import { zod } from "../util/zod"; 6 | import { createId } from "@paralleldrive/cuid2"; 7 | import { db } from "../drizzle"; 8 | import { and, eq, getTableColumns, isNull } from "drizzle-orm"; 9 | import { useTransaction } from "../util/transaction"; 10 | import { account } from "./account.sql"; 11 | import { assertActor } from "../actor"; 12 | import { workspace } from "../workspace/workspace.sql"; 13 | import { user } from "../user/user.sql"; 14 | 15 | export const Info = createSelectSchema(account, { 16 | id: (schema) => schema.id.cuid2(), 17 | email: (schema) => schema.email.trim().toLowerCase().nonempty(), 18 | }); 19 | export type Info = z.infer; 20 | 21 | export const create = zod( 22 | Info.pick({ email: true, id: true }).partial({ 23 | id: true, 24 | }), 25 | (input) => 26 | useTransaction(async (tx) => { 27 | const id = input.id ?? createId(); 28 | await tx.insert(account).values({ 29 | id, 30 | email: input.email, 31 | }); 32 | return id; 33 | }), 34 | ); 35 | 36 | export const fromID = zod(Info.shape.id, async (id) => 37 | db.transaction(async (tx) => { 38 | return tx 39 | .select() 40 | .from(account) 41 | .where(eq(account.id, id)) 42 | .execute() 43 | .then((rows) => rows[0]); 44 | }), 45 | ); 46 | 47 | export const fromEmail = zod(Info.shape.email, async (email) => 48 | db.transaction(async (tx) => { 49 | return tx 50 | .select() 51 | .from(account) 52 | .where(eq(account.email, email)) 53 | .execute() 54 | .then((rows) => rows[0]); 55 | }), 56 | ); 57 | 58 | export function workspaces() { 59 | const actor = assertActor("account"); 60 | return useTransaction((tx) => 61 | tx 62 | .select(getTableColumns(workspace)) 63 | .from(workspace) 64 | .innerJoin(user, eq(user.workspaceID, workspace.id)) 65 | .where( 66 | and( 67 | eq(user.email, actor.properties.email), 68 | isNull(user.timeDeleted), 69 | isNull(workspace.timeDeleted), 70 | ), 71 | ) 72 | .execute(), 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /packages/core/src/actor.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { createContext } from "./context"; 3 | 4 | export const PublicActor = z.object({ 5 | type: z.literal("public"), 6 | properties: z.object({}), 7 | }); 8 | export type PublicActor = z.infer; 9 | 10 | export const AccountActor = z.object({ 11 | type: z.literal("account"), 12 | properties: z.object({ 13 | accountID: z.string().cuid2(), 14 | email: z.string().nonempty(), 15 | }), 16 | }); 17 | export type AccountActor = z.infer; 18 | 19 | export const UserActor = z.object({ 20 | type: z.literal("user"), 21 | properties: z.object({ 22 | userID: z.string().cuid2(), 23 | workspaceID: z.string().cuid2(), 24 | }), 25 | }); 26 | export type UserActor = z.infer; 27 | 28 | export const SystemActor = z.object({ 29 | type: z.literal("system"), 30 | properties: z.object({ 31 | workspaceID: z.string().cuid2(), 32 | }), 33 | }); 34 | export type SystemActor = z.infer; 35 | 36 | export const Actor = z.discriminatedUnion("type", [ 37 | UserActor, 38 | AccountActor, 39 | PublicActor, 40 | SystemActor, 41 | ]); 42 | export type Actor = z.infer; 43 | 44 | const ActorContext = createContext("actor"); 45 | 46 | export const useActor = ActorContext.use; 47 | export const withActor = ActorContext.with; 48 | 49 | export function assertActor(type: T) { 50 | const actor = useActor(); 51 | if (actor.type !== type) { 52 | throw new Error(`Expected actor type ${type}, got ${actor.type}`); 53 | } 54 | 55 | return actor as Extract; 56 | } 57 | 58 | export function useWorkspace() { 59 | const actor = useActor(); 60 | if ("workspaceID" in actor.properties) return actor.properties.workspaceID; 61 | throw new Error(`Expected actor to have workspaceID`); 62 | } 63 | -------------------------------------------------------------------------------- /packages/core/src/agent/agent.pg.ts: -------------------------------------------------------------------------------- 1 | import { pgTable, varchar, integer, bigint } from "drizzle-orm/pg-core"; 2 | import { timestamps, workspaceID } from "../util/sql.pg"; 3 | import { workspaceIndexes } from "../workspace/workspace.pg"; 4 | 5 | export const agentUsageTable = pgTable( 6 | "agent_usage", 7 | { 8 | ...workspaceID, 9 | ...timestamps, 10 | requestID: varchar("request_id", { length: 255 }), 11 | model: varchar("model", { length: 255 }).notNull(), 12 | inputTokens: integer("input_tokens").notNull(), 13 | outputTokens: integer("output_tokens").notNull(), 14 | cost: bigint("cost", { mode: "number" }).notNull(), 15 | }, 16 | (table) => [...workspaceIndexes(table)], 17 | ); 18 | -------------------------------------------------------------------------------- /packages/core/src/alert/alert.sql.ts: -------------------------------------------------------------------------------- 1 | import { 2 | json, 3 | mysqlTable, 4 | primaryKey, 5 | mysqlEnum, 6 | } from "drizzle-orm/mysql-core"; 7 | import { timestampsNext, workspaceID } from "../util/sql"; 8 | import { Alert } from "."; 9 | 10 | export const Event = ["issue", "autodeploy", "autodeploy.error"] as const; 11 | export const alert = mysqlTable( 12 | "issue_alert", 13 | { 14 | ...workspaceID, 15 | ...timestampsNext, 16 | source: json("source").$type().notNull(), 17 | destination: json("destination").$type().notNull(), 18 | event: mysqlEnum("event", Event).default("issue"), 19 | }, 20 | (table) => ({ 21 | primary: primaryKey({ columns: [table.workspaceID, table.id] }), 22 | }) 23 | ); 24 | -------------------------------------------------------------------------------- /packages/core/src/app/index.ts: -------------------------------------------------------------------------------- 1 | export * as App from "./"; 2 | export { Stage } from "./stage"; 3 | 4 | import { createSelectSchema } from "drizzle-zod"; 5 | import { app } from "./app.sql"; 6 | import { z } from "zod"; 7 | import { zod } from "../util/zod"; 8 | import { createId } from "@paralleldrive/cuid2"; 9 | import { db } from "../drizzle"; 10 | import { eq, and } from "drizzle-orm"; 11 | import { useTransaction } from "../util/transaction"; 12 | import { useWorkspace } from "../actor"; 13 | 14 | export const Info = createSelectSchema(app, { 15 | id: (schema) => schema.id.cuid2(), 16 | workspaceID: (schema) => schema.workspaceID.cuid2(), 17 | name: (schema) => schema.name.trim().nonempty(), 18 | }).omit({ 19 | workspaceID: true, 20 | }); 21 | export type Info = z.infer; 22 | 23 | export const create = zod( 24 | Info.pick({ name: true, id: true }).partial({ 25 | id: true, 26 | }), 27 | (input) => 28 | useTransaction(async (tx) => { 29 | const id = input.id ?? createId(); 30 | await tx.insert(app).ignore().values({ 31 | id, 32 | workspaceID: useWorkspace(), 33 | name: input.name, 34 | }); 35 | return tx 36 | .select({ id: app.id }) 37 | .from(app) 38 | .where( 39 | and(eq(app.name, input.name), eq(app.workspaceID, useWorkspace())) 40 | ) 41 | .execute() 42 | .then((rows) => rows[0]!.id); 43 | }) 44 | ); 45 | 46 | export const fromID = zod(Info.shape.id, async (id) => 47 | db.transaction(async (tx) => { 48 | return tx 49 | .select() 50 | .from(app) 51 | .where(and(eq(app.id, id), eq(app.workspaceID, useWorkspace()))) 52 | .execute() 53 | .then((rows) => rows[0]); 54 | }) 55 | ); 56 | 57 | export const fromName = zod(Info.shape.name, async (name) => 58 | db.transaction(async (tx) => { 59 | return tx 60 | .select() 61 | .from(app) 62 | .where(and(eq(app.name, name), eq(app.workspaceID, useWorkspace()))) 63 | .execute() 64 | .then((rows) => rows[0]); 65 | }) 66 | ); 67 | -------------------------------------------------------------------------------- /packages/core/src/aws/aws.sql.ts: -------------------------------------------------------------------------------- 1 | import { 2 | index, 3 | mysqlTable, 4 | primaryKey, 5 | timestamp, 6 | uniqueIndex, 7 | varchar, 8 | } from "drizzle-orm/mysql-core"; 9 | import { timestamps, workspaceID } from "../util/sql"; 10 | 11 | export const awsAccount = mysqlTable( 12 | "aws_account", 13 | { 14 | ...workspaceID, 15 | ...timestamps, 16 | accountID: varchar("account_id", { length: 12 }).notNull(), 17 | timeFailed: timestamp("time_failed", { 18 | mode: "string", 19 | }), 20 | timeDiscovered: timestamp("time_discovered", { 21 | mode: "string", 22 | }), 23 | }, 24 | (table) => [ 25 | primaryKey({ columns: [table.workspaceID, table.id] }), 26 | uniqueIndex("account_id").on(table.workspaceID, table.accountID), 27 | index("updated").on(table.timeUpdated), 28 | index("account_id_idx").on(table.accountID), 29 | ], 30 | ); 31 | -------------------------------------------------------------------------------- /packages/core/src/aws/index.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { zod } from "../util/zod"; 3 | 4 | export { Account } from "./account"; 5 | import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts"; 6 | import { useWorkspace } from "../actor"; 7 | import { useTransaction } from "../util/transaction"; 8 | import { awsAccount } from "./aws.sql"; 9 | import { and, eq, sql } from "drizzle-orm"; 10 | import { RETRY_STRATEGY } from "../util/aws"; 11 | 12 | export * as AWS from "."; 13 | 14 | const sts = new STSClient({ 15 | retryStrategy: RETRY_STRATEGY, 16 | }); 17 | 18 | export const assumeRole = zod(z.string(), async (id) => { 19 | const workspaceID = useWorkspace(); 20 | try { 21 | const result = await sts.send( 22 | new AssumeRoleCommand({ 23 | RoleArn: `arn:aws:iam::${id}:role/sst-${workspaceID}`, 24 | RoleSessionName: "sst", 25 | ExternalId: workspaceID, 26 | DurationSeconds: 3600, 27 | }), 28 | ); 29 | await useTransaction((tx) => 30 | tx 31 | .update(awsAccount) 32 | .set({ 33 | timeFailed: null, 34 | }) 35 | .where( 36 | and( 37 | eq(awsAccount.accountID, id), 38 | eq(awsAccount.workspaceID, workspaceID), 39 | ), 40 | ) 41 | .execute(), 42 | ); 43 | return { 44 | secretAccessKey: result.Credentials!.SecretAccessKey!, 45 | accessKeyId: result.Credentials!.AccessKeyId!, 46 | sessionToken: result.Credentials!.SessionToken!, 47 | }; 48 | } catch (e: any) { 49 | console.log("failed to assume role", e); 50 | if (e.name === "AccessDenied") { 51 | const r = await useTransaction((tx) => 52 | tx 53 | .update(awsAccount) 54 | .set({ 55 | timeFailed: sql`now()`, 56 | }) 57 | .where( 58 | and( 59 | eq(awsAccount.accountID, id), 60 | eq(awsAccount.workspaceID, workspaceID), 61 | ), 62 | ) 63 | .execute(), 64 | ); 65 | return; 66 | } 67 | 68 | throw e; 69 | } 70 | }); 71 | 72 | export type Credentials = Exclude< 73 | Awaited>, 74 | undefined 75 | >; 76 | -------------------------------------------------------------------------------- /packages/core/src/billing/billing.sql.ts: -------------------------------------------------------------------------------- 1 | import { 2 | mysqlTable, 3 | primaryKey, 4 | date, 5 | uniqueIndex, 6 | mysqlEnum, 7 | bigint, 8 | varchar, 9 | timestamp, 10 | } from "drizzle-orm/mysql-core"; 11 | import { timestamps, workspaceID, cuid } from "../util/sql"; 12 | 13 | export const Standing = ["good", "overdue"] as const; 14 | export const usage = mysqlTable( 15 | "usage", 16 | { 17 | workspaceID: workspaceID.workspaceID, 18 | ...timestamps, 19 | id: cuid("id").notNull(), 20 | stageID: cuid("stage_id").notNull(), 21 | day: date("day", { mode: "string" }).notNull(), 22 | invocations: bigint("invocations", { mode: "number" }).notNull(), 23 | }, 24 | (table) => ({ 25 | primary: primaryKey({ columns: [table.workspaceID, table.id] }), 26 | stage: uniqueIndex("stage").on(table.workspaceID, table.stageID, table.day), 27 | }), 28 | ); 29 | 30 | export const stripeTable = mysqlTable( 31 | "stripe", 32 | { 33 | ...workspaceID, 34 | ...timestamps, 35 | customerID: varchar("customer_id", { length: 255 }), 36 | subscriptionID: varchar("subscription_id", { length: 255 }), 37 | subscriptionItemID: varchar("subscription_item_id", { 38 | length: 255, 39 | }), 40 | priceID: varchar("price_id", { length: 255 }), 41 | couponID: varchar("coupon_id", { length: 255 }), 42 | standing: mysqlEnum("standing", Standing), 43 | timeTrialEnded: timestamp("time_trial_ended", { mode: "string" }), 44 | }, 45 | (table) => ({ 46 | primary: primaryKey({ columns: [table.workspaceID, table.id] }), 47 | workspace: uniqueIndex("workspaceID").on(table.workspaceID), 48 | }), 49 | ); 50 | -------------------------------------------------------------------------------- /packages/core/src/context.ts: -------------------------------------------------------------------------------- 1 | import { AsyncLocalStorage } from "node:async_hooks"; 2 | 3 | export function createContext(name: string) { 4 | const storage = new AsyncLocalStorage(); 5 | return { 6 | use() { 7 | const result = storage.getStore(); 8 | if (!result) { 9 | throw new Error("Context not provided: " + name); 10 | } 11 | return result; 12 | }, 13 | with(value: T, fn: () => R) { 14 | return storage.run(value, fn); 15 | }, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/src/drizzle/index.ts: -------------------------------------------------------------------------------- 1 | import { drizzle } from "drizzle-orm/planetscale-serverless"; 2 | import { Client } from "@planetscale/database"; 3 | import { Resource } from "sst"; 4 | import { fetch } from "undici"; 5 | export * from "drizzle-orm"; 6 | export { MySqlColumn } from "drizzle-orm/mysql-core"; 7 | 8 | const client = new Client({ 9 | host: Resource.Database.host, 10 | username: Resource.Database.username, 11 | password: Resource.Database.password, 12 | fetch, 13 | }); 14 | 15 | export const db = drizzle(client, { 16 | logger: 17 | process.env.DRIZZLE_LOG === "true" 18 | ? { 19 | logQuery(query, params) { 20 | console.log({ 21 | query, 22 | params: params.length, 23 | }); 24 | }, 25 | } 26 | : undefined, 27 | }); 28 | -------------------------------------------------------------------------------- /packages/core/src/drizzle/postgres.ts: -------------------------------------------------------------------------------- 1 | import { drizzle } from "drizzle-orm/postgres-js"; 2 | 3 | import { default as pg } from "postgres"; 4 | import { Resource } from "sst"; 5 | 6 | const pgClient = pg({ 7 | idle_timeout: 30000, 8 | connect_timeout: 30000, 9 | host: Resource.Postgres.host, 10 | database: Resource.Postgres.database, 11 | user: Resource.Postgres.username, 12 | password: Resource.Postgres.password, 13 | port: Resource.Postgres.port, 14 | max: parseInt(process.env.POSTGRES_POOL_MAX || "1"), 15 | }); 16 | 17 | export const postgres = drizzle(pgClient, {}); 18 | -------------------------------------------------------------------------------- /packages/core/src/email-octopus/index.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { zod } from "../util/zod"; 3 | import { Resource } from "sst"; 4 | import { logger } from "../util/log"; 5 | import { User } from "../user"; 6 | 7 | export namespace EmailOctopus { 8 | const log = logger({ 9 | service: "email-octopus", 10 | }); 11 | export const subscribe = zod( 12 | z.object({ 13 | userID: z.string(), 14 | }), 15 | async (input) => { 16 | if (Resource.EmailOctopusSecret.value === "disabled") return; 17 | const user = await User.fromID(input.userID); 18 | if (!user) return; 19 | const response = await fetch( 20 | `https://emailoctopus.com/api/1.6/lists/28a43870-ee3c-11ef-bebf-b194b5c89918/contacts`, 21 | { 22 | method: "POST", 23 | body: JSON.stringify({ 24 | api_key: Resource.EmailOctopusSecret.value, 25 | email_address: user?.email, 26 | fields: { 27 | UserID: user.id, 28 | WorkspaceID: user.workspaceID, 29 | }, 30 | }), 31 | headers: { "Content-Type": "application/json" }, 32 | }, 33 | ).then( 34 | (res) => 35 | res.json() as unknown as { 36 | id: string | null; 37 | error?: { code: string }; 38 | }, 39 | ); 40 | log.info("response", response); 41 | return response; 42 | }, 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /packages/core/src/event.ts: -------------------------------------------------------------------------------- 1 | import { event as sstEvent } from "sst/event"; 2 | import { ZodValidator } from "sst/event/validator"; 3 | import { useActor } from "./actor"; 4 | 5 | export const createEvent = sstEvent.builder({ 6 | validator: ZodValidator, 7 | metadata() { 8 | return { 9 | actor: useActor(), 10 | }; 11 | }, 12 | }); 13 | 14 | import { openevent } from "@openauthjs/openevent/event"; 15 | export { publish } from "@openauthjs/openevent/publisher/drizzle"; 16 | 17 | export const event = openevent({ 18 | metadata() { 19 | return { 20 | actor: useActor(), 21 | }; 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /packages/core/src/git/git.sql.ts: -------------------------------------------------------------------------------- 1 | import { 2 | bigint, 3 | foreignKey, 4 | index, 5 | mysqlTable, 6 | primaryKey, 7 | timestamp, 8 | uniqueIndex, 9 | varchar, 10 | } from "drizzle-orm/mysql-core"; 11 | import { cuid, timestamps, timestampsNext, workspaceID } from "../util/sql"; 12 | import { workspaceIndexes } from "../workspace/workspace.sql"; 13 | 14 | export const githubOrgTable = mysqlTable( 15 | "github_organization", 16 | { 17 | ...workspaceID, 18 | ...timestampsNext, 19 | timeDisconnected: timestamp("time_disconnected"), 20 | externalOrgID: bigint("external_org_id", { mode: "number" }).notNull(), 21 | login: varchar("login", { length: 255 }).notNull(), 22 | installationID: bigint("installation_id", { mode: "number" }).notNull(), 23 | }, 24 | (table) => ({ 25 | ...workspaceIndexes(table), 26 | externalOrgID: uniqueIndex("unique_external_org_id").on( 27 | table.workspaceID, 28 | table.externalOrgID 29 | ), 30 | installationID: index("installation_id").on(table.installationID), 31 | }) 32 | ); 33 | 34 | export const githubRepoTable = mysqlTable( 35 | "github_repository", 36 | { 37 | ...workspaceID, 38 | ...timestampsNext, 39 | timeDisconnected: timestamp("time_disconnected"), 40 | githubOrgID: cuid("github_org_id").notNull(), 41 | externalRepoID: bigint("external_repo_id", { mode: "number" }).notNull(), 42 | name: varchar("name", { length: 255 }).notNull(), 43 | }, 44 | (table) => ({ 45 | ...workspaceIndexes(table), 46 | githubOrgID: foreignKey({ 47 | columns: [table.workspaceID, table.githubOrgID], 48 | foreignColumns: [githubOrgTable.workspaceID, githubOrgTable.id], 49 | }).onDelete("cascade"), 50 | uniqueExternalRepoID: uniqueIndex("unique_external_repo_id").on( 51 | table.workspaceID, 52 | table.githubOrgID, 53 | table.externalRepoID 54 | ), 55 | externalRepoID: index("external_repo_id").on(table.externalRepoID), 56 | }) 57 | ); 58 | -------------------------------------------------------------------------------- /packages/core/src/lambda/lambda.sql.ts: -------------------------------------------------------------------------------- 1 | import { json, mysqlTable, primaryKey, varchar } from "drizzle-orm/mysql-core"; 2 | import { timestamps, workspaceID } from "../util/sql"; 3 | import { Actor } from "../actor"; 4 | 5 | export const lambdaPayload = mysqlTable( 6 | "lambda_payload", 7 | { 8 | ...workspaceID, 9 | ...timestamps, 10 | key: varchar("key", { length: 255 }).notNull(), 11 | name: varchar("name", { length: 255 }).notNull(), 12 | payload: json("payload").notNull(), 13 | creator: json("creator").notNull().$type(), 14 | }, 15 | (table) => ({ 16 | primary: primaryKey({ columns: [table.workspaceID, table.id] }), 17 | }) 18 | ); 19 | -------------------------------------------------------------------------------- /packages/core/src/log/log.sql.ts: -------------------------------------------------------------------------------- 1 | import { 2 | mysqlTable, 3 | primaryKey, 4 | text, 5 | timestamp, 6 | mysqlEnum, 7 | uniqueIndex, 8 | varchar, 9 | } from "drizzle-orm/mysql-core"; 10 | import { cuid, timestamps, workspaceID } from "../util/sql"; 11 | 12 | export const log_poller = mysqlTable( 13 | "log_poller", 14 | { 15 | ...workspaceID, 16 | ...timestamps, 17 | stageID: cuid("stage_id").notNull(), 18 | logGroup: varchar("log_group", { length: 512 }).notNull(), 19 | executionARN: text("execution_arn"), 20 | }, 21 | (table) => ({ 22 | primary: primaryKey({ 23 | columns: [table.workspaceID, table.id], 24 | }), 25 | logGroup: uniqueIndex("log_group").on( 26 | table.workspaceID, 27 | table.stageID, 28 | table.logGroup 29 | ), 30 | }) 31 | ); 32 | 33 | export const log_search = mysqlTable( 34 | "log_search", 35 | { 36 | ...workspaceID, 37 | ...timestamps, 38 | userID: cuid("user_id").notNull(), 39 | profileID: varchar("profile_id", { length: 33 }), 40 | stageID: cuid("stage_id").notNull(), 41 | logGroup: varchar("log_group", { length: 512 }).notNull(), 42 | timeStart: timestamp("time_start", { 43 | mode: "string", 44 | }), 45 | timeEnd: timestamp("time_end", { 46 | mode: "string", 47 | }), 48 | outcome: mysqlEnum("outcome", ["completed", "partial"]), 49 | }, 50 | (table) => ({ 51 | primary: primaryKey({ columns: [table.workspaceID, table.id] }), 52 | }) 53 | ); 54 | -------------------------------------------------------------------------------- /packages/core/src/pulumi/index.ts: -------------------------------------------------------------------------------- 1 | export module Pulumi { 2 | export function nameFromURN(input: string) { 3 | return input.split("::").at(-1)!; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/src/realtime/index.ts: -------------------------------------------------------------------------------- 1 | export * as Realtime from "."; 2 | import { 3 | IoTDataPlaneClient, 4 | PublishCommand, 5 | } from "@aws-sdk/client-iot-data-plane"; 6 | import { useWorkspace } from "../actor"; 7 | import { Resource } from "sst"; 8 | 9 | const data = new IoTDataPlaneClient({}); 10 | 11 | export async function publish( 12 | topic: string, 13 | properties: any, 14 | profileID?: string, 15 | ) { 16 | const workspaceID = useWorkspace(); 17 | await data.send( 18 | new PublishCommand({ 19 | payload: Buffer.from( 20 | JSON.stringify({ 21 | properties, 22 | workspaceID, 23 | }), 24 | ), 25 | topic: `console/${Resource.App.stage}/${workspaceID}/${ 26 | profileID ? profileID : "all" 27 | }/${topic}`, 28 | }), 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/replicache/index.ts: -------------------------------------------------------------------------------- 1 | export * as Replicache from "."; 2 | import { 3 | GetObjectCommand, 4 | NoSuchKey, 5 | PutObjectCommand, 6 | S3Client, 7 | } from "@aws-sdk/client-s3"; 8 | import { compress, decompress } from "../util/compress"; 9 | import { Resource } from "sst"; 10 | import { Websocket } from "../websocket"; 11 | 12 | export async function poke(profileID?: string) { 13 | console.log("sending poke"); 14 | await Websocket.publish("poke", {}); 15 | console.log("poke sent"); 16 | } 17 | 18 | const s3 = new S3Client({}); 19 | export module CVR { 20 | interface Info { 21 | data: Record; 22 | clientVersion: number; 23 | } 24 | 25 | export async function key(clientGroupID: string, cookie: number) { 26 | return ( 27 | ["temporary", "weekly", "cvr", clientGroupID, (cookie as number) || 0] 28 | .map((x) => x.toString()) 29 | .join("/") + ".gz" 30 | ); 31 | } 32 | 33 | export async function get(clientGroupID: string, cookie: number) { 34 | const path = key(clientGroupID, cookie); 35 | const result = await s3 36 | .send( 37 | new GetObjectCommand({ 38 | Bucket: Resource.Storage.name, 39 | Key: await path, 40 | }), 41 | ) 42 | .catch((e) => { 43 | if (e instanceof NoSuchKey) return; 44 | throw e; 45 | }); 46 | if (!result) return; 47 | const data = await decompress(await result.Body!.transformToByteArray()!); 48 | return JSON.parse(data.toString()) as Info; 49 | } 50 | 51 | export async function put(clientGroupID: string, version: number, cvr: Info) { 52 | const path = await key(clientGroupID, version); 53 | await s3.send( 54 | new PutObjectCommand({ 55 | Bucket: Resource.Storage.name, 56 | Key: path, 57 | ContentEncoding: "gzip", 58 | ContentType: "application/json", 59 | Body: await compress(JSON.stringify(cvr)), 60 | }), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/core/src/replicache/replicache.sql.ts: -------------------------------------------------------------------------------- 1 | import { 2 | bigint, 3 | char, 4 | index, 5 | int, 6 | json, 7 | mysqlTable, 8 | primaryKey, 9 | } from "drizzle-orm/mysql-core"; 10 | import { timestamps, id } from "../util/sql"; 11 | import { Actor } from "../actor"; 12 | 13 | export const replicache_client_group = mysqlTable( 14 | "replicache_client_group", 15 | { 16 | ...timestamps, 17 | id: char("id", { length: 36 }).notNull(), 18 | actor: json("actor").$type(), 19 | cvrVersion: int("cvr_version").notNull(), 20 | clientVersion: int("client_version").notNull(), 21 | }, 22 | (table) => ({ 23 | primary: primaryKey({ columns: [table.id] }), 24 | }) 25 | ); 26 | 27 | export const replicache_client = mysqlTable( 28 | "replicache_client", 29 | { 30 | id: char("id", { length: 36 }).notNull().primaryKey(), 31 | mutationID: bigint("mutation_id", { 32 | mode: "number", 33 | }) 34 | .default(0) 35 | .notNull(), 36 | ...timestamps, 37 | clientGroupID: char("client_group_id", { length: 36 }).notNull(), 38 | clientVersion: int("client_version").notNull(), 39 | }, 40 | (table) => ({ 41 | clientGroupID: index("client_group_id").on(table.clientGroupID), 42 | }) 43 | ); 44 | 45 | export const replicache_cvr = mysqlTable( 46 | "replicache_cvr", 47 | { 48 | ...id, 49 | ...timestamps, 50 | data: json("data").$type>().notNull(), 51 | id: int("id").notNull(), 52 | clientGroupID: char("client_group_id", { length: 36 }).notNull(), 53 | clientVersion: int("client_version").notNull(), 54 | }, 55 | (table) => ({ 56 | primary: primaryKey({ columns: [table.clientGroupID, table.id] }), 57 | }) 58 | ); 59 | -------------------------------------------------------------------------------- /packages/core/src/run/run.pg.ts: -------------------------------------------------------------------------------- 1 | import { pgTable, json, varchar, unique, boolean } from "drizzle-orm/pg-core"; 2 | import { cuid, timestamps, utc, workspaceID } from "../util/sql.pg"; 3 | import { workspaceIndexes } from "../workspace/workspace.pg"; 4 | import { AutodeployConfig, Log, RunError, Trigger } from "./run.sql"; 5 | import { Actor } from "../actor"; 6 | 7 | export const runTable = pgTable( 8 | "run", 9 | { 10 | ...workspaceID, 11 | ...timestamps, 12 | timeStarted: utc("time_started"), 13 | timeCompleted: utc("time_completed"), 14 | appID: cuid("app_id").notNull(), 15 | stageName: varchar("stage_name", { length: 255 }), 16 | region: varchar("region", { length: 255 }), 17 | awsAccountExternalID: varchar("aws_account_external_id", { 18 | length: 12, 19 | }), 20 | log: json("log").$type(), 21 | trigger: json("trigger").$type().notNull(), 22 | config: json("config").$type(), 23 | error: json("error").$type(), 24 | active: boolean("active"), 25 | retrier: json("retrier").$type(), 26 | force: boolean("force"), 27 | }, 28 | (table) => [ 29 | ...workspaceIndexes(table), 30 | unique("unique_stage_active").on( 31 | table.workspaceID, 32 | table.stageName, 33 | table.region, 34 | table.awsAccountExternalID, 35 | table.active, 36 | ), 37 | ], 38 | ); 39 | -------------------------------------------------------------------------------- /packages/core/src/slack/slack.sql.ts: -------------------------------------------------------------------------------- 1 | import { 2 | mysqlTable, 3 | primaryKey, 4 | text, 5 | uniqueIndex, 6 | varchar, 7 | } from "drizzle-orm/mysql-core"; 8 | import { timestamps, workspaceID } from "../util/sql"; 9 | 10 | export const slackTeam = mysqlTable( 11 | "slack_team", 12 | { 13 | ...workspaceID, 14 | ...timestamps, 15 | teamID: varchar("team_id", { length: 255 }).notNull(), 16 | teamName: varchar("team_name", { length: 255 }).notNull(), 17 | accessToken: text("access_token").notNull(), 18 | }, 19 | (table) => ({ 20 | primary: primaryKey({ columns: [table.workspaceID, table.id] }), 21 | team: uniqueIndex("team").on(table.workspaceID, table.teamID), 22 | }) 23 | ); 24 | -------------------------------------------------------------------------------- /packages/core/src/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * as Storage from "./index"; 2 | 3 | import { 4 | GetObjectCommand, 5 | PutObjectCommand, 6 | PutObjectCommandInput, 7 | S3Client, 8 | } from "@aws-sdk/client-s3"; 9 | import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; 10 | import { createId } from "@paralleldrive/cuid2"; 11 | import { compress } from "../util/compress"; 12 | import { Resource } from "sst"; 13 | 14 | const s3 = new S3Client({}); 15 | export async function putEphemeral( 16 | body: string, 17 | options?: Omit< 18 | PutObjectCommandInput, 19 | "Body" | "Key" | "Bucket" | "ContentEncoding" 20 | >, 21 | ) { 22 | const key = `ephemeral/${createId()}`; 23 | 24 | await s3.send( 25 | new PutObjectCommand({ 26 | Key: key, 27 | Bucket: Resource.Storage.name, 28 | ContentEncoding: "gzip", 29 | Body: await compress(body), 30 | ...options, 31 | }), 32 | ); 33 | 34 | const url = await getSignedUrl( 35 | s3, 36 | new GetObjectCommand({ 37 | Bucket: Resource.Storage.name, 38 | Key: key, 39 | }), 40 | ); 41 | 42 | return url; 43 | } 44 | -------------------------------------------------------------------------------- /packages/core/src/stripe/index.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from "sst"; 2 | import { Stripe } from "stripe"; 3 | 4 | export const stripe = new Stripe(Resource.StripeSecretKey.value, { 5 | apiVersion: "2024-06-20", 6 | }); 7 | -------------------------------------------------------------------------------- /packages/core/src/user/user.pg.ts: -------------------------------------------------------------------------------- 1 | import { index, pgTable, uniqueIndex, varchar } from "drizzle-orm/pg-core"; 2 | import { timestamps, utc, workspaceID } from "../util/sql.pg"; 3 | import { workspaceIndexes } from "../workspace/workspace.pg"; 4 | 5 | export const userTable = pgTable( 6 | "user", 7 | { 8 | ...workspaceID, 9 | ...timestamps, 10 | email: varchar("email", { length: 255 }).notNull(), 11 | timeSeen: utc("time_seen"), 12 | }, 13 | (table) => [ 14 | ...workspaceIndexes(table), 15 | uniqueIndex("email").on(table.workspaceID, table.email), 16 | index("email_global").on(table.email), 17 | ], 18 | ); 19 | -------------------------------------------------------------------------------- /packages/core/src/user/user.sql.ts: -------------------------------------------------------------------------------- 1 | import { 2 | index, 3 | mysqlTable, 4 | primaryKey, 5 | timestamp, 6 | uniqueIndex, 7 | varchar, 8 | } from "drizzle-orm/mysql-core"; 9 | import { timestamps, workspaceID } from "../util/sql"; 10 | 11 | export const user = mysqlTable( 12 | "user", 13 | { 14 | ...workspaceID, 15 | ...timestamps, 16 | email: varchar("email", { length: 255 }).notNull(), 17 | timeSeen: timestamp("time_seen", { 18 | mode: "string", 19 | }), 20 | }, 21 | (table) => ({ 22 | primary: primaryKey({ columns: [table.workspaceID, table.id] }), 23 | email: uniqueIndex("email").on(table.workspaceID, table.email), 24 | emailGlobal: index("email_global").on(table.email), 25 | }) 26 | ); 27 | -------------------------------------------------------------------------------- /packages/core/src/util/aws.ts: -------------------------------------------------------------------------------- 1 | import { StandardRetryStrategy } from "@aws-sdk/middleware-retry"; 2 | 3 | export const RETRY_STRATEGY = new StandardRetryStrategy(async () => 10000, { 4 | retryDecider: (e: any) => { 5 | if ( 6 | [ 7 | "ThrottlingException", 8 | "Throttling", 9 | "TooManyRequestsException", 10 | "OperationAbortedException", 11 | "TimeoutError", 12 | "NetworkingError", 13 | "SlowDown", 14 | ].includes(e.name) 15 | ) { 16 | return true; 17 | } 18 | return false; 19 | }, 20 | delayDecider: (_, attempts) => { 21 | return Math.min(1.5 ** attempts * 100, 5000); 22 | }, 23 | // AWS SDK v3 has an idea of "retry tokens" which are used to 24 | // prevent multiple retries from happening at the same time. 25 | // This is a workaround to disable that. 26 | retryQuota: { 27 | hasRetryTokens: () => true, 28 | releaseRetryTokens: () => {}, 29 | retrieveRetryTokens: () => 1, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /packages/core/src/util/benchmark.ts: -------------------------------------------------------------------------------- 1 | export async function benchmark( 2 | label: string, 3 | cb: () => Promise 4 | ): Promise { 5 | const start = performance.now(); 6 | const result = await cb(); 7 | console.log(label + "=" + Math.ceil(performance.now() - start).toString()); 8 | return result; 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/src/util/compress.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from "util"; 2 | import zlib from "zlib"; 3 | 4 | export function compress(input: zlib.InputType) { 5 | return promisify(zlib.gzip)(input); 6 | } 7 | 8 | export function decompress(input: zlib.InputType) { 9 | return promisify(zlib.gunzip)(input); 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/src/util/disposable.ts: -------------------------------------------------------------------------------- 1 | export function disposable( 2 | init: () => T, 3 | cleanup: (resource: T) => void, 4 | ): T & { [Symbol.dispose]: () => void } { 5 | const resource = init(); 6 | return Object.assign(resource, { 7 | [Symbol.dispose]: () => cleanup(resource), 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/src/util/error.ts: -------------------------------------------------------------------------------- 1 | export class VisibleError extends Error { 2 | constructor(public code: string, message: string) { 3 | super(message); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/src/util/json.ts: -------------------------------------------------------------------------------- 1 | import * as relaxed from "relaxed-json"; 2 | 3 | export function extractJSON(input: string) { 4 | const stack = [] as any[]; 5 | const results = []; 6 | let startIdx = 0; 7 | 8 | for (let i = 0; i < input.length; i++) { 9 | const char = input[i]; 10 | 11 | if (char === "{") { 12 | stack.push("{"); 13 | if (stack.length === 1) { 14 | startIdx = i; 15 | } 16 | } 17 | 18 | if (char === "}") { 19 | stack.pop(); 20 | if (stack.length === 0) { 21 | try { 22 | const jsonString = input.substring(startIdx, i + 1); 23 | const jsonObj = relaxed.parse(jsonString); 24 | results.push(jsonObj as any); 25 | } catch (e) {} 26 | } 27 | } 28 | } 29 | 30 | return results; 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/util/lazy.ts: -------------------------------------------------------------------------------- 1 | export function lazy(callback: () => T) { 2 | let loaded = false; 3 | let result: T; 4 | 5 | return () => { 6 | if (!loaded) { 7 | result = callback(); 8 | loaded = true; 9 | } 10 | return result; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/src/util/log.ts: -------------------------------------------------------------------------------- 1 | export function logger(tags?: Record) { 2 | tags = tags || {}; 3 | 4 | return { 5 | info(message?: any, ...optionalParams: any[]) { 6 | const prefix = Object.entries(tags) 7 | .map(([key, value]) => `${key}=${value}`) 8 | .join(" "); 9 | console.log(prefix, message, ...optionalParams); 10 | }, 11 | tag(key: string, value: string) { 12 | tags[key] = value; 13 | }, 14 | clone() { 15 | return logger({ ...tags }); 16 | }, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/util/object.ts: -------------------------------------------------------------------------------- 1 | export function objectFlatten( 2 | obj: Record, 3 | prefix: string = "", 4 | ): Record { 5 | const result: Record = {}; 6 | 7 | for (const key of Object.keys(obj)) { 8 | const value = obj[key]; 9 | const newKey = prefix 10 | ? prefix + (Array.isArray(obj) ? `[${key}]` : `.${key}`) 11 | : key; 12 | 13 | if (value === null || value === undefined) { 14 | result[newKey] = value; 15 | continue; 16 | } 17 | 18 | if (typeof value === "object") { 19 | Object.assign(result, objectFlatten(value, newKey)); 20 | continue; 21 | } 22 | 23 | result[newKey] = value; 24 | } 25 | 26 | return result; 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/util/queue.ts: -------------------------------------------------------------------------------- 1 | export async function queue( 2 | concurrency: number, 3 | items: T[], 4 | processItem: (item: T) => Promise 5 | ) { 6 | const workers = [...new Array(concurrency)]; 7 | await Promise.all( 8 | workers.map(async (_, index) => { 9 | let count = 0; 10 | while (true) { 11 | const item = items.pop(); 12 | if (!item) { 13 | break; 14 | } 15 | await processItem(item); 16 | count++; 17 | } 18 | }) 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/util/retry.ts: -------------------------------------------------------------------------------- 1 | export async function retry(max: number, callback: () => Promise) { 2 | let final: any; 3 | for (let i = 0; i < max; i++) { 4 | try { 5 | const result = await callback(); 6 | return result; 7 | } catch (err) { 8 | final = err; 9 | continue; 10 | } 11 | } 12 | console.error(final); 13 | } 14 | 15 | export function retrySync(max: number, callback: () => T) { 16 | let final: any; 17 | for (let i = 0; i < max; i++) { 18 | try { 19 | const result = callback(); 20 | return result; 21 | } catch (err) { 22 | final = err; 23 | continue; 24 | } 25 | } 26 | console.error(final); 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/util/sql.pg.ts: -------------------------------------------------------------------------------- 1 | export { createId } from "@paralleldrive/cuid2"; 2 | import { sql } from "drizzle-orm"; 3 | import { varchar, timestamp } from "drizzle-orm/pg-core"; 4 | 5 | export const cuid = (name: string) => varchar(name, { length: 24 }); 6 | 7 | export const id = { 8 | get id() { 9 | return cuid("id").primaryKey().notNull(); 10 | }, 11 | }; 12 | 13 | export const workspaceID = { 14 | get id() { 15 | return cuid("id").notNull(); 16 | }, 17 | get workspaceID() { 18 | return cuid("workspace_id").notNull(); 19 | }, 20 | }; 21 | 22 | export const utc = (name: string) => 23 | timestamp(name, { 24 | withTimezone: true, 25 | }); 26 | 27 | export const timestamps = { 28 | timeCreated: utc("time_created").notNull().defaultNow(), 29 | timeDeleted: utc("time_deleted"), 30 | timeUpdated: utc("time_updated").notNull().defaultNow(), 31 | }; 32 | -------------------------------------------------------------------------------- /packages/core/src/util/sql.ts: -------------------------------------------------------------------------------- 1 | import { char, timestamp } from "drizzle-orm/mysql-core"; 2 | import { sql } from "drizzle-orm"; 3 | export { createId } from "@paralleldrive/cuid2"; 4 | 5 | export const cuid = (name: string) => char(name, { length: 24 }); 6 | export const id = { 7 | get id() { 8 | return cuid("id").primaryKey().notNull(); 9 | }, 10 | }; 11 | 12 | export const workspaceID = { 13 | get id() { 14 | return cuid("id").notNull(); 15 | }, 16 | get workspaceID() { 17 | return cuid("workspace_id").notNull(); 18 | }, 19 | }; 20 | 21 | export const timestamps = { 22 | timeCreated: timestamp("time_created", { 23 | mode: "string", 24 | }) 25 | .notNull() 26 | .default(sql`CURRENT_TIMESTAMP`), 27 | timeUpdated: timestamp("time_updated", { 28 | mode: "string", 29 | }) 30 | .notNull() 31 | .default(sql`CURRENT_TIMESTAMP`) 32 | .onUpdateNow(), 33 | timeDeleted: timestamp("time_deleted", { 34 | mode: "string", 35 | }), 36 | }; 37 | 38 | export const timestampsNext = { 39 | timeCreated: timestamp("time_created") 40 | .notNull() 41 | .default(sql`CURRENT_TIMESTAMP`), 42 | timeUpdated: timestamp("time_updated") 43 | .notNull() 44 | .default(sql`CURRENT_TIMESTAMP`) 45 | .onUpdateNow(), 46 | timeDeleted: timestamp("time_deleted"), 47 | }; 48 | 49 | import { customType } from "drizzle-orm/mysql-core"; 50 | import { gunzipSync, gzipSync } from "zlib"; 51 | 52 | export const blob = (name: string) => 53 | customType<{ data: TData; driverData: string }>({ 54 | dataType() { 55 | return "longtext"; 56 | }, 57 | fromDriver(value) { 58 | return JSON.parse(gunzipSync(Buffer.from(value, "binary")).toString()); 59 | }, 60 | toDriver(value: TData) { 61 | return gzipSync(Buffer.from(JSON.stringify(value))).toString("binary"); 62 | }, 63 | })(name); 64 | -------------------------------------------------------------------------------- /packages/core/src/util/string.ts: -------------------------------------------------------------------------------- 1 | export function countLeadingSpaces(str: string) { 2 | let count = 0; 3 | for (let char of str) { 4 | if (char === " ") { 5 | count++; 6 | } else if (char === "\t") { 7 | count += 2; 8 | } else { 9 | break; 10 | } 11 | } 12 | return count; 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/util/zod.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export function zod< 4 | Schema extends z.ZodSchema, 5 | Return extends any 6 | >(schema: Schema, func: (value: z.infer) => Return) { 7 | const result = (input: z.infer) => { 8 | const parsed = schema.parse(input); 9 | return func(parsed); 10 | }; 11 | result.schema = schema; 12 | return result; 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/warning/warning.sql.ts: -------------------------------------------------------------------------------- 1 | import { 2 | json, 3 | mysqlTable, 4 | primaryKey, 5 | uniqueIndex, 6 | varchar, 7 | } from "drizzle-orm/mysql-core"; 8 | import { cuid, timestamps, workspaceID } from "../util/sql"; 9 | 10 | export const warning = mysqlTable( 11 | "warning", 12 | { 13 | ...workspaceID, 14 | ...timestamps, 15 | stageID: cuid("stage_id"), 16 | type: varchar("type", { length: 255 }).notNull(), 17 | target: varchar("target", { length: 255 }).notNull(), 18 | data: json("data"), 19 | }, 20 | (table) => ({ 21 | primary: primaryKey({ columns: [table.workspaceID, table.id] }), 22 | unique: uniqueIndex("unique").on( 23 | table.workspaceID, 24 | table.stageID, 25 | table.type, 26 | table.target 27 | ), 28 | }) 29 | ); 30 | -------------------------------------------------------------------------------- /packages/core/src/websocket/index.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from "sst"; 2 | import { useWorkspace } from "../actor"; 3 | import { fetch } from "undici"; 4 | 5 | export namespace Websocket { 6 | export async function publish(type: string, properties: Record) { 7 | const event = { 8 | type, 9 | properties: { 10 | ...properties, 11 | workspaceID: useWorkspace(), 12 | }, 13 | }; 14 | const channel = `/workspace/${event.properties.workspaceID}`; 15 | const body = JSON.stringify({ 16 | channel, 17 | events: [JSON.stringify(event)], 18 | }); 19 | await fetch("https://" + Resource.Websocket.http + "/event", { 20 | method: "POST", 21 | headers: { 22 | "Content-Type": "application/json", 23 | "x-api-key": Resource.Websocket.token, 24 | }, 25 | body, 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/workspace/workspace.pg.ts: -------------------------------------------------------------------------------- 1 | import {} from "drizzle-orm/postgres-js"; 2 | import { timestamps, id, utc } from "../util/sql.pg"; 3 | import { 4 | boolean, 5 | pgTable, 6 | primaryKey, 7 | uniqueIndex, 8 | varchar, 9 | } from "drizzle-orm/pg-core"; 10 | 11 | export const workspaceTable = pgTable( 12 | "workspace", 13 | { 14 | ...id, 15 | ...timestamps, 16 | slug: varchar("slug", { length: 255 }).notNull(), 17 | settingIssue: boolean("setting_issue").notNull(), 18 | timeGated: utc("time_gated"), 19 | }, 20 | (table) => [uniqueIndex("slug").on(table.slug)], 21 | ); 22 | 23 | export function workspaceIndexes(table: any) { 24 | return [ 25 | primaryKey({ 26 | columns: [table.workspaceID, table.id], 27 | }), 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/src/workspace/workspace.sql.ts: -------------------------------------------------------------------------------- 1 | import { 2 | boolean, 3 | foreignKey, 4 | mysqlTable, 5 | primaryKey, 6 | timestamp, 7 | uniqueIndex, 8 | varchar, 9 | } from "drizzle-orm/mysql-core"; 10 | import { timestamps, id } from "../util/sql"; 11 | 12 | export const workspace = mysqlTable( 13 | "workspace", 14 | { 15 | ...id, 16 | ...timestamps, 17 | slug: varchar("slug", { length: 255 }).notNull(), 18 | oldSlug: varchar("old_slug", { length: 255 }), 19 | settingIssue: boolean("setting_issue").notNull().default(true), 20 | timeGated: timestamp("time_gated", { 21 | mode: "string", 22 | }), 23 | }, 24 | (table) => ({ 25 | slug: uniqueIndex("slug").on(table.slug), 26 | }), 27 | ); 28 | 29 | export function workspaceIndexes(table: any) { 30 | return { 31 | primary: primaryKey({ columns: [table.workspaceID, table.id] }), 32 | workspace: foreignKey({ 33 | foreignColumns: [workspace.id], 34 | columns: [table.workspaceID], 35 | }), 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /* This file is auto-generated by SST. Do not edit. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | /* deno-fmt-ignore-file */ 5 | 6 | /// 7 | 8 | import "sst" 9 | export {} -------------------------------------------------------------------------------- /packages/core/test/account.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from "vitest"; 2 | import { createId } from "@paralleldrive/cuid2"; 3 | import { Account } from "../src/account"; 4 | 5 | it("create account", async (_ctx) => { 6 | const email = createId() + "@example.com"; 7 | const accountID = createId(); 8 | const account = await Account.create({ 9 | email, 10 | id: accountID, 11 | }); 12 | 13 | expect(await Account.fromID(accountID).then((x) => x?.id)).toEqual(accountID); 14 | expect(await Account.fromEmail(email).then((x) => x?.email)).toEqual(email); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/core/test/log/__snapshots__/error.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Bun Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`container error 1`] = ` 4 | { 5 | "error": "Error", 6 | "failed": false, 7 | "message": "test", 8 | "stack": [ 9 | { 10 | "raw": "at (/home/bun/app/packages/backend/src/api/index.ts:74:23)", 11 | }, 12 | { 13 | "raw": "at (/home/bun/app/packages/backend/src/api/index.ts:73:25)", 14 | }, 15 | { 16 | "raw": "at (/home/bun/app/node_modules/hono/dist/compose.js:29:23)", 17 | }, 18 | { 19 | "raw": "at dispatch (/home/bun/app/node_modules/hono/dist/compose.js:7:32)", 20 | }, 21 | { 22 | "raw": "at run (node:async_hooks:64:22)", 23 | }, 24 | { 25 | "raw": "at auth (/home/bun/app/packages/backend/src/api/auth.ts:22:47)", 26 | }, 27 | { 28 | "raw": "at (/home/bun/app/node_modules/hono/dist/compose.js:29:23)", 29 | }, 30 | { 31 | "raw": "at dispatch (/home/bun/app/node_modules/hono/dist/compose.js:7:32)", 32 | }, 33 | { 34 | "raw": "at (/home/bun/app/packages/backend/src/api/index.ts:26:15)", 35 | }, 36 | { 37 | "raw": "at (/home/bun/app/node_modules/hono/dist/compose.js:29:23)", 38 | }, 39 | ], 40 | } 41 | `; 42 | -------------------------------------------------------------------------------- /packages/core/test/log/__snapshots__/lambda.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Bun Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`invocation 1`] = `[]`; 4 | -------------------------------------------------------------------------------- /packages/core/test/log/error.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "bun:test"; 2 | import { LogError } from "../../src/log/error"; 3 | 4 | test("container error", () => { 5 | const msg = `2025-02-26T16:52:16.348Z 00000000-0000-0000-0000-000000000000 ERROR Error: test 6 | at (/home/bun/app/packages/backend/src/api/index.ts:74:23) 7 | at (/home/bun/app/packages/backend/src/api/index.ts:73:25) 8 | at (/home/bun/app/node_modules/hono/dist/compose.js:29:23) 9 | at dispatch (/home/bun/app/node_modules/hono/dist/compose.js:7:32) 10 | at run (node:async_hooks:64:22) 11 | at auth (/home/bun/app/packages/backend/src/api/auth.ts:22:47) 12 | at (/home/bun/app/node_modules/hono/dist/compose.js:29:23) 13 | at dispatch (/home/bun/app/node_modules/hono/dist/compose.js:7:32) 14 | at (/home/bun/app/packages/backend/src/api/index.ts:26:15) 15 | at (/home/bun/app/node_modules/hono/dist/compose.js:29:23)`; 16 | const error = LogError.extract(msg); 17 | 18 | expect(error).toBeDefined(); 19 | expect(error).toMatchSnapshot(); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/core/test/util/disposable.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "bun:test"; 2 | import { disposable } from "../../src/util/disposable"; 3 | 4 | test("disposable", () => { 5 | let closed = false; 6 | (function run() { 7 | using thing = disposable( 8 | () => ({}), 9 | () => (closed = true), 10 | ); 11 | })(); 12 | expect(closed).toBe(true); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "compilerOptions": { 4 | "strict": true, 5 | "jsx": "react", 6 | "module": "esnext", 7 | "moduleResolution": "bundler", 8 | "noUncheckedIndexedAccess": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/mail/emails/templates/static/ibm-plex-mono-latin-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sst/console/53d4c9161b99d1d8c8d8e6e7e0d404c5aba24a3f/packages/mail/emails/templates/static/ibm-plex-mono-latin-400.woff2 -------------------------------------------------------------------------------- /packages/mail/emails/templates/static/ibm-plex-mono-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sst/console/53d4c9161b99d1d8c8d8e6e7e0d404c5aba24a3f/packages/mail/emails/templates/static/ibm-plex-mono-latin-500.woff2 -------------------------------------------------------------------------------- /packages/mail/emails/templates/static/ibm-plex-mono-latin-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sst/console/53d4c9161b99d1d8c8d8e6e7e0d404c5aba24a3f/packages/mail/emails/templates/static/ibm-plex-mono-latin-600.woff2 -------------------------------------------------------------------------------- /packages/mail/emails/templates/static/ibm-plex-mono-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sst/console/53d4c9161b99d1d8c8d8e6e7e0d404c5aba24a3f/packages/mail/emails/templates/static/ibm-plex-mono-latin-700.woff2 -------------------------------------------------------------------------------- /packages/mail/emails/templates/static/rubik-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sst/console/53d4c9161b99d1d8c8d8e6e7e0d404c5aba24a3f/packages/mail/emails/templates/static/rubik-latin.woff2 -------------------------------------------------------------------------------- /packages/mail/emails/templates/static/sst-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sst/console/53d4c9161b99d1d8c8d8e6e7e0d404c5aba24a3f/packages/mail/emails/templates/static/sst-logo.png -------------------------------------------------------------------------------- /packages/mail/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@console/mail", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "dev": "email preview emails/templates" 7 | }, 8 | "dependencies": { 9 | "@jsx-email/all": "^2.2.0", 10 | "@jsx-email/cli": "^1.1.0", 11 | "@types/react": "18.0.25", 12 | "react": "18.2.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/mail/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /* This file is auto-generated by SST. Do not edit. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | /* deno-fmt-ignore-file */ 5 | 6 | /// 7 | 8 | import "sst" 9 | export {} -------------------------------------------------------------------------------- /packages/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | src/scrap.ts 2 | -------------------------------------------------------------------------------- /packages/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@console/scripts", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "start": "tsx", 7 | "shell": "sst shell" 8 | }, 9 | "dependencies": { 10 | "@aws-sdk/client-cloudwatch": "3.600.0", 11 | "@console/core": "workspace:*", 12 | "@console/mail": "workspace:*", 13 | "@jsx-email/render": "^1.1.0", 14 | "@tsconfig/node16": "^16.1.0", 15 | "luxon": "3.5.0", 16 | "sst": "3.12.5", 17 | "tsx": "^3.12.7" 18 | }, 19 | "devDependencies": { 20 | "@types/inquirer": "^9.0.3", 21 | "@types/luxon": "3.4.2", 22 | "inquirer": "^9.2.7" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/scripts/src/apply-coupon.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from "sst"; 2 | import { stripe } from "@console/core/stripe/index"; 3 | import { useTransaction } from "@console/core/util/transaction"; 4 | import { eq } from "@console/core/drizzle/index"; 5 | import { workspace } from "@console/core/workspace/workspace.sql"; 6 | import { stripeTable } from "@console/core/billing/billing.sql"; 7 | import { DateTime } from "luxon"; 8 | 9 | const args = process.argv.slice(2); 10 | const workspaceSlug = args[0]; 11 | const discount = args[1]; 12 | const coupon = 13 | discount === "50%" 14 | ? Resource.StripeCoupon50ID.value 15 | : discount === "80%" 16 | ? Resource.StripeCoupon80ID.value 17 | : undefined; 18 | 19 | if (args.length !== 2 || !workspaceSlug || !coupon) { 20 | console.error("Usage: apply-coupon.ts <50%|80%>"); 21 | process.exit(1); 22 | } 23 | 24 | const result = await useTransaction((tx) => 25 | tx 26 | .select({ 27 | workspaceID: workspace.id, 28 | slug: workspace.slug, 29 | stripeCustomerID: stripeTable.customerID, 30 | }) 31 | .from(workspace) 32 | .innerJoin(stripeTable, eq(workspace.id, stripeTable.workspaceID)) 33 | .where(eq(workspace.slug, workspaceSlug)) 34 | .execute() 35 | .then((rows) => rows[0]), 36 | ); 37 | if (!result || !result.stripeCustomerID) { 38 | console.error("Workspace not found"); 39 | process.exit(1); 40 | } 41 | 42 | const response = await stripe.customers.update(result.stripeCustomerID, { 43 | coupon, 44 | }); 45 | 46 | console.log(""); 47 | console.log(`Coupon applied!`); 48 | console.log(`Workspace: ${result.slug} (id: ${result.workspaceID})`); 49 | console.log( 50 | `Expiry: ${DateTime.fromSeconds( 51 | response.discount?.end ?? 0, 52 | ).toLocaleString(DateTime.DATE_FULL)}`, 53 | ); 54 | console.log(""); 55 | -------------------------------------------------------------------------------- /packages/scripts/src/backfill-run-stage-name.ts: -------------------------------------------------------------------------------- 1 | import { and, db, eq, isNotNull, isNull } from "@console/core/drizzle"; 2 | import { workspace } from "@console/core/workspace/workspace.sql"; 3 | import { runTable } from "@console/core/run/run.sql"; 4 | import { stage } from "@console/core/app/app.sql"; 5 | import { awsAccount } from "@console/core/aws/aws.sql"; 6 | 7 | const SIZE = 100000; 8 | const runs = await db 9 | .select({ 10 | workspaceID: stage.workspaceID, 11 | id: runTable.id, 12 | stageID: runTable.stageID, 13 | stageName: stage.name, 14 | region: stage.region, 15 | awsAccountExternalID: awsAccount.accountID, 16 | }) 17 | .from(runTable) 18 | .innerJoin(stage, eq(runTable.stageID, stage.id)) 19 | .innerJoin(awsAccount, eq(stage.awsAccountID, awsAccount.id)) 20 | .where(and(isNotNull(runTable.stageID), isNull(runTable.stageName))) 21 | .limit(SIZE) 22 | .execute(); 23 | 24 | console.log("found", runs.length, "runs"); 25 | 26 | const processedStageIDs = new Set(); 27 | for (const run of runs) { 28 | if (processedStageIDs.has(run.stageID!)) continue; 29 | processedStageIDs.add(run.stageID!); 30 | await db 31 | .update(runTable) 32 | .set({ 33 | stageName: run.stageName, 34 | region: run.region, 35 | awsAccountExternalID: run.awsAccountExternalID, 36 | }) 37 | .where( 38 | and( 39 | eq(runTable.workspaceID, run.workspaceID), 40 | eq(runTable.stageID, run.stageID!) 41 | ) 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /packages/scripts/src/backfill-stripe-price-id.ts: -------------------------------------------------------------------------------- 1 | import { and, db, isNotNull, isNull } from "@console/core/drizzle"; 2 | import { stripeTable } from "@console/core/billing/billing.sql"; 3 | import { Resource } from "sst"; 4 | 5 | await db 6 | .update(stripeTable) 7 | .set({ 8 | priceID: Resource.StripeInvocationsPriceID.value, 9 | }) 10 | .where( 11 | and(isNotNull(stripeTable.subscriptionID), isNull(stripeTable.priceID)) 12 | ); 13 | -------------------------------------------------------------------------------- /packages/scripts/src/backfill-workspace-stripe-customer-id.ts: -------------------------------------------------------------------------------- 1 | import { Workspace } from "@console/core/workspace"; 2 | import { db, eq, isNull } from "@console/core/drizzle"; 3 | import { workspace } from "@console/core/workspace/workspace.sql"; 4 | import { stripeTable } from "@console/core/billing/billing.sql"; 5 | import { Billing } from "@console/core/billing"; 6 | import { withActor } from "@console/core/actor"; 7 | 8 | const workspaces = await db 9 | .select({ 10 | id: workspace.id, 11 | }) 12 | .from(workspace) 13 | .leftJoin(stripeTable, eq(workspace.id, stripeTable.workspaceID)) 14 | .where(isNull(stripeTable.customerID)) 15 | .execute(); 16 | 17 | console.log("found", workspaces.length, "workspaces"); 18 | 19 | for (const workspace of workspaces) { 20 | await withActor( 21 | { 22 | type: "system", 23 | properties: { 24 | workspaceID: workspace.id, 25 | }, 26 | }, 27 | async () => { 28 | await Billing.Stripe.createCustomer(); 29 | }, 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/scripts/src/common.ts: -------------------------------------------------------------------------------- 1 | import { db, inArray, or } from "@console/core/drizzle/index"; 2 | import { workspace } from "@console/core/workspace/workspace.sql"; 3 | import readline from "readline"; 4 | 5 | const rl = readline.createInterface({ 6 | input: process.stdin, 7 | output: process.stdout, 8 | }); 9 | 10 | export function prompt(question: string) { 11 | return new Promise((resolve) => { 12 | rl.question(question, (answer) => { 13 | resolve(answer.trim()); 14 | }); 15 | }); 16 | } 17 | 18 | export async function promptWorkspaces() { 19 | const workspaceFilter: string[] = await prompt("workspaces: ").then((x) => 20 | x.split(" ").filter(Boolean), 21 | ); 22 | 23 | const results = await db 24 | .select({ 25 | workspaceID: workspace.id, 26 | slug: workspace.slug, 27 | }) 28 | .from(workspace) 29 | .where( 30 | or( 31 | inArray(workspace.slug, workspaceFilter), 32 | inArray(workspace.id, workspaceFilter), 33 | ), 34 | ) 35 | .execute(); 36 | 37 | console.log( 38 | "found:", 39 | results.map((x) => x.slug), 40 | ); 41 | 42 | return results.map((x) => x.workspaceID); 43 | } 44 | -------------------------------------------------------------------------------- /packages/scripts/src/create-user.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from "@console/core/workspace/workspace.sql"; 2 | import { db } from "@console/core/drizzle"; 3 | 4 | process.env.EVENT_BUS_ARN = 5 | "arn:aws:events:us-east-1:226609089145:event-bus/production-console-bus"; 6 | const workspaces = await db.select().from(workspace).execute(); 7 | -------------------------------------------------------------------------------- /packages/scripts/src/create-workspace.ts: -------------------------------------------------------------------------------- 1 | import { provideActor } from "@console/core/actor"; 2 | import { User } from "@console/core/user"; 3 | import { Workspace } from "@console/core/workspace"; 4 | import inquirer from "inquirer"; 5 | 6 | const result = await inquirer.prompt([ 7 | { 8 | type: "input", 9 | name: "name", 10 | message: "Workspace name", 11 | }, 12 | { 13 | type: "input", 14 | name: "email", 15 | message: "Email of initial user", 16 | }, 17 | ]); 18 | 19 | const workspace = await Workspace.create({ 20 | slug: result.name, 21 | }); 22 | 23 | provideActor({ 24 | type: "system", 25 | properties: { 26 | workspaceID: workspace, 27 | }, 28 | }); 29 | 30 | await User.create({ 31 | email: result.email, 32 | }); 33 | -------------------------------------------------------------------------------- /packages/scripts/src/grant-trial.ts: -------------------------------------------------------------------------------- 1 | import { Billing, Stripe } from "@console/core/billing"; 2 | import { withActor } from "@console/core/actor"; 3 | import { DateTime } from "luxon"; 4 | 5 | const workspaceID = "tviez52nfa0b6aerfw9wh597"; 6 | const timeTrialEnded = DateTime.utc(2030, 1, 1).toSQL({ includeOffset: false }); 7 | 8 | await withActor( 9 | { 10 | type: "system", 11 | properties: { 12 | workspaceID, 13 | }, 14 | }, 15 | async () => { 16 | await Stripe.grantTrial(timeTrialEnded!); 17 | await Billing.updateGatingStatus(); 18 | } 19 | ); 20 | -------------------------------------------------------------------------------- /packages/scripts/src/integrate.ts: -------------------------------------------------------------------------------- 1 | import { provideActor } from "@console/core/actor"; 2 | import { AWS } from "@console/core/aws/index"; 3 | import { awsAccount } from "@console/core/aws/aws.sql"; 4 | import { db } from "@console/core/drizzle/index"; 5 | 6 | const rows = await db.select().from(awsAccount).execute(); 7 | for (const row of rows) { 8 | provideActor({ 9 | type: "system", 10 | properties: { 11 | workspaceID: row.workspaceID, 12 | }, 13 | }); 14 | await AWS.Account.Events.Created.publish({ 15 | awsAccountID: row.id, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /packages/scripts/src/subscribe-issues.ts: -------------------------------------------------------------------------------- 1 | import { withActor } from "@console/core/actor"; 2 | import { db, inArray } from "@console/core/drizzle/index"; 3 | import { stage } from "@console/core/app/app.sql"; 4 | import { State } from "@console/core/state/index"; 5 | import { queue } from "@console/core/util/queue"; 6 | import { promptWorkspaces } from "./common"; 7 | import { bus } from "sst/aws/bus"; 8 | import { Resource } from "sst"; 9 | import { Issue } from "@console/core/issue"; 10 | 11 | const stages = await db 12 | .select() 13 | .from(stage) 14 | .offset(100_000 * 2) 15 | .limit(100_000) 16 | // .where(inArray(stage.workspaceID, await promptWorkspaces())) 17 | .execute(); 18 | 19 | console.log(stages.length); 20 | await queue( 21 | 100, 22 | stages, 23 | async (stage) => 24 | await withActor( 25 | { 26 | type: "system", 27 | properties: { 28 | workspaceID: stage.workspaceID, 29 | }, 30 | }, 31 | async () => { 32 | await bus.publish(Resource.Bus, State.Event.StateRefreshed, { 33 | stageID: stage.id, 34 | }); 35 | }, 36 | ), 37 | ); 38 | console.log("done with all"); 39 | -------------------------------------------------------------------------------- /packages/scripts/src/sync-aws-accounts.ts: -------------------------------------------------------------------------------- 1 | import { awsAccount } from "@console/core/aws/aws.sql"; 2 | import { AWS } from "@console/core/aws"; 3 | import { withActor } from "@console/core/actor"; 4 | import { db, inArray, or } from "@console/core/drizzle"; 5 | import { queue } from "@console/core/util/queue"; 6 | import { promptWorkspaces } from "./common"; 7 | 8 | const accounts = await db 9 | .select() 10 | .from(awsAccount) 11 | .where(inArray(awsAccount.workspaceID, await promptWorkspaces())) 12 | .execute(); 13 | 14 | await queue(100, accounts, (account) => 15 | withActor( 16 | { 17 | type: "system", 18 | properties: { 19 | workspaceID: account.workspaceID, 20 | }, 21 | }, 22 | async () => { 23 | await AWS.Account.Events.Created.publish({ awsAccountID: account.id }); 24 | await new Promise((resolve) => setTimeout(resolve, 30_000)); 25 | } 26 | ) 27 | ); 28 | 29 | console.log("done"); 30 | -------------------------------------------------------------------------------- /packages/scripts/src/sync-stages.ts: -------------------------------------------------------------------------------- 1 | import { and, db, gt, inArray } from "@console/core/drizzle"; 2 | import { stage } from "@console/core/app/app.sql"; 3 | import { Stage } from "@console/core/app"; 4 | import { queue } from "@console/core/util/queue"; 5 | import { Issue } from "@console/core/issue"; 6 | import { withActor } from "@console/core/actor"; 7 | import { promptWorkspaces } from "./common"; 8 | 9 | const stages = await db 10 | .select() 11 | .from(stage) 12 | .where(inArray(stage.workspaceID, await promptWorkspaces())) 13 | .execute(); 14 | console.log("found", stages.length, "stages"); 15 | await queue(100, stages, async (stage) => { 16 | await withActor( 17 | { 18 | type: "system", 19 | properties: { 20 | workspaceID: stage.workspaceID, 21 | }, 22 | }, 23 | () => 24 | Stage.Events.Updated.publish({ 25 | stageID: stage.id, 26 | }) 27 | ); 28 | }); 29 | 30 | export {}; 31 | -------------------------------------------------------------------------------- /packages/scripts/src/sync-usage.ts: -------------------------------------------------------------------------------- 1 | import { db, inArray } from "@console/core/drizzle"; 2 | import { stage } from "@console/core/app/app.sql"; 3 | import { App } from "@console/core/app"; 4 | import { withActor } from "@console/core/actor"; 5 | import { promptWorkspaces } from "./common"; 6 | 7 | const stages = await db 8 | .select() 9 | .from(stage) 10 | .where(inArray(stage.workspaceID, await promptWorkspaces())) 11 | .execute(); 12 | 13 | for (const stage of stages) { 14 | await withActor( 15 | { 16 | type: "system", 17 | properties: { 18 | workspaceID: stage.workspaceID, 19 | }, 20 | }, 21 | () => 22 | App.Stage.Events.UsageRequested.publish({ 23 | stageID: stage.id, 24 | daysOffset: 1, 25 | }) 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /packages/scripts/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /* This file is auto-generated by SST. Do not edit. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | /* deno-fmt-ignore-file */ 5 | 6 | /// 7 | 8 | import "sst" 9 | export {} -------------------------------------------------------------------------------- /packages/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node16/tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react", 5 | "noUncheckedIndexedAccess": true, 6 | "module": "esnext", 7 | "moduleResolution": "bundler", 8 | "baseUrl": "." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.0.0", 4 | "private": true 5 | } -------------------------------------------------------------------------------- /packages/web/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /* This file is auto-generated by SST. Do not edit. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | /* deno-fmt-ignore-file */ 5 | 6 | /// 7 | 8 | import "sst" 9 | export {} -------------------------------------------------------------------------------- /packages/web/workspace/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /packages/web/workspace/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | ### Dev 4 | 5 | Runs the app in dev mode with `pnpm dev` and go to `http://localhost:3000` 6 | 7 | ### Connect to dev 8 | 9 | To only run the frontend and connect it to the dev backend. 10 | 11 | ```bash 12 | bun sst shell --stage=dev --target=Workspace bun dev 13 | ``` 14 | 15 | ### Dummy 16 | 17 | To run the dummy 18 | 19 | 1. Add the following to `/etc/hosts` 20 | 21 | ```bash 22 | 127.0.0.1 localhost dummy.localhost 23 | ``` 24 | 25 | 2. Go to `http://dummy.localhost:3000` and login 26 | 27 | 3. [Select the mode](/packages/functions/src/replicache/dummy/data.ts) with `http://dummy.localhost:3000?dummy=` 28 | -------------------------------------------------------------------------------- /packages/web/workspace/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 18 | 19 | 20 | Console 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /packages/web/workspace/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@console/workspace", 3 | "version": "0.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "vite", 7 | "dev": "vite", 8 | "build": "vite build", 9 | "serve": "vite preview", 10 | "typecheck": "tsc --noEmit --incremental" 11 | }, 12 | "type": "module", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@macaron-css/vite": "1.5.1", 16 | "sst": "3.12.5", 17 | "typescript": "5.5.4", 18 | "vite": "5.4.10", 19 | "vite-plugin-inspect": "0.7.42", 20 | "vite-plugin-solid": "2.10.2" 21 | }, 22 | "dependencies": { 23 | "@botpoison/browser": "0.1.30", 24 | "@console/backend": "workspace:*", 25 | "@console/core": "workspace:*", 26 | "@console/zero": "workspace:*", 27 | "@fontsource/ibm-plex-mono": "4.5.13", 28 | "@fontsource/rubik": "4.5.14", 29 | "@kobalte/core": "0.13.7", 30 | "@macaron-css/core": "1.5.1", 31 | "@macaron-css/solid": "1.5.3", 32 | "@modular-forms/solid": "0.25.0", 33 | "@openauthjs/solid": "0.0.0-20250322224806", 34 | "@paralleldrive/cuid2": "2.2.2", 35 | "@rocicorp/zero": "0.19.2025051702", 36 | "@solid-primitives/event-bus": "1.0.11", 37 | "@solid-primitives/event-listener": "2.3.3", 38 | "@solid-primitives/input-mask": "0.2.2", 39 | "@solid-primitives/keyboard": "1.2.8", 40 | "@solid-primitives/mutation-observer": "1.1.17", 41 | "@solid-primitives/storage": "2.1.4", 42 | "@solidjs/router": "0.15.1", 43 | "@types/luxon": "3.4.2", 44 | "aws-iot-device-sdk-v2": "1.20.1", 45 | "hono": "4.6.5", 46 | "luxon": "3.5.0", 47 | "modern-normalize": "1.1.0", 48 | "posthog-js": "1.154.5", 49 | "remeda": "^2.17.3", 50 | "replicache": "14.2.2", 51 | "solid-js": "1.9.3", 52 | "solid-list": "^0.3.0", 53 | "valibot": "^1.0.0-rc.1", 54 | "virtua": "0.39.3", 55 | "zod": "3.24.1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/web/workspace/public/email: -------------------------------------------------------------------------------- 1 | ../../../mail/emails/templates/static -------------------------------------------------------------------------------- /packages/web/workspace/public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sst/console/53d4c9161b99d1d8c8d8e6e7e0d404c5aba24a3f/packages/web/workspace/public/og.png -------------------------------------------------------------------------------- /packages/web/workspace/public/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sst/console/53d4c9161b99d1d8c8d8e6e7e0d404c5aba24a3f/packages/web/workspace/public/screenshot.png -------------------------------------------------------------------------------- /packages/web/workspace/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sst/console/53d4c9161b99d1d8c8d8e6e7e0d404c5aba24a3f/packages/web/workspace/src/assets/favicon.ico -------------------------------------------------------------------------------- /packages/web/workspace/src/common/context.tsx: -------------------------------------------------------------------------------- 1 | import { ParentProps, Show, createContext, useContext } from "solid-js"; 2 | 3 | export function createInitializedContext< 4 | Name extends string, 5 | T extends { ready: boolean } 6 | >(name: Name, cb: () => T) { 7 | const ctx = createContext(); 8 | 9 | return { 10 | use: () => { 11 | const context = useContext(ctx); 12 | if (!context) throw new Error(`No ${name} context`); 13 | return context; 14 | }, 15 | provider: (props: ParentProps) => { 16 | const value = cb(); 17 | return ( 18 | 19 | 20 | {props.children} 21 | 22 | 23 | ); 24 | }, 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/web/workspace/src/common/hash.ts: -------------------------------------------------------------------------------- 1 | export async function hash(input: string) { 2 | return Array.from( 3 | new Uint8Array( 4 | await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input)) 5 | ) 6 | ) 7 | .map((b) => b.toString(16).padStart(2, "0")) 8 | .join(""); 9 | } 10 | -------------------------------------------------------------------------------- /packages/web/workspace/src/common/resource-icon.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IconJob, 3 | IconApi, 4 | IconRDS, 5 | IconAuth, 6 | IconCron, 7 | IconStack, 8 | IconTable, 9 | IconTopic, 10 | IconQueue, 11 | IconScript, 12 | IconBucket, 13 | IconAppSync, 14 | IconCognito, 15 | IconEventBus, 16 | IconFunction, 17 | IconRemixSite, 18 | IconAstroSite, 19 | IconNextjsSite, 20 | IconStaticSite, 21 | IconWebSocketApi, 22 | IconKinesisStream, 23 | IconSvelteKitSite, 24 | IconSolidStartSite, 25 | IconApiGatewayV1Api, 26 | } from "@console/web/ui/icons/custom"; 27 | import { JSX } from "solid-js"; 28 | import type { Resource } from "@console/core/app/resource"; 29 | 30 | export const ResourceIcon = { 31 | Api: IconApi, 32 | Job: IconJob, 33 | RDS: IconRDS, 34 | Auth: IconAuth, 35 | Cron: IconCron, 36 | Queue: IconQueue, 37 | Stack: IconStack, 38 | Table: IconTable, 39 | Topic: IconTopic, 40 | Bucket: IconBucket, 41 | Script: IconScript, 42 | AppSync: IconAppSync, 43 | Cognito: IconCognito, 44 | EventBus: IconEventBus, 45 | Function: IconFunction, 46 | AstroSite: IconAstroSite, 47 | RemixSite: IconRemixSite, 48 | NextjsSite: IconNextjsSite, 49 | StaticSite: IconStaticSite, 50 | SlsNextjsSite: IconNextjsSite, 51 | WebSocketApi: IconWebSocketApi, 52 | KinesisStream: IconKinesisStream, 53 | SvelteKitSite: IconSvelteKitSite, 54 | SolidStartSite: IconSolidStartSite, 55 | ApiGatewayV1Api: IconApiGatewayV1Api, 56 | Service: IconFunction, 57 | } satisfies Record JSX.Element>; 58 | -------------------------------------------------------------------------------- /packages/web/workspace/src/common/url-builder.ts: -------------------------------------------------------------------------------- 1 | export function githubRepo(owner: string, repo: string) { 2 | return `https://github.com/${owner}/${repo}`; 3 | } 4 | 5 | export function githubCommit(repo: string, commit: string) { 6 | return `${repo}/commit/${commit}`; 7 | } 8 | 9 | export function githubRef(repo: string, ref: string) { 10 | return `${repo}/tree/${ref}`; 11 | } 12 | 13 | export function githubPr(repo: string, pr: number) { 14 | return `${repo}/pull/${pr}`; 15 | } 16 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/aws/account.ts: -------------------------------------------------------------------------------- 1 | import type { Account } from "@console/core/aws/account"; 2 | import { Store } from "../store"; 3 | 4 | export const AccountStore = new Store() 5 | .type() 6 | .scan("list", () => [`awsAccount`]) 7 | .get((id: string) => [`awsAccount`, id]) 8 | .build(); 9 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/aws/index.ts: -------------------------------------------------------------------------------- 1 | export * as AWS from "."; 2 | 3 | export { AccountStore } from "./account"; 4 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/issue.ts: -------------------------------------------------------------------------------- 1 | import type { Issue } from "@console/core/issue/index"; 2 | import { Store } from "./store"; 3 | 4 | export const IssueCountStore = new Store() 5 | .type() 6 | .scan("forIssue", (issueGroup: string) => [`issueCount`, issueGroup]) 7 | .build(); 8 | 9 | export const IssueStore = new Store() 10 | .type() 11 | .scan("forStage", (stageID: string) => ["issue", stageID]) 12 | .get((stageID: string, issueID: string) => ["issue", stageID, issueID]) 13 | .build(); 14 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/lambda-payload.ts: -------------------------------------------------------------------------------- 1 | import type { LambdaPayload } from "@console/core/lambda/index"; 2 | import { Store } from "./store"; 3 | 4 | export const LambdaPayloadStore = new Store() 5 | .type() 6 | .scan("list", () => ["lambdaPayload"]) 7 | .get((id: string) => [`lambdaPayload`, id]) 8 | .build(); 9 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/log-poller.ts: -------------------------------------------------------------------------------- 1 | import type { LogPoller } from "@console/core/log/poller"; 2 | import { Store } from "./store"; 3 | 4 | export const LogPollerStore = new Store() 5 | .type() 6 | .scan("list", () => ["log_poller"]) 7 | .get((id: string) => [`log_poller`, id]) 8 | .build(); 9 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/log-search.ts: -------------------------------------------------------------------------------- 1 | import type { Search } from "@console/core/log/index"; 2 | import { Store } from "./store"; 3 | 4 | export const LogSearchStore = new Store() 5 | .type() 6 | .scan("list", () => [`log_search`]) 7 | .get((id: string) => [`log_search`, id]) 8 | .build(); 9 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/resource.ts: -------------------------------------------------------------------------------- 1 | import type { Resource } from "@console/core/app/resource"; 2 | import { Store } from "./store"; 3 | 4 | export const ResourceStore = new Store() 5 | .type() 6 | .scan("forStage", (stageID: string) => ["resource", stageID]) 7 | .get((id: string) => ["resource", id]) 8 | .build(); 9 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/stage.ts: -------------------------------------------------------------------------------- 1 | import { ReadTransaction } from "replicache"; 2 | import type { Stage } from "@console/core/app/stage"; 3 | import { Store } from "./store"; 4 | 5 | export const StageStore = new Store() 6 | .type() 7 | .scan("list", () => ["stage"]) 8 | .get((stageID: string) => ["stage", stageID]) 9 | .build(); 10 | 11 | export function ActiveStages() { 12 | return async (tx: ReadTransaction) => { 13 | return (await StageStore.list(tx)).filter(stage => !stage.timeDeleted); 14 | }; 15 | } 16 | 17 | export function ActiveStagesForApp(appID: string) { 18 | return async (tx: ReadTransaction) => { 19 | return (await StageStore.list(tx)) 20 | .filter(stage => stage.appID === appID && !stage.timeDeleted); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/usage.ts: -------------------------------------------------------------------------------- 1 | import type { Usage } from "@console/core/billing/index"; 2 | import type { State } from "@console/core/state/index"; 3 | import { Store } from "./store"; 4 | 5 | type PricingTier = { 6 | from: number; 7 | to: number; 8 | rate: number; 9 | }; 10 | 11 | export type PricingPlan = PricingTier[]; 12 | 13 | export const INVOCATIONS_PRICING_PLAN: PricingPlan = [ 14 | { from: 0, to: 1000000, rate: 0 }, 15 | { from: 1000000, to: 10000000, rate: 0.00002 }, 16 | { from: 10000000, to: Infinity, rate: 0.000002 }, 17 | ]; 18 | 19 | export const RESOURCES_PRICING_PLAN: PricingPlan = [ 20 | { from: 0, to: 350, rate: 0 }, 21 | { from: 0, to: 2000, rate: 0.086 }, 22 | { from: 2000, to: Infinity, rate: 0.032 }, 23 | ]; 24 | 25 | export const InvocationsUsageStore = new Store() 26 | .type() 27 | .scan("list", () => [`usage`]) 28 | .scan("forStage", (stageID: string) => [`usage`, stageID]) 29 | .build(); 30 | 31 | export const ResourcesUsageStore = new Store() 32 | .type() 33 | .scan("list", () => [`stateCount`]) 34 | .build(); 35 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/user.ts: -------------------------------------------------------------------------------- 1 | import type { User } from "@console/core/user/index"; 2 | import { Store } from "./store"; 3 | 4 | export const UserStore = new Store() 5 | .type() 6 | .get((userID: string) => [`user`, userID]) 7 | .scan("list", () => [`user`]) 8 | .build(); 9 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/warning.ts: -------------------------------------------------------------------------------- 1 | import type { Info } from "@console/core/warning/index"; 2 | import { Store } from "./store"; 3 | 4 | export const WarningStore = new Store() 5 | .type() 6 | .scan("list", () => ["warning"]) 7 | .scan("forStage", (stageID: string) => ["warning", stageID]) 8 | .scan("forType", (stageID: string, type: Info["type"]) => [ 9 | "warning", 10 | stageID, 11 | type, 12 | ]) 13 | .get((stageID: string, type: string, id: string) => [ 14 | "warning", 15 | stageID, 16 | type, 17 | id, 18 | ]) 19 | .build(); 20 | -------------------------------------------------------------------------------- /packages/web/workspace/src/data/workspace.ts: -------------------------------------------------------------------------------- 1 | import type { Info } from "@console/core/workspace/index"; 2 | import { Store } from "./store"; 3 | 4 | export const WorkspaceStore = new Store() 5 | .type() 6 | .scan("list", () => ["workspace"]) 7 | .get((id: string) => [`workspace`, id]) 8 | .build(); 9 | -------------------------------------------------------------------------------- /packages/web/workspace/src/index.tsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from "solid-js/web"; 3 | import posthog from "posthog-js"; 4 | posthog.init("phc_M0b2lW4smpsGIufiTBZ22USKwCy0fyqljMOGufJc79p", { 5 | api_host: "https://telemetry.ion.sst.dev", 6 | }); 7 | 8 | import "modern-normalize/modern-normalize.css"; 9 | import { App } from "./App"; 10 | import { StorageProvider } from "./providers/account"; 11 | 12 | const root = document.getElementById("root"); 13 | 14 | if (import.meta.env.DEV && !(root instanceof HTMLElement)) { 15 | throw new Error( 16 | "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got mispelled?" 17 | ); 18 | } 19 | 20 | render( 21 | () => ( 22 | 23 | 24 | 25 | ), 26 | root! 27 | ); 28 | -------------------------------------------------------------------------------- /packages/web/workspace/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/debug/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | import { styled } from "@macaron-css/solid"; 3 | import { Link } from "@solidjs/router"; 4 | 5 | const Parent = styled(Link, { 6 | base: { 7 | color: "red", 8 | }, 9 | }); 10 | 11 | const Child = styled("div", { 12 | base: { 13 | selectors: { 14 | [`${Parent}:hover &`]: { 15 | color: "blue", 16 | }, 17 | }, 18 | }, 19 | }); 20 | 21 | export function Debug() { 22 | return ( 23 | 24 | Foo 25 | 26 | ); 27 | } 28 | */ 29 | 30 | export function DebugRoute() { 31 | return ; 32 | } 33 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/local.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "@solidjs/router"; 2 | import { createEffect } from "solid-js"; 3 | import { useLocalContext } from "../providers/local"; 4 | import { Splash } from "../ui/splash"; 5 | import { useOpenAuth } from "@openauthjs/solid" 6 | 7 | 8 | export function Local() { 9 | const ctx = useLocalContext(); 10 | const nav = useNavigate(); 11 | createEffect(async () => { 12 | const { app, stage } = ctx; 13 | if (!app || !stage) return; 14 | const auth = useOpenAuth(); 15 | for (const item of Object.keys(auth.all)) { 16 | const access = await auth.access(item) 17 | const result = await fetch( 18 | import.meta.env.VITE_API_URL + 19 | "/local?" + 20 | new URLSearchParams({ 21 | app, 22 | stage, 23 | }).toString(), 24 | { 25 | headers: { 26 | authorization: `Bearer ${access}`, 27 | "content-type": "application/json", 28 | }, 29 | }, 30 | ).then((res) => res.json()); 31 | if (!result.length) continue; 32 | nav(`/${result[0]}/${app}/${stage}`); 33 | } 34 | }); 35 | 36 | return ; 37 | } 38 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Show } from "solid-js"; 2 | import { styled } from "@macaron-css/solid"; 3 | import { A } from "@solidjs/router"; 4 | import { Text } from "@console/web/ui/text"; 5 | import { Stack, Fullscreen } from "@console/web/ui/layout"; 6 | import { Header } from "./workspace/header"; 7 | import { theme } from "@console/web/ui/theme"; 8 | 9 | const HomeLink = styled(A, { 10 | base: { 11 | fontSize: theme.font.size.sm, 12 | }, 13 | }); 14 | 15 | const NotAllowedDesc = styled("div", { 16 | base: { 17 | fontSize: theme.font.size.sm, 18 | color: theme.color.text.secondary.base, 19 | }, 20 | }); 21 | 22 | interface ErrorScreenProps { 23 | inset?: "none" | "header" | "header-tabs"; 24 | message?: string; 25 | header?: boolean; 26 | } 27 | export function NotFound(props: ErrorScreenProps) { 28 | return ( 29 | <> 30 | 31 |
32 | 33 | 36 | 37 | {props.message || "Page not found"} 38 | Go back home 39 | 40 | 41 | 42 | ); 43 | } 44 | 45 | export function NotAllowed(props: ErrorScreenProps) { 46 | return ( 47 | <> 48 | 49 |
50 | 51 | 54 | 55 | Access not allowed 56 | 57 | You don't have access to this page,{" "} 58 | go back home. 59 | 60 | 61 | 62 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/app/autodeploy/index.tsx: -------------------------------------------------------------------------------- 1 | import { Route } from "@solidjs/router"; 2 | import { AutodeployNotFound } from "./not-found"; 3 | import { Detail } from "./detail"; 4 | import { List } from "./list"; 5 | import { useApi } from "../../context"; 6 | import { GatedOverlayWarning } from "../warning"; 7 | 8 | export const Autodeploy = ( 9 | { 11 | const api = useApi(); 12 | return ( 13 | <> 14 | {api.isGated && } 15 | {props.children} 16 | 17 | ); 18 | }} 19 | > 20 | 21 | 22 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/app/autodeploy/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from "../header"; 2 | import { NotFound } from "../../../not-found"; 3 | 4 | export function AutodeployNotFound() { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/app/context.tsx: -------------------------------------------------------------------------------- 1 | import { useReplicache } from "@console/web/providers/replicache"; 2 | import { createContext, useContext } from "solid-js"; 3 | import { useParams } from "@solidjs/router"; 4 | import { AppStore } from "@console/web/data/app"; 5 | 6 | export const AppContext = createContext>(); 7 | 8 | export function createAppContext() { 9 | const params = useParams(); 10 | const rep = useReplicache(); 11 | const app = AppStore.all.watch( 12 | rep, 13 | () => [], 14 | (items) => items.find((app) => app.name === params.appName) 15 | ); 16 | 17 | return { 18 | get ready() { 19 | return app.ready; 20 | }, 21 | get app() { 22 | return app()!; 23 | }, 24 | }; 25 | } 26 | 27 | export function useAppContext() { 28 | const context = useContext(AppContext); 29 | if (!context) throw new Error("No app context"); 30 | return context; 31 | } 32 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/context.ts: -------------------------------------------------------------------------------- 1 | import { createInitializedContext } from "@console/web/common/context"; 2 | import { Workspace } from "@console/core/workspace/index"; 3 | import { type app } from "@console/backend/api/index"; 4 | import { useReplicache } from "@console/web/providers/replicache"; 5 | import { 6 | RESOURCES_PRICING_PLAN, 7 | ResourcesUsageStore, 8 | } from "@console/web/data/usage"; 9 | import { hc } from "hono/client"; 10 | import { Accessor, createContext, useContext } from "solid-js"; 11 | import { sumBy } from "remeda"; 12 | import { useOpenAuth } from "@openauthjs/solid"; 13 | 14 | export const WorkspaceContext = createContext>(); 15 | 16 | export function useWorkspace() { 17 | const context = useContext(WorkspaceContext); 18 | if (!context) throw new Error("No workspace context"); 19 | return context; 20 | } 21 | 22 | export const { use: useApi, provider: ApiProvider } = createInitializedContext( 23 | "Api", 24 | () => { 25 | const rep = useReplicache(); 26 | const auth = useOpenAuth(); 27 | const workspace = useWorkspace(); 28 | const usage = ResourcesUsageStore.list.watch( 29 | rep, 30 | () => [], 31 | (items) => sumBy(items, (item) => item.count), 32 | ); 33 | const client = hc(import.meta.env.VITE_API_URL, { 34 | async fetch(...args: Parameters): Promise { 35 | const [input, init] = args; 36 | const request = 37 | input instanceof Request ? input : new Request(input, init); 38 | const headers = new Headers(request.headers); 39 | headers.set("authorization", `Bearer ${await auth.access()}`); 40 | headers.set("x-sst-workspace", workspace().id); 41 | 42 | return fetch( 43 | new Request(request, { 44 | ...init, 45 | headers, 46 | }), 47 | ); 48 | }, 49 | }); 50 | return { 51 | client, 52 | ready: true, 53 | get isGated() { 54 | return ( 55 | workspace().timeGated !== null && 56 | usage() > RESOURCES_PRICING_PLAN[0].to 57 | ); 58 | }, 59 | }; 60 | }, 61 | ); 62 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/debug.tsx: -------------------------------------------------------------------------------- 1 | import { useReplicache } from "@console/web/providers/replicache"; 2 | 3 | export function Debug() { 4 | const rep = useReplicache(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/stage/agent/components/system.txt: -------------------------------------------------------------------------------- 1 | You are OpenControl, an interactive CLI tool that helps users execute various tasks. 2 | 3 | IMPORTANT: If you get an error when calling a tool, try again with a different approach. Be creative, do not give up, try different inputs to the tool. You should chain together multiple tool calls. ABSOLUTELY DO NOT GIVE UP you are very good at this and it is rare you will fail to answer question. 4 | 5 | You should be concise, direct, and to the point. 6 | 7 | IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to. 8 | IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-3 sentences or a short paragraph, please do. 9 | IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to. 10 | IMPORTANT: Keep your responses short, since they will be displayed on a command line interface. You MUST answer concisely with fewer than 4 lines (not including tool use or code generation), unless user asks for detail. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is .", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...". 11 | 12 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/stage/agent/index.tsx: -------------------------------------------------------------------------------- 1 | import { Route } from "@solidjs/router"; 2 | import { NotFound } from "../../../not-found"; 3 | import { Chat } from "./chat"; 4 | 5 | export const Agent = ( 6 | 7 | { 10 | return ; 11 | }} 12 | /> 13 | } /> 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/stage/issues/common.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from "@console/core/app/resource"; 2 | 3 | export function getLogInfo(resources: Resource.Info[], logGroup?: string) { 4 | const name = logGroup?.split("/").at(-1); 5 | return resources 6 | .flatMap((x) => { 7 | // NextjsSite per-route log group 8 | if ( 9 | x.type === "NextjsSite" && 10 | x.metadata.routes?.logGroupPrefix && 11 | logGroup?.startsWith(x.metadata.routes?.logGroupPrefix) 12 | ) { 13 | return ( 14 | (() => { 15 | // get the server function for the NextjsSite 16 | const serverFunction = resources.find( 17 | (y) => 18 | y.type === "Function" && y.metadata.arn === x.metadata.server, 19 | ); 20 | if (!serverFunction || serverFunction.type !== "Function") return; 21 | 22 | // get the route matching the log group 23 | const route = x.metadata.routes.data.find((route: any) => 24 | logGroup?.endsWith(route.logGroupPath), 25 | ); 26 | if (!route) return; 27 | 28 | return [ 29 | { 30 | uri: `${serverFunction.id}?logGroup=${logGroup}`, 31 | name: `Route: ${route.route}`, 32 | missingSourcemap: serverFunction.metadata.missingSourcemap, 33 | }, 34 | ]; 35 | })() ?? [] 36 | ); 37 | } 38 | // Function log group 39 | else if (x.type === "Function" && name && x.metadata.arn.endsWith(name)) { 40 | return [ 41 | { 42 | uri: x.id, 43 | name: x.metadata.handler, 44 | missingSourcemap: x.metadata.missingSourcemap, 45 | }, 46 | ]; 47 | } 48 | return []; 49 | }) 50 | .at(0); 51 | } 52 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/stage/issues/index.tsx: -------------------------------------------------------------------------------- 1 | import { Route } from "@solidjs/router"; 2 | import { List } from "./list"; 3 | import { Detail } from "./detail"; 4 | import { NotFound } from "../../../not-found"; 5 | import { useApi } from "../../context"; 6 | import { createStageContext } from "../context"; 7 | import { GatedOverlayWarning } from "../../app/warning"; 8 | 9 | export const Issues = ( 10 | { 12 | const api = useApi(); 13 | const ctx = createStageContext(); 14 | return ( 15 | <> 16 | {api.isGated && !ctx.connected && } 17 | {props.children} 18 | 19 | ); 20 | }} 21 | > 22 | 23 | 24 | } /> 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/stage/logs/dummy.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sst/console/53d4c9161b99d1d8c8d8e6e7e0d404c5aba24a3f/packages/web/workspace/src/pages/workspace/stage/logs/dummy.ts -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/stage/logs/index.tsx: -------------------------------------------------------------------------------- 1 | import { Route } from "@solidjs/router"; 2 | import { NotFound } from "../../../not-found"; 3 | import { List } from "./list"; 4 | import { AWSNext } from "./aws/next"; 5 | import { useApi } from "../../context"; 6 | import { createStageContext } from "../context"; 7 | import { GatedOverlayWarning } from "../../app/warning"; 8 | 9 | export const Logs = ( 10 | 11 | 12 | { 15 | const api = useApi(); 16 | const ctx = createStageContext(); 17 | return ( 18 | <> 19 | {api.isGated && !ctx.connected && } 20 | 21 | 22 | ); 23 | }} 24 | /> 25 | } /> 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/stage/resources/index.tsx: -------------------------------------------------------------------------------- 1 | import { NavigationAction, useCommandBar } from "@console/web/pages/workspace/command-bar"; 2 | import { useStageContext } from "@console/web/pages/workspace/stage/context"; 3 | import { Route, useNavigate } from "@solidjs/router"; 4 | import { IconSubRight } from "@console/web/ui/icons/custom"; 5 | import { NotFound } from "../../../not-found"; 6 | import { Detail } from "./detail"; 7 | import { List } from "./list"; 8 | 9 | export const Resources = ( 10 | { 12 | const ctx = useStageContext(); 13 | const bar = useCommandBar(); 14 | const nav = useNavigate(); 15 | 16 | bar.register("resources", async () => { 17 | return [ 18 | NavigationAction({ 19 | icon: IconSubRight, 20 | path: "./updates", 21 | category: ctx.stage.name, 22 | title: "History", 23 | nav, 24 | }), 25 | ]; 26 | }); 27 | return props.children; 28 | }} 29 | > 30 | 31 | 32 | } /> 33 | 34 | ); 35 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/stage/updates/index.tsx: -------------------------------------------------------------------------------- 1 | import { Route } from "@solidjs/router"; 2 | import { NotFound } from "../../../not-found"; 3 | import { Detail } from "./detail"; 4 | import { List } from "./list"; 5 | import { useApi } from "../../context"; 6 | import { createStageContext } from "../context"; 7 | import { GatedOverlayWarning } from "../../app/warning"; 8 | 9 | export const Updates = ( 10 | 11 | { 14 | const api = useApi(); 15 | const ctx = createStageContext(); 16 | return ( 17 | <> 18 | {api.isGated && !ctx.connected && } 19 | 20 | 21 | ); 22 | }} 23 | /> 24 | 25 | } /> 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /packages/web/workspace/src/pages/workspace/zero.tsx: -------------------------------------------------------------------------------- 1 | import { Query, Schema, Zero } from "@rocicorp/zero" 2 | import { useQuery } from "@rocicorp/zero/solid" 3 | import { schema } from "@console/zero/schema" 4 | import { useWorkspace } from "./context" 5 | import { createInitializedContext } from "@console/web/common/context" 6 | import { createEffect } from "solid-js" 7 | import { useOpenAuth } from "@openauthjs/solid" 8 | import { useAccount } from "@console/web/providers/account" 9 | 10 | export const { use: useZero, provider: ZeroProvider } = 11 | createInitializedContext("ZeroContext", () => { 12 | const auth = useOpenAuth() 13 | const account = useAccount() 14 | const workspace = useWorkspace() 15 | const zero = new Zero({ 16 | schema: schema, 17 | auth: () => auth.access(), 18 | userID: account.current.email, 19 | storageKey: workspace().id, 20 | server: import.meta.env.VITE_ZERO_URL, 21 | }) 22 | 23 | return { 24 | mutate: zero.mutate, 25 | query: zero.query, 26 | client: zero, 27 | ready: true, 28 | }; 29 | }); 30 | 31 | export function usePersistentQuery(querySignal: () => Query) { 32 | const workspace = useWorkspace() 33 | // @ts-ignore 34 | const q = () => querySignal().where("workspace_id", "=", workspace().id).where("time_deleted", "IS", null) 35 | createEffect(() => { 36 | q().preload() 37 | }) 38 | return useQuery(q) 39 | } 40 | -------------------------------------------------------------------------------- /packages/web/workspace/src/providers/bus.tsx: -------------------------------------------------------------------------------- 1 | import { createEmitter } from "@solid-primitives/event-bus"; 2 | import type { Invocation, LogEvent } from "@console/core/log/index"; 3 | 4 | export const bus = createEmitter<{ 5 | poke: { 6 | workspaceID: string; 7 | }; 8 | log: LogEvent[]; 9 | "log.url": string; 10 | "bar.show": boolean; 11 | "invocation.url": string; 12 | invocation: Invocation[]; 13 | "log.cleared": { 14 | source: string; 15 | }; 16 | // TODO: fix types 17 | "worker.stdout": any; // Events["worker.stdout"]; 18 | "function.invoked": any; // Events["function.invoked"]; 19 | "function.success": any; // Events["function.success"]; 20 | "function.error": any; // Events["function.error"]; 21 | "cli.dev": { 22 | stage: string; 23 | app: string; 24 | }; 25 | }>(); 26 | -------------------------------------------------------------------------------- /packages/web/workspace/src/providers/dummy.tsx: -------------------------------------------------------------------------------- 1 | import { createInitializedContext } from "@console/web/common/context"; 2 | import type { DummyMode } from "@console/backend/replicache/dummy/data"; 3 | import { useSearchParams } from "@solidjs/router"; 4 | import { useStorage } from "./account"; 5 | 6 | export const { use: useDummy, provider: DummyProvider } = 7 | createInitializedContext("dummy", () => { 8 | const storage = useStorage(); 9 | const [search] = useSearchParams<{ 10 | dummy: DummyMode; 11 | }>(); 12 | storage.set("dummy", search.dummy || storage.value.dummy || "base"); 13 | const splits = location.hostname.split("."); 14 | const isDummy = splits[0] === "dummy" && splits[1] === "localhost"; 15 | 16 | const result = () => 17 | isDummy ? (storage.value.dummy as DummyMode) : undefined; 18 | result.ready = true; 19 | if (isDummy) console.log("dummy mode", storage.value.dummy); 20 | return result; 21 | }); 22 | 23 | export const { use: useDummyConfig, provider: DummyConfigProvider } = 24 | createInitializedContext("dummyConfig", () => { 25 | const dummy = useDummy(); 26 | const result = () => 27 | dummy() 28 | ? { 29 | user: "me@example.com", 30 | local: { 31 | app: "my-sst-app", 32 | stage: "local", 33 | }, 34 | } 35 | : undefined; 36 | result.ready = true; 37 | return result; 38 | }); 39 | -------------------------------------------------------------------------------- /packages/web/workspace/src/providers/flags.tsx: -------------------------------------------------------------------------------- 1 | import { createInitializedContext } from "@console/web/common/context"; 2 | import { createMemo } from "solid-js"; 3 | import { useSearchParams } from "@solidjs/router"; 4 | import { useAccount } from "./account"; 5 | 6 | export const { use: useFlags, provider: FlagsProvider } = 7 | createInitializedContext("FlagsContext", () => { 8 | const account = useAccount() 9 | const email = createMemo(() => account.current.email); 10 | const [search] = useSearchParams(); 11 | const internal = createMemo( 12 | () => email().endsWith("@sst.dev") || search.internal === "true", 13 | ); 14 | const local = window.location.hostname.includes("localhost"); 15 | 16 | return { 17 | ready: true, 18 | get zero() { 19 | return internal() || local; 20 | } 21 | }; 22 | }); 23 | -------------------------------------------------------------------------------- /packages/web/workspace/src/providers/replicache-status.ts: -------------------------------------------------------------------------------- 1 | import { createInitializedContext } from "@console/web/common/context"; 2 | import { createStore } from "solid-js/store"; 3 | 4 | export const { use: useReplicacheStatus, provider: ReplicacheStatusProvider } = 5 | createInitializedContext("ReplicacheStatusProvider", () => { 6 | const [store, setStore] = createStore< 7 | Record< 8 | string, 9 | { 10 | synced?: boolean; 11 | } 12 | > 13 | >(); 14 | 15 | return { 16 | isSynced(id: string) { 17 | return Boolean(store[id]?.synced); 18 | }, 19 | markSynced(id: string) { 20 | setStore(id, { 21 | synced: true, 22 | }); 23 | }, 24 | ready: true, 25 | }; 26 | }); 27 | -------------------------------------------------------------------------------- /packages/web/workspace/src/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /* This file is auto-generated by SST. Do not edit. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | /// 5 | interface ImportMetaEnv { 6 | readonly VITE_API_URL: string 7 | readonly VITE_AUTH_URL: string 8 | readonly VITE_STAGE: string 9 | readonly VITE_ZERO_URL: string 10 | readonly VITE_CONNECT_URL: string 11 | readonly VITE_ISSUES_URL: string 12 | readonly VITE_WEBSOCKET_HTTP: string 13 | readonly VITE_WEBSOCKET_REALTIME: string 14 | } 15 | interface ImportMeta { 16 | readonly env: ImportMetaEnv 17 | } -------------------------------------------------------------------------------- /packages/web/workspace/src/ui/modal.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@macaron-css/solid"; 2 | import { theme } from "./theme"; 3 | import { createEffect, createSignal, ParentProps, untrack } from "solid-js"; 4 | import { createEventListener } from "@solid-primitives/event-listener"; 5 | import { Portal } from "solid-js/web"; 6 | import { bus } from "../providers/bus"; 7 | 8 | const Root = styled("div", { 9 | base: { 10 | position: "fixed", 11 | backgroundColor: theme.color.background.overlay, 12 | opacity: 0, 13 | inset: 0, 14 | display: "flex", 15 | justifyContent: "center", 16 | alignItems: "start", 17 | pointerEvents: "none", 18 | transition: "200ms opacity", 19 | paddingTop: "10vh", 20 | zIndex: 10, 21 | }, 22 | variants: { 23 | show: { 24 | true: { 25 | opacity: 1, 26 | pointerEvents: "all", 27 | backdropFilter: "blur(2px)", 28 | WebkitBackdropFilter: "blur(2px)", 29 | }, 30 | }, 31 | }, 32 | }); 33 | 34 | const Content = styled("div", { 35 | base: { 36 | borderRadius: 10, 37 | flexShrink: 0, 38 | boxShadow: theme.color.shadow.drop.long, 39 | // backdropFilter: "blur(10px)", 40 | // WebkitBackdropFilter: "blur(10px)", 41 | background: theme.color.background.modal, 42 | transform: "scale(0.95)", 43 | transition: "200ms all", 44 | selectors: { 45 | [`${Root.selector({ show: true })} &`]: { 46 | transform: "initial", 47 | }, 48 | }, 49 | }, 50 | }); 51 | 52 | export function Modal( 53 | props: ParentProps<{ show: boolean; onClose: () => void }>, 54 | ) { 55 | let content!: HTMLDivElement; 56 | createEventListener(document, "mouseup", (e) => { 57 | if (!props.show) return; 58 | if (!content.contains(e.target as Node)) { 59 | props.onClose(); 60 | } 61 | }); 62 | 63 | createEventListener(window, "keydown", (e) => { 64 | if (e.key === "Escape") { 65 | if (!props.show) return; 66 | props.onClose(); 67 | } 68 | }); 69 | 70 | bus.on("bar.show", () => { 71 | if (props.show) props.onClose(); 72 | }); 73 | 74 | return ( 75 | 76 | 77 | {props.children} 78 | 79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /packages/web/workspace/src/ui/splash.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@macaron-css/solid"; 2 | import { IconApp } from "./icons/custom"; 3 | import { Fullscreen } from "./layout"; 4 | import { theme } from "./theme"; 5 | import { globalKeyframes } from "@macaron-css/core"; 6 | 7 | const LogoIcon = styled("div", { 8 | base: { 9 | width: 42, 10 | height: 42, 11 | opacity: 0, 12 | color: theme.color.icon.dimmed, 13 | }, 14 | variants: { 15 | pulse: { 16 | true: { 17 | animation: "logo-pulse 2.2s linear infinite alternate", 18 | }, 19 | false: { 20 | animationDelay: "0.3s", 21 | animation: "1s delayedFadeIn", 22 | animationFillMode: "forwards", 23 | }, 24 | }, 25 | }, 26 | defaultVariants: { 27 | pulse: false, 28 | }, 29 | }); 30 | 31 | globalKeyframes("delayedFadeIn", { 32 | "0%": { 33 | opacity: 0, 34 | }, 35 | "100%": { 36 | opacity: 1, 37 | }, 38 | }); 39 | 40 | globalKeyframes("logo-pulse", { 41 | "0%": { 42 | opacity: 0.3, 43 | }, 44 | "50%": { 45 | opacity: 1, 46 | }, 47 | "100%": { 48 | opacity: 0.3, 49 | }, 50 | }); 51 | 52 | interface SplashProps { 53 | pulse?: boolean; 54 | } 55 | export function Splash(props: SplashProps) { 56 | return ( 57 | 58 | 59 | 60 | 61 | 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /packages/web/workspace/src/ui/utility.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from "@macaron-css/core"; 2 | import { theme } from "./theme"; 3 | 4 | export const utility = { 5 | textLine() { 6 | return { 7 | overflow: "hidden", 8 | whiteSpace: "nowrap", 9 | textOverflow: "ellipsis", 10 | } as any; 11 | }, 12 | stack(space: keyof (typeof theme)["space"]) { 13 | return { 14 | display: "flex", 15 | flexDirection: "column", 16 | gap: theme.space[space], 17 | } as any; 18 | }, 19 | row(space: keyof (typeof theme)["space"]) { 20 | return { 21 | display: "flex", 22 | gap: theme.space[space], 23 | } as any; 24 | }, 25 | 26 | text: { 27 | line: { 28 | overflow: "hidden", 29 | whiteSpace: "nowrap", 30 | textOverflow: "ellipsis", 31 | } as any, 32 | label: { 33 | fontWeight: 500, 34 | letterSpacing: 0.5, 35 | textTransform: "uppercase", 36 | fontFamily: theme.font.family.code, 37 | } as any, 38 | pre: { 39 | whiteSpace: "pre-wrap", 40 | overflowWrap: "anywhere", 41 | } as any, 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /packages/web/workspace/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /* This file is auto-generated by SST. Do not edit. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | /* deno-fmt-ignore-file */ 5 | 6 | /// 7 | 8 | import "sst" 9 | export {} -------------------------------------------------------------------------------- /packages/web/workspace/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "skipLibCheck": true, 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "jsx": "preserve", 11 | "jsxImportSource": "solid-js", 12 | "types": [ 13 | "vite/client" 14 | ], 15 | "noEmit": true, 16 | "isolatedModules": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@console/web/*": [ 20 | "./src/*" 21 | ] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/web/workspace/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import solidPlugin from "vite-plugin-solid"; 3 | import { macaronVitePlugin } from "@macaron-css/vite"; 4 | import path from "path"; 5 | 6 | export default defineConfig({ 7 | plugins: [macaronVitePlugin(), solidPlugin()], 8 | server: { 9 | port: 3000, 10 | host: "0.0.0.0", 11 | }, 12 | build: { 13 | target: "esnext", 14 | }, 15 | resolve: { 16 | alias: { 17 | "@console/web": path.resolve(__dirname, "./src"), 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /packages/zero/.gitignore: -------------------------------------------------------------------------------- 1 | sync-replica* 2 | -------------------------------------------------------------------------------- /packages/zero/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@console/zero", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "dependencies": { 6 | "@rocicorp/zero": "0.19.2025051702" 7 | }, 8 | "scripts": { 9 | "dev": "zero-deploy-permissions && zero-cache" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/zero/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /* This file is auto-generated by SST. Do not edit. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | /* deno-fmt-ignore-file */ 5 | 6 | /// 7 | 8 | import "sst" 9 | export {} -------------------------------------------------------------------------------- /patches/@macaron-css%2Fsolid@1.5.3.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@macaron-css/solid/.bun-tag-1514d668548fb0b3 b/.bun-tag-1514d668548fb0b3 2 | new file mode 100644 3 | index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 4 | diff --git a/node_modules/@macaron-css/solid/.bun-tag-9d48210b8d0a8094 b/.bun-tag-9d48210b8d0a8094 5 | new file mode 100644 6 | index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 7 | diff --git a/package.json b/package.json 8 | index c3f06bb43f13f07c712043f8e3beac3a0ea1b8fa..86af93a7f500de2117b91ee3424f3e109b1ea597 100644 9 | --- a/package.json 10 | +++ b/package.json 11 | @@ -2,7 +2,6 @@ 12 | "name": "@macaron-css/solid", 13 | "version": "1.5.3", 14 | "license": "MIT", 15 | - "type": "module", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/mokshit06/macaron.git", 19 | @@ -11,12 +10,14 @@ 20 | "exports": { 21 | ".": { 22 | "types": "./dist/index.d.mts", 23 | - "import": "./dist/index.mjs" 24 | + "import": "./dist/index.mjs", 25 | + "require": "./dist/index.js" 26 | }, 27 | "./dist/*": "./dist/*", 28 | "./runtime": { 29 | "types": "./dist/runtime.d.mts", 30 | - "import": "./dist/runtime.mjs" 31 | + "import": "./dist/runtime.mjs", 32 | + "require": "./dist/runtime.js" 33 | } 34 | }, 35 | "dependencies": { 36 | -------------------------------------------------------------------------------- /scripts/mysql.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bun 2 | 3 | import { Resource } from "sst"; 4 | 5 | Bun.spawnSync( 6 | [ 7 | `mysql`, 8 | `-u${Resource.Database.username}`, 9 | `-p${Resource.Database.password}`, 10 | `-h${Resource.Database.host}`, 11 | `${Resource.Database.database}`, 12 | ], 13 | { 14 | stdin: "inherit", 15 | stdout: "inherit", 16 | stderr: "inherit", 17 | env: { 18 | ...process.env, 19 | }, 20 | }, 21 | ); 22 | -------------------------------------------------------------------------------- /scripts/psql.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bun 2 | 3 | import { Resource } from "sst"; 4 | 5 | Bun.spawnSync( 6 | [ 7 | `psql`, 8 | `-U${Resource.Postgres.username}`, 9 | `-h${Resource.Postgres.host}`, 10 | `${Resource.Postgres.database}`, 11 | ], 12 | { 13 | stdin: "inherit", 14 | stdout: "inherit", 15 | stderr: "inherit", 16 | env: { 17 | ...process.env, 18 | PGPASSWORD: Resource.Postgres.password, 19 | }, 20 | }, 21 | ); 22 | -------------------------------------------------------------------------------- /sst.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export default $config({ 3 | app(input) { 4 | return { 5 | name: "console", 6 | removal: input?.stage === "production" ? "retain" : "remove", 7 | home: "aws", 8 | providers: { 9 | aws: { 10 | region: "us-east-1", 11 | profile: input.stage === "production" ? "sst-production" : "sst-dev", 12 | }, 13 | planetscale: "0.2.2", 14 | command: "1.0.2", 15 | random: "4.17.0", 16 | }, 17 | }; 18 | }, 19 | console: { 20 | autodeploy: { 21 | runner(input) { 22 | return { 23 | engine: "codebuild", 24 | compute: "large", 25 | vpc: 26 | input.stage === "production" 27 | ? { 28 | id: "vpc-0f06c4b635a760100", 29 | subnets: ["subnet-0af5c5640dfe75a22"], 30 | securityGroups: ["sg-0f360ed3d2f363121"], 31 | } 32 | : { 33 | id: "vpc-069d2d529d3288945", 34 | subnets: ["subnet-0b50769394a27a57d"], 35 | securityGroups: ["sg-038ad39edab8e193b"], 36 | }, 37 | }; 38 | }, 39 | }, 40 | }, 41 | async run() { 42 | $transform(sst.aws.Function, (input) => { 43 | input.runtime = "nodejs22.x"; 44 | }); 45 | await import("./infra/dns"); 46 | await import("./infra/cluster"); 47 | await import("./infra/planetscale"); 48 | await import("./infra/websocket"); 49 | await import("./infra/postgres"); 50 | await import("./infra/bus"); 51 | await import("./infra/event"); 52 | await import("./infra/email"); 53 | await import("./infra/alerts"); 54 | await import("./infra/storage"); 55 | await import("./infra/auth"); 56 | await import("./infra/api"); 57 | await import("./infra/web"); 58 | await import("./infra/issues"); 59 | await import("./infra/autodeploy"); 60 | await import("./infra/billing"); 61 | await import("./infra/cluster"); 62 | await import("./infra/opencontrol"); 63 | 64 | return { 65 | bar: "ok", 66 | }; 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /sst.v2.config.ts: -------------------------------------------------------------------------------- 1 | import { SSTConfig } from "sst"; 2 | import { DNS } from "./stacks/dns"; 3 | import { API } from "./stacks/api"; 4 | import { Web } from "./stacks/web"; 5 | import { Auth } from "./stacks/auth"; 6 | import { Email } from "./stacks/email"; 7 | import { Events } from "./stacks/events"; 8 | import { Issues } from "./stacks/issues"; 9 | import { Billing } from "./stacks/billing"; 10 | import { Secrets } from "./stacks/secrets"; 11 | import { Connect } from "./stacks/connect"; 12 | import { Realtime } from "./stacks/realtime"; 13 | import { Storage } from "./stacks/storage"; 14 | import { Alerts } from "./stacks/alerts"; 15 | import { Run } from "./stacks/run"; 16 | 17 | export default { 18 | config(input) { 19 | return { 20 | name: "console", 21 | region: "us-east-1", 22 | profile: input.stage === "production" ? "sst-production" : "sst-dev", 23 | }; 24 | }, 25 | stacks(app) { 26 | if (app.stage !== "production") { 27 | app.setDefaultRemovalPolicy("destroy"); 28 | } 29 | app.setDefaultFunctionProps({ 30 | tracing: "disabled", 31 | architecture: "arm_64", 32 | // nodejs: { 33 | // sourcemap: false, 34 | // }, 35 | }); 36 | 37 | app.addDefaultFunctionEnv({ 38 | NODE_OPTIONS: "--stack-trace-limit=100", 39 | }); 40 | app 41 | .stack(DNS) 42 | .stack(Email) 43 | .stack(Alerts) 44 | .stack(Storage) 45 | .stack(Secrets) 46 | .stack(Auth) 47 | .stack(Events) 48 | .stack(Issues) 49 | .stack(Run) 50 | .stack(API) 51 | .stack(Realtime) 52 | .stack(Connect) 53 | .stack(Web) 54 | .stack(Billing); 55 | }, 56 | } satisfies SSTConfig; 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "exclude": ["packages"], 4 | "compilerOptions": { 5 | "module": "ES2022", 6 | "moduleResolution": "Bundler" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "tasks": { 4 | "typecheck": {}, 5 | "build": { 6 | "outputs": ["./dist"] 7 | } 8 | } 9 | } 10 | --------------------------------------------------------------------------------