waw is a minimal Node.js (≥ 24) platform that loads modules and either:
- executes a CLI command exposed by modules (
cli.js), or - boots the runtime by sequentially loading each module’s
index.js.
The core is intentionally small. It does not implement application frameworks, routing, databases, generators, or business logic — modules do.
- Tiny core — only module discovery, dependency install, ordering, CLI dispatch, runtime bootstrap
- Modules first — all behavior comes from modules
- Explicit over magic — behavior is defined by files (
module.json,cli.js,index.js) - Sync by default — dependency installation and module load preparation are deterministic
If a feature is not implemented by a module, it does not exist.
- Node.js ≥ 24
npmavailable inPATH
npm install -g wawproject/
├── config.json
├── server.json (optional)
├── server/ (default local modules folder; configurable)
│ ├── my-module/
│ │ ├── module.json
│ │ ├── index.js (optional runtime entry)
│ │ └── cli.js (optional CLI commands)On startup, waw reads config from (merged, later wins):
- Global
waw/config.json - Global
waw/server.json - Project
./config.json - Project
./server.json
Then it discovers modules:
-
Local modules: directories under
/<project>/<config.server>- If
config.serveris not set and aserver/folder exists, it uses"server".
- If
-
Global modules: declared in
config.modulesand fetched into the globally installed waw package underwaw/server/<name>.
Global module fetch behavior:
-
config.modulesis interpreted as a map of{ <moduleName>: <orgKey> }. -
Supported
orgKeyvalues are:waw→https://github.com/WebArtWork/waw-{NAME}.gititkp→git@github.com:IT-Kamianets/waw-{NAME}.git
-
If a required global module directory is missing, waw performs a destructive force sync of that repo into
waw/server/<name>.
Fallback behavior:
- If no modules are found at all, waw force-syncs and loads a global module named
corefrom thewaworg pattern.
Project-type auto modules:
-
If the project contains one of these marker files:
angular.json→ loads global moduleangularreact.json→ loads global modulereactvue.json→ loads global modulevuewjst.json→ loads global modulewjst
-
Each is fetched from the
waworg pattern if missing.
Note: In this repository, the loader contains only these mechanics. Any application/framework behavior must be implemented by modules.
Each module is a folder containing module.json.
Supported fields used by this loader include:
dependencies: an object of npm dependencies to install into the module directorybefore/after: ordering constraints (string or array; supports"*")priority: numeric priority used only when ordering constraints form a cycle
The loader normalizes and augments module metadata at load time:
m.__root/m.rootPath: absolute path to the module folderm.__name: directory name (module name)m.__global: boolean whether module is globalm.files: snapshot list of files under the module directory (excludingnode_modulesand.git)
Modules may declare npm dependencies inside module.json:
{
"dependencies": {
"express": "^4.19.0"
}
}Behavior:
-
Dependencies are installed synchronously during module loading.
-
Installation is performed into
<moduleRoot>/node_modules. -
waw installs all declared deps in a single
npm icall using:--no-save --no-package-lock --no-audit --no-fund --loglevel=error--legacy-peer-deps
-
The loader checks installed versions with a small semver matcher supporting:
- exact
x.y.z ^x.y.z,~x.y.z>=x.y.z*/latest
- exact
No package.json is created inside modules by the loader.
Modules may define ordering rules in module.json:
{ "before": "*" }{ "after": "core", "before": "*" }Rules:
before/afteraccept a string or an array"*"means “all modules except those explicitly constrained in the opposite direction”- Ordering is a stable topological sort based on discovery order
- If constraints produce a cycle, waw falls back to sorting by
priority(descending)
A module may provide CLI commands by including a file named exactly cli.js.
Example:
// cli.js
module.exports.love = function (waw) {
console.log("waw loves you :)");
process.exit(0);
};Run:
waw loveDispatch rules:
-
waw reads the raw command from
process.argv[2]. -
CLI files are scanned from the last module to the first (later modules override earlier ones).
-
Only files with base name
cli.jsare considered (not*.cli.js). -
Matching is attempted in this order:
- exact key match with raw command (
ex[cmdRaw]) - exact key match with lowercased command (
ex[cmd]) - case-insensitive scan of exported keys
- exact key match with raw command (
-
cli.jsmay export:-
an object of command functions, and/or
-
a function
(cmdRaw, waw) => boolean|void- If it returns
false, it is treated as “not handled” and waw continues scanning. - Any other return value (or no return) is treated as handled.
- If it returns
-
When a command is executed, the command receives a context object (see below) and the process is expected to exit if appropriate.
If no CLI command is handled, waw starts the runtime using nodemon.
Nodemon runs util.runtime.js and watches:
-
Project:
<project>/server<project>/angular.json<project>/react.json<project>/vue.json<project>/config.json
-
Global waw install:
<waw>/server<waw>/config.json
When runtime starts, waw sequentially loads each module’s index.js:
- If
<moduleRoot>/index.jsexists and exports a function, it is called asawait func(waw). - Errors are caught and printed; loading proceeds to the next module.
Every CLI command and runtime module receives a shared waw object containing:
argv— CLI args afterwaw(process.argv.slice(2))projectPath— current working directorymodulesPath— resolved modules directory in the project (defaults to<project>/server)wawPath— directory of the globally installedwawpackagewawModulesPath—<wawPath>/serverconfigPath/configServerPath— project config file pathswawConfigPath/wawConfigServerPath— global config file pathsconfig— merged config snapshot (global + project)projectType— one of:angular | react | vue | wjst | waw | ""
exists(...parts)isFile(...parts)isDir(...parts)ensureDir(...parts)rm(...parts)readText(path, fallback?)writeText(path, data)readJson(path, fallback?)writeJson(path, obj, pretty = true)readWrite(fromPath, toPath, replaceMap)
terminal— interactive CLI helper (ask/confirm/choose + spinner)git— git workflows utility (init, attach, forceSync, publish, etc.)
emit(event, ...args)on(event, fn)once(event, fn)off(event, fn)
When running CLI commands, waw injects these additional fields:
modules— ordered list of loaded modulesmodule— current module objectmodule_root— current module root pathmodule_config— current module config object (module.jsoncontent + loader metadata)
- Not a backend framework
- Not tied to Express/MongoDB/etc.
- Not a generator by default
- Not async-heavy
- Not opinionated
Those belong in modules.
Contributions are welcome:
- Improve the loader (keep it small)
- Improve documentation
- Create modules
- Report bugs
See CONTRIBUTING.md.
MIT © Web Art Work