diff --git a/cmd/kosli/assertApproval.go b/cmd/kosli/assertApproval.go index 8f5eb846c..090955b8b 100644 --- a/cmd/kosli/assertApproval.go +++ b/cmd/kosli/assertApproval.go @@ -48,7 +48,7 @@ func newAssertApprovalCmd(out io.Writer) *cobra.Command { Short: assertApprovalShortDesc, Long: assertApprovalLongDesc, Example: assertApprovalExample, - Deprecated: "this command is deprecated and will be removed in a future release.", + Deprecated: deprecatedCommandMsg, PreRunE: func(cmd *cobra.Command, args []string) error { err := RequireGlobalFlags(global, []string{"Org", "ApiToken"}) if err != nil { diff --git a/cmd/kosli/attestDecision.go b/cmd/kosli/attestDecision.go index 59c4d07e8..ae20612ae 100644 --- a/cmd/kosli/attestDecision.go +++ b/cmd/kosli/attestDecision.go @@ -7,6 +7,7 @@ import ( "net/url" "os" + "github.com/kosli-dev/cli/internal/docgen" "github.com/kosli-dev/cli/internal/requests" "github.com/spf13/cobra" ) @@ -27,7 +28,7 @@ type attestDecisionOptions struct { payload DecisionAttestationPayload } -const attestDecisionShortDesc = `[BETA] Record a compliance decision against a control in a Kosli trail. ` +const attestDecisionShortDesc = `Record a compliance decision against a control in a Kosli trail. ` const attestDecisionLongDesc = attestDecisionShortDesc + ` Use this command to record the outcome of evaluating a control as part of your delivery @@ -92,11 +93,12 @@ func newAttestDecisionCmd(out io.Writer) *cobra.Command { }, } cmd := &cobra.Command{ - Use: "decision [IMAGE-NAME | FILE-PATH | DIR-PATH]", - Short: attestDecisionShortDesc, - Long: attestDecisionLongDesc, - Example: attestDecisionExample, - Hidden: true, + Use: "decision [IMAGE-NAME | FILE-PATH | DIR-PATH]", + Short: attestDecisionShortDesc, + Long: attestDecisionLongDesc, + Example: attestDecisionExample, + Hidden: true, + Annotations: map[string]string{docgen.DocHiddenAnnotation: "", betaCLIAnnotation: ""}, PreRunE: func(cmd *cobra.Command, args []string) error { err := CustomMaximumNArgs(1, args) if err != nil { diff --git a/cmd/kosli/docs.go b/cmd/kosli/docs.go index 00140383b..9cffff0e9 100644 --- a/cmd/kosli/docs.go +++ b/cmd/kosli/docs.go @@ -67,7 +67,8 @@ func (o *docsOptions) run() error { Name: cmd.CommandPath(), Beta: isBeta(cmd), Deprecated: isDeprecated(cmd), - DeprecMsg: cmd.Deprecated, + DeprecMsg: deprecationHint(cmd), + Hidden: isDocHidden(cmd), Summary: cmd.Short, Long: cmd.Long, UseLine: cmd.UseLine(), diff --git a/cmd/kosli/evaluate.go b/cmd/kosli/evaluate.go index e2eb4e8a6..702d9ae4a 100644 --- a/cmd/kosli/evaluate.go +++ b/cmd/kosli/evaluate.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" ) -const evaluateShortDesc = `[BETA] Evaluate data against Rego policies.` +const evaluateShortDesc = `Evaluate data against Rego policies.` // Backtick breaks (`"` + "`x`" + `"`) are needed to embed markdown // inline code spans inside raw string literals. @@ -36,9 +36,10 @@ logic reusable across environments with different tolerances.` func newEvaluateCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "evaluate", - Short: evaluateShortDesc, - Long: evaluateLongDesc, + Use: "evaluate", + Short: evaluateShortDesc, + Long: evaluateLongDesc, + Annotations: map[string]string{betaCLIAnnotation: ""}, } // Add subcommands diff --git a/cmd/kosli/evaluateInput.go b/cmd/kosli/evaluateInput.go index 61d561c3b..55fc3674c 100644 --- a/cmd/kosli/evaluateInput.go +++ b/cmd/kosli/evaluateInput.go @@ -15,7 +15,7 @@ type evaluateInputOptions struct { inputFile string } -const evaluateInputShortDesc = `[BETA] Evaluate a local JSON input against a Rego policy.` +const evaluateInputShortDesc = `Evaluate a local JSON input against a Rego policy.` const evaluateInputLongDesc = evaluateInputShortDesc + ` Read JSON from a file or stdin and evaluate it against a Rego policy. diff --git a/cmd/kosli/evaluateTrail.go b/cmd/kosli/evaluateTrail.go index f967f2ce9..30ce4eba9 100644 --- a/cmd/kosli/evaluateTrail.go +++ b/cmd/kosli/evaluateTrail.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" ) -const evaluateTrailShortDesc = `[BETA] Evaluate a trail against a policy.` +const evaluateTrailShortDesc = `Evaluate a trail against a policy.` const evaluateTrailLongDesc = evaluateTrailShortDesc + ` Fetch a single trail from Kosli and evaluate it against a Rego policy. diff --git a/cmd/kosli/evaluateTrails.go b/cmd/kosli/evaluateTrails.go index 801a1850e..c4c015b43 100644 --- a/cmd/kosli/evaluateTrails.go +++ b/cmd/kosli/evaluateTrails.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" ) -const evaluateTrailsShortDesc = `[BETA] Evaluate multiple trails against a policy.` +const evaluateTrailsShortDesc = `Evaluate multiple trails against a policy.` const evaluateTrailsLongDesc = evaluateTrailsShortDesc + ` Fetch multiple trails from Kosli and evaluate them together against a Rego policy. diff --git a/cmd/kosli/getApproval.go b/cmd/kosli/getApproval.go index eab82ce2f..792a9203a 100644 --- a/cmd/kosli/getApproval.go +++ b/cmd/kosli/getApproval.go @@ -56,7 +56,7 @@ func newGetApprovalCmd(out io.Writer) *cobra.Command { Long: getApprovalLongDesc, Example: getApprovalExample, Args: cobra.ExactArgs(1), - Deprecated: "this command is deprecated and will be removed in a future release.", + Deprecated: deprecatedCommandMsg, PreRunE: func(cmd *cobra.Command, args []string) error { err := RequireGlobalFlags(global, []string{"Org", "ApiToken"}) if err != nil { diff --git a/cmd/kosli/lifecycle_test.go b/cmd/kosli/lifecycle_test.go new file mode 100644 index 000000000..3bc6e20b0 --- /dev/null +++ b/cmd/kosli/lifecycle_test.go @@ -0,0 +1,68 @@ +package main + +import ( + "io" + "strings" + "testing" + + "github.com/kosli-dev/cli/internal/docgen" + "github.com/spf13/cobra" +) + +func TestLifecycleEvaluateIsBeta(t *testing.T) { + cmd := newEvaluateCmd(io.Discard) + if !isBeta(cmd) { + t.Error("expected evaluate command to be beta") + } + // subcommands inherit beta via parent walk + for _, sub := range cmd.Commands() { + if !isBeta(sub) { + t.Errorf("expected subcommand %q to inherit beta", sub.Name()) + } + } +} + +func TestLifecycleAttestDecisionIsBetaAndDocHidden(t *testing.T) { + global = &GlobalOpts{} + cmd := newAttestDecisionCmd(io.Discard) + if !cmd.Hidden { + t.Error("expected attest decision to stay Hidden") + } + if !isBeta(cmd) { + t.Error("expected attest decision to be beta") + } + if _, ok := cmd.Annotations[docgen.DocHiddenAnnotation]; !ok { + t.Error("expected attest decision to carry the docHidden annotation") + } + if !isDocHidden(cmd) { + t.Error("expected isDocHidden to be true for attest decision") + } +} + +func TestDeprecationHint(t *testing.T) { + generic := &cobra.Command{Use: "x", Deprecated: deprecatedCommandMsg} + if got := deprecationHint(generic); got != "" { + t.Errorf("expected generic deprecation message suppressed, got %q", got) + } + custom := &cobra.Command{Use: "y", Deprecated: "use 'kosli snapshot paths' instead"} + if got := deprecationHint(custom); got != "use 'kosli snapshot paths' instead" { + t.Errorf("expected custom hint preserved, got %q", got) + } + none := &cobra.Command{Use: "z"} + if got := deprecationHint(none); got != "" { + t.Errorf("expected empty for non-deprecated command, got %q", got) + } +} + +func TestLifecycleNoBetaTextPrefix(t *testing.T) { + global = &GlobalOpts{} + cmds := []*cobra.Command{ + newEvaluateCmd(io.Discard), + newAttestDecisionCmd(io.Discard), + } + for _, c := range cmds { + if strings.Contains(c.Short, "[BETA]") { + t.Errorf("command %q still has [BETA] text prefix in Short", c.Name()) + } + } +} diff --git a/cmd/kosli/listApprovals.go b/cmd/kosli/listApprovals.go index 4ea277eec..ed125e5ca 100644 --- a/cmd/kosli/listApprovals.go +++ b/cmd/kosli/listApprovals.go @@ -55,7 +55,7 @@ func newListApprovalsCmd(out io.Writer) *cobra.Command { Long: listApprovalsLongDesc, Example: listApprovalsExample, Args: cobra.NoArgs, - Deprecated: "this command is deprecated and will be removed in a future release.", + Deprecated: deprecatedCommandMsg, PreRunE: func(cmd *cobra.Command, args []string) error { err := RequireGlobalFlags(global, []string{"Org", "ApiToken"}) if err != nil { diff --git a/cmd/kosli/report.go b/cmd/kosli/report.go index 7f880cee5..8d28066eb 100644 --- a/cmd/kosli/report.go +++ b/cmd/kosli/report.go @@ -13,7 +13,7 @@ func newReportCmd(out io.Writer) *cobra.Command { Use: "report", Short: reportDesc, Long: reportDesc, - Deprecated: "this command is deprecated and will be removed in a future release.", + Deprecated: deprecatedCommandMsg, } // Add subcommands diff --git a/cmd/kosli/reportApproval.go b/cmd/kosli/reportApproval.go index b128df21b..43a1f1ba1 100644 --- a/cmd/kosli/reportApproval.go +++ b/cmd/kosli/reportApproval.go @@ -87,7 +87,7 @@ func newReportApprovalCmd(out io.Writer) *cobra.Command { Short: reportApprovalShortDesc, Long: reportApprovalLongDesc, Example: reportApprovalExample, - Deprecated: "this command is deprecated and will be removed in a future release.", + Deprecated: deprecatedCommandMsg, PreRunE: func(cmd *cobra.Command, args []string) error { err := RequireGlobalFlags(global, []string{"Org", "ApiToken"}) if err != nil { diff --git a/cmd/kosli/request.go b/cmd/kosli/request.go index 551dd71f7..0f3cf4159 100644 --- a/cmd/kosli/request.go +++ b/cmd/kosli/request.go @@ -13,7 +13,7 @@ func newRequestCmd(out io.Writer) *cobra.Command { Use: "request", Short: requestDesc, Long: requestDesc, - Deprecated: "this command is deprecated and will be removed in a future release.", + Deprecated: deprecatedCommandMsg, } // Add subcommands diff --git a/cmd/kosli/requestApproval.go b/cmd/kosli/requestApproval.go index d484d1c51..f15368473 100644 --- a/cmd/kosli/requestApproval.go +++ b/cmd/kosli/requestApproval.go @@ -57,7 +57,7 @@ func newRequestApprovalCmd(out io.Writer) *cobra.Command { Short: requestApprovalShortDesc, Long: requestApprovalLongDesc, Example: requestApprovalExample, - Deprecated: "this command is deprecated and will be removed in a future release.", + Deprecated: deprecatedCommandMsg, PreRunE: func(cmd *cobra.Command, args []string) error { err := RequireGlobalFlags(global, []string{"Org", "ApiToken"}) if err != nil { diff --git a/cmd/kosli/root.go b/cmd/kosli/root.go index 3b5ce9208..9b4ec0625 100644 --- a/cmd/kosli/root.go +++ b/cmd/kosli/root.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/kosli-dev/cli/internal/docgen" "github.com/kosli-dev/cli/internal/requests" "github.com/kosli-dev/cli/internal/security" "github.com/kosli-dev/cli/internal/version" @@ -16,6 +17,10 @@ import ( "github.com/spf13/viper" ) +const betaCLIAnnotation = "betaCLI" + +const deprecatedCommandMsg = "this command is deprecated and will be removed in a future release." + var globalUsage = `The Kosli CLI. Environment variables: @@ -572,12 +577,12 @@ func bindFlags(cmd *cobra.Command, v *viper.Viper) { } func isBeta(cmd *cobra.Command) bool { - if _, ok := cmd.Annotations["betaCLI"]; ok { + if _, ok := cmd.Annotations[betaCLIAnnotation]; ok { return true } var beta bool cmd.VisitParents(func(cmd *cobra.Command) { - if _, ok := cmd.Annotations["betaCLI"]; ok { + if _, ok := cmd.Annotations[betaCLIAnnotation]; ok { beta = true } }) @@ -588,6 +593,21 @@ func isDeprecated(cmd *cobra.Command) bool { return cmd.Deprecated != "" } +func isDocHidden(cmd *cobra.Command) bool { + _, ok := cmd.Annotations[docgen.DocHiddenAnnotation] + return ok +} + +// deprecationHint returns the per-command migration hint to show on the docs +// page. The generic boilerplate is conveyed by the docs snippet, so it is +// suppressed here to avoid duplication; only custom hints are surfaced. +func deprecationHint(cmd *cobra.Command) string { + if cmd.Deprecated == deprecatedCommandMsg { + return "" + } + return cmd.Deprecated +} + const usageTemplate = `{{- if isBeta .}}Beta Feature: {{.CommandPath}} is a beta feature. Beta features provide early access to product functionality. These diff --git a/cmd/kosli/testdata/output/docs/mintlify/artifact.md b/cmd/kosli/testdata/output/docs/mintlify/artifact.md index b021046a8..74ab3ba6b 100644 --- a/cmd/kosli/testdata/output/docs/mintlify/artifact.md +++ b/cmd/kosli/testdata/output/docs/mintlify/artifact.md @@ -1,13 +1,15 @@ --- title: "artifact" -beta: false -deprecated: true +tag: "DEPRECATED" description: "Report an artifact creation to a Kosli flow. " --- - -**artifact** is deprecated. see kosli attest commands Deprecated commands will be removed in a future release. - +import CliDeprecatedNotice from "/snippets/cli-deprecated-notice.mdx"; + + + +see kosli attest commands + ## Synopsis ```shell diff --git a/cmd/kosli/testdata/output/docs/mintlify/snyk.md b/cmd/kosli/testdata/output/docs/mintlify/snyk.md index 8cc9443ad..728b2d1be 100644 --- a/cmd/kosli/testdata/output/docs/mintlify/snyk.md +++ b/cmd/kosli/testdata/output/docs/mintlify/snyk.md @@ -1,7 +1,5 @@ --- title: "snyk" -beta: false -deprecated: false description: "Report a snyk attestation to an artifact or a trail in a Kosli flow. " --- diff --git a/internal/docgen/formatter.go b/internal/docgen/formatter.go index a787d7816..45f1c2169 100644 --- a/internal/docgen/formatter.go +++ b/internal/docgen/formatter.go @@ -2,12 +2,19 @@ package docgen import "github.com/spf13/cobra" +// DocHiddenAnnotation marks a hidden command that should still get a generated +// docs page (with hidden: true front matter), as opposed to internal commands +// that are never documented. cmd/kosli sets this annotation; the tree walker +// checks for its presence. +const DocHiddenAnnotation = "docHidden" + // CommandMeta holds metadata about a cobra command for doc generation. type CommandMeta struct { Name string Beta bool Deprecated bool DeprecMsg string + Hidden bool Summary string Long string UseLine string diff --git a/internal/docgen/generate.go b/internal/docgen/generate.go index 4dc282f0c..a03c8579b 100644 --- a/internal/docgen/generate.go +++ b/internal/docgen/generate.go @@ -15,8 +15,9 @@ import ( // leaf command using the provided Formatter. func GenMarkdownTree(cmd *cobra.Command, dir string, formatter Formatter, metaFn CommandMetaFunc) error { for _, c := range cmd.Commands() { - // skip all unavailable commands except deprecated ones - if (!c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand()) && c.Deprecated == "" { + // skip unavailable commands, except deprecated ones and hidden-but-documented ones + _, docHidden := c.Annotations[DocHiddenAnnotation] + if (!c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand()) && c.Deprecated == "" && !docHidden { continue } if err := GenMarkdownTree(c, dir, formatter, metaFn); err != nil { @@ -60,8 +61,9 @@ func genMarkdownCustom(cmd *cobra.Command, w io.Writer, formatter Formatter, met // Title buf.WriteString(formatter.Title(name)) - // Beta warning - if meta.Beta { + // Beta warning. Deprecated takes precedence (matching lifecycleTag), so a + // command that is somehow both renders only the deprecated notice. + if meta.Beta && !meta.Deprecated { buf.WriteString(formatter.BetaWarning(name)) } diff --git a/internal/docgen/generate_test.go b/internal/docgen/generate_test.go index e8a994a3a..a05de19e8 100644 --- a/internal/docgen/generate_test.go +++ b/internal/docgen/generate_test.go @@ -81,6 +81,46 @@ func TestGenMarkdownTreeSkipsHiddenCommands(t *testing.T) { } } +func TestGenMarkdownTreeIncludesDocHiddenCommands(t *testing.T) { + dir := t.TempDir() + + root := &cobra.Command{Use: "root"} + docHidden := &cobra.Command{ + Use: "dochidden", + Short: "A hidden but documented cmd", + Long: "Hidden but documented long desc.", + Hidden: true, + Annotations: map[string]string{DocHiddenAnnotation: ""}, + RunE: func(cmd *cobra.Command, args []string) error { return nil }, + } + root.AddCommand(docHidden) + + metaFn := func(cmd *cobra.Command) CommandMeta { + _, hidden := cmd.Annotations[DocHiddenAnnotation] + return CommandMeta{ + Name: cmd.CommandPath(), + Hidden: hidden, + Summary: cmd.Short, + Long: cmd.Long, + UseLine: cmd.UseLine(), + Runnable: cmd.Runnable(), + } + } + + if err := GenMarkdownTree(root, dir, MintlifyFormatter{}, metaFn); err != nil { + t.Fatalf("GenMarkdownTree error: %v", err) + } + + expected := filepath.Join(dir, "root_dochidden.md") + content, err := os.ReadFile(expected) + if err != nil { + t.Fatalf("expected doc-hidden command to be generated: %v", err) + } + if !strings.Contains(string(content), "hidden: true") { + t.Errorf("expected hidden: true front matter, got:\n%s", string(content)) + } +} + func TestGenMarkdownTreeIncludesDeprecatedCommands(t *testing.T) { dir := t.TempDir() @@ -120,8 +160,8 @@ func TestGenMarkdownTreeIncludesDeprecatedCommands(t *testing.T) { if err != nil { t.Fatalf("failed to read file: %v", err) } - if !strings.Contains(string(content), "") { - t.Error("expected deprecation warning in output") + if !strings.Contains(string(content), "") { + t.Error("expected deprecation snippet in output") } } diff --git a/internal/docgen/mintlify.go b/internal/docgen/mintlify.go index a68cba965..768894669 100644 --- a/internal/docgen/mintlify.go +++ b/internal/docgen/mintlify.go @@ -16,28 +16,43 @@ func (MintlifyFormatter) Title(name string) string { func (MintlifyFormatter) FrontMatter(meta CommandMeta) string { desc := sanitizeDescription(meta.Summary) - return fmt.Sprintf("---\ntitle: \"%s\"\nbeta: %t\ndeprecated: %t\ndescription: \"%s\"\n---\n\n", - meta.Name, meta.Beta, meta.Deprecated, desc) + var b strings.Builder + fmt.Fprintf(&b, "---\ntitle: \"%s\"\n", meta.Name) + if tag := lifecycleTag(meta); tag != "" { + fmt.Fprintf(&b, "tag: \"%s\"\n", tag) + } + if meta.Hidden { + b.WriteString("hidden: true\n") + } + fmt.Fprintf(&b, "description: \"%s\"\n---\n\n", desc) + return b.String() +} + +// lifecycleTag returns the Mintlify sidebar tag for a command's stage. +// Deprecated takes precedence over beta if both are somehow set. +func lifecycleTag(meta CommandMeta) string { + switch { + case meta.Deprecated: + return "DEPRECATED" + case meta.Beta: + return "BETA" + default: + return "" + } } func (MintlifyFormatter) BetaWarning(name string) string { - var b strings.Builder - b.WriteString("\n") - fmt.Fprintf(&b, "**%s** is a beta feature. ", name) - fmt.Fprintf(&b, "Beta features provide early access to product functionality. ") - fmt.Fprintf(&b, "These features may change between releases without warning, or can be removed in a ") - fmt.Fprintf(&b, "future release.\n") - fmt.Fprintf(&b, "Please contact us to enable this feature for your organization.\n") - b.WriteString("\n") - return b.String() + return "import CliBetaNotice from \"/snippets/cli-beta-notice.mdx\";\n\n\n\n" } func (MintlifyFormatter) DeprecatedWarning(name, message string) string { var b strings.Builder - b.WriteString("\n") - fmt.Fprintf(&b, "**%s** is deprecated. %s ", name, message) - fmt.Fprintf(&b, "Deprecated commands will be removed in a future release.\n") - b.WriteString("\n") + b.WriteString("import CliDeprecatedNotice from \"/snippets/cli-deprecated-notice.mdx\";\n\n\n\n") + if message != "" { + // Escape the same way prose is escaped elsewhere, so a future hint + // containing <, {, or > does not break the MDX build. + fmt.Fprintf(&b, "%s\n\n", escapeMintlifyProse(message)) + } return b.String() } diff --git a/internal/docgen/mintlify_test.go b/internal/docgen/mintlify_test.go index f15c0197e..4949d3b8b 100644 --- a/internal/docgen/mintlify_test.go +++ b/internal/docgen/mintlify_test.go @@ -36,17 +36,60 @@ func TestMintlifyFrontMatterTruncatesLongDescription(t *testing.T) { } } +func TestMintlifyFrontMatterBetaTag(t *testing.T) { + f := MintlifyFormatter{} + got := f.FrontMatter(CommandMeta{Name: "kosli evaluate", Beta: true}) + if !strings.Contains(got, `tag: "BETA"`) { + t.Errorf("expected BETA tag, got:\n%s", got) + } +} + +func TestMintlifyFrontMatterDeprecatedTag(t *testing.T) { + f := MintlifyFormatter{} + got := f.FrontMatter(CommandMeta{Name: "kosli report approval", Deprecated: true}) + if !strings.Contains(got, `tag: "DEPRECATED"`) { + t.Errorf("expected DEPRECATED tag, got:\n%s", got) + } +} + +func TestMintlifyFrontMatterDeprecatedWinsOverBeta(t *testing.T) { + f := MintlifyFormatter{} + got := f.FrontMatter(CommandMeta{Name: "cmd", Beta: true, Deprecated: true}) + if !strings.Contains(got, `tag: "DEPRECATED"`) || strings.Contains(got, `tag: "BETA"`) { + t.Errorf("expected DEPRECATED to win, got:\n%s", got) + } +} + +func TestMintlifyFrontMatterHidden(t *testing.T) { + f := MintlifyFormatter{} + got := f.FrontMatter(CommandMeta{Name: "kosli attest decision", Hidden: true}) + if !strings.Contains(got, "hidden: true") { + t.Errorf("expected hidden: true, got:\n%s", got) + } +} + +func TestMintlifyFrontMatterNormalHasNoTagOrHidden(t *testing.T) { + f := MintlifyFormatter{} + got := f.FrontMatter(CommandMeta{Name: "kosli attest snyk"}) + if strings.Contains(got, "tag:") { + t.Errorf("expected no tag for normal command, got:\n%s", got) + } + if strings.Contains(got, "hidden:") { + t.Errorf("expected no hidden key for normal command, got:\n%s", got) + } +} + func TestMintlifyBetaWarning(t *testing.T) { f := MintlifyFormatter{} - got := f.BetaWarning("kosli foo") - if !strings.Contains(got, "") { - t.Error("expected Warning component") + got := f.BetaWarning("kosli evaluate") + if !strings.Contains(got, `import CliBetaNotice from "/snippets/cli-beta-notice.mdx";`) { + t.Errorf("expected beta snippet import, got:\n%s", got) } - if !strings.Contains(got, "") { - t.Error("expected closing Warning component") + if !strings.Contains(got, "") { + t.Errorf("expected beta snippet component, got:\n%s", got) } - if !strings.Contains(got, "**kosli foo** is a beta feature") { - t.Error("expected command name in warning") + if strings.Contains(got, "") { + t.Errorf("notice prose should live in the snippet, not the generator, got:\n%s", got) } } @@ -63,12 +106,26 @@ func TestMintlifyTutorialTip(t *testing.T) { func TestMintlifyDeprecatedWarning(t *testing.T) { f := MintlifyFormatter{} - got := f.DeprecatedWarning("kosli artifact", "see kosli attest commands") - if !strings.Contains(got, "") { - t.Error("expected Warning component") + got := f.DeprecatedWarning("kosli snapshot server", "use 'kosli snapshot paths' instead") + if !strings.Contains(got, `import CliDeprecatedNotice from "/snippets/cli-deprecated-notice.mdx";`) { + t.Errorf("expected deprecated snippet import, got:\n%s", got) } - if !strings.Contains(got, "**kosli artifact** is deprecated") { - t.Error("expected deprecation message") + if !strings.Contains(got, "") { + t.Errorf("expected deprecated snippet component, got:\n%s", got) + } + if !strings.Contains(got, "use 'kosli snapshot paths' instead") { + t.Errorf("expected migration message as plain text, got:\n%s", got) + } + if strings.Contains(got, "") { + t.Errorf("notice prose should live in the snippet, not the generator, got:\n%s", got) + } +} + +func TestMintlifyDeprecatedWarningEmptyMessage(t *testing.T) { + f := MintlifyFormatter{} + got := f.DeprecatedWarning("cmd", "") + if !strings.Contains(got, "") { + t.Errorf("expected snippet component, got:\n%s", got) } }