waw
Version:
**waw** is a minimal Node.js (≥ 24) platform that loads **modules** and either:
316 lines (211 loc) • 8.75 kB
Markdown
# waw Framework
**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.
---
## Philosophy
- **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.
---
## Requirements
- **Node.js ≥ 24**
- `npm` available in `PATH`
---
## Installation
```sh
npm install -g waw
````
---
## Project structure (minimal)
```txt
project/
├── 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)
```
---
## How waw works
### 1) Configuration & module discovery
On startup, waw reads config from (merged, later wins):
1. Global `waw/config.json`
2. Global `waw/server.json`
3. Project `./config.json`
4. Project `./server.json`
Then it discovers modules:
* **Local modules**: directories under `/<project>/<config.server>`
* If `config.server` is not set and a `server/` folder exists, it uses `"server"`.
* **Global modules**: declared in `config.modules` and fetched into the globally installed waw package under `waw/server/<name>`.
Global module fetch behavior:
* `config.modules` is interpreted as a map of `{ <moduleName>: <orgKey> }`.
* Supported `orgKey` values are:
* `waw` → `https://github.com/WebArtWork/waw-{NAME}.git`
* `itkp` → `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 **`core`** from the `waw` org pattern.
Project-type auto modules:
* If the project contains one of these marker files:
* `angular.json` → loads global module `angular`
* `react.json` → loads global module `react`
* `vue.json` → loads global module `vue`
* `wjst.json` → loads global module `wjst`
* Each is fetched from the `waw` org pattern if missing.
> Note: In this repository, the loader contains only these mechanics. Any application/framework behavior must be implemented by modules.
---
### 2) Module definition (`module.json`)
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 directory
* `before` / `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 folder
* `m.__name`: directory name (module name)
* `m.__global`: boolean whether module is global
* `m.files`: snapshot list of files under the module directory (excluding `node_modules` and `.git`)
---
### 3) Dependencies
Modules may declare npm dependencies inside `module.json`:
```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 i` call 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`
No `package.json` is created inside modules by the loader.
---
### 4) Module ordering (`before` / `after`)
Modules may define ordering rules in `module.json`:
```json
{ "before": "*" }
```
```json
{ "after": "core", "before": "*" }
```
Rules:
* `before` / `after` accept 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)
---
### 5) CLI commands (`cli.js`)
A module may provide CLI commands by including a file named **exactly** `cli.js`.
Example:
```js
// cli.js
module.exports.love = function (waw) {
console.log("waw loves you :)");
process.exit(0);
};
```
Run:
```sh
waw love
```
Dispatch 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.js`** are considered (not `*.cli.js`).
* Matching is attempted in this order:
1. exact key match with raw command (`ex[cmdRaw]`)
2. exact key match with lowercased command (`ex[cmd]`)
3. case-insensitive scan of exported keys
* `cli.js` may 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.
When a command is executed, the command receives a context object (see below) and the process is expected to exit if appropriate.
---
### 6) Runtime (`index.js`)
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.js` exists and exports a function, it is called as `await func(waw)`.
* Errors are caught and printed; loading proceeds to the next module.
---
## The `waw` context
Every CLI command and runtime module receives a shared `waw` object containing:
### Core properties
* `argv` — CLI args after `waw` (`process.argv.slice(2)`)
* `projectPath` — current working directory
* `modulesPath` — resolved modules directory in the project (defaults to `<project>/server`)
* `wawPath` — directory of the globally installed `waw` package
* `wawModulesPath` — `<wawPath>/server`
* `configPath` / `configServerPath` — project config file paths
* `wawConfigPath` / `wawConfigServerPath` — global config file paths
* `config` — merged config snapshot (global + project)
* `projectType` — one of: `angular | react | vue | wjst | waw | ""`
### FS helpers
* `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)`
### Utilities
* `terminal` — interactive CLI helper (ask/confirm/choose + spinner)
* `git` — git workflows utility (init, attach, forceSync, publish, etc.)
### Events
* `emit(event, ...args)`
* `on(event, fn)`
* `once(event, fn)`
* `off(event, fn)`
### Extra fields injected by the loader
When running CLI commands, waw injects these additional fields:
* `modules` — ordered list of loaded modules
* `module` — current module object
* `module_root` — current module root path
* `module_config` — current module config object (`module.json` content + loader metadata)
---
## What waw is not
* Not a backend framework
* Not tied to Express/MongoDB/etc.
* Not a generator by default
* Not async-heavy
* Not opinionated
Those belong in modules.
---
## Contributing
Contributions are welcome:
* Improve the loader (keep it small)
* Improve documentation
* Create modules
* Report bugs
See `CONTRIBUTING.md`.
---
## License
MIT © Web Art Work