Unify session-aware CLI parsing and add session-id compatibility
This commit is contained in:
@@ -16,7 +16,6 @@ import (
|
|||||||
// Clean removes local workspace/spool state while preserving durable cache
|
// Clean removes local workspace/spool state while preserving durable cache
|
||||||
// state unless cache cleanup is explicitly requested.
|
// state unless cache cleanup is explicitly requested.
|
||||||
func Clean(ctx context.Context, args []string, out io.Writer) error {
|
func Clean(ctx context.Context, args []string, out io.Writer) error {
|
||||||
positionalSessionID, args := pullLeadingSessionID(args)
|
|
||||||
fs := flag.NewFlagSet("clean", flag.ContinueOnError)
|
fs := flag.NewFlagSet("clean", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
var flags commonConfigFlags
|
var flags commonConfigFlags
|
||||||
@@ -27,20 +26,8 @@ func Clean(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
fs.BoolVar(&all, "all", false, "clean all local session work/spool state")
|
fs.BoolVar(&all, "all", false, "clean all local session work/spool state")
|
||||||
fs.BoolVar(&dryRun, "dry-run", false, "print cleanup targets without deleting")
|
fs.BoolVar(&dryRun, "dry-run", false, "print cleanup targets without deleting")
|
||||||
fs.BoolVar(&clearCache, "clear-cache", false, "also clear durable S3 audio cache entries")
|
fs.BoolVar(&clearCache, "clear-cache", false, "also clear durable S3 audio cache entries")
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := parseSessionAwareFlags("clean", fs, args, &flags.sessionID); err != nil {
|
||||||
return fmt.Errorf("clean: invalid flags: %w", err)
|
return err
|
||||||
}
|
|
||||||
if positionalSessionID == "" {
|
|
||||||
if err := applyParsedSessionIDArg("clean", fs, &flags.sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("clean: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("clean", positionalSessionID, &flags.sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if all {
|
if all {
|
||||||
return cleanAllLocal(flags, dryRun, clearCache, out)
|
return cleanAllLocal(flags, dryRun, clearCache, out)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ func addCommonConfigFlags(fs *flag.FlagSet, flags *commonConfigFlags) {
|
|||||||
fs.StringVar(&flags.campaignPath, "campaign", "", "campaign ID")
|
fs.StringVar(&flags.campaignPath, "campaign", "", "campaign ID")
|
||||||
fs.StringVar(&flags.campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
fs.StringVar(&flags.campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
||||||
fs.StringVar(&flags.sessionPath, "session", "", "path to session.yml")
|
fs.StringVar(&flags.sessionPath, "session", "", "path to session.yml")
|
||||||
|
fs.StringVar(&flags.sessionID, "session-id", "", "session identifier")
|
||||||
fs.StringVar(&flags.previousSessionID, "previous-session-id", "", "expected previous session identifier")
|
fs.StringVar(&flags.previousSessionID, "previous-session-id", "", "expected previous session identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,25 +112,12 @@ func Artifacts(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
|
|
||||||
// SessionValidate performs a read-only session preflight.
|
// SessionValidate performs a read-only session preflight.
|
||||||
func SessionValidate(ctx context.Context, args []string, out io.Writer) error {
|
func SessionValidate(ctx context.Context, args []string, out io.Writer) error {
|
||||||
positionalSessionID, args := pullLeadingSessionID(args)
|
|
||||||
fs := flag.NewFlagSet("session validate", flag.ContinueOnError)
|
fs := flag.NewFlagSet("session validate", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
var flags commonConfigFlags
|
var flags commonConfigFlags
|
||||||
addCommonConfigFlags(fs, &flags)
|
addCommonConfigFlags(fs, &flags)
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := parseSessionAwareFlags("session validate", fs, args, &flags.sessionID); err != nil {
|
||||||
return fmt.Errorf("session validate: invalid flags: %w", err)
|
return err
|
||||||
}
|
|
||||||
if positionalSessionID == "" {
|
|
||||||
if err := applyParsedSessionIDArg("session validate", fs, &flags.sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("session validate: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("session validate", positionalSessionID, &flags.sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(flags.sessionID) == "" {
|
if strings.TrimSpace(flags.sessionID) == "" {
|
||||||
return fmt.Errorf("session validate: session_id is required")
|
return fmt.Errorf("session validate: session_id is required")
|
||||||
@@ -193,25 +181,12 @@ func SessionValidate(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
|
|
||||||
// Status reports effective local/remote session state.
|
// Status reports effective local/remote session state.
|
||||||
func Status(ctx context.Context, args []string, out io.Writer) error {
|
func Status(ctx context.Context, args []string, out io.Writer) error {
|
||||||
positionalSessionID, args := pullLeadingSessionID(args)
|
|
||||||
fs := flag.NewFlagSet("status", flag.ContinueOnError)
|
fs := flag.NewFlagSet("status", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
var flags commonConfigFlags
|
var flags commonConfigFlags
|
||||||
addCommonConfigFlags(fs, &flags)
|
addCommonConfigFlags(fs, &flags)
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := parseSessionAwareFlags("status", fs, args, &flags.sessionID); err != nil {
|
||||||
return fmt.Errorf("status: invalid flags: %w", err)
|
return err
|
||||||
}
|
|
||||||
if positionalSessionID == "" {
|
|
||||||
if err := applyParsedSessionIDArg("status", fs, &flags.sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("status: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("status", positionalSessionID, &flags.sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(flags.sessionID) == "" {
|
if strings.TrimSpace(flags.sessionID) == "" {
|
||||||
return fmt.Errorf("status: session_id is required")
|
return fmt.Errorf("status: session_id is required")
|
||||||
@@ -282,7 +257,6 @@ func Status(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
|
|
||||||
// SessionInit creates a local or remote session.yml skeleton.
|
// SessionInit creates a local or remote session.yml skeleton.
|
||||||
func SessionInit(ctx context.Context, args []string, out io.Writer) error {
|
func SessionInit(ctx context.Context, args []string, out io.Writer) error {
|
||||||
positionalSessionID, args := pullLeadingSessionID(args)
|
|
||||||
fs := flag.NewFlagSet("session init", flag.ContinueOnError)
|
fs := flag.NewFlagSet("session init", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
var pipelinePath, campaignPath, campaignFilePath, sessionID, previousSessionID, date, title, output, audioS3Prefix, audioDir string
|
var pipelinePath, campaignPath, campaignFilePath, sessionID, previousSessionID, date, title, output, audioS3Prefix, audioDir string
|
||||||
@@ -290,6 +264,7 @@ func SessionInit(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml (optional; defaults searched)")
|
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml (optional; defaults searched)")
|
||||||
fs.StringVar(&campaignPath, "campaign", "", "campaign ID")
|
fs.StringVar(&campaignPath, "campaign", "", "campaign ID")
|
||||||
fs.StringVar(&campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
fs.StringVar(&campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
||||||
|
fs.StringVar(&sessionID, "session-id", "", "session identifier")
|
||||||
fs.StringVar(&previousSessionID, "previous-session-id", "", "previous session identifier")
|
fs.StringVar(&previousSessionID, "previous-session-id", "", "previous session identifier")
|
||||||
fs.StringVar(&date, "date", "", "session date")
|
fs.StringVar(&date, "date", "", "session date")
|
||||||
fs.StringVar(&title, "title", "", "session title")
|
fs.StringVar(&title, "title", "", "session title")
|
||||||
@@ -298,20 +273,8 @@ func SessionInit(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
fs.StringVar(&audioDir, "audio-dir", "", "local audio directory")
|
fs.StringVar(&audioDir, "audio-dir", "", "local audio directory")
|
||||||
fs.BoolVar(&remote, "remote", false, "write session.yml to S3 session prefix")
|
fs.BoolVar(&remote, "remote", false, "write session.yml to S3 session prefix")
|
||||||
fs.BoolVar(&force, "force", false, "overwrite existing target")
|
fs.BoolVar(&force, "force", false, "overwrite existing target")
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := parseSessionAwareFlags("session init", fs, args, &sessionID); err != nil {
|
||||||
return fmt.Errorf("session init: invalid flags: %w", err)
|
return err
|
||||||
}
|
|
||||||
if positionalSessionID == "" {
|
|
||||||
if err := applyParsedSessionIDArg("session init", fs, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("session init: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("session init", positionalSessionID, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(sessionID) == "" {
|
if strings.TrimSpace(sessionID) == "" {
|
||||||
return fmt.Errorf("session init: session_id is required")
|
return fmt.Errorf("session init: session_id is required")
|
||||||
@@ -405,27 +368,14 @@ func SessionInit(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
|
|
||||||
// ArtifactsList lists effective artifact sources.
|
// ArtifactsList lists effective artifact sources.
|
||||||
func ArtifactsList(ctx context.Context, args []string, out io.Writer) error {
|
func ArtifactsList(ctx context.Context, args []string, out io.Writer) error {
|
||||||
positionalSessionID, args := pullLeadingSessionID(args)
|
|
||||||
fs := flag.NewFlagSet("artifacts list", flag.ContinueOnError)
|
fs := flag.NewFlagSet("artifacts list", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
var flags commonConfigFlags
|
var flags commonConfigFlags
|
||||||
var remote bool
|
var remote bool
|
||||||
addCommonConfigFlags(fs, &flags)
|
addCommonConfigFlags(fs, &flags)
|
||||||
fs.BoolVar(&remote, "remote", false, "inspect remote publish availability")
|
fs.BoolVar(&remote, "remote", false, "inspect remote publish availability")
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := parseSessionAwareFlags("artifacts list", fs, args, &flags.sessionID); err != nil {
|
||||||
return fmt.Errorf("artifacts list: invalid flags: %w", err)
|
return err
|
||||||
}
|
|
||||||
if positionalSessionID == "" {
|
|
||||||
if err := applyParsedSessionIDArg("artifacts list", fs, &flags.sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("artifacts list: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("artifacts list", positionalSessionID, &flags.sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(flags.sessionID) == "" {
|
if strings.TrimSpace(flags.sessionID) == "" {
|
||||||
return fmt.Errorf("artifacts list: session_id is required")
|
return fmt.Errorf("artifacts list: session_id is required")
|
||||||
@@ -463,25 +413,12 @@ func Locks(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
|
|
||||||
// LocksList lists effective publish locks.
|
// LocksList lists effective publish locks.
|
||||||
func LocksList(ctx context.Context, args []string, out io.Writer) error {
|
func LocksList(ctx context.Context, args []string, out io.Writer) error {
|
||||||
positionalSessionID, args := pullLeadingSessionID(args)
|
|
||||||
fs := flag.NewFlagSet("locks", flag.ContinueOnError)
|
fs := flag.NewFlagSet("locks", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
var flags commonConfigFlags
|
var flags commonConfigFlags
|
||||||
addCommonConfigFlags(fs, &flags)
|
addCommonConfigFlags(fs, &flags)
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := parseSessionAwareFlags("locks", fs, args, &flags.sessionID); err != nil {
|
||||||
return fmt.Errorf("locks: invalid flags: %w", err)
|
return err
|
||||||
}
|
|
||||||
if positionalSessionID == "" {
|
|
||||||
if err := applyParsedSessionIDArg("locks", fs, &flags.sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("locks: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("locks", positionalSessionID, &flags.sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(flags.sessionID) == "" {
|
if strings.TrimSpace(flags.sessionID) == "" {
|
||||||
return fmt.Errorf("locks: session_id is required")
|
return fmt.Errorf("locks: session_id is required")
|
||||||
@@ -515,11 +452,18 @@ func LocksAdd(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
return fmt.Errorf("locks add: invalid flags: %w", err)
|
return fmt.Errorf("locks add: invalid flags: %w", err)
|
||||||
}
|
}
|
||||||
if source == "" {
|
if source == "" {
|
||||||
if fs.NArg() != 2 {
|
switch fs.NArg() {
|
||||||
|
case 2:
|
||||||
|
positionalSessionID = strings.TrimSpace(fs.Arg(0))
|
||||||
|
source = strings.TrimSpace(fs.Arg(1))
|
||||||
|
case 1:
|
||||||
|
if strings.TrimSpace(flags.sessionID) == "" {
|
||||||
|
return fmt.Errorf("locks add: expected session_id and source id")
|
||||||
|
}
|
||||||
|
source = strings.TrimSpace(fs.Arg(0))
|
||||||
|
default:
|
||||||
return fmt.Errorf("locks add: expected session_id and source id")
|
return fmt.Errorf("locks add: expected session_id and source id")
|
||||||
}
|
}
|
||||||
positionalSessionID = strings.TrimSpace(fs.Arg(0))
|
|
||||||
source = strings.TrimSpace(fs.Arg(1))
|
|
||||||
} else if fs.NArg() != 0 {
|
} else if fs.NArg() != 0 {
|
||||||
return fmt.Errorf("locks add: unexpected positional arguments")
|
return fmt.Errorf("locks add: unexpected positional arguments")
|
||||||
}
|
}
|
||||||
@@ -572,11 +516,18 @@ func LocksRemove(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
return fmt.Errorf("locks remove: invalid flags: %w", err)
|
return fmt.Errorf("locks remove: invalid flags: %w", err)
|
||||||
}
|
}
|
||||||
if source == "" {
|
if source == "" {
|
||||||
if fs.NArg() != 2 {
|
switch fs.NArg() {
|
||||||
|
case 2:
|
||||||
|
positionalSessionID = strings.TrimSpace(fs.Arg(0))
|
||||||
|
source = strings.TrimSpace(fs.Arg(1))
|
||||||
|
case 1:
|
||||||
|
if strings.TrimSpace(flags.sessionID) == "" {
|
||||||
|
return fmt.Errorf("locks remove: expected session_id and source id")
|
||||||
|
}
|
||||||
|
source = strings.TrimSpace(fs.Arg(0))
|
||||||
|
default:
|
||||||
return fmt.Errorf("locks remove: expected session_id and source id")
|
return fmt.Errorf("locks remove: expected session_id and source id")
|
||||||
}
|
}
|
||||||
positionalSessionID = strings.TrimSpace(fs.Arg(0))
|
|
||||||
source = strings.TrimSpace(fs.Arg(1))
|
|
||||||
} else if fs.NArg() != 0 {
|
} else if fs.NArg() != 0 {
|
||||||
return fmt.Errorf("locks remove: unexpected positional arguments")
|
return fmt.Errorf("locks remove: unexpected positional arguments")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
||||||
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
||||||
@@ -17,46 +16,21 @@ import (
|
|||||||
|
|
||||||
// Plan validates configuration, prepares the local workdir, and prints stage order.
|
// Plan validates configuration, prepares the local workdir, and prints stage order.
|
||||||
func Plan(ctx context.Context, args []string, out io.Writer) error {
|
func Plan(ctx context.Context, args []string, out io.Writer) error {
|
||||||
positionalSessionID, args := pullLeadingSessionID(args)
|
|
||||||
fs := flag.NewFlagSet("plan", flag.ContinueOnError)
|
fs := flag.NewFlagSet("plan", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
|
|
||||||
var pipelinePath string
|
var flags commonConfigFlags
|
||||||
var campaignPath string
|
|
||||||
var campaignFilePath string
|
|
||||||
var sessionPath string
|
|
||||||
var sessionID string
|
|
||||||
var previousSessionID string
|
|
||||||
var force bool
|
var force bool
|
||||||
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml (optional; defaults searched)")
|
addCommonConfigFlags(fs, &flags)
|
||||||
fs.StringVar(&campaignPath, "campaign", "", "campaign ID")
|
|
||||||
fs.StringVar(&campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
|
||||||
fs.StringVar(&sessionPath, "session", "", "path to session.yml")
|
|
||||||
fs.StringVar(&previousSessionID, "previous-session-id", "", "expected previous session identifier")
|
|
||||||
fs.BoolVar(&force, "force", false, "force stage execution (reserved for future behavior)")
|
fs.BoolVar(&force, "force", false, "force stage execution (reserved for future behavior)")
|
||||||
|
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := parseSessionAwareFlags("plan", fs, args, &flags.sessionID); err != nil {
|
||||||
return fmt.Errorf("plan: invalid flags: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
if positionalSessionID == "" {
|
if flags.sessionID == "" {
|
||||||
if err := applyParsedSessionIDArg("plan", fs, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("plan: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("plan", positionalSessionID, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(sessionID) == "" {
|
|
||||||
return fmt.Errorf("plan: session_id is required")
|
return fmt.Errorf("plan: session_id is required")
|
||||||
}
|
}
|
||||||
cfg, err := loadCommandConfig(ctx, pipelinePath, campaignPath, campaignFilePath, sessionPath, config.SessionLoadOptions{
|
cfg, err := loadCommandConfig(ctx, flags.pipelinePath, flags.campaignPath, flags.campaignFilePath, flags.sessionPath, flags.sessionOptions())
|
||||||
SessionID: sessionID,
|
|
||||||
PreviousSessionID: previousSessionID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("plan: %w", err)
|
return fmt.Errorf("plan: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,20 +27,11 @@ func Restore(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
fs := flag.NewFlagSet("restore", flag.ContinueOnError)
|
fs := flag.NewFlagSet("restore", flag.ContinueOnError)
|
||||||
fs.SetOutput(out)
|
fs.SetOutput(out)
|
||||||
|
|
||||||
var pipelinePath string
|
var flags commonConfigFlags
|
||||||
var campaignPath string
|
|
||||||
var campaignFilePath string
|
|
||||||
var sessionPath string
|
|
||||||
var sessionID string
|
|
||||||
var previousSessionID string
|
|
||||||
var dryRun bool
|
var dryRun bool
|
||||||
var force bool
|
var force bool
|
||||||
var includeAudio bool
|
var includeAudio bool
|
||||||
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml (optional; defaults searched)")
|
addCommonConfigFlags(fs, &flags)
|
||||||
fs.StringVar(&campaignPath, "campaign", "", "campaign ID")
|
|
||||||
fs.StringVar(&campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
|
||||||
fs.StringVar(&sessionPath, "session", "", "path to session.yml")
|
|
||||||
fs.StringVar(&previousSessionID, "previous-session-id", "", "expected previous session identifier")
|
|
||||||
fs.BoolVar(&dryRun, "dry-run", false, "plan restore actions without writing local files")
|
fs.BoolVar(&dryRun, "dry-run", false, "plan restore actions without writing local files")
|
||||||
fs.BoolVar(&force, "force", false, "overwrite local conflicts with remote state")
|
fs.BoolVar(&force, "force", false, "overwrite local conflicts with remote state")
|
||||||
fs.BoolVar(&includeAudio, "include-audio", false, "include archived session-level audio objects")
|
fs.BoolVar(&includeAudio, "include-audio", false, "include archived session-level audio objects")
|
||||||
@@ -57,25 +48,13 @@ func Restore(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("restore: invalid flags: %w", err)
|
return fmt.Errorf("restore: invalid flags: %w", err)
|
||||||
}
|
}
|
||||||
if positionalSessionID == "" {
|
if err := resolveParsedSessionID("restore", positionalSessionID, fs, &flags.sessionID); err != nil {
|
||||||
if err := applyParsedSessionIDArg("restore", fs, &sessionID); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("restore: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("restore", positionalSessionID, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(sessionID) == "" {
|
if strings.TrimSpace(flags.sessionID) == "" {
|
||||||
return fmt.Errorf("restore: session_id is required")
|
return fmt.Errorf("restore: session_id is required")
|
||||||
}
|
}
|
||||||
cfg, err := loadCommandConfig(ctx, pipelinePath, campaignPath, campaignFilePath, sessionPath, config.SessionLoadOptions{
|
cfg, err := loadCommandConfig(ctx, flags.pipelinePath, flags.campaignPath, flags.campaignFilePath, flags.sessionPath, flags.sessionOptions())
|
||||||
SessionID: sessionID,
|
|
||||||
PreviousSessionID: previousSessionID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("restore: %w", err)
|
return fmt.Errorf("restore: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
||||||
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
||||||
@@ -14,48 +13,23 @@ import (
|
|||||||
|
|
||||||
// Resume continues execution from the first non-succeeded stage in the manifest.
|
// Resume continues execution from the first non-succeeded stage in the manifest.
|
||||||
func Resume(ctx context.Context, args []string, out io.Writer) error {
|
func Resume(ctx context.Context, args []string, out io.Writer) error {
|
||||||
positionalSessionID, args := pullLeadingSessionID(args)
|
|
||||||
fs := flag.NewFlagSet("resume", flag.ContinueOnError)
|
fs := flag.NewFlagSet("resume", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
|
|
||||||
var pipelinePath string
|
var flags commonConfigFlags
|
||||||
var campaignPath string
|
|
||||||
var campaignFilePath string
|
|
||||||
var sessionPath string
|
|
||||||
var sessionID string
|
|
||||||
var previousSessionID string
|
|
||||||
var force bool
|
var force bool
|
||||||
var selectedArtifacts artifactSelectionFlag
|
var selectedArtifacts artifactSelectionFlag
|
||||||
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml (optional; defaults searched)")
|
addCommonConfigFlags(fs, &flags)
|
||||||
fs.StringVar(&campaignPath, "campaign", "", "campaign ID")
|
|
||||||
fs.StringVar(&campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
|
||||||
fs.StringVar(&sessionPath, "session", "", "path to session.yml")
|
|
||||||
fs.StringVar(&previousSessionID, "previous-session-id", "", "expected previous session identifier")
|
|
||||||
fs.BoolVar(&force, "force", false, "force stage execution")
|
fs.BoolVar(&force, "force", false, "force stage execution")
|
||||||
fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to execute and publish (comma-separated or repeatable)")
|
fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to execute and publish (comma-separated or repeatable)")
|
||||||
|
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := parseSessionAwareFlags("resume", fs, args, &flags.sessionID); err != nil {
|
||||||
return fmt.Errorf("resume: invalid flags: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
if positionalSessionID == "" {
|
if flags.sessionID == "" {
|
||||||
if err := applyParsedSessionIDArg("resume", fs, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("resume: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("resume", positionalSessionID, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(sessionID) == "" {
|
|
||||||
return fmt.Errorf("resume: session_id is required")
|
return fmt.Errorf("resume: session_id is required")
|
||||||
}
|
}
|
||||||
cfg, err := loadCommandConfig(ctx, pipelinePath, campaignPath, campaignFilePath, sessionPath, config.SessionLoadOptions{
|
cfg, err := loadCommandConfig(ctx, flags.pipelinePath, flags.campaignPath, flags.campaignFilePath, flags.sessionPath, flags.sessionOptions())
|
||||||
SessionID: sessionID,
|
|
||||||
PreviousSessionID: previousSessionID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("resume: %w", err)
|
return fmt.Errorf("resume: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,55 +5,29 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run executes the pipeline plan and persists manifest state.
|
// Run executes the pipeline plan and persists manifest state.
|
||||||
func Run(ctx context.Context, args []string, out io.Writer) error {
|
func Run(ctx context.Context, args []string, out io.Writer) error {
|
||||||
positionalSessionID, args := pullLeadingSessionID(args)
|
|
||||||
fs := flag.NewFlagSet("run", flag.ContinueOnError)
|
fs := flag.NewFlagSet("run", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
|
|
||||||
var pipelinePath string
|
var flags commonConfigFlags
|
||||||
var campaignPath string
|
|
||||||
var campaignFilePath string
|
|
||||||
var sessionPath string
|
|
||||||
var sessionID string
|
|
||||||
var previousSessionID string
|
|
||||||
var force bool
|
var force bool
|
||||||
var selectedArtifacts artifactSelectionFlag
|
var selectedArtifacts artifactSelectionFlag
|
||||||
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml (optional; defaults searched)")
|
addCommonConfigFlags(fs, &flags)
|
||||||
fs.StringVar(&campaignPath, "campaign", "", "campaign ID")
|
|
||||||
fs.StringVar(&campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
|
||||||
fs.StringVar(&sessionPath, "session", "", "path to session.yml")
|
|
||||||
fs.StringVar(&previousSessionID, "previous-session-id", "", "expected previous session identifier")
|
|
||||||
fs.BoolVar(&force, "force", false, "force stage execution (reserved for future behavior)")
|
fs.BoolVar(&force, "force", false, "force stage execution (reserved for future behavior)")
|
||||||
fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to execute and publish (comma-separated or repeatable)")
|
fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to execute and publish (comma-separated or repeatable)")
|
||||||
|
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := parseSessionAwareFlags("run", fs, args, &flags.sessionID); err != nil {
|
||||||
return fmt.Errorf("run: invalid flags: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
if positionalSessionID == "" {
|
if flags.sessionID == "" {
|
||||||
if err := applyParsedSessionIDArg("run", fs, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("run: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("run", positionalSessionID, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(sessionID) == "" {
|
|
||||||
return fmt.Errorf("run: session_id is required")
|
return fmt.Errorf("run: session_id is required")
|
||||||
}
|
}
|
||||||
cfg, err := loadCommandConfig(ctx, pipelinePath, campaignPath, campaignFilePath, sessionPath, config.SessionLoadOptions{
|
cfg, err := loadCommandConfig(ctx, flags.pipelinePath, flags.campaignPath, flags.campaignFilePath, flags.sessionPath, flags.sessionOptions())
|
||||||
SessionID: sessionID,
|
|
||||||
PreviousSessionID: previousSessionID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("run: %w", err)
|
return fmt.Errorf("run: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,19 +23,10 @@ func RunStage(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
fs := flag.NewFlagSet("run-stage", flag.ContinueOnError)
|
fs := flag.NewFlagSet("run-stage", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
|
|
||||||
var pipelinePath string
|
var flags commonConfigFlags
|
||||||
var campaignPath string
|
|
||||||
var campaignFilePath string
|
|
||||||
var sessionPath string
|
|
||||||
var sessionID string
|
|
||||||
var previousSessionID string
|
|
||||||
var force bool
|
var force bool
|
||||||
var selectedArtifacts artifactSelectionFlag
|
var selectedArtifacts artifactSelectionFlag
|
||||||
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml (optional; defaults searched)")
|
addCommonConfigFlags(fs, &flags)
|
||||||
fs.StringVar(&campaignPath, "campaign", "", "campaign ID")
|
|
||||||
fs.StringVar(&campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
|
||||||
fs.StringVar(&sessionPath, "session", "", "path to session.yml")
|
|
||||||
fs.StringVar(&previousSessionID, "previous-session-id", "", "expected previous session identifier")
|
|
||||||
fs.BoolVar(&force, "force", false, "force stage execution (reserved for future behavior)")
|
fs.BoolVar(&force, "force", false, "force stage execution (reserved for future behavior)")
|
||||||
fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to execute or publish (comma-separated or repeatable)")
|
fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to execute or publish (comma-separated or repeatable)")
|
||||||
|
|
||||||
@@ -47,16 +38,21 @@ func RunStage(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
case 2:
|
case 2:
|
||||||
stageName = strings.TrimSpace(fs.Arg(0))
|
stageName = strings.TrimSpace(fs.Arg(0))
|
||||||
positionalSessionID = strings.TrimSpace(fs.Arg(1))
|
positionalSessionID = strings.TrimSpace(fs.Arg(1))
|
||||||
|
case 1:
|
||||||
|
if strings.TrimSpace(flags.sessionID) == "" {
|
||||||
|
return fmt.Errorf("run-stage: expected stage name and session_id")
|
||||||
|
}
|
||||||
|
stageName = strings.TrimSpace(fs.Arg(0))
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("run-stage: expected stage name and session_id")
|
return fmt.Errorf("run-stage: expected stage name and session_id")
|
||||||
}
|
}
|
||||||
} else if fs.NArg() != 0 {
|
} else if fs.NArg() != 0 {
|
||||||
return fmt.Errorf("run-stage: unexpected positional arguments")
|
return fmt.Errorf("run-stage: unexpected positional arguments")
|
||||||
}
|
}
|
||||||
if err := applyPositionalSessionID("run-stage", positionalSessionID, &sessionID); err != nil {
|
if err := applyPositionalSessionID("run-stage", positionalSessionID, &flags.sessionID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(sessionID) == "" {
|
if strings.TrimSpace(flags.sessionID) == "" {
|
||||||
return fmt.Errorf("run-stage: session_id is required")
|
return fmt.Errorf("run-stage: session_id is required")
|
||||||
}
|
}
|
||||||
normalizedArtifacts, err := selectedArtifacts.Normalize()
|
normalizedArtifacts, err := selectedArtifacts.Normalize()
|
||||||
@@ -70,12 +66,12 @@ func RunStage(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
summary, err := runSingleStageCommand(ctx, singleStageCommand{
|
summary, err := runSingleStageCommand(ctx, singleStageCommand{
|
||||||
CommandName: "run-stage",
|
CommandName: "run-stage",
|
||||||
StageName: stageName,
|
StageName: stageName,
|
||||||
PipelinePath: pipelinePath,
|
PipelinePath: flags.pipelinePath,
|
||||||
CampaignPath: campaignPath,
|
CampaignPath: flags.campaignPath,
|
||||||
CampaignFilePath: campaignFilePath,
|
CampaignFilePath: flags.campaignFilePath,
|
||||||
SessionPath: sessionPath,
|
SessionPath: flags.sessionPath,
|
||||||
SessionID: sessionID,
|
SessionID: flags.sessionID,
|
||||||
PreviousSessionID: previousSessionID,
|
PreviousSessionID: flags.previousSessionID,
|
||||||
Force: force,
|
Force: force,
|
||||||
SelectedArtifacts: normalizedArtifacts,
|
SelectedArtifacts: normalizedArtifacts,
|
||||||
})
|
})
|
||||||
@@ -97,40 +93,18 @@ func RunStage(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
|
|
||||||
// Analyze force-runs the analyze stage.
|
// Analyze force-runs the analyze stage.
|
||||||
func Analyze(ctx context.Context, args []string, out io.Writer) error {
|
func Analyze(ctx context.Context, args []string, out io.Writer) error {
|
||||||
positionalSessionID, args := pullLeadingSessionID(args)
|
|
||||||
fs := flag.NewFlagSet("analyze", flag.ContinueOnError)
|
fs := flag.NewFlagSet("analyze", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
|
|
||||||
var pipelinePath string
|
var flags commonConfigFlags
|
||||||
var campaignPath string
|
|
||||||
var campaignFilePath string
|
|
||||||
var sessionPath string
|
|
||||||
var sessionID string
|
|
||||||
var previousSessionID string
|
|
||||||
var selectedArtifacts artifactSelectionFlag
|
var selectedArtifacts artifactSelectionFlag
|
||||||
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml (optional; defaults searched)")
|
addCommonConfigFlags(fs, &flags)
|
||||||
fs.StringVar(&campaignPath, "campaign", "", "campaign ID")
|
|
||||||
fs.StringVar(&campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
|
||||||
fs.StringVar(&sessionPath, "session", "", "path to session.yml")
|
|
||||||
fs.StringVar(&previousSessionID, "previous-session-id", "", "expected previous session identifier")
|
|
||||||
fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to execute during analyze (comma-separated or repeatable)")
|
fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to execute during analyze (comma-separated or repeatable)")
|
||||||
|
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := parseSessionAwareFlags("analyze", fs, args, &flags.sessionID); err != nil {
|
||||||
return fmt.Errorf("analyze: invalid flags: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
if positionalSessionID == "" {
|
if strings.TrimSpace(flags.sessionID) == "" {
|
||||||
if err := applyParsedSessionIDArg("analyze", fs, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("analyze: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("analyze", positionalSessionID, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(sessionID) == "" {
|
|
||||||
return fmt.Errorf("analyze: session_id is required")
|
return fmt.Errorf("analyze: session_id is required")
|
||||||
}
|
}
|
||||||
normalizedArtifacts, err := selectedArtifacts.Normalize()
|
normalizedArtifacts, err := selectedArtifacts.Normalize()
|
||||||
@@ -141,12 +115,12 @@ func Analyze(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
summary, err := runSingleStageCommand(ctx, singleStageCommand{
|
summary, err := runSingleStageCommand(ctx, singleStageCommand{
|
||||||
CommandName: "analyze",
|
CommandName: "analyze",
|
||||||
StageName: "analyze",
|
StageName: "analyze",
|
||||||
PipelinePath: pipelinePath,
|
PipelinePath: flags.pipelinePath,
|
||||||
CampaignPath: campaignPath,
|
CampaignPath: flags.campaignPath,
|
||||||
CampaignFilePath: campaignFilePath,
|
CampaignFilePath: flags.campaignFilePath,
|
||||||
SessionPath: sessionPath,
|
SessionPath: flags.sessionPath,
|
||||||
SessionID: sessionID,
|
SessionID: flags.sessionID,
|
||||||
PreviousSessionID: previousSessionID,
|
PreviousSessionID: flags.previousSessionID,
|
||||||
Force: true,
|
Force: true,
|
||||||
SelectedArtifacts: normalizedArtifacts,
|
SelectedArtifacts: normalizedArtifacts,
|
||||||
})
|
})
|
||||||
@@ -166,40 +140,18 @@ func Analyze(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
|
|
||||||
// Publish force-runs the publish stage.
|
// Publish force-runs the publish stage.
|
||||||
func Publish(ctx context.Context, args []string, out io.Writer) error {
|
func Publish(ctx context.Context, args []string, out io.Writer) error {
|
||||||
positionalSessionID, args := pullLeadingSessionID(args)
|
|
||||||
fs := flag.NewFlagSet("publish", flag.ContinueOnError)
|
fs := flag.NewFlagSet("publish", flag.ContinueOnError)
|
||||||
fs.SetOutput(io.Discard)
|
fs.SetOutput(io.Discard)
|
||||||
|
|
||||||
var pipelinePath string
|
var flags commonConfigFlags
|
||||||
var campaignPath string
|
|
||||||
var campaignFilePath string
|
|
||||||
var sessionPath string
|
|
||||||
var sessionID string
|
|
||||||
var previousSessionID string
|
|
||||||
var selectedArtifacts artifactSelectionFlag
|
var selectedArtifacts artifactSelectionFlag
|
||||||
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml (optional; defaults searched)")
|
addCommonConfigFlags(fs, &flags)
|
||||||
fs.StringVar(&campaignPath, "campaign", "", "campaign ID")
|
|
||||||
fs.StringVar(&campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
|
||||||
fs.StringVar(&sessionPath, "session", "", "path to session.yml")
|
|
||||||
fs.StringVar(&previousSessionID, "previous-session-id", "", "expected previous session identifier")
|
|
||||||
fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to publish (comma-separated or repeatable)")
|
fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to publish (comma-separated or repeatable)")
|
||||||
|
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := parseSessionAwareFlags("publish", fs, args, &flags.sessionID); err != nil {
|
||||||
return fmt.Errorf("publish: invalid flags: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
if positionalSessionID == "" {
|
if strings.TrimSpace(flags.sessionID) == "" {
|
||||||
if err := applyParsedSessionIDArg("publish", fs, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fs.NArg() != 0 {
|
|
||||||
return fmt.Errorf("publish: unexpected positional arguments")
|
|
||||||
}
|
|
||||||
if err := applyPositionalSessionID("publish", positionalSessionID, &sessionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(sessionID) == "" {
|
|
||||||
return fmt.Errorf("publish: session_id is required")
|
return fmt.Errorf("publish: session_id is required")
|
||||||
}
|
}
|
||||||
normalizedArtifacts, err := selectedArtifacts.Normalize()
|
normalizedArtifacts, err := selectedArtifacts.Normalize()
|
||||||
@@ -210,12 +162,12 @@ func Publish(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
summary, err := runSingleStageCommand(ctx, singleStageCommand{
|
summary, err := runSingleStageCommand(ctx, singleStageCommand{
|
||||||
CommandName: "publish",
|
CommandName: "publish",
|
||||||
StageName: "publish",
|
StageName: "publish",
|
||||||
PipelinePath: pipelinePath,
|
PipelinePath: flags.pipelinePath,
|
||||||
CampaignPath: campaignPath,
|
CampaignPath: flags.campaignPath,
|
||||||
CampaignFilePath: campaignFilePath,
|
CampaignFilePath: flags.campaignFilePath,
|
||||||
SessionPath: sessionPath,
|
SessionPath: flags.sessionPath,
|
||||||
SessionID: sessionID,
|
SessionID: flags.sessionID,
|
||||||
PreviousSessionID: previousSessionID,
|
PreviousSessionID: flags.previousSessionID,
|
||||||
Force: true,
|
Force: true,
|
||||||
SelectedArtifacts: normalizedArtifacts,
|
SelectedArtifacts: normalizedArtifacts,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -41,3 +41,21 @@ func applyParsedSessionIDArg(command string, fs *flag.FlagSet, sessionID *string
|
|||||||
return fmt.Errorf("%s: unexpected positional arguments", command)
|
return fmt.Errorf("%s: unexpected positional arguments", command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveParsedSessionID(command, positionalSessionID string, fs *flag.FlagSet, sessionID *string) error {
|
||||||
|
if strings.TrimSpace(positionalSessionID) == "" {
|
||||||
|
return applyParsedSessionIDArg(command, fs, sessionID)
|
||||||
|
}
|
||||||
|
if fs.NArg() != 0 {
|
||||||
|
return fmt.Errorf("%s: unexpected positional arguments", command)
|
||||||
|
}
|
||||||
|
return applyPositionalSessionID(command, positionalSessionID, sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSessionAwareFlags(command string, fs *flag.FlagSet, args []string, sessionID *string) error {
|
||||||
|
positionalSessionID, args := pullLeadingSessionID(args)
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return fmt.Errorf("%s: invalid flags: %w", command, err)
|
||||||
|
}
|
||||||
|
return resolveParsedSessionID(command, positionalSessionID, fs, sessionID)
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,15 +71,36 @@ func TestExecutePositionalSessionIDMismatchFails(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecuteSessionIDFlagFails(t *testing.T) {
|
func TestExecuteSessionIDFlagMismatchFails(t *testing.T) {
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
code := Execute([]string{"session", "status", "2026-05-03", "--session-id", "2026-05-04"}, &stdout, &stderr)
|
code := Execute([]string{"session", "status", "2026-05-03", "--session-id", "2026-05-04"}, &stdout, &stderr)
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
t.Fatal("exit code = 0, want non-zero")
|
t.Fatal("exit code = 0, want non-zero")
|
||||||
}
|
}
|
||||||
if !strings.Contains(stderr.String(), "flag provided but not defined: -session-id") {
|
if !strings.Contains(stderr.String(), "does not match expected session id") {
|
||||||
t.Fatalf("stderr = %q, want invalid --session-id flag", stderr.String())
|
t.Fatalf("stderr = %q, want positional/flag mismatch", stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecuteSessionIDFlagAcceptedWithoutPositional(t *testing.T) {
|
||||||
|
workspaceRoot := t.TempDir()
|
||||||
|
pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot)
|
||||||
|
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
code := Execute([]string{
|
||||||
|
"session", "status",
|
||||||
|
"--session-id", "2026-05-03",
|
||||||
|
"--config", pipelinePath,
|
||||||
|
"--campaign-file", campaignPath,
|
||||||
|
"--session", sessionPath,
|
||||||
|
}, &stdout, &stderr)
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout.String(), "Session: 2026-05-03") {
|
||||||
|
t.Fatalf("stdout = %q, want status output", stdout.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user