From c6cd0927bfc21a63098f74f3ecce1800ba227f4d Mon Sep 17 00:00:00 2001 From: slice Date: Wed, 31 May 2023 19:20:39 -0700 Subject: [PATCH 1/4] migrate to sqlite I don't have a terribly good reason to do this, but it makes database setup much less of a pain. --- .gitignore | 2 + Cargo.lock | 147 ++++++++------------------------ crates/watchdog/Cargo.toml | 2 +- crates/watchdog/src/config.rs | 4 +- crates/watchdog/src/db.rs | 135 +++++++++++++++++------------ crates/watchdog/src/main.rs | 17 +++- crates/watchdog/src/schema.sql | 116 +++++++++++-------------- crates/watchdog/src/scraping.rs | 2 +- 8 files changed, 188 insertions(+), 237 deletions(-) diff --git a/.gitignore b/.gitignore index d7b43b8..1a493ee 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ /havoc_*.json /*.js *.db +*.db-shm +*.db-wal node_modules .next diff --git a/Cargo.lock b/Cargo.lock index 0e83884..e89e9f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,12 +207,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "better_scoped_tls" version = "0.1.0" @@ -560,27 +554,6 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", - "subtle", -] - -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", ] [[package]] @@ -663,6 +636,18 @@ dependencies = [ "instant", ] +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -927,24 +912,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - [[package]] name = "http" version = "0.2.9" @@ -1256,6 +1223,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.8" @@ -1323,15 +1301,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" -[[package]] -name = "md-5" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" -dependencies = [ - "digest", -] - [[package]] name = "memchr" version = "2.5.0" @@ -1755,17 +1724,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall", - "thiserror", -] - [[package]] name = "regex" version = "1.7.1" @@ -1940,17 +1898,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha1" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sha2" version = "0.10.6" @@ -2022,6 +1969,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "sqlformat" version = "0.2.1" @@ -2051,38 +2007,32 @@ checksum = "dcbc16ddba161afc99e14d1713a453747a2b07fc097d2009f4c300ec99286105" dependencies = [ "ahash", "atoi", - "base64", "bitflags", "byteorder", "bytes", "chrono", "crc", "crossbeam-queue", - "dirs", "dotenvy", "either", "event-listener", + "flume", "futures-channel", "futures-core", + "futures-executor", "futures-intrusive", "futures-util", "hashlink", "hex", - "hkdf", - "hmac", "indexmap", "itoa", "libc", + "libsqlite3-sys", "log", - "md-5", "memchr", "once_cell", "paste", "percent-encoding", - "rand", - "serde", - "serde_json", - "sha1", "sha2", "smallvec", "sqlformat", @@ -2091,7 +2041,6 @@ dependencies = [ "thiserror", "tokio-stream", "url", - "whoami", ] [[package]] @@ -2192,12 +2141,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - [[package]] name = "swc_atoms" version = "0.4.39" @@ -2846,26 +2789,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "web-sys" -version = "0.3.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "whoami" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - [[package]] name = "winapi" version = "0.3.9" diff --git a/crates/watchdog/Cargo.toml b/crates/watchdog/Cargo.toml index e5d938f..3e1838a 100644 --- a/crates/watchdog/Cargo.toml +++ b/crates/watchdog/Cargo.toml @@ -21,4 +21,4 @@ toml = "0.5" tokio = { version = "1.21.2", features = ["full"] } axum = "0.6.10" tower-http = { version = "0.3.4", features = ["trace"] } -sqlx = { version = "0.6", features = [ "runtime-tokio-native-tls", "postgres", "chrono" ] } +sqlx = { version = "0.6", features = [ "runtime-tokio-native-tls", "sqlite", "chrono" ] } diff --git a/crates/watchdog/src/config.rs b/crates/watchdog/src/config.rs index 5ff4abd..6c244f6 100644 --- a/crates/watchdog/src/config.rs +++ b/crates/watchdog/src/config.rs @@ -3,7 +3,7 @@ use serde::Deserialize; use crate::subscription::Subscription; #[derive(Clone, Deserialize)] -pub struct PostgresConfig { +pub struct SqliteConfig { pub url: String, #[serde(default = "default_max_connections")] @@ -19,5 +19,5 @@ pub struct Config { pub interval_milliseconds: u64, pub subscriptions: Vec, pub http_api_server_bind_address: std::net::SocketAddr, - pub postgres: PostgresConfig, + pub sqlite: SqliteConfig, } diff --git a/crates/watchdog/src/db.rs b/crates/watchdog/src/db.rs index bd5630d..1ab8df5 100644 --- a/crates/watchdog/src/db.rs +++ b/crates/watchdog/src/db.rs @@ -3,11 +3,11 @@ use havoc::{ discord::{AssetCache, AssetsExt, Branch, FeAsset, FeAssetType, FeBuild, RootScript}, scrape::extract_assets_from_chunk_loader, }; -use sqlx::{postgres::PgRow, Postgres, Row}; +use sqlx::{sqlite::SqliteRow, Row, Sqlite}; #[derive(Clone)] pub struct Db { - pub pool: sqlx::Pool, + pub pool: sqlx::SqlitePool, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -28,7 +28,7 @@ impl DetectedAssetKind { } impl Db { - pub fn new(pool: sqlx::Pool) -> Self { + pub fn new(pool: sqlx::SqlitePool) -> Self { Self { pool } } @@ -36,79 +36,52 @@ impl Db { pub async fn last_known_build_hash_on_branch(&self, branch: Branch) -> Result> { Ok(sqlx::query( "SELECT build_id - FROM detected_builds_on_branches - WHERE branch = $1::discord_branch + FROM build_deploys + WHERE branch = ?1 ORDER BY detected_at DESC LIMIT 1", ) .bind(branch.to_string().to_lowercase()) .fetch_optional(&self.pool) .await? - .map(|row: PgRow| row.get(0))) + .map(|row: SqliteRow| row.get(0))) } - pub async fn detected_assets(&self, build: &FeBuild, cache: &mut AssetCache) -> Result<()> { + pub async fn catalog_and_extract_assets( + &self, + build: &FeBuild, + cache: &mut AssetCache, + ) -> Result<()> { let mut transaction = self.pool.begin().await?; - async fn detected_asset( - transaction: &mut sqlx::Transaction<'_, Postgres>, - build: &FeBuild, - asset: &FeAsset, - kind: DetectedAssetKind, - ) -> Result<()> { - sqlx::query(&format!( - "INSERT INTO detected_assets (build_id, surface, determined_surface_script_type, name) - VALUES ($1, $2, {determined_surface_script_type}, $3) - ON CONFLICT DO NOTHING", - determined_surface_script_type = match kind { - DetectedAssetKind::Deep | DetectedAssetKind::Surface => "NULL".to_owned(), - DetectedAssetKind::SurfaceScript(kind) => - format!("'{:?}'", kind).to_lowercase() + "::surface_script_type", - } - )) - .bind(&build.manifest.hash) - .bind(kind.is_surface()) - .bind(asset.filename()) - .execute(transaction) - .await?; - - Ok(()) - } - for stylesheet in build .manifest .assets .iter() .filter_by_type(FeAssetType::Css) { - detected_asset( + insert_asset( &mut transaction, - build, stylesheet, DetectedAssetKind::Surface, + None, ) .await?; + associate_asset(&mut transaction, build, stylesheet).await?; } + // TODO: Insert module IDs from surface and deep scripts. + let chunks = extract_assets_from_chunk_loader(&build.manifest, cache).await?; for (chunk_id, chunk_asset) in chunks.iter() { - detected_asset( + insert_asset( &mut transaction, - build, chunk_asset, DetectedAssetKind::Deep, + Some(*chunk_id), ) .await?; - - sqlx::query( - "INSERT INTO asset_chunk_ids (build_id, name, chunk_id) - VALUES ($1, $2, $3)", - ) - .bind(&build.manifest.hash) - .bind(chunk_asset.filename()) - .bind(i32::try_from(*chunk_id).expect("chunk id doesn't fit in an i32")) - .execute(&mut transaction) - .await?; + associate_asset(&mut transaction, &build, chunk_asset).await?; } for (script, detected_kind) in build @@ -118,13 +91,14 @@ impl Db { .filter_by_type(FeAssetType::Js) .zip(RootScript::assumed_ordering().into_iter()) { - detected_asset( + insert_asset( &mut transaction, - build, script, DetectedAssetKind::SurfaceScript(detected_kind), + None, ) .await?; + associate_asset(&mut transaction, build, script).await?; } transaction.commit().await?; @@ -134,7 +108,7 @@ impl Db { /// Check whether a build hash is present in the database. pub async fn build_hash_is_catalogued(&self, build_hash: &str) -> Result { Ok( - sqlx::query("SELECT build_id FROM detected_builds WHERE build_id = $1") + sqlx::query("SELECT build_id FROM builds WHERE build_id = ?1") .bind(build_hash) .fetch_optional(&self.pool) .await? @@ -157,9 +131,11 @@ impl Db { let mut transaction = self.pool.begin().await?; + tracing::debug!(?build.number, ?build.manifest.hash, ?branch, "inserting build"); + sqlx::query( - "INSERT INTO detected_builds (build_id, build_number) - VALUES ($1, $2) + "INSERT INTO builds (build_id, build_number) + VALUES (?1, ?2) ON CONFLICT DO NOTHING", ) .bind(&hash) @@ -168,8 +144,8 @@ impl Db { .await?; sqlx::query( - "INSERT INTO detected_builds_on_branches (build_id, branch) - VALUES ($1, $2::discord_branch)", + "INSERT INTO build_deploys (build_id, branch) + VALUES (?1, ?2)", ) .bind(&hash) .bind(branch.to_string().to_lowercase()) @@ -181,3 +157,56 @@ impl Db { Ok(()) } } + +async fn insert_asset( + transaction: &mut sqlx::Transaction<'_, Sqlite>, + asset: &FeAsset, + kind: DetectedAssetKind, + chunk_id: Option, +) -> Result<()> { + let surface_script_type = match kind { + DetectedAssetKind::Deep | DetectedAssetKind::Surface => None, + DetectedAssetKind::SurfaceScript(kind) => Some(format!("{:?}", kind).to_lowercase()), + }; + + tracing::debug!( + asset = asset.filename(), + ?kind, + ?chunk_id, + "inserting asset" + ); + + sqlx::query( + "INSERT INTO assets (name, surface, surface_script_type, script_chunk_id) + VALUES (?1, ?2, ?3, ?4) + ON CONFLICT DO NOTHING", + ) + .bind(asset.filename()) + .bind(kind.is_surface()) + .bind(surface_script_type) + .bind(chunk_id) + .execute(transaction) + .await?; + + Ok(()) +} + +async fn associate_asset( + transaction: &mut sqlx::Transaction<'_, Sqlite>, + build: &FeBuild, + asset: &FeAsset, +) -> Result<()> { + tracing::debug!(?build.number, asset = asset.filename(), "associating asset"); + + sqlx::query( + "INSERT INTO build_assets (build_id, asset_name) + VALUES (?1, ?2) + ON CONFLICT DO NOTHING", + ) + .bind(&build.manifest.hash) + .bind(asset.filename()) + .execute(transaction) + .await?; + + Ok(()) +} diff --git a/crates/watchdog/src/main.rs b/crates/watchdog/src/main.rs index c75ca2f..9fcf8d6 100644 --- a/crates/watchdog/src/main.rs +++ b/crates/watchdog/src/main.rs @@ -1,15 +1,24 @@ +use std::str::FromStr; use std::time::{Duration, Instant}; use anyhow::{Context, Result}; +use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions}; use watchdog::config::Config; use watchdog::db::Db; async fn run(config: Config) -> Result<()> { - tracing::info!("connecting to postgres: {}", config.postgres.url); + tracing::info!( + "connecting to sqlite @ {} (max connections: {})", + config.sqlite.url, + config.sqlite.max_connections + ); - let pool = sqlx::postgres::PgPoolOptions::new() - .max_connections(config.postgres.max_connections) - .connect(&config.postgres.url) + let connect_options = SqliteConnectOptions::from_str(&config.sqlite.url)? + .foreign_keys(true) + .journal_mode(SqliteJournalMode::Wal); + let pool = SqlitePoolOptions::new() + .max_connections(config.sqlite.max_connections) + .connect_with(connect_options) .await?; let db = Db::new(pool); diff --git a/crates/watchdog/src/schema.sql b/crates/watchdog/src/schema.sql index 700a3b6..5e1d047 100644 --- a/crates/watchdog/src/schema.sql +++ b/crates/watchdog/src/schema.sql @@ -1,7 +1,7 @@ -BEGIN; +BEGIN IMMEDIATE TRANSACTION; -- Every frontend build that has been witnessed. -CREATE TABLE IF NOT EXISTS detected_builds ( +CREATE TABLE IF NOT EXISTS builds ( -- A number that seemingly increments for every build Discord creates, -- present in the client scripts. build_number INTEGER PRIMARY KEY, @@ -11,98 +11,86 @@ CREATE TABLE IF NOT EXISTS detected_builds ( build_id TEXT UNIQUE NOT NULL -- We don't have a `detected_at`/`first_detected_at` column because that - -- information can be determined from the `detected_builds_on_branches` table - -- in a more consistent manner that makes it clear on which branch we saw it - -- appear first. + -- information can be determined from the `build_deploys` table in a more + -- consistent manner that makes it clear on which branch we saw it appear + -- first. -- -- When detecting a build for the first time, however, it must be inserted - -- into this table before it may be inserted into - -- `detected_builds_on_branches`. + -- into this table before it may be inserted into `build_deploys`. ); -CREATE TYPE discord_branch AS ENUM ( - 'development', - 'canary', - 'ptb', - 'stable' -); - -CREATE TYPE surface_script_type AS ENUM ( - -- The script that handles kickstarting the loading of other Webpack chunks - -- that aren't surface level assets. - 'chunkloader', - - -- The Webpack chunk containing CSS class mappings. - 'classes', - - -- The Webpack chunk assumed to contain various vendor packages, such as - -- Sentry. - 'vendor', - - -- The Webpack chunk containing the bulk of the application code. - 'entrypoint' -); - --- Assets (CSS/JS/etc. files) associated with a build. --- --- TODO: We should probably scan the surface assets for deep ones (Twemoji, --- artwork, icons, etc.). -CREATE TABLE IF NOT EXISTS detected_assets ( - build_id TEXT NOT NULL REFERENCES detected_builds(build_id), +-- Witnessed frontend assets (CSS/JS/etc. files). +CREATE TABLE IF NOT EXISTS assets ( + -- The filename of the asset, including file extension. This can be fetched + -- from `discord.com/assets/...`. + name TEXT PRIMARY KEY NOT NULL, -- A "surface" asset is exposed directly in the app HTML, and not within an -- asset itself. Surface assets solely consist of the stylesheets and scripts -- necessary to boot the client, and are what the browser fetches first. - surface BOOLEAN NOT NULL, + surface BOOLEAN NOT NULL DEFAULT FALSE, - -- What purpose this asset serves, given it's a surface script. The scripts - -- that appear directly in the app HTML serve distinct purposes, and it's - -- useful to detect and store this information. + -- What purpose this asset serves, given that it's a surface script. The + -- scripts that appear directly in the app HTML serve distinct purposes, and + -- it's useful to detect and store this information. -- -- In practice, we assign the type of surface scripts through the order they -- appear in the HTML. However, this is fragile and may break in the future, -- necessitating the implementation of more resilient heuristics. - determined_surface_script_type surface_script_type DEFAULT NULL, - - -- The filename of the asset, including file extension. This can be fetched - -- from `discord.com/assets/...`. - name TEXT NOT NULL, + -- + -- Possible values: "chunkloader", "classes", 'vendor", "entrypoint" + surface_script_type TEXT, - UNIQUE (build_id, name) + -- The Webpack chunk ID associated with this asset, assuming it is a "deep" + -- (non-surface) script. + script_chunk_id INTEGER ); --- All detected script chunk (not module) IDs in a build. Each script chunk --- is also present as an asset in the `detected_assets` table. Unfortunately, --- this means they are bit disconnected. -CREATE TABLE IF NOT EXISTS asset_chunk_ids ( +-- Witnessed frontend assets associated with a frontend build. +CREATE TABLE IF NOT EXISTS build_assets ( build_id TEXT NOT NULL, + asset_name TEXT NOT NULL, + + FOREIGN KEY (build_id) REFERENCES builds(build_id), + FOREIGN KEY (asset_name) REFERENCES assets(name), + PRIMARY KEY (build_id, asset_name) +); + +CREATE TABLE IF NOT EXISTS module_ids ( + -- The name of the script asset containing Webpack modules. We assume that + -- assets are immutable: once they are built and uploaded to Discord's CDN, + -- we can parse module IDs out of the script and be done with the work + -- forever. name TEXT NOT NULL, - chunk_id INTEGER NOT NULL, + -- The Webpack module ID contained in this asset. + module_id INTEGER NOT NULL, - FOREIGN KEY (build_id, name) REFERENCES detected_assets (build_id, name), - UNIQUE (build_id, name) + FOREIGN KEY (name) REFERENCES assets(name) ); -- Instances of a Discord build detected on a specific branch. -- -- A single build can appear on multiple branches, although not necessarily -- at the same time. -CREATE TABLE IF NOT EXISTS detected_builds_on_branches ( - build_id TEXT NOT NULL REFERENCES detected_builds(build_id), - branch discord_branch NOT NULL, - detected_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP +CREATE TABLE IF NOT EXISTS build_deploys ( + build_id TEXT NOT NULL, + -- Values: "canary", "ptb", "stable", "development" + branch TEXT NOT NULL, + detected_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%d %H:%M:%f')), + + FOREIGN KEY (build_id) REFERENCES builds(build_id) ); -- A view that includes the build number alongside the build ID. Useful, since -- we also want the build number a lot of the time. -CREATE VIEW detections AS +CREATE VIEW IF NOT EXISTS detections AS SELECT - db.build_id, - db.build_number, - dbob.branch, - dbob.detected_at - FROM detected_builds_on_branches dbob - INNER JOIN detected_builds db ON db.build_id = dbob.build_id; + builds.build_id, + builds.build_number, + build_deploys.branch, + build_deploys.detected_at + FROM build_deploys + INNER JOIN builds ON builds.build_id = build_deploys.build_id; COMMIT; diff --git a/crates/watchdog/src/scraping.rs b/crates/watchdog/src/scraping.rs index 2d13394..f6bbda2 100644 --- a/crates/watchdog/src/scraping.rs +++ b/crates/watchdog/src/scraping.rs @@ -32,7 +32,7 @@ pub async fn detect_changes_on_branch( db.detected_build_change_on_branch(&build, branch).await?; if !build_was_previously_catalogued { - db.detected_assets(&build, &mut cache).await?; + db.catalog_and_extract_assets(&build, &mut cache).await?; } else { tracing::info!(?branch, ?build.number, ?build.manifest.hash, "avoiding build asset scrape, already in database"); } From 2508111b60273d0ca8d13e5e662b831c2d1d76ae Mon Sep 17 00:00:00 2001 From: slice Date: Thu, 1 Jun 2023 06:06:09 -0700 Subject: [PATCH 2/4] schema: use numeric timestamps Makes comparison easier. This is the part where I regret using SQLite though! --- crates/watchdog/src/schema.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/watchdog/src/schema.sql b/crates/watchdog/src/schema.sql index 5e1d047..704ec2b 100644 --- a/crates/watchdog/src/schema.sql +++ b/crates/watchdog/src/schema.sql @@ -77,7 +77,7 @@ CREATE TABLE IF NOT EXISTS build_deploys ( build_id TEXT NOT NULL, -- Values: "canary", "ptb", "stable", "development" branch TEXT NOT NULL, - detected_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%d %H:%M:%f')), + detected_at INTEGER NOT NULL DEFAULT (strftime('%s')), FOREIGN KEY (build_id) REFERENCES builds(build_id) ); From c0fb916cb4fddfbe8dde66a072f403742b9c9825 Mon Sep 17 00:00:00 2001 From: slice Date: Thu, 1 Jun 2023 06:07:24 -0700 Subject: [PATCH 3/4] web: migrate to sqlite Because better-sqlite3 is synchronous, it irks me a bit to be using them inside of server components. But maybe it's OK. Everything works, at least. --- spectacles/package-lock.json | 871 +++++++++++++++++++++++++---------- spectacles/package.json | 3 +- spectacles/src/app/page.tsx | 28 +- spectacles/src/db/build.ts | 19 +- spectacles/src/db/index.ts | 29 +- 5 files changed, 659 insertions(+), 291 deletions(-) diff --git a/spectacles/package-lock.json b/spectacles/package-lock.json index b49f7f4..0dd7ec1 100644 --- a/spectacles/package-lock.json +++ b/spectacles/package-lock.json @@ -8,15 +8,16 @@ "name": "spectacles", "version": "0.1.0", "dependencies": { + "@types/better-sqlite3": "^7.6.4", "@types/node": "20.2.5", "@types/react": "18.2.7", "@types/react-dom": "18.2.4", + "better-sqlite3": "^8.4.0", "classnames": "^2.3.2", "date-fns": "^2.30.0", "eslint": "8.41.0", "eslint-config-next": "13.4.4", "next": "13.4.4", - "pg": "^8.11.0", "react": "18.2.0", "react-dom": "18.2.0", "typescript": "5.0.4" @@ -330,6 +331,14 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.4.tgz", + "integrity": "sha512-dzrRZCYPXIXfSR1/surNbJ/grU3scTaygS0OMzjlGf71i9sc2fGyHPXXiXmEvNIoE0cGwsanEFMVJxPXmco9Eg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -654,6 +663,35 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/better-sqlite3": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.4.0.tgz", + "integrity": "sha512-NmsNW1CQvqMszu/CFAJ3pLct6NEFlNfuGM6vw72KHkjOD1UDnL96XNN1BMQc1hiHo8vE2GbOWQYIpZ+YM5wrZw==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.0" + } + }, "node_modules/big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", @@ -662,6 +700,24 @@ "node": ">=0.6" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", @@ -693,12 +749,27 @@ "node": ">=8" } }, - "node_modules/buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", - "engines": { - "node": ">=4" + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, "node_modules/bundle-name": { @@ -780,6 +851,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "node_modules/classnames": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", @@ -865,6 +941,20 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-equal": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", @@ -893,6 +983,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -956,6 +1054,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "engines": { + "node": ">=8" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -983,6 +1089,14 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.14.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz", @@ -1553,6 +1667,14 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1613,6 +1735,11 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -1664,6 +1791,11 @@ "is-callable": "^1.1.3" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1750,6 +1882,11 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -1930,6 +2067,25 @@ "node": ">=14.18.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -1975,6 +2131,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -2485,6 +2646,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2504,6 +2676,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2526,6 +2703,11 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2580,6 +2762,17 @@ } } }, + "node_modules/node-abi": { + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.43.0.tgz", + "integrity": "sha512-QB0MMv+tn9Ur2DtJrc8y09n0n6sw88CyDniWSX2cHW10goQXYPK9ZpFJOktDS4ron501edPX6h9i7Pg+RnH5nQ==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -2801,11 +2994,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2854,89 +3042,6 @@ "node": ">=8" } }, - "node_modules/pg": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.0.tgz", - "integrity": "sha512-meLUVPn2TWgJyLmy7el3fQQVwft4gU5NGyvV0XbD41iU9Jbg8lCH4zexhIkihDzVHJStlt6r088G6/fWeNjhXA==", - "dependencies": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.6.0", - "pg-pool": "^3.6.0", - "pg-protocol": "^1.6.0", - "pg-types": "^2.1.0", - "pgpass": "1.x" - }, - "engines": { - "node": ">= 8.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.1.0" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.0.tgz", - "integrity": "sha512-tGM8/s6frwuAIyRcJ6nWcIvd3+3NmUKIs6OjviIm1HPPFEt5MzQDOTBQyhPWg/m0kCl95M6gA1JaIXtS8KovOA==", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.0.tgz", - "integrity": "sha512-x14ibktcwlHKoHxx9X3uTVW9zIGR41ZB6QNhHb21OPNdCCO3NaRnpJuwKIQSR4u+Yqjx4HCvy7Hh7VSy1U4dGg==" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.0.tgz", - "integrity": "sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", - "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "dependencies": { - "split2": "^4.1.0" - } - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -2976,39 +3081,29 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "dependencies": { - "xtend": "^4.0.0" + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, "node_modules/prelude-ls": { @@ -3029,6 +3124,15 @@ "react-is": "^16.13.1" } }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -3056,6 +3160,28 @@ } ] }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -3084,6 +3210,19 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", @@ -3278,6 +3417,25 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -3356,6 +3514,49 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3372,14 +3573,6 @@ "node": ">=0.10.0" } }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "engines": { - "node": ">= 10.x" - } - }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -3399,6 +3592,14 @@ "node": ">=10.0.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", @@ -3567,6 +3768,32 @@ "node": ">=6" } }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3629,6 +3856,17 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3706,6 +3944,11 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3781,14 +4024,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -3993,6 +4228,14 @@ "tslib": "^2.4.0" } }, + "@types/better-sqlite3": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.4.tgz", + "integrity": "sha512-dzrRZCYPXIXfSR1/surNbJ/grU3scTaygS0OMzjlGf71i9sc2fGyHPXXiXmEvNIoE0cGwsanEFMVJxPXmco9Eg==", + "requires": { + "@types/node": "*" + } + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -4215,11 +4458,43 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "better-sqlite3": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.4.0.tgz", + "integrity": "sha512-NmsNW1CQvqMszu/CFAJ3pLct6NEFlNfuGM6vw72KHkjOD1UDnL96XNN1BMQc1hiHo8vE2GbOWQYIpZ+YM5wrZw==", + "requires": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.0" + } + }, "big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==" }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", @@ -4245,10 +4520,14 @@ "fill-range": "^7.0.1" } }, - "buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } }, "bundle-name": { "version": "3.0.0", @@ -4294,6 +4573,11 @@ "supports-color": "^7.1.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "classnames": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", @@ -4358,6 +4642,14 @@ "ms": "2.1.2" } }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, "deep-equal": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", @@ -4383,6 +4675,11 @@ "which-typed-array": "^1.1.9" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4422,6 +4719,11 @@ "object-keys": "^1.1.1" } }, + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4443,6 +4745,14 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, "enhanced-resolve": { "version": "5.14.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz", @@ -4870,6 +5180,11 @@ "strip-final-newline": "^3.0.0" } }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4923,6 +5238,11 @@ "flat-cache": "^3.0.4" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4962,6 +5282,11 @@ "is-callable": "^1.1.3" } }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5021,6 +5346,11 @@ "resolve-pkg-maps": "^1.0.0" } }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -5138,6 +5468,11 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==" }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -5171,6 +5506,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -5509,6 +5849,11 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==" }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5522,6 +5867,11 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5532,6 +5882,11 @@ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5560,6 +5915,14 @@ "zod": "3.21.4" } }, + "node-abi": { + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.43.0.tgz", + "integrity": "sha512-QB0MMv+tn9Ur2DtJrc8y09n0n6sw88CyDniWSX2cHW10goQXYPK9ZpFJOktDS4ron501edPX6h9i7Pg+RnH5nQ==", + "requires": { + "semver": "^7.3.5" + } + }, "npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -5705,11 +6068,6 @@ "p-limit": "^3.0.2" } }, - "packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5743,68 +6101,6 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, - "pg": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.0.tgz", - "integrity": "sha512-meLUVPn2TWgJyLmy7el3fQQVwft4gU5NGyvV0XbD41iU9Jbg8lCH4zexhIkihDzVHJStlt6r088G6/fWeNjhXA==", - "requires": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-cloudflare": "^1.1.0", - "pg-connection-string": "^2.6.0", - "pg-pool": "^3.6.0", - "pg-protocol": "^1.6.0", - "pg-types": "^2.1.0", - "pgpass": "1.x" - } - }, - "pg-cloudflare": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.0.tgz", - "integrity": "sha512-tGM8/s6frwuAIyRcJ6nWcIvd3+3NmUKIs6OjviIm1HPPFEt5MzQDOTBQyhPWg/m0kCl95M6gA1JaIXtS8KovOA==", - "optional": true - }, - "pg-connection-string": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.0.tgz", - "integrity": "sha512-x14ibktcwlHKoHxx9X3uTVW9zIGR41ZB6QNhHb21OPNdCCO3NaRnpJuwKIQSR4u+Yqjx4HCvy7Hh7VSy1U4dGg==" - }, - "pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" - }, - "pg-pool": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.0.tgz", - "integrity": "sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==", - "requires": {} - }, - "pg-protocol": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", - "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" - }, - "pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "requires": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - } - }, - "pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "requires": { - "split2": "^4.1.0" - } - }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -5825,27 +6121,23 @@ "source-map-js": "^1.0.2" } }, - "postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" - }, - "postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" - }, - "postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" - }, - "postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "requires": { - "xtend": "^4.0.0" + "prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "requires": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" } }, "prelude-ls": { @@ -5863,6 +6155,15 @@ "react-is": "^16.13.1" } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -5873,6 +6174,24 @@ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + } + } + }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -5895,6 +6214,16 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", @@ -6013,6 +6342,11 @@ "queue-microtask": "^1.2.2" } }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, "safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -6073,6 +6407,21 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "requires": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -6083,11 +6432,6 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" }, - "split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" - }, "stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -6101,6 +6445,14 @@ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string.prototype.matchall": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", @@ -6204,6 +6556,29 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -6253,6 +6628,14 @@ } } }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6305,6 +6688,11 @@ "punycode": "^2.1.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6359,11 +6747,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/spectacles/package.json b/spectacles/package.json index 2ba1503..58348ba 100644 --- a/spectacles/package.json +++ b/spectacles/package.json @@ -9,15 +9,16 @@ "lint": "next lint" }, "dependencies": { + "@types/better-sqlite3": "^7.6.4", "@types/node": "20.2.5", "@types/react": "18.2.7", "@types/react-dom": "18.2.4", + "better-sqlite3": "^8.4.0", "classnames": "^2.3.2", "date-fns": "^2.30.0", "eslint": "8.41.0", "eslint-config-next": "13.4.4", "next": "13.4.4", - "pg": "^8.11.0", "react": "18.2.0", "react-dom": "18.2.0", "typescript": "5.0.4" diff --git a/spectacles/src/app/page.tsx b/spectacles/src/app/page.tsx index 4a1744c..afec409 100644 --- a/spectacles/src/app/page.tsx +++ b/spectacles/src/app/page.tsx @@ -2,7 +2,7 @@ import React from "react"; import { appBranches, Branch, DetectedBuild } from "@/models/build"; import { latestBuildOnBranch } from "@/db/build"; import BuildHeader from "@/components/BuildHeader"; -import pg from "@/db"; +import db from "@/db"; import WrappingBuildsList from "@/components/WrappingBuildsList"; import styles from "./page.module.css"; @@ -13,30 +13,28 @@ const dateFormatter = new Intl.DateTimeFormat(undefined, { }); export default async function Index() { - const [latestBuildsEntries, historicalBuildsRows] = await Promise.all([ - Promise.all( - appBranches.map((branch) => - latestBuildOnBranch(branch).then((result) => [branch, result]) - ) - ), - pg.query(` - SELECT branch, build_number, build_id, detected_at - FROM detections - WHERE detected_at > (current_timestamp - INTERVAL '7 days') - ORDER BY detected_at DESC - `), + let latestBuildsEntries = appBranches.map((branch) => [ + branch, + latestBuildOnBranch(branch), ]); + let historicalStatement = db.prepare(` + SELECT branch, build_number, build_id, detected_at + FROM detections + WHERE detected_at > strftime('%s', 'now', '-7 days') + ORDER BY detected_at DESC + `); + let historicalBuildsRows = historicalStatement.all() as any[]; // When will TypeScript properly support this :pensive: let latestBuilds = Object.fromEntries(latestBuildsEntries) as { [branch in Exclude]: DetectedBuild; }; - let historical: DetectedBuild[] = historicalBuildsRows.rows.map((row) => ({ + let historical: DetectedBuild[] = historicalBuildsRows.map((row) => ({ branch: row.branch, number: row.build_number, id: row.build_id, - detectedAt: row.detected_at, + detectedAt: new Date(row.detected_at * 1000), })); const latest = Object.fromEntries( diff --git a/spectacles/src/db/build.ts b/spectacles/src/db/build.ts index 0804ab1..a3c88e1 100644 --- a/spectacles/src/db/build.ts +++ b/spectacles/src/db/build.ts @@ -1,25 +1,20 @@ import type { Branch, DetectedBuild } from "@/models/build"; -import pg from "@/db"; +import db from "@/db"; -export async function latestBuildOnBranch( - branch: Branch -): Promise { - const result = await pg.query( - ` +export function latestBuildOnBranch(branch: Branch): DetectedBuild { + const statement = db.prepare(` SELECT build_id, branch, detected_at, build_number FROM detections - WHERE branch = $1::discord_branch + WHERE branch = $branch ORDER BY detected_at DESC LIMIT 1; - `, - [branch] - ); - const row = result.rows[0]; + `); + const row = statement.get({ branch }) as any; return { number: row.build_number, id: row.build_id, branch: row.branch, - detectedAt: row.detected_at, + detectedAt: new Date(row.detected_at * 1000), }; } diff --git a/spectacles/src/db/index.ts b/spectacles/src/db/index.ts index 29adf7b..fc8ecc3 100644 --- a/spectacles/src/db/index.ts +++ b/spectacles/src/db/index.ts @@ -1,24 +1,15 @@ import "server-only"; -import { Pool, Client } from "pg"; +import SQLite3 from "better-sqlite3"; -let pg: Pool | Client; +let db: SQLite3.Database; -const globalForPostgres = global as unknown as { - pg: Pool | Client | undefined; -}; - -if (process.env.NODE_ENV === "production") { - console.log("[database] Creating Postgres pool (production)."); - pg = new Pool(); - pg.connect(); -} else { - if (!globalForPostgres.pg) { - // Don't bother creating a connection pool, because it'll be recreated on - // every request: https://github.com/vercel/next.js/issues/44330 - globalForPostgres.pg = new Client(); - globalForPostgres.pg.connect(); - } - pg = globalForPostgres.pg; +const SQLITE_URL = process.env.SQLITE_URL; +if (SQLITE_URL == null) { + throw new Error("No $SQLITE_URL specified"); } -export default pg; +db = new SQLite3(SQLITE_URL); +db.pragma("foreign_keys = ON"); +db.pragma("journal_mode = WAL"); + +export default db; From 521f5935b763071a7a1913ba2f1816f639e8ab98 Mon Sep 17 00:00:00 2001 From: slice Date: Thu, 1 Jun 2023 12:48:20 -0700 Subject: [PATCH 4/4] web: add base build detail view Navigate to /build/ or /build/ to view details about a build. Some interesting information we show at the moment is every time this build was detected on a channel (as well as the build it replaced when it was detected), the build's hash, and its assets. --- .gitignore | 2 + .../fonts/concourse_index_regular.woff2 | Bin 0 -> 14032 bytes spectacles/src/app/build/[id]/page.module.css | 43 ++++++ spectacles/src/app/build/[id]/page.tsx | 137 ++++++++++++++++++ spectacles/src/app/layout.tsx | 14 +- spectacles/src/app/page.tsx | 26 +++- .../src/components/BuildHeader.module.css | 14 ++ spectacles/src/components/BuildHeader.tsx | 46 +++--- .../components/WrappingBuildsList.module.css | 2 + .../src/components/WrappingBuildsList.tsx | 6 +- spectacles/src/db/asset.ts | 22 +++ spectacles/src/db/build.ts | 70 ++++++++- spectacles/src/models/asset.ts | 11 ++ spectacles/src/styles/base.css | 18 ++- 14 files changed, 378 insertions(+), 33 deletions(-) create mode 100644 spectacles/public/fonts/concourse_index_regular.woff2 create mode 100644 spectacles/src/app/build/[id]/page.module.css create mode 100644 spectacles/src/app/build/[id]/page.tsx create mode 100644 spectacles/src/db/asset.ts create mode 100644 spectacles/src/models/asset.ts diff --git a/.gitignore b/.gitignore index 1a493ee..d27b1c0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ *.db *.db-shm *.db-wal +/*.csv +/*.sql node_modules .next diff --git a/spectacles/public/fonts/concourse_index_regular.woff2 b/spectacles/public/fonts/concourse_index_regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..1fafa5c332a449cc21847b529a81f4935f4406f5 GIT binary patch literal 14032 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 + {humanFriendlyBranchName(branch)} + + ); +} + +function DetectionList({ + previousBuilds, + detections, +}: { + previousBuilds: (Build | null)[]; + detections: Detection[]; +}) { + return ( +
    + {detections + .sort((a, b) => a.detectedAt.getTime() - b.detectedAt.getTime()) + .map((detection, index) => { + let previousBuild = previousBuilds[index]; + let date = + index === 0 + ? " on " + format(detection.detectedAt, "MMM d, yyyy") + : formatDistance( + detection.detectedAt, + detections[index - 1].detectedAt + ) + " after"; + + return ( +
  1. +
    + {date} +
    + {previousBuild && ( +
    + (from{" "} + + {previousBuild.number} + + ) +
    + )} +
  2. + ); + })} +
+ ); +} + +export default function BuildDetails({ params }: { params: { id: string } }) { + let build = fetchBuild( + /^\d+$/.test(params.id) + ? { number: parseInt(params.id, 10) } + : { id: params.id } + ); + + if (build == null) { + return notFound(); + } + + let latest = latestBuildIDs(); + + let appearingAsBranch: React.ComponentProps["branch"]; + if (latest.canary === build.id && latest.ptb === build.id) { + appearingAsBranch = "dual"; + } else { + appearingAsBranch = Object.entries(latest).find( + ([_, buildID]) => buildID == build!.id + )?.[0] as Branch; + } + + let detections = fetchDetections(build.id); + let previousBuilds = detections.map((detection) => + findPreviousBuild(detection.branch, detection.detectedAt) + ); + + let assets = fetchBuildAssets(build.id).filter((asset) => asset.surface); + + return ( + <> + 1} + detectedAt={detections[0]?.detectedAt} + /> +
+
+

Detections

+ + +

Hash

+

+ {build.id} +

+

Assets

+ +
+
+ + ); +} + +export const dynamic = "force-dynamic"; diff --git a/spectacles/src/app/layout.tsx b/spectacles/src/app/layout.tsx index 8fb4bc9..6831ac8 100644 --- a/spectacles/src/app/layout.tsx +++ b/spectacles/src/app/layout.tsx @@ -1,7 +1,9 @@ import { PT_Sans, PT_Sans_Caption, PT_Sans_Narrow } from "next/font/google"; import Link from "next/link"; +import localFont from "next/font/local"; import "@/styles/base.css"; import styles from "./layout.module.css"; +import classNames from "classnames"; const PTSans = PT_Sans({ subsets: ["latin"], @@ -18,6 +20,11 @@ const PTSansNarrow = PT_Sans_Narrow({ weight: ["400", "700"], variable: "--condensed-sans-serif", }); +const ConcourseIndex = localFont({ + src: "../../public/fonts/concourse_index_regular.woff2", + variable: "--concourse-index", + display: "swap", +}); const iconPath = process.env.NODE_ENV === "development" ? "/favicon_dev.png" : "/favicon.png"; @@ -35,7 +42,12 @@ export default function RootLayout({ return (
spectacles
diff --git a/spectacles/src/app/page.tsx b/spectacles/src/app/page.tsx index afec409..7369b68 100644 --- a/spectacles/src/app/page.tsx +++ b/spectacles/src/app/page.tsx @@ -66,17 +66,33 @@ export default async function Index() { Latest Builds {/*live*/} {latest.ptb.number === latest.canary.number ? ( - + ) : ( <> - - + + )} - +
-

Recent Builds

+

Last 7 Days of Builds

{calendarized.map(({ day, builds }) => ( diff --git a/spectacles/src/components/BuildHeader.module.css b/spectacles/src/components/BuildHeader.module.css index 2b9fa91..af838a5 100644 --- a/spectacles/src/components/BuildHeader.module.css +++ b/spectacles/src/components/BuildHeader.module.css @@ -17,6 +17,16 @@ color: hsl(var(--hue) 40% 75%); } +.buildPlain { + background-image: linear-gradient( + to bottom, + transparent, + hsl(0deg 0% 30% / 15%) + ); + border-top: solid 1px hsl(0deg 0% 30%); + border-bottom: solid 1px hsl(0deg 0% 30%); +} + .buildHeader.buildDual { background-image: linear-gradient( to bottom, @@ -82,3 +92,7 @@ .buildHeader .buildNumber { font-size: 80%; } + +.mergeWithNavigation { + margin-top: calc(-2rem - 1px); +} diff --git a/spectacles/src/components/BuildHeader.tsx b/spectacles/src/components/BuildHeader.tsx index b80691c..6e5e862 100644 --- a/spectacles/src/components/BuildHeader.tsx +++ b/spectacles/src/components/BuildHeader.tsx @@ -1,6 +1,6 @@ import classNames from "classnames"; import { formatDistance } from "date-fns"; -import { Branch, DetectedBuild, humanFriendlyBranchName } from "@/models/build"; +import { Branch, Build, humanFriendlyBranchName } from "@/models/build"; import styles from "./BuildHeader.module.css"; const dateFormatter = new Intl.DateTimeFormat(undefined, { @@ -11,10 +11,12 @@ const dateFormatter = new Intl.DateTimeFormat(undefined, { }); export default function BuildHeader(props: { - branch: Branch | "dual"; - build: DetectedBuild; + branch?: Branch | "dual"; + build: Build; + detectedAt?: Date; + mergeWithNavigation?: boolean; + multipleDetections?: boolean; }) { - const date = new Date(props.build.detectedAt); const branch: React.ReactNode = props.branch === "dual" ? ( <> @@ -28,32 +30,42 @@ export default function BuildHeader(props: { ) : ( humanFriendlyBranchName(props.branch as Branch) ); - const ago = formatDistance(date, new Date()); + + let ago; + let date; + if (props.detectedAt != null) { + date = new Date(props.detectedAt); + ago = formatDistance(date, new Date()); + } return (
- {branch}{" "} + {branch != null && {branch}}{" "} {props.build.number}
-
-
- detected {ago} ago -
-
- {dateFormatter.format(date)} + {props.detectedAt && ( +
+
+ {props.multipleDetections && "first "}detected{" "} + {ago} ago +
+
+ {dateFormatter.format(date)} +
-
+ )}
); diff --git a/spectacles/src/components/WrappingBuildsList.module.css b/spectacles/src/components/WrappingBuildsList.module.css index f3c67ab..645b6f7 100644 --- a/spectacles/src/components/WrappingBuildsList.module.css +++ b/spectacles/src/components/WrappingBuildsList.module.css @@ -11,6 +11,8 @@ opacity: 0.4; border-radius: 0.25rem; line-height: 1.2; + color: inherit; + text-decoration: none; } .wrappingBuildsList .build.buildCollapsed { diff --git a/spectacles/src/components/WrappingBuildsList.tsx b/spectacles/src/components/WrappingBuildsList.tsx index 118c2f2..003d159 100644 --- a/spectacles/src/components/WrappingBuildsList.tsx +++ b/spectacles/src/components/WrappingBuildsList.tsx @@ -2,6 +2,7 @@ import classNames from "classnames"; import { Branch, DetectedBuild } from "@/models/build"; import { collapseBranches } from "@/models/collapsing"; import styles from "./WrappingBuildsList.module.css"; +import Link from "next/link"; const timeFormatter = new Intl.DateTimeFormat(undefined, { timeStyle: "long", @@ -30,7 +31,8 @@ export default function WrappingBuildsList(props: { : latestVersion(build.branch) === build.number; return ( -
{build.number} -
+ ); })}
diff --git a/spectacles/src/db/asset.ts b/spectacles/src/db/asset.ts new file mode 100644 index 0000000..522630a --- /dev/null +++ b/spectacles/src/db/asset.ts @@ -0,0 +1,22 @@ +import db from "@/db"; +import { Asset } from "@/models/asset"; + +/** Fetch all assets associated with a particular build. */ +export function fetchBuildAssets(buildID: string): Asset[] { + let statement = db.prepare(` + SELECT * + FROM build_assets + JOIN assets ON assets.name = build_assets.asset_name + WHERE build_id = ? + `); + + return statement.all(buildID).map((unknown) => { + let row = unknown as any; + return { + name: row.name, + surface: row.surface === 1, + surfaceScriptType: row.surface_script_type, + scriptChunkId: row.script_chunk_id, + }; + }); +} diff --git a/spectacles/src/db/build.ts b/spectacles/src/db/build.ts index a3c88e1..a64e974 100644 --- a/spectacles/src/db/build.ts +++ b/spectacles/src/db/build.ts @@ -1,15 +1,16 @@ -import type { Branch, DetectedBuild } from "@/models/build"; +import type { Branch, Build, DetectedBuild } from "@/models/build"; import db from "@/db"; +/** Fetches the latest build on a branch. */ export function latestBuildOnBranch(branch: Branch): DetectedBuild { - const statement = db.prepare(` + let statement = db.prepare(` SELECT build_id, branch, detected_at, build_number FROM detections WHERE branch = $branch ORDER BY detected_at DESC LIMIT 1; `); - const row = statement.get({ branch }) as any; + let row = statement.get({ branch }) as any; return { number: row.build_number, @@ -18,3 +19,66 @@ export function latestBuildOnBranch(branch: Branch): DetectedBuild { detectedAt: new Date(row.detected_at * 1000), }; } + +/** Fetches the latest build IDs on each branch. */ +export function latestBuildIDs(): { [branch in Branch]?: string } { + let statement = db.prepare(` + SELECT DISTINCT + branch, + first_value(build_id) OVER ( + PARTITION BY branch + ORDER BY detected_at DESC + ) AS build_id + FROM build_deploys; + `); + let rows = statement.all() as any[]; + + return Object.fromEntries(rows.map((row) => [row.branch, row.build_id])); +} + +/** Finds the last build that was detected on a branch before a certain date. */ +export function findPreviousBuild(branch: Branch, before: Date): Build | null { + let statement = db.prepare(` + SELECT build_number, build_id + FROM detections + WHERE branch = ? AND detected_at < ? + ORDER BY detected_at DESC + LIMIT 1 + `); + let row = statement.get(branch, before.getTime() / 1000) as any; + + return row != null ? { id: row.build_id, number: row.build_number } : null; +} + +export interface Detection { + branch: Branch; + detectedAt: Date; +} + +/** Fetches all times a build was detected on a branch. */ +export function fetchDetections(buildID: string): Detection[] { + let statement = db.prepare(` + SELECT branch, detected_at + FROM build_deploys + WHERE build_id = ? + `); + let rows = statement.all(buildID) as any[]; + + return rows.map((row) => ({ + branch: row.branch, + detectedAt: new Date(row.detected_at * 1000), + })); +} + +/** Fetches a specific build by its ID or number. */ +export function fetchBuild( + by: { id: string } | { number: number } +): Build | null { + let primaryExpr = "number" in by ? "build_number = ?" : "build_id = ?"; + + let row = db + .prepare(`SELECT build_number, build_id FROM builds WHERE ${primaryExpr}`) + .get("number" in by ? by.number : by.id) as any; + + return row == null ? null : { id: row.build_id, number: row.build_number }; +} diff --git a/spectacles/src/models/asset.ts b/spectacles/src/models/asset.ts new file mode 100644 index 0000000..08f1fce --- /dev/null +++ b/spectacles/src/models/asset.ts @@ -0,0 +1,11 @@ +export interface Asset { + name: string; + surface: boolean; + surfaceScriptType: + | "chunkloader" + | "classes" + | "vendor" + | "entrypoint" + | undefined; + scriptChunkId: number | undefined; +} diff --git a/spectacles/src/styles/base.css b/spectacles/src/styles/base.css index bf1d028..c11a640 100644 --- a/spectacles/src/styles/base.css +++ b/spectacles/src/styles/base.css @@ -1,7 +1,7 @@ @layer base { :root { - --system-sans-serif: -apple-system, blinkmacsystemfont, 'Source Sans Pro', - 'Lato', 'Arimo', 'Droid Sans', 'Noto Sans', 'DejaVu Sans', 'Segoe UI', + --system-sans-serif: -apple-system, blinkmacsystemfont, "Source Sans Pro", + "Lato", "Arimo", "Droid Sans", "Noto Sans", "DejaVu Sans", "Segoe UI", system-ui, sans-serif; --c-page-background: #111; @@ -30,13 +30,21 @@ background-color: var(--c-page-background); color: var(--c-page-foreground); } - + + a { + color: hsl(280deg 50% 70%); + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + body { margin: 0; font-family: var(--sans-serif); } - h1, h2, h3, @@ -70,4 +78,4 @@ main { margin: 2rem; } -} \ No newline at end of file +}