Skip to content
Merged
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
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,56 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [1.3.0] - 2026-06-16

### Added
- **Go plugin** — third language, generates production-ready Go backend:
- Router: **chi v5** (thin net/http wrapper, no framework lock-in)
- ORM: **GORM v2** + PostgreSQL via pgx driver
- Migration: **golang-migrate** (up/down SQL files, consistent with Alembic/Prisma)
- Config: **godotenv** + `internal/config` struct
- Dev: **air** hot reload + **Makefile** with `dev`, `build`, `migrate-up/down`, `test`, `lint`
- Logger: **slog** (stdlib, no dependency)
- **`--jwt` addon (Go)** — JWT auth overlay: `AuthService`, `Claims`, bcrypt password hashing, `middleware.Auth()` chi middleware, user CRUD handlers, GORM soft-delete model, migration `000002_create_users`
- **`--module-path` flag** — Go module path (e.g. `github.com/username/my-app`); prompted interactively for Go projects, defaults to `github.com/example/<name>` in non-TTY
- **Docker addon (Go)** — multi-stage `Dockerfile` (golang:1.22-alpine → alpine:3.20), `docker-compose.yml` with PostgreSQL healthcheck
- **CI addon (Go)** — GitHub Actions workflow with PostgreSQL service, `go vet`, `go test -race`
- **Testing addon (Go)** — `tests/health_test.go` using `net/http/httptest`, no external deps
- **`archgen add jwt`** — inject JWT addon into existing Go projects; reads module path from `go.mod` for correct placeholder replacement
- Go language choice added to interactive prompts; `database` prompt skipped for Go (always PostgreSQL)
- Go project detection in `detectProjectLanguage()` via `go.mod` presence

### Fixed
- `--module-path` flag correctly maps to `options.modulePath` (previously `--module` did not bind due to Commander camelCase convention)

## [1.2.0] - 2026-06-16

### Added
- **`--observability` addon (Node.js + Python)** — production-grade observability stack:
- Node.js: `src/telemetry.ts` (OpenTelemetry SDK auto-init), `src/plugins/metrics.plugin.ts` (Prometheus `/metrics` endpoint via `prom-client`), `src/config/sentry.ts` (Sentry stub, gated by `SENTRY_DSN`)
- Python: `app/core/observability.py` (`instrument_app(app)` — OTel + FastAPI instrumentation + Prometheus via `prometheus-fastapi-instrumentator`), `app/core/sentry.py` (Sentry stub)
- Both: `observability/prometheus.yml`, `observability/docker-compose.observability.yml` (Prometheus + Grafana + OTel Collector), `observability/otel-collector.yml`, `observability/grafana-dashboard.json` (request rate, p95 latency, error rate, active handles panels)
- Does **not** overwrite `app.ts`/`main.py` — overlay is additive, safe to combine with oauth/api-docs
- Deps injected: `@opentelemetry/sdk-node`, `@opentelemetry/auto-instrumentations-node`, `@opentelemetry/exporter-trace-otlp-http`, `prom-client` (Node); `opentelemetry-sdk`, `opentelemetry-instrumentation-fastapi`, `opentelemetry-exporter-otlp-proto-http`, `prometheus-fastapi-instrumentator`, `structlog` (Python)
- **`--sentry` sub-flag** — pair with `--observability` to inject `@sentry/node` / `sentry-sdk[fastapi]` into the project manifest; also supported via `archgen add observability --sentry`
- **`--pre-commit` addon (Python only)** — git hook suite matching Node.js `--husky`:
- `.pre-commit-config.yaml` with `ruff` (lint + format), `ruff-format`, `black`, `mypy`, trailing-whitespace, end-of-file-fixer
- Injects `pre-commit>=3.7.0` into `[project.optional-dependencies] dev`
- Next steps printed: `pre-commit install`
- **`archgen add observability --sentry`** — `AddAddonOptions` now accepts `sentry?: boolean` so Sentry dep injection works for post-scaffold addon injection too
- **Improved test quality in generated projects**:
- Node.js testing addon: fixed import path bug (`../../../base/src/app` → `../src/app`); added `tests/users.test.ts` (401/200/404 flow with auth); `jest.config.js` overlay with `coverageThreshold: 80%`
- Python testing addon: `conftest.py` now overrides `get_db` dependency → SQLite in-memory isolation; added `tests/test_auth.py` (register/login/401 flows) and `tests/test_users.py` (CRUD + auth guard); `pytest.ini` overlay with `--cov-fail-under=80`; `aiosqlite>=0.20.0` injected into dev deps automatically

