feat(v0.5.3): cmd new -- persistent preflight before workspace.Create; delegate to runWorkspaceEntry
This commit is contained in:
+43
-9
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warrenronsiek/ctask/internal/config"
|
||||
"github.com/warrenronsiek/ctask/internal/session"
|
||||
"github.com/warrenronsiek/ctask/internal/shell"
|
||||
"github.com/warrenronsiek/ctask/internal/workspace"
|
||||
)
|
||||
@@ -27,6 +26,7 @@ var (
|
||||
newAgent string
|
||||
newNoLaunch bool
|
||||
newProject bool
|
||||
newDirect bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -36,6 +36,7 @@ func init() {
|
||||
newCmd.Flags().BoolVar(&newShell, "shell", false, "Open interactive shell instead of agent")
|
||||
newCmd.Flags().StringVarP(&newAgent, "agent", "a", "", "Command to exec as the agent")
|
||||
newCmd.Flags().BoolVar(&newNoLaunch, "no-launch", false, "Create workspace only, do not launch")
|
||||
newCmd.Flags().BoolVar(&newDirect, "direct", false, "Bypass persistent session mode for this command")
|
||||
rootCmd.AddCommand(newCmd)
|
||||
}
|
||||
|
||||
@@ -45,6 +46,13 @@ func runNew(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Persistent-mode preflight runs BEFORE workspace.Create — a missing
|
||||
// tmux install must not leave a half-initialized workspace on disk.
|
||||
tmuxPath, err := newPersistentPreflight(newDirect)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
agent := newAgent
|
||||
if agent == "" {
|
||||
agent = config.ResolveAgent()
|
||||
@@ -119,16 +127,42 @@ func runNew(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta), ws.Meta.LaunchDir)
|
||||
|
||||
return session.Run(session.LaunchOpts{
|
||||
WsDir: ws.Path,
|
||||
EnvVars: envVars,
|
||||
// Re-set the workspace's root: workspace.Create returned ws but our
|
||||
// computed `root` is what should be exported via CTASK_ROOT (it may
|
||||
// differ from ws-derived defaults when --project + CTASK_PROJECT_ROOT
|
||||
// are in play).
|
||||
return runWorkspaceEntry(WorkspaceEntryOptions{
|
||||
WsPath: ws.Path,
|
||||
WsRoot: root,
|
||||
WsMeta: ws.Meta,
|
||||
Agent: agent,
|
||||
Mode: ws.Meta.Mode,
|
||||
Slug: ws.Meta.Slug,
|
||||
Shell: newShell,
|
||||
LaunchDir: ws.Meta.LaunchDir,
|
||||
Direct: newDirect,
|
||||
CommandName: "new",
|
||||
TmuxPath: tmuxPath,
|
||||
NewlyCreated: true,
|
||||
})
|
||||
}
|
||||
|
||||
// newPersistentPreflight runs the persistent-mode preflight for `ctask new`,
|
||||
// returning the validated tmux path on success or "" when persistent mode
|
||||
// is not in effect (or --direct was passed). When persistent mode IS in
|
||||
// effect and --direct was passed, prints the bypass warning and returns
|
||||
// ("", nil) — the workspace can still be created in direct mode.
|
||||
//
|
||||
// `new` is the only command where preflight runs *before* the workspace
|
||||
// exists; a tmux failure must not leave a half-initialized directory on
|
||||
// disk. (resume / last / open / attach run preflight inside
|
||||
// runWorkspaceEntry, after their own resolution step.)
|
||||
func newPersistentPreflight(directFlag bool) (string, error) {
|
||||
mode := config.ResolveSessionMode()
|
||||
if mode != "persistent" {
|
||||
return "", nil
|
||||
}
|
||||
if directFlag {
|
||||
fmt.Fprintln(os.Stderr,
|
||||
"[ctask] warning: --direct bypassing persistent mode (no tmux session exists for this workspace)")
|
||||
return "", nil
|
||||
}
|
||||
return preflightPersistentEntry("new")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests in this file mutate package globals (isTTYCheck, runWorkspaceEntry).
|
||||
// Do not run with t.Parallel().
|
||||
|
||||
func TestNewDirectModeSkipsPreflight(t *testing.T) {
|
||||
os.Unsetenv("CTASK_SESSION_MODE")
|
||||
// Direct mode (default): preflight is a no-op even when --direct is unset.
|
||||
if _, err := newPersistentPreflight(false); err != nil {
|
||||
t.Errorf("direct mode preflight should be no-op: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDirectFlagUnderPersistentEmitsWarningAndProceeds(t *testing.T) {
|
||||
os.Setenv("CTASK_SESSION_MODE", "persistent")
|
||||
t.Cleanup(func() { os.Unsetenv("CTASK_SESSION_MODE") })
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("pipe: %v", err)
|
||||
}
|
||||
orig := os.Stderr
|
||||
os.Stderr = w
|
||||
t.Cleanup(func() { os.Stderr = orig })
|
||||
|
||||
tmuxPath, err := newPersistentPreflight(true) // --direct
|
||||
w.Close()
|
||||
if err != nil {
|
||||
t.Errorf("--direct under persistent should not error pre-create: %v", err)
|
||||
}
|
||||
if tmuxPath != "" {
|
||||
t.Errorf("expected empty tmuxPath under --direct: got %q", tmuxPath)
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
n, _ := r.Read(buf)
|
||||
if !strings.Contains(string(buf[:n]), "--direct bypassing") {
|
||||
t.Errorf("expected bypass warning on stderr, got %q", string(buf[:n]))
|
||||
}
|
||||
}
|
||||
|
||||
// The behavioral assertion that runNew forwards the workspace produced by
|
||||
// workspace.Create into runWorkspaceEntry lives in cmd/entry_test.go
|
||||
// (Task 10). It uses the entry seam directly; we don't duplicate that here.
|
||||
Reference in New Issue
Block a user