Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .docs.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
index:
owner: mindovermachine-dev
topic: documentation
file: docz/docs-repos.html
visibility: public
format: sidebar
49 changes: 49 additions & 0 deletions cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,55 @@ func TestIndexCommand(t *testing.T) {
})
}

func TestTemplateStarlightScaffold(t *testing.T) {
dir := t.TempDir()
testutil.Chdir(t, dir)
t.Setenv("PATH", t.TempDir())
stdout, err := run(t, "template", "--starlight")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(stdout, filepath.Join("docs", "repository-template.md")) {
t.Errorf("stdout = %q", stdout)
}
if _, err := os.Stat(filepath.Join("docs", "repository-template.md")); err != nil {
t.Fatal(err)
}
if _, err := run(t, "template", "--starlight"); err == nil {
t.Fatal("expected refusing to overwrite the existing template")
}
if _, err := run(t, "template", "--starlight", "--force"); err != nil {
t.Fatalf("force overwrite failed: %v", err)
}

customOutput := filepath.Join(dir, "custom")
if _, err := run(t, "template", "--starlight", "--output", customOutput); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(filepath.Join(customOutput, "repository-template.md")); err != nil {
t.Fatal(err)
}
}

func TestIndexRequiresScaffoldedTemplateAndHidesScaffoldFlags(t *testing.T) {
dir := t.TempDir()
testutil.Chdir(t, dir)
t.Setenv("PATH", t.TempDir())
if _, err := run(t, "index", "--owner", "acme", "--format", "sidebar"); err == nil ||
!strings.Contains(err.Error(), "gh docs template --starlight --output docs") {
t.Fatalf("expected actionable missing-template error, got %v", err)
}
out, err := run(t, "index", "--help")
if err != nil {
t.Fatal(err)
}
for _, removed := range []string{"--template", "--starlight"} {
if strings.Contains(out, removed) {
t.Errorf("index help still advertises %s", removed)
}
}
}

