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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
cache: npm
- run: npm ci
- run: npm run check
- run: npm run smoke:packed

generated-runtime-matrix:
name: generated-runtime-matrix (${{ matrix.group.name }})
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ The format follows Keep a Changelog and the version numbers follow Semantic Vers

## [Unreleased]

## [0.4.4] - 2026-03-31

### Added

- Added `devforge doctor`, a machine-level readiness command that inspects Node.js, package managers, Corepack, Bun, Playwright browser installs, Git, Docker, and SSH setup before scaffold generation.
- Added `devforge init --preflight-only` so users can run the same stack-aware readiness checks as the normal init flow without writing project files yet.
- Added a packed-tarball smoke script and CI gate so DevForge now validates the shipped npm artifact in addition to the source checkout.

### Changed

- Centralized machine-remediation commands into a shared module so `doctor`, preflight output, and runtime guidance all recommend the same OS-specific fix commands.
- Improved CLI help and repository docs to explain the new preflight workflow, `doctor`, and the difference between source smoke checks and packed-artifact smoke checks.

## [0.4.3] - 2026-03-27

### Fixed
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ npm install -g @ali-dev11/devforge
devforge
```

Machine readiness check:

```bash
npx --yes @ali-dev11/devforge@latest doctor
```

Plan-only preflight:

```bash
npx --yes @ali-dev11/devforge@latest init --preflight-only
```

## What The CLI Asks You

DevForge keeps core setup decisions required, and pushes the rest behind optional customization steps.
Expand Down Expand Up @@ -82,13 +94,15 @@ The full prompt-by-prompt guide is here:

```bash
npm install
npm run dev -- --help
npm run lint
npm run typecheck
npm run test
npm run build
npm run check
npm run docs:changelog
npm run smoke
npm run smoke:packed
npm run runtime:matrix -- --scenario backend-hono --scenario cli-tool
```

Expand All @@ -102,6 +116,7 @@ npm run runtime:matrix -- --scenario backend-hono --scenario cli-tool
- `npm run check` is the main contributor safety command because it combines linting, typechecking, tests, and build verification.
- `npm run docs:changelog` refreshes the GitHub Pages changelog page from `CHANGELOG.md`.
- `npm run smoke` verifies a non-interactive scaffold run end to end.
- `npm run smoke:packed` packs the actual npm tarball, installs it into a temp directory, and verifies the published artifact shape instead of only the source checkout.
- `npm run runtime:matrix -- --scenario ...` installs, builds, and verifies generated projects so the scaffold output is tested as a product, not just as source code.

## Repository Docs
Expand Down
13 changes: 13 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ Track what changed in DevForge CLI across releases, including scaffolding behavi
- [GitHub Releases](https://github.com/Ali-dev11/devforge/releases)
- [Repository Changelog](https://github.com/Ali-dev11/devforge/blob/main/CHANGELOG.md)

## [0.4.4] - 2026-03-31

### Added

- Added `devforge doctor`, a machine-level readiness command that inspects Node.js, package managers, Corepack, Bun, Playwright browser installs, Git, Docker, and SSH setup before scaffold generation.
- Added `devforge init --preflight-only` so users can run the same stack-aware readiness checks as the normal init flow without writing project files yet.
- Added a packed-tarball smoke script and CI gate so DevForge now validates the shipped npm artifact in addition to the source checkout.

### Changed

- Centralized machine-remediation commands into a shared module so `doctor`, preflight output, and runtime guidance all recommend the same OS-specific fix commands.
- Improved CLI help and repository docs to explain the new preflight workflow, `doctor`, and the difference between source smoke checks and packed-artifact smoke checks.

## [0.4.3] - 2026-03-27

### Fixed
Expand Down
6 changes: 6 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ title: Development
```bash
npm install
npm run check
npx --yes @ali-dev11/devforge@latest doctor
```

## Repository Commands
Expand All @@ -28,6 +29,7 @@ npm run build
npm run check
npm run docs:changelog
npm run smoke
npm run smoke:packed
npm run runtime:matrix -- --scenario backend-hono --scenario cli-tool
```

