Loading cli_commands_test.go +1 −1 Original line number Diff line number Diff line Loading @@ -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"}, Loading i18n.go +16 −12 Original line number Diff line number Diff line Loading @@ -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", Loading Loading @@ -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.", Loading Loading @@ -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", Loading Loading @@ -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.", Loading main.go +20 −7 Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -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 } Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading main_test.go +126 −1 Original line number Diff line number Diff line Loading @@ -3,8 +3,10 @@ package main import ( "archive/tar" "bufio" "bytes" "compress/gzip" "fmt" "io" "net/url" "os" "path/filepath" Loading Loading @@ -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) } Loading Loading @@ -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 == "" { Loading Loading @@ -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 } Loading
cli_commands_test.go +1 −1 Original line number Diff line number Diff line Loading @@ -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"}, Loading
i18n.go +16 −12 Original line number Diff line number Diff line Loading @@ -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", Loading Loading @@ -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.", Loading Loading @@ -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", Loading Loading @@ -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.", Loading
main.go +20 −7 Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -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 } Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading
main_test.go +126 −1 Original line number Diff line number Diff line Loading @@ -3,8 +3,10 @@ package main import ( "archive/tar" "bufio" "bytes" "compress/gzip" "fmt" "io" "net/url" "os" "path/filepath" Loading Loading @@ -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) } Loading Loading @@ -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 == "" { Loading Loading @@ -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 }