### Fixed
- **Python testing addon** — `aiosqlite` (required by `sqlite+aiosqlite:///:memory:` in conftest) now injected into `[project.optional-dependencies] dev` when `--testing` is enabled
- **Python dep injection** — new `_injectPyprojectDeps(deps[])` batch helper replaces single-dep injection; new `_injectPyprojectDevDeps(deps[])` targets `[project.optional-dependencies] dev` array for dev-only packages
- **`archgen add` argument description** — removed stale `(docker|testing|ci|husky)` hint; now points to `archgen list`

### Tests
- **New unit test file** `observability-addon.test.ts` — 18 tests covering Node and Python observability wiring, pre-commit overlay, sentry flag, aiosqlite injection, dry-run guards, and no-app.ts-overwrite assertion
- Total: **160 unit tests** (up from 142)

## [1.1.0] - 2026-06-02

### Added
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# archgen

> A CLI tool that generates production-ready backend projects in seconds — so you can focus on building, not configuring.
> A CLI tool that generates production-ready backend projects in seconds — Node.js, Python, or Go — so you can focus on building, not configuring.

---

Expand Down Expand Up @@ -32,6 +32,9 @@ Answer a few prompts. Your project is ready in under a second.
- Optional S3 storage (AWS S3 / R2 / MinIO) — upload, presigned URLs, delete, exists
- Optional Claude Code setup — `CLAUDE.md` + pre-configured skills for Claude Code agent
- Optional Cursor setup — `.cursor/skills/` with pre-configured skills for Cursor agent
- Optional observability stack — OpenTelemetry traces + Prometheus `/metrics` + Grafana dashboard + OTel Collector (both Node.js and Python)
- Optional Sentry error tracking — injected alongside `--observability`
- Optional pre-commit hooks (Python) — ruff + black + mypy via `.pre-commit-config.yaml`
- Auto update notifier — hints when a new version is available
- Interactive CLI prompts — no flags required
- Post-scaffold addon injection with `archgen add`
Expand All @@ -45,6 +48,7 @@ Answer a few prompts. Your project is ready in under a second.
|----------|-------|
| Node.js | TypeScript · Fastify · Prisma · MariaDB/MySQL · Redis · JWT · Zod · Pino · Swagger |
| Python | FastAPI · SQLAlchemy 2.0 · Alembic · PostgreSQL · Redis · Pydantic v2 · APScheduler |
| Go | chi v5 · GORM v2 · PostgreSQL · golang-migrate · godotenv · slog · air |

---

