Skip to content

Commit 76d376c

Browse files
authored
Merge pull request #1 from m7medVision/fix-module-path
Fix module path
2 parents 6f62208 + afa0a11 commit 76d376c

21 files changed

Lines changed: 1450 additions & 1105 deletions

.github/workflows/release.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Release
2+
on:
3+
push:
4+
branches: [main]
5+
6+
jobs:
7+
release:
8+
runs-on: ubuntu-latest
9+
permissions:
10+
contents: write
11+
steps:
12+
- uses: actions/checkout@v4
13+
with:
14+
fetch-depth: 0 # required — needs full git history
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: 20
18+
- run: npm install -g semantic-release @semantic-release/changelog @semantic-release/git
19+
- run: npx semantic-release
20+
env:
21+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.releaserc.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"branches": ["main"],
3+
"plugins": [
4+
"@semantic-release/commit-analyzer",
5+
"@semantic-release/release-notes-generator",
6+
"@semantic-release/changelog",
7+
"@semantic-release/git"
8+
]
9+
}

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# 1.0.0 (2026-04-02)
2+
3+
4+
### Features
5+
6+
* my new init for next vibecoded application ([d97b2a3](https://github.com/m7medVision/wpswag/commit/d97b2a3a572280686e3dd83bf1b4818042c6b4df))

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# wpswag
2+
3+
`wpswag` converts a WordPress REST API into an OpenAPI 3.0.3 JSON file.
4+
5+
## Install
6+
7+
```bash
8+
go install github.com/m7medVision/wpswag@latest
9+
```
10+
11+
## Build
12+
13+
```bash
14+
go build -o wpswag .
15+
```
16+
17+
## Usage
18+
19+
```bash
20+
./wpswag convert -u "https://example.com/wp-json" -o openapi.json
21+
```
22+
23+
You can also run it without building:
24+
25+
```bash
26+
go run . convert -u "https://example.com/wp-json"
27+
```
28+
29+
## Notes
30+
31+
- Input can be a WordPress REST URL or a local JSON file.
32+
- Output defaults to `openapi.json`.
33+
- The generated spec includes typed schemas for core `wp/v2` resources when schema metadata is available.

cmd/convert.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"strconv"
10+
11+
"github.com/spf13/cobra"
12+
13+
"github.com/m7medVision/wpswag/internal/convert"
14+
"github.com/m7medVision/wpswag/internal/util"
15+
"github.com/m7medVision/wpswag/internal/wp"
16+
)
17+
18+
var (
19+
flagURL string
20+
flagOut string
21+
flagDebug bool
22+
)
23+
24+
var convertCmd = &cobra.Command{
25+
Use: "convert",
26+
Short: "Convert WordPress REST API JSON to OpenAPI 3.0 spec",
27+
Long: "Fetch a WordPress REST API index (URL or local file) and generate an OpenAPI 3.0.3 JSON specification.",
28+
RunE: runConvert,
29+
}
30+
31+
func init() {
32+
convertCmd.Flags().StringVarP(&flagURL, "url", "u", "", "WordPress REST URL or local JSON file (e.g. https://site/wp-json or ./wp-json.json)")
33+
convertCmd.Flags().StringVarP(&flagOut, "output", "o", "openapi.json", "Output OpenAPI file (default: openapi.json)")
34+
convertCmd.Flags().BoolVar(&flagDebug, "debug", false, "Print debug stats to stderr")
35+
_ = convertCmd.MarkFlagRequired("url")
36+
rootCmd.AddCommand(convertCmd)
37+
}
38+
39+
func runConvert(cmd *cobra.Command, args []string) error {
40+
data, err := util.Fetch(flagURL)
41+
if err != nil {
42+
return fmt.Errorf("fetch error: %w", err)
43+
}
44+
data = util.CleanJSON(data)
45+
46+
var idx wp.Index
47+
dec := json.NewDecoder(bytes.NewReader(data))
48+
dec.UseNumber()
49+
if err := dec.Decode(&idx); err != nil {
50+
return fmt.Errorf("decode error: %w", err)
51+
}
52+
53+
conv := convert.NewConverter(&idx, flagURL)
54+
spec, stats, err := conv.Convert()
55+
if err != nil {
56+
fmt.Fprintf(os.Stderr, "convert error: %v\n", err)
57+
}
58+
59+
if flagDebug {
60+
fmt.Fprintf(os.Stderr, "routes=%d endpoints=%d ops=%d paths_out=%d\n",
61+
stats.Routes, stats.Endpoints, stats.Ops, len(spec.Paths))
62+
}
63+
64+
out, err := json.MarshalIndent(spec, "", " ")
65+
if err != nil {
66+
return fmt.Errorf("marshal error: %w", err)
67+
}
68+
69+
if err := os.WriteFile(flagOut, out, 0644); err != nil {
70+
return fmt.Errorf("write error: %w", err)
71+
}
72+
73+
fmt.Fprintf(os.Stderr, "wrote %s (%s bytes)\n", filepath.Base(flagOut), strconv.Itoa(len(out)))
74+
return nil
75+
}

cmd/root.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var rootCmd = &cobra.Command{
11+
Use: "wpswag",
12+
Short: "Generate OpenAPI 3.0 specs from WordPress REST APIs",
13+
Long: "wpswag converts a WordPress REST API index or namespace JSON into an OpenAPI 3.0.3 specification.",
14+
}
15+
16+
// Execute runs the root command.
17+
func Execute() {
18+
if err := rootCmd.Execute(); err != nil {
19+
fmt.Fprintln(os.Stderr, err)
20+
os.Exit(1)
21+
}
22+
}

go.mod

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1-
module sensepost.com/wpswag
1+
module github.com/m7medVision/wpswag
22

33
go 1.25.1
4+
5+
require github.com/spf13/cobra v1.10.2
6+
7+
require (
8+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
9+
github.com/spf13/pflag v1.0.9 // indirect
10+
)

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
2+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
3+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
4+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
5+
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
6+
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
7+
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
8+
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
9+
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
10+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/convert/builder.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package convert
2+
3+
import (
4+
"strings"
5+
6+
"github.com/m7medVision/wpswag/internal/oas"
7+
)
8+
9+
// Builder incrementally builds an OpenAPI 3.0.3 spec from WordPress routes.
10+
type Builder struct {
11+
spec *oas.Spec
12+
stats Stats
13+
}
14+
15+
// Stats holds conversion statistics.
16+
type Stats struct {
17+
Routes int
18+
Endpoints int
19+
Ops int
20+
}
21+
22+
// NewBuilder creates a new spec builder with the given metadata.
23+
func NewBuilder(title, description, serverURL string) *Builder {
24+
if title == "" {
25+
title = "WordPress REST"
26+
}
27+
if serverURL == "" {
28+
serverURL = "https://example.com/wp-json"
29+
}
30+
return &Builder{
31+
spec: &oas.Spec{
32+
OpenAPI: "3.0.3",
33+
Info: oas.Info{
34+
Title: title,
35+
Description: description,
36+
Version: "1.0.0",
37+
},
38+
Servers: []oas.Server{{URL: serverURL}},
39+
Paths: map[string]oas.PathItem{},
40+
},
41+
}
42+
}
43+
44+
// AddPath sets a path item on the spec.
45+
func (b *Builder) AddPath(path string, item oas.PathItem) {
46+
b.spec.Paths[path] = item
47+
}
48+
49+
// GetPath returns the current path item for a given path.
50+
func (b *Builder) GetPath(path string) oas.PathItem {
51+
return b.spec.Paths[path]
52+
}
53+
54+
// AddSchema stores a reusable schema component on the spec.
55+
func (b *Builder) AddSchema(name string, schema oas.Schema) {
56+
if b.spec.Components == nil {
57+
b.spec.Components = &oas.Components{Schemas: map[string]oas.Schema{}}
58+
}
59+
if b.spec.Components.Schemas == nil {
60+
b.spec.Components.Schemas = map[string]oas.Schema{}
61+
}
62+
if _, exists := b.spec.Components.Schemas[name]; exists {
63+
return
64+
}
65+
b.spec.Components.Schemas[name] = schema
66+
}
67+
68+
// IncrementRoutes increments the route counter.
69+
func (b *Builder) IncrementRoutes() {
70+
b.stats.Routes++
71+
}
72+
73+
// IncrementEndpoints increments the endpoint counter.
74+
func (b *Builder) IncrementEndpoints() {
75+
b.stats.Endpoints++
76+
}
77+
78+
// IncrementOps increments the operations counter.
79+
func (b *Builder) IncrementOps() {
80+
b.stats.Ops++
81+
}
82+
83+
// SetMethodOperation sets an operation on a path item by HTTP method.
84+
func SetMethodOperation(pi *oas.PathItem, method string, op *oas.Operation) {
85+
switch strings.ToUpper(method) {
86+
case "GET":
87+
pi.Get = op
88+
case "POST":
89+
pi.Post = op
90+
case "PUT":
91+
pi.Put = op
92+
case "PATCH":
93+
pi.Patch = op
94+
case "DELETE":
95+
pi.Delete = op
96+
case "OPTIONS":
97+
pi.Options = op
98+
case "HEAD":
99+
pi.Head = op
100+
}
101+
}
102+
103+
// IsPathItemEmpty returns true if the path item has no operations.
104+
func IsPathItemEmpty(pi oas.PathItem) bool {
105+
return pi.Get == nil && pi.Post == nil && pi.Put == nil &&
106+
pi.Patch == nil && pi.Delete == nil && pi.Options == nil && pi.Head == nil
107+
}
108+
109+
// Build returns the final spec and stats.
110+
func (b *Builder) Build() (*oas.Spec, *Stats) {
111+
return b.spec, &b.stats
112+
}

0 commit comments

Comments
 (0)