feat(04-01): generate jobs and admin controllers, reject model imports

Scaffold pact.Job and pact.AdminController stubs with Winter YAML
assets, and fail summer build when models imports a sibling package.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-09-18 13:41:13 +02:00
parent d8c56509f2
commit c87bbc37fb
7 changed files with 476 additions and 1 deletions

View File

@@ -24,6 +24,9 @@ type artifactData struct {
Ident string
Table string
Func string
Worker string
Kind string
ConfigDir string
MigrationID string
UpSQL string
DownSQL string
@@ -187,6 +190,124 @@ func MakeCommand(ctx context.Context, startDir, pluginID, name string) (Artifact
return ArtifactResult{PluginDir: plugin.Dir, Files: []string{path}, Hint: plugin.Hint}, nil
}
// MakeJob writes a pact.Job stub without importing River.
func MakeJob(ctx context.Context, startDir, pluginID, name string) (ArtifactResult, error) {
plugin, err := resolvePlugin(startDir, pluginID)
if err != nil {
return ArtifactResult{}, err
}
if err := validateExportedIdent(name); err != nil {
return ArtifactResult{}, fmt.Errorf("build: job: %w", err)
}
fn := name + "Job"
if err := rejectDuplicateIdent(plugin.Dir, "jobs", fn); err != nil {
return ArtifactResult{}, err
}
rel := filepath.Join("jobs", toSnake(name)+".go")
if err := rejectDuplicateFile(plugin.Dir, rel); err != nil {
return ArtifactResult{}, err
}
src, err := renderStub("job.go", artifactData{
Ident: name,
Func: fn,
Worker: unexportedIdent(name) + "Job",
Kind: plugin.ID + "." + toSnake(name),
})
if err != nil {
return ArtifactResult{}, err
}
path := filepath.Join(plugin.Dir, rel)
if err := writeFile(path, src); err != nil {
return ArtifactResult{}, err
}
if err := finishArtifact(ctx, plugin); err != nil {
os.Remove(path)
return ArtifactResult{}, err
}
return ArtifactResult{PluginDir: plugin.Dir, Files: []string{path}, Hint: plugin.Hint}, nil
}
// MakeAdminController writes a pact.AdminController and Winter-shaped YAML.
func MakeAdminController(ctx context.Context, startDir, pluginID, name string) (ArtifactResult, error) {
plugin, err := resolvePlugin(startDir, pluginID)
if err != nil {
return ArtifactResult{}, err
}
if err := validateExportedIdent(name); err != nil {
return ArtifactResult{}, fmt.Errorf("build: admin-controller: %w", err)
}
fn := name + "Controller"
if err := rejectDuplicateIdent(plugin.Dir, "controllers", fn); err != nil {
return ArtifactResult{}, err
}
snake := toSnake(name)
rel := filepath.Join("controllers", snake+".go")
if err := rejectDuplicateFile(plugin.Dir, rel); err != nil {
return ArtifactResult{}, err
}
configDir := filepath.ToSlash(filepath.Join("controllers", snake))
fieldsRel := filepath.Join("controllers", snake, "fields.yaml")
columnsRel := filepath.Join("controllers", snake, "columns.yaml")
for _, yamlRel := range []string{fieldsRel, columnsRel} {
if err := rejectDuplicateFile(plugin.Dir, yamlRel); err != nil {
return ArtifactResult{}, err
}
}
src, err := renderStub("admin_controller.go", artifactData{
Ident: name,
Func: fn,
Worker: unexportedIdent(name) + "Admin",
Kind: plugin.ID + "." + snake,
ConfigDir: configDir,
})
if err != nil {
return ArtifactResult{}, err
}
fieldsSrc, err := renderStub("fields.yaml", nil)
if err != nil {
return ArtifactResult{}, err
}
columnsSrc, err := renderStub("columns.yaml", nil)
if err != nil {
return ArtifactResult{}, err
}
var created []string
rollback := true
defer func() {
if rollback {
for _, path := range created {
os.Remove(path)
}
}
}()
goPath := filepath.Join(plugin.Dir, rel)
if err := writeFile(goPath, src); err != nil {
return ArtifactResult{}, err
}
created = append(created, goPath)
fieldsPath := filepath.Join(plugin.Dir, fieldsRel)
if err := writeFile(fieldsPath, fieldsSrc); err != nil {
return ArtifactResult{}, err
}
created = append(created, fieldsPath)
columnsPath := filepath.Join(plugin.Dir, columnsRel)
if err := writeFile(columnsPath, columnsSrc); err != nil {
return ArtifactResult{}, err
}
created = append(created, columnsPath)
if err := finishArtifact(ctx, plugin); err != nil {
return ArtifactResult{}, err
}
rollback = false
return ArtifactResult{PluginDir: plugin.Dir, Files: created, Hint: plugin.Hint}, nil
}
func unexportedIdent(name string) string {
r, size := utf8.DecodeRuneInString(name)
return string(unicode.ToLower(r)) + name[size:]
}
type pluginRef struct {
Dir string
ID string