Expand All @@ -39,14 +41,18 @@ npm run runtime:matrix -- --scenario backend-hono --scenario cli-tool
- `npm run test` runs focused regression tests for prompting, normalization, generator output, changelog rendering, and runtime-matrix coverage.
- `npm run build` compiles the CLI to `dist/`, which mirrors what npm users receive.
- `npm run check` is the primary contributor gate because it runs lint, types, tests, and build verification together.
- `npx --yes @ali-dev11/devforge@latest doctor` checks the local machine for the tool and runtime prerequisites that commonly break first-run scaffolds.
- `npm run docs:changelog` keeps the GitHub Pages changelog synchronized with `CHANGELOG.md`.
- `npm run smoke` verifies a fast end-to-end scaffold run without interactive prompts.
- `npm run smoke:packed` verifies the built npm tarball by installing the packed artifact into a temp directory and running the shipped CLI from there.
- `npm run runtime:matrix -- --scenario ...` validates generated projects as products by installing, building, and checking runtime behavior for representative stacks.

## Working On Generated Scaffolds

- Prefer `npm run smoke` for quick CLI sanity checks.
- Run `npx --yes @ali-dev11/devforge@latest init --preflight-only` when you want stack-aware readiness checks without writing a new project yet.
- Use `npm run runtime:matrix` when changing templates, prompts, package-manager behavior, or generated runtime surfaces.
- Use `npm run smoke:packed` when changing the package entrypoints, published files, CLI dispatch, or install-time behavior.
- If you touch microfrontend templates, validate the generated `dev` workflow, not just build output.
- If you touch docs or release notes, rerun `npm run docs:changelog`.

Expand Down
3 changes: 3 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ DevForge CLI turns project intent into a runnable JavaScript or TypeScript repos
## Command Reference

