Commit 7c909b71 authored by Mickaël Desfrênes's avatar Mickaël Desfrênes
Browse files

add warning when feeding folder

parent ceb8ff42
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -56,7 +56,7 @@ func TestCLICommandsExposeExpectedFlags(t *testing.T) {
		"bundles-list":   {"directory"},
		"bundles-add":    {"directory"},
		"bundles-remove": {"directory"},
		"feed":           {"directory", "verbose"},
		"feed":           {"directory", "verbose", "yes"},
		"sync":           {"directory"},
		"basex":          {"directory", "verbose"},
		"templates-list": {"directory"},
+16 −12
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ commandes new, projects et cache-clear qui peuvent être lancées aussi en dehor
		"flag.freeze.output":        "Dossier de sortie des fichiers HTML",
		"flag.freeze.debug":         "Debug",
		"flag.feed.verbose":         "Afficher les commandes exécutées",
		"flag.feed.yes":             "Remplacer la base de données sans confirmation",
		"flag.basex.verbose":        "Afficher la commande exécutée",
		"flag.projects.json_out":    "Sortie JSON",

@@ -100,6 +101,7 @@ commandes new, projects et cache-clear qui peuvent être lancées aussi en dehor
		"label.project_title":            "Titre du projet",
		"label.choice":                   "Choix",
		"question.install_demo":          "Voulez-vous installer une édition de démonstration ?",
		"question.feed_replace_database": "Attention ! Vous vous apprêtez à écraser la base de données existante, souhaitez-vous continuer ?",

		"err.no_release_available":    "aucune release disponible",
		"err.not_max_directory":       "Le dossier n'est pas une installation de MaX.",
@@ -166,6 +168,7 @@ which can also be run outside a MaX directory.`,
		"flag.freeze.output":        "Output directory for HTML files",
		"flag.freeze.debug":         "Debug",
		"flag.feed.verbose":         "Show executed commands",
		"flag.feed.yes":             "Replace the database without confirmation",
		"flag.basex.verbose":        "Show executed command",
		"flag.projects.json_out":    "JSON output",

@@ -209,6 +212,7 @@ which can also be run outside a MaX directory.`,
		"label.project_title":            "Project title",
		"label.choice":                   "Choice",
		"question.install_demo":          "Do you want to install a demo edition?",
		"question.feed_replace_database": "Feeding a directory will replace the entire database. Continue?",

		"err.no_release_available":    "no release available",
		"err.not_max_directory":       "Directory is not a MaX installation.",
+20 −7
Original line number Diff line number Diff line
@@ -43,7 +43,7 @@ import (
//go:embed max_releases.json
var fallbackReleases []byte

const version = "0.3.2"
const version = "0.3.3"

const maxConfigFile = "config.xml"
const baseXDistro = "https://files.basex.org/releases/11.7/BaseX117.zip"
@@ -420,16 +420,18 @@ func newBundlesRemoveCmd() *cobra.Command {
func newFeedCmd() *cobra.Command {
	directory := cwd()
	verbose := false
	yes := false
	cmd := &cobra.Command{
		Use:   "feed <feed-path>",
		Short: T("cmd.feed.short"),
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			return runFeed(args[0], directory, verbose)
			return runFeed(args[0], directory, verbose, yes)
		},
	}
	cmd.Flags().StringVar(&directory, "directory", directory, T("flag.common.directory"))
	cmd.Flags().BoolVar(&verbose, "verbose", false, T("flag.feed.verbose"))
	cmd.Flags().BoolVarP(&yes, "yes", "y", false, T("flag.feed.yes"))
	return cmd
}

@@ -2268,7 +2270,7 @@ func runBundlesRemove(bundleName, directory string) error {
	return runBundlesList(abs)
}

func runFeed(feedPath, directory string, verbose bool) error {
func runFeed(feedPath, directory string, verbose bool, yes bool) error {
	abs, err := filepath.Abs(directory)
	if err != nil {
		return err
@@ -2279,10 +2281,6 @@ func runFeed(feedPath, directory string, verbose bool) error {
	if err := os.Chdir(abs); err != nil {
		return err
	}
	javaBin, err := ensureJava()
	if err != nil {
		return err
	}
	feedAbs, err := filepath.Abs(feedPath)
	if err != nil {
		return err
@@ -2292,6 +2290,21 @@ func runFeed(feedPath, directory string, verbose bool) error {
		return fmt.Errorf(T("err.feed_not_exists"), feedAbs)
	}

	if !stat.Mode().IsRegular() && !yes {
		ok, err := confirm(T("question.feed_replace_database"), false)
		if err != nil {
			return err
		}
		if !ok {
			return nil
		}
	}

	javaBin, err := ensureJava()
	if err != nil {
		return err
	}

	createDB := exec.Command(javaBin, "-cp", cpPaths(abs), "-Xmx2g", "org.basex.BaseX", "if(not(db:exists('max'))) then db:create('max') else ()")
	createDB.Stdout = os.Stdout
	createDB.Stderr = os.Stderr
+126 −1
Original line number Diff line number Diff line
@@ -3,8 +3,10 @@ package main
import (
	"archive/tar"
	"bufio"
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"net/url"
	"os"
	"path/filepath"
@@ -221,7 +223,7 @@ func TestFeedCommandRunsForFileAndDirectory(t *testing.T) {
	}

	root = newRootCmd()
	root.SetArgs([]string{"feed", feedDir, "--directory", projectDir})
	root.SetArgs([]string{"feed", feedDir, "--directory", projectDir, "--yes"})
	if err := root.Execute(); err != nil {
		t.Fatalf("feed command failed for directory input: %v", err)
	}
@@ -252,6 +254,76 @@ func TestFeedCommandRunsForFileAndDirectory(t *testing.T) {
	}
}

func TestFeedDirectoryConfirmationCanAbort(t *testing.T) {
	previousLocale := currentLocale
	setLocale(localeEN)
	t.Cleanup(func() {
		setLocale(previousLocale)
	})

	tmpHome := t.TempDir()
	prevHome := userMaxDir
	prevCache := cacheDir
	userMaxDir = tmpHome
	cacheDir = filepath.Join(userMaxDir, "cache")
	t.Cleanup(func() {
		userMaxDir = prevHome
		cacheDir = prevCache
	})

	oldWD, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		_ = os.Chdir(oldWD)
	})

	projectDir := filepath.Join(t.TempDir(), "project")
	if err := os.MkdirAll(projectDir, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(projectDir, maxConfigFile), []byte(configInitTemplate), 0o644); err != nil {
		t.Fatal(err)
	}
	feedDir := filepath.Join(projectDir, "batch")
	if err := os.MkdirAll(feedDir, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(feedDir, "a.xml"), []byte("<a/>"), 0o644); err != nil {
		t.Fatal(err)
	}

	logPath := filepath.Join(t.TempDir(), "java.log")
	t.Setenv("CLIMAX_TEST_JAVA_LOG", logPath)
	if err := installFakeBundledJava(tmpHome); err != nil {
		t.Fatal(err)
	}

	withStdinContent(t, "n\n", func() {
		out := captureStdoutForMainTest(t, func() {
			root := newRootCmd()
			root.SetArgs([]string{"feed", feedDir, "--directory", projectDir})
			if err := root.Execute(); err != nil {
				t.Fatalf("feed command failed after declined confirmation: %v", err)
			}
		})
		if !strings.Contains(out, "entire database") {
			t.Fatalf("expected replacement warning in confirmation prompt, got:\n%s", out)
		}
	})

	if fileExists(logPath) {
		lines, err := readLogLines(logPath)
		if err != nil {
			t.Fatal(err)
		}
		if len(lines) != 0 {
			t.Fatalf("expected no java invocations after declined confirmation, got:\n%v", lines)
		}
	}
}

func installFakeBundledJava(home string) error {
	javaBin := bundledJavaPath(filepath.Join(home, "jdk"), currentSystem())
	if javaBin == "" {
@@ -287,3 +359,56 @@ func readLogLines(path string) ([]string, error) {
	}
	return lines, nil
}

func withStdinContent(t *testing.T, content string, fn func()) {
	t.Helper()

	f, err := os.CreateTemp(t.TempDir(), "stdin-*")
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()

	if _, err := f.WriteString(content); err != nil {
		t.Fatal(err)
	}
	if _, err := f.Seek(0, io.SeekStart); err != nil {
		t.Fatal(err)
	}

	oldStdin := os.Stdin
	os.Stdin = f
	defer func() {
		os.Stdin = oldStdin
	}()

	fn()
}

func captureStdoutForMainTest(t *testing.T, fn func()) string {
	t.Helper()

	reader, writer, err := os.Pipe()
	if err != nil {
		t.Fatalf("failed to create stdout pipe: %v", err)
	}
	defer reader.Close()

	oldOut := os.Stdout
	os.Stdout = writer
	defer func() {
		os.Stdout = oldOut
	}()

	done := make(chan string, 1)
	go func() {
		var buf bytes.Buffer
		_, _ = io.Copy(&buf, reader)
		done <- buf.String()
	}()

	fn()

	_ = writer.Close()
	return <-done
}