Configuration hardening
This commit is contained in:
+170
-7
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -163,6 +164,11 @@ var commands = []command{
|
||||
description: "Inspect, explain, and validate the resolved project configuration.",
|
||||
run: runConfig,
|
||||
},
|
||||
{
|
||||
name: "music",
|
||||
description: "List, scan, build, and validate configured music datasets.",
|
||||
run: runMusic,
|
||||
},
|
||||
{
|
||||
name: "compare",
|
||||
description: "Compare current source content against the built archives.",
|
||||
@@ -336,6 +342,8 @@ type buildHAKOptions struct {
|
||||
filteredHAKs []string
|
||||
filteredArchives []string
|
||||
sourceManifest string
|
||||
musicDatasets []string
|
||||
skipMusic bool
|
||||
planOnly bool
|
||||
logLevel logLevel
|
||||
}
|
||||
@@ -567,14 +575,17 @@ func runBuildHAKs(ctx context) error {
|
||||
spin.configure(ctx.stderr, console.spinnerEnabled)
|
||||
spin.update("Build HAKs: starting")
|
||||
var result pipeline.BuildResult
|
||||
pipelineOpts := pipeline.BuildHAKOptions{
|
||||
Progress: console.progress,
|
||||
ArchiveNames: opts.filteredArchives,
|
||||
SourceManifestPath: opts.sourceManifest,
|
||||
SkipMusic: opts.skipMusic,
|
||||
MusicDatasetIDs: opts.musicDatasets,
|
||||
}
|
||||
if opts.planOnly {
|
||||
result, err = pipeline.PlanHAKsWithProgress(p, console.progress)
|
||||
result, err = pipeline.PlanHAKsWithOptions(p, pipelineOpts)
|
||||
} else {
|
||||
result, err = pipeline.BuildHAKsWithOptions(p, pipeline.BuildHAKOptions{
|
||||
Progress: console.progress,
|
||||
ArchiveNames: opts.filteredArchives,
|
||||
SourceManifestPath: opts.sourceManifest,
|
||||
})
|
||||
result, err = pipeline.BuildHAKsWithOptions(p, pipelineOpts)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -594,7 +605,7 @@ func parseBuildHAKArgs(args []string) (buildHAKOptions, error) {
|
||||
arg := args[index]
|
||||
switch arg {
|
||||
case "-h", "--help":
|
||||
return opts, errors.New("usage: build-haks [--hak <hak-name> ...] [--archive <archive-name> ...] [--source-manifest <path>] [--plan-only] [--quiet|--verbose|--debug]")
|
||||
return opts, errors.New("usage: build-haks [--hak <hak-name> ...] [--archive <archive-name> ...] [--source-manifest <path>] [--plan-only] [--skip-music] [--music-dataset <id> ...] [--quiet|--verbose|--debug]")
|
||||
case "--hak":
|
||||
index++
|
||||
if index >= len(args) {
|
||||
@@ -609,6 +620,14 @@ func parseBuildHAKArgs(args []string) (buildHAKOptions, error) {
|
||||
opts.filteredArchives = append(opts.filteredArchives, args[index])
|
||||
case "--plan-only":
|
||||
opts.planOnly = true
|
||||
case "--skip-music":
|
||||
opts.skipMusic = true
|
||||
case "--music-dataset":
|
||||
index++
|
||||
if index >= len(args) {
|
||||
return opts, errors.New("--music-dataset requires a value")
|
||||
}
|
||||
opts.musicDatasets = append(opts.musicDatasets, args[index])
|
||||
case "--quiet":
|
||||
opts.logLevel = logLevelQuiet
|
||||
case "--verbose":
|
||||
@@ -634,6 +653,10 @@ func parseBuildHAKArgs(args []string) (buildHAKOptions, error) {
|
||||
opts.sourceManifest = value
|
||||
continue
|
||||
}
|
||||
if value, ok := parseInlineFlagValue(arg, "--music-dataset"); ok {
|
||||
opts.musicDatasets = append(opts.musicDatasets, value)
|
||||
continue
|
||||
}
|
||||
if arg == "--quiet=true" {
|
||||
opts.logLevel = logLevelQuiet
|
||||
continue
|
||||
@@ -652,6 +675,146 @@ func parseInlineFlagValue(arg, name string) (string, bool) {
|
||||
return strings.TrimSpace(strings.TrimPrefix(arg, prefix)), true
|
||||
}
|
||||
|
||||
type musicCommandOptions struct {
|
||||
datasets []string
|
||||
json bool
|
||||
dryRun bool
|
||||
check bool
|
||||
force bool
|
||||
}
|
||||
|
||||
func runMusic(ctx context) error {
|
||||
if len(ctx.args) < 2 {
|
||||
return errors.New("usage: music <list-datasets|scan|build|credits|validate|manifest|normalize> [--dataset <id>] [--json] [--dry-run] [--check] [--force]")
|
||||
}
|
||||
p, err := loadProject(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.Scan(); err != nil {
|
||||
return err
|
||||
}
|
||||
subcommand := ctx.args[1]
|
||||
opts, err := parseMusicCommandArgs(ctx.args[2:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch subcommand {
|
||||
case "list-datasets":
|
||||
return emitMusicDatasets(ctx, p, opts)
|
||||
case "scan":
|
||||
result, err := pipeline.BuildMusic(p, pipeline.MusicBuildOptions{DatasetIDs: opts.datasets, Write: false})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emitMusicResult(ctx, "scan", result, opts)
|
||||
case "build", "credits":
|
||||
write := true
|
||||
if subcommand == "build" && opts.dryRun {
|
||||
write = false
|
||||
}
|
||||
if subcommand == "credits" && opts.check {
|
||||
write = false
|
||||
}
|
||||
result, err := pipeline.BuildMusic(p, pipeline.MusicBuildOptions{DatasetIDs: opts.datasets, Write: write})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emitMusicResult(ctx, subcommand, result, opts)
|
||||
case "manifest", "normalize":
|
||||
result, err := pipeline.BuildMusic(p, pipeline.MusicBuildOptions{DatasetIDs: opts.datasets, Write: false})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emitMusicResult(ctx, subcommand, result, opts)
|
||||
case "validate":
|
||||
if err := p.ValidateLayout(); err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := pipeline.BuildMusic(p, pipeline.MusicBuildOptions{DatasetIDs: opts.datasets, Write: false})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emitMusicResult(ctx, "validate", result, opts)
|
||||
default:
|
||||
return fmt.Errorf("unknown music subcommand %q", subcommand)
|
||||
}
|
||||
}
|
||||
|
||||
func parseMusicCommandArgs(args []string) (musicCommandOptions, error) {
|
||||
opts := musicCommandOptions{}
|
||||
for index := 0; index < len(args); index++ {
|
||||
arg := args[index]
|
||||
switch arg {
|
||||
case "--dataset":
|
||||
index++
|
||||
if index >= len(args) || strings.TrimSpace(args[index]) == "" {
|
||||
return opts, errors.New("--dataset requires a value")
|
||||
}
|
||||
opts.datasets = append(opts.datasets, args[index])
|
||||
case "--json":
|
||||
opts.json = true
|
||||
case "--dry-run":
|
||||
opts.dryRun = true
|
||||
case "--check":
|
||||
opts.check = true
|
||||
case "--force":
|
||||
opts.force = true
|
||||
default:
|
||||
if value, ok := parseInlineFlagValue(arg, "--dataset"); ok {
|
||||
opts.datasets = append(opts.datasets, value)
|
||||
continue
|
||||
}
|
||||
return opts, fmt.Errorf("unknown music argument %q", arg)
|
||||
}
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func emitMusicDatasets(ctx context, p *project.Project, opts musicCommandOptions) error {
|
||||
effective := p.EffectiveConfig()
|
||||
if opts.json {
|
||||
return emitConfigValue(ctx.stdout, effective.Music.Datasets, "json")
|
||||
}
|
||||
if len(effective.Music.Datasets) == 0 {
|
||||
fmt.Fprintln(ctx.stdout, "music datasets: none")
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintln(ctx.stdout, "music datasets:")
|
||||
ids := make([]string, 0, len(effective.Music.Datasets))
|
||||
for id := range effective.Music.Datasets {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Strings(ids)
|
||||
for _, id := range ids {
|
||||
ds := effective.Music.Datasets[id]
|
||||
fmt.Fprintf(ctx.stdout, " %s: source=%s output=%s prefix=%s\n", id, ds.Source, ds.Output, ds.Prefix)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func emitMusicResult(ctx context, action string, result pipeline.BuildResult, opts musicCommandOptions) error {
|
||||
if opts.json {
|
||||
return emitConfigValue(ctx.stdout, result.CreditsSummary, "json")
|
||||
}
|
||||
fmt.Fprintf(ctx.stdout, "music %s\n", action)
|
||||
if len(result.CreditsSummary.ScannedDirs) > 0 {
|
||||
fmt.Fprintf(ctx.stdout, "scanned: %s\n", strings.Join(result.CreditsSummary.ScannedDirs, ", "))
|
||||
}
|
||||
fmt.Fprintf(ctx.stdout, "tracks: %d\n", result.CreditsSummary.TrackCount)
|
||||
if action != "scan" && len(result.CreditsArtifactPaths) > 0 {
|
||||
fmt.Fprintf(ctx.stdout, "credits artifacts: %d\n", len(result.CreditsArtifactPaths))
|
||||
}
|
||||
if len(result.CreditsSummary.Mappings) > 0 && ctx.logLevel >= logLevelVerbose {
|
||||
fmt.Fprintln(ctx.stdout, "mappings:")
|
||||
for _, mapping := range result.CreditsSummary.Mappings {
|
||||
fmt.Fprintf(ctx.stdout, " %s -> %s\n", mapping.Source, mapping.Output)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(ctx.stdout, "status: ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
func runExtract(ctx context) error {
|
||||
p, err := loadProject(ctx)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user