{"id":50913183,"url":"https://github.com/p4suta/ghost-to-md","last_synced_at":"2026-06-16T12:02:21.492Z","repository":{"id":342804659,"uuid":"1175241897","full_name":"P4suta/ghost-to-md","owner":"P4suta","description":"CLI tool to convert Ghost CMS JSON exports to Markdown files with YAML front matter","archived":false,"fork":false,"pushed_at":"2026-03-07T12:44:19.000Z","size":55,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-07T19:25:57.887Z","etag":null,"topics":["clean-architecture","cli","ghost-cms","go","markdown","static-site-generator"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/P4suta.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-07T12:42:37.000Z","updated_at":"2026-03-07T12:44:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/P4suta/ghost-to-md","commit_stats":null,"previous_names":["p4suta/ghost-to-md"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/P4suta/ghost-to-md","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/P4suta%2Fghost-to-md","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/P4suta%2Fghost-to-md/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/P4suta%2Fghost-to-md/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/P4suta%2Fghost-to-md/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/P4suta","download_url":"https://codeload.github.com/P4suta/ghost-to-md/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/P4suta%2Fghost-to-md/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34404748,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clean-architecture","cli","ghost-cms","go","markdown","static-site-generator"],"created_at":"2026-06-16T12:02:19.719Z","updated_at":"2026-06-16T12:02:21.471Z","avatar_url":"https://github.com/P4suta.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ghost-to-md\n\nCLI tool to convert Ghost CMS JSON exports into Markdown files with YAML front matter.\n\n[![Go](https://img.shields.io/badge/Go-1.26-00ADD8?logo=go)](https://go.dev/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Dependencies](https://img.shields.io/badge/Dependencies-1%20external-brightgreen.svg)](#design-decisions)\n\n## Overview\n\n**ghost-to-md** parses Ghost CMS JSON export files and generates individual Markdown files, each with YAML front matter containing metadata (title, date, tags, authors, etc.). The output is directly compatible with static site generators such as Hugo, Jekyll, Astro, and Zola.\n\nBuilt with Clean Architecture principles and a single external dependency (`golang.org/x/net/html`), this tool handles Ghost-specific HTML structures including all 10 types of Ghost editor cards (`kg-*` classes).\n\n## Features\n\n- **Ghost JSON parsing** - Reads `db[0].data` structure with posts, tags, users, and join tables (`posts_tags`, `posts_authors`, `posts_meta`)\n- **10 Ghost card types** - Image, gallery, code, bookmark, callout, toggle, embed, button, product, and file cards\n- **YAML front matter** - Title, slug, date, tags, authors, description, featured image, canonical URL, visibility\n- **Filtering** - By status (`published`/`draft`/`scheduled`), page inclusion, internal tag handling\n- **URL rewriting** - Replace `__GHOST_URL__` placeholders with actual base URL\n- **Single binary** - No runtime dependencies, cross-platform\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────┐\n│                   cmd/main.go                   │\n│              (manual DI, CLI flags)             │\n├─────────────────────────────────────────────────┤\n│                    usecase                       │\n│       MigrateUseCase (orchestrator)             │\n├──────────┬──────────────────┬───────────────────┤\n│   port   │      port        │       port        │\n│  Reader  │    Converter     │      Writer       │\n├──────────┼──────────────────┼───────────────────┤\n│ ghostjson│     htmlconv     │     fswriter      │\n│ (adapter)│    (adapter)     │    (adapter)      │\n├──────────┴──────────────────┴───────────────────┤\n│                    domain                        │\n│    Post, Tag, Author, FrontMatter, Resolver     │\n│           (pure Go, zero dependencies)          │\n└─────────────────────────────────────────────────┘\n```\n\nThe project follows **Ports \u0026 Adapters** (hexagonal) architecture. The domain layer has zero external dependencies. Port interfaces are defined by the consumer (usecase layer), and adapters implement these interfaces. Dependencies always point inward.\n\n## Installation\n\n```bash\n# Build from source\ngit clone https://github.com/P4suta/ghost-to-md.git\ncd ghost-to-md\ngo build ./cmd/ghost-to-md\n```\n\n## Usage\n\n```\nUsage: ghost-to-md [options] \u003cghost-export.json\u003e\n\nOptions:\n  -output string      Output directory path (default \"./output\")\n  -status string      Filter by status (published/draft/scheduled)\n  -include-pages      Include pages in output (default true)\n  -internal-tags      Include internal (#) tags in front matter\n  -url string         Replace __GHOST_URL__ with this base URL\n  -verbose            Enable verbose logging\n  -version            Show version\n```\n\n### Examples\n\n```bash\n# Basic conversion\nghost-to-md export.json\n\n# Published posts only, with URL rewriting\nghost-to-md -status published -url https://example.com export.json\n\n# Output to custom directory, include internal tags\nghost-to-md -output ./content/posts -internal-tags export.json\n\n# Drafts only, verbose logging\nghost-to-md -status draft -verbose export.json\n```\n\n## Output Format\n\nEach post generates a Markdown file named `YYYY-MM-DD-slug.md`:\n\n```markdown\n---\ntitle: \"My Blog Post: A Guide\"\nslug: my-blog-post-a-guide\ndate: 2026-01-15T09:00:00Z\nlastmod: 2026-02-20T14:30:00Z\ntags:\n  - Go\n  - Tutorial\nauthors:\n  - John Doe\ndescription: \"A comprehensive guide to building CLI tools\"\nfeatured_image: https://example.com/images/cover.jpg\n---\n\n## Introduction\n\nThis is the **converted** Markdown content...\n```\n\n## Project Structure\n\n```\ncmd/ghost-to-md/\n  main.go                     # Entry point, CLI flags, manual DI\ninternal/\n  domain/\n    model.go                  # Post, Tag, Author, Article, RawExport\n    resolver.go               # ResolveRelationships (joins flat tables)\n    frontmatter.go            # YAML front matter generation\n    naming.go                 # Filename from slug and date\n  port/\n    reader.go                 # ExportReader interface\n    converter.go              # ContentConverter interface\n    writer.go                 # ArticleWriter interface\n  adapter/\n    ghostjson/\n      schema.go               # JSON struct definitions, intBool type\n      reader.go               # Ghost JSON deserializer\n    htmlconv/\n      converter.go            # HTML-to-Markdown converter\n      handlers.go             # Per-element handlers (headings, lists, etc.)\n      ghost_cards.go          # Ghost kg-* card handlers (10 types)\n    fswriter/\n      writer.go               # Writes .md files to disk\n  usecase/\n    migrate.go                # MigrateUseCase orchestrator\n    options.go                # MigrateOptions, ProgressReporter\ntestdata/\n  fixtures/minimal.json       # Test fixture for Ghost JSON reader\n  golden/                     # Golden files for HTML-to-Markdown tests\n```\n\n## Design Decisions\n\n| Decision | Rationale |\n|----------|-----------|\n| **Single external dependency** (`golang.org/x/net/html`) | Minimizes supply chain risk; stdlib handles JSON, flags, filesystem, logging |\n| **Hand-written YAML front matter** | Avoids pulling in a YAML library for write-only serialization; `escapeYAML` handles special characters |\n| **`intBool` custom type** | Ghost JSON inconsistently encodes booleans as `true`/`false` or `0`/`1`; `intBool` unmarshals both via custom `UnmarshalJSON` |\n| **Single-method interfaces** | `ExportReader`, `ContentConverter`, `ArticleWriter` each have one method, following Go's `io.Reader` convention |\n| **Relationship resolution in domain** | `ResolveRelationships` joins flat Ghost tables (`posts_tags`, `posts_authors`, `posts_meta`) into structured `Post` objects, keeping JSON parsing separate from domain logic |\n| **Golden file tests** | HTML-to-Markdown conversion tested against committed golden files; update with `UPDATE_GOLDEN=1 go test ./...` |\n\n## Testing\n\n```bash\ngo test ./...          # Run all tests\ngo vet ./...           # Static analysis\nmake test              # Same as go test ./...\n```\n\nTesting approach:\n- **Table-driven tests** - Standard Go idiom for parameterized test cases\n- **Golden file tests** - Expected Markdown output committed to `testdata/golden/`; regenerate with `UPDATE_GOLDEN=1`\n- **`testing/fstest`** - In-memory filesystem for writer tests\n- **No external test dependencies** - All tests use stdlib `testing` package\n\n## License\n\n[MIT](LICENSE)\n\n---\n\n*This project includes a detailed [technical design document](ghost-to-md%20%E6%8A%80%E8%A1%93%E8%A8%AD%E8%A8%88%E6%9B%B8.md) written in Japanese.*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fp4suta%2Fghost-to-md","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fp4suta%2Fghost-to-md","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fp4suta%2Fghost-to-md/lists"}