Add `relspec job list` and `relspec job run <name>` driven by YAML job manifests (relspec.yml / relspec.<name>.yml), so multi-file merge and conversion workflows can be expressed declaratively instead of as long shell command lines. v1 contract (see docs/JOB_FILES.md): - `command` is a closed allow-list (convert, merge, scripts-list); no field accepts a shell string or executable path. - Deterministic discovery: default file first, then named files sorted lexically; all files merged into one namespace; duplicate job names across files are a hard error. - Every path resolves relative to the job file's directory; absolute, home-relative and directory-escaping paths are rejected at validation. - Database credentials referenced by env-var name via `conn_env:`; connection strings are never stored and are redacted from logs/plan. - Full validation (version, unknown fields, command/format, per-command input/output shape, path traversal, depends_on targets, dependency cycles) runs before anything is read, written or executed; per-job pre-flight then checks input existence, script dirs, env vars and the output overwrite policy for the whole plan. - `depends_on` closure runs in deterministic topological order; `--no-deps` runs only the named job. - `--dry-run` (alias `--plan`) prints the resolved plan and exits 0 without touching inputs, outputs or databases. - A failing job propagates the underlying non-zero exit status, logs FAILED (never OK), and writes no success marker. pkg/jobs is side-effect free (discovery/parse/validate/plan only); execution adapters live in cmd/relspec/job.go. Includes unit tests for discovery, validation, planning and path safety, plus CLI tests for end-to-end convert/merge, scripts-list across multiple directories, dry-run, dependency chains, exit-code propagation and log redaction. Deferred: live `scripts execute` from jobs, split/inspect/diff/templ commands, job-to-job output wiring, log rotation/retention. Refs #20 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
232 lines
8.3 KiB
Markdown
232 lines
8.3 KiB
Markdown
# RelSpec
|
|
|
|
[](https://git.warky.dev/wdevs/relspecgo/releases/latest)
|
|
[](https://git.warky.dev/wdevs/relspecgo/actions/workflows/ci.yml)
|
|
[](https://git.warky.dev/wdevs/relspecgo/actions/workflows/integration-tests.yml)
|
|
[](https://go.dev/dl/)
|
|
[](LICENSE)
|
|
|
|
> Bidirectional database schema conversion, validation, and templating tool.
|
|
|
|

|
|
|
|
## Install
|
|
|
|
```bash
|
|
go install -v git.warky.dev/wdevs/relspecgo/cmd/relspec@latest
|
|
```
|
|
|
|
## Supported Formats
|
|
|
|
| Direction | Formats |
|
|
|-----------|---------|
|
|
| **Readers** | `bun` `dbml` `dctx` `drawdb` `drizzle` `gorm` `graphql` `json` `mssql` `pgsql` `prisma` `sqldir` `sqlite` `typeorm` `yaml` |
|
|
| **Writers** | `bun` `dbml` `dctx` `drawdb` `drizzle` `gorm` `graphql` `json` `mssql` `pgsql` `prisma` `sqlexec` `sqlite` `template` `typeorm` `yaml` |
|
|
|
|
## Commands
|
|
|
|
### `convert` — Schema conversion
|
|
|
|
```bash
|
|
# PostgreSQL → GORM models
|
|
relspec convert --from pgsql --from-conn "postgres://user:pass@localhost/mydb" \
|
|
--to gorm --to-path models/ --package models
|
|
|
|
# DBML → PostgreSQL DDL
|
|
relspec convert --from dbml --from-path schema.dbml --to pgsql --to-path schema.sql
|
|
|
|
# PostgreSQL → SQLite (auto flattens schemas)
|
|
relspec convert --from pgsql --from-conn "postgres://..." --to sqlite --to-path schema.sql
|
|
|
|
# Multiple input files merged
|
|
relspec convert --from json --from-list "a.json,b.json" --to yaml --to-path merged.yaml
|
|
```
|
|
|
|
PostgreSQL connections opened by relspec set `application_name` by default to
|
|
`relspecgo/<version>` (with component suffixes internally, e.g. readers/writers).
|
|
If you need a custom value, provide `application_name` explicitly in the connection
|
|
string query parameters.
|
|
|
|
### `merge` — Additive schema merge (never modifies existing items)
|
|
|
|
```bash
|
|
# Merge two JSON schemas
|
|
relspec merge --target json --target-path base.json \
|
|
--source json --source-path additions.json \
|
|
--output json --output-path merged.json
|
|
|
|
# Merge PostgreSQL into JSON, skipping tables
|
|
relspec merge --target json --target-path current.json \
|
|
--source pgsql --source-conn "postgres://user:pass@localhost/db" \
|
|
--output json --output-path updated.json \
|
|
--skip-tables "audit_log,temp_tables"
|
|
```
|
|
|
|
Skip flags: `--skip-relations` `--skip-views` `--skip-domains` `--skip-enums` `--skip-sequences`
|
|
|
|
### `inspect` — Schema validation / linting
|
|
|
|
```bash
|
|
# Validate PostgreSQL database
|
|
relspec inspect --from pgsql --from-conn "postgres://user:pass@localhost/mydb"
|
|
|
|
# Validate DBML with custom rules
|
|
relspec inspect --from dbml --from-path schema.dbml --rules .relspec-rules.yaml
|
|
|
|
# JSON report output
|
|
relspec inspect --from json --from-path db.json --output-format json --output report.json
|
|
|
|
# Filter to specific schema
|
|
relspec inspect --from pgsql --from-conn "..." --schema public
|
|
```
|
|
|
|
Rules: naming conventions, PK/FK standards, missing indexes, reserved keywords, circular dependencies.
|
|
|
|
### `diff` — Schema comparison
|
|
|
|
```bash
|
|
relspec diff --from pgsql --from-conn "postgres://localhost/db1" \
|
|
--to pgsql --to-conn "postgres://localhost/db2"
|
|
```
|
|
|
|
### `templ` — Custom template rendering
|
|
|
|
```bash
|
|
# Render database schema to Markdown docs
|
|
relspec templ --from pgsql --from-conn "postgres://user:pass@localhost/db" \
|
|
--template docs.tmpl --output schema-docs.md
|
|
|
|
# One TypeScript file per table
|
|
relspec templ --from dbml --from-path schema.dbml \
|
|
--template ts-model.tmpl --mode table \
|
|
--output ./models/ --filename-pattern "{{.Name | toCamelCase}}.ts"
|
|
```
|
|
|
|
Modes: `database` (default) · `schema` · `table` · `script`
|
|
|
|
Template functions: string utils (`toCamelCase`, `toSnakeCase`, `pluralize`, …), type converters (`sqlToGo`, `sqlToTypeScript`, …), filters, loop helpers, safe access.
|
|
|
|
### `job` — Declarative job files
|
|
|
|
Run named jobs from a `relspec.yml` manifest instead of repeating long command lines.
|
|
|
|
```bash
|
|
# List jobs discovered in ./relspec.yml and ./relspec.<name>.yml (deterministic)
|
|
relspec job list
|
|
|
|
# Validate and print the plan without running anything
|
|
relspec job run build-schema --plan
|
|
|
|
# Run a job (and its declared dependencies)
|
|
relspec job run build-schema
|
|
```
|
|
|
|
```yaml
|
|
# relspec.yml
|
|
version: 1
|
|
jobs:
|
|
build-schema:
|
|
command: convert # closed allow-list: convert | merge | scripts-list
|
|
description: Merge the DBML sources and emit PostgreSQL DDL
|
|
inputs:
|
|
- path: schema/core.dbml
|
|
format: dbml
|
|
- path: schema/tenant.dbml
|
|
format: dbml
|
|
output:
|
|
format: pgsql
|
|
path: build/schema.sql
|
|
overwrite: true
|
|
options:
|
|
flatten_schema: false
|
|
logfile: .relspec/log/build-schema.log
|
|
```
|
|
|
|
The job system is **not** a shell: `command` is a fixed enum, every path is
|
|
resolved relative to the job file and may not escape it, and remote database
|
|
credentials are referenced by environment-variable name (`conn_env:`) and
|
|
redacted from logs. The whole plan — unknown commands/formats, duplicate job
|
|
names, missing inputs, path traversal, dependency cycles — is validated before
|
|
any job runs. See [docs/JOB_FILES.md](docs/JOB_FILES.md).
|
|
|
|
### `edit` — Interactive TUI editor
|
|
|
|
```bash
|
|
# Edit DBML schema interactively
|
|
relspec edit --from dbml --from-path schema.dbml --to dbml --to-path schema.dbml
|
|
|
|
# Edit live PostgreSQL database
|
|
relspec edit --from pgsql --from-conn "postgres://user:pass@localhost/mydb" \
|
|
--to pgsql --to-conn "postgres://user:pass@localhost/mydb"
|
|
```
|
|
|
|
<p align="center">
|
|
<img src="./assets/image/screenshots/main_screen.jpg">
|
|
</p>
|
|
<p align="center">
|
|
<img src="./assets/image/screenshots/table_view.jpg">
|
|
</p>
|
|
<p align="center">
|
|
<img src="./assets/image/screenshots/edit_column.jpg">
|
|
</p>
|
|
|
|
## Development
|
|
|
|
**Prerequisites:** Go 1.24.0+
|
|
|
|
```bash
|
|
make build # → build/relspec
|
|
make test # race detection + coverage
|
|
make lint # requires golangci-lint
|
|
make coverage # → coverage.html
|
|
make install # → $GOPATH/bin
|
|
```
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
cmd/relspec/ CLI commands
|
|
pkg/readers/ Input format readers
|
|
pkg/writers/ Output format writers
|
|
pkg/inspector/ Schema validation
|
|
pkg/diff/ Schema comparison
|
|
pkg/merge/ Schema merging
|
|
pkg/models/ Internal data models
|
|
pkg/transform/ Transformation logic
|
|
pkg/pgsql/ PostgreSQL utilities
|
|
pkg/sqltypes/ Nullable SQL types for generated/hand-written models (see below)
|
|
```
|
|
|
|
## Nullable Types (`pkg/sqltypes`)
|
|
|
|
The `bun` and `gorm` writers can generate model structs using
|
|
[`pkg/sqltypes`](./pkg/sqltypes/README.md) — nullable types (`SqlString`,
|
|
`SqlInt32`, `SqlTimeStamp`, `SqlStringArray`, …) that implement
|
|
`database/sql.Scanner`, `driver.Valuer`, and JSON/YAML/XML marshalling in one
|
|
type, selected via `--types sqltypes`. See the
|
|
[`pkg/sqltypes` README](./pkg/sqltypes/README.md) for the full type
|
|
reference, or the [`bun`](./pkg/writers/bun/README.md) /
|
|
[`gorm`](./pkg/writers/gorm/README.md) writer docs for the `--types` flag
|
|
(`sqltypes`, `stdlib`, or `baselib`). PostgreSQL array columns are the one
|
|
exception: the `bun` writer always generates native Go slices (`[]string`,
|
|
`[]int32`, …) with an explicit `array` bun tag, regardless of `--types` —
|
|
see [`bun`'s `--array-nullable`](./pkg/writers/bun/README.md#nullablearrays)
|
|
flag for nullable-array handling. The `SqlXxxArray` wrapper types remain
|
|
available in `pkg/sqltypes` and are still used by the `gorm` writer.
|
|
|
|
## Contributing
|
|
|
|
1. Register or sign in with GitHub at [git.warky.dev](https://git.warky.dev)
|
|
2. Clone the repository: `git clone https://git.warky.dev/wdevs/relspecgo.git`
|
|
3. Create a feature branch: `git checkout -b feature/your-feature-name`
|
|
4. Commit your changes and push the branch
|
|
5. Open a pull request with a description of the new feature or fix
|
|
|
|
For questions or discussion, join the Discord: [discord.gg/74rcTujp25](https://discord.gg/74rcTujp25) — `warkyhein`
|
|
|
|
## Links
|
|
|
|
- [Todo](./TODO.md)
|
|
- [AI Use Policy](./AI_USE.md)
|
|
- [License](LICENSE) — Apache 2.0 · Copyright 2025 Warky Devs
|