diff --git a/README.md b/README.md index 033f41c..3ab2aa5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,13 @@ > curl -fsSL https://devmagic.run/install | bash > ``` > -> _(P.S.: It is always recommended to [see what you are running](https://devmagic.run/install) before doing so.)_ +> _(P.S.: It is always recommended to [see what you are running](https://devmagic.run/install) before doing so. For a hardened `curl` invocation — one that refuses to silently downgrade to plain HTTP or an old TLS version — add `--proto '=https' --tlsv1.2`, the same flags [rustup](https://rustup.rs) and other installers use:_ +> +> ```sh +> curl --proto '=https' --tlsv1.2 -fsSL https://devmagic.run/install | bash +> ``` +> +> _)_ ## TL;DR diff --git a/docs/adr/0006-generic-template-installer.md b/docs/adr/0006-generic-template-installer.md index 3f5bfd9..a986057 100644 --- a/docs/adr/0006-generic-template-installer.md +++ b/docs/adr/0006-generic-template-installer.md @@ -54,4 +54,4 @@ A single generic script that downloads and parses a manifest per group. Rejected ## Notes - Related: [ADR 0005](0005-generate-devcontainer-files-from-templates.md) (template folder and devcontainer generation). -- Future: refactor `setup/install-prettier.sh` to source its config files from `templates/prettier/` instead of heredocs; add more groups (VS Code settings, license, CI workflows) as needed. +- Future: refactor `setup/install-prettier.sh` to source its config files from `templates/prettier/` instead of heredocs; add more groups (VS Code settings, license, CI workflows) as needed. Tracked in [RFC 0001 - Unify Installer Implementation](../rfcs/0001-unify-installer-implementation.md). diff --git a/docs/adr/README.md b/docs/adr/README.md index 03e3cc7..f1aa431 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -10,6 +10,8 @@ An Architecture Decision Record (ADR) captures an important architectural decisi - What alternatives were considered - What the trade-offs and consequences are +ADRs are short and final — they record the _result_ of a decision, not the discussion that led to it. For the full discussion (options being weighed, open questions, an in-progress design that hasn't been decided yet), see [`../rfcs/`](../rfcs/README.md). + ## Format We use a simplified version of [Michael Nygard's ADR template](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions), which is the most popular format in open-source projects. diff --git a/docs/rfcs/0001-unify-installer-implementation.md b/docs/rfcs/0001-unify-installer-implementation.md new file mode 100644 index 0000000..14203e5 --- /dev/null +++ b/docs/rfcs/0001-unify-installer-implementation.md @@ -0,0 +1,74 @@ +# 0001 - Unify Installer Implementation (Scripts vs. Generated Templates) + +**Status:** Exploring + +**Started:** 2026-07-14 + +**Participants:** Marcelo Almeida, AI pairing session (Claude Code) + +## Problem + +`www/app/install/[script]/route.ts` currently does two structurally different things depending on the id it's asked for, and the code that does the "template" side embeds a full bash script as a JS template literal with string interpolation. + +## Context + +[ADR 0006](../adr/0006-generic-template-installer.md) introduced the `templates:` registry entries and `generateTemplateInstaller()`, which builds a bash script on the fly (as a template string inside `route.ts`) that `curl`s each registered file to its destination. This sits alongside the pre-existing `scripts:` entries, which serve a bash file that already lives in the repo (`setup/*.sh`) more or less as-is (with a `PACKAGE_MANAGER` env var injected for the `prettier` script). + +So today there are two paths that both end up "putting files into a new project," implemented two different ways: + +1. **`scripts:`** — a real `.sh` file in `setup/`, containing arbitrary logic (e.g. `install-prettier.sh` edits `package.json`, installs Husky, configures lint-staged — not just file copies). +2. **`templates:`** — no file on disk; `route.ts` generates the installer text itself, string-building a bash script (heredoc-style JS template literal with `${...}` interpolation for paths, notes, the `REPO_URL` detection logic, etc). + +This PR's ADR already flagged the seams: + +> - Known duplication: `install-prettier.sh` still embeds the prettier config in heredocs; a future refactor can make it download from `templates/prettier/` instead. +> - `www/app/install/[script]/route.ts` ... Interpolated values are all registry-controlled. + +## Discussion Log + +### 2026-07-14 + +Marcelo, reviewing the merged PR: + +> `[script]/route.ts` - this file seems to be doing a lot of stuff. Right now it is working, so let's merge as is: I still want to think more about it. Problems I currently have with it and you might have already mentioned in the ADR (do not fix them now, please): (a) The shell script embedded in it, with a bunch of string interpolation. Seems like something that should be in a template, using proper templating language and logic. (b) It might return a generated script that creates a file from a template OR a separate script (saved in the repo) that does the same thing, but differently, if I understood correctly. Edit: you mention this in the ADR already... I read the code before reading the ADR. I agree with you: next step could be unifying everything, moving shell scripts under 'setup' to 'templates'. + +Claude's read on it: agreed on both counts. (a) is the more clear-cut win — moving the generated-installer logic out of a JS string and into an actual template file (checked into `templates/`, rendered with a real templating step, even if minimal) makes it reviewable, diffable, and testable the same way the devcontainer templates already are. (b) is more of a design question than a bug: `setup/*.sh` scripts carry real logic (install order, conditionals, `package.json` mutation) that plain file-copy templates can't express, so "unify everything" probably doesn't mean "delete one path" — it likely means giving both paths the same _shape_: templates live under `templates/`, and the registry entry says whether the id needs (a) a plain file-copy installer (today's `templates:` behavior) or (b) a scripted installer with real logic (today's `scripts:` behavior, but sourced from `templates/` instead of `setup/`). That would remove the `setup/` vs `templates/` split entirely and leave `route.ts` as a dispatcher instead of a generator. + +## Options Considered + +### Option 1: Move the generated-installer template into a real file + +Keep the file-copy vs. scripted-logic distinction, but stop building the file-copy installer as a JS template literal in `route.ts`. Store it as an actual template file (e.g. `templates/_installer.sh.tmpl`) with `{{...}}` placeholders in the same style as the devcontainer templates, and have `route.ts` do simple substitution (or a minimal templating step) instead of string-building bash inline. + +- **Pro:** the installer becomes reviewable/diffable like any other template; keeps today's split between `scripts:` and `templates:` (smaller change). +- **Con:** doesn't address point (b) — two mechanisms still exist for conceptually the same job. + +### Option 2: Move `setup/*.sh` scripts under `templates/`, one registry entry type + +Relocate `setup/devmagic.sh`, `setup/install-prettier.sh`, `setup/devcontainer-setup.sh`, `setup/generate.sh` under `templates/` (e.g. `templates/prettier/install.sh`, `templates/devcontainer/install.sh`), so all installable things live in one place regardless of whether they're a plain file-copy or a scripted installer with logic. The registry entry would declare which kind it is; `route.ts` becomes a thin dispatcher (serve-as-is for scripted entries, generate-from-template for file-copy entries) instead of containing the generation logic itself. + +- **Pro:** one place (`templates/`) for everything a project can install; matches the `templates/` model already established by ADR 0005/0006; directly fixes the duplication ADR 0006 flagged (e.g. `install-prettier.sh` could pull its config from `templates/prettier/` instead of embedding a second copy in heredocs). +- **Con:** bigger change — touches every existing script path and reference (README, ADRs, the registry, `www/lib/install-scripts.ts`). + +### Option 3: Do nothing further + +Leave `route.ts` as-is; it works today and both paths are already registry-driven and reasonably well-tested. + +- **Pro:** zero effort, zero regression risk. +- **Con:** the duplication and embedded-bash-in-JS concerns don't go away, and every new scripted installer added under `setup/` widens the gap Option 2 would close. + +## Open Questions + +- Is a "real templating language" (Option 1) worth adding as a dependency, or is placeholder substitution (already used for the devcontainer templates) good enough for the installer script too? +- If `setup/` moves under `templates/` (Option 2), does the distinction between "file-copy template" and "scripted installer" become a `type:` field in the registry, or an implicit fact about the entry (has a `files:` list vs. a `scriptPath:`)? +- Worth doing before or after more template groups are added? More groups added under the current split means more to migrate later. + +## Decision + +Not yet — intentionally left open. Revisit before adding the next scripted (non-file-copy) template, since each new one under `setup/` widens the gap Option 2 would close. + +## Related + +- [ADR 0005 - Generate Devcontainer Files from Templates](../adr/0005-generate-devcontainer-files-from-templates.md) +- [ADR 0006 - Serve Generic Project Templates Through the Installer Registry](../adr/0006-generic-template-installer.md) +- [PR #83](https://github.com/marcelocra/devmagic/pull/83) (merged; this RFC captures follow-up discussion from its review) diff --git a/docs/rfcs/README.md b/docs/rfcs/README.md new file mode 100644 index 0000000..21a455d --- /dev/null +++ b/docs/rfcs/README.md @@ -0,0 +1,45 @@ +# RFCs (Design Discussions) + +This directory holds the **running discussion** for a feature or bug before (and often during and after) it gets built: the problem, the context, the options considered, the back-and-forth that led to a decision, and open questions. It is DevMagic's equivalent of what many projects call a "design doc" or "proposal." + +## RFC vs. ADR vs. `docs/archives/` + +DevMagic already has two other places decisions show up, and it's worth being precise about how they differ: + +- **RFC (`docs/rfcs/`, this directory)** — the _process_ of reaching a decision. Long-form, informal, evolving. Captures the problem, the context, every option considered (including ones we rejected), the discussion (including AI pairing sessions — paste the relevant exchange in, don't just summarize it away), and open questions that are still, well, open. An RFC can stay in "Draft" or "Exploring" status indefinitely — not everything needs to be resolved immediately, and writing it down is valuable even before a decision is made. +- **[ADR](../adr/) (`docs/adr/`)** — the _result_. Short, final, rarely edited after acceptance. Title, status, context (a paragraph, not a transcript), the decision, alternatives considered (a summary, not the debate), and consequences. If an RFC leads to a decision, the ADR is the two-paragraph summary of it — and should link back to the RFC for anyone who wants the full story. +- **`docs/archives/`** — informal planning notes and brainstorms that predate this convention (blog planning, an early refactor plan, etc.). Kept for historical reference; not maintained as a structured system. New planning work should use `docs/rfcs/` instead. + +Put differently: **RFC → (optional) ADR**. Many RFCs never need an ADR — a bug fix or a small feature doesn't need a decision record, just a place to think out loud and leave a trail. Reach for an ADR once there's a decision worth being able to cite later ("why did we do it this way?"). + +## When to write one + +Not everything needs an RFC. Use your judgement — the [OpenBMC design doc guidelines](https://github.com/openbmc/docs/blob/master/designs/design-template.md) put it well: if a change can be made in a single, reasonably small patchset with little impact, it doesn't need a design discussion. + +Write one when: + +- The problem or the right approach isn't obvious, and you want to think through it (or work through it with an AI pairing session) before touching code. +- There are multiple real options and you want a record of why the others were rejected. +- The discussion is likely to resurface later ("didn't we consider X already?"). + +## Format + +1. Copy `template.md` to a new file: `NNNN-short-title.md`, numbered sequentially like ADRs (shared numbering isn't required — RFC numbers and ADR numbers are independent sequences). +2. Start in whatever status fits — usually `Exploring` or `Draft`. +3. Keep appending to the Discussion Log as the conversation develops. Don't rewrite history — append, with dates. It's fine (encouraged, even) to paste in verbatim excerpts from a chat with an AI assistant or a conversation with a collaborator; that context is exactly what gets lost when people only keep the final decision. +4. When (if) a decision is reached, either fold the summary into the RFC's own "Decision" section, or — for decisions worth citing later — write an ADR and link to it from the RFC (and vice versa). +5. Update the status: `Decided`, `Implemented`, `Superseded`, or `Abandoned`. Don't delete abandoned RFCs — the discussion of why something _wasn't_ done is often as valuable as why something was. + +## Index + +- [0001 - Unify Installer Implementation (Scripts vs. Generated Templates)](0001-unify-installer-implementation.md) — **Exploring** + +## Further Reading + +The RFC vs. ADR distinction above (and this whole convention) is drawn from these: + +- [Engineering Planning with RFCs, Design Documents and ADRs](https://newsletter.pragmaticengineer.com/p/rfcs-and-design-docs) — the RFC/ADR workflow this directory follows: write the RFC, review async, decide, write a short ADR the same day. +- [Companies Using RFCs or Design Docs and Examples of These](https://blog.pragmaticengineer.com/rfcs-and-design-docs/) — real templates and examples from Airbnb, Amazon, and others, including Stedi's lightweight "Decision Records" format. +- [Notes on technical design documentation practices (RFCs, ADRs, decision logs)](https://gist.github.com/0xdevalias/7fbbed02d61190c617393e2e51372a11) — a broad, continuously-updated survey of the space. +- [Document your technical decisions using RFC and ADR](https://emanuelcasco.vercel.app/blog/document-with-rfc-and-adr) — a concise walkthrough of using both together. +- [OpenBMC design doc guidelines](https://github.com/openbmc/docs/blob/master/designs/design-template.md) — source of the "not everything needs one" guidance above, plus a full design doc template from an active open-source project. diff --git a/docs/rfcs/template.md b/docs/rfcs/template.md new file mode 100644 index 0000000..cf538d1 --- /dev/null +++ b/docs/rfcs/template.md @@ -0,0 +1,45 @@ +# NNNN - Title in Present Tense + +**Status:** Exploring + +**Started:** YYYY-MM-DD + +**Participants:** Who's involved (a person, "AI pairing session", both) + +## Problem + +What's broken, missing, or awkward? Who's affected, and how did it come up? + +## Context + +Constraints, prior art, related code, related RFCs/ADRs. Enough for someone with no memory of the conversation to understand why this matters. + +## Discussion Log + +Append-only. Each entry is a dated note, a paste from a conversation, or a summary of a call — whatever actually happened. Don't clean this up into something tidier after the fact; the messy version is the useful one. + +### YYYY-MM-DD + +What was discussed, considered, or decided in this round. Paste verbatim exchanges when they carry the reasoning (a chat excerpt, a code review comment thread, a summary of a conversation). + +## Options Considered + +### Option 1: Name + +What it is, what it costs, what it buys. + +### Option 2: Name + +... + +## Open Questions + +Things that are still unresolved. It's fine for this list to be the only content in an early RFC. + +## Decision + +Fill in once (if) something is decided. Link to an ADR if the decision is significant enough to want a permanent, short record (`../adr/`). + +## Related + +Links to PRs, issues, ADRs, other RFCs. diff --git a/www/app/install-scripts/page.tsx b/www/app/install-scripts/page.tsx index dc19f1b..1f6ff05 100644 --- a/www/app/install-scripts/page.tsx +++ b/www/app/install-scripts/page.tsx @@ -1,6 +1,22 @@ import { loadInstallScripts, loadInstallTemplates } from "@/lib/install-scripts"; import Link from "next/link"; +// Descriptions in the registry use backticks for inline code (e.g. `prettier`). +// This renders those as instead of literal backticks; kept intentionally +// tiny — no markdown parser, no nested formatting, just this one case. +function withInlineCode(text: string) { + const parts = text.split("`"); + return parts.map((part, i) => + i % 2 === 1 ? ( + + {part} + + ) : ( + part + ), + ); +} + export default function InstallScriptsPage() { const scripts = loadInstallScripts(); const templates = loadInstallTemplates(); @@ -35,9 +51,9 @@ export default function InstallScriptsPage() {
{scripts.map((script) => ( -
+

{script.name}

-

{script.description}

+

{withInlineCode(script.description)}

{script.requirements && script.requirements.length > 0 && (
@@ -118,9 +134,13 @@ curl -fsSL https://devmagic.run/install/${script.id}?pm=bun | bash`}
{templates.map((template) => ( -
+

{template.name}

-

{template.description}

+

{withInlineCode(template.description)}

Files it creates: diff --git a/www/app/page.tsx b/www/app/page.tsx index f2af389..46b5b3b 100644 --- a/www/app/page.tsx +++ b/www/app/page.tsx @@ -2,9 +2,11 @@ import { Button } from "@/components/button"; import { CodeBlock } from "@/components/code-block"; import Link from "next/link"; import { getTranslations } from "next-intl/server"; +import { loadInstallScripts, loadInstallTemplates } from "@/lib/install-scripts"; export default async function Home() { const t = await getTranslations("home"); + const catalog = [...loadInstallScripts(), ...loadInstallTemplates()]; return ( <> @@ -218,6 +220,30 @@ export default async function Home() {
+ + {/* More Templates & Scripts */} +
+
+

{t("moreTemplates")}

+

{t("moreTemplatesSubtitle")}

+ +
+ {catalog.map((item) => ( + + {item.name} + + ))} +
+ + + {t("viewAllTemplates")} → + +
+
); } diff --git a/www/components/footer.tsx b/www/components/footer.tsx index 5d24fc9..0ad85e3 100644 --- a/www/components/footer.tsx +++ b/www/components/footer.tsx @@ -48,6 +48,14 @@ export function Footer() { {t("features")} +
  • + + {t("templatesAndScripts")} + +