{"id":36927104,"url":"https://github.com/matzehuels/stacktower","last_synced_at":"2026-04-03T04:02:59.366Z","repository":{"id":327522850,"uuid":"1039749901","full_name":"matzehuels/stacktower","owner":"matzehuels","description":"Visualize package dependencies as XKCD-style tower diagrams. Supports Python, Rust, JavaScript, Ruby, PHP, Java, and Go.","archived":false,"fork":false,"pushed_at":"2025-12-19T04:11:17.000Z","size":1636,"stargazers_count":403,"open_issues_count":6,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-12-19T12:46:43.075Z","etag":null,"topics":["dependencies","golang","graphs"],"latest_commit_sha":null,"homepage":"https://stacktower.io/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/matzehuels.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":"2025-08-17T22:50:34.000Z","updated_at":"2025-12-19T12:45:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/matzehuels/stacktower","commit_stats":null,"previous_names":["matzehuels/stacktower"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/matzehuels/stacktower","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matzehuels%2Fstacktower","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matzehuels%2Fstacktower/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matzehuels%2Fstacktower/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matzehuels%2Fstacktower/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/matzehuels","download_url":"https://codeload.github.com/matzehuels/stacktower/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matzehuels%2Fstacktower/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28369835,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T00:51:29.220Z","status":"online","status_checked_at":"2026-01-13T02:00:06.734Z","response_time":56,"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":["dependencies","golang","graphs"],"created_at":"2026-01-13T00:00:52.470Z","updated_at":"2026-04-03T04:02:59.355Z","avatar_url":"https://github.com/matzehuels.png","language":"Go","readme":"# Stacktower\n\n[![CI](https://github.com/matzehuels/stacktower/actions/workflows/ci.yml/badge.svg)](https://github.com/matzehuels/stacktower/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/matzehuels/stacktower/graph/badge.svg)](https://codecov.io/gh/matzehuels/stacktower)\n[![Go Report Card](https://goreportcard.com/badge/github.com/matzehuels/stacktower)](https://goreportcard.com/report/github.com/matzehuels/stacktower)\n[![Go Reference](https://pkg.go.dev/badge/github.com/matzehuels/stacktower.svg)](https://pkg.go.dev/github.com/matzehuels/stacktower)\n[![Release](https://img.shields.io/github/v/release/matzehuels/stacktower)](https://github.com/matzehuels/stacktower/releases)\n[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\nInspired by [XKCD #2347](https://xkcd.com/2347/), Stacktower renders dependency graphs as **physical towers** where blocks rest on what they depend on. Your application sits at the top, supported by libraries below—all the way down to that one critical package maintained by _some dude in Nebraska_.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"blogpost/plots/showcase/python/fastapi.svg\" alt=\"FastAPI dependency tower\" width=\"600\"\u003e\n\u003c/p\u003e\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://app.stacktower.io\"\u003e\u003cstrong\u003e🚀 Try the Web App\u003c/strong\u003e\u003c/a\u003e · \u003ca href=\"https://www.stacktower.io\"\u003e\u003cstrong\u003e📖 Read the Story\u003c/strong\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n\u003e ⚠️ **Note:** The ordering algorithms are still experimental and may not produce nicely stacked towers for projects with a large number of dependencies. Results can vary. We're actively working on improvements.\n\n## Installation\n\n### Homebrew (macOS/Linux)\n\n```bash\nbrew install matzehuels/tap/stacktower\n```\n\n### Go\n\n```bash\ngo install github.com/matzehuels/stacktower@latest\n```\n\n### From Source\n\n```bash\ngit clone https://github.com/matzehuels/stacktower.git\ncd stacktower\ngo build -o bin/stacktower ./cmd/stacktower\n```\n\n## Quick Start\n\n```bash\n# Render the included Flask example (XKCD-style tower is the default)\nstacktower render examples/real/flask.json -o flask.svg\n```\n\n## Usage\n\nStacktower works in two stages: **parse** dependency data from package registries or manifest files, then **render** visualizations.\n\n### Parsing Dependencies\n\nThe `parse` command auto-detects whether you're providing a package name or a manifest file:\n\n```bash\nstacktower parse \u003clanguage\u003e \u003cpackage-or-file\u003e [flags]\n```\n\n**Supported languages:** `python`, `rust`, `javascript`, `ruby`, `php`, `java`, `go`\n\n#### From Package Registries\n\n```bash\nstacktower parse python fastapi -o fastapi.json                  # PyPI\nstacktower parse rust serde -o serde.json                        # crates.io\nstacktower parse javascript yargs -o yargs.json                  # npm\nstacktower parse ruby rails -o rails.json                        # RubyGems\nstacktower parse php monolog/monolog -o monolog.json             # Packagist\nstacktower parse java com.google.guava:guava -o guava.json       # Maven Central\nstacktower parse go github.com/gin-gonic/gin -o gin.json         # Go Module Proxy\n```\n\n#### From Manifest Files (Auto-Detected)\n\n```bash\nstacktower parse python examples/manifest/poetry.lock -o deps.json\nstacktower parse python examples/manifest/requirements.txt -o deps.json\nstacktower parse rust examples/manifest/Cargo.toml -o deps.json\nstacktower parse javascript examples/manifest/package.json -o deps.json\nstacktower parse ruby examples/manifest/Gemfile -o deps.json\nstacktower parse php examples/manifest/composer.json -o deps.json\nstacktower parse java examples/manifest/pom.xml -o deps.json\nstacktower parse go examples/manifest/go.mod -o deps.json\n```\n\nWhen the argument exists on disk or matches a known manifest filename, Stacktower automatically parses it as a manifest.\n\nThe project name (root node) is auto-detected from the manifest or a sibling file:\n\n- **Cargo.toml**: `[package].name`\n- **go.mod**: `module` directive\n- **package.json**: `name` field\n- **composer.json**: `name` field\n- **pom.xml**: `groupId:artifactId`\n- **poetry.lock / requirements.txt**: `pyproject.toml` (sibling)\n- **Gemfile**: `*.gemspec` (sibling)\n\nUse `--name` to override the auto-detected name:\n\n```bash\nstacktower parse python requirements.txt --name=\"my-project\" -o deps.json\nstacktower parse ruby Gemfile -n my-rails-app -o deps.json\n```\n\n#### Metadata Enrichment\n\nBy default, Stacktower enriches packages with GitHub metadata (stars, maintainers, last commit) for richer visualizations. Set `GITHUB_TOKEN` to enable this:\n\n```bash\nexport GITHUB_TOKEN=your_token\nstacktower parse python fastapi -o fastapi.json\n\n# Skip enrichment if you don't have a token\nstacktower parse python fastapi --skip-enrich -o fastapi.json\n```\n\n### Rendering\n\nThe `render` command generates visualizations from parsed JSON graphs:\n\n```bash\nstacktower render \u003cfile\u003e [flags]\n```\n\nThis is a shortcut that combines `layout` and `visualize` in one step. For more control, you can run them separately:\n\n```bash\n# Two-step workflow with intermediate layout\nstacktower layout examples/real/flask.json -o flask.layout.json\nstacktower visualize flask.layout.json -o flask.svg\n```\n\n#### Visualization Types\n\n```bash\n# Hand-drawn XKCD-style tower (default)\nstacktower render examples/real/flask.json -o flask.svg\n\n# Disable hand-drawn effects for a cleaner look\nstacktower render examples/real/serde.json --style simple --randomize=false --popups=false -o serde.svg\n\n# Traditional node-link diagram (uses Graphviz DOT)\nstacktower render examples/real/yargs.json -t nodelink -o yargs.svg\n```\n\n#### Output Formats\n\n```bash\n# SVG output (default)\nstacktower render examples/real/flask.json -o flask.svg\n\n# JSON layout export (for external tools or re-rendering)\nstacktower render examples/real/flask.json -f json -o flask.json\n\n# PDF output\nstacktower render examples/real/flask.json -f pdf -o flask.pdf\n\n# PNG output (2x scale by default)\nstacktower render examples/real/flask.json -f png -o flask.png\n\n# Multiple formats at once (outputs flask.svg, flask.json, flask.pdf)\nstacktower render examples/real/flask.json -f svg,json,pdf -o flask\n```\n\nOutput path behavior:\n\n- **No `-o`**: Derives from input (`input.json` → `input.\u003cformat\u003e`)\n- **Single format**: Uses exact path (`-o out.svg` → `out.svg`)\n- **Multiple formats**: Strips extension, adds format (`-o out -f svg,json` → `out.svg`, `out.json`)\n\n\u003e **Note:** PDF and PNG output requires [librsvg](https://wiki.gnome.org/Projects/LibRsvg):\n\u003e\n\u003e - macOS: `brew install librsvg`\n\u003e - Linux: `apt install librsvg2-bin`\n\n### Included Examples\n\nThe repository ships with pre-parsed graphs so you can experiment immediately:\n\n```bash\n# Real packages with full metadata (XKCD-style by default)\nstacktower render examples/real/flask.json -o flask.svg\nstacktower render examples/real/serde.json -o serde.svg\nstacktower render examples/real/yargs.json -o yargs.svg\n\n# With Nebraska guy ranking\nstacktower render examples/real/flask.json --nebraska -o flask.svg\n\n# Synthetic test cases\nstacktower render examples/test/diamond.json -o diamond.svg\n```\n\n## Options Reference\n\n### Global Options\n\n| Flag              | Description                                              |\n| ----------------- | -------------------------------------------------------- |\n| `-v`, `--verbose` | Enable debug logging (search space info, timing details) |\n\n### Parse Options\n\n| Flag             | Description                                                                |\n| ---------------- | -------------------------------------------------------------------------- |\n| `-o`, `--output` | Output file (stdout if empty)                                              |\n| `-n`, `--name`   | Project name for manifest parsing (auto-detected from manifest if not set) |\n| `--max-depth N`  | Maximum dependency depth (default: 10)                                     |\n| `--max-nodes N`  | Maximum packages to fetch (default: 5000)                                  |\n| `--skip-enrich`  | Skip metadata enrichment (GitHub descriptions, etc.)                       |\n| `--no-cache`     | Disable caching                                                            |\n\n### Render Options\n\n| Flag             | Description                                                                                                           |\n| ---------------- | --------------------------------------------------------------------------------------------------------------------- |\n| `-o`, `--output` | Output file or base path for multiple formats                                                                         |\n| `-t`, `--type`   | Visualization type: `tower` (default), `nodelink`                                                                     |\n| `-f`, `--format` | Output format(s): `svg` (default), `json`, `pdf`, `png` (comma-separated)                                             |\n| `--normalize`    | Apply graph normalization: break cycles, remove transitive edges, assign layers, subdivide long edges (default: true) |\n\n#### Tower Options\n\n| Flag                              | Description                                                           |\n| --------------------------------- | --------------------------------------------------------------------- |\n| `--width N`                       | Frame width in pixels (default: 800)                                  |\n| `--height N`                      | Frame height in pixels (default: 600)                                 |\n| `--style handdrawn\\|simple`       | Visual style (default: handdrawn)                                     |\n| `--randomize`                     | Vary block widths to visualize load-bearing structure (default: true) |\n| `--merge`                         | Merge subdivider blocks into continuous towers (default: true)        |\n| `--popups`                        | Enable hover popups with package metadata (default: true)             |\n| `--nebraska`                      | Show \"Nebraska guy\" maintainer ranking panel                          |\n| `--edges`                         | Show dependency edges as dashed lines                                 |\n| `--ordering optimal\\|barycentric` | Crossing minimization algorithm (default: optimal)                    |\n| `--ordering-timeout N`            | Timeout for optimal search in seconds (default: 60)                   |\n| `--no-cache`                      | Disable caching                                                       |\n\n## JSON Format\n\nThe render layer accepts a simple JSON format, making it easy to visualize **any** directed graph—not just package dependencies. You can hand-craft graphs for component diagrams, callgraphs, or pipe output from other tools.\n\n### Minimal Example\n\n```json\n{\n  \"nodes\": [{ \"id\": \"app\" }, { \"id\": \"lib-a\" }, { \"id\": \"lib-b\" }],\n  \"edges\": [\n    { \"from\": \"app\", \"to\": \"lib-a\" },\n    { \"from\": \"lib-a\", \"to\": \"lib-b\" }\n  ]\n}\n```\n\n### Required Fields\n\n| Field          | Type   | Description                                 |\n| -------------- | ------ | ------------------------------------------- |\n| `nodes[].id`   | string | Unique node identifier (displayed as label) |\n| `edges[].from` | string | Source node ID                              |\n| `edges[].to`   | string | Target node ID                              |\n\n### Optional Fields\n\n| Field          | Type   | Description                                            |\n| -------------- | ------ | ------------------------------------------------------ |\n| `nodes[].row`  | int    | Pre-assigned layer (computed automatically if omitted) |\n| `nodes[].kind` | string | Internal use: `\"subdivider\"` or `\"auxiliary\"`          |\n| `nodes[].meta` | object | Freeform metadata for display features                 |\n\n### Recognized `meta` Keys\n\nThese keys are read by specific render flags. All are optional—missing keys simply disable the corresponding feature.\n\n| Key                 | Type          | Used By                                    |\n| ------------------- | ------------- | ------------------------------------------ |\n| `repo_url`          | string        | Clickable blocks, `--popups`, `--nebraska` |\n| `repo_stars`        | int           | `--popups`                                 |\n| `repo_owner`        | string        | `--nebraska`                               |\n| `repo_maintainers`  | []string      | `--nebraska`                               |\n| `repo_last_commit`  | string (date) | `--popups`, brittle detection              |\n| `repo_last_release` | string (date) | `--popups`                                 |\n| `repo_archived`     | bool          | `--popups`, brittle detection              |\n| `summary`           | string        | `--popups` (fallback: `description`)       |\n\nThe `--detailed` flag (node-link only) displays **all** meta keys in the node label.\n\n## How It Works\n\n1. **Parse** — Fetch package metadata from registries or local manifest files\n2. **Reduce** — Remove transitive edges to show only direct dependencies\n3. **Layer** — Assign each package to a row based on its depth\n4. **Order** — Minimize edge crossings using branch-and-bound with PQ-tree pruning\n5. **Layout** — Compute block widths proportional to downstream dependents\n6. **Render** — Generate output in SVG, JSON, PDF, or PNG format\n\nThe ordering step is where the magic happens. Stacktower uses an optimal search algorithm that guarantees minimum crossings for small-to-medium graphs. For larger graphs, it gracefully falls back after a configurable timeout.\n\n## GitHub Authentication\n\nFor parsing repositories directly from GitHub, you can authenticate using the device flow:\n\n```bash\n# Login with GitHub (opens browser for device authorization)\nstacktower github login\n\n# Check current session\nstacktower github whoami\n\n# Logout\nstacktower github logout\n\n# Parse a manifest from a GitHub repository\nstacktower parse github owner/repo -o deps.json\n```\n\n## Environment Variables\n\n| Variable       | Description                                      |\n| -------------- | ------------------------------------------------ |\n| `GITHUB_TOKEN` | GitHub API token for metadata enrichment         |\n| `GITLAB_TOKEN` | GitLab API token for metadata enrichment         |\n\n## Caching\n\nHTTP responses are cached in `~/.cache/stacktower/` with a 24-hour TTL. Use `--no-cache` to disable caching for a single request.\n\n```bash\n# Clear the entire cache\nstacktower cache clear\n\n# Show cache directory path\nstacktower cache path\n```\n\n## Using as a Library\n\nStacktower can be used as a Go library for programmatic graph visualization.\n\n```go\nimport (\n    \"github.com/matzehuels/stacktower/pkg/core/dag\"\n    \"github.com/matzehuels/stacktower/pkg/core/dag/transform\"\n    \"github.com/matzehuels/stacktower/pkg/core/render/tower/layout\"\n    \"github.com/matzehuels/stacktower/pkg/core/render/tower/sink\"\n)\n\n// Build a graph\ng := dag.New(nil)\ng.AddNode(dag.Node{ID: \"app\", Row: 0})\ng.AddNode(dag.Node{ID: \"lib\", Row: 1})\ng.AddEdge(dag.Edge{From: \"app\", To: \"lib\"})\n\n// Normalize and render\ntransform.Normalize(g)\nl := layout.Build(g, 800, 600)\nsvg := sink.RenderSVG(l, sink.WithGraph(g), sink.WithPopups())\n```\n\n📚 **[Full API documentation on pkg.go.dev](https://pkg.go.dev/github.com/matzehuels/stacktower)**\n\nKey packages:\n\n- [`pkg/core/dag`](https://pkg.go.dev/github.com/matzehuels/stacktower/pkg/core/dag) — DAG data structure and crossing algorithms\n- [`pkg/core/dag/transform`](https://pkg.go.dev/github.com/matzehuels/stacktower/pkg/core/dag/transform) — Graph normalization pipeline\n- [`pkg/core/render/tower`](https://pkg.go.dev/github.com/matzehuels/stacktower/pkg/core/render/tower) — Layout, ordering, and rendering\n- [`pkg/core/deps`](https://pkg.go.dev/github.com/matzehuels/stacktower/pkg/core/deps) — Dependency resolution from registries\n- [`pkg/pipeline`](https://pkg.go.dev/github.com/matzehuels/stacktower/pkg/pipeline) — Complete parse → layout → render pipeline\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on adding new languages, manifest parsers, or output formats.\n\n## Development\n\n```bash\nmake install-tools  # Install required tools (golangci-lint, goimports, govulncheck)\nmake check          # Run all CI checks locally (fmt, lint, test, vuln)\nmake build          # Build binary to bin/stacktower\n```\n\n| Command         | Description                                |\n| --------------- | ------------------------------------------ |\n| `make check`    | Format, lint, test, vulncheck (same as CI) |\n| `make fmt`      | Format code with gofmt and goimports       |\n| `make lint`     | Run golangci-lint                          |\n| `make test`     | Run tests with race detector               |\n| `make cover`    | Run tests with coverage report             |\n| `make vuln`     | Check for known vulnerabilities            |\n| `make e2e`      | Run end-to-end tests                       |\n| `make snapshot` | Build release locally (no publish)         |\n\nCommit messages follow [Conventional Commits](https://www.conventionalcommits.org/).\n\n## Learn More\n\n- 📖 **[stacktower.io](https://www.stacktower.io)** — Interactive examples and the full story behind tower visualizations\n- 🐛 **[Issues](https://github.com/matzehuels/stacktower/issues)** — Bug reports and feature requests\n\n## License\n\nApache-2.0\n","funding_links":[],"categories":["Utilities","公用事业公司"],"sub_categories":["Utility/Miscellaneous","实用程序/Miscellaneous"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatzehuels%2Fstacktower","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatzehuels%2Fstacktower","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatzehuels%2Fstacktower/lists"}