Skip to content

Commit 46bc484

Browse files
authored
feat: add config-driven scaffolds and managed feature updates (#10)
1 parent 34ed433 commit 46bc484

23 files changed

+1294
-19
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,27 @@ The format follows Keep a Changelog and the version numbers follow Semantic Vers
66

77
## [Unreleased]
88

9+
## [0.4.5] - 2026-03-31
10+
11+
### Added
12+
13+
- Added config-as-code support with `devforge init --config <path>` so saved scaffold plans can be replayed non-interactively.
14+
- Added `devforge init --save-config [path]` so interactive runs can emit a reusable `devforge.config.json` file for teams and future scaffolds.
15+
- Added config regression coverage that round-trips normalized plans through saved config files and verifies scaffold output stays deterministic.
16+
- Added `devforge add <feature>` for managed post-scaffold updates, with initial support for `testing`, `docker`, `github-actions`, and `ai-rules`.
17+
18+
### Changed
19+
20+
- Extended CLI help, generated project docs, and repository docs to explain the new config-driven workflow and how reusable scaffold plans fit alongside interactive prompts.
21+
- Routed config-driven runs through the same environment defaults and normalization flow as interactive runs so support-matrix rules stay consistent across both entrypoints.
22+
- Extended the packed npm-artifact smoke path so it now exercises `devforge add docker` after scaffold creation, verifying the shipped CLI can perform managed post-scaffold updates.
23+
24+
### Fixed
25+
26+
- Made post-scaffold feature application metadata-driven by reading `.devforge/project-plan.json`, which avoids stack guessing and keeps repeated `devforge add` runs idempotent.
27+
- Fixed `devforge init --save-config` with the default in-project config path so the saved config no longer makes the target directory look non-empty before generation.
28+
- Tightened `devforge add` guidance and managed-file updates so repeated runs avoid unnecessary churn and generated README content stays aligned with newly added managed features.
29+
930
## [0.4.4] - 2026-03-31
1031

1132
### Added

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,25 @@ Machine readiness check:
4545
npx --yes @ali-dev11/devforge@latest doctor
4646
```
4747

48+
Add managed features later:
49+
50+
```bash
51+
npx --yes @ali-dev11/devforge@latest add testing
52+
npx --yes @ali-dev11/devforge@latest add docker
53+
```
54+
4855
Plan-only preflight:
4956

5057
```bash
5158
npx --yes @ali-dev11/devforge@latest init --preflight-only
5259
```
5360

61+
Config-driven scaffold:
62+
63+
```bash
64+
npx --yes @ali-dev11/devforge@latest init --config ./devforge.config.json --output ./my-app
65+
```
66+
5467
## What The CLI Asks You
5568

5669
DevForge keeps core setup decisions required, and pushes the rest behind optional customization steps.
@@ -119,6 +132,21 @@ npm run runtime:matrix -- --scenario backend-hono --scenario cli-tool
119132
- `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.
120133
- `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.
121134

135+
## Config As Code
136+
137+
- `devforge init --save-config` saves the resolved scaffold plan as `devforge.config.json` in the generated project by default.
138+
- `devforge init --save-config ./configs/web.json` saves the normalized plan to a custom location.
139+
- `devforge init --config ./devforge.config.json --output ./my-app` replays a saved scaffold non-interactively.
140+
- `--output` and `--name` can still override the saved config at runtime, so the same config file stays reusable across multiple projects.
141+
142+
## Add Features Later
143+
144+
- `devforge add testing` enables the scaffold's recommended test runner for the saved stack and writes the matching test config and starter tests.
145+
- `devforge add docker` adds the generated `Dockerfile` and `.dockerignore` for the current DevForge project.
146+
- `devforge add github-actions` adds the generated CI workflow for the current project stack.
147+
- `devforge add ai-rules` restores `AGENTS.md`, `.cursor`, `.claude`, and the AI rule source docs when those were skipped initially.
148+
- `devforge add` works only inside DevForge-generated projects because it reads `.devforge/project-plan.json` to update managed files safely.
149+
122150
## Repository Docs
123151

124152
- [Documentation Site](https://ali-dev11.github.io/devforge/)

docs/changelog.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,27 @@ Track what changed in DevForge CLI across releases, including scaffolding behavi
99
- [GitHub Releases](https://github.com/Ali-dev11/devforge/releases)
1010
- [Repository Changelog](https://github.com/Ali-dev11/devforge/blob/main/CHANGELOG.md)
1111

12+
## [0.4.5] - 2026-03-31
13+
14+
### Added
15+
16+
- Added config-as-code support with `devforge init --config <path>` so saved scaffold plans can be replayed non-interactively.
17+
- Added `devforge init --save-config [path]` so interactive runs can emit a reusable `devforge.config.json` file for teams and future scaffolds.
18+
- Added config regression coverage that round-trips normalized plans through saved config files and verifies scaffold output stays deterministic.
19+
- Added `devforge add <feature>` for managed post-scaffold updates, with initial support for `testing`, `docker`, `github-actions`, and `ai-rules`.
20+
21+
### Changed
22+
23+
- Extended CLI help, generated project docs, and repository docs to explain the new config-driven workflow and how reusable scaffold plans fit alongside interactive prompts.
24+
- Routed config-driven runs through the same environment defaults and normalization flow as interactive runs so support-matrix rules stay consistent across both entrypoints.
25+
- Extended the packed npm-artifact smoke path so it now exercises `devforge add docker` after scaffold creation, verifying the shipped CLI can perform managed post-scaffold updates.
26+
27+
### Fixed
28+
29+
- Made post-scaffold feature application metadata-driven by reading `.devforge/project-plan.json`, which avoids stack guessing and keeps repeated `devforge add` runs idempotent.
30+
- Fixed `devforge init --save-config` with the default in-project config path so the saved config no longer makes the target directory look non-empty before generation.
31+
- Tightened `devforge add` guidance and managed-file updates so repeated runs avoid unnecessary churn and generated README content stays aligned with newly added managed features.
32+
1233
## [0.4.4] - 2026-03-31
1334

1435
### Added

docs/development.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ npm run check
1818
npx --yes @ali-dev11/devforge@latest doctor
1919
```
2020

21+
## Config-As-Code Workflow
22+
23+
```bash
24+
npx --yes @ali-dev11/devforge@latest init --yes --save-config
25+
npx --yes @ali-dev11/devforge@latest init --config ./devforge.config.json --output ./my-app
26+
npx --yes @ali-dev11/devforge@latest add testing
27+
```
28+
29+
- `--save-config` writes the normalized project plan to `devforge.config.json` so the same scaffold can be replayed later.
30+
- `--config` runs the generator non-interactively from a saved plan while still letting `--output` or `--name` override the destination.
31+
- Config-driven runs pass through the same normalization and compatibility checks as interactive runs, so invalid hand-written combinations are still corrected or rejected with guidance.
32+
- `devforge add <feature>` updates an existing DevForge project by reading `.devforge/project-plan.json` and rewriting only the managed files for that feature.
33+
2134
## Repository Commands
2235

2336
```bash
@@ -51,6 +64,9 @@ npm run runtime:matrix -- --scenario backend-hono --scenario cli-tool
5164

5265
- Prefer `npm run smoke` for quick CLI sanity checks.
5366
- Run `npx --yes @ali-dev11/devforge@latest init --preflight-only` when you want stack-aware readiness checks without writing a new project yet.
67+
- Use `npx --yes @ali-dev11/devforge@latest init --save-config` when you want an interactive run to become a reusable team preset later.
68+
- Use `npx --yes @ali-dev11/devforge@latest init --config ./devforge.config.json --output ./my-app` when validating reproducible scaffold output.
69+
- Use `npx --yes @ali-dev11/devforge@latest add testing`, `add docker`, `add github-actions`, or `add ai-rules` when validating managed post-scaffold updates.
5470
- Use `npm run runtime:matrix` when changing templates, prompts, package-manager behavior, or generated runtime surfaces.
5571
- Use `npm run smoke:packed` when changing the package entrypoints, published files, CLI dispatch, or install-time behavior.
5672
- If you touch microfrontend templates, validate the generated `dev` workflow, not just build output.

docs/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ DevForge CLI turns project intent into a runnable JavaScript or TypeScript repos
3535
- `npx --yes @ali-dev11/devforge@latest`: run DevForge without a global install.
3636
- `npx --yes @ali-dev11/devforge@latest doctor`: inspect local machine readiness before generating a scaffold.
3737
- `npx --yes @ali-dev11/devforge@latest init --preflight-only`: run stack-aware checks for the chosen plan without writing files yet.
38+
- `npx --yes @ali-dev11/devforge@latest init --save-config`: save the resolved scaffold plan as `devforge.config.json` for reuse.
39+
- `npx --yes @ali-dev11/devforge@latest init --config ./devforge.config.json --output ./my-app`: regenerate a saved scaffold non-interactively in a new location.
40+
- `npx --yes @ali-dev11/devforge@latest add testing`: add the recommended managed testing setup to an existing DevForge project.
41+
- `npx --yes @ali-dev11/devforge@latest add docker`: add the generated Docker assets to an existing DevForge project.
42+
- `npx --yes @ali-dev11/devforge@latest add github-actions`: add the generated CI workflow to an existing DevForge project.
43+
- `npx --yes @ali-dev11/devforge@latest add ai-rules`: add the managed AI rule files back into an existing DevForge project.
3844
- `Project prompts`: use the [Prompt Reference](./prompts.md) when you want to know what a question changes before answering it.
3945
- `npm run check`: validate the DevForge repository itself before pushing changes.
4046
- `npm run smoke`: verify a non-interactive scaffold path.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ali-dev11/devforge",
3-
"version": "0.4.4",
3+
"version": "0.4.5",
44
"description": "Production-focused AI-native project scaffolding CLI for JavaScript and TypeScript teams.",
55
"license": "MIT",
66
"author": "Ali-dev11",

scripts/smoke-packed.mjs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mkdirSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
1+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
22
import { tmpdir } from "node:os";
33
import { join, resolve } from "node:path";
44
import { spawnSync } from "node:child_process";
@@ -70,12 +70,31 @@ try {
7070
resolve(installDir, "node_modules/@ali-dev11/devforge/dist/bin/devforge.js"),
7171
"init",
7272
"--yes",
73+
"--save-config",
7374
"--skip-install",
7475
"--output",
7576
outputDir,
7677
],
7778
rootDir,
7879
);
80+
81+
run(
82+
"node",
83+
[
84+
resolve(installDir, "node_modules/@ali-dev11/devforge/dist/bin/devforge.js"),
85+
"add",
86+
"docker",
87+
],
88+
outputDir,
89+
);
90+
91+
if (!existsSync(resolve(outputDir, "devforge.config.json"))) {
92+
throw new Error("Packed smoke run did not create devforge.config.json during `devforge init --save-config`.");
93+
}
94+
95+
if (!existsSync(resolve(outputDir, "Dockerfile"))) {
96+
throw new Error("Packed smoke run did not create Dockerfile after `devforge add docker`.");
97+
}
7998
} finally {
8099
rmSync(workspace, { recursive: true, force: true });
81100
}

src/cli.ts

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { CliOptions } from "./types.js";
2+
import { runAddCommand } from "./commands/add.js";
23
import { runDoctorCommand } from "./commands/doctor.js";
34
import { runInitCommand } from "./commands/init.js";
45
import { DEVFORGE_VERSION } from "./version.js";
@@ -13,18 +14,36 @@ function readFlagValue(flag: string, args: string[]): string {
1314
return value;
1415
}
1516

17+
function readOptionalFlagValue(args: string[]): string | undefined {
18+
const value = args[0];
19+
20+
if (!value || value.startsWith("-")) {
21+
return undefined;
22+
}
23+
24+
args.shift();
25+
return value;
26+
}
27+
1628
function assertInitOnlyFlag(currentCommand: CliOptions["command"], flag: string): void {
1729
if (currentCommand !== "init") {
1830
throw new Error(`${flag} can only be used with \`devforge init\`.`);
1931
}
2032
}
2133

