@plumbus/ui 0.1.0
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/dist/.tsbuildinfo +1 -0
- package/dist/generators/__tests__/auth-generator.test.d.ts +2 -0
- package/dist/generators/__tests__/auth-generator.test.d.ts.map +1 -0
- package/dist/generators/__tests__/auth-generator.test.js +222 -0
- package/dist/generators/__tests__/auth-generator.test.js.map +1 -0
- package/dist/generators/__tests__/client-generator.test.d.ts +2 -0
- package/dist/generators/__tests__/client-generator.test.d.ts.map +1 -0
- package/dist/generators/__tests__/client-generator.test.js +224 -0
- package/dist/generators/__tests__/client-generator.test.js.map +1 -0
- package/dist/generators/__tests__/form-generator.test.d.ts +2 -0
- package/dist/generators/__tests__/form-generator.test.d.ts.map +1 -0
- package/dist/generators/__tests__/form-generator.test.js +192 -0
- package/dist/generators/__tests__/form-generator.test.js.map +1 -0
- package/dist/generators/__tests__/nextjs-template.test.d.ts +2 -0
- package/dist/generators/__tests__/nextjs-template.test.d.ts.map +1 -0
- package/dist/generators/__tests__/nextjs-template.test.js +214 -0
- package/dist/generators/__tests__/nextjs-template.test.js.map +1 -0
- package/dist/generators/auth-generator.d.ts +31 -0
- package/dist/generators/auth-generator.d.ts.map +1 -0
- package/dist/generators/auth-generator.js +292 -0
- package/dist/generators/auth-generator.js.map +1 -0
- package/dist/generators/client-generator.d.ts +31 -0
- package/dist/generators/client-generator.d.ts.map +1 -0
- package/dist/generators/client-generator.js +336 -0
- package/dist/generators/client-generator.js.map +1 -0
- package/dist/generators/form-generator.d.ts +47 -0
- package/dist/generators/form-generator.d.ts.map +1 -0
- package/dist/generators/form-generator.js +189 -0
- package/dist/generators/form-generator.js.map +1 -0
- package/dist/generators/index.d.ts +9 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +6 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/nextjs-template.d.ts +44 -0
- package/dist/generators/nextjs-template.d.ts.map +1 -0
- package/dist/generators/nextjs-template.js +444 -0
- package/dist/generators/nextjs-template.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/instructions/auth-generator.md +154 -0
- package/instructions/client-generator.md +149 -0
- package/instructions/form-generator.md +157 -0
- package/instructions/framework.md +108 -0
- package/instructions/nextjs-template.md +160 -0
- package/instructions/patterns.md +109 -0
- package/instructions/testing.md +211 -0
- package/package.json +52 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Client Generator
|
|
2
|
+
|
|
3
|
+
Generates typed fetch-based API clients and React hooks from capability contracts and flow definitions.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
interface ClientGeneratorConfig {
|
|
9
|
+
/** Base URL for API requests (default: "") */
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
/** Include JSDoc comments in generated code */
|
|
12
|
+
includeJsDoc?: boolean;
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Individual Generators
|
|
17
|
+
|
|
18
|
+
### `generateCapabilityTypes(cap)`
|
|
19
|
+
|
|
20
|
+
Produces TypeScript type aliases for a capability's input and output:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
generateCapabilityTypes({ name: "getUser", kind: "query", domain: "users", ... })
|
|
24
|
+
// → export type GetUserInput = Record<string, unknown>;
|
|
25
|
+
// export type GetUserOutput = Record<string, unknown>;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### `generateTypedClient(cap, config?)`
|
|
29
|
+
|
|
30
|
+
Produces an async fetch function for a capability. Route path follows `GET /api/{domain}/{kebab-name}` for queries, `POST` for actions/jobs:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
generateTypedClient({ name: "getUser", kind: "query", domain: "users", ... })
|
|
34
|
+
// → export async function getUser(input: GetUserInput, options?): Promise<GetUserOutput> { ... }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- **GET** capabilities serialize input as query parameters via `URLSearchParams`.
|
|
38
|
+
- **POST** capabilities send input as `JSON.stringify(input)` in the request body.
|
|
39
|
+
- All functions accept `options?: { headers?: Record<string, string>; signal?: AbortSignal }`.
|
|
40
|
+
- Non-OK responses throw an error with `status`, `code`, and `metadata` properties.
|
|
41
|
+
|
|
42
|
+
### `generateQueryHook(cap, config?)`
|
|
43
|
+
|
|
44
|
+
Produces a React hook that auto-fetches on mount/input change:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
generateQueryHook({ name: "getUser", kind: "query", domain: "users", ... })
|
|
48
|
+
// → export function useGetUser(input: GetUserInput) {
|
|
49
|
+
// const [data, setData] = useState<GetUserOutput | null>(null);
|
|
50
|
+
// ...
|
|
51
|
+
// return { data, loading, error };
|
|
52
|
+
// }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- Uses `useState` + `useEffect` with cancellation.
|
|
56
|
+
- Re-fetches when `JSON.stringify(input)` changes.
|
|
57
|
+
|
|
58
|
+
### `generateMutationHook(cap, config?)`
|
|
59
|
+
|
|
60
|
+
Produces a React hook for manual invocation (actions, jobs):
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
generateMutationHook({ name: "createUser", kind: "action", domain: "users", ... })
|
|
64
|
+
// → export function useCreateUser() {
|
|
65
|
+
// ...
|
|
66
|
+
// return { mutate, data, loading, error, reset };
|
|
67
|
+
// }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- `mutate(input)` triggers the request and returns the result.
|
|
71
|
+
- `reset()` clears data and error state.
|
|
72
|
+
|
|
73
|
+
### `generateReactHook(cap, config?)`
|
|
74
|
+
|
|
75
|
+
Dispatches to `generateQueryHook` or `generateMutationHook` based on `cap.kind`:
|
|
76
|
+
- `kind === "query"` → query hook
|
|
77
|
+
- Anything else → mutation hook
|
|
78
|
+
|
|
79
|
+
### `generateFlowTrigger(flow, config?)`
|
|
80
|
+
|
|
81
|
+
Produces a function to start a flow execution:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
interface FlowTriggerInput {
|
|
85
|
+
name: string;
|
|
86
|
+
domain?: string;
|
|
87
|
+
description?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
generateFlowTrigger({ name: "orderFulfillment", domain: "orders" })
|
|
91
|
+
// → export async function startOrderFulfillment(input, options?)
|
|
92
|
+
// : Promise<{ executionId: string; status: string }> { ... }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- POSTs to `/api/{domain}/{kebab-name}/start`.
|
|
96
|
+
|
|
97
|
+
### `generateErrorTypes()`
|
|
98
|
+
|
|
99
|
+
Produces `PlumbusApiError` interface and `isPlumbusApiError()` type guard — always included in module output.
|
|
100
|
+
|
|
101
|
+
## Module Generators
|
|
102
|
+
|
|
103
|
+
### `generateClientModule(capabilities, flows, config?)`
|
|
104
|
+
|
|
105
|
+
Combines all generators into a single client file:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const code = generateClientModule(
|
|
109
|
+
[getUser, createUser, deleteUser],
|
|
110
|
+
[{ name: "orderFulfillment", domain: "orders" }],
|
|
111
|
+
{ baseUrl: "/api", includeJsDoc: true },
|
|
112
|
+
);
|
|
113
|
+
// Output: .plumbus/generated/ui/client.ts (written by CLI)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Output order: error types → capability types → flow types → client functions → flow triggers.
|
|
117
|
+
|
|
118
|
+
### `generateHooksModule(capabilities, config?)`
|
|
119
|
+
|
|
120
|
+
Produces a React hooks file that imports from the client module:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
const code = generateHooksModule([getUser, createUser]);
|
|
124
|
+
// Output: .plumbus/generated/ui/hooks.ts (written by CLI)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- Auto-imports `useState`, `useEffect` from React.
|
|
128
|
+
- Auto-imports types and functions from `./client`.
|
|
129
|
+
|
|
130
|
+
## URL Routing Convention
|
|
131
|
+
|
|
132
|
+
| Capability Kind | HTTP Method | URL Pattern |
|
|
133
|
+
|----------------|-------------|-------------|
|
|
134
|
+
| query | GET | `/api/{domain}/{kebab-name}` |
|
|
135
|
+
| action | POST | `/api/{domain}/{kebab-name}` |
|
|
136
|
+
| job | POST | `/api/{domain}/{kebab-name}` |
|
|
137
|
+
| eventHandler | POST | `/api/{domain}/{kebab-name}` |
|
|
138
|
+
|
|
139
|
+
Flow triggers always POST to `/api/{domain}/{kebab-name}/start`.
|
|
140
|
+
|
|
141
|
+
## Internal Helpers
|
|
142
|
+
|
|
143
|
+
| Helper | Purpose |
|
|
144
|
+
|--------|---------|
|
|
145
|
+
| `toCamelCase(str)` | Capability function names — `getUser` |
|
|
146
|
+
| `toPascalCase(str)` | Type names — `GetUser` |
|
|
147
|
+
| `toKebabCase(str)` | URL path segments — `get-user` |
|
|
148
|
+
| `httpMethod(kind)` | `"query" → "GET"`, everything else → `"POST"` |
|
|
149
|
+
| `capabilityPath(domain, name)` | `/api/{domain}/{kebab-name}` |
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Form Generator
|
|
2
|
+
|
|
3
|
+
Extracts form field metadata from Zod schemas attached to capability contracts. Produces structured hints that UI components can consume for rendering form fields with labels, input types, validation, and options.
|
|
4
|
+
|
|
5
|
+
## Types
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
type FormFieldType = "text" | "number" | "boolean" | "select" | "textarea" | "date" | "hidden";
|
|
9
|
+
|
|
10
|
+
interface FormFieldHint {
|
|
11
|
+
name: string; // Field key from the schema
|
|
12
|
+
label: string; // Human-readable label (auto-derived)
|
|
13
|
+
fieldType: FormFieldType; // Suggested HTML input type
|
|
14
|
+
required: boolean; // true unless ZodOptional or ZodDefault
|
|
15
|
+
defaultValue?: unknown; // From .default() if set
|
|
16
|
+
zodType: string; // Underlying Zod type name (e.g. "ZodString")
|
|
17
|
+
options?: string[]; // For ZodEnum — the allowed values
|
|
18
|
+
validation: FormValidation;
|
|
19
|
+
description?: string; // From .describe() if set
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface FormValidation {
|
|
23
|
+
min?: number; // ZodNumber .min()
|
|
24
|
+
max?: number; // ZodNumber .max()
|
|
25
|
+
minLength?: number; // ZodString .min()
|
|
26
|
+
maxLength?: number; // ZodString .max()
|
|
27
|
+
pattern?: string; // ZodString .regex() source, or "email"/"url"
|
|
28
|
+
nullable?: boolean; // ZodNullable wrapper
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface FormHints {
|
|
32
|
+
capabilityName: string;
|
|
33
|
+
kind: string;
|
|
34
|
+
fields: FormFieldHint[];
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## How It Works
|
|
39
|
+
|
|
40
|
+
The form generator introspects Zod schemas at runtime via their `_def` property. It does **not** use `z.infer` or code generation — it reads the schema tree directly.
|
|
41
|
+
|
|
42
|
+
### Schema Unwrapping
|
|
43
|
+
|
|
44
|
+
Wrapper types are unwrapped to find the underlying type:
|
|
45
|
+
- `ZodOptional` → unwrap `innerType`, mark `required: false`
|
|
46
|
+
- `ZodNullable` → unwrap `innerType`, set `validation.nullable: true`
|
|
47
|
+
- `ZodDefault` → unwrap `innerType`, mark `required: false`, extract default value
|
|
48
|
+
|
|
49
|
+
### Zod Type → Form Field Type Mapping
|
|
50
|
+
|
|
51
|
+
| Zod Type | Form Field Type |
|
|
52
|
+
|----------|----------------|
|
|
53
|
+
| ZodString | `"text"` |
|
|
54
|
+
| ZodNumber, ZodBigInt | `"number"` |
|
|
55
|
+
| ZodBoolean | `"boolean"` |
|
|
56
|
+
| ZodDate | `"date"` |
|
|
57
|
+
| ZodEnum, ZodNativeEnum | `"select"` |
|
|
58
|
+
| ZodObject, ZodArray, ZodRecord | `"textarea"` |
|
|
59
|
+
| Other | `"text"` |
|
|
60
|
+
|
|
61
|
+
### Validation Extraction
|
|
62
|
+
|
|
63
|
+
Checks are read from `_def.checks` array:
|
|
64
|
+
|
|
65
|
+
| Check Kind | Extracted As |
|
|
66
|
+
|------------|-------------|
|
|
67
|
+
| `min` (ZodString) | `minLength` |
|
|
68
|
+
| `max` (ZodString) | `maxLength` |
|
|
69
|
+
| `min` (ZodNumber) | `min` |
|
|
70
|
+
| `max` (ZodNumber) | `max` |
|
|
71
|
+
| `regex` | `pattern` (regex source) |
|
|
72
|
+
| `email` | `pattern: "email"` |
|
|
73
|
+
| `url` | `pattern: "url"` |
|
|
74
|
+
|
|
75
|
+
### Label Generation
|
|
76
|
+
|
|
77
|
+
Field names are converted to title case: `firstName` → `"First Name"`, `user_email` → `"User Email"`.
|
|
78
|
+
|
|
79
|
+
## Functions
|
|
80
|
+
|
|
81
|
+
### `extractFieldHint(name, schema)`
|
|
82
|
+
|
|
83
|
+
Extract metadata for a single Zod field:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { z } from "zod";
|
|
87
|
+
import { extractFieldHint } from "@plumbus/ui";
|
|
88
|
+
|
|
89
|
+
const hint = extractFieldHint("email", z.string().email().describe("User's email"));
|
|
90
|
+
// → { name: "email", label: "Email", fieldType: "text", required: true,
|
|
91
|
+
// zodType: "ZodString", validation: { pattern: "email" }, description: "User's email" }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `extractFormHints(cap)`
|
|
95
|
+
|
|
96
|
+
Extract hints for all fields in a capability's `input` schema (must be `ZodObject`):
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const hints = extractFormHints(createUserCapability);
|
|
100
|
+
// → { capabilityName: "createUser", kind: "action", fields: [...] }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Iterates over the `ZodObject` shape via `_def.shape()`.
|
|
104
|
+
|
|
105
|
+
### `generateFormHintsCode(cap)`
|
|
106
|
+
|
|
107
|
+
Produce a TypeScript constant with the extracted hints:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
generateFormHintsCode(cap)
|
|
111
|
+
// → export const CreateUserFormHints = { ... } as const;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `generateFormHintsModule(capabilities)`
|
|
115
|
+
|
|
116
|
+
Produce a module exporting form hints for all capabilities:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const code = generateFormHintsModule([createUser, updateUser]);
|
|
120
|
+
// Write to: generated/form-hints.ts
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Usage Pattern
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// 1. Extract hints at build time
|
|
127
|
+
const hints = extractFormHints(createUserCapability);
|
|
128
|
+
|
|
129
|
+
// 2. Use hints in a React component
|
|
130
|
+
function DynamicForm({ hints }: { hints: FormHints }) {
|
|
131
|
+
return (
|
|
132
|
+
<form>
|
|
133
|
+
{hints.fields.map((field) => (
|
|
134
|
+
<div key={field.name}>
|
|
135
|
+
<label>{field.label}</label>
|
|
136
|
+
{field.fieldType === "select" ? (
|
|
137
|
+
<select name={field.name} required={field.required}>
|
|
138
|
+
{field.options?.map((opt) => <option key={opt}>{opt}</option>)}
|
|
139
|
+
</select>
|
|
140
|
+
) : (
|
|
141
|
+
<input
|
|
142
|
+
name={field.name}
|
|
143
|
+
type={field.fieldType}
|
|
144
|
+
required={field.required}
|
|
145
|
+
minLength={field.validation.minLength}
|
|
146
|
+
maxLength={field.validation.maxLength}
|
|
147
|
+
min={field.validation.min}
|
|
148
|
+
max={field.validation.max}
|
|
149
|
+
defaultValue={field.defaultValue as string}
|
|
150
|
+
/>
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
))}
|
|
154
|
+
</form>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# @plumbus/ui — UI Code Generation Framework
|
|
2
|
+
|
|
3
|
+
`@plumbus/ui` is the frontend code generation layer for the Plumbus framework. It reads capability contracts, flow definitions, and auth configuration from `@plumbus/core` and produces ready-to-use TypeScript/React source files — typed API clients, React hooks, auth modules, form metadata, and full Next.js project scaffolds.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
All generated output is **source code as strings** — the package does not ship React components or a runtime. Instead, AI agents and CLI tooling call generator functions to produce `.ts`/`.tsx` files that applications import directly.
|
|
8
|
+
|
|
9
|
+
## Package Layout
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
packages/ui/
|
|
13
|
+
src/
|
|
14
|
+
index.ts # Barrel re-exports
|
|
15
|
+
generators/
|
|
16
|
+
index.ts # Generator barrel
|
|
17
|
+
client-generator.ts # Typed fetch clients + React hooks
|
|
18
|
+
auth-generator.ts # Auth types, token utils, hooks, route guard
|
|
19
|
+
form-generator.ts # Zod schema → form field metadata
|
|
20
|
+
nextjs-template.ts # Full Next.js project scaffold
|
|
21
|
+
__tests__/
|
|
22
|
+
client-generator.test.ts
|
|
23
|
+
auth-generator.test.ts
|
|
24
|
+
form-generator.test.ts
|
|
25
|
+
nextjs-template.test.ts
|
|
26
|
+
instructions/ # AI agent instructions (this directory)
|
|
27
|
+
package.json
|
|
28
|
+
tsconfig.json
|
|
29
|
+
vitest.config.browser.ts
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Core Concepts
|
|
33
|
+
|
|
34
|
+
| Concept | Description |
|
|
35
|
+
|---------|-------------|
|
|
36
|
+
| **Generator** | A function that takes a contract/config and returns a string of TypeScript/TSX source code |
|
|
37
|
+
| **CapabilityContract** | Imported from `@plumbus/core` — defines name, domain, kind, input/output schemas, access |
|
|
38
|
+
| **Module generator** | Combines multiple generators into a single file with proper imports |
|
|
39
|
+
| **GeneratedFile** | `{ path: string; content: string }` — represents a file to write to disk |
|
|
40
|
+
|
|
41
|
+
## Generator Categories
|
|
42
|
+
|
|
43
|
+
| Generator | Input | Output |
|
|
44
|
+
|-----------|-------|--------|
|
|
45
|
+
| **Client** | `CapabilityContract[]`, `FlowTriggerInput[]` | Typed fetch functions, React query/mutation hooks |
|
|
46
|
+
| **Auth** | `AuthHelperConfig` | Login/logout, token utils, useAuth hook, RouteGuard, tenant context |
|
|
47
|
+
| **Form** | `CapabilityContract` (with Zod input schema) | Field metadata (type, label, validation, options) |
|
|
48
|
+
| **Next.js** | `NextjsTemplateConfig`, capabilities | Full project: package.json, layout, pages, middleware, API routes |
|
|
49
|
+
|
|
50
|
+
## Relationship to @plumbus/core
|
|
51
|
+
|
|
52
|
+
`@plumbus/ui` depends on `@plumbus/core` for:
|
|
53
|
+
- `CapabilityContract` type — the shape of capability definitions
|
|
54
|
+
- Zod schemas — introspected at runtime for form hint extraction
|
|
55
|
+
- No runtime coupling — generators produce standalone code
|
|
56
|
+
|
|
57
|
+
## Key Imports
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import {
|
|
61
|
+
// Client generators
|
|
62
|
+
generateClientModule,
|
|
63
|
+
generateHooksModule,
|
|
64
|
+
generateTypedClient,
|
|
65
|
+
generateReactHook,
|
|
66
|
+
generateCapabilityTypes,
|
|
67
|
+
generateFlowTrigger,
|
|
68
|
+
generateErrorTypes,
|
|
69
|
+
|
|
70
|
+
// Auth generators
|
|
71
|
+
generateAuthModule,
|
|
72
|
+
generateAuthTypes,
|
|
73
|
+
generateTokenUtils,
|
|
74
|
+
generateAuthFunctions,
|
|
75
|
+
generateUseAuthHook,
|
|
76
|
+
generateUseCurrentUserHook,
|
|
77
|
+
generateRouteGuard,
|
|
78
|
+
generateTenantContext,
|
|
79
|
+
|
|
80
|
+
// Form generators
|
|
81
|
+
extractFormHints,
|
|
82
|
+
extractFieldHint,
|
|
83
|
+
generateFormHintsCode,
|
|
84
|
+
generateFormHintsModule,
|
|
85
|
+
|
|
86
|
+
// Next.js generators
|
|
87
|
+
generateNextjsTemplate,
|
|
88
|
+
generateLayout,
|
|
89
|
+
generateHomePage,
|
|
90
|
+
generateCapabilityPage,
|
|
91
|
+
generateMiddleware,
|
|
92
|
+
generateApiRouteHelper,
|
|
93
|
+
generateAuthProvider,
|
|
94
|
+
generateErrorBoundary,
|
|
95
|
+
generateLoadingComponent,
|
|
96
|
+
generatePackageJson,
|
|
97
|
+
generateTsConfig,
|
|
98
|
+
generateEnvLocal,
|
|
99
|
+
generatePlaceholderFiles,
|
|
100
|
+
} from "@plumbus/ui";
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## How Agents Should Use This Package
|
|
104
|
+
|
|
105
|
+
1. **Run `plumbus ui generate`** — auto-detects the frontend directory (e.g., `frontend/`) and writes typed client, hooks, auth, and form-hints modules to `{frontend}/generated/`. Also writes to `.plumbus/generated/ui/` as a contract artifact cache.
|
|
106
|
+
2. **Run `plumbus ui nextjs <dir>`** — scaffolds a Next.js project that includes the generated modules in `{dir}/generated/`. The frontend imports them via `@/generated/hooks`, `@/generated/client`, etc.
|
|
107
|
+
3. **Never copy generated files manually.** Re-running `plumbus ui generate` updates `{frontend}/generated/` in place. The CLI auto-detects the frontend by looking for `tsconfig.json` in `frontend/`, `web/`, `client/`, or `app/`.
|
|
108
|
+
4. To customize the output location: `plumbus ui generate --out-dir path/to/generated`.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Next.js Template Generator
|
|
2
|
+
|
|
3
|
+
Scaffolds a complete Next.js 14 project wired to a Plumbus backend — layout, pages, auth, middleware, API proxy, error boundary, and environment config.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
interface NextjsTemplateConfig {
|
|
9
|
+
appName: string; // Application display name
|
|
10
|
+
auth?: boolean; // Include auth wiring (default: true)
|
|
11
|
+
apiBaseUrl?: string; // Backend URL (default: "http://localhost:3000")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface GeneratedFile {
|
|
15
|
+
path: string; // Relative file path within the project
|
|
16
|
+
content: string; // File contents
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Full Scaffold
|
|
21
|
+
|
|
22
|
+
### `generateNextjsTemplate(config, capabilities?)`
|
|
23
|
+
|
|
24
|
+
Returns `GeneratedFile[]` — a complete Next.js project. Write each file to disk:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const files = generateNextjsTemplate(
|
|
28
|
+
{ appName: "My App", auth: true, apiBaseUrl: "http://localhost:3000" },
|
|
29
|
+
[getUser, createUser],
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
writeFileSync(join(outputDir, file.path), file.content);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Generated Project Structure
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
package.json # Next.js 14, React 18, TypeScript 5, Tailwind CSS 4
|
|
41
|
+
tsconfig.json # Strict, bundler module resolution
|
|
42
|
+
postcss.config.mjs # PostCSS config with @tailwindcss/postcss
|
|
43
|
+
.env.local # API base URL, auth flag, secrets
|
|
44
|
+
middleware.ts # Auth token check, protected paths
|
|
45
|
+
app/
|
|
46
|
+
globals.css # Tailwind import + base resets
|
|
47
|
+
layout.tsx # Root layout (with AuthProvider if auth)
|
|
48
|
+
page.tsx # Home page
|
|
49
|
+
loading.tsx # Global loading skeleton
|
|
50
|
+
error.tsx # Global error boundary
|
|
51
|
+
{capability-slug}/page.tsx # Per-capability pages (query or action)
|
|
52
|
+
api/plumbus/[...path]/route.ts # API proxy to Plumbus backend
|
|
53
|
+
components/
|
|
54
|
+
AuthProvider.tsx # Context-based auth provider (if auth)
|
|
55
|
+
generated/
|
|
56
|
+
.gitkeep # Placeholder for generated client files
|
|
57
|
+
hooks/
|
|
58
|
+
.gitkeep # Placeholder for custom hooks
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Individual File Generators
|
|
62
|
+
|
|
63
|
+
### `generatePackageJson(config)`
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"name": "{kebab-case-app-name}",
|
|
68
|
+
"dependencies": { "next": "^14", "react": "^18", "react-dom": "^18", "tailwindcss": "^4", "@tailwindcss/postcss": "^4" },
|
|
69
|
+
"devDependencies": { "typescript": "^5", "@types/react": "^18", "@types/react-dom": "^18" }
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `generateTsConfig()`
|
|
74
|
+
|
|
75
|
+
Strict TypeScript config for Next.js: `target: "ES2017"`, `module: "esnext"`, `moduleResolution: "bundler"`, `jsx: "preserve"`, path alias `@/*`.
|
|
76
|
+
|
|
77
|
+
### `generateGlobalsCss()`
|
|
78
|
+
|
|
79
|
+
Minimal `app/globals.css` with Tailwind CSS import and base resets (box-sizing, margin, antialiased text). Apps customize by extending this file with their own theme tokens.
|
|
80
|
+
|
|
81
|
+
### `generatePostcssConfig()`
|
|
82
|
+
|
|
83
|
+
PostCSS config at `postcss.config.mjs` with `@tailwindcss/postcss` plugin. Required for Tailwind CSS 4 processing.
|
|
84
|
+
|
|
85
|
+
### `generateLayout(config)`
|
|
86
|
+
|
|
87
|
+
Root layout with `<html>` + `<body>`. Imports `./globals.css` for Tailwind styles. If `auth !== false`, wraps children in `<AuthProvider>`.
|
|
88
|
+
|
|
89
|
+
### `generateHomePage(config)`
|
|
90
|
+
|
|
91
|
+
Simple welcome page with app name and description text.
|
|
92
|
+
|
|
93
|
+
### `generateCapabilityPage(cap)`
|
|
94
|
+
|
|
95
|
+
Route: `app/{kebab-name}/page.tsx`
|
|
96
|
+
|
|
97
|
+
Generated page depends on capability kind:
|
|
98
|
+
|
|
99
|
+
| Kind | UI Pattern |
|
|
100
|
+
|------|-----------|
|
|
101
|
+
| `query` | Auto-fetches with `use{Name}({})`, shows loading/error/data states |
|
|
102
|
+
| `action`/`job` | Form with `handleSubmit`, uses `use{Name}()` mutation hook, shows submit/loading/error/result |
|
|
103
|
+
|
|
104
|
+
All pages use `"use client"` directive and import hooks from `@/generated/hooks`.
|
|
105
|
+
|
|
106
|
+
### `generateAuthProvider()`
|
|
107
|
+
|
|
108
|
+
Context-based provider at `components/AuthProvider.tsx`:
|
|
109
|
+
- Creates `AuthContext` with `AuthState`.
|
|
110
|
+
- On mount: checks stored token, refreshes session.
|
|
111
|
+
- Exports `useAuthContext()` hook.
|
|
112
|
+
- Imports from `@/generated/auth`.
|
|
113
|
+
|
|
114
|
+
### `generateMiddleware(config)`
|
|
115
|
+
|
|
116
|
+
Next.js middleware at `middleware.ts`:
|
|
117
|
+
- When auth enabled: checks `auth_token` cookie for protected paths (`/dashboard`, `/settings`, `/api/protected`).
|
|
118
|
+
- Redirects to `/login` if no token.
|
|
119
|
+
- Matcher excludes `_next/static`, `_next/image`, and `favicon.ico`.
|
|
120
|
+
|
|
121
|
+
### `generateApiRouteHelper(config)`
|
|
122
|
+
|
|
123
|
+
Catch-all API proxy at `app/api/plumbus/[...path]/route.ts`:
|
|
124
|
+
- Forwards requests to `{apiBaseUrl}/{path}`.
|
|
125
|
+
- Preserves auth headers, query parameters, and request method.
|
|
126
|
+
- Supports GET, POST, PUT, PATCH, DELETE.
|
|
127
|
+
|
|
128
|
+
### `generateEnvLocal(config)`
|
|
129
|
+
|
|
130
|
+
Environment variables template:
|
|
131
|
+
```env
|
|
132
|
+
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000
|
|
133
|
+
NEXT_PUBLIC_AUTH_ENABLED=true
|
|
134
|
+
AUTH_SECRET=change-me-in-production
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `generateErrorBoundary()`
|
|
138
|
+
|
|
139
|
+
Client error component at `app/error.tsx` — logs error, shows message, provides retry button.
|
|
140
|
+
|
|
141
|
+
### `generateLoadingComponent()`
|
|
142
|
+
|
|
143
|
+
Loading skeleton at `app/loading.tsx` with `role="status"` and `aria-label="Loading"`.
|
|
144
|
+
|
|
145
|
+
### `generatePlaceholderFiles()`
|
|
146
|
+
|
|
147
|
+
Returns `generated/.gitkeep` and `hooks/.gitkeep`.
|
|
148
|
+
|
|
149
|
+
## Auth Integration
|
|
150
|
+
|
|
151
|
+
When `auth: true` (default):
|
|
152
|
+
1. `AuthProvider` wraps the app layout.
|
|
153
|
+
2. Middleware protects configured paths.
|
|
154
|
+
3. Generated pages can use `useAuthContext()`.
|
|
155
|
+
4. API proxy forwards `Authorization` headers.
|
|
156
|
+
|
|
157
|
+
When `auth: false`:
|
|
158
|
+
- No `AuthProvider` import in layout.
|
|
159
|
+
- Middleware runs but skips auth checks.
|
|
160
|
+
- Pages still work for public capabilities.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Patterns & Conventions
|
|
2
|
+
|
|
3
|
+
## Naming Conventions
|
|
4
|
+
|
|
5
|
+
| Element | Convention | Example |
|
|
6
|
+
|---------|-----------|---------|
|
|
7
|
+
| Generator function | `generate{Thing}` | `generateClientModule`, `generateAuthModule` |
|
|
8
|
+
| Extractor function | `extract{Thing}` | `extractFormHints`, `extractFieldHint` |
|
|
9
|
+
| Generated React hook | `use{PascalName}` | `useGetUser`, `useCreateOrder` |
|
|
10
|
+
| Generated client function | `{camelName}` | `getUser`, `createOrder` |
|
|
11
|
+
| Generated type | `{PascalName}Input` / `{PascalName}Output` | `GetUserInput`, `GetUserOutput` |
|
|
12
|
+
| Generated flow trigger | `start{PascalName}` | `startOrderFulfillment` |
|
|
13
|
+
| Generated file | `kebab-case.ts` / `.tsx` | `client.ts`, `auth.ts`, `form-hints.ts` |
|
|
14
|
+
| Test file | `{generator-name}.test.ts` | `client-generator.test.ts` |
|
|
15
|
+
|
|
16
|
+
## File Structure
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
packages/ui/
|
|
20
|
+
src/
|
|
21
|
+
index.ts # Barrel re-exports (types + functions)
|
|
22
|
+
generators/
|
|
23
|
+
index.ts # Generator barrel
|
|
24
|
+
client-generator.ts # Typed clients + React hooks
|
|
25
|
+
auth-generator.ts # Auth types, functions, hooks
|
|
26
|
+
form-generator.ts # Zod introspection + form hints
|
|
27
|
+
nextjs-template.ts # Full Next.js scaffolding
|
|
28
|
+
__tests__/
|
|
29
|
+
client-generator.test.ts
|
|
30
|
+
auth-generator.test.ts
|
|
31
|
+
form-generator.test.ts
|
|
32
|
+
nextjs-template.test.ts
|
|
33
|
+
instructions/ # AI agent instructions
|
|
34
|
+
package.json
|
|
35
|
+
tsconfig.json
|
|
36
|
+
vitest.config.browser.ts
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Do's
|
|
40
|
+
|
|
41
|
+
- **Do** use the `CapabilityContract` type from `@plumbus/core` as the input for all generators that work with capabilities.
|
|
42
|
+
- **Do** return plain strings from generators — they produce source code, not runtime objects.
|
|
43
|
+
- **Do** use `GeneratedFile[]` for multi-file generators (Next.js template) so callers can write files to disk.
|
|
44
|
+
- **Do** add the `// Auto-generated by @plumbus/ui — do not edit` comment at the top of module generators.
|
|
45
|
+
- **Do** handle SSR safety in generated auth code — guard `localStorage` with `typeof window === "undefined"`.
|
|
46
|
+
- **Do** import React hooks (`useState`, `useEffect`) in generated hook modules.
|
|
47
|
+
- **Do** use the same naming helpers (`toCamelCase`, `toPascalCase`, `toKebabCase`) consistently across generators.
|
|
48
|
+
- **Do** keep generated code self-contained — each module generator produces a file that works with only its stated imports.
|
|
49
|
+
- **Do** support optional `ClientGeneratorConfig` / `AuthHelperConfig` / `NextjsTemplateConfig` for customization.
|
|
50
|
+
- **Do** unwrap Zod wrappers (`ZodOptional`, `ZodNullable`, `ZodDefault`) before checking the underlying type.
|
|
51
|
+
|
|
52
|
+
## Don'ts
|
|
53
|
+
|
|
54
|
+
- **Don't** generate code that depends on `@plumbus/ui` at runtime — generated code imports from React, a client module, or `@/generated/*` paths only.
|
|
55
|
+
- **Don't** use `z.infer` in generators — introspect schemas via `_def` for form hints.
|
|
56
|
+
- **Don't** mutate `CapabilityContract` objects — generators are pure functions.
|
|
57
|
+
- **Don't** hardcode API paths — always derive from `domain` + `name` using `capabilityPath()`.
|
|
58
|
+
- **Don't** embed secrets in generated code — use environment variables (`process.env`).
|
|
59
|
+
- **Don't** add framework-specific runtime dependencies to generated Next.js projects (no `@plumbus/core` import in generated frontend code).
|
|
60
|
+
- **Don't** edit files in `generated/` directories manually — they are regenerated.
|
|
61
|
+
|
|
62
|
+
## Common Workflows
|
|
63
|
+
|
|
64
|
+
### Generate a Full Frontend from Capabilities
|
|
65
|
+
|
|
66
|
+
Use the CLI — never copy files manually:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Scaffold a Next.js app (includes generated modules in frontend/generated/)
|
|
70
|
+
plumbus ui nextjs frontend
|
|
71
|
+
|
|
72
|
+
# Regenerate UI modules after changing capabilities
|
|
73
|
+
# Auto-detects frontend/ and writes to frontend/generated/
|
|
74
|
+
plumbus ui generate
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The `plumbus ui generate` command auto-detects the frontend directory by looking for `tsconfig.json` in `frontend/`, `web/`, `client/`, or `app/`. Override with `--out-dir`.
|
|
78
|
+
|
|
79
|
+
### Scaffold a New Next.js Application
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { generateNextjsTemplate } from "@plumbus/ui";
|
|
83
|
+
|
|
84
|
+
const files = generateNextjsTemplate({ appName: "My App", auth: true }, capabilities);
|
|
85
|
+
for (const file of files) {
|
|
86
|
+
mkdirSync(dirname(join(outDir, file.path)), { recursive: true });
|
|
87
|
+
writeFileSync(join(outDir, file.path), file.content);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Add a New Generator
|
|
92
|
+
|
|
93
|
+
1. Create `src/generators/{name}-generator.ts`.
|
|
94
|
+
2. Export individual generator functions + a module-level `generate{Name}Module()`.
|
|
95
|
+
3. Re-export from `src/generators/index.ts` and `src/index.ts`.
|
|
96
|
+
4. Add tests at `src/generators/__tests__/{name}-generator.test.ts`.
|
|
97
|
+
5. Add an instruction file at `instructions/{name}-generator.md`.
|
|
98
|
+
|
|
99
|
+
## Generated Code Output Conventions
|
|
100
|
+
|
|
101
|
+
| Convention | Rule |
|
|
102
|
+
|-----------|------|
|
|
103
|
+
| Module header | `// Auto-generated by @plumbus/ui — do not edit` |
|
|
104
|
+
| React imports | `import { useState, useEffect } from "react"` |
|
|
105
|
+
| Client imports | `import { functionName } from "./client"` |
|
|
106
|
+
| Auth imports | `import { getStoredToken, ... } from "@/generated/auth"` |
|
|
107
|
+
| Type imports | `import type { ... } from "./client"` |
|
|
108
|
+
| Error handling | Throw enriched `Error` with `status`, `code`, `metadata` |
|
|
109
|
+
| Cancellation | Query hooks track `cancelled` flag for cleanup |
|