Expand All @@ -68,6 +72,11 @@ archgen create my-app --cursor # add Cursor agent setup (
archgen create my-app --email # add nodemailer SMTP support
archgen create my-app --s3 # add AWS S3 / R2 / MinIO support
archgen create my-app --claude-code --cursor # add both AI agent setups
archgen create my-app --observability # add OTel + Prometheus + Grafana
archgen create my-app --observability --sentry # add observability + Sentry
archgen create my-app --pre-commit # add pre-commit hooks (Python only)
archgen create my-api --language go --module-path github.com/user/my-api # Go project
archgen create my-api --language go --jwt --docker # Go with JWT auth + Docker
archgen create my-app --force # overwrite existing directory
archgen create my-app --dry-run # preview files without writing
archgen create my-app --skip-git # skip automatic git init
Expand All @@ -87,6 +96,10 @@ archgen add claude-code # Claude Code setup (CLAUDE.md + .claude/skills/)
archgen add cursor # Cursor agent setup (.cursor/skills/)
archgen add email # nodemailer SMTP email support
archgen add s3 # AWS S3 / R2 / MinIO storage support
archgen add observability # OTel + Prometheus + Grafana
archgen add observability --sentry # with Sentry error tracking
archgen add pre-commit # pre-commit hooks (Python only: ruff + black + mypy)
archgen add jwt # JWT auth addon (Go only)
archgen add ci --dry-run # preview changes without writing
```

Expand Down Expand Up @@ -123,6 +136,11 @@ archgen doctor # check that required tools are installed
| `--api-docs` | Include Scalar + Swagger API docs | `false` |
| `--email` | Include nodemailer SMTP email support | `false` |
| `--s3` | Include AWS S3 / R2 / MinIO storage | `false` |
| `--observability` | Include OTel + Prometheus + Grafana stack | `false` |
| `--sentry` | Include Sentry (pair with `--observability`) | `false` |
| `--pre-commit` | Include pre-commit hooks (Python only) | `false` |
| `--jwt` | Include JWT auth routes + middleware (Go only) | `false` |
| `--module-path` | Go module path (e.g. `github.com/user/app`) | prompt |
| `-a, --author` | Author name | `Your Name` |
| `-d, --description` | Project description | — |
| `--force` | Overwrite existing directory | `false` |
Expand Down
7 changes: 4 additions & 3 deletions cli/command/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { logger } from "../../core/logger";
import { registry } from "../../core/registry";

export const addCommand = new Command("add")
.argument("<addon>", "Addon to inject (docker|testing|ci|husky)")
.argument("<addon>", "Addon to inject (run 'archgen list' to see available addons)")
.option("--dry-run", "Preview files without writing", false)
.option("--sentry", "Include Sentry dep when adding observability addon", false)
.description("Add an addon to an existing project in the current directory")
.action(async (addon: string, options: { dryRun: boolean }) => {
.action(async (addon: string, options: { dryRun: boolean; sentry: boolean }) => {
const cwd = process.cwd();
const language = detectProjectLanguage(cwd);

Expand Down Expand Up @@ -37,7 +38,7 @@ export const addCommand = new Command("add")

const archgen = new ArchGen();
try {
await archgen.addAddon(cwd, language, addon, { dryRun: options.dryRun });
await archgen.addAddon(cwd, language, addon, { dryRun: options.dryRun, sentry: options.sentry });
} catch (error) {
if (error instanceof ArchGenError) {
logger.error(error.message);
Expand Down
11 changes: 10 additions & 1 deletion cli/command/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { findPresetFile, loadPreset, mergePreset } from "../../core/config-prese

const VALID_NODE_DATABASES = ["mysql", "postgresql"];
const VALID_PYTHON_DATABASES = ["postgresql", "sqlite"];
const VALID_GO_DATABASES = ["postgresql"];

export const createCommand = new Command("create")
.argument("<project-name>", "Name of the project")
Expand All @@ -23,6 +24,11 @@ export const createCommand = new Command("create")
.option("--email", "Include email service (SMTP)")
.option("--s3", "Include AWS S3 / Cloudflare R2 / MinIO storage service")
.option("--queue", "Include background job queue (BullMQ for Node, arq for Python)")
.option("--pre-commit", "Include pre-commit hooks (Python only: ruff + black + mypy)")
.option("--observability", "Include observability stack (OpenTelemetry + Prometheus + Sentry stub)")
.option("--sentry", "Include Sentry error tracking dependency (requires --observability)")
.option("--jwt", "Include JWT authentication (Go only)")
.option("--module-path <path>", "Go module path (e.g. github.com/username/my-app)")
.option("--all", "Include all addons (docker + testing + ci)", false)
.option("-a, --author <n>", "Author name")
.option("-d, --description <desc>", "Project description")
Expand Down Expand Up @@ -52,7 +58,10 @@ export const createCommand = new Command("create")

if (finalOptions.database) {
const lang = finalOptions.language;
const valid = lang === "python" ? VALID_PYTHON_DATABASES : VALID_NODE_DATABASES;
const valid =
lang === "python" ? VALID_PYTHON_DATABASES :
lang === "go" ? VALID_GO_DATABASES :
VALID_NODE_DATABASES;
if (!valid.includes(finalOptions.database)) {
logger.error(
`Invalid database "${finalOptions.database}" for ${lang}. Must be one of: ${valid.join(", ")}`,
Expand Down
38 changes: 34 additions & 4 deletions cli/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export async function promptMissingOptions(
husky: false,
claudeCode: false,
cursor: false,
preCommit: false,
observability: false,
sentry: false,
jwt: false,
...options,
language: options.language ?? "node",
};
Expand All @@ -30,6 +34,7 @@ export async function promptMissingOptions(
choices: [
{ title: "Node.js (TypeScript + Fastify)", value: "node" },
{ title: "Python (FastAPI)", value: "python" },
{ title: "Go (chi + GORM + PostgreSQL)", value: "go" },
],
});
}
Expand Down Expand Up @@ -59,6 +64,19 @@ export async function promptMissingOptions(
});
}

if (!options.modulePath) {
const fixedLang = options.language;
questions.push({
type: (_prev, values) => {
const lang = fixedLang ?? (values as GenerateOptions).language;
return lang === "go" ? "text" : null;
},
name: "modulePath",
message: "Go module path:",
initial: `github.com/example/${_projectName}`,
});
}

if (!options.docker) {
questions.push({
type: "confirm",
Expand Down Expand Up @@ -112,30 +130,39 @@ export async function promptMissingOptions(
});
}

const extraAddons = ["websocket", "oauth", "apiDocs", "email", "s3", "queue"] as const;
const anyExtraSet = extraAddons.some((k) => options[k] !== undefined);
const extraAddons = ["websocket", "oauth", "apiDocs", "email", "s3", "queue", "observability", "preCommit", "jwt"] as const;
const anyExtraSet = extraAddons.some((k) => options[k as keyof GenerateOptions] !== undefined);
if (!anyExtraSet) {
const fixedLang = options.language;
questions.push({
type: (_prev, values) => {
const lang = fixedLang ?? (values as GenerateOptions).language;
return lang === "node" || lang === "python" ? "multiselect" : null;
return lang === "node" || lang === "python" || lang === "go" ? "multiselect" : null;
},
name: "extraAddons",
message: "Include extra addons? (Space to select, Enter to confirm)",
choices: (_prev, values) => {
const lang = fixedLang ?? (values as GenerateOptions).language;
if (lang === "go") {
return [
{ title: "JWT authentication", value: "jwt", selected: false },
];
}
const nodeOnly = lang === "node";
const pythonOnly = lang === "python";
const all = [
{ title: "WebSocket", value: "websocket", selected: false },
{ title: "OAuth2 (Google + GitHub)", value: "oauth", selected: false },
{ title: "API Docs", value: "apiDocs", selected: false },
{ title: "Email (SMTP)", value: "email", selected: false },
{ title: "Storage (S3 / R2 / MinIO)", value: "s3", selected: false },
{ title: "Queue (BullMQ / arq)", value: "queue", selected: false },
{ title: "Observability (OTel + Prometheus)", value: "observability", selected: false },
{ title: "Husky + lint-staged", value: "husky", selected: false, disabled: !nodeOnly },
{ title: "pre-commit hooks (ruff + black + mypy)", value: "preCommit", selected: false, disabled: !pythonOnly },
];
return nodeOnly ? all : all.filter((c) => c.value !== "husky");
if (nodeOnly) return all.filter((c) => c.value !== "preCommit");
return all.filter((c) => c.value !== "husky");
},
hint: "none to skip",
});
Expand Down Expand Up @@ -174,6 +201,9 @@ export async function promptMissingOptions(
raw.s3 = selected.includes("s3");
raw.queue = selected.includes("queue");
if (selected.includes("husky")) raw.husky = true;
if (selected.includes("observability")) raw.observability = true;
if (selected.includes("preCommit")) raw.preCommit = true;
if (selected.includes("jwt")) raw.jwt = true;
delete raw.extraAddons;
}
return raw as unknown as GenerateOptions;
Expand Down
7 changes: 6 additions & 1 deletion core/detector.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import fs from "fs-extra";
import path from "path";

export type DetectedLanguage = "node" | "python" | null;
export type DetectedLanguage = "node" | "python" | "go" | null;

export function detectProjectLanguage(cwd: string): DetectedLanguage {
const pkgPath = path.join(cwd, "package.json");
const pyprojectPath = path.join(cwd, "pyproject.toml");
const mainPyPath = path.join(cwd, "main.py");
const goModPath = path.join(cwd, "go.mod");

if (fs.existsSync(pkgPath)) {
try {
Expand All @@ -25,5 +26,9 @@ export function detectProjectLanguage(cwd: string): DetectedLanguage {
return "python";
}

if (fs.existsSync(goModPath)) {
return "go";
}

return null;
}
4 changes: 3 additions & 1 deletion core/registry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NodePlugin } from "../plugins/node";
import { PythonPlugin } from "../plugins/python";
import { GoPlugin } from "../plugins/go";
import { Plugin } from "../types";

class PluginRegistry {
Expand All @@ -12,7 +13,8 @@ class PluginRegistry {

private registerDefaults() {
this.register("node", new NodePlugin());
this.register("python", new PythonPlugin())
this.register("python", new PythonPlugin());
this.register("go", new GoPlugin());
}

register(name: string, plugin: Plugin) {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@kidkender/archgen",
"version": "1.1.0",
"description": "Generate production-ready Node.js and Python project structures in seconds",
"version": "1.3.0",
"description": "Generate production-ready Node.js, Python, and Go project structures in seconds",
"main": "dist/index.js",
"module": "dist/index.mjs",
"exports": {
Expand Down
7 changes: 7 additions & 0 deletions plugins/go/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { PluginConfig } from "../../types";

export const goConfig: PluginConfig = {
name: "Go",
description: "Go · chi · GORM · PostgreSQL · golang-migrate · slog",
addons: ["docker", "testing", "ci", "jwt"],
};
Loading
Loading