@eventmodelers/cli 1.0.53 → 1.0.55

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 (53) hide show
  1. package/README.md +6 -0
  2. package/cli.js +29 -1
  3. package/package.json +1 -1
  4. package/shared/build-kit/lib/ralph.js +103 -5
  5. package/shared/build-kit/lib/util/find-slice.cjs +2 -0
  6. package/shared/skills/learn-eventmodelers-api/SKILL.md +3 -0
  7. package/stacks/node/templates/build-kit/lib/check-commit-scope.cjs +17 -4
  8. package/stacks/node/templates/root/.githooks/pre-commit +10 -3
  9. package/stacks/react/templates/.claude/skills/build-automation/SKILL.md +42 -0
  10. package/stacks/react/templates/.claude/skills/build-state-change/SKILL.md +43 -0
  11. package/stacks/react/templates/.claude/skills/build-state-view/SKILL.md +42 -0
  12. package/stacks/react/templates/build-kit/CLAUDE.md +62 -0
  13. package/stacks/react/templates/build-kit/README.md +79 -0
  14. package/stacks/react/templates/build-kit/lib/AGENT.md +47 -0
  15. package/stacks/react/templates/build-kit/lib/backend-prompt.md +135 -0
  16. package/stacks/react/templates/build-kit/lib/prompt.md +139 -0
  17. package/stacks/react/templates/build-kit/lib/ralph.js +508 -0
  18. package/stacks/react/templates/build-kit/package.json +9 -0
  19. package/stacks/react/templates/build-kit/ralph-claude.js +107 -0
  20. package/stacks/react/templates/build-kit/ralph-ollama.js +40 -0
  21. package/stacks/supabase/templates/build-kit/lib/check-commit-scope.cjs +17 -4
  22. package/stacks/supabase/templates/root/.githooks/pre-commit +10 -3
  23. package/stacks/supabase-react/templates/.claude/skills/build-state-change/SKILL.md +305 -0
  24. package/stacks/supabase-react/templates/.claude/skills/build-state-view/SKILL.md +238 -0
  25. package/stacks/supabase-react/templates/.claude/skills/init-style-guide/SKILL.md +60 -0
  26. package/stacks/supabase-react/templates/.claude/skills/learn-styleguide/SKILL.md +28 -0
  27. package/stacks/supabase-react/templates/.claude/skills/learn-styleguide/references/README.md +5 -0
  28. package/stacks/supabase-react/templates/build-kit/CLAUDE.md +149 -0
  29. package/stacks/supabase-react/templates/build-kit/lib/AGENT.md +47 -0
  30. package/stacks/supabase-react/templates/build-kit/lib/backend-prompt.md +139 -0
  31. package/stacks/supabase-react/templates/build-kit/lib/prompt.md +145 -0
  32. package/stacks/supabase-react/templates/root/.env.example +12 -0
  33. package/stacks/supabase-react/templates/root/.oxlintrc.json +9 -0
  34. package/stacks/supabase-react/templates/root/README.md +49 -0
  35. package/stacks/supabase-react/templates/root/index.html +13 -0
  36. package/stacks/supabase-react/templates/root/package.json +26 -0
  37. package/stacks/supabase-react/templates/root/public/favicon.svg +1 -0
  38. package/stacks/supabase-react/templates/root/public/icons.svg +24 -0
  39. package/stacks/supabase-react/templates/root/src/App.css +184 -0
  40. package/stacks/supabase-react/templates/root/src/App.tsx +122 -0
  41. package/stacks/supabase-react/templates/root/src/assets/hero.png +0 -0
  42. package/stacks/supabase-react/templates/root/src/assets/react.svg +1 -0
  43. package/stacks/supabase-react/templates/root/src/assets/vite.svg +1 -0
  44. package/stacks/supabase-react/templates/root/src/index.css +111 -0
  45. package/stacks/supabase-react/templates/root/src/lib/api.ts +127 -0
  46. package/stacks/supabase-react/templates/root/src/lib/supabase.ts +10 -0
  47. package/stacks/supabase-react/templates/root/src/main.tsx +10 -0
  48. package/stacks/supabase-react/templates/root/src/slices/.gitkeep +0 -0
  49. package/stacks/supabase-react/templates/root/src/vite-env.d.ts +13 -0
  50. package/stacks/supabase-react/templates/root/tsconfig.app.json +26 -0
  51. package/stacks/supabase-react/templates/root/tsconfig.json +7 -0
  52. package/stacks/supabase-react/templates/root/tsconfig.node.json +23 -0
  53. package/stacks/supabase-react/templates/root/vite.config.ts +7 -0
