Files
OmniRoute/scripts/check/check-db-rules.mjs
Diego Rodrigues de Sa e Souza 9350a5d6c6 Release v3.8.22 (#3623)
* chore(release): open v3.8.22 development cycle

* refactor(dashboard): extract ProviderDetailPageClient — #3501 Phase 0 (#3633)

#3501 Phase 0: extract ProviderDetailPageClient + smoke test.

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>

* refactor(dashboard): extract auth-import modals — #3501 Phase 1a (#3634)

#3501 Phase 1a: extract 3 auth-import modal clusters.

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>

* fix(db): reclassify localDb unexported modules as intentionally-internal (#3499) (#3635)

Closes #3499 — reclassify localDb unexported modules as intentionally-internal (audit + honest gate framing).

* refactor(db): move call_logs aggregations into callLogStats db module (#3500) (#3636)

#3500 slice 1: call_logs aggregations → src/lib/db/callLogStats.ts (Rule #5). Byte-identical queries; TDD 6/6.

* refactor(dashboard): extract EditCompatibleNodeModal — #3501 Phase 1b (#3638)

#3501 Phase 1b: extract EditCompatibleNodeModal (cycle-safe via leaf constants module).

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>

* refactor(db): move community_servers SQL into gamification db module (#3500 slice 3) (#3639)

#3500 slice 3: community_servers SQL → gamification db module.

* refactor(db): move usage_history SQL into usageAnalytics module (#3500 slice 2) (#3644)

#3500 slice 2: usage_history/daily_usage_summary SQL → usageAnalytics db module.

* refactor(db): move skills UPDATE + db-backups SQL into db modules (#3500 slice 5) (#3647)

#3500 slice 5: skills UPDATE (allowlist) + db-backups SQL → db modules.

* refactor(db): move usage_logs/semantic_cache/proxy_logs SQL into db modules (#3500 slice 4) (#3648)

#3500 slice 4: usage_logs/semantic_cache/proxy_logs SQL → db modules. All internal routes done (2 external by-design remain).

* chore(db-gate): reclassify external-DB reads, fully close #3500 (#3649)

Closes #3500: reclassify external-DB reads; all internal raw-SQL migrated to db/ modules.

* refactor(dashboard): extract pure helpers to providerPageHelpers — #3501 Phase 2 (#3653)

#3501 Phase 2: extract pure helpers to providerPageHelpers (leaf, cycle-safe).

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>

* refactor(dashboard): extract remaining shared helpers to providerPageHelpers — #3501 Phase 2b (#3658)

#3501 Phase 2b: extract remaining shared helpers to providerPageHelpers (leaf, cycle-safe). Heavy modals unblocked.

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>

* fix(reasoning): replay reasoning_content on plain DeepSeek turns (#1682) (#3632)

Integrated into release/v3.8.22

* fix(kiro): route enterprise IAM Identity Center accounts to their regional endpoint (#3631)

Integrated into release/v3.8.22

* refactor: small code cleanup (#3523)

Integrated into release/v3.8.22

* fix(combo): skip same-provider targets on 408/500/502/503/504/524 errors (#3637)

Integrated into release/v3.8.22 — circuit-breaker guard added in review (#1731v2)

* feat(providers): add MiMoCode free-tier provider with bootstrap JWT auth (#3659)

Integrated into release/v3.8.22 — page.tsx conflict resolved + NoAuthAccountCard re-applied to ProviderDetailPageClient in review. MiMoCode endpoint validated live.

* Log Responses WebSocket calls in history (#3616)

Integrated into release/v3.8.22 — Codex Responses WebSocket call history logging.

* Add Claude Code routing preference for unprefixed Claude models (#3540)

Integrated into release/v3.8.22 — page.tsx conflict resolved (re-applied toggle to ProviderDetailPageClient) + disable-test updated for catalog drift in review.

* docs(changelog): credit #3632/#3631/#3637/#3659/#3540/#3616/#3523 (v3.8.22 targeted review round)

* fix(mimocode): add required authHeader:"none" to registry entry (#3659 follow-up)

The mimocode RegistryEntry omitted the required authHeader field, which broke
typecheck:core (TS2741). Match the no-auth convention (authType:"none" + authHeader:"none")
used by veoaifree-web and other free providers. Follow-up to #3659 (@pizzav-xyz).

* fix(responses): detect stream readiness for tool-call-only and object-less chunks (#3612) (#3661)

Closes #3612

* fix(mitm): remove duplicated 'Command failed:' error prefix (#3641) (#3662)

Closes #3641

* fix(cli): honor HERMES_HOME for Hermes Agent config path (#3628) (#3663)

Closes #3628

* fix(api): fetch live OpenCode model catalog for no-auth model picker (#3611) (#3664)

Closes #3611

* fix(api): flag provider topology error state by current status, not stale history (#3619) (#3666)

Closes #3619

* fix(electron): launch peer-stamping server-ws.mjs entrypoint to avoid 403 LOCAL_ONLY (#3386) (#3665)

Closes #3386

* fix(dashboard): restore home topology live in-flight pulse (#3507) (#3667)

Closes #3507

* fix(oauth): name Kiro/AWS auto-imported accounts and dedupe by profileArn (#3615) (#3671)

Closes #3615

* fix(resilience): clear stale transient connection cooldowns on startup (#3625) (#3672)

Closes #3625

* fix(i18n): use logical CSS direction utilities for sidebar and key overlays (RTL #3541) (#3670)

Closes #3541

* fix(dashboard): honor auto-hide and switch to visible filter on passthrough Test-all (#3610) (#3669)

Closes #3610

* refactor(dashboard): extract AddApiKeyModal + EditConnectionModal — #3501 Phase 1c (#3674)

#3501 Phase 1c: extract AddApiKeyModal, EditConnectionModal, WebSessionCredentialGuide into components/; god-component 10,166->8,092 LOC. Reconciles the v3.8.22 file-size drift for this file.

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>

* docs(changelog): reconcile v3.8.22 — credit #3621/#3622 + MiMoCode follow-up roll-up

* refactor(dashboard): extract ConnectionRow + ModelCompatPopover + SiliconFlowEndpointModal — #3501 Phase 1d (#3676)

#3501 Phase 1d: god-component 8,092->6,838 LOC.

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>

* feat(obsidian): add WebDAV config route + encrypt creds at rest (#3485 part 1) (#3677)

Part 1 of #3485. Adds /api/settings/obsidian/webdav (GET/POST/DELETE) wiring the ready obsidianSync lib, encrypts webdav password + obsidian token at rest, removes the duplicate UI block, drops the KNOWN_MISSING entry. WebDAV file server is part 2.

* feat(obsidian): add /api/v1/webdav file server for Obsidian vault sync (#3485 part 2) (#3678)

Part 2 of #3485. WebDAV server (PROPFIND/GET/PUT/DELETE/MKCOL/MOVE/OPTIONS) handled in the custom server layer (standalone-server-ws.mjs) since the App Router cannot export WebDAV methods. Basic-Auth (constant-time), path-traversal hardened, password decrypt ported from encryption.ts (parity-tested), DATA_DIR resolution parity-tested against dataPaths.ts. End-to-end Obsidian-over-Tailscale validation is a live VPS step (Rule #18).

* fix(combo): stop premature context compaction — real auto-combo windows + per-target compression limit (#3680)

Integrated into release/v3.8.22

* feat(dashboard): deactivate/activate accounts from the quota overview (#3675)

Integrated into release/v3.8.22

* fix(dashboard): close review gaps in bulk provider connection actions (#3271 follow-up) (#3673)

Integrated into release/v3.8.22 — page.tsx conflict (god-component split #3501) resolved by re-applying the bulk-action deltas to ProviderDetailPageClient.tsx

* refactor(dashboard): extract useModelCompatState hook + model sections — #3501 Phase 1e (#3683)

#3501 Phase 1e: extract useModelCompatState hook (unblocks the model sections) + ModelRow/PassthroughModelsSection/PassthroughModelRow/CustomModelsSection/CompatibleModelsSection. god-component 6,838->4,921 LOC.

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>

* refactor(dashboard): extract useProviderConnections/Settings/Models hooks — #3501 Phase 1f (#3684)

#3501 Phase 1f: god-component 4,948->4,062 LOC. Connection state+handlers, settings, and model metadata moved into hooks/.

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>

* chore(release): v3.8.22 CHANGELOG + env-doc sync

- Set release date in CHANGELOG [3.8.22] to 2026-06-11
- Add HERMES_HOME to .env.example (from #3628/#3663)
- Add HERMES_HOME + OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS to ENVIRONMENT.md (#3628/#3540)

* docs(changelog): credit #3673 + #3675 — leninejunior bulk-actions + quota-toggle

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: Abhishek Divekar <adivekar@utexas.edu>
Co-authored-by: NOXX - Commiter <artur1992123@mail.ru>
Co-authored-by: Nicolas Lorin <androw95220@gmail.com>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: kkkayye <98376609+kkkayye@users.noreply.github.com>
Co-authored-by: Witroch4 <witalo_rocha@hotmail.com>
Co-authored-by: Lenine Júnior <lenine@engrene.com.br>
2026-06-11 18:52:29 -03:00

262 lines
13 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// scripts/check/check-db-rules.mjs
// Gate de convenções de banco (CLAUDE.md Hard Rules #2 e #5). Três verificações:
// (a) Todo módulo de domínio em src/lib/db/*.ts deve ser re-exportado por
// src/lib/localDb.ts (camada de compat). Um módulo db NOVO que não é
// re-exportado (e não está congelado) falha — força a decisão consciente
// de expor ou justificar (Hard Rule #2).
// (b) src/lib/localDb.ts é APENAS camada de re-export: nada de lógica
// (function/class/arrow de negócio). Mata o anti-padrão de "só uma
// funçãozinha aqui" que vira regra de negócio fora dos módulos db/.
// (c) Nenhum SQL cru em src/app/api/**/route.ts ou open-sse/handlers/*.ts.
// SQL deve viver em src/lib/db/ (Hard Rule #5). Ofensores pré-existentes
// são congelados; QUALQUER novo SQL cru em rota/handler falha.
import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
const cwd = process.cwd();
const DB_DIR = path.join(cwd, "src/lib/db");
const LOCAL_DB = path.join(cwd, "src/lib/localDb.ts");
const API_DIR = path.join(cwd, "src/app/api");
const HANDLERS_DIR = path.join(cwd, "open-sse/handlers");
// (a) Módulos db/ que NÃO são re-exportados por localDb.ts por DESIGN (Hard Rule #2:
// "Never barrel-import from localDb.ts — import specific db/ modules instead").
// Cada entrada aqui foi auditada e é consumida via import direto de "@/lib/db/X"
// (estático ou dinâmico) pelos seus consumidores — exatamente o padrão correto.
// Re-exportar esses módulos via localDb.ts INCENTIVARIA o anti-padrão proibido.
// O gate ainda bloqueia QUALQUER módulo db/ NOVO que não seja re-exportado E não
// esteja nessa lista — mantendo a decisão consciente obrigatória (Hard Rule #2).
// Legenda de classificação:
// type-only = exporta apenas tipos (sem runtime API), não há o que re-exportar
// db-internal = importado apenas dentro de src/lib/db/ (coordenação interna)
// intentionally-internal = consumido por import direto fora de db/ (correto per Rule #2)
// DEAD? = zero importers encontrados na auditoria de 2026-06-11; não deletar
// sem investigação — pode ser reserva de schema ou F2 pendente
export const INTENTIONALLY_INTERNAL = new Set([
"_rowTypes", // type-only: 5 importers internos em db/ (AgentBridge/Inspector row types)
"cleanup", // intentionally-internal: 3 API routes (purge-quota-snapshots, purge-call-logs, purge-detailed-logs)
"cliToolState", // intentionally-internal: 14+ API routes em /api/cli-tools/*-settings
"comboForecast", // intentionally-internal: src/lib/usage/comboForecast.ts
"commandCodeAuth", // intentionally-internal: 5 API routes em /api/providers/command-code/auth/*
"compression", // intentionally-internal: 2 API routes (settings/compression, context/rtk/config)
"compressionScheduler", // DEAD?: 0 importers na auditoria de 2026-06-11; mantido para schema reservation
"detailedLogs", // intentionally-internal: 3 callers (callLogs.ts, logs/detail route, embeddings handler)
"discovery", // DEAD?: 0 importers na auditoria de 2026-06-11; lib/discovery/index.ts não usa db/discovery
"domainState", // intentionally-internal: 5 callers (batchWriter, circuitBreaker, costRules, fallbackPolicy, lockoutPolicy)
"encryption", // intentionally-internal: 8+ callers (container, webhookDispatcher, cloudAgent/credentials, services/apiKey, 4+ routes, open-sse)
"healthCheck", // db-internal: importado por db/core.ts (runDbHealthCheck)
"jsonMigration", // intentionally-internal: src/app/api/settings/import-json/route.ts
"migrationRunner", // db-internal: importado por db/core.ts (runMigrations ao inicializar o DB)
"notion", // intentionally-internal: settings/notion API route + open-sse/mcp-server/tools/notionTools.ts
"obsidian", // intentionally-internal: src/lib/obsidianSync.ts + settings/obsidian route + MCP obsidianTools.ts
"pluginMetrics", // DEAD? (production): write path não foi conectado ainda (documentado no cabeçalho do módulo); testado por tests/unit/plugins-metrics.test.ts
"prompts", // DEAD? (production): zero callers de produção encontrados; domínio domain/prompts.ts é independente; testado por tests/integration/proxy-pipeline.test.ts
"providerStats", // intentionally-internal: src/app/api/provider-stats/route.ts
"recovery", // intentionally-internal: bin/cli/runtime.mjs (import() dinâmico) + tests
"secrets", // intentionally-internal: src/instrumentation-node.ts (import() dinâmico na inicialização)
"serviceModels", // intentionally-internal: 3 callers (services/modelSync, services/bootstrap, /api/services/9router/models)
"stateReset", // db-internal: 3 callers dentro de src/lib/db/ (core, backup, apiKeys) para coordenação de reset
"stats", // intentionally-internal: src/app/api/settings/database/refresh-stats/route.ts
"tierConfig", // intentionally-internal: open-sse/services/tierResolver.ts (require() dinâmico)
]);
// Alias para retrocompatibilidade com os testes existentes que importam KNOWN_UNEXPORTED.
// O comportamento do gate é idêntico — só o nome e os comentários mudaram (#3499).
export const KNOWN_UNEXPORTED = INTENTIONALLY_INTERNAL;
// (c) Leituras de SQL contra bancos EXTERNOS, permitidas por design (#3500).
// Estas rotas NÃO consultam o DB do OmniRoute (getDbInstance) — elas abrem o
// SQLite de OUTRO aplicativo (Cursor / Kiro) para auto-importar credenciais.
// Por isso NÃO podem viver em src/lib/db/ (que é o domínio do DB do OmniRoute):
// são leituras read-only de um arquivo externo, com caminho/escopo próprios.
// Continuam no allowlist como exceção DOCUMENTADA — o gate ainda bloqueia
// QUALQUER novo SQL cru contra o DB do OmniRoute em rotas/handlers.
// Toda a dívida real da Hard Rule #5 (15 rotas internas) foi migrada para
// módulos src/lib/db/ nas slices do #3500; este set ficou só com as exceções.
const EXTERNAL_DB_ALLOWED = new Set([
"src/app/api/oauth/cursor/auto-import/route.ts", // read-only no itemTable do SQLite do Cursor (DB externo)
"src/app/api/oauth/kiro/auto-import/route.ts", // read-only no SQLite do Kiro (DB externo)
]);
// Alias de retrocompatibilidade (testes/consumidores que importam KNOWN_RAW_SQL).
// Comportamento do gate idêntico — só o nome e o enquadramento mudaram (#3500).
const KNOWN_RAW_SQL = EXTERNAL_DB_ALLOWED;
// Módulos sempre excluídos da checagem (a): não são domínio re-exportável.
const DB_MODULE_EXCLUDE = new Set(["core", "localDb", "index"]);
function walk(dir, acc = []) {
if (!fs.existsSync(dir)) return acc;
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, e.name);
if (e.isDirectory()) walk(p, acc);
else acc.push(p);
}
return acc;
}
// Lista os módulos de domínio em src/lib/db (top-level *.ts), excluindo
// core/localDb/index, *.d.ts e qualquer subdiretório (migrations/, adapters/, __tests__/).
export function collectDbModules(dbDir = DB_DIR) {
if (!fs.existsSync(dbDir)) return [];
return fs
.readdirSync(dbDir, { withFileTypes: true })
.filter((e) => e.isFile() && /\.ts$/.test(e.name) && !/\.d\.ts$/.test(e.name))
.map((e) => e.name.replace(/\.ts$/, ""))
.filter((name) => !DB_MODULE_EXCLUDE.has(name))
.sort();
}
// Extrai os nomes de módulo re-exportados de localDb.ts a partir de
// `... from "./db/X"` (cobre export {…}, export * e export type {…}).
export function extractReexportedModules(localDbSource) {
const re = /from\s+["']\.\/db\/([A-Za-z0-9_]+)["']/g;
const out = new Set();
let m;
while ((m = re.exec(localDbSource))) out.add(m[1]);
return out;
}
// (a) Módulos db/ que não são re-exportados e não estão na lista de
// intencionalmente-internos (INTENTIONALLY_INTERNAL). O gate falha para
// qualquer módulo NOVO que não seja re-exportado nem justificado.
export function findMissingReexports(dbModules, reexported, allowlist = INTENTIONALLY_INTERNAL) {
return dbModules.filter((mod) => !reexported.has(mod) && !allowlist.has(mod));
}
// (b) localDb.ts deve conter SOMENTE import/export + comentários (sem lógica).
// Remove comentários e strings, depois procura declarações de runtime.
export function hasLogic(localDbSource) {
const stripped = localDbSource
// comentários de bloco
.replace(/\/\*[\s\S]*?\*\//g, "")
// comentários de linha
.replace(/\/\/[^\n]*/g, "")
// template strings
.replace(/`(?:\\[\s\S]|[^\\`])*`/g, '""')
// strings simples/duplas (paths de import etc.)
.replace(/"(?:\\.|[^"\\])*"/g, '""')
.replace(/'(?:\\.|[^'\\])*'/g, '""');
// function/class declaradas, ou atribuição a função (const X = (…) =>, const X = function).
const logicPatterns = [
/(^|[^.\w])function\s+[A-Za-z_$]/, // function decl (não method .foo())
/(^|[^.\w])class\s+[A-Za-z_$]/, // class decl
/(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*(?:async\s*)?\(/, // const X = (…) ... (arrow/call)
/(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*(?:async\s+)?function\b/, // const X = function
];
return logicPatterns.some((rx) => rx.test(stripped));
}
// SQL cru é sempre uma STRING passada a db.prepare()/exec(): casamos os padrões
// SÓ dentro de literais de string (não em código JS — `import … from`, `.set(`,
// `new Set(`, `delete x` etc. são falsos positivos se varrermos o código todo).
const SQL_PATTERNS = [
/\bSELECT\b[\s\S]*?\bFROM\b/i, // SELECT … FROM (multi-linha)
/\bINSERT\s+INTO\b/i,
/\bUPDATE\b[\s\S]*?\bSET\b/i, // UPDATE … SET (multi-linha)
/\bDELETE\s+FROM\b/i,
/\bCREATE\s+TABLE\b/i,
];
// Remove comentários (linha // … e blocos /* */) — SQL em comentário não conta.
function stripComments(source) {
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
}
// Extrai o conteúdo de todos os literais de string (template, aspas duplas, aspas
// simples) de um trecho de código já sem comentários. Retorna a concatenação dos
// corpos — é nesse corpo que SQL cru vive.
export function extractStringLiterals(code) {
const re = /`(?:\\[\s\S]|[^\\`])*`|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g;
const out = [];
let m;
while ((m = re.exec(code))) {
// tira as aspas/crases delimitadoras
out.push(m[0].slice(1, -1));
}
return out.join("\n\n"); // separador que nenhum padrão SQL atravessa
}
// (c) Arquivos com SQL cru dentro de literais de string (linhas não-comentário),
// fora do allowlist.
export function findRawSql(files, allowlist = KNOWN_RAW_SQL) {
const offenders = [];
for (const file of files) {
const rel = path.relative(cwd, file).replace(/\\/g, "/");
if (allowlist.has(rel)) continue;
let src;
try {
src = fs.readFileSync(file, "utf8");
} catch {
continue;
}
const literals = extractStringLiterals(stripComments(src));
if (SQL_PATTERNS.some((rx) => rx.test(literals))) {
offenders.push(rel);
}
}
return offenders;
}
// Coleta os arquivos sujeitos à checagem (c): rotas de API + handlers de stream.
export function collectSqlScanFiles(apiDir = API_DIR, handlersDir = HANDLERS_DIR) {
const routes = walk(apiDir).filter((p) => /(^|\/)route\.tsx?$/.test(p.replace(/\\/g, "/")));
const handlers = fs.existsSync(handlersDir)
? fs
.readdirSync(handlersDir, { withFileTypes: true })
.filter((e) => e.isFile() && /\.tsx?$/.test(e.name))
.map((e) => path.join(handlersDir, e.name))
: [];
return [...routes, ...handlers];
}
function main() {
const failures = [];
// (a) re-export completeness
const dbModules = collectDbModules();
const reexported = extractReexportedModules(fs.readFileSync(LOCAL_DB, "utf8"));
const missing = findMissingReexports(dbModules, reexported);
if (missing.length) {
failures.push(
`[#2 re-export] ${missing.length} módulo(s) db/ não re-exportado(s) por src/lib/localDb.ts:\n` +
missing.map((m) => ` ✗ src/lib/db/${m}.ts`).join("\n") +
`\n → re-exporte de src/lib/localDb.ts (apenas a lista de re-export, nada de lógica)` +
` ou adicione a INTENTIONALLY_INTERNAL com justificativa (import direto de "@/lib/db/${missing[0]}").`
);
}
// (b) localDb sem lógica
if (hasLogic(fs.readFileSync(LOCAL_DB, "utf8"))) {
failures.push(
`[#2 sem-lógica] src/lib/localDb.ts contém lógica (function/class/arrow). É camada de` +
` re-export apenas — mova a lógica para um módulo src/lib/db/.`
);
}
// (c) SQL cru fora de db/
const rawSql = findRawSql(collectSqlScanFiles());
if (rawSql.length) {
failures.push(
`[#5 sql-cru] ${rawSql.length} arquivo(s) com SQL cru fora de src/lib/db/:\n` +
rawSql.map((f) => `${f}`).join("\n") +
`\n → mova o SQL para um módulo src/lib/db/ (nunca SQL cru em rota/handler)` +
` ou congele em KNOWN_RAW_SQL com justificativa.`
);
}
if (failures.length) {
console.error(`[check-db-rules] FALHOU:\n\n` + failures.join("\n\n"));
process.exit(1);
}
console.log(
`[check-db-rules] OK (${dbModules.length} módulos db/, ${reexported.size} re-exportados, ` +
`${INTENTIONALLY_INTERNAL.size} intencionalmente-internos (Rule #2); ${EXTERNAL_DB_ALLOWED.size} leituras de DB externo permitidas (#3500))`
);
}
if (import.meta.url === pathToFileURL(process.argv[1] || "").href) main();