march-hare 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1
- package/dist/action/index.d.ts +2 -2
- package/dist/action/utils.d.ts +2 -2
- package/dist/actions/index.d.ts +2 -2
- package/dist/actions/types.d.ts +3 -3
- package/dist/actions/utils.d.ts +3 -3
- package/dist/app/index.d.ts +7 -7
- package/dist/app/types.d.ts +5 -5
- package/dist/boundary/components/broadcast/index.d.ts +2 -2
- package/dist/boundary/components/broadcast/types.d.ts +1 -1
- package/dist/boundary/components/consumer/components/partition/index.d.ts +1 -1
- package/dist/boundary/components/consumer/components/partition/types.d.ts +1 -1
- package/dist/boundary/components/consumer/index.d.ts +5 -5
- package/dist/boundary/components/consumer/types.d.ts +1 -1
- package/dist/boundary/components/consumer/utils.d.ts +1 -1
- package/dist/boundary/components/env/index.d.ts +3 -27
- package/dist/boundary/components/env/types.d.ts +24 -2
- package/dist/boundary/components/env/utils.d.ts +1 -1
- package/dist/boundary/components/scope/index.d.ts +2 -2
- package/dist/boundary/components/scope/types.d.ts +1 -1
- package/dist/boundary/components/scope/utils.d.ts +1 -1
- package/dist/boundary/components/sharing/index.d.ts +1 -1
- package/dist/boundary/components/tap/index.d.ts +3 -3
- package/dist/boundary/components/tap/types.d.ts +2 -2
- package/dist/boundary/components/tap/utils.d.ts +1 -1
- package/dist/boundary/components/tasks/index.d.ts +2 -2
- package/dist/boundary/components/tasks/utils.d.ts +1 -1
- package/dist/boundary/index.d.ts +1 -1
- package/dist/boundary/types.d.ts +2 -2
- package/dist/cache/index.d.ts +2 -2
- package/dist/cache/types.d.ts +1 -1
- package/dist/cli/bin/mh.js +10 -0
- package/dist/cli/lib/banner/index.js +14 -0
- package/dist/cli/lib/commands/app/index.js +37 -0
- package/dist/cli/lib/commands/feature/index.js +55 -0
- package/dist/cli/lib/commands/index.js +89 -0
- package/dist/cli/lib/commands/init/index.js +29 -0
- package/dist/cli/lib/commands/shared/index.js +56 -0
- package/dist/cli/lib/index.js +56 -0
- package/dist/cli/lib/parser/index.js +24 -0
- package/dist/cli/lib/prompt/index.js +61 -0
- package/dist/cli/lib/runner/index.js +46 -0
- package/dist/cli/lib/runner/types.js +1 -0
- package/dist/cli/lib/runner/utils.js +60 -0
- package/dist/cli/lib/types.js +1 -0
- package/dist/cli/lib/utils.js +20 -0
- package/dist/cli/templates/app/action/actions.ts.ejs.t +10 -0
- package/dist/cli/templates/app/action/types.ts.ejs.t +7 -0
- package/dist/cli/templates/app/integration/index.integration.tsx.ejs.t +13 -0
- package/dist/cli/templates/app/page/actions.ts.ejs.t +14 -0
- package/dist/cli/templates/app/page/index.tsx.ejs.t +20 -0
- package/dist/cli/templates/app/page/styles.ts.ejs.t +35 -0
- package/dist/cli/templates/app/page/types.ts.ejs.t +12 -0
- package/dist/cli/templates/feature/action/actions.ts.ejs.t +10 -0
- package/dist/cli/templates/feature/action/types.ts.ejs.t +7 -0
- package/dist/cli/templates/feature/multicast/types.ts.ejs.t +7 -0
- package/dist/cli/templates/feature/presentational/index.tsx.ejs.t +14 -0
- package/dist/cli/templates/feature/presentational/types.ts.ejs.t +12 -0
- package/dist/cli/templates/feature/presentational/utils.ts.ejs.t +8 -0
- package/dist/cli/templates/feature/stateful/actions.ts.ejs.t +16 -0
- package/dist/cli/templates/feature/stateful/index.tsx.ejs.t +19 -0
- package/dist/cli/templates/feature/stateful/types.ts.ejs.t +16 -0
- package/dist/cli/templates/feature/stateful/utils.ts.ejs.t +8 -0
- package/dist/cli/templates/feature/unit/index.test.tsx.ejs.t +21 -0
- package/dist/cli/templates/init/new/README.md.ejs.t +48 -0
- package/dist/cli/templates/init/new/eslint.config.js.ejs.t +88 -0
- package/dist/cli/templates/init/new/gitignore.ejs.t +9 -0
- package/dist/cli/templates/init/new/index.html.ejs.t +18 -0
- package/dist/cli/templates/init/new/package.json.ejs.t +54 -0
- package/dist/cli/templates/init/new/playwright.config.ts.ejs.t +17 -0
- package/dist/cli/templates/init/new/prettierrc.ejs.t +8 -0
- package/dist/cli/templates/init/new/src.app.index.tsx.ejs.t +14 -0
- package/dist/cli/templates/init/new/src.app.pages.home.actions.ts.ejs.t +16 -0
- package/dist/cli/templates/init/new/src.app.pages.home.index.tsx.ejs.t +30 -0
- package/dist/cli/templates/init/new/src.app.pages.home.integration.tsx.ejs.t +28 -0
- package/dist/cli/templates/init/new/src.app.pages.home.styles.ts.ejs.t +45 -0
- package/dist/cli/templates/init/new/src.app.pages.home.types.ts.ejs.t +12 -0
- package/dist/cli/templates/init/new/src.app.utils.ts.ejs.t +9 -0
- package/dist/cli/templates/init/new/src.features.greet.actions.ts.ejs.t +20 -0
- package/dist/cli/templates/init/new/src.features.greet.index.test.tsx.ejs.t +21 -0
- package/dist/cli/templates/init/new/src.features.greet.index.tsx.ejs.t +24 -0
- package/dist/cli/templates/init/new/src.features.greet.types.ts.ejs.t +18 -0
- package/dist/cli/templates/init/new/src.features.greet.utils.ts.ejs.t +8 -0
- package/dist/cli/templates/init/new/src.index.tsx.ejs.t +8 -0
- package/dist/cli/templates/init/new/src.shared.components.button.index.test.tsx.ejs.t +13 -0
- package/dist/cli/templates/init/new/src.shared.components.button.index.tsx.ejs.t +10 -0
- package/dist/cli/templates/init/new/src.shared.components.button.types.ts.ejs.t +6 -0
- package/dist/cli/templates/init/new/src.shared.resources.index.ts.ejs.t +4 -0
- package/dist/cli/templates/init/new/src.shared.theme.index.ts.ejs.t +51 -0
- package/dist/cli/templates/init/new/src.shared.types.index.ts.ejs.t +23 -0
- package/dist/cli/templates/init/new/src.test-setup.ts.ejs.t +10 -0
- package/dist/cli/templates/init/new/src.vite-env.d.ts.ejs.t +4 -0
- package/dist/cli/templates/init/new/tests.home.e2e.ts.ejs.t +14 -0
- package/dist/cli/templates/init/new/tsconfig.json.ejs.t +29 -0
- package/dist/cli/templates/init/new/vite.config.ts.ejs.t +17 -0
- package/dist/cli/templates/init/new/vitest.config.ts.ejs.t +24 -0
- package/dist/cli/templates/shared/component/index.tsx.ejs.t +9 -0
- package/dist/cli/templates/shared/component/types.ts.ejs.t +8 -0
- package/dist/cli/templates/shared/resource/index.ts.ejs.t +15 -0
- package/dist/cli/templates/shared/resource/types.ts.ejs.t +10 -0
- package/dist/cli/templates/shared/type-broadcast/types.ts.ejs.t +7 -0
- package/dist/cli/templates/shared/type-payload/types.ts.ejs.t +9 -0
- package/dist/cli/templates/shared/unit-component/index.test.tsx.ejs.t +13 -0
- package/dist/cli/templates/shared/unit-resource/index.test.ts.ejs.t +15 -0
- package/dist/cli/templates/shared/unit-util/index.test.ts.ejs.t +11 -0
- package/dist/cli/templates/shared/util/index.ts.ejs.t +6 -0
- package/dist/coalesce/index.d.ts +1 -1
- package/dist/context/index.d.ts +1 -1
- package/dist/error/types.d.ts +1 -1
- package/dist/error/utils.d.ts +1 -1
- package/dist/index.d.ts +16 -16
- package/dist/march-hare.js.map +1 -1
- package/dist/march-hare.umd.cjs.map +1 -1
- package/dist/resource/index.d.ts +4 -4
- package/dist/resource/types.d.ts +3 -3
- package/dist/resource/utils.d.ts +4 -4
- package/dist/scope/index.d.ts +3 -3
- package/dist/scope/types.d.ts +2 -2
- package/dist/scope/utils.d.ts +2 -2
- package/dist/shared/index.d.ts +4 -4
- package/dist/types/index.d.ts +5 -5
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/types.d.ts +1 -1
- package/dist/utils/utils.d.ts +1 -1
- package/dist/with/index.d.ts +3 -3
- package/dist/with/types.d.ts +2 -2
- package/dist/with/utils.d.ts +3 -3
- package/package.json +18 -4
- package/src/cli/README.md +314 -0
package/dist/resource/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ResourceHandle } from './types';
|
|
2
|
-
import { Cache } from './utils';
|
|
3
|
-
import { AppFetcher } from '../app/types';
|
|
4
|
-
export type { Coalesce, Fetcher, Invocation, ResourceHandle } from './types';
|
|
1
|
+
import { ResourceHandle } from './types.js';
|
|
2
|
+
import { Cache } from './utils.js';
|
|
3
|
+
import { AppFetcher } from '../app/types.js';
|
|
4
|
+
export type { Coalesce, Fetcher, Invocation, ResourceHandle } from './types.js';
|
|
5
5
|
/**
|
|
6
6
|
* Evicts cache entries across every Resource constructed in the
|
|
7
7
|
* current process. Resources register themselves on declaration, so
|
package/dist/resource/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Env } from '../boundary/components/env/
|
|
2
|
-
import { Cache } from '../cache/index';
|
|
3
|
-
import { BroadcastPayload, MulticastPayload, Filter } from '../types/index';
|
|
1
|
+
import { Env } from '../boundary/components/env/types.js';
|
|
2
|
+
import { Cache } from '../cache/index.js';
|
|
3
|
+
import { BroadcastPayload, MulticastPayload, Filter } from '../types/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* Dispatch surface exposed on a Resource fetcher's `context`. Restricted
|
|
6
6
|
* to broadcast and multicast actions — unicast targets the calling
|
package/dist/resource/utils.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Cache } from '../cache/index';
|
|
2
|
-
import { unset } from '../utils/utils';
|
|
3
|
-
import { Fetcher, ResourceEvictor, ResourceHandle } from './types';
|
|
4
|
-
export { Cache } from '../cache/index';
|
|
1
|
+
import { Cache } from '../cache/index.js';
|
|
2
|
+
import { unset } from '../utils/utils.js';
|
|
3
|
+
import { Fetcher, ResourceEvictor, ResourceHandle } from './types.js';
|
|
4
|
+
export { Cache } from '../cache/index.js';
|
|
5
5
|
/**
|
|
6
6
|
* Default in-memory `Cache` used when {@link Resource} is constructed
|
|
7
7
|
* without an explicit one. Each fetcher gets its own slot via the
|
package/dist/scope/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ScopeHandle } from './types';
|
|
2
|
-
export type { ScopeHandle } from './types';
|
|
3
|
-
export { createScope } from './utils';
|
|
1
|
+
import { ScopeHandle } from './types.js';
|
|
2
|
+
export type { ScopeHandle } from './types.js';
|
|
3
|
+
export { createScope } from './utils.js';
|
|
4
4
|
/**
|
|
5
5
|
* Standalone counterpart to `app.Scope<MulticastActions>()`, exported
|
|
6
6
|
* as `shared.Scope` — opens a typed multicast scope without
|
package/dist/scope/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AppContextHandle, AppResource } from '../app/types';
|
|
2
|
-
import { Actions, Model, Props } from '../types/index';
|
|
1
|
+
import { AppContextHandle, AppResource } from '../app/types.js';
|
|
2
|
+
import { Actions, Model, Props } from '../types/index.js';
|
|
3
3
|
import type * as React from "react";
|
|
4
4
|
/**
|
|
5
5
|
* Handle returned by `app.Scope<MulticastActions>()`. Mirrors the
|
package/dist/scope/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Cache } from '../cache/index';
|
|
2
|
-
import { ScopeHandle } from './types';
|
|
1
|
+
import { Cache } from '../cache/index.js';
|
|
2
|
+
import { ScopeHandle } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Internal constructor for a {@link ScopeHandle}. Called from inside
|
|
5
5
|
* `App<E>()` so the enclosing Env shape `E` is captured at the type
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AppFetcher } from '../app/types';
|
|
2
|
-
import { ResourceHandle } from '../resource/types';
|
|
3
|
-
export { useContext, useEnv } from '../app/index';
|
|
4
|
-
export { Scope } from '../scope/index';
|
|
1
|
+
import { AppFetcher } from '../app/types.js';
|
|
2
|
+
import { ResourceHandle } from '../resource/types.js';
|
|
3
|
+
export { useContext, useEnv } from '../app/index.js';
|
|
4
|
+
export { Scope } from '../scope/index.js';
|
|
5
5
|
/**
|
|
6
6
|
* Standalone counterpart to `app.Resource`, exported as
|
|
7
7
|
* `shared.Resource`. Takes the **Env shape `E` as a mandatory first
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Operation, Process, Inspect, Box } from 'immertation';
|
|
2
|
-
import { ActionId, Task, Tasks } from '../boundary/components/tasks/types';
|
|
3
|
-
import { Fault } from '../error/types';
|
|
4
|
-
import { Env } from '../boundary/components/env/
|
|
5
|
-
import { Coalesce, Invocation } from '../resource/types';
|
|
6
|
-
import { WithHandle } from '../with/types';
|
|
2
|
+
import { ActionId, Task, Tasks } from '../boundary/components/tasks/types.js';
|
|
3
|
+
import { Fault } from '../error/types.js';
|
|
4
|
+
import { Env } from '../boundary/components/env/types.js';
|
|
5
|
+
import { Coalesce, Invocation } from '../resource/types.js';
|
|
6
|
+
import { WithHandle } from '../with/types.js';
|
|
7
7
|
import * as React from "react";
|
|
8
8
|
/**
|
|
9
9
|
* Chainable handle returned from `context.actions.resource(invocation)`.
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Pk } from '../types/index';
|
|
2
|
-
export { unset } from './utils';
|
|
3
|
-
export type { Stored, Unset } from './types';
|
|
1
|
+
import { Pk } from '../types/index.js';
|
|
2
|
+
export { unset } from './utils.js';
|
|
3
|
+
export type { Stored, Unset } from './types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Returns a promise that resolves after the specified number of
|
|
6
6
|
* milliseconds, or rejects with an {@link Aborted} when the signal is aborted. Use to inject a cancellable
|
package/dist/utils/types.d.ts
CHANGED
package/dist/utils/utils.d.ts
CHANGED
package/dist/with/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Actions, HandlerContext, Model, Props } from '../types/index';
|
|
2
|
-
import { Env } from '../boundary/components/env/
|
|
3
|
-
import { BooleanPaths, Get, Paths } from './types';
|
|
1
|
+
import { Actions, HandlerContext, Model, Props } from '../types/index.js';
|
|
2
|
+
import { Env } from '../boundary/components/env/types.js';
|
|
3
|
+
import { BooleanPaths, Get, Paths } from './types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Handler factories that wire an action directly to a model field. Prefer
|
|
6
6
|
* `context.with` from `useContext<Model>()` for first-class autocompletion
|
package/dist/with/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Actions, HandlerContext, Maybe, Model, Props } from '../types/index';
|
|
2
|
-
import { Env } from '../boundary/components/env/
|
|
1
|
+
import { Actions, HandlerContext, Maybe, Model, Props } from '../types/index.js';
|
|
2
|
+
import { Env } from '../boundary/components/env/types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Non-nullable primitive leaves that {@link Paths} can terminate on. Used
|
|
5
5
|
* internally to stop the recursive walk at scalar boundaries; consumers
|
package/dist/with/utils.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Actions, HandlerContext, Model, Props } from '../types/index';
|
|
2
|
-
import { Env } from '../boundary/components/env/
|
|
3
|
-
import { WithHandle } from './types';
|
|
1
|
+
import { Actions, HandlerContext, Model, Props } from '../types/index.js';
|
|
2
|
+
import { Env } from '../boundary/components/env/types.js';
|
|
3
|
+
import { WithHandle } from './types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Walks the lodash-style dotted `path` on `target`, stopping one segment
|
|
6
6
|
* short so callers can mutate or read the leaf via the returned `cursor`
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "march-hare",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"packageManager": "yarn@1.22.22",
|
|
7
7
|
"main": "./dist/march-hare.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"mh": "./dist/cli/bin/mh.js"
|
|
11
|
+
},
|
|
9
12
|
"exports": {
|
|
10
13
|
".": {
|
|
11
14
|
"types": "./dist/index.d.ts",
|
|
@@ -14,8 +17,16 @@
|
|
|
14
17
|
}
|
|
15
18
|
},
|
|
16
19
|
"dependencies": {
|
|
20
|
+
"@inquirer/prompts": "^7.2.0",
|
|
21
|
+
"change-case": "^5.4.4",
|
|
22
|
+
"ejs": "^3.1.10",
|
|
17
23
|
"eventemitter3": "^5.0.4",
|
|
18
|
-
"
|
|
24
|
+
"figlet": "^1.7.0",
|
|
25
|
+
"find-up": "^8.0.0",
|
|
26
|
+
"gray-matter": "^4.0.3",
|
|
27
|
+
"immertation": "^0.1.26",
|
|
28
|
+
"kleur": "^4.1.5",
|
|
29
|
+
"tinyglobby": "^0.2.17"
|
|
19
30
|
},
|
|
20
31
|
"peerDependencies": {
|
|
21
32
|
"@mobily/ts-belt": "^3.0.0",
|
|
@@ -26,10 +37,12 @@
|
|
|
26
37
|
"vitest/vite": "^7.3.1"
|
|
27
38
|
},
|
|
28
39
|
"files": [
|
|
29
|
-
"dist"
|
|
40
|
+
"dist",
|
|
41
|
+
"src/cli/README.md"
|
|
30
42
|
],
|
|
31
43
|
"scripts": {
|
|
32
|
-
"build": "vite build",
|
|
44
|
+
"build": "vite build && npm run build:cli",
|
|
45
|
+
"build:cli": "tsc -p tsconfig.cli.json && rm -rf dist/cli/templates && cp -R src/cli/templates dist/cli/templates && chmod +x dist/cli/bin/mh.js",
|
|
33
46
|
"build:example": "vite build --mode example --outDir dist-example --base /MarchHare/ && mv dist-example/src/example/index.html dist-example/index.html && rm -rf dist-example/src",
|
|
34
47
|
"dev": "vite",
|
|
35
48
|
"preview": "vite preview",
|
|
@@ -53,6 +66,7 @@
|
|
|
53
66
|
"@testing-library/jest-dom": "^6.9.1",
|
|
54
67
|
"@testing-library/react": "^16.3.2",
|
|
55
68
|
"@types/dom-navigation": "^1.0.7",
|
|
69
|
+
"@types/ejs": "^3.1.5",
|
|
56
70
|
"@types/lodash": "^4.17.23",
|
|
57
71
|
"@types/ms": "^2.1.0",
|
|
58
72
|
"@types/ramda": "^0.31.1",
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# `@march-hare/cli` — the `mh` scaffolder
|
|
2
|
+
|
|
3
|
+
A [Hygen](https://github.com/jondot/hygen)-style code generator that
|
|
4
|
+
scaffolds [March Hare](../../README.md) projects and the building
|
|
5
|
+
blocks inside them. Templates mirror the layout of
|
|
6
|
+
[`src/example/`](../example/) and the FSD layering rules enforced by
|
|
7
|
+
[`eslint-plugin-boundaries`](https://github.com/javierbrea/eslint-plugin-boundaries):
|
|
8
|
+
imports flow strictly downward (`app → features → shared`).
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
__ ___ __ __ __
|
|
12
|
+
/ |/ /___ ___________/ /_ / / / /___ _________
|
|
13
|
+
/ /|_/ / __ `/ ___/ ___/ __ \ / /_/ / __ `/ ___/ _ \
|
|
14
|
+
/ / / / /_/ / / / /__/ / / / / __ / /_/ / / / __/
|
|
15
|
+
/_/ /_/\__,_/_/ \___/_/ /_/ /_/ /_/\__,_/_/ \___/
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Contents
|
|
19
|
+
|
|
20
|
+
- [Quick start](#quick-start)
|
|
21
|
+
- [Interactive menu vs. direct commands](#interactive-menu-vs-direct-commands)
|
|
22
|
+
- [`mh init`](#mh-init)
|
|
23
|
+
- [`mh app …`](#mh-app-)
|
|
24
|
+
- [`mh feature …`](#mh-feature-)
|
|
25
|
+
- [`mh shared …`](#mh-shared-)
|
|
26
|
+
- [Generated project layout](#generated-project-layout)
|
|
27
|
+
- [How the templates work](#how-the-templates-work)
|
|
28
|
+
- [Adding your own generators](#adding-your-own-generators)
|
|
29
|
+
- [Development](#development)
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
The CLI ships with `march-hare` itself — installing the library
|
|
34
|
+
exposes the `mh` binary:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install -g march-hare # global: `mh` is on your PATH
|
|
38
|
+
mh init my-project
|
|
39
|
+
|
|
40
|
+
# — or —
|
|
41
|
+
|
|
42
|
+
npm install march-hare # local: invoke through npx / package scripts
|
|
43
|
+
npx mh init my-project
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Working inside this repo? Run the CLI straight from source — root
|
|
47
|
+
`yarn install` covers all its deps:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
node dist/cli/bin/mh.js # interactive menu
|
|
51
|
+
node dist/cli/bin/mh.js init demo # scaffold a project into ./demo
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
When run without arguments the CLI prints the Figlet banner and an
|
|
55
|
+
interactive menu of every top-level command.
|
|
56
|
+
|
|
57
|
+
## Interactive menu vs. direct commands
|
|
58
|
+
|
|
59
|
+
Every command in the CLI is a node in a tree. Pass none, one, or all
|
|
60
|
+
of the path segments — the CLI fills in the gaps with prompts.
|
|
61
|
+
|
|
62
|
+
| You type | What happens |
|
|
63
|
+
| -------------------- | -------------------------------------------------- |
|
|
64
|
+
| `mh` | Menu of top-level commands |
|
|
65
|
+
| `mh feature` | Menu of sub-commands under `feature` |
|
|
66
|
+
| `mh feature new` | Prompts you for the feature name then scaffolds it |
|
|
67
|
+
| `mh feature new foo` | Scaffolds `features/foo/` with no prompts |
|
|
68
|
+
| `mh --help` | Prints the whole command tree |
|
|
69
|
+
|
|
70
|
+
Any leaf command accepts `--name=value` and `--flag` style overrides
|
|
71
|
+
so you can drive it from scripts without prompts.
|
|
72
|
+
|
|
73
|
+
## `mh init`
|
|
74
|
+
|
|
75
|
+
Bootstraps a brand-new March Hare project mirroring `src/example/`.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
mh init my-project
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Prompts you for:
|
|
82
|
+
|
|
83
|
+
- **Project name** — kebab-case slug used for the package name and the
|
|
84
|
+
folder it lives in.
|
|
85
|
+
- **Description** — populates `package.json` and the root README.
|
|
86
|
+
- **API base URL** — seeded into the App's Env at
|
|
87
|
+
`src/app/utils.ts`.
|
|
88
|
+
|
|
89
|
+
What you get:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
my-project/
|
|
93
|
+
├── eslint.config.js ← FSD boundaries enforced via plugin-boundaries
|
|
94
|
+
├── package.json ← React 19 + March Hare 0.13 + Vite + Vitest + Playwright
|
|
95
|
+
├── playwright.config.ts
|
|
96
|
+
├── tsconfig.json ← @app/* @features/* @shared/* path aliases
|
|
97
|
+
├── vite.config.ts
|
|
98
|
+
├── vitest.config.ts
|
|
99
|
+
├── index.html
|
|
100
|
+
├── README.md
|
|
101
|
+
├── tests/
|
|
102
|
+
│ └── home.e2e.ts
|
|
103
|
+
└── src/
|
|
104
|
+
├── index.tsx
|
|
105
|
+
├── test-setup.ts
|
|
106
|
+
├── vite-env.d.ts
|
|
107
|
+
├── app/
|
|
108
|
+
│ ├── index.tsx ← <app.Boundary> root
|
|
109
|
+
│ ├── utils.ts ← App<Env.X>({ env: { apiBase } })
|
|
110
|
+
│ └── pages/home/ ← home page (button + greeting)
|
|
111
|
+
│ ├── index.tsx
|
|
112
|
+
│ ├── types.ts
|
|
113
|
+
│ ├── actions.ts
|
|
114
|
+
│ ├── styles.ts
|
|
115
|
+
│ └── index.integration.tsx
|
|
116
|
+
├── features/greet/ ← stateful "say hello" feature
|
|
117
|
+
│ ├── index.tsx
|
|
118
|
+
│ ├── types.ts
|
|
119
|
+
│ ├── actions.ts
|
|
120
|
+
│ ├── utils.ts ← shared.Scope<Envs, typeof Multicast>()
|
|
121
|
+
│ └── index.test.tsx
|
|
122
|
+
└── shared/
|
|
123
|
+
├── components/button/ ← antd wrapper + tests
|
|
124
|
+
├── theme/ ← colour / spacing / font / radius / shadow tokens
|
|
125
|
+
├── types/ ← Env namespace + Envs alias + Payload + Broadcast
|
|
126
|
+
└── resources/ ← empty barrel; add resources with `mh shared resource`
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
A working "Say hello" button is wired end-to-end so you can verify the
|
|
130
|
+
project boots before deleting it.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
cd my-project
|
|
134
|
+
yarn install # or npm install
|
|
135
|
+
yarn dev
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## `mh app …`
|
|
139
|
+
|
|
140
|
+
Manage the host layer.
|
|
141
|
+
|
|
142
|
+
| Command | What it does |
|
|
143
|
+
| ---------------------- | ------------------------------------------------------------------- |
|
|
144
|
+
| `mh app new <name>` | New page under `src/app/pages/<name>/` (index/types/actions/styles) |
|
|
145
|
+
| `mh app integration` | Picks a page, drops in an `index.integration.tsx` |
|
|
146
|
+
| `mh app action <page>` | Picks (or accepts) a page, injects a new `Actions.X` + handler |
|
|
147
|
+
|
|
148
|
+
Examples:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
mh app new dashboard --tagline="Live metrics"
|
|
152
|
+
mh app integration # prompts you to pick a page
|
|
153
|
+
mh app action dashboard Refresh # injects Actions.Refresh + a handler
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The `action` command injects into both `types.ts` (adds
|
|
157
|
+
`static Refresh = Action("Refresh")`) and `actions.ts` (adds an empty
|
|
158
|
+
`actions.useAction(Actions.Refresh, …)` block before `return actions`).
|
|
159
|
+
Re-running the same command is a no-op thanks to `skip_if`.
|
|
160
|
+
|
|
161
|
+
## `mh feature …`
|
|
162
|
+
|
|
163
|
+
Manage feature slices.
|
|
164
|
+
|
|
165
|
+
| Command | What it does |
|
|
166
|
+
| -------------------------------- | ------------------------------------------------------------------------ |
|
|
167
|
+
| `mh feature new <name>` | New feature; asks whether it owns state (`--stateful` / `--no-stateful`) |
|
|
168
|
+
| `mh feature unit <name>` | Adds a `index.test.tsx` next to an existing feature |
|
|
169
|
+
| `mh feature action <feat> <act>` | Injects an `Actions.<Act>` member + a handler stub into the feature |
|
|
170
|
+
| `mh feature multicast <feat>` | Injects a multicast action into the feature's `Multicast` class |
|
|
171
|
+
|
|
172
|
+
Stateful features get four files: `index.tsx`, `types.ts`, `actions.ts`,
|
|
173
|
+
`utils.ts`. Presentational features get three (no `actions.ts`). Both
|
|
174
|
+
shapes include a `Multicast` class and a `scope` handle
|
|
175
|
+
(`shared.Scope<Envs, typeof Multicast>()`) — the structural contract
|
|
176
|
+
for the feature's private bus.
|
|
177
|
+
|
|
178
|
+
## `mh shared …`
|
|
179
|
+
|
|
180
|
+
Manage reusable building blocks.
|
|
181
|
+
|
|
182
|
+
| Command | What it does |
|
|
183
|
+
| --------------------------------- | ------------------------------------------------------------ |
|
|
184
|
+
| `mh shared component <name>` | New presentational atom under `src/shared/components/` |
|
|
185
|
+
| `mh shared resource <name>` | New `shared.Resource<Envs, T>` under `src/shared/resources/` |
|
|
186
|
+
| `mh shared util <name>` | New util module under `src/shared/utils/` |
|
|
187
|
+
| `mh shared type payload <name>` | Injects a `Payload.<Name>` type into `shared/types` |
|
|
188
|
+
| `mh shared type broadcast <name>` | Injects a global broadcast action into `shared/types` |
|
|
189
|
+
| `mh shared unit <kind> <name>` | Adds a unit test for a shared module |
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
mh shared component card
|
|
193
|
+
mh shared resource user
|
|
194
|
+
mh shared util parse-date
|
|
195
|
+
mh shared type payload Notification
|
|
196
|
+
mh shared type broadcast Toast
|
|
197
|
+
mh shared unit components card
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
After creating a resource, re-export it from
|
|
201
|
+
`src/shared/resources/index.ts` so it stays reachable as
|
|
202
|
+
`resource.<name>.fetch()`:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
export * as user from "./user/index.ts";
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Generated project layout
|
|
209
|
+
|
|
210
|
+
The CLI's `init` template seeds the App's `Env` shape based on the
|
|
211
|
+
project name. If you run `mh init billing`, the generated
|
|
212
|
+
`shared/types/index.ts` declares `Env.Billing` and `Envs = Env.Billing`:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
export namespace Env {
|
|
216
|
+
export type Billing = {
|
|
217
|
+
apiBase: string;
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export type Envs = Env.Billing;
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
When you later add a second deployable (e.g. a marketing site) you
|
|
225
|
+
widen `Envs` to a union and add a [user-defined type
|
|
226
|
+
guard](https://www.typescriptlang.org/docs/handbook/advanced-types.html)
|
|
227
|
+
to narrow at the call site — see the example's
|
|
228
|
+
[README](../example/README.md) for the full pattern.
|
|
229
|
+
|
|
230
|
+
## How the templates work
|
|
231
|
+
|
|
232
|
+
Templates live under [`templates/<generator>/<action>/`](./templates).
|
|
233
|
+
Each `.ejs.t` file has a tiny [Hygen-style](https://www.hygen.io/docs/templates)
|
|
234
|
+
frontmatter block plus an EJS body.
|
|
235
|
+
|
|
236
|
+
```ejs
|
|
237
|
+
---
|
|
238
|
+
to: src/features/<%= name %>/index.tsx
|
|
239
|
+
---
|
|
240
|
+
import * as React from "react";
|
|
241
|
+
// …
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Supported frontmatter keys:
|
|
245
|
+
|
|
246
|
+
| Key | What it does |
|
|
247
|
+
| --------- | ------------------------------------------------------------------------------------------ |
|
|
248
|
+
| `to` | Output path. Rendered through EJS so it can reference template vars. |
|
|
249
|
+
| `inject` | Set to `true` to inject the body into an existing file instead of overwriting it. |
|
|
250
|
+
| `before` | Regex (multiline). With `inject`, inserts the body **above** the first match. |
|
|
251
|
+
| `after` | Regex (multiline). With `inject`, inserts the body **below** the first match. |
|
|
252
|
+
| `skip_if` | Regex (multiline). With `inject`, skip the file if it already matches (idempotent reruns). |
|
|
253
|
+
| `if` | EJS expression. Skip the template entirely unless it evaluates truthy. |
|
|
254
|
+
| `force` | EJS expression. When truthy, overwrite an existing file (default: skip). |
|
|
255
|
+
|
|
256
|
+
Helpers available inside templates:
|
|
257
|
+
|
|
258
|
+
| Helper | Example | Result |
|
|
259
|
+
| ----------- | ------------------- | ----------- |
|
|
260
|
+
| `kebab(s)` | `kebab("AddCat")` | `"add-cat"` |
|
|
261
|
+
| `pascal(s)` | `pascal("add-cat")` | `"AddCat"` |
|
|
262
|
+
| `camel(s)` | `camel("add-cat")` | `"addCat"` |
|
|
263
|
+
| `title(s)` | `title("add-cat")` | `"Add Cat"` |
|
|
264
|
+
|
|
265
|
+
Standard variables passed to every render:
|
|
266
|
+
|
|
267
|
+
- `name` — the kebab-case name accepted from positional / prompt.
|
|
268
|
+
- `pascalName` — same value run through `pascal()`.
|
|
269
|
+
|
|
270
|
+
Commands pass additional vars (e.g. `feature`, `page`, `rawName`,
|
|
271
|
+
`env`, `apiBase`) — see the individual command files under
|
|
272
|
+
[`lib/commands/`](./lib/commands).
|
|
273
|
+
|
|
274
|
+
## Adding your own generators
|
|
275
|
+
|
|
276
|
+
1. Create `templates/<generator>/<action>/foo.ejs.t` with a `to:`
|
|
277
|
+
frontmatter and an EJS body.
|
|
278
|
+
2. Wire a leaf into [`lib/commands/index.ts`](./lib/commands/index.ts) that calls
|
|
279
|
+
`scaffold("<generator>", "<action>", vars, { cwd })`.
|
|
280
|
+
3. Optionally add prompts via the helpers in
|
|
281
|
+
[`lib/prompt/index.ts`](./lib/prompt/index.ts).
|
|
282
|
+
|
|
283
|
+
Templates can render any number of files — every `.ejs.t` under the
|
|
284
|
+
action directory is processed, with each `to:` resolved
|
|
285
|
+
independently. Use nested directories under the action to keep big
|
|
286
|
+
generators (like `init`) tidy.
|
|
287
|
+
|
|
288
|
+
## Development
|
|
289
|
+
|
|
290
|
+
The CLI's runtime deps (`@inquirer/prompts`, `ejs`, `figlet`, `kleur`)
|
|
291
|
+
live in the root [`package.json`](../../package.json). A single
|
|
292
|
+
`yarn install` at the repo root is all you need:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
yarn install # from the repo root
|
|
296
|
+
node dist/cli/bin/mh.js # run the CLI from source
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
There are no build steps — the CLI is plain ESM JavaScript. Templates
|
|
300
|
+
are read directly from disk at runtime.
|
|
301
|
+
|
|
302
|
+
To verify a change end-to-end:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
cd /tmp && rm -rf check && mkdir check && cd check
|
|
306
|
+
node "$OLDPWD/dist/cli/bin/mh.js" init demo \
|
|
307
|
+
--description="dev check" --apiBase="https://api.example.test"
|
|
308
|
+
cd demo
|
|
309
|
+
node /Users/adamtimberlake/Webroot/MarchHare/dist/cli/bin/mh.js feature new counter --stateful
|
|
310
|
+
node /Users/adamtimberlake/Webroot/MarchHare/dist/cli/bin/mh.js feature action counter Reset
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Then `yarn install && yarn checks` inside the generated project should
|
|
314
|
+
pass cleanly.
|