34+
function assertInitOrAddFlag(currentCommand: CliOptions["command"], flag: string): void {
35+
if (currentCommand !== "init" && currentCommand !== "add") {
36+
throw new Error(`${flag} can only be used with \`devforge init\` or \`devforge add\`.`);
37+
}
38+
}
39+
2240
export function parseArgs(argv: string[]): CliOptions {
2341
const options: CliOptions = {
2442
command: "init",
2543
resume: false,
2644
skipInstall: false,
2745
preflightOnly: false,
46+
saveConfig: false,
2847
yes: false,
2948
};
3049

@@ -44,8 +63,21 @@ export function parseArgs(argv: string[]): CliOptions {
4463
if (firstArg === "doctor") {
4564
options.command = "doctor";
4665
args.shift();
47-
} else
48-
if (firstArg === "init") {
66+
} else if (firstArg === "add") {
67+
options.command = "add";
68+
args.shift();
69+
if (args[0] === "--help" || args[0] === "-h") {
70+
options.command = "help";
71+
return options;
72+
}
73+
const feature = args.shift();
74+
75+
if (!feature || feature.startsWith("-")) {
76+
throw new Error("Missing feature for `devforge add`. Expected one of: testing, docker, github-actions, ai-rules.");
77+
}
78+
79+
options.addFeature = feature as CliOptions["addFeature"];
80+
} else if (firstArg === "init") {
4981
args.shift();
5082
} else if (firstArg && !firstArg.startsWith("-")) {
5183
throw new Error(`Unknown command: ${firstArg}`);
@@ -68,13 +100,22 @@ export function parseArgs(argv: string[]): CliOptions {
68100
options.command = "version";
69101
return options;
70102
case "--skip-install":
71-
assertInitOnlyFlag(options.command, "--skip-install");
103+
assertInitOrAddFlag(options.command, "--skip-install");
72104
options.skipInstall = true;
73105
break;
74106
case "--preflight-only":
75107
assertInitOnlyFlag(options.command, "--preflight-only");
76108
options.preflightOnly = true;
77109
break;
110+
case "--config":
111+
assertInitOnlyFlag(options.command, "--config");
112+
options.configPath = readFlagValue("--config", args);
113+
break;
114+
case "--save-config":
115+
assertInitOnlyFlag(options.command, "--save-config");
116+
options.saveConfig = true;
117+
options.saveConfigPath = readOptionalFlagValue(args);
118+
break;
78119
case "--yes":
79120
case "-y":
80121
assertInitOnlyFlag(options.command, "--yes");
@@ -97,6 +138,10 @@ export function parseArgs(argv: string[]): CliOptions {
97138
}
98139
}
99140

141+
if (options.resume && options.configPath) {
142+
throw new Error("`--resume` cannot be used together with `--config`.");
143+
}
144+
100145
return options;
101146
}
102147

@@ -107,17 +152,22 @@ Usage:
107152
devforge
108153
devforge init
109154
devforge init --resume
155+
devforge init --config ./devforge.config.json
156+
devforge add testing
110157
devforge doctor
111158
112159
Commands:
113160
init Start a new scaffold session
161+
add Add a managed feature to an existing DevForge project
114162
doctor Inspect local machine readiness for DevForge scaffolds
115163
help Show command help
116164
version Print the current CLI version
117165
118166
Flags:
119167
--resume Resume the last saved init session
120-
--skip-install Generate files without installing dependencies
168+
--config <path> Load a saved DevForge config and run non-interactively
169+
--save-config Save the resolved scaffold plan as devforge.config.json
170+
--skip-install Generate or update files without installing dependencies
121171
--preflight-only Stop after printing stack-aware readiness checks
122172
--yes, -y Use defaults without prompts
123173
--output <dir> Write the generated project to a custom directory
@@ -128,7 +178,11 @@ Flags:
128178
Examples:
129179
npx @ali-dev11/devforge@latest
130180
npx @ali-dev11/devforge@latest doctor
181+
npx @ali-dev11/devforge@latest init --config ./devforge.config.json --output ./my-app
182+
npx @ali-dev11/devforge@latest init --yes --save-config
183+
npx @ali-dev11/devforge@latest add docker
131184
npx @ali-dev11/devforge@latest init --yes --skip-install --output ./my-app
185+
devforge add testing --skip-install
132186
devforge init --preflight-only
133187
devforge init --resume
134188
`);
@@ -156,5 +210,10 @@ export async function runCli(argv = process.argv.slice(2)): Promise<void> {
156210
return;
157211
}
158212

213+
if (options.command === "add") {
214+
await runAddCommand(options);
215+
return;
216+
}
217+
159218
await runInitCommand(options);
160219
}

0 commit comments

Comments
 (0)