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.
Files changed (129) hide show
  1. package/README.md +25 -1
  2. package/dist/action/index.d.ts +2 -2
  3. package/dist/action/utils.d.ts +2 -2
  4. package/dist/actions/index.d.ts +2 -2
  5. package/dist/actions/types.d.ts +3 -3
  6. package/dist/actions/utils.d.ts +3 -3
  7. package/dist/app/index.d.ts +7 -7
  8. package/dist/app/types.d.ts +5 -5
  9. package/dist/boundary/components/broadcast/index.d.ts +2 -2
  10. package/dist/boundary/components/broadcast/types.d.ts +1 -1
  11. package/dist/boundary/components/consumer/components/partition/index.d.ts +1 -1
  12. package/dist/boundary/components/consumer/components/partition/types.d.ts +1 -1
  13. package/dist/boundary/components/consumer/index.d.ts +5 -5
  14. package/dist/boundary/components/consumer/types.d.ts +1 -1
  15. package/dist/boundary/components/consumer/utils.d.ts +1 -1
  16. package/dist/boundary/components/env/index.d.ts +3 -27
  17. package/dist/boundary/components/env/types.d.ts +24 -2
  18. package/dist/boundary/components/env/utils.d.ts +1 -1
  19. package/dist/boundary/components/scope/index.d.ts +2 -2
  20. package/dist/boundary/components/scope/types.d.ts +1 -1
  21. package/dist/boundary/components/scope/utils.d.ts +1 -1
  22. package/dist/boundary/components/sharing/index.d.ts +1 -1
  23. package/dist/boundary/components/tap/index.d.ts +3 -3
  24. package/dist/boundary/components/tap/types.d.ts +2 -2
  25. package/dist/boundary/components/tap/utils.d.ts +1 -1
  26. package/dist/boundary/components/tasks/index.d.ts +2 -2
  27. package/dist/boundary/components/tasks/utils.d.ts +1 -1
  28. package/dist/boundary/index.d.ts +1 -1
  29. package/dist/boundary/types.d.ts +2 -2
  30. package/dist/cache/index.d.ts +2 -2
  31. package/dist/cache/types.d.ts +1 -1
  32. package/dist/cli/bin/mh.js +10 -0
  33. package/dist/cli/lib/banner/index.js +14 -0
  34. package/dist/cli/lib/commands/app/index.js +37 -0
  35. package/dist/cli/lib/commands/feature/index.js +55 -0
  36. package/dist/cli/lib/commands/index.js +89 -0
  37. package/dist/cli/lib/commands/init/index.js +29 -0
  38. package/dist/cli/lib/commands/shared/index.js +56 -0
  39. package/dist/cli/lib/index.js +56 -0
  40. package/dist/cli/lib/parser/index.js +24 -0
  41. package/dist/cli/lib/prompt/index.js +61 -0
  42. package/dist/cli/lib/runner/index.js +46 -0
  43. package/dist/cli/lib/runner/types.js +1 -0
  44. package/dist/cli/lib/runner/utils.js +60 -0
  45. package/dist/cli/lib/types.js +1 -0
  46. package/dist/cli/lib/utils.js +20 -0
  47. package/dist/cli/templates/app/action/actions.ts.ejs.t +10 -0
  48. package/dist/cli/templates/app/action/types.ts.ejs.t +7 -0
  49. package/dist/cli/templates/app/integration/index.integration.tsx.ejs.t +13 -0
  50. package/dist/cli/templates/app/page/actions.ts.ejs.t +14 -0
  51. package/dist/cli/templates/app/page/index.tsx.ejs.t +20 -0
  52. package/dist/cli/templates/app/page/styles.ts.ejs.t +35 -0
  53. package/dist/cli/templates/app/page/types.ts.ejs.t +12 -0
  54. package/dist/cli/templates/feature/action/actions.ts.ejs.t +10 -0
  55. package/dist/cli/templates/feature/action/types.ts.ejs.t +7 -0
  56. package/dist/cli/templates/feature/multicast/types.ts.ejs.t +7 -0
  57. package/dist/cli/templates/feature/presentational/index.tsx.ejs.t +14 -0
  58. package/dist/cli/templates/feature/presentational/types.ts.ejs.t +12 -0
  59. package/dist/cli/templates/feature/presentational/utils.ts.ejs.t +8 -0
  60. package/dist/cli/templates/feature/stateful/actions.ts.ejs.t +16 -0
  61. package/dist/cli/templates/feature/stateful/index.tsx.ejs.t +19 -0
  62. package/dist/cli/templates/feature/stateful/types.ts.ejs.t +16 -0
  63. package/dist/cli/templates/feature/stateful/utils.ts.ejs.t +8 -0
  64. package/dist/cli/templates/feature/unit/index.test.tsx.ejs.t +21 -0
  65. package/dist/cli/templates/init/new/README.md.ejs.t +48 -0
  66. package/dist/cli/templates/init/new/eslint.config.js.ejs.t +88 -0
  67. package/dist/cli/templates/init/new/gitignore.ejs.t +9 -0
  68. package/dist/cli/templates/init/new/index.html.ejs.t +18 -0
  69. package/dist/cli/templates/init/new/package.json.ejs.t +54 -0
  70. package/dist/cli/templates/init/new/playwright.config.ts.ejs.t +17 -0
  71. package/dist/cli/templates/init/new/prettierrc.ejs.t +8 -0
  72. package/dist/cli/templates/init/new/src.app.index.tsx.ejs.t +14 -0
  73. package/dist/cli/templates/init/new/src.app.pages.home.actions.ts.ejs.t +16 -0
  74. package/dist/cli/templates/init/new/src.app.pages.home.index.tsx.ejs.t +30 -0
  75. package/dist/cli/templates/init/new/src.app.pages.home.integration.tsx.ejs.t +28 -0
  76. package/dist/cli/templates/init/new/src.app.pages.home.styles.ts.ejs.t +45 -0
  77. package/dist/cli/templates/init/new/src.app.pages.home.types.ts.ejs.t +12 -0
  78. package/dist/cli/templates/init/new/src.app.utils.ts.ejs.t +9 -0
  79. package/dist/cli/templates/init/new/src.features.greet.actions.ts.ejs.t +20 -0
  80. package/dist/cli/templates/init/new/src.features.greet.index.test.tsx.ejs.t +21 -0
  81. package/dist/cli/templates/init/new/src.features.greet.index.tsx.ejs.t +24 -0
  82. package/dist/cli/templates/init/new/src.features.greet.types.ts.ejs.t +18 -0
  83. package/dist/cli/templates/init/new/src.features.greet.utils.ts.ejs.t +8 -0
  84. package/dist/cli/templates/init/new/src.index.tsx.ejs.t +8 -0
  85. package/dist/cli/templates/init/new/src.shared.components.button.index.test.tsx.ejs.t +13 -0
  86. package/dist/cli/templates/init/new/src.shared.components.button.index.tsx.ejs.t +10 -0
  87. package/dist/cli/templates/init/new/src.shared.components.button.types.ts.ejs.t +6 -0
  88. package/dist/cli/templates/init/new/src.shared.resources.index.ts.ejs.t +4 -0
  89. package/dist/cli/templates/init/new/src.shared.theme.index.ts.ejs.t +51 -0
  90. package/dist/cli/templates/init/new/src.shared.types.index.ts.ejs.t +23 -0
  91. package/dist/cli/templates/init/new/src.test-setup.ts.ejs.t +10 -0
  92. package/dist/cli/templates/init/new/src.vite-env.d.ts.ejs.t +4 -0
  93. package/dist/cli/templates/init/new/tests.home.e2e.ts.ejs.t +14 -0
  94. package/dist/cli/templates/init/new/tsconfig.json.ejs.t +29 -0
  95. package/dist/cli/templates/init/new/vite.config.ts.ejs.t +17 -0
  96. package/dist/cli/templates/init/new/vitest.config.ts.ejs.t +24 -0
  97. package/dist/cli/templates/shared/component/index.tsx.ejs.t +9 -0
  98. package/dist/cli/templates/shared/component/types.ts.ejs.t +8 -0
  99. package/dist/cli/templates/shared/resource/index.ts.ejs.t +15 -0
  100. package/dist/cli/templates/shared/resource/types.ts.ejs.t +10 -0
  101. package/dist/cli/templates/shared/type-broadcast/types.ts.ejs.t +7 -0
  102. package/dist/cli/templates/shared/type-payload/types.ts.ejs.t +9 -0
  103. package/dist/cli/templates/shared/unit-component/index.test.tsx.ejs.t +13 -0
  104. package/dist/cli/templates/shared/unit-resource/index.test.ts.ejs.t +15 -0
  105. package/dist/cli/templates/shared/unit-util/index.test.ts.ejs.t +11 -0
  106. package/dist/cli/templates/shared/util/index.ts.ejs.t +6 -0
  107. package/dist/coalesce/index.d.ts +1 -1
  108. package/dist/context/index.d.ts +1 -1
  109. package/dist/error/types.d.ts +1 -1
  110. package/dist/error/utils.d.ts +1 -1
  111. package/dist/index.d.ts +16 -16
  112. package/dist/march-hare.js.map +1 -1
  113. package/dist/march-hare.umd.cjs.map +1 -1
  114. package/dist/resource/index.d.ts +4 -4
  115. package/dist/resource/types.d.ts +3 -3
  116. package/dist/resource/utils.d.ts +4 -4
  117. package/dist/scope/index.d.ts +3 -3
  118. package/dist/scope/types.d.ts +2 -2
  119. package/dist/scope/utils.d.ts +2 -2
  120. package/dist/shared/index.d.ts +4 -4
  121. package/dist/types/index.d.ts +5 -5
  122. package/dist/utils/index.d.ts +3 -3
  123. package/dist/utils/types.d.ts +1 -1
  124. package/dist/utils/utils.d.ts +1 -1
  125. package/dist/with/index.d.ts +3 -3
  126. package/dist/with/types.d.ts +2 -2
  127. package/dist/with/utils.d.ts +3 -3
  128. package/package.json +18 -4
  129. package/src/cli/README.md +314 -0
package/README.md CHANGED
@@ -27,8 +27,9 @@
27
27
  1. [Multicast actions](#multicast-actions)
28
28
  1. [Global data](#global-data)
29
29
  1. [Reusable components](#reusable-components)
30
+ 1. [Scaffolding CLI](#scaffolding-cli)
30
31
 
31
- For advanced topics, see the [recipes directory](./recipes/). For a worked end-to-end example with the FSD layout, see [`src/example/`](./src/example/README.md).
32
+ For advanced topics, see the [recipes directory](./recipes/). For a worked end-to-end example with the FSD layout, see [`src/example/`](./src/example/README.md). To scaffold a new project that mirrors that example, see [`src/cli/`](./src/cli/README.md).
32
33
 
33
34
  ## Benefits
34
35
 
@@ -896,3 +897,26 @@ function Where(): React.ReactElement {
896
897
  When a reusable component or resource is genuinely Env-agnostic &mdash; the fetcher never touches `context.env`, the hook never calls `shared.useEnv` &mdash; pass `Envless` as `E` instead of spelling out `Record<never, never>`: `shared.Resource<Envless, T>`, `shared.useContext<Envless, M, A>()`. It's a named alias for the empty-record shape exported from `march-hare`, kept around purely for legibility at the call site.
897
898
 
898
899
  For one-line handler binding &mdash; flipping a boolean, assigning a payload to a leaf, pinning a field to a fixed value &mdash; reach for `context.with.{update,invert,always}`. See the [`With` helpers recipe](./recipes/with-helpers.md) for the full surface.
900
+
901
+ ## Scaffolding CLI
902
+
903
+ A [Hygen](https://github.com/jondot/hygen)-style scaffolder ships under [`src/cli/`](./src/cli/) as the `mh` binary. It mirrors the layout of [`src/example/`](./src/example/) and the FSD layering rules enforced by `eslint-plugin-boundaries` &mdash; imports flow strictly downward (`app → features → shared`).
904
+
905
+ ```bash
906
+ cd src/cli
907
+ npm install
908
+ npm link # creates the global `mh` binary
909
+ ```
910
+
911
+ Run it with no arguments for an interactive menu, or drive any leaf command directly:
912
+
913
+ ```bash
914
+ mh # banner + interactive menu
915
+ mh init my-project # bootstrap a new project
916
+ mh feature new add-cat # add a stateful feature
917
+ mh app new dashboard # add a page
918
+ mh shared component card # add a shared component
919
+ mh feature action counter Reset # inject an Action + handler stub
920
+ ```
921
+
922
+ Every command lives in a tree &mdash; typing `mh feature` opens a sub-menu, typing `mh feature new` prompts for a name, typing `mh feature new add-cat` runs non-interactively. See [`src/cli/README.md`](./src/cli/README.md) for the full command surface, the template format, and instructions for adding your own generators.
@@ -1,5 +1,5 @@
1
- import { HandlerPayload, BroadcastPayload, MulticastPayload, Distribution, Filter } from '../types/index';
2
- export { getActionSymbol, getLifecycleType, isBroadcastAction, isMulticastAction, getName, isChanneledAction, } from './utils';
1
+ import { HandlerPayload, BroadcastPayload, MulticastPayload, Distribution, Filter } from '../types/index.js';
2
+ export { getActionSymbol, getLifecycleType, isBroadcastAction, isMulticastAction, getName, isChanneledAction, } from './utils.js';
3
3
  /**
4
4
  * Interface for the Action factory function.
5
5
  */
@@ -1,5 +1,5 @@
1
- import { ChanneledAction, AnyAction } from '../types/index';
2
- import { ActionId } from '../boundary/components/tasks/types';
1
+ import { ChanneledAction, AnyAction } from '../types/index.js';
2
+ import { ActionId } from '../boundary/components/tasks/types.js';
3
3
  /**
4
4
  * Extracts the underlying symbol from an action or channeled action.
5
5
  * This symbol is used as the event emitter key for dispatching.
@@ -1,5 +1,5 @@
1
- import { Data } from './types';
2
- import { Model, Props, Actions, UseActions } from '../types/index';
1
+ import { Data } from './types.js';
2
+ import { Model, Props, Actions, UseActions } from '../types/index.js';
3
3
  /**
4
4
  * A hook for managing state with actions.
5
5
  *
@@ -1,8 +1,8 @@
1
1
  import { default as EventEmitter } from 'eventemitter3';
2
2
  import { RefObject } from 'react';
3
- import { Model, HandlerContext, Actions, Props, Tasks, ActionId, Phase, Filter } from '../types/index';
4
- import { BroadcastEmitter } from '../boundary/components/broadcast/utils';
5
- import { ScopeContext } from '../boundary/components/scope/types';
3
+ import { Model, HandlerContext, Actions, Props, Tasks, ActionId, Phase, Filter } from '../types/index.js';
4
+ import { BroadcastEmitter } from '../boundary/components/broadcast/utils.js';
5
+ import { ScopeContext } from '../boundary/components/scope/types.js';
6
6
  /**
7
7
  * Function signature for action handlers registered via `useAction`.
8
8
  * Receives the reactive context and payload, returning void or a promise/generator.
@@ -1,8 +1,8 @@
1
1
  import { RefObject } from 'react';
2
- import { Props, Model, Actions, Filter, ActionId, HandlerPayload, ChanneledAction, HandlerContext } from '../types/index';
2
+ import { Props, Model, Actions, Filter, ActionId, HandlerPayload, ChanneledAction, HandlerContext } from '../types/index.js';
3
3
  import { default as EventEmitter } from 'eventemitter3';
4
- import { Dispatchers, LifecycleConfig, Scope } from './types';
5
- import { isChanneledAction, getActionSymbol } from '../action/index';
4
+ import { Dispatchers, LifecycleConfig, Scope } from './types.js';
5
+ import { isChanneledAction, getActionSymbol } from '../action/index.js';
6
6
  import * as React from "react";
7
7
  /**
8
8
  * Creates a new object with getters for each property of the input object.
@@ -1,10 +1,10 @@
1
- import { Env } from '../boundary/components/env/index';
2
- import { Cache } from '../cache/index';
3
- import { Actions, Model, Props } from '../types/index';
4
- import { AppHandle, AppContextHandle } from './types';
5
- import { Tap } from '../boundary/components/tap/types';
6
- export type { AppArgs, AppContextHandle, AppFetcher, AppResource, } from './types';
7
- export type { AppHandle } from './types';
1
+ import { Env } from '../boundary/components/env/types.js';
2
+ import { Cache } from '../cache/index.js';
3
+ import { Actions, Model, Props } from '../types/index.js';
4
+ import { AppHandle, AppContextHandle } from './types.js';
5
+ import { Tap } from '../boundary/components/tap/types.js';
6
+ export type { AppArgs, AppContextHandle, AppFetcher, AppResource, } from './types.js';
7
+ export type { AppHandle } from './types.js';
8
8
  /**
9
9
  * Creates an `App` &mdash; the entrypoint for a typed Env shape `E`,
10
10
  * inferred from `config.env`. `App<E>` exposes `Boundary`, hooks, and
@@ -1,8 +1,8 @@
1
- import { Args, ResourceHandle } from '../resource/types';
2
- import { Actions, Context, Model, Props, UseActions } from '../types/index';
3
- import { Data } from '../actions/types';
4
- import { Env } from '../boundary/components/env/index';
5
- import { WithHandle } from '../with/types';
1
+ import { Args, ResourceHandle } from '../resource/types.js';
2
+ import { Actions, Context, Model, Props, UseActions } from '../types/index.js';
3
+ import { Data } from '../actions/types.js';
4
+ import { Env } from '../boundary/components/env/types.js';
5
+ import { WithHandle } from '../with/types.js';
6
6
  /**
7
7
  * Args object passed to an `app.Resource` fetcher. Same shape as the
8
8
  * base `Resource` fetcher's args but with `env` typed as `E`.
@@ -1,6 +1,6 @@
1
- import { Props } from './types';
1
+ import { Props } from './types.js';
2
2
  import * as React from "react";
3
- export { useBroadcast, BroadcastEmitter } from './utils';
3
+ export { useBroadcast, BroadcastEmitter } from './utils.js';
4
4
  /**
5
5
  * Creates a new broadcast context for distributed actions. Only needed if you
6
6
  * want to isolate a broadcast context, useful for libraries that want to provide
@@ -1,4 +1,4 @@
1
- import { BroadcastEmitter } from './utils';
1
+ import { BroadcastEmitter } from './utils.js';
2
2
  import * as React from "react";
3
3
  /**
4
4
  * The broadcast context is a BroadcastEmitter used for distributed actions across components.
@@ -1,4 +1,4 @@
1
- import { Props } from './types';
1
+ import { Props } from './types.js';
2
2
  import * as React from "react";
3
3
  /**
4
4
  * Renders output for the `stream()` method by subscribing to distributed action events.
@@ -1,4 +1,4 @@
1
- import { ConsumerRenderer } from '../../types';
1
+ import { ConsumerRenderer } from '../../types.js';
2
2
  /**
3
3
  * Props for the Partition component.
4
4
  * @internal
@@ -1,9 +1,9 @@
1
- import { Props } from './types';
1
+ import { Props } from './types.js';
2
2
  import * as React from "react";
3
- export { useConsumer } from './utils';
4
- export { Partition } from './components/partition/index';
5
- export type { Props as PartitionProps } from './components/partition/types';
6
- export type { ConsumerRenderer, Entry, ConsumerContext } from './types';
3
+ export { useConsumer } from './utils.js';
4
+ export { Partition } from './components/partition/index.js';
5
+ export type { Props as PartitionProps } from './components/partition/types.js';
6
+ export type { ConsumerRenderer, Entry, ConsumerContext } from './types.js';
7
7
  /**
8
8
  * Creates a new consumer context for storing distributed action values. Only needed if you
9
9
  * want to isolate a consumer context, useful for libraries that want to provide
@@ -1,5 +1,5 @@
1
1
  import { Inspect, State } from 'immertation';
2
- import { ActionId } from '../tasks/types';
2
+ import { ActionId } from '../tasks/types.js';
3
3
  import * as React from "react";
4
4
  /**
5
5
  * Callback function for the stream() method.
@@ -1,4 +1,4 @@
1
- import { ConsumerContext } from './types';
1
+ import { ConsumerContext } from './types.js';
2
2
  import * as React from "react";
3
3
  /**
4
4
  * React context for the consumer store.
@@ -1,32 +1,8 @@
1
- import { Props } from './types';
1
+ import { Props } from './types.js';
2
2
  import * as React from "react";
3
- export { useEnv } from './utils';
3
+ export { useEnv } from './utils.js';
4
4
  /**
5
- * Loose runtime shape for the per-`<Boundary>` Env. Each {@link App}
6
- * narrows this to its own typed env via `App<E>({ env })`; the
7
- * loose type exists so the framework's internal plumbing
8
- * (`<Boundary>`, `useEnv`, handler `context.env`, Resource
9
- * fetcher `context.env`) does not need to be parametric over E.
10
- *
11
- * Consumers should declare their Env shape inline via `App({ env })`
12
- * &mdash; the inferred `E` is what flows through `app.useContext`,
13
- * `app.useEnv`, and `app.Resource`. Module augmentation of `Env`
14
- * is no longer required.
15
- */
16
- export type Env = Record<string, unknown>;
17
- /**
18
- * `E` generic for `shared.X<E, ...>` factories whose callers don't read
19
- * anything off the Env. Equivalent to `Record<never, never>` &mdash; the
20
- * named alias keeps consumer sites legible (`shared.Resource<Envless, T>`
21
- * over `shared.Resource<Record<never, never>, T>`) and signals intent.
22
- *
23
- * Reach for `Envless` only when the component or resource is genuinely
24
- * Env-agnostic. Anything that reads `context.env.x` should declare the
25
- * required shape (or a union of host Envs) as `E` instead.
26
- */
27
- export type Envless = Record<never, never>;
28
- /**
29
- * Provides a per-Boundary {@link Env} value to every component inside
5
+ * Provides a per-Boundary {@link EnvType} value to every component inside
30
6
  * the boundary. Usually wired in via the `<Boundary env={initial}>`
31
7
  * prop rather than used directly.
32
8
  *
@@ -1,6 +1,28 @@
1
1
  import { ReactNode } from 'react';
2
- import { Env } from './index';
3
- export type { Env } from './index';
2
+ /**
3
+ * Loose runtime shape for the per-`<Boundary>` Env. Each {@link App}
4
+ * narrows this to its own typed env via `App<E>({ env })`; the
5
+ * loose type exists so the framework's internal plumbing
6
+ * (`<Boundary>`, `useEnv`, handler `context.env`, Resource
7
+ * fetcher `context.env`) does not need to be parametric over E.
8
+ *
9
+ * Consumers should declare their Env shape inline via `App({ env })`
10
+ * &mdash; the inferred `E` is what flows through `app.useContext`,
11
+ * `app.useEnv`, and `app.Resource`. Module augmentation of `Env`
12
+ * is no longer required.
13
+ */
14
+ export type Env = Record<string, unknown>;
15
+ /**
16
+ * `E` generic for `shared.X<E, ...>` factories whose callers don't read
17
+ * anything off the Env. Equivalent to `Record<never, never>` &mdash; the
18
+ * named alias keeps consumer sites legible (`shared.Resource<Envless, T>`
19
+ * over `shared.Resource<Record<never, never>, T>`) and signals intent.
20
+ *
21
+ * Reach for `Envless` only when the component or resource is genuinely
22
+ * Env-agnostic. Anything that reads `context.env.x` should declare the
23
+ * required shape (or a union of host Envs) as `E` instead.
24
+ */
25
+ export type Envless = Record<never, never>;
4
26
  /**
5
27
  * Props for the Env provider component. Accepts the initial Env
6
28
  * value that satisfies the augmented {@link Env} interface.
@@ -1,5 +1,5 @@
1
1
  import { RefObject } from 'react';
2
- import { Env } from './index';
2
+ import { Env } from './types.js';
3
3
  import * as React from "react";
4
4
  /**
5
5
  * React context exposing the per-Boundary Env ref. The ref itself is
@@ -1,2 +1,2 @@
1
- export { Context, useScope, getScope } from './utils';
2
- export type { ScopeEntry, ScopeContext } from './types';
1
+ export { Context, useScope, getScope } from './utils.js';
2
+ export type { ScopeEntry, ScopeContext } from './types.js';
@@ -1,4 +1,4 @@
1
- import { BroadcastEmitter } from '../broadcast/utils';
1
+ import { BroadcastEmitter } from '../broadcast/utils.js';
2
2
  /**
3
3
  * Runtime entry for a single multicast scope opened by an
4
4
  * `<app.Scope().Boundary>`. The `id` uniquely identifies this scope
@@ -1,4 +1,4 @@
1
- import { ScopeContext, ScopeEntry } from './types';
1
+ import { ScopeContext, ScopeEntry } from './types.js';
2
2
  import * as React from "react";
3
3
  /**
4
4
  * React context for the nearest multicast scope. `null` at the root.
@@ -1,4 +1,4 @@
1
- import { Invocation } from '../../../resource/index';
1
+ import { Invocation } from '../../../resource/index.js';
2
2
  import * as React from "react";
3
3
  /**
4
4
  * Per-`<Boundary>` registry for `.coalesce(token)` sharing. Outer map
@@ -1,7 +1,7 @@
1
- import { Props } from './types';
1
+ import { Props } from './types.js';
2
2
  import * as React from "react";
3
- export { useTap } from './utils';
4
- export type { Tap, Taps, Invocation, Failure, Mutations, Snapshot, } from './types';
3
+ export { useTap } from './utils.js';
4
+ export type { Tap, Taps, Invocation, Failure, Mutations, Snapshot, } from './types.js';
5
5
  /**
6
6
  * Internal provider that wires a {@link Tap} observer into the React
7
7
  * context consumed by `useActions` during dispatch. Rendered by the
@@ -1,5 +1,5 @@
1
- import { Reason } from '../../../error/types';
2
- import { Task } from '../tasks/types';
1
+ import { Reason } from '../../../error/types.js';
2
+ import { Task } from '../tasks/types.js';
3
3
  import type * as React from "react";
4
4
  /**
5
5
  * Identity of a handler invocation: the action being handled and the
@@ -1,4 +1,4 @@
1
- import { Tap } from './types';
1
+ import { Tap } from './types.js';
2
2
  import * as React from "react";
3
3
  /**
4
4
  * React context carrying the active {@link Tap} observer for the
@@ -1,6 +1,6 @@
1
- import { Props } from './types';
1
+ import { Props } from './types.js';
2
2
  import * as React from "react";
3
- export type { Task } from './types';
3
+ export type { Task } from './types.js';
4
4
  /**
5
5
  * Creates a new tasks context for action control. Only needed if you
6
6
  * want to isolate a tasks context, useful for libraries that want to provide
@@ -1,4 +1,4 @@
1
- import { Tasks } from './types';
1
+ import { Tasks } from './types.js';
2
2
  import * as React from "react";
3
3
  /**
4
4
  * React context for the shared tasks Set.
@@ -1,4 +1,4 @@
1
- import { Props } from './types';
1
+ import { Props } from './types.js';
2
2
  import * as React from "react";
3
3
  /**
4
4
  * Low-level boundary primitive. Wraps children with the Broadcaster,
@@ -1,5 +1,5 @@
1
- import { Env } from './components/env/types';
2
- import { Tap } from './components/tap/types';
1
+ import { Env } from './components/env/types.js';
2
+ import { Tap } from './components/tap/types.js';
3
3
  import type * as React from "react";
4
4
  /**
5
5
  * Props accepted by the bare `<Boundary>` provider.
@@ -1,5 +1,5 @@
1
- import { Adapter, Stored } from './types';
2
- export type { Adapter, Encoded } from './types';
1
+ import { Adapter, Stored } from './types.js';
2
+ export type { Adapter, Encoded } from './types.js';
3
3
  /**
4
4
  * Persistence-aware cache for a single {@link Resource}. Wraps a
5
5
  * **strictly synchronous** {@link Adapter} (localStorage, MMKV,
@@ -1,4 +1,4 @@
1
- export type { Stored } from '../utils/types';
1
+ export type { Stored } from '../utils/types.js';
2
2
  /**
3
3
  * On-disk JSON shape of a `Stored` envelope. The Cache wrapper
4
4
  * encodes a populated Stored as `{ data, at: at.toString() }` so the
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import process from "node:process";
3
+ import { main } from "../lib/index.js";
4
+ main(process.argv.slice(2)).catch((error) => {
5
+ if (error instanceof Error && error.name === "ExitPromptError") {
6
+ process.exit(130);
7
+ }
8
+ console.error(error);
9
+ process.exit(1);
10
+ });
@@ -0,0 +1,14 @@
1
+ import figlet from "figlet";
2
+ import kleur from "kleur";
3
+ import { config } from "../utils.js";
4
+ export function banner() {
5
+ const art = figlet.textSync(config.banner.title, {
6
+ font: config.banner.font,
7
+ horizontalLayout: "default",
8
+ verticalLayout: "default",
9
+ });
10
+ console.log(kleur.magenta(art));
11
+ console.log(kleur.gray(` ${config.banner.tagline} `) +
12
+ kleur.dim(config.banner.subtitle));
13
+ console.log();
14
+ }
@@ -0,0 +1,37 @@
1
+ import path from "node:path";
2
+ import kleur from "kleur";
3
+ import { pascalCase, capitalCase } from "change-case";
4
+ import { scaffold } from "../../runner/index.js";
5
+ import { askName, askDescription, pickDirectory, requireProjectRoot, } from "../../prompt/index.js";
6
+ export async function newPage({ positional, flags, }) {
7
+ const root = requireProjectRoot();
8
+ const name = positional[0] || (await askName("Page name (kebab-case)"));
9
+ const heading = (typeof flags.heading === "string" ? flags.heading : undefined) ||
10
+ capitalCase(name);
11
+ const tagline = (typeof flags.tagline === "string" ? flags.tagline : undefined) ||
12
+ (await askDescription("Page tagline", `Welcome to ${heading}`));
13
+ await scaffold("app", "page", { name, heading, tagline, pascalName: pascalCase(name) }, { cwd: root });
14
+ console.log(kleur.green("\n Page ready."), kleur.dim(`Wire it up in src/app/index.tsx with <${pascalCase(name)}Page />.`));
15
+ }
16
+ export async function integration({ positional }) {
17
+ const root = requireProjectRoot();
18
+ const pagesRoot = path.join(root, "src", "app", "pages");
19
+ const name = positional[0] || (await pickDirectory("page", pagesRoot));
20
+ await scaffold("app", "integration", { name, pascalName: pascalCase(name) }, { cwd: root });
21
+ console.log(kleur.green("\n Integration test added."), kleur.dim(`Run with \`make integration\` or \`npx playwright test\`.`));
22
+ }
23
+ export async function action({ positional, flags, }) {
24
+ const root = requireProjectRoot();
25
+ const pagesRoot = path.join(root, "src", "app", "pages");
26
+ const page = positional[0] || (await pickDirectory("page", pagesRoot));
27
+ const name = positional[1] ||
28
+ (typeof flags.name === "string" ? flags.name : undefined) ||
29
+ (await askName("Action name (PascalCase)"));
30
+ await scaffold("app", "action", {
31
+ page,
32
+ name: pascalCase(name),
33
+ pascalName: pascalCase(name),
34
+ rawName: name,
35
+ }, { cwd: root });
36
+ console.log(kleur.green("\n Action added."), kleur.dim(`Dispatch with actions.dispatch(Actions.${pascalCase(name)}).`));
37
+ }
@@ -0,0 +1,55 @@
1
+ import path from "node:path";
2
+ import kleur from "kleur";
3
+ import { pascalCase } from "change-case";
4
+ import { scaffold } from "../../runner/index.js";
5
+ import { askName, askConfirm, pickDirectory, requireProjectRoot, } from "../../prompt/index.js";
6
+ async function resolveStateful(flags) {
7
+ if (flags.stateful !== undefined)
8
+ return flags.stateful !== false;
9
+ if (flags.presentational !== undefined)
10
+ return flags.presentational === false;
11
+ return askConfirm("Does this feature own state and actions?", true);
12
+ }
13
+ export async function newFeature({ positional, flags, }) {
14
+ const root = requireProjectRoot();
15
+ const name = positional[0] || (await askName("Feature name (kebab-case)"));
16
+ const stateful = await resolveStateful(flags);
17
+ const action = stateful ? "stateful" : "presentational";
18
+ await scaffold("feature", action, { name, pascalName: pascalCase(name) }, { cwd: root });
19
+ console.log(kleur.green("\n Feature ready."), kleur.dim(`Mount it inside a page with <${pascalCase(name)} />.`));
20
+ }
21
+ export async function unit({ positional }) {
22
+ const root = requireProjectRoot();
23
+ const featuresRoot = path.join(root, "src", "features");
24
+ const name = positional[0] || (await pickDirectory("feature", featuresRoot));
25
+ await scaffold("feature", "unit", { name, pascalName: pascalCase(name) }, { cwd: root });
26
+ }
27
+ export async function action({ positional, flags, }) {
28
+ const root = requireProjectRoot();
29
+ const featuresRoot = path.join(root, "src", "features");
30
+ const feature = positional[0] || (await pickDirectory("feature", featuresRoot));
31
+ const name = positional[1] ||
32
+ (typeof flags.name === "string" ? flags.name : undefined) ||
33
+ (await askName("Action name (PascalCase)"));
34
+ await scaffold("feature", "action", {
35
+ feature,
36
+ name: pascalCase(name),
37
+ pascalName: pascalCase(name),
38
+ rawName: name,
39
+ }, { cwd: root });
40
+ }
41
+ export async function multicast({ positional, flags, }) {
42
+ const root = requireProjectRoot();
43
+ const featuresRoot = path.join(root, "src", "features");
44
+ const feature = positional[0] || (await pickDirectory("feature", featuresRoot));
45
+ const name = positional[1] ||
46
+ (typeof flags.name === "string" ? flags.name : undefined) ||
47
+ (await askName("Multicast action (PascalCase)"));
48
+ await scaffold("feature", "multicast", {
49
+ feature,
50
+ featurePascal: pascalCase(feature),
51
+ name: pascalCase(name),
52
+ pascalName: pascalCase(name),
53
+ rawName: name,
54
+ }, { cwd: root });
55
+ }
@@ -0,0 +1,89 @@
1
+ import * as init from "./init/index.js";
2
+ import * as app from "./app/index.js";
3
+ import * as feature from "./feature/index.js";
4
+ import * as shared from "./shared/index.js";
5
+ export const tree = {
6
+ init: {
7
+ leaf: true,
8
+ description: "Bootstrap a new March Hare project",
9
+ run: init.run,
10
+ },
11
+ app: {
12
+ leaf: false,
13
+ description: "Manage the host (pages, integration tests, actions)",
14
+ children: {
15
+ new: {
16
+ leaf: true,
17
+ description: "Create a new page under app/pages/",
18
+ run: app.newPage,
19
+ },
20
+ integration: {
21
+ leaf: true,
22
+ description: "Add an integration test for an existing page",
23
+ run: app.integration,
24
+ },
25
+ action: {
26
+ leaf: true,
27
+ description: "Add a new action handler to an existing page",
28
+ run: app.action,
29
+ },
30
+ },
31
+ },
32
+ feature: {
33
+ leaf: false,
34
+ description: "Manage features (slices, unit tests, actions)",
35
+ children: {
36
+ new: {
37
+ leaf: true,
38
+ description: "Create a new feature slice",
39
+ run: feature.newFeature,
40
+ },
41
+ unit: {
42
+ leaf: true,
43
+ description: "Add a unit test next to an existing feature",
44
+ run: feature.unit,
45
+ },
46
+ action: {
47
+ leaf: true,
48
+ description: "Add a new action handler to an existing feature",
49
+ run: feature.action,
50
+ },
51
+ multicast: {
52
+ leaf: true,
53
+ description: "Add a multicast action to an existing feature's Scope",
54
+ run: feature.multicast,
55
+ },
56
+ },
57
+ },
58
+ shared: {
59
+ leaf: false,
60
+ description: "Manage shared building blocks",
61
+ children: {
62
+ component: {
63
+ leaf: true,
64
+ description: "Create a new shared component",
65
+ run: shared.component,
66
+ },
67
+ resource: {
68
+ leaf: true,
69
+ description: "Create a new shared resource",
70
+ run: shared.resource,
71
+ },
72
+ util: {
73
+ leaf: true,
74
+ description: "Create a new shared utility",
75
+ run: shared.util,
76
+ },
77
+ type: {
78
+ leaf: true,
79
+ description: "Add a shared type/payload/broadcast namespace",
80
+ run: shared.type,
81
+ },
82
+ unit: {
83
+ leaf: true,
84
+ description: "Add a unit test next to an existing shared module",
85
+ run: shared.unit,
86
+ },
87
+ },
88
+ },
89
+ };
@@ -0,0 +1,29 @@
1
+ import path from "node:path";
2
+ import process from "node:process";
3
+ import kleur from "kleur";
4
+ import { pascalCase } from "change-case";
5
+ import { scaffold } from "../../runner/index.js";
6
+ import { askName, askDescription } from "../../prompt/index.js";
7
+ export async function run({ positional, flags }) {
8
+ const rawName = positional[0] ||
9
+ (typeof flags.name === "string" ? flags.name : undefined) ||
10
+ (await askName("Project name", "my-app"));
11
+ const description = (typeof flags.description === "string" ? flags.description : undefined) ||
12
+ (await askDescription("Short description", `A March Hare project: ${rawName}`));
13
+ const apiBase = (typeof flags.apiBase === "string" ? flags.apiBase : undefined) ||
14
+ (await askDescription("Default API base URL", "https://api.example.com"));
15
+ const cwd = path.resolve(process.cwd(), rawName);
16
+ const env = pascalCase(rawName);
17
+ console.log();
18
+ console.log(kleur.bold(` Scaffolding ${kleur.magenta(rawName)} into ${kleur.gray(cwd)}`));
19
+ console.log();
20
+ await scaffold("init", "new", { name: rawName, description, apiBase, env }, { cwd });
21
+ console.log();
22
+ console.log(kleur.green(" Project ready."));
23
+ console.log();
24
+ console.log(kleur.bold(" Next steps:"));
25
+ console.log(` cd ${rawName}`);
26
+ console.log(" yarn install");
27
+ console.log(" yarn dev");
28
+ console.log();
29
+ }