Skip to content

Commit c9b4037

Browse files
qdrivenclaude
andcommitted
feat: add spark docs init and spark docs site commands (Refs: #9)
- spark docs init [--root]: create standard docs folder structure - spark docs site [--root]: init docmd config with auto-detected project title and GitHub Pages URL from git remote - Auto-installs @docmd/core globally if not found Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5268467 commit c9b4037

File tree

4 files changed

+316
-0
lines changed

4 files changed

+316
-0
lines changed

cmd/docs/docs.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package docs
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
var DocsCmd = &cobra.Command{
8+
Use: "docs",
9+
Short: "Documentation management commands",
10+
Long: `Manage project documentation structure and site configuration.
11+
12+
This includes:
13+
- init: Create docs folder structure
14+
- site: Initialize docmd site configuration`,
15+
}
16+
17+
func init() {
18+
}

cmd/docs/init.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package docs
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/pterm/pterm"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
var docsDirs = []string{
13+
"analysis",
14+
"features",
15+
"quick-start",
16+
"spec",
17+
"tips",
18+
"usage",
19+
}
20+
21+
var docsFiles = []string{
22+
"Agents.md",
23+
"index.md",
24+
"README.md",
25+
}
26+
27+
var docsInitCmd = &cobra.Command{
28+
Use: "init",
29+
Short: "Create docs folder structure",
30+
Long: `Create the standard docs folder structure:
31+
32+
Agents.md, analysis/, features/, index.md, quick-start/,
33+
README.md, spec/, tips/, usage/
34+
35+
Skips files and directories that already exist.`,
36+
RunE: func(cmd *cobra.Command, args []string) error {
37+
root, err := cmd.Flags().GetString("root")
38+
if err != nil {
39+
return err
40+
}
41+
42+
docsPath := filepath.Join(root, "docs")
43+
return initDocs(docsPath)
44+
},
45+
}
46+
47+
func initDocs(docsPath string) error {
48+
pterm.Info.Printf("Initializing docs structure in %s\n", docsPath)
49+
50+
if err := os.MkdirAll(docsPath, 0755); err != nil {
51+
return fmt.Errorf("failed to create docs directory: %w", err)
52+
}
53+
54+
for _, dir := range docsDirs {
55+
p := filepath.Join(docsPath, dir)
56+
if _, err := os.Stat(p); os.IsNotExist(err) {
57+
if err := os.MkdirAll(p, 0755); err != nil {
58+
return fmt.Errorf("failed to create %s: %w", dir, err)
59+
}
60+
pterm.Success.Printf("Created directory: %s/\n", dir)
61+
} else {
62+
pterm.Info.Printf("Already exists: %s/\n", dir)
63+
}
64+
}
65+
66+
for _, file := range docsFiles {
67+
p := filepath.Join(docsPath, file)
68+
if _, err := os.Stat(p); os.IsNotExist(err) {
69+
if err := os.WriteFile(p, []byte(""), 0644); err != nil {
70+
return fmt.Errorf("failed to create %s: %w", file, err)
71+
}
72+
pterm.Success.Printf("Created file: %s\n", file)
73+
} else {
74+
pterm.Info.Printf("Already exists: %s\n", file)
75+
}
76+
}
77+
78+
pterm.Success.Println("Docs structure initialized.")
79+
return nil
80+
}
81+
82+
func init() {
83+
docsInitCmd.Flags().String("root", ".", "Project root directory")
84+
DocsCmd.AddCommand(docsInitCmd)
85+
}

cmd/docs/site.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package docs
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"strings"
9+
10+
"github.com/pterm/pterm"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var siteCmd = &cobra.Command{
15+
Use: "site",
16+
Short: "Initialize docmd site configuration",
17+
Long: `Initialize docmd documentation site configuration.
18+
19+
Creates docmd.config.js using the docs/ directory as source.
20+
Auto-detects project title and URL from git remote.
21+
Installs @docmd/core if not found.`,
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
root, err := cmd.Flags().GetString("root")
24+
if err != nil {
25+
return err
26+
}
27+
return initSite(root)
28+
},
29+
}
30+
31+
func initSite(root string) error {
32+
absRoot, err := filepath.Abs(root)
33+
if err != nil {
34+
return err
35+
}
36+
37+
title, siteURL := detectRepoInfo(absRoot)
38+
39+
pterm.Info.Printf("Project title: %s\n", title)
40+
pterm.Info.Printf("Site URL: %s\n", siteURL)
41+
42+
if err := ensureDocmdInstalled(); err != nil {
43+
return err
44+
}
45+
46+
configPath := filepath.Join(absRoot, "docmd.config.js")
47+
if _, err := os.Stat(configPath); err == nil {
48+
overwrite, _ := pterm.DefaultInteractiveConfirm.Show(
49+
fmt.Sprintf("docmd.config.js already exists. Overwrite?"),
50+
)
51+
if !overwrite {
52+
pterm.Info.Println("Skipped.")
53+
return nil
54+
}
55+
}
56+
57+
config := generateConfig(title, siteURL)
58+
if err := os.WriteFile(configPath, []byte(config), 0644); err != nil {
59+
return fmt.Errorf("failed to write docmd.config.js: %w", err)
60+
}
61+
pterm.Success.Println("Created docmd.config.js")
62+
63+
packageJSON := filepath.Join(absRoot, "package.json")
64+
if _, err := os.Stat(packageJSON); os.IsNotExist(err) {
65+
if err := initPackageJSON(absRoot); err != nil {
66+
return err
67+
}
68+
}
69+
70+
if err := runNpmInstall(absRoot); err != nil {
71+
return err
72+
}
73+
74+
pterm.Success.Println("Docmd site initialized. Run `docmd dev` to preview.")
75+
return nil
76+
}
77+
78+
func detectRepoInfo(root string) (title, siteURL string) {
79+
title = filepath.Base(root)
80+
siteURL = ""
81+
82+
cmd := exec.Command("git", "remote", "get-url", "origin")
83+
cmd.Dir = root
84+
remoteURL, err := cmd.Output()
85+
if err != nil {
86+
return
87+
}
88+
url := strings.TrimSpace(string(remoteURL))
89+
90+
owner, repo := parseGitURL(url)
91+
if owner != "" && repo != "" {
92+
title = repo
93+
siteURL = fmt.Sprintf("https://%s.github.io/%s", owner, repo)
94+
}
95+
return
96+
}
97+
98+
func parseGitURL(raw string) (owner, repo string) {
99+
url := raw
100+
url = strings.TrimPrefix(url, "git@github.com:")
101+
url = strings.TrimPrefix(url, "https://github.com/")
102+
url = strings.TrimPrefix(url, "ssh://git@github.com/")
103+
url = strings.TrimSuffix(url, ".git")
104+
105+
parts := strings.SplitN(url, "/", 2)
106+
if len(parts) == 2 {
107+
owner = parts[0]
108+
repo = parts[1]
109+
}
110+
return
111+
}
112+
113+
func ensureDocmdInstalled() error {
114+
if _, err := exec.LookPath("docmd"); err == nil {
115+
return nil
116+
}
117+
118+
pterm.Info.Println("docmd not found. Installing @docmd/core globally...")
119+
cmd := exec.Command("npm", "install", "-g", "@docmd/core")
120+
cmd.Stdout = os.Stdout
121+
cmd.Stderr = os.Stderr
122+
if err := cmd.Run(); err != nil {
123+
return fmt.Errorf("failed to install docmd: %w", err)
124+
}
125+
pterm.Success.Println("Installed @docmd/core globally.")
126+
return nil
127+
}
128+
129+
func generateConfig(title, siteURL string) string {
130+
return fmt.Sprintf(`// docmd.config.js
131+
export default defineConfig({
132+
title: '%s',
133+
url: '%s',
134+
135+
src: 'docs',
136+
out: 'site',
137+
138+
layout: {
139+
spa: true,
140+
header: { enabled: true },
141+
sidebar: { collapsible: true, defaultCollapsed: false },
142+
optionsMenu: {
143+
position: 'sidebar-top',
144+
components: { search: true, themeSwitch: true, sponsor: null },
145+
},
146+
footer: {
147+
style: 'minimal',
148+
content: '© ' + new Date().getFullYear() + ' %s',
149+
branding: true,
150+
},
151+
},
152+
153+
theme: {
154+
name: 'sky',
155+
appearance: 'system',
156+
codeHighlight: true,
157+
customCss: [],
158+
},
159+
160+
minify: true,
161+
autoTitleFromH1: true,
162+
copyCode: true,
163+
pageNavigation: true,
164+
165+
navigation: [
166+
{ title: 'Home', path: '/', icon: 'home' },
167+
],
168+
169+
plugins: {
170+
seo: {
171+
defaultDescription: '%s documentation',
172+
openGraph: { defaultImage: '' },
173+
twitter: { cardType: 'summary_large_image' },
174+
},
175+
sitemap: { defaultChangefreq: 'weekly' },
176+
search: {},
177+
mermaid: {},
178+
llms: { fullContext: true },
179+
},
180+
});
181+
`, title, siteURL, title, title)
182+
}
183+
184+
func initPackageJSON(root string) error {
185+
cmd := exec.Command("npm", "init", "-y")
186+
cmd.Dir = root
187+
cmd.Stdout = os.Stdout
188+
cmd.Stderr = os.Stderr
189+
if err := cmd.Run(); err != nil {
190+
return fmt.Errorf("failed to init package.json: %w", err)
191+
}
192+
pterm.Success.Println("Created package.json")
193+
return nil
194+
}
195+
196+
func runNpmInstall(root string) error {
197+
cmd := exec.Command("npm", "install")
198+
cmd.Dir = root
199+
cmd.Stdout = os.Stdout
200+
cmd.Stderr = os.Stderr
201+
if err := cmd.Run(); err != nil {
202+
return fmt.Errorf("failed to run npm install: %w", err)
203+
}
204+
pterm.Success.Println("Dependencies installed.")
205+
return nil
206+
}
207+
208+
func init() {
209+
siteCmd.Flags().String("root", ".", "Project root directory")
210+
DocsCmd.AddCommand(siteCmd)
211+
}

cmd/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"fmt"
5+
"spark/cmd/docs"
56
"spark/cmd/git"
67
"spark/cmd/magic"
78
"spark/cmd/script"
@@ -39,6 +40,7 @@ func init() {
3940
rootCmd.AddCommand(git.GitCmd)
4041
rootCmd.AddCommand(magic.MagicCmd)
4142
rootCmd.AddCommand(script.ScriptCmd)
43+
rootCmd.AddCommand(docs.DocsCmd)
4244
}
4345

4446
func initConfig() {

0 commit comments

Comments
 (0)