- `npx --yes @ali-dev11/devforge@latest`: run DevForge without a global install.
- `npx --yes @ali-dev11/devforge@latest doctor`: inspect local machine readiness before generating a scaffold.
- `npx --yes @ali-dev11/devforge@latest init --preflight-only`: run stack-aware checks for the chosen plan without writing files yet.
- `Project prompts`: use the [Prompt Reference](./prompts.md) when you want to know what a question changes before answering it.
- `npm run check`: validate the DevForge repository itself before pushing changes.
- `npm run smoke`: verify a non-interactive scaffold path.
- `npm run smoke:packed`: validate the built npm tarball instead of only the source tree.
- `npm run runtime:matrix -- --scenario ...`: validate generated installs, builds, and runtime behavior for representative stacks.
- `npm run docs:changelog`: refresh the GitHub Pages changelog page from the main changelog file.

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ali-dev11/devforge",
"version": "0.4.3",
"version": "0.4.4",
"description": "Production-focused AI-native project scaffolding CLI for JavaScript and TypeScript teams.",
"license": "MIT",
"author": "Ali-dev11",
Expand Down Expand Up @@ -64,7 +64,8 @@
"pretypecheck": "npm run prepare:metadata",
"version": "npm run prepare:metadata && git add src/generated/package-metadata.ts",
"prepack": "npm run check",
"smoke": "npm run build && node --eval \"require('node:fs').rmSync('/tmp/devforge-smoke', { recursive: true, force: true })\" && node dist/bin/devforge.js init --yes --skip-install --output /tmp/devforge-smoke"
"smoke": "npm run build && node --eval \"require('node:fs').rmSync('/tmp/devforge-smoke', { recursive: true, force: true })\" && node dist/bin/devforge.js init --yes --skip-install --output /tmp/devforge-smoke",
"smoke:packed": "node scripts/smoke-packed.mjs"
},
"engines": {
"node": ">=20.0.0"
Expand Down
81 changes: 81 additions & 0 deletions scripts/smoke-packed.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { mkdirSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { spawnSync } from "node:child_process";
import process from "node:process";

function run(command, args, cwd, extraEnv = {}) {
const result = spawnSync(command, args, {
cwd,
encoding: "utf8",
stdio: "inherit",
env: {
...process.env,
...extraEnv,
},
});

if (result.status !== 0) {
throw new Error(`${command} ${args.join(" ")} failed with exit code ${result.status ?? "unknown"}`);
}
}

const rootDir = process.cwd();
const workspace = mkdtempSync(join(tmpdir(), "devforge-packed-smoke-"));
const packDir = join(workspace, "pack");
const installDir = join(workspace, "install");
const outputDir = join(workspace, "output");
const npmCacheDir = join(workspace, "npm-cache");

mkdirSync(packDir, { recursive: true });
mkdirSync(installDir, { recursive: true });

try {
run(
"npm",
["pack", "--ignore-scripts", "--pack-destination", packDir],
rootDir,
{
npm_config_cache: npmCacheDir,
},
);

const tarball = readdirSync(packDir)
.find((entry) => entry.endsWith(".tgz"));

if (!tarball) {
throw new Error("Could not find the packed DevForge tarball.");
}

run(
"npm",
[
"install",
"--ignore-scripts",
"--no-audit",
"--no-fund",
"--prefix",
installDir,
resolve(packDir, tarball),
],
rootDir,
{
npm_config_cache: npmCacheDir,
},
);

run(
"node",
[
resolve(installDir, "node_modules/@ali-dev11/devforge/dist/bin/devforge.js"),
"init",
"--yes",
"--skip-install",
"--output",
outputDir,
],
rootDir,
);
} finally {
rmSync(workspace, { recursive: true, force: true });
}
33 changes: 32 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CliOptions } from "./types.js";
import { runDoctorCommand } from "./commands/doctor.js";
import { runInitCommand } from "./commands/init.js";
import { DEVFORGE_VERSION } from "./version.js";

Expand All @@ -12,11 +13,18 @@ function readFlagValue(flag: string, args: string[]): string {
return value;
}

function parseArgs(argv: string[]): CliOptions {
function assertInitOnlyFlag(currentCommand: CliOptions["command"], flag: string): void {
if (currentCommand !== "init") {
throw new Error(`${flag} can only be used with \`devforge init\`.`);
}
}

export function parseArgs(argv: string[]): CliOptions {
const options: CliOptions = {
command: "init",
resume: false,
skipInstall: false,
preflightOnly: false,
yes: false,
};

Expand All @@ -33,6 +41,10 @@ function parseArgs(argv: string[]): CliOptions {
return options;
}

if (firstArg === "doctor") {
options.command = "doctor";
args.shift();
} else
if (firstArg === "init") {
args.shift();
} else if (firstArg && !firstArg.startsWith("-")) {
Expand All @@ -44,6 +56,7 @@ function parseArgs(argv: string[]): CliOptions {

switch (current) {
case "--resume":
assertInitOnlyFlag(options.command, "--resume");
options.resume = true;
break;
case "--help":
Expand All @@ -55,16 +68,24 @@ function parseArgs(argv: string[]): CliOptions {
options.command = "version";
return options;
case "--skip-install":
assertInitOnlyFlag(options.command, "--skip-install");
options.skipInstall = true;
break;
case "--preflight-only":
assertInitOnlyFlag(options.command, "--preflight-only");
options.preflightOnly = true;
break;
case "--yes":
case "-y":
assertInitOnlyFlag(options.command, "--yes");
options.yes = true;
break;
case "--output":
assertInitOnlyFlag(options.command, "--output");
options.outputDir = readFlagValue("--output", args);
break;
case "--name":
assertInitOnlyFlag(options.command, "--name");
options.projectName = readFlagValue("--name", args);
break;
default:
Expand All @@ -86,15 +107,18 @@ Usage:
devforge
devforge init
devforge init --resume
devforge doctor

Commands:
init Start a new scaffold session
doctor Inspect local machine readiness for DevForge scaffolds
help Show command help
version Print the current CLI version

Flags:
--resume Resume the last saved init session
--skip-install Generate files without installing dependencies
--preflight-only Stop after printing stack-aware readiness checks
--yes, -y Use defaults without prompts
--output <dir> Write the generated project to a custom directory
--name <name> Override the generated project name
Expand All @@ -103,7 +127,9 @@ Flags:

Examples:
npx @ali-dev11/devforge@latest
npx @ali-dev11/devforge@latest doctor
npx @ali-dev11/devforge@latest init --yes --skip-install --output ./my-app
devforge init --preflight-only
devforge init --resume
`);
}
Expand All @@ -125,5 +151,10 @@ export async function runCli(argv = process.argv.slice(2)): Promise<void> {
return;
}

if (options.command === "doctor") {
await runDoctorCommand();
return;
}

await runInitCommand(options);
}
23 changes: 23 additions & 0 deletions src/commands/doctor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { detectEnvironment } from "../engines/environment.js";
import { buildDoctorPreflightReport, printPreflightReport } from "../preflight.js";
import { banner, info, step, success, warn } from "../utils/logger.js";

export async function runDoctorCommand(): Promise<void> {
const environment = detectEnvironment();
const report = buildDoctorPreflightReport(environment);

banner("DevForge Doctor");
info(`Platform: ${environment.platform}/${environment.arch}`);
info(`Node.js: ${environment.nodeVersion}`);
step(`Package manager preference: ${environment.recommendedPackageManager}`);

printPreflightReport(report, { showHealthy: true });

if (report.hasBlockingIssues) {
warn("Doctor found blocking issues that should be resolved before scaffolding.");
process.exitCode = 1;
return;
}

success("\nDoctor completed. Your machine-level setup looks workable for DevForge, with any optional follow-ups listed above.");
}
Loading
Loading