feat(job): support templ command

This commit is contained in:
SG Command
2026-09-08 06:17:45 +02:00
parent cdd066dafe
commit 3d57c947cd
4 changed files with 170 additions and 12 deletions
+61 -1
View File
@@ -16,7 +16,9 @@ import (
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/readers"
"git.warky.dev/wdevs/relspecgo/pkg/readers/sqldir"
"git.warky.dev/wdevs/relspecgo/pkg/writers"
wpgsql "git.warky.dev/wdevs/relspecgo/pkg/writers/pgsql"
wtemplate "git.warky.dev/wdevs/relspecgo/pkg/writers/template"
)
var (
@@ -54,7 +56,7 @@ inputs, output and options:
logfile: .relspec/log/build-schema.log
Rules and guarantees:
- command is a closed allow-list (convert, merge, scripts-list). Arbitrary
- command is a closed allow-list (convert, merge, scripts-list, templ). Arbitrary
shell strings are never executed.
- Every path is relative to the directory holding the job file and may not
escape it. Absolute and home-relative paths are rejected.
@@ -215,6 +217,7 @@ type resolvedJob struct {
outputConn string // resolved connection string (secret)
outputConnEnv string
logPath string
templatePath string
secrets []string // resolved secret values to redact from logs
}
@@ -236,6 +239,17 @@ func preflightJob(j *jobs.Job) (*resolvedJob, error) {
}
rj.logPath = p
}
if j.Template != "" {
p, err := jobs.SafeJoin(root, j.Template)
if err != nil {
return nil, fmt.Errorf("template: %w", err)
}
info, err := os.Stat(p)
if err != nil || info.IsDir() {
return nil, fmt.Errorf("template %q: not found or is a directory", j.Template)
}
rj.templatePath = p
}
for i, in := range j.Inputs {
ri := resolvedInput{format: strings.ToLower(in.Format)}
@@ -348,6 +362,8 @@ func executeResolvedJob(rj *resolvedJob) (err error) {
err = runMergeJob(rj, lg)
case jobs.CommandScriptsList:
err = runScriptsListJob(rj, lg)
case jobs.CommandTempl:
err = runTemplJob(rj, lg)
default:
err = fmt.Errorf("unsupported command %q", rj.job.Command)
}
@@ -359,6 +375,50 @@ func executeResolvedJob(rj *resolvedJob) (err error) {
return err
}
func runTemplJob(rj *resolvedJob, lg *jobLogger) error {
db, err := readJobInputs(rj, lg)
if err != nil {
return err
}
if schema := rj.job.Options.Schema; schema != "" {
found := false
for _, s := range db.Schemas {
if s.Name == schema {
db.Schemas = []*models.Schema{s}
found = true
break
}
}
if !found {
return fmt.Errorf("schema not found: %s", schema)
}
}
mode := rj.job.Mode
if mode == "" {
mode = "database"
}
pattern := rj.job.FilenamePattern
if pattern == "" {
pattern = "{{.Name}}.txt"
}
writer, err := wtemplate.NewWriter(&writers.WriterOptions{
OutputPath: rj.outputPath,
Metadata: map[string]interface{}{
"template_path": rj.templatePath,
"mode": mode,
"filename_pattern": pattern,
},
})
if err != nil {
return fmt.Errorf("create template writer: %w", err)
}
lg.logf("applying template: %s (mode %s)", rj.templatePath, mode)
if err := writer.WriteDatabase(db); err != nil {
return fmt.Errorf("execute template: %w", err)
}
return nil
}
func runConvertJob(rj *resolvedJob, lg *jobLogger) error {
db, err := readJobInputs(rj, lg)
if err != nil {