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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,6 @@ crash.*.log

# Sandbox artefact: the harness bind-mounts /dev/null here, it is not a project file
.mcp.json

# Site mockups, the maintainer's own work in progress
maquettes-site/
5 changes: 4 additions & 1 deletion README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
<!-- Generated by `mise run docs:coverage`. Do not edit by hand. -->

**Pointez Terraform et les CLI officielles vers votre propre machine.**
Aucun compte cloud, aucun identifiant, et rien de créé nulle part.
Aucun compte cloud, aucun identifiant de cloud, et rien de créé nulle part.

Terraform et OpenTofu pilotent Scaleway et Outscale. Chaque pack a en plus
son CLI officiel, et chacun d'eux pilote cet émulateur de bout en bout :
Expand All @@ -71,6 +71,9 @@ son CLI officiel, et chacun d'eux pilote cet émulateur de bout en bout :

**Sur votre machine**, un binaire statique et une stack assez courte pour se lire :

> Il vous faut **Git** et **Terraform >= 1.7.0**. feint lui-même n'a aucune dépendance
> d'exécution : c'est la stack qui réclame le moteur.

```bash
brew install stephrobert/feint/feint
git clone https://github.com/stephrobert/feint
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
> [!IMPORTANT]
> **What is safe to point at this emulator, and what is not.**
>
> **Proven**: 350 of the 373 mounted operations are driven by a real client, on every pull request. `scw`, `octl`, `exo`, Terraform and OpenTofu run against the emulator in CI, and machines really boot: an ssh login on each provider's own default account, isolated subnets, a firewall that filters. The whole chain is described in [docs/conformance.md](docs/conformance.md).
> **Proven**: 350 of the 373 mounted operations are driven by a real client, on every pull request: each pack's own official CLI, and an infrastructure engine wherever a pack admits one, all of them running against the emulator in CI, and machines really boot: an ssh login on each provider's own default account, isolated subnets, a firewall that filters. The whole chain is described in [docs/conformance.md](docs/conformance.md).
>
> **Not proven**: quotas, prices, real capacity, identifier validation, authentication, eventual consistency. The 50 sections of [docs/limits.md](docs/limits.md) each say what one costs. An emulator with a single implicit account and no price list would have to invent those figures, and somebody would act on them.
>
Expand All @@ -42,7 +42,7 @@
<!-- Generated by `mise run docs:coverage`. Do not edit by hand. -->

**Point Terraform and the official cloud CLIs at your own machine.**
No cloud account, no credentials, and nothing created anywhere.
No cloud account, no cloud credentials, and nothing created anywhere.

Terraform and OpenTofu drive Scaleway and Outscale. Each pack also has its
own official CLI, and every one of them drives this emulator end to end:
Expand All @@ -60,6 +60,9 @@ own official CLI, and every one of them drives this emulator end to end:

**On your machine** — one static binary, and a stack short enough to read whole:

> You need **Git** and **Terraform >= 1.7.0**. feint itself has no runtime
> dependency: it is the stack that asks for the engine.

```bash
brew install stephrobert/feint/feint
git clone https://github.com/stephrobert/feint
Expand Down
6 changes: 3 additions & 3 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ nothing is signed until it is released:
go install github.com/stephrobert/feint/cmd/feint@latest
```

That gives you the control plane — every API answers, `scw`, `octl`, `exo`
and Terraform drive it, and nothing runs. It is what CI uses and it needs no
prerequisite at all.
That gives you the control plane — every API answers, `scw`, `octl` and `exo`
drive their own packs, Terraform and OpenTofu drive Scaleway and Outscale, and
nothing runs. It is what CI uses and it needs no prerequisite at all.

## With Homebrew

Expand Down
4 changes: 2 additions & 2 deletions internal/cli/capability_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,15 @@ func renderPromise(french bool) (string, error) {

if french {
b.WriteString("**Pointez Terraform et les CLI officielles vers votre propre machine.**\n")
b.WriteString("Aucun compte cloud, aucun identifiant, et rien de créé nulle part.\n\n")
b.WriteString("Aucun compte cloud, aucun identifiant de cloud, et rien de créé nulle part.\n\n")
b.WriteString(wrapParagraph(fmt.Sprintf(
"%s pilotent %s. Chaque pack a en plus son CLI officiel, et chacun d'eux pilote cet "+
"émulateur de bout en bout :",
joinNames(engineNames, "et"), joinNames(drivenNames, "et"))))
b.WriteString("\n")
} else {
b.WriteString("**Point Terraform and the official cloud CLIs at your own machine.**\n")
b.WriteString("No cloud account, no credentials, and nothing created anywhere.\n\n")
b.WriteString("No cloud account, no cloud credentials, and nothing created anywhere.\n\n")
b.WriteString(wrapParagraph(fmt.Sprintf(
"%s drive %s. Each pack also has its own official CLI, and every one of them drives "+
"this emulator end to end:",
Expand Down
3 changes: 2 additions & 1 deletion internal/cli/docs_banner.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ func renderSafety(facts safetyFacts, french bool) string {
b.WriteString("> [!IMPORTANT]\n")
b.WriteString("> **What is safe to point at this emulator, and what is not.**\n>\n")
fmt.Fprintf(&b, "> **Proven**: %d of the %d mounted operations are driven by a real client, on every "+
"pull request. `scw`, `octl`, `exo`, Terraform and OpenTofu run against the emulator in "+
"pull request: each pack's own official CLI, and an infrastructure engine "+
"wherever a pack admits one, all of them running against the emulator in "+
"CI, and machines really boot: an ssh login on each provider's own default account, isolated "+
"subnets, a firewall that filters. The whole chain is described in "+
"[docs/conformance.md](docs/conformance.md).\n>\n", facts.Driven, facts.Mounted)
Expand Down
70 changes: 70 additions & 0 deletions internal/cli/docs_quickstart.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
)

Expand Down Expand Up @@ -91,6 +92,32 @@ func renderQuickstart(goMod, changelog string, french bool) (string, error) {
} else {
b.WriteString("**On your machine** — one static binary, and a stack short enough to read whole:\n\n")
}

// What the reader needs that these four commands do not install.
//
// They install feint, clone, change directory and run — and the third one
// runs the engine the stack declares, which is not feint. Measured
// 2026-08-28: a reader on a machine carrying neither Git nor Terraform
// cannot reach the `Apply complete!` this block prints underneath, which is
// #593's own complaint one level out: a Quick Start that displays an output
// its own commands cannot produce.
//
// The engine and its floor are read from the stack's own declaration and its
// `required_version`, never typed here: a number written beside a file that
// owns it is a number that goes stale in silence, and this block exists
// because that happened to the resource count.
if engine, floor, ok := quickstartEngineFloor(stack); ok {
if french {
fmt.Fprintf(&b, "> Il vous faut **Git** et **%s %s**. feint lui-même n'a aucune dépendance\n"+
"> d'exécution : c'est la stack qui réclame le moteur.\n\n",
engineName(engine), floor)
} else {
fmt.Fprintf(&b, "> You need **Git** and **%s %s**. feint itself has no runtime\n"+
"> dependency: it is the stack that asks for the engine.\n\n",
engineName(engine), floor)
}
}

b.WriteString("```bash\n")
fmt.Fprintf(&b, "brew install %s/%s\n", slug, pathBase(slug))
fmt.Fprintf(&b, "git clone https://github.com/%s\n", slug)
Expand Down Expand Up @@ -364,3 +391,46 @@ func writeSplicedTranslatedBanner(root string, order []string, routes map[string
}
return os.WriteFile(path, []byte(updated), 0o644) //nolint:gosec // documentation is world-readable by design
}