func TestTemplateCommand(t *testing.T) {
dir := t.TempDir()
cfg := testutil.WriteFile(t, dir, "cfg.yml", "template:\n output: "+filepath.Join(dir, "site")+"\n")
Expand Down
16 changes: 13 additions & 3 deletions cmd/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,30 @@ func newIndexCmd() *cobra.Command {
Use: "index",
Short: "Generate an index of documentation repositories",
Long: `List repositories of a user/organization that carry a given topic and
write their name, description and homepage URL to a YAML, JSON, HTML or Starlight sidebar file.
write their name, description and homepage URL to a YAML, JSON or HTML file.
The sidebar format writes repository pages from a Markdown or MDX template and a paired
Starlight sidebar module.

With --file the extension decides the format; --file and --format are mutually
exclusive. Missing parent directories are created.

Precedence: flags > config file (.docs.yml) > built-in fallbacks.
Config keys, under an "index" node: owner, visibility, topic, format, file, limit.`,
Config keys, under an "index" node: owner, visibility, topic, format, file, pages-dir, base-url, badge-url, limit.
Sidebar mode requires docs/repository-template.md or docs/repository-template.mdx; .md takes precedence when both exist. See "gh docs template".`,
RunE: func(cmd *cobra.Command, _ []string) error {
cfg, err := index.LoadConfig(configFile, cmd.Flags().Changed("config"))
if err != nil {
return err
}
path, err := index.Run(index.Merge(cfg, flags))
merged := index.Merge(cfg, flags)
path, err := index.Run(merged)
if err != nil {
return err
}
fmt.Fprintf(cmd.OutOrStdout(), "Wrote %s\n", path)
if merged.Format == "sidebar" {
fmt.Fprintf(cmd.OutOrStdout(), "Wrote pages in %s\n", merged.PagesDir)
}
return nil
},
}
Expand All @@ -45,6 +52,9 @@ Config keys, under an "index" node: owner, visibility, topic, format, file, limi
f.StringVarP(&flags.Topic, "topic", "t", "", "Repository topic to filter on")
f.StringVarP(&flags.Format, "format", "F", "", "Output format: yaml, json, html or sidebar (not valid with --file)")
f.StringVarP(&flags.File, "file", "f", "", "Output file path (.yml, .yaml, .json, .html or .mjs); extension sets the format")
f.StringVar(&flags.PagesDir, "pages-dir", "", "Directory for generated Markdown or MDX repository pages (sidebar format)")
f.StringVar(&flags.BaseURL, "base-url", "", "Base URL for generated local sidebar links")
f.StringVar(&flags.BadgeURL, "badge-url", "", "Optional status badge URL for repository page templates")
f.IntVarP(&flags.Limit, "limit", "l", 0, "Maximum number of repositories to fetch")
c.MarkFlagsMutuallyExclusive("file", "format")
return c
Expand Down
28 changes: 23 additions & 5 deletions cmd/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package cmd
import (
"errors"
"fmt"
"path/filepath"

"github.com/mindovermachine-dev/gh-docs/internal/index"
"github.com/mindovermachine-dev/gh-docs/internal/template"
"github.com/spf13/cobra"
)
Expand All @@ -13,26 +15,40 @@ func newTemplateCmd() *cobra.Command {
configFile string
flags template.Config
jekyll bool
starlight bool
force bool
)

c := &cobra.Command{
Use: "template",
Short: "Generate starter files for the documentation site",
Long: `Generate a minimal GitHub Pages site that renders the list produced by
"index". Files are plain Jekyll sources; GitHub Pages builds them itself.
Long: `Generate starter files for the documentation site.

Use --jekyll to generate a GitHub Pages index, or --starlight to copy a Markdown
repository page template used by "index --format sidebar".

Precedence: flags > config file (.docs.yml) > built-in fallbacks.
Config keys, under a "template" node: output.`,
RunE: func(cmd *cobra.Command, _ []string) error {
if !jekyll {
return errors.New("a template type is required: use --jekyll")
if !jekyll && !starlight {
return errors.New("a template type is required: use --jekyll or --starlight")
}
cfg, err := template.LoadConfig(configFile, cmd.Flags().Changed("config"))
if err != nil {
return err
}
paths, err := template.Jekyll(template.Merge(cfg, flags), force)
merged := template.Merge(cfg, flags)
if starlight {
destination := filepath.Join(merged.Output, "repository-template.md")
if err := index.StarlightTemplate(destination, force); err != nil {
return err
}
if _, err := fmt.Fprintf(cmd.OutOrStdout(), "Wrote %s\n", destination); err != nil {
return err
}
return nil
}
paths, err := template.Jekyll(merged, force)
if err != nil {
return err
}
Expand All @@ -45,8 +61,10 @@ Config keys, under a "template" node: output.`,

f := c.Flags()
f.BoolVar(&jekyll, "jekyll", false, "Generate a Jekyll (GitHub Pages) index.md and _config.yml")
f.BoolVar(&starlight, "starlight", false, "Copy a Starlight repository page template into the output folder")
f.StringVarP(&configFile, "config", "c", ".docs.yml", "Path to the config file")
f.StringVarP(&flags.Output, "output", "o", "", "Output folder")
f.BoolVar(&force, "force", false, "Overwrite existing files")
c.MarkFlagsMutuallyExclusive("jekyll", "starlight")
return c
}
72 changes: 23 additions & 49 deletions docs/content/commands/index.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: index
description: List documentation repositories and write their metadata to an output file.
description: Export repository data or generate repository pages and a Starlight sidebar.
---

**`gh docs index` lists repositories owned by a GitHub user or organization, filters them by repository topic, and writes each matching repository's name, description, and homepage URL.**
**`gh docs index` lists repositories owned by a GitHub user or organization and filters them by repository topic. YAML, JSON, and HTML produce a single-file export; sidebar mode produces one Markdown or MDX page per repository and a Starlight sidebar module.**

## Usage

Expand All @@ -15,17 +15,20 @@ The owner can be set in `.docs.yml` instead of passed as a flag. The command inv

## Flags

| Flag | Default | Description |
| -------------------- | ---------------- | -------------------------------------------------------------------------------------- |
| `-c`, `--config` | `.docs.yml` | Path to the configuration file. |
| `-o`, `--owner` | None | GitHub user or organization to list. Required unless configured. |
| `-v`, `--visibility` | `public` | Repository visibility: `public`, `internal`, or `private`. |
| `-t`, `--topic` | `documentation` | Repository topic to match. Matching is case-insensitive. |
| `-F`, `--format` | `yaml` | Output format: `yaml`, `json`, `html`, or `sidebar`. Cannot be used with `--file`. |
| `-f`, `--file` | `docs-repos.yml` | Output path. Its extension selects the format. Missing parent directories are created. |
| `-l`, `--limit` | `1000` | Maximum number of repositories returned by `gh repo list`. |

Supported output extensions are `.yml`, `.yaml`, `.json`, `.html`, and `.mjs`. The `.mjs` extension selects the Starlight sidebar format.
| Flag | Default | Description |
| -------------------- | ------------------- | ------------------------------------------------------------------------------------------------ |
| `-c`, `--config` | `.docs.yml` | Path to the configuration file. |
| `-o`, `--owner` | None | GitHub user or organization to list. Required unless configured. |
| `-v`, `--visibility` | `public` | Repository visibility: `public`, `internal`, or `private`. |
| `-t`, `--topic` | `documentation` | Repository topic to match. Matching is case-insensitive. |
| `-F`, `--format` | `yaml` | Output format: `yaml`, `json`, `html`, or `sidebar`. Cannot be used with `--file`. |
| `-f`, `--file` | `docs-repos.yml` | Output path. `.mjs` selects paired sidebar/page generation. |
| `--pages-dir` | `docs/content/repo` | Directory for generated Markdown or MDX pages in sidebar mode. |
| `--base-url` | Inferred | URL prefix for local sidebar links when the filesystem layout does not determine the site route. |
| `--badge-url` | None | Optional status badge URL exposed to the page template. |
| `-l`, `--limit` | `1000` | Maximum number of repositories returned by `gh repo list`. |

Supported index output extensions are `.yml`, `.yaml`, `.json`, `.html`, and `.mjs`. YAML, JSON, and HTML are single-file exports. The `.mjs` extension selects the paired sidebar/pages format. Page templates are read from `docs/repository-template.md` or `docs/repository-template.mdx`; `.md` is preferred if both exist, and generated pages use the selected template's extension.

## formats

Expand Down Expand Up @@ -83,49 +86,20 @@ An empty GitHub homepage URL is emitted as an empty string. The repository descr

### Sidebar (ES module)

The `sidebar` format writes an ES module exporting an Astro Starlight theme compatible sidebar array. Select it with `--format sidebar` or give `--file` a `.mjs` extension.
The `sidebar` format writes a Markdown page for every matching repository and an ES module exporting local links to those pages. Select it with `--format sidebar` or give `--file` a `.mjs` extension.

See [Starlight's sidebar reference](https://starlight.astro.build/guides/sidebar/)

```sh
gh docs index --owner mindovermachine-dev --file docs/docs-repos.mjs
gh docs index --owner mindovermachine-dev --file docs/sidebar/repositories.mjs \
--pages-dir astro/src/content/docs/repositories
```

The export contains a `Documentation` group. Each matching repository becomes a `{ label, link }` item, the form supported for external links by Starlight. `label` is the repository name. `link` uses the GitHub homepage URL when configured; if it is empty, the CLI uses `https://github.com/<owner>/<repo>`.
The generated module exports one `Documentation` group with `{ label, link }` items. Links point to the pages generated in the same invocation, not to GitHub or repository homepage URLs. For the default `docs/content/repo` directory the inferred route is `/repo/<slug>/`; for a path under `src/content/docs`, the route is inferred relative to `docs`. Use `--base-url` when you need to specify a route explicitly. Missing directories are created and generated pages are overwritten. Pages for repositories no longer selected are not deleted and must be cleaned up manually.

```js
export const sidebar = [
{
label: "Documentation",
items: [{ label: "remote-astro-theme", link: "https://docs.example.org/remote-astro-theme/" }],
},
];
```
Sidebar mode reads `docs/repository-template.md` or `docs/repository-template.mdx`. If both exist, it uses `.md`. Generated pages keep the selected template's extension. Create the default `.md` template with `gh docs template --starlight --output docs`; edit or rename it to `.mdx` as needed, then run `gh docs index --format sidebar` again.

Import the named export into the site's Astro configuration and use it in Starlight's main configuration site. The following is an example of how it is used in combination with a [`remote-astro-theme`](https://docs.mindovermachine.dev/remote-astro-theme)

```js
// from mindovermachine-dev/mindovermachine-dev.github.io
import { sidebar } from "./docs-repos.mjs";

export default {
theme: "mindovermachine-dev/mom-doc-theme@main",
astro: {
site: "https://docs.mindovermachine.dev",
base: ".",
server: { host: true },
},
title: "Mind over Machine Dev Docs",
social: [
{
icon: "github",
label: "GitHub",
href: "https://github.com/mindovermachine-dev",
},
],
sidebar,
};
```
See [Starlight sidebar and page templates](../output-formats/starlight-sidebar.md) for template keywords, delimiters, configuration, and site imports.

## Examples

Expand All @@ -141,7 +115,7 @@ Write a JSON index at a specific path:
gh docs index --owner mindovermachine-dev --file site/data/docs-repos.json
```

Select an organization and topic from flags, and write a Starlight sidebar module:
Select an organization and topic from flags, and write repository pages plus a Starlight sidebar module:

```sh
gh docs index --owner mindovermachine-dev --topic documentation --file docs/docs-repos.mjs
Expand Down
29 changes: 20 additions & 9 deletions docs/content/commands/template.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: template
description: Copy embedded starter files for a GitHub Pages Jekyll site.
description: Copy starter files for Jekyll sites or Starlight repository pages.
---

`gh docs template` copies embedded site templates into an output folder. The current template type is Jekyll, intended for GitHub Pages to build without a separate local Jekyll build step.
`gh docs template` copies starter files into an output folder. Use Jekyll for a simple GitHub Pages site or Starlight to create the Markdown template used by `gh docs index --format sidebar`.

## Usage

Expand All @@ -13,23 +13,34 @@ gh docs template --jekyll --output docz

By default, the output folder is `docs`. Use another folder when that name conflicts with the extension binary or an existing file.

Create the Starlight repository page template with:

```sh
gh docs template --starlight
```

This writes `docs/repository-template.md`. Edit this standalone Markdown file, then use `gh docs index --format sidebar`; sidebar mode reads exactly that path.

## Flags

| Flag | Default | Description |
| ---------------- | ----------- | ------------------------------------------------ |
| `--jekyll` | Not enabled | Required. Selects the Jekyll template. |
| `-c`, `--config` | `.docs.yml` | Path to the configuration file. |
| `-o`, `--output` | `docs` | Destination folder. |
| `--force` | Not enabled | Overwrite generated files if they already exist. |
| Flag | Default | Description |
| ---------------- | ----------- | ----------------------------------------------------------------- |
| `--jekyll` | Not enabled | Select the Jekyll starter. Mutually exclusive with `--starlight`. |
| `--starlight` | Not enabled | Copy `repository-template.md` from the Starlight starter source. |
| `-c`, `--config` | `.docs.yml` | Path to the configuration file. |
| `-o`, `--output` | `docs` | Destination folder. |
| `--force` | Not enabled | Overwrite generated files if they already exist. |

The `template` node in `.docs.yml` supports the `output` setting. The `--output` flag takes precedence over that setting.

## Generated files

The command copies the embedded files `index.md` and `_config.yml`. The index reads `docs-repos.yml` from the site root and renders each entry as a link when it has a homepage URL.
`--jekyll` copies the embedded files `index.md` and `_config.yml`. The index reads `docs-repos.yml` from the site root and renders each entry as a link when it has a homepage URL.

If either generated file already exists, the command stops before writing unless `--force` is supplied. Use `--force` only when replacing those files is intended.

```sh
gh docs template --jekyll --output docz --force
```

`--starlight` copies the maintained Markdown source from `internal/index/templates/starlight.md` to `<output>/repository-template.md`. The default output folder therefore creates `docs/repository-template.md`; an alternate `--output` directory is honored. If the file exists, the command refuses to overwrite it unless `--force` is provided.
Loading
Loading