Ctrl K
ComunidadGuías…

scripts/apply-init-sql.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
/**
 * Applies an init.sql file the way the Supabase SQL Editor does, then checks
 * that the install is usable. CI's fresh-install gate runs this against an
 * empty local Supabase database.
 *
 * Why not `psql -f`: psql splits the file into statements and, by default,
 * keeps going after an error, so a broken statement costs one error line and
 * the rest of the schema still lands. The SQL Editor, which the README and
 * /docs/self-host tell people to use, sends the whole file as ONE simple-
 * protocol query. Postgres then treats everything since the last COMMIT as a
 * single implicit transaction: one failing statement rolls all of it back and
 * nothing after it runs. That is how a broken superseded migration left
 * self-hosters without a `requests` table. node-postgres sends a
 * parameterless query exactly that way, so this reproduces the Editor.
 *
 * Usage:
 *   node scripts/apply-init-sql.mjs <connection-url> [file]   (file defaults to supabase/init.sql)
 */

import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import pg from 'pg'

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')

// Tables the product cannot start without. `requests` sits near the end of
// init.sql, so it is also the canary for "the file stopped partway through".
const REQUIRED_TABLES = [
  'public.organizations',
  'public.org_members',
  'public.projects',
  'public.api_keys',
  'public.provider_keys',
  'public.user_profiles',
  'public.model_prices',
  'public.background_migrations',
  'public.requests',
  'public.requests_fallback',
]

async function main(argv) {
  const [connectionString, fileArg] = argv.slice(2)
  if (!connectionString) {
    console.error('usage: node scripts/apply-init-sql.mjs <connection-url> [file]')
    return 2
  }
  const file = path.resolve(fileArg ?? path.join(root, 'supabase', 'init.sql'))
  const sql = fs.readFileSync(file, 'utf8')

  const client = new pg.Client({ connectionString })
  await client.connect()
  try {
    try {
      await client.query(sql)
    } catch (err) {
      // The connection string carries a password, so only the Postgres error
      // and its position are printed.
      const message = err instanceof Error ? err.message : String(err)
      console.error(`init.sql failed as a single query: ${message}`)
      if (err && typeof err === 'object' && 'position' in err && err.position) {
        const offset = Number(err.position)
        const line = sql.slice(0, offset).split('\n').length
        console.error(`  at ${path.relative(process.cwd(), file)} line ${line}`)
      }
      return 1
    }

    const { rows } = await client.query(
      'SELECT name FROM unnest($1::text[]) AS name WHERE to_regclass(name) IS NULL',
      [REQUIRED_TABLES],
    )
    if (rows.length > 0) {
      console.error(`init.sql ran but these tables are missing: ${rows.map((r) => r.name).join(', ')}`)
      return 1
    }
    console.log(`init.sql applied as one query; ${REQUIRED_TABLES.length} required tables present`)
    return 0
  } finally {
    await client.end()
  }
}

main(process.argv).then(
  (code) => {
    process.exitCode = code
  },
  (err) => {
    console.error(err instanceof Error ? err.message : err)
    process.exitCode = 1
  },
)