~bigbes/confluence-md-utilities

ref: 5fabfe018459f627441085dd7f87f1f9e4e97af8 confluence-md-utilities/cmd/mdcx/push.go -rw-r--r-- 3.2 KiB
5fabfe01 — Eugene Blikh feat: add verify command, improve round-trip fidelity 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package main

import (
	"fmt"
	"io"
	"os"

	"github.com/spf13/cobra"

	"sourcecraft.dev/bigbes/confluence-md-utilities/api"
	"sourcecraft.dev/bigbes/confluence-md-utilities/converter"
	"sourcecraft.dev/bigbes/confluence-md-utilities/template"
)

var (
	pushMessage        string
	pushRaw            bool
	pushTemplate       bool
	pushMarkerStart    string
	pushMarkerEnd      string
)

var pushCmd = &cobra.Command{
	Use:   "push <confluence-url> [input.md]",
	Short: "Push local Markdown to a Confluence page",
	Long: `Convert a local Markdown file to Confluence storage format and update
the page at the given URL.

By default, the entire page body is replaced with the converted content.

With --template, the current page body is preserved as a template:
the content between marker comments is replaced with the new content,
keeping everything else (metadata table, changelog, etc.) intact.

With --raw, the input is treated as Confluence storage XML (no conversion).

Reads from stdin if no input file is specified.

Authentication via --token flag or CONFLUENCE_TOKEN environment variable.`,
	Args: cobra.RangeArgs(1, 2),
	RunE: func(cmd *cobra.Command, args []string) error {
		token := resolveToken()
		if token == "" {
			return fmt.Errorf("Confluence token required: use --token flag or set CONFLUENCE_TOKEN env var")
		}

		ref, err := api.ParsePageURL(args[0])
		if err != nil {
			return err
		}

		// Read input
		var input []byte
		if len(args) > 1 {
			input, err = os.ReadFile(args[1])
		} else {
			input, err = io.ReadAll(os.Stdin)
		}
		if err != nil {
			return fmt.Errorf("reading input: %w", err)
		}

		// Convert to Confluence XML if not raw
		var newXML string
		if pushRaw {
			newXML = string(input)
		} else {
			newXML, err = converter.MarkdownToConfluence(input)
			if err != nil {
				return fmt.Errorf("converting markdown: %w", err)
			}
		}

		client := api.NewClient(ref.BaseURL, token)

		// Fetch current page for version info (and template if needed)
		page, err := client.GetPage(ref)
		if err != nil {
			return err
		}

		fmt.Fprintf(os.Stderr, "Updating page: %s (id=%s, version=%d -> %d)\n",
			page.Title, page.ID, page.Version.Number, page.Version.Number+1)

		// If template mode, embed into existing page body
		body := newXML
		if pushTemplate {
			body, err = template.Embed(page.Body.Storage.Value, newXML, pushMarkerStart, pushMarkerEnd)
			if err != nil {
				return fmt.Errorf("embedding into template: %w", err)
			}
		}

		if err := client.UpdateContent(page.ID, page, body, pushMessage); err != nil {
			return err
		}

		fmt.Fprintf(os.Stderr, "Page updated successfully\n")
		return nil
	},
}

func init() {
	pushCmd.Flags().StringVarP(&pushMessage, "message", "m", "", "Version message for the update")
	pushCmd.Flags().BoolVar(&pushRaw, "raw", false, "Input is raw Confluence storage XML (skip conversion)")
	pushCmd.Flags().BoolVar(&pushTemplate, "template", false, "Embed content into existing page body between markers")
	pushCmd.Flags().StringVar(&pushMarkerStart, "marker-start", template.DefaultMarkerStart, "Start marker comment (with --template)")
	pushCmd.Flags().StringVar(&pushMarkerEnd, "marker-end", template.DefaultMarkerEnd, "End marker comment (with --template)")
	rootCmd.AddCommand(pushCmd)
}