@@ -0,0 +1,127 @@
1
+ import { supabase } from './supabase';
2
+
3
+ /**
4
+ * Common API client — the ONLY place that knows about base URLs, auth headers,
5
+ * correlation/causation tracing, and how to reach Supabase tables. Slice components
6
+ * must never call `fetch` directly, read the Supabase session, or import
7
+ * `./supabase` themselves — they call `postCommand`/`queryReadModel` and handle only
8
+ * the request/response shape their command/read model defines.
9
+ *
10
+ * Every call here has two implementations: a real one (Supabase) and a mock one that
11
+ * serves a numbered sample instead. This is what makes every slice component testable
12
+ * in isolation — flip `VITE_DATA_MODE` to `mock` and every read/write in the app is
13
+ * served from `samples/sample-<VITE_MOCK_SAMPLE>.json` files instead of a live backend.
14
+ */
15
+
16
+ export const API_BASE = import.meta.env.VITE_API_BASE ?? '';
17
+
18
+ /** `true` when reads/writes should be served from samples instead of Supabase/the backend. */
19
+ export function isMockMode(): boolean {
20
+ return import.meta.env.VITE_DATA_MODE === 'mock';
21
+ }
22
+
23
+ /** Which numbered sample set is active in mock mode, e.g. `"1"`, `"2"`. */
24
+ export function activeMockSample(): string {
25
+ return import.meta.env.VITE_MOCK_SAMPLE ?? '1';
26
+ }
27
+
28
+ async function getAuthHeaders(): Promise<Record<string, string>> {
29
+ const {
30
+ data: { session },
31
+ } = await supabase.auth.getSession();
32
+
33
+ return {
34
+ 'Content-Type': 'application/json',
35
+ Authorization: session ? `Bearer ${session.access_token}` : '',
36
+ 'x-correlation-id': crypto.randomUUID(),
37
+ 'x-causation-id': crypto.randomUUID(),
38
+ };
39
+ }
40
+
41
+ export class ApiError extends Error {
42
+ status: number;
43
+ body: unknown;
44
+
45
+ constructor(message: string, status: number, body?: unknown) {
46
+ super(message);
47
+ this.name = 'ApiError';
48
+ this.status = status;
49
+ this.body = body;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * POST a command to `path` (taken verbatim from the command's API description in
55
+ * slice.json — never invented) with `body` as the JSON payload. Resolves with the
56
+ * parsed JSON response, or throws `ApiError` on a non-2xx response.
57
+ *
58
+ * In mock mode this never hits the network — it resolves with `{ ok: true }` so a
59
+ * command component can be exercised (submitting/success states) fully offline.
60
+ */
61
+ export async function postCommand<TResponse = unknown>(
62
+ path: string,
63
+ body: unknown,
64
+ ): Promise<TResponse> {
65
+ if (isMockMode()) {
66
+ return { ok: true } as TResponse;
67
+ }
68
+
69
+ const headers = await getAuthHeaders();
70
+ const res = await fetch(`${API_BASE}${path}`, {
71
+ method: 'POST',
72
+ headers,
73
+ body: JSON.stringify(body),
74
+ });
75
+
76
+ const payload = await res.json().catch(() => undefined);
77
+
78
+ if (!res.ok) {
79
+ const message =
80
+ (payload && (payload.error || payload.message)) || `Request failed with status ${res.status}`;
81
+ throw new ApiError(message, res.status, payload);
82
+ }
83
+
84
+ return payload as TResponse;
85
+ }
86
+
87
+ export interface ReadModelParams {
88
+ /** `readmodel.apiEndpoint`, verbatim — the Postgres table/view name. */
89
+ table: string;
90
+ /** Column names to select, comma-separated (never `'*'`). */
91
+ select: string;
92
+ /** Simple equality filters, e.g. `{ customer_id: customerId }`. */
93
+ filters?: Record<string, unknown>;
94
+ /** `true` for a single-row read model (`.maybeSingle()`), `false` for a list. */
95
+ single?: boolean;
96
+ }
97
+
98
+ /**
99
+ * Read a read model's data — `params.table` via Supabase in the real implementation,
100
+ * or `samples[activeMockSample()]` in mock mode. `samples` is this component's own
101
+ * `samples/sample-*.json` map (see the component for how it's built); every read model
102
+ * component supplies its own, `queryReadModel` never reaches into a slice folder itself.
103
+ */
104
+ export async function queryReadModel<T>(
105
+ params: ReadModelParams,
106
+ samples?: Record<string, T>,
107
+ ): Promise<T> {
108
+ if (isMockMode()) {
109
+ const n = activeMockSample();
110
+ const sample = samples?.[n];
111
+ if (sample === undefined) {
112
+ throw new Error(
113
+ `Mock mode is on (VITE_MOCK_SAMPLE=${n}) but no sample-${n}.json exists for table "${params.table}" — add one under this component's samples/ folder.`,
114
+ );
115
+ }
116
+ return sample;
117
+ }
118
+
119
+ let query = supabase.from(params.table).select(params.select);
120
+ for (const [column, value] of Object.entries(params.filters ?? {})) {
121
+ query = query.eq(column, value);
122
+ }
123
+
124
+ const { data, error } = params.single ? await query.maybeSingle() : await query;
125
+ if (error) throw new ApiError(error.message, 500, error);
126
+ return data as T;
127
+ }
@@ -0,0 +1,10 @@
1
+ import { createClient, type SupabaseClient } from '@supabase/supabase-js';
2
+
3
+ const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
4
+ const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
5
+
6
+ if (!supabaseUrl || !supabaseAnonKey) {
7
+ throw new Error('Missing VITE_SUPABASE_URL / VITE_SUPABASE_ANON_KEY environment variables');
8
+ }
9
+
10
+ export const supabase: SupabaseClient = createClient(supabaseUrl, supabaseAnonKey);
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -0,0 +1,13 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_SUPABASE_URL: string;
5
+ readonly VITE_SUPABASE_ANON_KEY: string;
6
+ readonly VITE_API_BASE: string;
7
+ readonly VITE_DATA_MODE?: 'mock' | 'supabase';
8
+ readonly VITE_MOCK_SAMPLE?: string;
9
+ }
10
+
11
+ interface ImportMeta {
12
+ readonly env: ImportMetaEnv;
13
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023", "DOM"],
6
+ "module": "esnext",
7
+ "types": ["vite/client"],
8
+ "allowArbitraryExtensions": true,
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Linting */
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": true,
22
+ "erasableSyntaxOnly": true,
23
+ "noFallthroughCasesInSwitch": true
24
+ },
25
+ "include": ["src"]
26
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023"],
6
+ "types": ["node"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "module": "nodenext",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "erasableSyntaxOnly": true,
20
+ "noFallthroughCasesInSwitch": true
21
+ },
22
+ "include": ["vite.config.ts"]
23
+ }
@@ -0,0 +1,7 @@
1
+ import react from '@vitejs/plugin-react'
2
+ import { defineConfig } from 'vite'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })