push.go

v0.1.0
Doc Versions Source
1
package main
2
3
import (
4
	"fmt"
5
	"io"
6
	"os"
7
8
	"github.com/spf13/cobra"
9
10
	"go.bigb.es/confluence-md-utilities/api"
11
	"go.bigb.es/confluence-md-utilities/converter"
12
	"go.bigb.es/confluence-md-utilities/template"
13
)
14
15
var (
16
	pushMessage        string
17
	pushRaw            bool
18
	pushTemplate       bool
19
	pushMarkerStart    string
20
	pushMarkerEnd      string
21
)
22
23
var pushCmd = &cobra.Command{
24
	Use:   "push <confluence-url> [input.md]",
25
	Short: "Push local Markdown to a Confluence page",
26
	Long: `Convert a local Markdown file to Confluence storage format and update
27
the page at the given URL.
28
29
By default, the entire page body is replaced with the converted content.
30
31
With --template, the current page body is preserved as a template:
32
the content between marker comments is replaced with the new content,
33
keeping everything else (metadata table, changelog, etc.) intact.
34
35
With --raw, the input is treated as Confluence storage XML (no conversion).
36
37
Reads from stdin if no input file is specified.
38
39
Authentication via --token flag or CONFLUENCE_TOKEN environment variable.`,
40
	Args: cobra.RangeArgs(1, 2),
41
	RunE: func(cmd *cobra.Command, args []string) error {
42
		token := resolveToken()
43
		if token == "" {
44
			return fmt.Errorf("Confluence token required: use --token flag or set CONFLUENCE_TOKEN env var")
45
		}
46
47
		ref, err := api.ParsePageURL(args[0])
48
		if err != nil {
49
			return err
50
		}
51
52
		// Read input
53
		var input []byte
54
		if len(args) > 1 {
55
			input, err = os.ReadFile(args[1])
56
		} else {
57
			input, err = io.ReadAll(os.Stdin)
58
		}
59
		if err != nil {
60
			return fmt.Errorf("reading input: %w", err)
61
		}
62
63
		// Convert to Confluence XML if not raw
64
		var newXML string
65
		if pushRaw {
66
			newXML = string(input)
67
		} else {
68
			newXML, err = converter.MarkdownToConfluence(input)
69
			if err != nil {
70
				return fmt.Errorf("converting markdown: %w", err)
71
			}
72
		}
73
74
		client := api.NewClient(ref.BaseURL, token)
75
76
		// Fetch current page for version info (and template if needed)
77
		page, err := client.GetPage(ref)
78
		if err != nil {
79
			return err
80
		}
81
82
		fmt.Fprintf(os.Stderr, "Updating page: %s (id=%s, version=%d -> %d)\n",
83
			page.Title, page.ID, page.Version.Number, page.Version.Number+1)
84
85
		// If template mode, embed into existing page body
86
		body := newXML
87
		if pushTemplate {
88
			body, err = template.Embed(page.Body.Storage.Value, newXML, pushMarkerStart, pushMarkerEnd)
89
			if err != nil {
90
				return fmt.Errorf("embedding into template: %w", err)
91
			}
92
		}
93
94
		if err := client.UpdateContent(page.ID, page, body, pushMessage); err != nil {
95
			return err
96
		}
97
98
		fmt.Fprintf(os.Stderr, "Page updated successfully\n")
99
		return nil
100
	},
101
}
102
103
func init() {
104
	pushCmd.Flags().StringVarP(&pushMessage, "message", "m", "", "Version message for the update")
105
	pushCmd.Flags().BoolVar(&pushRaw, "raw", false, "Input is raw Confluence storage XML (skip conversion)")
106
	pushCmd.Flags().BoolVar(&pushTemplate, "template", false, "Embed content into existing page body between markers")
107
	pushCmd.Flags().StringVar(&pushMarkerStart, "marker-start", template.DefaultMarkerStart, "Start marker comment (with --template)")
108
	pushCmd.Flags().StringVar(&pushMarkerEnd, "marker-end", template.DefaultMarkerEnd, "End marker comment (with --template)")
109
	rootCmd.AddCommand(pushCmd)
110
}
111

Source Files