// quickstartEngineFloor answers the engine a quickstart declares and the lowest
// version its Terraform will accept, both read from the files that own them.
//
// Two files, because the two facts live apart: `feint.yaml` names the engine
// `feint up` will run, and `main.tf`'s `required_version` names the floor that
// engine will refuse to go below. Typing either one beside this block is what
// #593's resource count already proved goes stale.
//
// It answers false rather than guessing when either is missing: a prerequisite
// line that names a version nobody declared is worse than none, because a reader
// installs that version and still fails.
func quickstartEngineFloor(stack string) (engine, floor string, ok bool) {
declaration, err := os.ReadFile(filepath.Join(stack, "feint.yaml"))
if err != nil {
return "", "", false
}
engineLine := regexp.MustCompile(`(?m)^\s*engine:\s*(\S+)\s*$`).FindSubmatch(declaration)
if engineLine == nil {
return "", "", false
}
main, err := os.ReadFile(filepath.Join(stack, "main.tf"))
if err != nil {
return "", "", false
}
version := regexp.MustCompile(`required_version\s*=\s*"([^"]+)"`).FindSubmatch(main)
if version == nil {
return "", "", false
}
return string(engineLine[1]), strings.TrimSpace(string(version[1])), true
}

// engineName spells an engine the way its own project does.
func engineName(engine string) string {
switch engine {
case "terraform":
return "Terraform"
case "opentofu":
return "OpenTofu"
default:
return engine
}
}
Loading