Skip to content
chamixPublic

About

Stack-agnostic scaffolding CLI with a plugin architecture — bootstrap clean, tested project skeletons (starting with Electron + TypeScript) without reinventing templating tools.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

stackfold

Stack-agnostic scaffolding CLI with a plugin-based generator system.

Status

Three plugins implemented and tested:

  • electron-ts — Electron + TypeScript desktop app shell (sandboxed windows, external-link policy, narrow preload bridge), with optional Settings (Dark Mode), Skins, and About/Help features.
  • web-app-spa — client-side app (React, Vue, or vanilla + Vite) that calls APIs over fetch; no SSR.
  • web-app-standalone-html — a single, dependency-free HTML file — no server, no network call after the initial load.

Why

Built to stop re-solving the same app shell from scratch for each new personal project (starting from the md-view / md-presenter lineage for electron-ts), but designed stack-agnostic from day one: the CLI core doesn't know about Electron, React, Vue, or plain HTML — each lives entirely in its own plugin. Adding support for another stack later means writing another plugin, not touching the core.

Architecture

Clean-architecture-ish layering, dependencies point inward:

src/
  core/       domain types + pure orchestration logic (no I/O)
  plugins/    one folder per supported stack, each implementing the
              plugin contract (electron-ts, web-app-spa,
              web-app-standalone-html)
  infra/      adapters: file system, git (execa), prompts (@inquirer),
              plugin discovery (cosmiconfig), expression evaluation
              (json-logic-js)
  cli/        composition root — commander wiring, entry point

core/ has zero dependencies on infra/ or plugins/; it only depends on its own types. cli/ is the only layer allowed to wire concrete adapters into the orchestrator.

Four ports define the boundary between core/ and infra/: Prompter, FileWriter, ProcessRunner, and ExpressionEvaluator. The last resolves an expression-shaped value (e.g. { "var": "appName" }) against an answers context; its sole adapter, infra/expressionEvaluator.ts, is a thin wrapper over json-logic-js — a restricted, non-Turing-complete rule format, never an eval.

Declarative scaffold authoring (internal, experimental)

core/authoring.ts adds a second way to produce a StackfoldPlugin: a ScaffoldDefinition — plain JSON describing questions, an ordered list of ActionSteps, and optional lifecycle hooks (preInit, postWrite, postInit) — compiles via compileScaffold() into a plugin indistinguishable at runtime from a hand-written one.

Built-in action types: mkdir, writeFile, runCommand, npmInstall, gitInit (copyFile was removed — it was identical to writeFile with no real copy semantics). File content in writeFile is templated with Handlebars. New action types can be added via registerAction() without touching the orchestrator.

This is internal and experimental — none of the three plugins in Status use it yet; they remain hand-written via the questions()/buildBlueprint() contract, and there is no forced migration. The declarative authoring system is the source of truth going forward: ProjectBlueprint is just one possible outcome it produces. Readers who want the full rationale can look up ADR SK-ADR-002 in this project's governance docs (not included in this repository).

Stack

  • commander — CLI framework
  • @inquirer/prompts — interactive prompts
  • execa — process execution
  • cosmiconfig — plugin/config discovery
  • zod — validation
  • handlebars — file content templating in the declarative authoring system
  • json-logic-js — expression evaluation behind the ExpressionEvaluator port
  • TypeScript 6.x (deliberately not 7's native compiler yet — typescript-eslint support isn't there for it as of this writing)

Development

npm install
npm run dev -- init electron-ts   # run the CLI without building
npm run dev -- init web-app-spa
npm run dev -- init web-app-standalone-html
npm run build                     # tsc -> dist/
npm test                          # vitest
npm run typecheck                 # tsc --noEmit

init also runs non-interactively via --config <path> (answers loaded from a json/yaml/js file through cosmiconfig) and/or repeatable --answer key=value flags (--answer wins over --config on a shared key; neither flag given keeps the normal interactive prompts):

stackfold init electron-ts ./out --config config.json
stackfold init electron-ts ./out --answer appName="CI Smoke Test" --answer includeSettings=false

stackfold list shows every registered plugin, and stackfold info <stack> shows one plugin's detailed help (usage notes, prerequisites, the questions it will ask). A plugin's richer help (display name, details prose) is optional in the plugin contract — stackfold --help/-h and the two commands above work the same with or without it, falling back to the plugin's id and its mandatory one-line description:

stackfold list
stackfold info electron-ts

License

MIT — see LICENSE.

About

Stack-agnostic scaffolding CLI with a plugin architecture — bootstrap clean, tested project skeletons (starting with Electron + TypeScript) without reinventing templating tools.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages