CLI
Кожна команда hopak, її прапорці та згенерований результат.
CLI hopak — це єдине, що записує вихідні файли на диск: моделі, маршрути, CRUD, міграції, dev-сертифікати. Немає жодних конфіг-прапорців, які змусили б рантайм матеріалізувати код чи крипто за вас. Нижче — список усіх команд.
Встановлення
Глобально (рекомендовано)
bun add -g @hopak/cli
У $PATH з’являється бінарник hopak. Використовуйте його для
hopak new <name> та будь-якої разової команди.
Як dev-залежність
bun add -d @hopak/cli
Викликайте через bunx hopak <cmd> або через скрипти в
package.json. Фіксує версію CLI разом із проєктом — зручно для
команд і CI.
hopak new <name>
Створює новий проєкт Hopak у ./<name>/. Директорія не повинна
існувати. Типовий діалект — SQLite (нуль залежностей, працює
офлайн, файл зберігається у .hopak/data.db). Інший діалект
задається наперед через --db:
hopak new my-app # SQLite (default)
hopak new my-app --db postgres # Postgres — installs `postgres` driver
hopak new my-app --db mysql # MySQL — installs `mysql2` driver
hopak new my-app --db sqlite # explicit opt-in (same as default)
Прапорці:
| Прапорець | Ефект |
|---|---|
--db <sqlite|postgres|mysql> | Конфігурує hopak.config.ts, додає драйвер у package.json, засіває .env.example заглушкою DATABASE_URL. |
--no-install | Пропускає bun install — корисно для CI або офлайн-сценаріїв. |
Генерує (типово SQLite):
my-app/
├── app/
│ ├── models/post.ts # example model — edit fields to taste
│ ├── routes/index.ts # GET /
│ └── routes/api/
│ ├── posts.ts # GET list + POST create (uses crud.*)
│ └── posts/[id].ts # GET/PUT/PATCH/DELETE (uses crud.*)
├── public/ # static files
├── hopak.config.ts # database: { dialect: 'sqlite', ... }
├── main.ts # await hopak().listen()
├── tsconfig.json
├── package.json # depends on @hopak/core, dev-depends on @hopak/cli
├── .gitignore
├── .env.example
└── README.md
З --db postgres / --db mysql блок database: виглядає як
{ dialect: 'postgres', url: process.env.DATABASE_URL }, драйвер
(postgres або mysql2) додається в залежності, а .env.example
містить заглушку DATABASE_URL=….
Після створення:
cd my-app
# sqlite: just run —
hopak dev
# postgres / mysql: fill in the connection first —
cp .env.example .env
# edit DATABASE_URL, then:
hopak sync # CREATE TABLE IF NOT EXISTS for every model
hopak dev
hopak sync — це шлях початкового завантаження для нових проєктів.
Щойно потрібна еволюція схеми (додати колонку до наявної таблиці),
переходьте на міграції — hopak migrate init фіксує поточний стан,
а далі канонічний потік: hopak migrate new <name>. sync
відмовляється працювати, щойно в app/migrations/ з’являються
файли.
hopak dev
Запускає проєкт у режимі Bun --hot плюс легковаговий файловий
спостерігач за app/, щоб нові файли моделей чи маршрутів викликали
холодний рестарт (власний hot reload Bun патчить лише вже
імпортовані модулі).
hopak dev
На старті:
Hopak.js v0.5.0
↳ Listening on http://localhost:3000
↳ Database: sqlite
Зміни в наявних файлах перезавантажуються через HMR Bun зі
збереженням стану (мілісекунди). Додавання/видалення файлу маршруту
чи моделі виводить New/removed file under app/ — restarting…, і
дочірній dev-процес перезапускається.
Натисніть Ctrl-C, щоб зупинити.
hopak generate <kind> [<name>]
Генерує файли з шаблону. Псевдонім — hopak g. Чотири види:
| Вид | Що пише | Аргумент |
|---|---|---|
model <name> | app/models/<name>.ts (одна таблиця) | обов’язковий |
crud <name> | app/routes/api/<plural>.ts + app/routes/api/<plural>/[id].ts (REST для моделі) | обов’язковий |
route <path> | app/routes/<path>.ts (один обробник) | обов’язковий |
cert | .hopak/certs/dev.{key,crt} + локальний .gitignore (для локального HTTPS) | відсутній |
hopak generate model <name>
hopak generate model comment
# → Created file app/models/comment.ts
// app/models/comment.ts
import { model, text } from '@hopak/core';
export default model('comment', {
name: text().required(),
});
Відредагуйте поля під свою доменну модель. Створення самої моделі
вже дає типізований клієнт через ctx.db.model('comment'); таблиця
БД з’явиться на наступному hopak sync (або на першому hopak dev)
у проєкті без міграцій — якщо проєкт використовує міграції, напишіть
hopak migrate new add_comments + hopak migrate up. Далі
hopak generate crud comment згенерує HTTP-ендпоїнти.
hopak generate crud <name>
hopak generate crud post
# → Created file app/routes/api/posts.ts
# → Created file app/routes/api/posts/[id].ts
Кожен згенерований файл використовує crud-хелпери з @hopak/core:
// app/routes/api/posts.ts
import { crud } from '@hopak/core';
import post from '../../models/post';
export const GET = crud.list(post);
export const POST = crud.create(post);
// app/routes/api/posts/[id].ts
import { crud } from '@hopak/core';
import post from '../../../models/post';
export const GET = crud.read(post);
export const PUT = crud.update(post);
export const PATCH = crud.patch(post);
export const DELETE = crud.remove(post);
Шість ендпоїнтів на /api/<plural>/ — усі з пагінацією, валідацією
та вирізанням чутливих полів (password / secret / token). Щоб
налаштувати дієслово — замініть експорт власним defineRoute(...);
щоб прибрати дієслово повністю — видаліть експорт (маршрутизатор
відповідатиме 405 Method Not Allowed з заголовком Allow:, що
перелічує решту).
hopak generate route <path>
hopak generate route search
hopak generate route posts/[id]/publish
hopak generate route api/users/[id]
hopak generate route files/[...rest]
Створює файл за вказаним шляхом під app/routes/. Провідний / та
хвостовий .ts прибираються — posts/new.ts і posts/new обидва
дають app/routes/posts/new.ts. Шаблон починається з обробника
GET:
import { defineRoute } from '@hopak/core';
export const GET = defineRoute({
handler: (ctx) => ({ ok: true, path: ctx.path }),
});
Перейменуйте експорт у будь-яке дієслово або додайте кілька експортів у одному файлі.
hopak generate cert
hopak generate cert
# → Generating self-signed dev certificate { path: ".hopak/certs" }
# → Dev certificate ready. Re-run `hopak dev` with HTTPS enabled.
Один раз викликає openssl req -x509, пише трійку
key/cert/gitignore і виходить. Ідемпотентно — якщо обидва файли вже
існують, команда нічого не робить. Поєднуйте з
server.https.enabled: true у hopak.config.ts; hopak dev
відмовляється стартувати з увімкненим HTTPS, але без файлів
сертифіката, і вказує повернутися сюди.
Потрібен openssl на машині. macOS постачає його разом; на Linux:
apt install openssl / apk add openssl.
Політика відмови
hopak generate model/route/crud ніколи не перезаписує. Якщо
цільовий файл існує, команда завершується з кодом 1. Виняток —
generate cert: ідемпотентна і виходить із 0, коли файли вже
існують.
hopak use <capability>
Вмикає можливість у наявному проєкті. Одна команда встановлює потрібні пакети, патчить відповідні файли й додає ключі до env.
Запуск без аргументів показує, що доступно:
hopak use
# Usage: hopak use <capability>
#
# Available:
# sqlite SQLite via bun:sqlite (default, zero install)
# postgres Postgres via postgres.js
# mysql MySQL via mysql2
# request-log Per-request logging — tags each request with an id and logs method/path/status/ms
# auth JWT auth — signup/login/me routes + requireAuth() middleware
Діалекти БД — sqlite / postgres / mysql
Перемикання діалекту: встановлює драйвер (postgres / mysql2),
переписує блок database: у hopak.config.ts і додає
DATABASE_URL до .env.example.
hopak use postgres
hopak use mysql
hopak use sqlite
Патчер замінює блок за замовчуванням (той, що пише hopak new) на
місці, але відмовляється чіпати блок, який ви налаштували (власний
шлях файлу, додаткові URL-параметри, ssl-конфігурація) — він
друкує сніпет для вставки і виходить з 1, щоб налаштування не
втратилися мовчки.
Для нового проєкту краще hopak new <name> --db <dialect> — на
один крок менше.
request-log
Патчить main.ts так, щоб кожен запит отримував кореляційний id і
рядок журналу. Було:
import { hopak } from '@hopak/core';
await hopak().listen();
Стає:
import { hopak, requestId, requestLog } from '@hopak/core';
await hopak().before(requestId()).after(requestLog()).listen();
Наступні запуски бачать, що requestId + requestLog уже в
ланцюжку (за іменем фабрики, не за точним викликом), і повідомляють
Already using request-log — безпечно викликати в setup-скриптах.
Якщо main.ts відійшов від шаблону, патчер відмовляється і друкує
сніпет.
Формати ('simple' / 'json') та кастомні генератори id описані у
рецепті в основній документації.
auth
Генерує JWT-автентифікацію за обліковими даними однією командою. Створює:
app/middleware/auth.ts # exports requireAuth + signToken
app/routes/api/auth/signup.ts # POST /api/auth/signup
app/routes/api/auth/login.ts # POST /api/auth/login
app/routes/api/auth/me.ts # GET /api/auth/me (requires token)
app/models/user.ts # created only if you don't already have one
Також додає JWT_SECRET до .env.example і виконує
bun add @hopak/auth jose.
hopak use auth
# → files created, deps installed
# → next: copy .env.example → .env, set JWT_SECRET
#
# no migrations yet:
# hopak sync && hopak dev
#
# migrations already in use:
# hopak migrate new add_users
# # fill in up/down with CREATE TABLE users (...)
# hopak migrate up && hopak dev
Якщо якийсь зі згенерованих файлів уже є, команда відмовляється перезаписувати його і друкує сніпет, щоб його можна було доправити вручну. Повний API (OAuth-провайдери, RBAC, розширення claim) — у розділі Auth.
hopak sync
Створює відсутні таблиці з поточних моделей — початкове завантаження
для dev. Генерує CREATE TABLE IF NOT EXISTS для кожної
зареєстрованої моделі та CREATE INDEX IF NOT EXISTS для кожного
.index()-поля; топологічно сортує так, щоб цілі FK йшли перед
залежностями.
hopak sync
Syncing schema to database {"cwd":"/.../my-app"}
Schema synchronized {"models":3,"dialect":"sqlite"}
Можна викликати скільки завгодно разів. Корисно в CI, одразу після
hopak use postgres на чистій базі або перед першим hopak dev
на Postgres / MySQL. Не ALTERʼить наявні таблиці — щойно колонки
моделі розходяться з БД, sync друкує попередження про drift і
вказує на hopak migrate.
Коли з’являється app/migrations/, sync відмовляється працювати
та скеровує на hopak migrate up, щоб два механізми не конфліктували.
hopak migrate
Еволюція схеми з історією та відкатами. Псевдонім — hopak m. Підкоманди:
| Команда | Ефект |
|---|---|
hopak migrate init | Згенерувати початкову міграцію з поточних моделей (разово) |
hopak migrate new <name> | Порожній скелет файлу з up/down |
hopak migrate up [--to ID] [--dry-run] | Застосувати очікувані міграції |
hopak migrate down [--steps N] [--to ID] [--dry-run] | Відкотити (типово: останню 1) |
hopak migrate status | Застосовані / очікувані / відсутні |
Кожна міграція — один .ts-файл у app/migrations/:
// app/migrations/20260422T160100_add_role.ts
import type { MigrationContext } from '@hopak/core';
export const description = 'Add role column to user';
export async function up(ctx: MigrationContext): Promise<void> {
await ctx.sql`ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'user'`;
}
export async function down(ctx: MigrationContext): Promise<void> {
await ctx.sql`ALTER TABLE users DROP COLUMN role`;
}
ctx.db — повноцінний Hopak db-клієнт; використовуйте його для
міграцій даних (доповнення колонок, переписування рядків) поруч із
DDL у тому самому файлі.
SQLite + Postgres виконують кожну міграцію в транзакції. MySQL авто-комітить DDL, тож розбивайте багатокрокові зміни на окремі файли; інакше обрив посередині залишить часткову картину.
Повний покроковий опис — у розділі Міграції.
hopak check
Аудитує стан проєкту без запуску сервера. Друкує кольоровий звіт
про те, що Hopak побачить на старті: конфіг, розташування БД,
виявлені моделі, знайдені маршрути, статична директорія. Валідує
конфіг одразу й швидко падає на невірному dialect, port або
logLevel.
hopak check
✓ Config hopak.config.ts loaded
✓ Database sqlite (/.../my-app/.hopak/data.db)
✓ Models 3 loaded (comment, user, post)
✓ Routes 8 file route(s)
✓ Static serving public/
Виходить з кодом 1, якщо якийсь файл моделі/маршруту не
сканується або конфіг невірний — безпечно викликати у CI, щоб
зловити зламані скелети ще до dev-сервера.
Структура файлів
CLI покладається на стандартну структуру Hopak, якщо ви не перевизначили шляхи:
my-app/
├── app/
│ ├── models/ # hopak generate model <name> writes here
│ ├── routes/ # hopak generate route/crud writes here
│ └── migrations/ # hopak migrate init / new writes here
├── public/ # served as static files
├── .hopak/ # runtime state (db file, dev cert); gitignored
├── hopak.config.ts # optional
└── main.ts # entry point executed by hopak dev
hopak sync, hopak check та hopak dev читають
hopak.config.ts, щоб знайти ці директорії.
Власні шляхи проєкту
Перевизначте вихідні директорії у hopak.config.ts:
import { defineConfig } from '@hopak/core';
export default defineConfig({
paths: {
models: 'src/domain',
routes: 'src/api',
public: 'static',
},
});
Після цього hopak generate model post пише у src/domain/post.ts,
а hopak dev / hopak check шукають у нових локаціях.
Інтеграція зі скриптами package.json
Створений проєкт постачається з:
{
"scripts": {
"dev": "hopak dev",
"start": "bun run main.ts"
}
}
Типові розширення:
{
"scripts": {
"dev": "hopak dev",
"start": "bun run main.ts",
"sync": "hopak sync",
"check": "hopak check",
"test": "bun test"
}
}
Коли @hopak/cli зазначено як devDependency, bun run dev
викликає локальний бінарник — на цій машині глобальне
встановлення не потрібне.
hopak —version / —help
hopak --version # prints package version
hopak -v
hopak --help # prints command overview
hopak -h
hopak # (no args — same as --help)