@hwy-fm/cli
v0.1.2
Published
Build toolchain and CLI for `@hwy-fm` applications — project scaffolding, dev server with HMR, production builds, and interactive REPL.
Downloads
1,121
Maintainers
Readme
@hwy-fm/cli
Build toolchain and CLI for @hwy-fm applications — project scaffolding, dev server with HMR, production builds, and interactive REPL.
Quick Start
npm install -g @hwy-fm/cli
hwy create my-app
cd my-app && npm install && hwy startCommands
| Command | Description |
|---------|-------------|
| hwy create <name> --template <url> | Scaffold a new project from a git template |
| hwy start | Start dev server with hot module replacement |
| hwy build | Production build + generate deployment package.json |
| hwy bundle | Production bundle only (no deployment package.json) |
| hwy clean | Remove build output directories |
| hwy repl | Interactive REPL for script-mode commands |
| hwy version | Print CLI version |
| hwy help | Print usage and available commands |
Global Options
| Option | Description |
|--------|-------------|
| --project <path> | Project root directory (default: cwd) |
| --env <name> | Target environment (e.g. production, staging) |
| --verbose | Enable detailed logging |
| --debug | Enable pipeline tracing and topology output |
| --bundler <name> | Bundler to use (default: webpack) |
| --no_clean | Skip output directory cleanup before build/bundle |
Project Configuration
Create a project.config.ts (or .js, .json) at the project root:
import { defineConfig } from '@hwy-fm/cli';
export default defineConfig({
root: '.',
source: 'src',
output: 'dist',
client: {
entry: 'app/main.ts',
index: 'index.html',
port: 4200,
proxy: [
{ host: 'http://localhost:3000', proxyApi: '/api' },
],
},
server: {
entry: 'server/main.ts',
},
env: {
production: {
sourceMap: false,
client: { publicPath: '/cdn/' },
},
},
});Shared Options
| Option | Default | Description |
|--------|---------|-------------|
| root | '.' | Project root directory (relative to cwd) |
| source | 'src' | Source directory relative to root |
| output | 'dist' | Output directory relative to root |
| nodeModules | 'node_modules' | node_modules path relative to root |
| tsConfig | 'tsconfig.json' | TypeScript config path |
| sourceMap | false | Enable source maps |
| resolveAlias | — | Module resolve aliases, e.g. { '@app': 'src/app' } |
| define | — | Compile-time constants injected via DefinePlugin |
| minimize | true | Minify production output |
Platforms
The CLI builds up to four platforms in a single project. Set any platform to false to disable it.
| Platform | Config Key | Description |
|----------|------------|-------------|
| Client | client | Browser bundle — entry, HTML template, styles, assets |
| Server | server | Node.js SSR bundle — supports hot reload |
| Server Entry | serverEntry | SSR entry for client-side hydration |
| DLL | dll | Vendor pre-bundle — shared dependencies |
Client
client: {
entry: 'app/main.ts',
index: 'index.html',
publicPath: '/',
port: 3000,
assets: ['assets/'],
styles: ['styles/global.scss'],
themeVariable: 'styles/variables.scss',
browserTarget: '> 1%, last 4 versions',
proxy: { '/api': 'http://localhost:4000' },
templateContent: (params) => `<!DOCTYPE html>...`,
styleLoaderOptions: { attributes: { nonce: 'abc' } },
}Server
server: {
entry: 'server/main.ts',
outputFormat: 'cjs', // 'cjs' | 'esm'
nodeExternals: true, // keep node_modules out of bundle
nodeExternalsAllowlist: [], // packages to include despite nodeExternals
hotContext: 'server.context', // hot-reload context module
watchFile: ['config/'], // extra paths to watch for restart
buildAssets(platforms, readSource) {
const client = platforms['client'];
return {
scripts: client.entrypoints.main?.js || [],
inlineStyles: (client.entrypoints.main?.css || []).map(readSource),
};
},
}DLL
dll: {
entry: {
vendor: ['react', 'react-dom'],
charts: ['echarts'], // built after vendor
shared: ['./src/shared/index.ts'],
},
}Environment Overrides
env: {
production: {
sourceMap: false,
minimize: true,
client: { publicPath: '/cdn/' },
configureWebpack: (config, { platform, mode }) => { /* ... */ },
},
staging: {
client: { publicPath: '/staging/' },
},
}Merge strategy: extends → base → env.development (if start) → env[targetEnv]
Config Extension
// project.config.ts
export default defineConfig({
extends: './base.config.ts',
client: { port: 4200 },
});configureWebpack
Modify the final webpack configuration for every platform:
export default defineConfig({
configureWebpack(config, { platform, mode }) {
if (platform === 'client') {
config.devtool = 'eval-source-map';
}
// return void to mutate in-place, or return a new object to replace
},
});Plugin System
Custom Commands
Register additional CLI commands via plugins:
// plugins/info.ts
import { defineCmd } from '@hwy-fm/cli';
defineCmd({
name: 'info',
description: 'Print project information',
options: [
{ flag: '--all, -a', desc: 'Show all details' },
],
execute(ctx) {
console.log('Project info...');
ctx.result = { exitCode: 0 };
},
});Load plugins in your config:
export default defineConfig({
plugins: [
'./plugins/info.ts',
'@my-scope/cli-plugin-lint',
],
});Custom Bundler
Replace or add a bundler strategy:
import { defineBundlerStrategy } from '@hwy-fm/cli';
import { KernelModel } from '@hwy-fm/kernel';
const MyBundlerModel = KernelModel();
// Register seeds and instructions on MyBundlerModel for 'build' and 'start'...
defineBundlerStrategy('my-bundler', MyBundlerModel);Then use it: hwy build --bundler my-bundler
Custom Webpack Rules
Extend webpack's module rule pipeline:
import { defineRuleStrategy } from '@hwy-fm/cli/webpack';
defineRuleStrategy((context) => ({
test: /\.svg$/,
type: 'asset/resource',
}));Return a { rules, plugins } object when the strategy also needs plugins:
import { defineRuleStrategy } from '@hwy-fm/cli/webpack';
import { VueLoaderPlugin } from 'vue-loader';
defineRuleStrategy((context) => ({
rules: { test: /\.vue$/, loader: 'vue-loader' },
plugins: new VueLoaderPlugin(),
}));REPL
Two ways to enter interactive mode:
Direct REPL
hwy replEnters REPL mode — dispatches input to user-defined seeds (not build/start/bundle/clean):
hwy> exitScript REPL
hwy my-app.tsWhen the first argument is a .ts, .js, or .mjs file, the CLI:
- Loads the file (side-effect imports register seeds/instructions into the kernel)
- Automatically enters REPL in script mode, dispatching commands to the script's kernel
This lets you write a standalone executable file:
// my-app.ts
import { Kernel } from '@hwy-fm/kernel';
import '@hwy-fm/std';
@Kernel.Process.seed('hello')
class HelloSeed {
execute(ctx) {
console.log('Hello from script!');
}
}
@Kernel.Process.seed('greet')
class GreetSeed {
execute(ctx) {
const name = ctx.input.args?.[0] || 'World';
console.log(`Hi, ${name}!`);
}
}
@Kernel.bootstrap()
class App {}Run it:
hwy my-app.ts🧩 REPL [script] — type "exit" to quit
hwy> hello
Hello from script!
hwy> greet Alice
Hi, Alice!
hwy> exitREPL Features
| Feature | Example |
|---------|---------|
| Chain commands | hello && greet Alice |
| Pipe input | echo "hello" \| hwy repl |
| Clear screen | cls or clear |
| Exit | exit or quit |
Piped input mode (non-TTY) runs commands line by line and exits automatically.
Dev Server
hwy start compiles your code, starts a dev server, and hot-reloads on file changes.
Modes
| Mode | Activation | How it works |
|------|-----------|--------------|
| VM (default) | — | Runs compiled server code in vm.runInNewContext. Hot-reload is instant (no process restart). |
| Nodemon | --nodemon flag | Spawns a child process with tsx/ts-node. Restarts on file changes via chokidar. Supports --inspect debugging. |
Server-only mode
If your project has no client config, hwy start skips the Express dev server entirely — no port 3000, no proxy. The server runs standalone, and file-watch / hot-reload still works.
The hotReload() contract (VM mode)
In VM mode, the CLI injects a global hotReload function into your server code. Your server must call it once listening is ready:
const server = http.createServer(app);
server.listen(3001, () => {
hotReload({
hotHost: 'http://localhost:3001', // proxy target (optional)
hotReload: () => server.close(), // cleanup before next reload
});
});| Field | Required | Description |
|-------|----------|-------------|
| hotHost | No | The address to reverse-proxy from the dev server. Omit for non-HTTP protocols (WebSocket, TCP, gRPC). |
| hotReload | No | Cleanup callback — called before the next hot-reload cycle to release resources (close sockets, etc.). |
When hotHost is omitted, the dev server won't set up a reverse proxy — useful for protocols that don't need one.
Custom server context
Create a server.context.ts (or configure server.hotContext) to inject variables into the VM sandbox or replace the default execution entirely:
// server.context.ts
export default (config, fullConfig) => ({
// Extra globals available in VM sandbox
MY_CONFIG: { port: 3001 },
// Or replace VM execution with a custom handler:
// hotHandler: (config) => { ... return { hotHost, hotReload } }
});Architecture
The CLI is built on @hwy-fm/kernel + @hwy-fm/std — it uses the same pipeline architecture it helps you build:
argv
│
▼
Catch [ timing — measures build/bundle/clean/create time ]
│
▼
Guard [ load project config + plugins ]
│
▼
Check [ validate configs — platforms, entries, paths ]
│
▼
Process [ command seed — create / start / build / ... ]
│
▼
Deliver [ set process.exitCode ]
│
▼
Trace [ debug output — topology, traces ]Build commands (start, build, bundle, clean) dispatch into a separate bundler sub-kernel (e.g. webpack), maintaining physical isolation between CLI orchestration and bundler logic.
Public API
import {
defineConfig, // Type-safe project config helper
defineCmd, // Register a custom CLI command
CliKernel, // The CLI Kernel model instance
CLI_EXT_COMMANDS, // DI token for plugin commands
BUNDLER_STRATEGY, // DI token for bundler strategies
defineBundlerStrategy, // Register a custom bundler
} from '@hwy-fm/cli';
import {
defineRuleStrategy, // Register a custom webpack rule
RULE_STRATEGY, // DI token for webpack rules
} from '@hwy-fm/cli/webpack';Related Packages
| Package | Description |
|---------|-------------|
| @hwy-fm/kernel | The pipeline engine powering the CLI |
| @hwy-fm/std | Pre-configured Slots + Gateway |
| @hwy-fm/di | Dependency injection |
License
MIT © hwyn
