diff --git a/apps/scripts/README.md b/apps/scripts/README.md new file mode 100644 index 0000000..15aabeb --- /dev/null +++ b/apps/scripts/README.md @@ -0,0 +1,112 @@ +# grants-stack-indexer: scripts + +This package contains scripts for managing the database schema and migrations. + +## Available Scripts + +| Script | Description | +| ------------------- | --------------------------------------- | +| `script:db:migrate` | Runs all pending database migrations | +| `script:db:reset` | Drops and recreates the database schema | + +## Environment Setup + +1. Create a `.env` file in the `apps/scripts` directory: + +```env +# Database connection URL +DATABASE_URL=postgresql://user:password@localhost:5432/mydb + +# Schema name to manage +DATABASE_SCHEMA=grants_stack +``` + +### Environment Variables + +| Variable | Description | Example | +| ----------------- | ------------------------- | ------------------------------------------------ | +| `DATABASE_URL` | PostgreSQL connection URL | `postgresql://user:password@localhost:5432/mydb` | +| `DATABASE_SCHEMA` | Database schema name | `grants_stack` | + +## Usage + +First, install dependencies: + +```bash +pnpm install +``` + +### Running Migrations + +To apply all pending migrations: + +```bash +pnpm script:db:migrate +``` + +This will: + +1. Load environment variables +2. Connect to the database +3. Create the schema if it doesn't exist +4. Run any pending migrations +5. Log the results + +### Resetting the Database + +To completely reset the database schema: + +```bash +pnpm script:db:reset +``` + +**Warning**: This will: + +1. Drop the existing schema and all its data +2. Recreate an empty schema +3. You'll need to run migrations again after reset + +## Development + +### Adding New Migrations + +1. Create a new migration file in [`packages/repository/src/migrations`](../../packages//repository//migrations) +2. Name it using the format: `YYYYMMDDTHHmmss_description.ts` +3. Implement the `up` and `down` functions +4. Run `pnpm script:db:migrate` to apply the new migration + +Example migration file: + +```typescript +import { Kysely } from "kysely"; + +export async function up(db: Kysely): Promise { + // Your migration code here +} + +export async function down(db: Kysely): Promise { + // Code to reverse the migration +} +``` + +## Troubleshooting + +### Common Issues + +1. **Connection Error** + + - Check if PostgreSQL is running + - Verify DATABASE_URL is correct + - Ensure the database exists + +2. **Permission Error** + + - Verify user has necessary permissions + - Check schema ownership + +3. **Migration Failed** + - Check migration logs + - Ensure no conflicting changes + - Verify schema consistency + +TODO: add E2E tests for the scripts diff --git a/apps/scripts/package.json b/apps/scripts/package.json new file mode 100644 index 0000000..bbb77c5 --- /dev/null +++ b/apps/scripts/package.json @@ -0,0 +1,36 @@ +{ + "name": "@grants-stack-indexer/scripts", + "version": "0.0.1", + "private": true, + "description": "", + "license": "MIT", + "author": "Wonderland", + "type": "module", + "directories": { + "src": "src" + }, + "files": [ + "package.json" + ], + "scripts": { + "build": "tsc -p tsconfig.build.json", + "check-types": "tsc --noEmit -p ./tsconfig.json", + "clean": "rm -rf dist/", + "format": "prettier --check \"{src,test}/**/*.{js,ts,json}\"", + "format:fix": "prettier --write \"{src,test}/**/*.{js,ts,json}\"", + "lint": "eslint \"{src,test}/**/*.{js,ts,json}\"", + "lint:fix": "pnpm lint --fix", + "script:db:migrate": "tsx src/migrateDb.script.ts", + "script:db:reset": "tsx src/resetDb.script.ts", + "test": "vitest run --config vitest.config.ts --passWithNoTests", + "test:cov": "vitest run --config vitest.config.ts --coverage --passWithNoTests" + }, + "dependencies": { + "@grants-stack-indexer/repository": "workspace:*", + "dotenv": "16.4.5", + "zod": "3.23.8" + }, + "devDependencies": { + "tsx": "4.19.2" + } +} diff --git a/apps/scripts/src/migrateDb.script.ts b/apps/scripts/src/migrateDb.script.ts new file mode 100644 index 0000000..9c413bc --- /dev/null +++ b/apps/scripts/src/migrateDb.script.ts @@ -0,0 +1,69 @@ +import { configDotenv } from "dotenv"; + +import { createKyselyDatabase, migrateToLatest } from "@grants-stack-indexer/repository"; + +import { getDatabaseConfigFromEnv } from "./schemas/index.js"; + +configDotenv(); + +/** + * This script handles database migrations for the grants-stack-indexer project. + * + * It performs the following steps: + * 1. Loads environment variables from .env file + * 2. Gets database configuration (URL and schema name) from environment + * 3. Creates a Kysely database connection with the specified schema + * 4. Runs any pending migrations from packages/repository/migrations + * 5. Reports success/failure of migrations + * 6. Closes database connection and exits + * + * Environment variables required: + * - DATABASE_URL: PostgreSQL connection string + * - DATABASE_SCHEMA: Schema name to migrate (e.g. "grants_stack") + * + * The script will: + * - Create the schema if it doesn't exist + * - Run all pending migrations in order + * - Log results of each migration + * - Exit with code 0 on success, 1 on failure + */ + +export const main = async (): Promise => { + const { DATABASE_URL, DATABASE_SCHEMA } = getDatabaseConfigFromEnv(); + + const db = createKyselyDatabase({ + connectionString: DATABASE_URL, + withSchema: DATABASE_SCHEMA, + }); + + console.log(`Migrating database schema '${DATABASE_SCHEMA}'...`); + + const migrationResults = await migrateToLatest({ + db, + schema: DATABASE_SCHEMA, + }); + + if (migrationResults && migrationResults?.length > 0) { + const failedMigrations = migrationResults.filter( + (migrationResult) => migrationResult.status === "Error", + ); + + if (failedMigrations.length > 0) { + console.error("❌ Failed migrations:", failedMigrations); + throw new Error("Failed migrations"); + } + + console.log(`✅ Migrations applied successfully`); + } else { + console.log("No migrations to apply"); + } + + await db.destroy(); + + process.exit(0); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/apps/scripts/src/resetDb.script.ts b/apps/scripts/src/resetDb.script.ts new file mode 100644 index 0000000..37450f2 --- /dev/null +++ b/apps/scripts/src/resetDb.script.ts @@ -0,0 +1,70 @@ +import { configDotenv } from "dotenv"; + +import { createKyselyDatabase, resetDatabase } from "@grants-stack-indexer/repository"; + +import { getDatabaseConfigFromEnv } from "./schemas/index.js"; + +configDotenv(); + +/** + * This script handles database reset for the grants-stack-indexer project. + * + * It performs the following steps: + * 1. Loads environment variables from .env file + * 2. Gets database configuration (URL and schema name) from environment + * 3. Creates a Kysely database connection with the specified schema + * 4. Drops and recreates the database schema + * 5. Reports success/failure of reset operation + * 6. Closes database connection and exits + * + * Environment variables required: + * - DATABASE_URL: PostgreSQL connection string + * - DATABASE_SCHEMA: Schema name to reset (e.g. "grants_stack") + * + * The script will: + * - Drop the schema if it exists + * - Recreate an empty schema + * - Log results of the reset operation + * - Exit with code 0 on success, 1 on failure + * + * WARNING: This is a destructive operation that will delete all data in the schema. + * Make sure you have backups if needed before running this script. + */ + +const main = async (): Promise => { + const { DATABASE_URL, DATABASE_SCHEMA } = getDatabaseConfigFromEnv(); + + const db = createKyselyDatabase({ + connectionString: DATABASE_URL, + withSchema: DATABASE_SCHEMA, + }); + + console.log(`Resetting database schema '${DATABASE_SCHEMA}'...`); + + const resetResults = await resetDatabase({ + db, + schema: DATABASE_SCHEMA, + }); + + if (resetResults && resetResults?.length > 0) { + const failedResets = resetResults.filter((resetResult) => resetResult.status === "Error"); + + if (failedResets.length > 0) { + console.error("❌ Failed resets:", failedResets); + throw new Error("Failed resets"); + } + + console.log(`✅ Reset applied successfully`); + } else { + console.log("No resets to apply"); + } + + await db.destroy(); + + process.exit(0); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/apps/scripts/src/schemas/index.ts b/apps/scripts/src/schemas/index.ts new file mode 100644 index 0000000..a18141a --- /dev/null +++ b/apps/scripts/src/schemas/index.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; + +const dbEnvSchema = z.object({ + DATABASE_URL: z.string().url(), + DATABASE_SCHEMA: z.string().min(1), +}); + +export type DbEnvConfig = z.infer; + +export function getDatabaseConfigFromEnv(): DbEnvConfig { + const result = dbEnvSchema.safeParse(process.env); + + if (!result.success) { + console.error("❌ Invalid environment variables:", result.error.format()); + throw new Error("Invalid environment variables"); + } + + return result.data; +} diff --git a/apps/scripts/tsconfig.build.json b/apps/scripts/tsconfig.build.json new file mode 100644 index 0000000..32768e3 --- /dev/null +++ b/apps/scripts/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "composite": true, + "declarationMap": true, + "declaration": true, + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test"] +} diff --git a/apps/scripts/tsconfig.json b/apps/scripts/tsconfig.json new file mode 100644 index 0000000..21c1c5b --- /dev/null +++ b/apps/scripts/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*", "test/**/*"] +} diff --git a/apps/scripts/vitest.config.ts b/apps/scripts/vitest.config.ts new file mode 100644 index 0000000..fcafa05 --- /dev/null +++ b/apps/scripts/vitest.config.ts @@ -0,0 +1,22 @@ +import path from "path"; +import { configDefaults, defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, // Use Vitest's global API without importing it in each file + environment: "node", // Use the Node.js environment + include: ["test/**/*.spec.ts"], // Include test files + exclude: ["node_modules", "dist"], // Exclude certain directories + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], // Coverage reporters + exclude: ["node_modules", "dist", ...configDefaults.exclude], // Files to exclude from coverage + }, + }, + resolve: { + alias: { + // Setup path alias based on tsconfig paths + "@": path.resolve(__dirname, "src"), + }, + }, +}); diff --git a/package.json b/package.json index 6b53b91..a0ecea6 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", "prepare": "husky", + "script:db:migrate": "pnpm run --filter @grants-stack-indexer/scripts script:db:migrate", + "script:db:reset": "pnpm run --filter @grants-stack-indexer/scripts script:db:reset", "start": "turbo run start", "test": "turbo run test", "test:cov": "turbo run test:cov", diff --git a/packages/repository/src/db/connection.ts b/packages/repository/src/db/connection.ts index 6f2cd51..6c9e0a6 100644 --- a/packages/repository/src/db/connection.ts +++ b/packages/repository/src/db/connection.ts @@ -1,5 +1,5 @@ -import { CamelCasePlugin, ColumnType, Kysely, PostgresDialect } from "kysely"; -import { Pool, PoolConfig } from "pg"; +import { CamelCasePlugin, ColumnType, Kysely, PostgresDialect, WithSchemaPlugin } from "kysely"; +import pg from "pg"; import { Application, @@ -12,8 +12,11 @@ import { StatusSnapshot, } from "../internal.js"; -export interface DatabaseConfig extends PoolConfig { +const { Pool } = pg; + +export interface DatabaseConfig extends pg.PoolConfig { connectionString: string; + withSchema?: string; } type ApplicationTable = Omit & { @@ -38,10 +41,15 @@ export interface Database { * Creates and configures a Kysely database instance for PostgreSQL. * * @param config - The database configuration object extending PoolConfig. + * @param config.connectionString - The connection string for the database. + * @param config.withSchema - The schema to use for the database. Defaults to `public`. * @returns A configured Kysely instance for the Database. * * This function sets up a PostgreSQL database connection using Kysely ORM. * + * It uses the `CamelCasePlugin` to convert all table names to camel case. + * It uses the `WithSchemaPlugin` to automatically prefix all table names with the schema name on queries. + * * @example * const dbConfig: DatabaseConfig = { * connectionString: 'postgresql://user:password@localhost:5432/mydb' @@ -59,5 +67,10 @@ export const createKyselyPostgresDb = (config: DatabaseConfig): Kysely }), }); - return new Kysely({ dialect, plugins: [new CamelCasePlugin()] }); + const withSchema = config.withSchema ?? "public"; + + return new Kysely({ + dialect, + plugins: [new CamelCasePlugin(), new WithSchemaPlugin(withSchema)], + }); }; diff --git a/packages/repository/src/db/helpers.ts b/packages/repository/src/db/helpers.ts new file mode 100644 index 0000000..119bea6 --- /dev/null +++ b/packages/repository/src/db/helpers.ts @@ -0,0 +1,13 @@ +import { SchemaModule } from "kysely"; + +/** + * Since WithSchemaPlugin doesn't work with `sql.table`, we need to get the schema name manually. + * ref: https://github.com/kysely-org/kysely/issues/761 + */ +export const getSchemaName = (schema: SchemaModule): string => { + let name = "public"; + schema.createTable("test").$call((b) => { + name = b.toOperationNode().table.table.schema?.name ?? "public"; + }); + return name; +}; diff --git a/packages/repository/src/db/index.ts b/packages/repository/src/db/index.ts new file mode 100644 index 0000000..f5909da --- /dev/null +++ b/packages/repository/src/db/index.ts @@ -0,0 +1,2 @@ +export * from "./connection.js"; +export * from "./provider.js"; diff --git a/packages/repository/src/db/provider.ts b/packages/repository/src/db/provider.ts new file mode 100644 index 0000000..0ecc705 --- /dev/null +++ b/packages/repository/src/db/provider.ts @@ -0,0 +1,89 @@ +import { promises as fs } from "fs"; +import * as path from "path"; +import { FileMigrationProvider, Kysely, MigrationResult, Migrator, NO_MIGRATIONS } from "kysely"; + +import { Database } from "./connection.js"; + +export interface MigrationConfig { + db: Kysely; + schema: string; +} + +/** + * Applies all migrations to the database up to the latest version. + * + * @param config - The migration configuration. + * @param config.db - The Kysely database instance. + * @param config.schema - The schema to use for the migrations. Should be the same as the schema used in the Kysely database instance. + * @returns The migration results. + */ +export async function migrateToLatest( + config: MigrationConfig, +): Promise { + await config.db.schema.createSchema(config.schema).ifNotExists().execute(); + + const migrator = new Migrator({ + db: config.db, + provider: new FileMigrationProvider({ + fs, + path, + migrationFolder: path.join( + path.dirname(new URL(import.meta.url).pathname), + "../migrations", + ), + }), + migrationTableSchema: config.schema, + }); + + const { error, results } = await migrator.migrateToLatest(); + + results?.forEach((it) => { + if (it.status === "Success") { + console.log(`migration "${it.migrationName}" was executed successfully`); + } else if (it.status === "Error") { + console.error(`failed to execute migration "${it.migrationName}"`); + } + }); + + if (error) { + console.error("failed to migrate"); + console.error(error); + throw error; + } + + return results; +} + +/** + * Resets the database by rolling back all migrations. + * + * @param config - The migration configuration. + * @param config.db - The Kysely database instance. + * @param config.schema - The schema to use for the migrations. Should be the same as the schema used in the Kysely database instance. + * @returns The migration results. + */ +export async function resetDatabase( + config: MigrationConfig, +): Promise { + const migrator = new Migrator({ + db: config.db, + provider: new FileMigrationProvider({ + fs, + path, + migrationFolder: path.join( + path.dirname(new URL(import.meta.url).pathname), + "../migrations", + ), + }), + }); + + const { error, results } = await migrator.migrateTo(NO_MIGRATIONS); + + if (error) { + console.error("failed to reset database"); + console.error(error); + throw error; + } + + return results; +} diff --git a/packages/repository/src/external.ts b/packages/repository/src/external.ts index c3cc4d8..a7d4553 100644 --- a/packages/repository/src/external.ts +++ b/packages/repository/src/external.ts @@ -49,3 +49,6 @@ export { } from "./repositories/kysely/index.js"; export { createKyselyPostgresDb as createKyselyDatabase } from "./internal.js"; + +export { migrateToLatest, resetDatabase } from "./db/index.js"; +export type { MigrationConfig } from "./db/index.js"; diff --git a/packages/repository/src/internal.ts b/packages/repository/src/internal.ts index 74974b2..c27844e 100644 --- a/packages/repository/src/internal.ts +++ b/packages/repository/src/internal.ts @@ -2,3 +2,4 @@ export * from "./types/index.js"; export * from "./interfaces/index.js"; export * from "./db/connection.js"; export * from "./repositories/kysely/index.js"; +export * from "./db/helpers.js"; diff --git a/packages/repository/src/migrations/20241029T120000_initial.ts b/packages/repository/src/migrations/20241029T120000_initial.ts new file mode 100644 index 0000000..2c18c91 --- /dev/null +++ b/packages/repository/src/migrations/20241029T120000_initial.ts @@ -0,0 +1,310 @@ +import { Kysely, sql } from "kysely"; + +import { getSchemaName } from "../db/helpers.js"; + +/** + * The up function is called when you update your database schema to the next version and down when you go back to previous version. + * The only argument for the functions is an instance of Kysely. It's important to use Kysely and not Kysely. + * ref: https://kysely.dev/docs/migrations#migration-files + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function up(db: Kysely): Promise { + const BIGINT_TYPE = sql`decimal(78,0)`; + const ADDRESS_TYPE = "text"; + const CHAIN_ID_TYPE = "integer"; + const PENDING_ROLE_TYPE = "text"; + const CURRENCY_TYPE = sql`numeric(18,2)`; + + const schema = getSchemaName(db.schema); + await db.schema.createType("project_type").asEnum(["canonical", "linked"]).execute(); + + console.log("schema", schema); + + await db.schema + .createTable("projects") + .addColumn("id", "text") + .addColumn("name", "text") + .addColumn("nonce", BIGINT_TYPE) + .addColumn("anchorAddress", ADDRESS_TYPE) + .addColumn("chainId", CHAIN_ID_TYPE) + .addColumn("projectNumber", "integer") + .addColumn("registryAddress", ADDRESS_TYPE) + .addColumn("metadataCid", "text") + .addColumn("metadata", "jsonb") + .addColumn("createdByAddress", ADDRESS_TYPE) + .addColumn("createdAtBlock", BIGINT_TYPE) + .addColumn("updatedAtBlock", BIGINT_TYPE) + .addColumn("tags", sql`text[]`) + .addColumn("projectType", sql.table(`${schema}.project_type`)) + + .addPrimaryKeyConstraint("projects_pkey", ["id", "chainId"]) + .execute(); + + await db.schema + .createIndex("idx_projects_metadata_hash") + .on("projects") + .expression(sql`md5(metadata::text)`) + .where(sql.ref("metadata"), "is not", null) + .execute(); + + await db.schema + .createTable("pending_project_roles") + .addColumn("id", "serial", (col) => col.primaryKey()) + .addColumn("chainId", CHAIN_ID_TYPE) + .addColumn("role", PENDING_ROLE_TYPE) + .addColumn("address", ADDRESS_TYPE) + .addColumn("createdAtBlock", BIGINT_TYPE) + .execute(); + + await db.schema.createType("project_role_name").asEnum(["owner", "member"]).execute(); + + await db.schema + .createTable("project_roles") + .addColumn("chainId", CHAIN_ID_TYPE) + .addColumn("projectId", "text") + .addColumn("address", ADDRESS_TYPE) + .addColumn("role", sql.table(`${schema}.project_role_name`)) + .addColumn("createdAtBlock", BIGINT_TYPE) + .addPrimaryKeyConstraint("project_roles_pkey", ["chainId", "projectId", "address", "role"]) + .addForeignKeyConstraint( + "project_roles_projects_fkey", + ["chainId", "projectId"], + "projects", + ["chainId", "id"], + ) + .execute(); + + await db.schema + .createTable("rounds") + .addColumn("id", "text") + .addColumn("chainId", CHAIN_ID_TYPE) + + .addColumn("tags", sql`text[]`) + + .addColumn("matchAmount", BIGINT_TYPE) + .addColumn("matchTokenAddress", ADDRESS_TYPE) + .addColumn("matchAmountInUsd", CURRENCY_TYPE) + + .addColumn("fundedAmount", BIGINT_TYPE, (col) => col.defaultTo("0")) + .addColumn("fundedAmountInUsd", CURRENCY_TYPE, (col) => col.defaultTo("0")) + + .addColumn("applicationMetadataCid", "text") + .addColumn("applicationMetadata", "jsonb") + .addColumn("roundMetadataCid", "text") + .addColumn("roundMetadata", "jsonb") + + .addColumn("applicationsStartTime", "timestamptz") + .addColumn("applicationsEndTime", "timestamptz") + .addColumn("donationsStartTime", "timestamptz") + .addColumn("donationsEndTime", "timestamptz") + + .addColumn("createdByAddress", ADDRESS_TYPE) + .addColumn("createdAtBlock", BIGINT_TYPE) + .addColumn("updatedAtBlock", BIGINT_TYPE) + + // POOL_MANAGER_ROLE = bytes32(poolId); + .addColumn("managerRole", "text") + // POOL_ADMIN_ROLE = keccak256(abi.encodePacked(poolId, "admin")); + .addColumn("adminRole", "text") + + .addColumn("strategyAddress", "text") + .addColumn("strategyId", "text") + .addColumn("strategyName", "text") + + .addColumn("matchingDistribution", "jsonb") + .addColumn("readyForPayoutTransaction", "text") + + .addColumn("projectId", "text") + + .addForeignKeyConstraint("rounds_projects_fkey", ["chainId", "projectId"], "projects", [ + "chainId", + "id", + ]) + + // aggregates + + .addColumn("totalAmountDonatedInUsd", CURRENCY_TYPE) + .addColumn("totalDonationsCount", "integer") + .addColumn("uniqueDonorsCount", "integer") + .addColumn("totalDistributed", BIGINT_TYPE, (col) => col.defaultTo("0")) + + .addPrimaryKeyConstraint("rounds_pkey", ["id", "chainId"]) + .execute(); + + await db.schema + .createIndex("idx_rounds_manager_role") + .on("rounds") + .columns(["managerRole"]) + .execute(); + + await db.schema + .createIndex("idx_rounds_admin_role") + .on("rounds") + .columns(["adminRole"]) + .execute(); + + await db.schema + .createIndex("idx_rounds_round_metadata_not_null") + .on("rounds") + .expression(sql`md5(round_metadata::text)`) + .where(sql.ref("round_metadata"), "is not", null) + .execute(); + + await db.schema + .createTable("pending_round_roles") + .addColumn("id", "serial", (col) => col.primaryKey()) + .addColumn("chainId", CHAIN_ID_TYPE) + .addColumn("role", PENDING_ROLE_TYPE) + .addColumn("address", ADDRESS_TYPE) + .addColumn("createdAtBlock", BIGINT_TYPE) + .execute(); + + await db.schema.createType("round_role_name").asEnum(["admin", "manager"]).execute(); + + await db.schema + .createTable("round_roles") + .addColumn("chainId", CHAIN_ID_TYPE) + .addColumn("roundId", "text") + .addColumn("address", ADDRESS_TYPE) + .addColumn("role", sql.table(`${schema}.round_role_name`)) + .addColumn("createdAtBlock", BIGINT_TYPE) + .addPrimaryKeyConstraint("round_roles_pkey", ["chainId", "roundId", "address", "role"]) + .addForeignKeyConstraint("round_roles_rounds_fkey", ["chainId", "roundId"], "rounds", [ + "chainId", + "id", + ]) + .execute(); + + await db.schema + .createType("application_status") + .asEnum(["PENDING", "APPROVED", "REJECTED", "CANCELLED", "IN_REVIEW"]) + .execute(); + + await db.schema + .createTable("applications") + .addColumn("id", "text") + .addColumn("chainId", CHAIN_ID_TYPE) + .addColumn("roundId", ADDRESS_TYPE) + .addColumn("projectId", "text") + .addColumn("anchorAddress", ADDRESS_TYPE) + .addColumn("status", sql.table(`${schema}.application_status`)) + .addColumn("statusSnapshots", "jsonb") + .addColumn("distributionTransaction", "text") + + .addColumn("metadataCid", "text") + .addColumn("metadata", "jsonb") + + .addColumn("createdByAddress", ADDRESS_TYPE) + .addColumn("createdAtBlock", BIGINT_TYPE) + .addColumn("statusUpdatedAtBlock", BIGINT_TYPE) + + // aggregates + .addColumn("totalDonationsCount", "integer") + .addColumn("totalAmountDonatedInUsd", CURRENCY_TYPE) + .addColumn("uniqueDonorsCount", "integer") + + .addColumn("tags", sql`text[]`) + + .addPrimaryKeyConstraint("applications_pkey", ["chainId", "roundId", "id"]) + .addForeignKeyConstraint( + "applications_rounds_fkey", + ["roundId", "chainId"], + "rounds", + ["id", "chainId"], + (cb) => cb.onDelete("cascade"), + ) + + .execute(); + + await db.schema + .createTable("applications_payouts") + .addColumn("id", "serial", (col) => col.primaryKey()) + .addColumn("chainId", CHAIN_ID_TYPE) + .addColumn("applicationId", "text") + .addColumn("roundId", "text") + .addColumn("amount", BIGINT_TYPE) + .addColumn("tokenAddress", ADDRESS_TYPE) + .addColumn("amountInUsd", CURRENCY_TYPE) + .addColumn("amountInRoundMatchToken", "text") + .addColumn("transactionHash", "text") + .addColumn("timestamp", "timestamptz") + .addColumn("sender", ADDRESS_TYPE) + .addForeignKeyConstraint( + "applications_payouts_applications_fkey", + ["chainId", "roundId", "applicationId"], + "applications", + ["chainId", "roundId", "id"], + (cb) => cb.onDelete("cascade"), + ) + .execute(); + + await db.schema + .createTable("donations") + + .addColumn("id", "text") + .addColumn("chainId", CHAIN_ID_TYPE) + .addColumn("roundId", ADDRESS_TYPE) + .addColumn("applicationId", "text") + .addColumn("donorAddress", ADDRESS_TYPE) + .addColumn("recipientAddress", ADDRESS_TYPE) + .addColumn("projectId", "text") + .addColumn("transactionHash", "text") + .addColumn("blockNumber", BIGINT_TYPE) + .addColumn("tokenAddress", ADDRESS_TYPE) + + .addColumn("timestamp", "timestamptz") + + .addColumn("amount", BIGINT_TYPE) + .addColumn("amountInUsd", CURRENCY_TYPE) + .addColumn("amountInRoundMatchToken", BIGINT_TYPE) + + .addPrimaryKeyConstraint("donations_pkey", ["id"]) + + .execute(); + + await db.schema + .createIndex("idx_donations_donor_chain") + .on("donations") + .columns(["donorAddress"]) + .execute(); + + await db.schema + .createIndex("idx_donations_chain_round") + .on("donations") + .columns(["chainId", "roundId"]) + .execute(); + + await db.schema + .createIndex("idx_donations_chain_round_app") + .on("donations") + .columns(["chainId", "roundId", "applicationId"]) + .execute(); + + await db.schema + .createTable("legacy_projects") + .addColumn("id", "serial", (col) => col.primaryKey()) + .addColumn("v1ProjectId", "text") + .addColumn("v2ProjectId", "text") + .addUniqueConstraint("unique_v1ProjectId", ["v1ProjectId"]) + .addUniqueConstraint("unique_v2ProjectId", ["v2ProjectId"]) + .execute(); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function down(db: Kysely): Promise { + // Drop everything in reverse order + await db.schema.dropTable("legacy_projects").execute(); + await db.schema.dropTable("donations").execute(); + await db.schema.dropTable("applications_payouts").execute(); + await db.schema.dropTable("applications").execute(); + await db.schema.dropType("application_status").execute(); + await db.schema.dropTable("round_roles").execute(); + await db.schema.dropType("round_role_name").execute(); + await db.schema.dropTable("pending_round_roles").execute(); + await db.schema.dropTable("rounds").execute(); + await db.schema.dropTable("project_roles").execute(); + await db.schema.dropType("project_role_name").execute(); + await db.schema.dropTable("pending_project_roles").execute(); + await db.schema.dropTable("projects").execute(); + await db.schema.dropType("project_type").execute(); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8619cb0..9794387 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,6 +96,22 @@ importers: specifier: 5.2.2 version: 5.2.2 + apps/scripts: + dependencies: + "@grants-stack-indexer/repository": + specifier: workspace:* + version: link:../../packages/repository + dotenv: + specifier: 16.4.5 + version: 16.4.5 + zod: + specifier: 3.23.8 + version: 3.23.8 + devDependencies: + tsx: + specifier: 4.19.2 + version: 4.19.2 + packages/chain-providers: dependencies: "@grants-stack-indexer/shared": @@ -522,6 +538,15 @@ packages: cpu: [ppc64] os: [aix] + "@esbuild/aix-ppc64@0.23.1": + resolution: + { + integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [aix] + "@esbuild/android-arm64@0.21.5": resolution: { @@ -531,6 +556,15 @@ packages: cpu: [arm64] os: [android] + "@esbuild/android-arm64@0.23.1": + resolution: + { + integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [android] + "@esbuild/android-arm@0.21.5": resolution: { @@ -540,6 +574,15 @@ packages: cpu: [arm] os: [android] + "@esbuild/android-arm@0.23.1": + resolution: + { + integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [android] + "@esbuild/android-x64@0.21.5": resolution: { @@ -549,6 +592,15 @@ packages: cpu: [x64] os: [android] + "@esbuild/android-x64@0.23.1": + resolution: + { + integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [android] + "@esbuild/darwin-arm64@0.21.5": resolution: { @@ -558,6 +610,15 @@ packages: cpu: [arm64] os: [darwin] + "@esbuild/darwin-arm64@0.23.1": + resolution: + { + integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + "@esbuild/darwin-x64@0.21.5": resolution: { @@ -567,6 +628,15 @@ packages: cpu: [x64] os: [darwin] + "@esbuild/darwin-x64@0.23.1": + resolution: + { + integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + "@esbuild/freebsd-arm64@0.21.5": resolution: { @@ -576,6 +646,15 @@ packages: cpu: [arm64] os: [freebsd] + "@esbuild/freebsd-arm64@0.23.1": + resolution: + { + integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [freebsd] + "@esbuild/freebsd-x64@0.21.5": resolution: { @@ -585,6 +664,15 @@ packages: cpu: [x64] os: [freebsd] + "@esbuild/freebsd-x64@0.23.1": + resolution: + { + integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [freebsd] + "@esbuild/linux-arm64@0.21.5": resolution: { @@ -594,6 +682,15 @@ packages: cpu: [arm64] os: [linux] + "@esbuild/linux-arm64@0.23.1": + resolution: + { + integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + "@esbuild/linux-arm@0.21.5": resolution: { @@ -603,6 +700,15 @@ packages: cpu: [arm] os: [linux] + "@esbuild/linux-arm@0.23.1": + resolution: + { + integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [linux] + "@esbuild/linux-ia32@0.21.5": resolution: { @@ -612,6 +718,15 @@ packages: cpu: [ia32] os: [linux] + "@esbuild/linux-ia32@0.23.1": + resolution: + { + integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [linux] + "@esbuild/linux-loong64@0.21.5": resolution: { @@ -621,6 +736,15 @@ packages: cpu: [loong64] os: [linux] + "@esbuild/linux-loong64@0.23.1": + resolution: + { + integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==, + } + engines: { node: ">=18" } + cpu: [loong64] + os: [linux] + "@esbuild/linux-mips64el@0.21.5": resolution: { @@ -630,6 +754,15 @@ packages: cpu: [mips64el] os: [linux] + "@esbuild/linux-mips64el@0.23.1": + resolution: + { + integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==, + } + engines: { node: ">=18" } + cpu: [mips64el] + os: [linux] + "@esbuild/linux-ppc64@0.21.5": resolution: { @@ -639,6 +772,15 @@ packages: cpu: [ppc64] os: [linux] + "@esbuild/linux-ppc64@0.23.1": + resolution: + { + integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [linux] + "@esbuild/linux-riscv64@0.21.5": resolution: { @@ -648,6 +790,15 @@ packages: cpu: [riscv64] os: [linux] + "@esbuild/linux-riscv64@0.23.1": + resolution: + { + integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==, + } + engines: { node: ">=18" } + cpu: [riscv64] + os: [linux] + "@esbuild/linux-s390x@0.21.5": resolution: { @@ -657,6 +808,15 @@ packages: cpu: [s390x] os: [linux] + "@esbuild/linux-s390x@0.23.1": + resolution: + { + integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==, + } + engines: { node: ">=18" } + cpu: [s390x] + os: [linux] + "@esbuild/linux-x64@0.21.5": resolution: { @@ -666,6 +826,15 @@ packages: cpu: [x64] os: [linux] + "@esbuild/linux-x64@0.23.1": + resolution: + { + integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + "@esbuild/netbsd-x64@0.21.5": resolution: { @@ -675,6 +844,24 @@ packages: cpu: [x64] os: [netbsd] + "@esbuild/netbsd-x64@0.23.1": + resolution: + { + integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [netbsd] + + "@esbuild/openbsd-arm64@0.23.1": + resolution: + { + integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openbsd] + "@esbuild/openbsd-x64@0.21.5": resolution: { @@ -684,6 +871,15 @@ packages: cpu: [x64] os: [openbsd] + "@esbuild/openbsd-x64@0.23.1": + resolution: + { + integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [openbsd] + "@esbuild/sunos-x64@0.21.5": resolution: { @@ -693,6 +889,15 @@ packages: cpu: [x64] os: [sunos] + "@esbuild/sunos-x64@0.23.1": + resolution: + { + integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [sunos] + "@esbuild/win32-arm64@0.21.5": resolution: { @@ -702,6 +907,15 @@ packages: cpu: [arm64] os: [win32] + "@esbuild/win32-arm64@0.23.1": + resolution: + { + integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + "@esbuild/win32-ia32@0.21.5": resolution: { @@ -711,6 +925,15 @@ packages: cpu: [ia32] os: [win32] + "@esbuild/win32-ia32@0.23.1": + resolution: + { + integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [win32] + "@esbuild/win32-x64@0.21.5": resolution: { @@ -720,6 +943,15 @@ packages: cpu: [x64] os: [win32] + "@esbuild/win32-x64@0.23.1": + resolution: + { + integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + "@eslint-community/eslint-utils@4.4.0": resolution: { @@ -1977,6 +2209,13 @@ packages: } engines: { node: ">=8" } + dotenv@16.4.5: + resolution: + { + integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==, + } + engines: { node: ">=12" } + eastasianwidth@0.2.0: resolution: { @@ -2080,6 +2319,14 @@ packages: engines: { node: ">=12" } hasBin: true + esbuild@0.23.1: + resolution: + { + integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==, + } + engines: { node: ">=18" } + hasBin: true + escalade@3.2.0: resolution: { @@ -2408,6 +2655,12 @@ packages: } engines: { node: ">=16" } + get-tsconfig@4.8.1: + resolution: + { + integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==, + } + git-hooks-list@3.1.0: resolution: { @@ -3667,6 +3920,12 @@ packages: } engines: { node: ">=8" } + resolve-pkg-maps@1.0.0: + resolution: + { + integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==, + } + restore-cursor@5.1.0: resolution: { @@ -4114,6 +4373,14 @@ packages: integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==, } + tsx@4.19.2: + resolution: + { + integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==, + } + engines: { node: ">=18.0.0" } + hasBin: true + turbo-darwin-64@2.1.1: resolution: { @@ -4819,72 +5086,144 @@ snapshots: "@esbuild/aix-ppc64@0.21.5": optional: true + "@esbuild/aix-ppc64@0.23.1": + optional: true + "@esbuild/android-arm64@0.21.5": optional: true + "@esbuild/android-arm64@0.23.1": + optional: true + "@esbuild/android-arm@0.21.5": optional: true + "@esbuild/android-arm@0.23.1": + optional: true + "@esbuild/android-x64@0.21.5": optional: true + "@esbuild/android-x64@0.23.1": + optional: true + "@esbuild/darwin-arm64@0.21.5": optional: true + "@esbuild/darwin-arm64@0.23.1": + optional: true + "@esbuild/darwin-x64@0.21.5": optional: true + "@esbuild/darwin-x64@0.23.1": + optional: true + "@esbuild/freebsd-arm64@0.21.5": optional: true + "@esbuild/freebsd-arm64@0.23.1": + optional: true + "@esbuild/freebsd-x64@0.21.5": optional: true + "@esbuild/freebsd-x64@0.23.1": + optional: true + "@esbuild/linux-arm64@0.21.5": optional: true + "@esbuild/linux-arm64@0.23.1": + optional: true + "@esbuild/linux-arm@0.21.5": optional: true + "@esbuild/linux-arm@0.23.1": + optional: true + "@esbuild/linux-ia32@0.21.5": optional: true + "@esbuild/linux-ia32@0.23.1": + optional: true + "@esbuild/linux-loong64@0.21.5": optional: true + "@esbuild/linux-loong64@0.23.1": + optional: true + "@esbuild/linux-mips64el@0.21.5": optional: true + "@esbuild/linux-mips64el@0.23.1": + optional: true + "@esbuild/linux-ppc64@0.21.5": optional: true + "@esbuild/linux-ppc64@0.23.1": + optional: true + "@esbuild/linux-riscv64@0.21.5": optional: true + "@esbuild/linux-riscv64@0.23.1": + optional: true + "@esbuild/linux-s390x@0.21.5": optional: true + "@esbuild/linux-s390x@0.23.1": + optional: true + "@esbuild/linux-x64@0.21.5": optional: true + "@esbuild/linux-x64@0.23.1": + optional: true + "@esbuild/netbsd-x64@0.21.5": optional: true + "@esbuild/netbsd-x64@0.23.1": + optional: true + + "@esbuild/openbsd-arm64@0.23.1": + optional: true + "@esbuild/openbsd-x64@0.21.5": optional: true + "@esbuild/openbsd-x64@0.23.1": + optional: true + "@esbuild/sunos-x64@0.21.5": optional: true + "@esbuild/sunos-x64@0.23.1": + optional: true + "@esbuild/win32-arm64@0.21.5": optional: true + "@esbuild/win32-arm64@0.23.1": + optional: true + "@esbuild/win32-ia32@0.21.5": optional: true + "@esbuild/win32-ia32@0.23.1": + optional: true + "@esbuild/win32-x64@0.21.5": optional: true + "@esbuild/win32-x64@0.23.1": + optional: true + "@eslint-community/eslint-utils@4.4.0(eslint@8.56.0)": dependencies: eslint: 8.56.0 @@ -5632,6 +5971,8 @@ snapshots: dependencies: is-obj: 2.0.0 + dotenv@16.4.5: {} + eastasianwidth@0.2.0: {} electron-to-chromium@1.5.18: {} @@ -5697,6 +6038,33 @@ snapshots: "@esbuild/win32-ia32": 0.21.5 "@esbuild/win32-x64": 0.21.5 + esbuild@0.23.1: + optionalDependencies: + "@esbuild/aix-ppc64": 0.23.1 + "@esbuild/android-arm": 0.23.1 + "@esbuild/android-arm64": 0.23.1 + "@esbuild/android-x64": 0.23.1 + "@esbuild/darwin-arm64": 0.23.1 + "@esbuild/darwin-x64": 0.23.1 + "@esbuild/freebsd-arm64": 0.23.1 + "@esbuild/freebsd-x64": 0.23.1 + "@esbuild/linux-arm": 0.23.1 + "@esbuild/linux-arm64": 0.23.1 + "@esbuild/linux-ia32": 0.23.1 + "@esbuild/linux-loong64": 0.23.1 + "@esbuild/linux-mips64el": 0.23.1 + "@esbuild/linux-ppc64": 0.23.1 + "@esbuild/linux-riscv64": 0.23.1 + "@esbuild/linux-s390x": 0.23.1 + "@esbuild/linux-x64": 0.23.1 + "@esbuild/netbsd-x64": 0.23.1 + "@esbuild/openbsd-arm64": 0.23.1 + "@esbuild/openbsd-x64": 0.23.1 + "@esbuild/sunos-x64": 0.23.1 + "@esbuild/win32-arm64": 0.23.1 + "@esbuild/win32-ia32": 0.23.1 + "@esbuild/win32-x64": 0.23.1 + escalade@3.2.0: {} escape-string-regexp@1.0.5: {} @@ -5904,6 +6272,10 @@ snapshots: get-stream@8.0.1: {} + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + git-hooks-list@3.1.0: {} git-raw-commits@4.0.0: @@ -6543,6 +6915,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -6796,6 +7170,13 @@ snapshots: tslib@2.7.0: {} + tsx@4.19.2: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.8.1 + optionalDependencies: + fsevents: 2.3.3 + turbo-darwin-64@2.1.1: optional: true