Compare commits

...

1 Commits

Author SHA1 Message Date
CIAgent 18e269da72 docs(P03): complete install-docs-e2e phase
---ci---
phase: 3
milestone: v0.4
status: complete
requirements:
  covered: [REQ-4-005]
  partial: []
---/ci---
2026-09-12 23:11:30 +00:00
6 changed files with 180 additions and 6 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
{
"phase": 2,
"phase": 3,
"stage": "verify",
"milestone": "v0.4",
"phase_role": "execution",
"attempts": 0,
"updated_at": "2026-09-13T01:50:00Z"
"updated_at": "2026-09-13T04:00:00Z"
}
+2 -2
View File
@@ -15,7 +15,7 @@
|----|-------------|----------|-------|--------|
| REQ-4-003 | One-liner install script (`curl -fsSL <url> \| bash`): detects linux x64, resolves latest release from Gitea API, downloads binary + checksum, verifies sha256, installs to ~/.local/bin (PATH hint), degrades to source-bootstrap instructions when no binary | critical | 2 | complete |
| REQ-4-004 | Binary release pipeline: reproducible linux x64 build script, sha256 checksum sidecar, upload as release assets on every ship from v0.4 onward (ongoing binaries requirement) | critical | 2 | complete |
| REQ-4-005 | Install + quickstart documentation: README one-liner quickstart, CLI command reference, fresh-clone-to-running-stack end-to-end verification | high | 3 | pending |
| REQ-4-005 | Install + quickstart documentation: README one-liner quickstart, CLI command reference, fresh-clone-to-running-stack end-to-end verification | high | 3 | complete |
## v0.3 Requirements (Credential Engines)
@@ -195,7 +195,7 @@
| REQ-4-002 | 1 | complete |
| REQ-4-003 | 2 | complete |
| REQ-4-004 | 2 | complete |
| REQ-4-005 | 3 | pending |
| REQ-4-005 | 3 | complete |
### v0.3 (complete)
+1 -1
View File
@@ -23,7 +23,7 @@
| 0 | Pre-execution | complete | — | — | Specification, clarify, research, plan complete; .ciagent/ files updated for v0.4 |
| 1 | Bootstrap CLI core | complete | 0 | REQ-4-001, REQ-4-002 | `nextcraft doctor/bootstrap/verify/dev` work against a fresh clone; unit tests green |
| 2 | Binary build + release pipeline | complete | 1 | REQ-4-003, REQ-4-004 | Reproducible linux x64 binary + sha256 checksum; one-liner install script; assets uploaded to the Gitea release |
| 3 | Install docs + fresh-clone E2E | pending | 2 | REQ-4-005 | README quickstart verified end-to-end from a clean environment; fresh clone reaches running stack |
| 3 | Install docs + fresh-clone E2E | complete | 2 | REQ-4-005 | README quickstart verified end-to-end from a clean environment; fresh clone reaches running stack |
| 4 | Final review + ship | pending | 3 | — | Code review clean; audit passes; milestone tagged (v0.3.x final patch); release with binary assets created on Gitea |
---
+55 -1
View File
@@ -2,8 +2,62 @@
AI-native outcome school + marketplace — graduates prove what they can build, not what they can write.
## Quickstart
One-liner install (linux x64):
```sh
curl -fsSL https://git.coreci.dev/coreci/nextcraft/raw/main/scripts/install.sh | sh
```
That downloads the latest release's `nextcraft` CLI binary, verifies its sha256 checksum, and installs it to `~/.local/bin` (PATH hint printed if needed). Every release ships fresh binaries — re-run the one-liner to upgrade.
Then, from a clone of this repo:
```sh
nextcraft doctor # check prerequisites: node >= 18, pnpm >= 8, python3 >= 3.11, git, unshare
nextcraft bootstrap # pnpm install + ai-service venv + .env from template (idempotent)
nextcraft verify # health check: venv imports, uvicorn, ports, env
nextcraft dev # run the ai-service dev server on :8420 (web dev server: pnpm dev)
```
The E2E test (`apps/cli/tests/fresh-clone-e2e.test.ts`) proves this exact sequence on a fresh clone.
### No binary / non-linux?
The installer degrades to printed source instructions. Manual equivalent:
```sh
git clone https://git.coreci.dev/coreci/nextcraft.git && cd nextcraft
pnpm install
bash apps/ai-service/scripts/bootstrap.sh
cp apps/ai-service/.env.example apps/ai-service/.env
pnpm ai:dev
```
## CLI reference (`nextcraft`)
| Command | What it does | Exit codes |
|---------|--------------|------------|
| `doctor` | Checks prerequisites on PATH: node >= 18, pnpm >= 8, python3 >= 3.11, git, unshare (sandbox fabric). Every ✗ prints a fix hint. | 0 all pass, 1 any fail |
| `bootstrap` | Sets up the monorepo from a fresh clone: (1) locates the repo root, (2) `pnpm install`, (3) ai-service venv via `apps/ai-service/scripts/bootstrap.sh`, (4) copies `.env.example``.env` if absent, (5) warns on missing optional keys. Idempotent — safe to re-run. | 0 ok, 1 step failed |
| `verify` | Health check: ai-service venv + `import ai_service`, uvicorn importable, `.env` present (warn-only), `AI_PORT` (default 8420) free, workspace `node_modules` present. | 0 ok, 1 failures |
| `dev` | Thin passthrough to `apps/ai-service/scripts/dev.sh` (exports secrets from `.ciagent/.env.secrets` if present, runs uvicorn on :8420). Ctrl+C stops it. The web dev server is separate: `pnpm dev`. | child's exit code |
| `--help` / `-h` | Usage for the CLI or any command. | 0 |
| `--version` | Prints the version this binary was built as (matches the release tag). | 0 |
Exit-code contract: `0` success, `1` check/step failure (hint printed), `2` usage error.
## Docs
- [apps/cli/README.md](apps/cli/README.md) — CLI internals: build, binary pipeline, troubleshooting
- [.ciagent/PROJECT.md](.ciagent/PROJECT.md) — product spec and milestone history
- [.ciagent/ARCHITECTURE.md](.ciagent/ARCHITECTURE.md) — system architecture
## Status
**Milestone v0.1** — UI/UX Prototype (high-fidelity interactive, all mock data)
**Milestone v0.4** — Distribution & Bootstrap CLI (one-liner install, `nextcraft` binary releases on every ship)
Prior: v0.3 Credential Engines (shipped v0.2.8) · v0.2 AI Tutor Architecture (v0.2.0) · v0.1 UI/UX Prototype (v0.1.0)
Initialized via CIAgent v0.7.0
+48
View File
@@ -0,0 +1,48 @@
# @nextcraft/cli — nextcraft
The bootstrap CLI for the Nextcraft monorepo, shipped as a self-contained linux x64 binary (Node SEA) on every release.
## Commands
See the [root README quickstart](../../README.md) for the user-facing flow. Internals:
- `src/index.ts` — argv dispatch, exit-code contract (0 ok / 1 failure / 2 usage), direct-run guard (`argv[0] === argv[1]` detects SEA context — the installer renames the binary, so filename matching is unreliable)
- `src/commands/` — doctor / bootstrap / verify / dev; all orchestration delegates to `apps/ai-service/scripts/*.sh` via `src/lib/spawn.ts` (array-args only, SIGTERM→SIGKILL timeout ladder)
- `src/checks/` — pure logic: version compare, `.env` template diff
- `tests/` — node:test suites: dispatch, checks, spawn, command stubs, real-box doctor integration, install.sh fixture-server E2E (tamper rejection, degradation), release-assets token isolation, fresh-clone E2E
## Build
```sh
pnpm cli:typecheck # tsc --noEmit
pnpm cli:test # node:test suites
pnpm cli:build # tsc -p tsconfig.build.json -> dist/
pnpm --filter @nextcraft/cli build:binary <tag> # SEA binary + sha256 sidecar
```
`build:binary <tag>`: esbuild bundle (CJS, node18 target, version stamped via `NEXTCRAFT_VERSION_STAMP` define — `--version` reports the tag it was built as) → `node --experimental-sea-config` → postject injection into a copy of the system node binary → `dist/nextcraft-linux-x64` + `dist/nextcraft-linux-x64.sha256`. The binary runs without node on PATH (runtime embedded, ~117 MB).
## Release pipeline
Every ship from v0.3.2 onward runs `scripts/release-assets.sh <tag>` after tag+merge:
1. Builds the binary stamped with the tag
2. Resolves `GITEA_TOKEN` from `.env*` files ONLY (`.ciagent/.env.secrets` first) — never from shell env
3. Attaches `nextcraft-linux-x64` + `nextcraft-linux-x64.sha256` to the Gitea release (bounded retry, best-effort — never blocks the ship)
`scripts/install.sh` (POSIX sh, dash-safe): platform gate → Gitea latest-release API resolve → exact-name asset match → sha256 verify BEFORE install (mismatch = hard stop) → `~/.local/bin` install → PATH hint. Any failure degrades to printed source-bootstrap instructions.
## Secrets policy
The CLI never generates, writes, or echoes secrets. `bootstrap` copies `.env.example``.env` only when absent and warns on missing optional keys (mock providers keep the stack runnable keyless). Real keys live only in gitignored `.ciagent/.env.secrets`, exported by `apps/ai-service/scripts/dev.sh`.
## Troubleshooting
| Symptom | Cause / fix |
|---------|-------------|
| `pnpm not found` in doctor | `corepack enable pnpm` (installs to ~/.local/bin — ensure PATH includes it) |
| doctor passes but verify fails on venv | re-run `nextcraft bootstrap` (venv/pip resolution is idempotent) |
| `port 8420 busy` in verify | stop the process on :8420 (`kill $(lsof -t -i:8420)`) or set `AI_PORT` |
| install.sh says "no binary assets yet" | release predates the binary pipeline (pre-v0.3.2); use source bootstrap |
| Binary silent after rename | fixed since v0.3.2 (SEA argv detection); re-download the latest release |
| Checksum mismatch on install | do NOT run the download; delete it and retry — report if it persists |
+72
View File
@@ -0,0 +1,72 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdtempSync, existsSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const cliDir = join(dirname(fileURLToPath(import.meta.url)), "..");
const repoRoot = join(cliDir, "..", "..");
test("fresh-clone E2E: clone -> doctor -> bootstrap -> verify (happy path, transcript)", async () => {
const work = mkdtempSync(join(tmpdir(), "nc-e2e-"));
const cloneDir = join(work, "nextcraft");
const clone = spawnSync(
"git",
["clone", "--quiet", "--no-hardlinks", repoRoot, cloneDir],
{ encoding: "utf8", timeout: 120000 },
);
assert.equal(clone.status, 0, `clone failed: ${clone.stderr}`);
const binary = join(repoRoot, "apps", "cli", "dist", "nextcraft-linux-x64");
const cli = existsSync(binary)
? binary
: join(cliDir, "node_modules", ".bin", "tsx");
const transcript: string[] = [];
const step = (name: string, args: string[], expect: number, timeoutMs: number) => {
const { NODE_TEST_CONTEXT, ...restEnv } = process.env as Record<string, string | undefined>;
const env = {
...restEnv,
PATH: [join(process.env.HOME ?? "/home", ".local/bin"), process.env.PATH].filter(Boolean).join(":"),
NEXTCRAFT_VERSION: "v-e2e",
} as Record<string, string | undefined>;
const run = spawnSync(cli, existsSync(binary) ? args : [join(cliDir, "src", "index.ts"), ...args], {
cwd: cloneDir,
encoding: "utf8",
timeout: timeoutMs,
env,
});
transcript.push(`$ nextcraft ${args.join(" ")} -> exit ${run.status}`);
if (run.status !== expect) {
transcript.push(run.stdout, run.stderr);
assert.fail(
`${name} exited ${run.status} (expected ${expect})\nTRANSCRIPT:\n${transcript.join("\n")}\nstderr: ${run.stderr}`,
);
}
return run.stdout + run.stderr;
};
const doctorOut = step("doctor", ["doctor"], 0, 60000);
assert.ok(doctorOut.includes("node"), "doctor mentions node");
assert.ok(doctorOut.includes("pnpm"), "doctor mentions pnpm");
step("bootstrap", ["bootstrap"], 0, 600000);
const verifyOut = step("verify", ["verify"], 0, 120000);
assert.ok(verifyOut.includes("venv"), "verify covers venv");
assert.ok(existsSync(join(cloneDir, "apps", "ai-service", ".env")), ".env created in clone");
assert.ok(existsSync(join(cloneDir, "apps", "ai-service", ".venv")), "venv created in clone");
assert.ok(
existsSync(join(cloneDir, "node_modules", ".pnpm")),
"workspace node_modules present",
);
const envExample = readFileSync(join(repoRoot, "apps", "ai-service", ".env.example"), "utf8");
const envClone = readFileSync(join(cloneDir, "apps", "ai-service", ".env"), "utf8");
assert.equal(envClone, envExample, ".env content matches template");
console.log("E2E TRANSCRIPT:\n" + transcript.join("\n"));
});