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
21 changes: 20 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,25 @@ Edit `.env` with local values for development. **Do not commit `.env` or `.env.l
1. Fork the repository (or create a branch if you have write access).
2. Branch from `main` with a short descriptive name, for example `fix/post-list-error` or `docs/contributing`.
3. Keep changes focused — avoid unrelated refactors.
4. Open a pull request against `main` with a clear summary and test notes.
4. Open a **pull request** against `main` with a clear summary and test notes.
5. **Do not push directly to `main`.** Repository rules require PRs and passing checks (including CodeQL).

### Commit messages

Use clear, imperative subjects that explain *why* when helpful:

- `feat: add hugo frontmatter adapter option`
- `fix: surface GitHub 403 on protected branch`
- `docs: document PR publish modes`
- `chore: update release checklist`

Squash merges are typical for feature PRs.

### Tests required

- Run `pnpm build` and `pnpm test` before opening a PR.
- Run `pnpm test:e2e` when Studio UI, auth, or publish flows change.
- Add or update unit tests for logic changes in `packages/*` or `apps/studio/server` when practical.

Before a release, see [RELEASE_CHECKLIST.md](RELEASE_CHECKLIST.md) and [docs/manual-acceptance-test.md](docs/manual-acceptance-test.md).

Expand All @@ -39,6 +57,7 @@ From the repository root:
pnpm install # install workspace dependencies
pnpm build # build all packages and Studio (including server TypeScript)
pnpm test # run unit tests
pnpm test:e2e # Playwright smoke tests (demo mode)
pnpm dev # start Studio UI + publish API locally
```

Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# SourceDraft

SourceDraft is a free, open-source editor for Markdown and MDX blogs. You write in the browser, upload images, check SEO metadata, preview the generated file, and publish to a Git repository or remote CMS (WordPress, Ghost).
SourceDraft is a free, open-source **Git-based CMS** for Markdown and MDX publishing. You write in the browser with a Tiptap editor and slash commands, upload images, run content QA checks, preview the generated file, and publish to a Git repository or remote CMS (WordPress, Ghost).

Licensed under [AGPL-3.0-or-later](LICENSE).

**Project status:** SourceDraft is an early local/private MVP for Git-backed Markdown and MDX publishing. It is usable for solo writing and GitHub commits, but it is not a hosted CMS, multi-user product, or finished SaaS. See [docs/project-status.md](docs/project-status.md) and [CHANGELOG.md](CHANGELOG.md).

Expand Down Expand Up @@ -32,13 +34,18 @@ Your static site still builds and deploys exactly as before. SourceDraft creates

## What it does today

- Edit articles in Studio (title, slug, dates, category, tags, body, draft flag)
- Edit articles in Studio with a **Tiptap rich editor**, **slash commands**, and **source mode** for raw Markdown/MDX
- List and edit existing posts from your GitHub `contentDir`
- Validate fields against a universal article schema
- Preview Markdown or Astro MDX output and target file path before publishing
- **Content QA** — non-blocking warnings for SEO, alt text, headings, links, and body length
- **Publish checklist** — validation status, output path, publish mode, and warnings before publish
- Preview Markdown or MDX adapter output and target file path before publishing
- Publish to Git hosts (GitHub, GitLab, Bitbucket) or remote CMS APIs (WordPress, Ghost)
- **GitHub PR publishing** — direct commit, pull request, or draft pull request for protected branches
- Upload images to git `mediaDir`, Cloudinary, or (experimental) S3-compatible storage
- Optional deploy hooks after publish (Vercel, Netlify, Cloudflare Pages, generic)
- **Setup detection** — scan local project files and suggest adapter, content, and media paths
- **Content audit** — read-only scan of existing posts (frontmatter, duplicate slugs, complex MDX)
- Configure paths, adapter, and categories in `sourcedraft.config.json`
- Protect Studio with a server-side admin password
- **Demo mode** — explore Studio with sample posts without GitHub credentials
Expand Down Expand Up @@ -170,6 +177,10 @@ Issues and pull requests are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) fo
- [Ghost publishing](docs/ghost.md)
- [Deploy hooks](docs/deploy-hooks.md)
- [GitHub publishing](docs/github-publishing.md)
- [GitHub PR publishing](docs/github-pr-publishing.md)
- [Editor and source mode](docs/editor.md)
- [Setup detection](docs/setup-detection.md)
- [Content QA](docs/content-qa.md)
- [Media uploads](docs/media.md)
- [Configuration](docs/configuration.md)
- [Astro integration example](docs/astro-blog-example.md)
Expand Down
67 changes: 51 additions & 16 deletions RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,74 @@
# SourceDraft v0.1 release checklist
# SourceDraft release checklist

Use this before tagging `v0.1.0` or promoting the repository publicly.
Use this before tagging a public release or promoting the repository.

## Automated checks

```bash
pnpm install --lockfile-only
pnpm install --frozen-lockfile
pnpm build
pnpm test
pnpm test:e2e
```

- [ ] All three commands exit 0
- [ ] All four commands exit 0
- [ ] Studio build includes server TypeScript (`tsc -p server/tsconfig.json` in `apps/studio` build)
- [ ] CI workflow (`.github/workflows/ci.yml`) runs the same build and test commands
- [ ] CI workflow (`.github/workflows/ci.yml`) runs build, test, and studio-e2e on pull requests
- [ ] **CodeQL** checks pass on the release PR (JavaScript/TypeScript + Actions; no open high-severity alerts)

## Repository
## Repository hygiene

- [ ] `LICENSE` present (AGPL-3.0-or-later)
- [ ] `CHANGELOG.md` has a `v0.1.0` section
- [ ] `LICENSE` present (**AGPL-3.0-or-later**)
- [ ] `CHANGELOG.md` updated for the release version
- [ ] `CONTRIBUTING.md` present
- [ ] `.env` and `.env.local` are gitignored and not committed
- [ ] No real tokens or passwords in tracked files
- [ ] `.env` and `.env.local` are gitignored and **not committed**
- [ ] **No secrets check:** scan tracked files for tokens, passwords, or private keys (`rg -i 'ghp_|gho_|GITHUB_TOKEN=|password=' --glob '!*.example*'`)
- [ ] `sourcedraft.config.example.json` is generic (no site-specific secrets)
- [ ] No QuBrite hardcoding in `*.ts` / `*.tsx` app logic

## Documentation

- [ ] README quickstart matches current Studio UI and commands
- [ ] README describes Git-based Markdown/MDX publishing, adapters, publishers, PR publishing, editor, setup detection, content QA, and license
- [ ] Docs state: early local/private MVP, not hosted SaaS, not production multi-user auth
- [ ] GitHub Contents API limits documented
- [ ] `mediaDir` vs `publicMediaPath` documented
- [ ] Issue templates present under `.github/ISSUE_TEMPLATE/`

## Screenshots

```bash
pnpm screenshots:generate
```

- [ ] Regenerate Studio screenshots when UI changed materially
- [ ] Commit updated images under `docs/assets/` if diffs are intentional

## Smoke tests

### Demo mode (automated)

`pnpm test:e2e` covers demo login, editor, settings panels, publish simulation, and publish checklist.

- [ ] E2E green locally and in CI

### Protected-branch PR publishing (manual)

On a **test** GitHub repo with branch protection on `main`:

- [ ] Set `SOURCEDRAFT_PUBLISH_MODE=pull-request` in `.env`
- [ ] Publish a valid post from Studio
- [ ] PR is created with expected file path and branch prefix
- [ ] Optional: repeat with `draft-pull-request`

Details: [docs/github-pr-publishing.md](docs/github-pr-publishing.md)

## Manual acceptance

Run [docs/manual-acceptance-test.md](docs/manual-acceptance-test.md) against a **test** GitHub repository.
Run [docs/manual-acceptance-test.md](docs/manual-acceptance-test.md) against a test GitHub repository.

- [ ] Login and logout work
- [ ] Settings show adapter, `contentDir`, `mediaDir`, `publicMediaPath`
- [ ] Create post, upload image, publish
- [ ] Settings show setup detection, content audit, and setup health
- [ ] Create post, upload image, publish (direct mode)
- [ ] Edit existing post, publish update
- [ ] Verify files in GitHub match expectations

Expand All @@ -51,6 +81,11 @@ git push origin v0.1.0

Only tag after automated checks pass and manual acceptance is satisfactory.

## Known non-goals for v0.1
## Known non-goals (document, do not block release)

- OAuth, user accounts, hosted SaaS, team RBAC
- Full S3/R2 media upload (`s3-compatible` config validation only)
- Post list in Studio for Bitbucket, WordPress, and Ghost
- Git Trees API indexer for very large repos

Do not block release on: OAuth, user accounts, hosted SaaS, Cloudinary/S3/R2, Git Trees API, screenshots in repo, or Studio E2E test automation.
Roadmap: [docs/compatibility-roadmap.md](docs/compatibility-roadmap.md)
50 changes: 50 additions & 0 deletions docs/content-qa.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Content QA

SourceDraft runs **non-blocking** editorial checks while you write. They do not block publish unless required-field validation fails.

## Post details panel — Content quality

Warnings and suggestions for the current draft:

| Check | Type |
|-------|------|
| Meta title / description length | Guidance |
| Cover image alt text | Warning when hero is set |
| Image alt text in Markdown body | Warning |
| Duplicate H1 headings | Guidance |
| Long article without H2 sections | Guidance |
| Very short body | Guidance |
| Many external links | Guidance |
| Broken-looking internal links | Warning (vs loaded post slugs) |
| Missing social image when no hero | Guidance |

Required-field validation errors (title, slug, dates, etc.) also appear here.

## SEO / Sharing panel

Optional `metaTitle`, `metaDescription`, `canonicalUrl`, `socialImage`, and cover alt feed into frontmatter on publish. Soft length warnings use the same guidance thresholds as content quality.

## Publish checklist

The publish bar shows a compact checklist before you click **Publish**:

- Validation status
- Output path
- Publish mode and target branch / PR branch
- Draft vs live
- Media and SEO warning counts

## Content audit (existing posts)

Settings → **Content audit** scans posts already in your `contentDir` (read-only, no file changes):

- Valid vs invalid frontmatter
- Missing required fields and unsupported frontmatter keys
- Duplicate slugs
- Invalid dates
- **Source-only** posts with complex MDX (imports, exports, JSX)
- Ignored non-`.md` / `.mdx` files

Use the audit before importing an existing blog into SourceDraft editing workflows.

See also: [seo-fields.md](seo-fields.md) · [setup-detection.md](setup-detection.md)
6 changes: 3 additions & 3 deletions docs/design-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ The writing canvas and preview are capped around 72ch width. Body text uses ~18p

CSS variables live in `apps/studio/src/index.css` (`--font-ui`, `--font-writing`, `--font-mono`, and the `--text-*` scale).

## Markdown toolbar (v0.2)
## Body editor

Studio keeps a plain `<textarea>` for the article body and adds a formatting toolbar that inserts Markdown around the current selection via `apps/studio/src/lib/markdownEditor.ts`.
Studio uses a **Tiptap** rich editor with toolbar and slash commands. The persisted format remains a Markdown/MDX **string** for preview and publish. **Source mode** exposes a raw textarea for exact MDX editing.

**CodeMirror was not added in v0.2.** Toolbar actions only need selection ranges, text insertion, and focus restoration — all available on a textarea without a new dependency. CodeMirror 6 remains the preferred upgrade when we need syntax highlighting, bracket matching, multi-cursor editing, or built-in search/replace in source mode. Until then, the textarea keeps the bundle smaller and preserves MDX body text as a simple string end-to-end for preview and publish.
Details: [editor.md](editor.md). The legacy `MarkdownToolbar` component is unused; do not document it as the primary editor.
27 changes: 27 additions & 0 deletions docs/editor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Editor and source mode

Studio uses a **Tiptap**-powered body editor with a formatting toolbar and slash commands. The article body remains a **Markdown/MDX string** in app state — preview, autosave, outline, and publish all use that string.

## Rich mode

- Toolbar: headings, bold, italic, lists, blockquote, code block, link, image, horizontal rule
- Slash commands: `/h1`, `/h2`, `/h3`, `/quote`, `/code`, `/image`, `/hr`, `/link`, `/internal`, `/callout`
- Unknown MDX JSX blocks render as locked placeholders in rich mode (not deleted)

## Source mode

Toggle **Source** in the editor toolbar to edit raw Markdown/MDX in a textarea. Use source mode when:

- Posts contain custom MDX components
- Rich mode cannot round-trip complex syntax cleanly
- You need exact whitespace or frontmatter-adjacent body text

Switching back to rich mode re-parses the body string. Complex MDX may appear as non-editable blocks.

## Limitations

- Custom markdown serializer — not full CommonMark; nested or unusual markdown may not round-trip perfectly in rich mode
- No collaborative editing, comments, or cloud sync
- Internal link slash command inserts the first loaded post when the picker is not opened manually

See also: [content-qa.md](content-qa.md) · [design-notes.md](design-notes.md)
42 changes: 42 additions & 0 deletions docs/github-pr-publishing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# GitHub pull request publishing

Use PR publish modes when `main` (or your default branch) is **branch-protected** and direct commits are blocked.

## Modes

| Mode | Behavior |
|------|----------|
| `direct` | Commit post file to the base branch (default) |
| `pull-request` | Commit to a SourceDraft branch and open a PR into the base branch |
| `draft-pull-request` | Same as pull request, but the PR is created as a draft |

Studio shows the mode selector in the publish bar. The server default comes from config; you can override per publish in the UI.

## Configuration

Set in `.env` (server-side only):

| Variable | Purpose |
|----------|---------|
| `SOURCEDRAFT_PUBLISH_MODE` | `direct`, `pull-request`, or `draft-pull-request` |
| `SOURCEDRAFT_PR_BRANCH_PREFIX` | Prefix for generated branches (default `sourcedraft/`) |
| `SOURCEDRAFT_PR_DRAFT` | When `true` with `pull-request`, creates draft PRs |

**GitHub publisher only.** GitLab, Bitbucket, WordPress, and Ghost publishers use their native APIs — PR mode does not apply.

Deploy hooks are skipped for PR modes because the base branch is unchanged until merge.

## Protected branches

If direct publish fails with a protected-branch error, switch to `pull-request` or `draft-pull-request` and publish again. Studio surfaces the server error with this guidance.

## Smoke test (manual)

On a test repo with branch protection:

1. Set `SOURCEDRAFT_PUBLISH_MODE=pull-request` in `.env` and restart the API.
2. Publish a valid post from Studio.
3. Confirm a PR is created on GitHub with the expected file path.
4. Merge the PR and verify the file lands on the base branch.

See also: [github-publishing.md](github-publishing.md) · [RELEASE_CHECKLIST.md](../RELEASE_CHECKLIST.md)
4 changes: 2 additions & 2 deletions docs/project-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Early open-source MVP — usable for solo writing and publishing to Git or remot

| Area | Status |
|------|--------|
| **Studio** | Article editor, post list (git publishers), media library (git-backed), preview, SEO panel, setup/compatibility health |
| **Studio** | Tiptap editor + slash commands + source mode, post list (git publishers), media library (git-backed), preview, SEO panel, content QA, publish checklist, setup detection, content audit, setup/compatibility health |
| **Adapters** | `astro-mdx`, `markdown`, `nextjs-mdx`, `hugo-markdown`, `eleventy-jekyll-markdown`, `docusaurus-mdx`, `mkdocs-markdown`, `nuxt-content-markdown` |
| **Publishers** | `github`, `gitlab`, `bitbucket`, `wordpress`, `ghost` |
| **Publishers** | `github`, `gitlab`, `bitbucket`, `wordpress`, `ghost` (GitHub PR publish modes: direct, pull-request, draft-pull-request) |
| **Media** | `github-media` (images + PDF), `cloudinary` (images) |
| **Deploy hooks** | `generic`, `vercel`, `netlify`, `cloudflare-pages` |
| **Config** | `sourcedraft.config.json` + `.env`, `pnpm setup`, `pnpm validate:config` |
Expand Down
6 changes: 6 additions & 0 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,9 @@ When both hash and plaintext are set, the hash is used. Plaintext remains a lega
**Intended for local/private use.** Do not expose Studio publicly without HTTPS, stronger auth, and deployment hardening.

Report security concerns privately; redact tokens in bug reports. See [CONTRIBUTING.md](../CONTRIBUTING.md).

## Automated security checks

Pull requests and pushes to `main` run GitHub **CodeQL** analysis for JavaScript/TypeScript and Actions workflows. Fix or dismiss open CodeQL alerts before merging release changes.

CI also runs `pnpm build`, `pnpm test`, and Playwright smoke tests (`pnpm test:e2e`). See [RELEASE_CHECKLIST.md](../RELEASE_CHECKLIST.md).
34 changes: 34 additions & 0 deletions docs/setup-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Setup detection

Settings → **Setup detection** scans local project files and suggests configuration for SourceDraft. It does **not** write `sourcedraft.config.json` automatically.

## What it detects

Framework markers for:

- Astro MDX
- Next.js MDX
- Hugo
- Eleventy / Jekyll
- Docusaurus
- MkDocs
- Nuxt Content

For each match it suggests `adapter`, `contentDir`, `mediaDir`, `publicMediaPath`, and `defaultBranch`, plus a **confidence score** and explanation of signals found.

## API

`GET /api/setup/detect` (authenticated) scans:

1. `SOURCEDRAFT_REPO_ROOT` or `CMS_REPO_ROOT` if set
2. Otherwise walks up from the API working directory looking for `package.json`, `sourcedraft.config.json`, or common framework config files

## Applying suggestions

When confidence is high and there are no warnings, **Copy suggested config** copies a JSON snippet to the clipboard. Review paths, then paste into `sourcedraft.config.json` and run `pnpm validate:config`.

For interactive setup, use `pnpm setup` ([setup-wizard.md](setup-wizard.md)).

## Content audit

Settings → **Content audit** runs a separate read-only scan of existing posts via `GET /api/content/audit`. See [content-qa.md](content-qa.md#content-audit-existing-posts).
Loading