Ctrl K
ComunidadGuías…

app/scripts/smoke-node.mjs

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

Archivo original ↗Preguntar a KiBot
import assert from 'node:assert/strict'
import { spawn } from 'node:child_process'
import { randomBytes } from 'node:crypto'
import { once } from 'node:events'
import { mkdtempSync, mkdirSync, realpathSync, writeFileSync } from 'node:fs'
import { createServer } from 'node:net'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { setTimeout as delay } from 'node:timers/promises'
import Sqlite from 'better-sqlite3'
import { drizzle } from 'drizzle-orm/better-sqlite3'
import { migrate } from 'drizzle-orm/better-sqlite3/migrator'
import { readMigrationFiles } from 'drizzle-orm/migrator'
import { smokeMcp } from './smoke-mcp.mjs'
import { smokeSettings } from './smoke-settings.mjs'
import { smokeAssets } from './smoke-assets.mjs'
import { smokePlugins } from './smoke-plugins.mjs'
import { smokePluginServices } from './smoke-plugin-services.mjs'

const development = process.env.DEVPUSH_SMOKE_DEVELOPMENT === '1'
const sourceDevelopment = development && !process.env.DEVPUSH_SMOKE_APP_DIR
const appDir = resolve(
  process.env.DEVPUSH_SMOKE_APP_DIR ||
    fileURLToPath(new URL('../', import.meta.url)),
)
const packageDir = process.env.DEVPUSH_SMOKE_APP_DIR
  ? realpathSync(resolve(appDir, 'node_modules/devpush'))
  : appDir
mkdirSync(resolve(appDir, '.tmp'), { recursive: true })
const directory = mkdtempSync(resolve(appDir, '.tmp/node-smoke-'))
const mainPath = resolve(directory, 'main.sqlite')
const logsPath = resolve(directory, 'logs.sqlite')
const stores = [new Sqlite(mainPath), new Sqlite(logsPath)]
for (const [index, store] of stores.entries()) {
  store.pragma('foreign_keys = ON')
  store.pragma('journal_mode = WAL')
  if (!sourceDevelopment)
    migrate(drizzle(store), {
      migrationsFolder: resolve(
        packageDir,
        'src/server/db',
        index === 0 ? 'migrations-sqlite' : 'migrations-logs',
      ),
    })
}
const listener = createServer()
await new Promise((resolve) => listener.listen(0, '127.0.0.1', resolve))
const { port } = listener.address()
await new Promise((resolve, reject) =>
  listener.close((error) => (error ? reject(error) : resolve())),
)
const origin = `http://127.0.0.1:${port}`
const authSecret = randomBytes(32).toString('hex')
// Do not inherit .env or provider/storage credentials from the developer's
// app. This production process uses only disposable stores and generated keys.
const env = {
  PATH: process.env.PATH,
  TMPDIR: process.env.TMPDIR,
  NODE_ENV: development ? 'development' : 'production',
  HOST: '127.0.0.1',
  PORT: String(port),
  DEVPUSH_BASE_URL: origin,
  BETTER_AUTH_URL: origin,
  DEVPUSH_MCP_URL: `${origin}/mcp`,
  BETTER_AUTH_SECRET: authSecret,
  DEVPUSH_DATABASE_URL: '',
  DEVPUSH_LOG_DATABASE_URL: '',
  DEVPUSH_DATABASE_AUTH_TOKEN: '',
  DEVPUSH_LOG_DATABASE_AUTH_TOKEN: '',
  DEVPUSH_DATABASE_PATH: mainPath,
  DEVPUSH_LOG_DATABASE_PATH: logsPath,
  DEVPUSH_SECRET_ENCRYPTION_KEY: randomBytes(32).toString('hex'),
}
let output = ''
const server = spawn(
  sourceDevelopment ? 'npm' : process.execPath,
  sourceDevelopment
    ? ['run', 'dev', '--', '--host', '127.0.0.1', '--port', String(port)]
    : development
      ? [
          './node_modules/vite/bin/vite.js',
          'dev',
          '--host',
          '127.0.0.1',
          '--port',
          String(port),
          '--strictPort',
        ]
      : ['.output/server/index.mjs'],
  {
    cwd: appDir,
    env,
    detached: sourceDevelopment && process.platform !== 'win32',
    stdio: ['ignore', 'pipe', 'pipe'],
  },
)
server.stdout.on('data', (chunk) => {
  output += chunk
})
server.stderr.on('data', (chunk) => {
  output += chunk
})
try {
  for (let attempt = 0; ; attempt++) {
    try {
      assert.equal(
        (
          await fetch(`${origin}/api/health`, {
            signal: AbortSignal.timeout(2000),
          })
        ).status,
        200,
      )
      break
    } catch (error) {
      if (attempt === 79 || server.exitCode !== null) throw error
      await delay(250)
    }
  }
  if (sourceDevelopment) {
    assert.ok(
      stores[0]
        .prepare('pragma table_info(user)')
        .all()
        .some((column) => column.name === 'profile_confirmed_at'),
    )
    for (const [index, store] of stores.entries()) {
      const migrations = readMigrationFiles({
        migrationsFolder: resolve(
          packageDir,
          'src/server/db',
          index === 0 ? 'migrations-sqlite' : 'migrations-logs',
        ),
      })
      assert.deepEqual(
        store
          .prepare('select hash from __drizzle_migrations order by created_at')
          .all()
          .map((row) => row.hash),
        migrations.map((migration) => migration.hash),
      )
    }
    console.log(
      'Development startup: all main/log migrations applied before serving requests.',
    )
  }
  const login = await fetch(`${origin}/auth/login`)
  assert.equal(login.status, 200)
  await smokeAssets(origin, await login.text(), { production: !development })
  await smokePlugins(origin)
  const settings = await fetch(`${origin}/settings`)
  assert.equal(settings.status, 200)
  assert.match(await settings.text(), /data-slot="skeleton"/)
  await smokeSettings({
    origin,
    authSecret,
    appDir,
    development,
    modulePrefix: process.env.DEVPUSH_SMOKE_APP_DIR
      ? `@fs/${packageDir}/src`
      : 'src',
    query: async (index, sql) => {
      const statement = stores[index].prepare(sql)
      if (statement.reader) return statement.all()
      statement.run()
      return []
    },
  })
  await smokePluginServices({
    origin,
    authSecret,
    appDir,
    development,
    modulePrefix: process.env.DEVPUSH_SMOKE_APP_DIR
      ? `@fs/${packageDir}/src`
      : 'src',
    query: async (index, sql) => {
      const statement = stores[index].prepare(sql)
      if (statement.reader) return statement.all()
      statement.run()
      return []
    },
  })
  if (!development)
    await smokeMcp({
      origin,
      authSecret,
      supportsCimd: true,
      appDir,
      query: async (index, sql) => {
        const statement = stores[index].prepare(sql)
        if (statement.reader) return statement.all()
        statement.run()
        return []
      },
    })
  console.log(
    development
      ? 'Vite development: login, settings pending UI and concurrent settings saves passed.'
      : 'Node production: login, settings pending UI, REST, SDK2026 MCP, OAuth consent, grant revocation and refresh replay passed.',
  )
} catch (error) {
  console.error(output)
  throw error
} finally {
  writeFileSync(resolve(directory, 'server.log'), output)
  if (server.exitCode === null) {
    const closed = once(server, 'exit')
    if (sourceDevelopment && process.platform !== 'win32')
      process.kill(-server.pid, 'SIGTERM')
    else server.kill('SIGTERM')
    await closed
  }
  stores.forEach((store) => store.close())
}