@pikku/cli 0.12.24 → 0.12.26

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 (151) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/index-Ba9K10XZ.js +232 -0
  3. package/console-app/index.html +1 -1
  4. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  5. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-channel.js +21 -1
  8. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  9. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  10. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +50 -0
  12. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  13. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  15. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  16. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  17. package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
  18. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  19. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  20. package/dist/.pikku/function/pikku-functions-meta.gen.json +183 -104
  21. package/dist/.pikku/function/pikku-functions.gen.js +3 -1
  22. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  23. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  24. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  25. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  26. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  27. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  28. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  29. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  30. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  31. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  32. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  33. package/dist/.pikku/pikku-services.gen.d.ts +3 -1
  34. package/dist/.pikku/pikku-services.gen.js +2 -0
  35. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  36. package/dist/.pikku/pikku-types.gen.js +1 -1
  37. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  38. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  39. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  40. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  41. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  42. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  43. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +13 -9
  44. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  45. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  46. package/dist/.pikku/schemas/register.gen.js +17 -5
  47. package/dist/.pikku/schemas/schemas/DbAuditInput.schema.json +1 -0
  48. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  49. package/dist/.pikku/schemas/schemas/PikkuEmailsOutput.schema.json +1 -0
  50. package/dist/.pikku/schemas/schemas/PikkuFunctionTypesSplitInput.schema.json +1 -0
  51. package/dist/.pikku/schemas/schemas/PikkuTestsCoverageInput.schema.json +1 -1
  52. package/dist/.pikku/schemas/schemas/PikkuTriggerTypesInput.schema.json +1 -0
  53. package/dist/.pikku/schemas/schemas/WorkspaceValidateInput.schema.json +1 -0
  54. package/dist/.pikku/schemas/schemas/WorkspaceValidateOutput.schema.json +1 -0
  55. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  56. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  57. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  58. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  59. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  60. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  61. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  62. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  63. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  64. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  65. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +5 -5
  66. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  67. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  68. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  69. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  70. package/dist/bin/pikku-bin.mjs +2 -2
  71. package/dist/src/cli.wiring.js +39 -0
  72. package/dist/src/fabric/functions/validate-core.d.ts +20 -0
  73. package/dist/src/fabric/functions/validate-core.js +227 -0
  74. package/dist/src/fabric/functions/validate.function.js +12 -4
  75. package/dist/src/functions/commands/bootstrap.js +2 -2
  76. package/dist/src/functions/commands/console.js +7 -4
  77. package/dist/src/functions/commands/db-audit.d.ts +1 -0
  78. package/dist/src/functions/commands/db-audit.js +67 -0
  79. package/dist/src/functions/commands/db-migrate.js +7 -11
  80. package/dist/src/functions/commands/db-reset.js +12 -12
  81. package/dist/src/functions/commands/db-seed.js +11 -11
  82. package/dist/src/functions/commands/db-shared.d.ts +4 -19
  83. package/dist/src/functions/commands/db-shared.js +53 -17
  84. package/dist/src/functions/commands/dev.js +25 -14
  85. package/dist/src/functions/commands/emails-init.d.ts +5 -0
  86. package/dist/src/functions/commands/emails-init.js +162 -0
  87. package/dist/src/functions/commands/load-user-project.js +12 -3
  88. package/dist/src/functions/commands/new-addon.js +2 -2
  89. package/dist/src/functions/commands/tests-coverage.d.ts +3 -0
  90. package/dist/src/functions/commands/tests-coverage.js +34 -0
  91. package/dist/src/functions/commands/watch.js +7 -4
  92. package/dist/src/functions/commands/workspace-validate.d.ts +33 -0
  93. package/dist/src/functions/commands/workspace-validate.js +9 -0
  94. package/dist/src/functions/db/annotation-parser.d.ts +31 -0
  95. package/dist/src/functions/db/annotation-parser.js +93 -0
  96. package/dist/src/functions/db/coercion-plugin.d.ts +7 -0
  97. package/dist/src/functions/db/coercion-plugin.js +99 -0
  98. package/dist/src/functions/db/db-codegen.d.ts +24 -0
  99. package/dist/src/functions/db/db-codegen.js +276 -0
  100. package/dist/src/functions/db/db-introspector.d.ts +15 -0
  101. package/dist/src/functions/db/db-introspector.js +1 -0
  102. package/dist/src/functions/db/db-migrator.d.ts +32 -0
  103. package/dist/src/functions/db/db-migrator.js +65 -0
  104. package/dist/src/functions/db/local-db.d.ts +27 -33
  105. package/dist/src/functions/db/local-db.js +108 -57
  106. package/dist/src/functions/db/postgres/postgres-introspector.d.ts +10 -0
  107. package/dist/src/functions/db/postgres/postgres-introspector.js +54 -0
  108. package/dist/src/functions/db/postgres/postgres-migrator.d.ts +9 -0
  109. package/dist/src/functions/db/postgres/postgres-migrator.js +32 -0
  110. package/dist/src/functions/db/{seed.d.ts → sqlite/seed.d.ts} +2 -2
  111. package/dist/src/functions/db/sqlite/sqlite-introspector.d.ts +9 -0
  112. package/dist/src/functions/db/sqlite/sqlite-introspector.js +35 -0
  113. package/dist/src/functions/db/sqlite/sqlite-kysely.d.ts +8 -0
  114. package/dist/src/functions/db/sqlite/sqlite-kysely.js +62 -0
  115. package/dist/src/functions/db/sqlite/sqlite-migrator.d.ts +10 -0
  116. package/dist/src/functions/db/sqlite/sqlite-migrator.js +36 -0
  117. package/dist/src/functions/db/sqlite/sqlite-runtime-bun.d.ts +2 -0
  118. package/dist/src/functions/db/sqlite/sqlite-runtime-bun.js +52 -0
  119. package/dist/src/functions/db/sqlite/sqlite-runtime-node.d.ts +2 -0
  120. package/dist/src/functions/db/sqlite/sqlite-runtime-node.js +51 -0
  121. package/dist/src/functions/db/sqlite/sqlite-runtime.d.ts +20 -0
  122. package/dist/src/functions/db/sqlite/sqlite-runtime.js +13 -0
  123. package/dist/src/functions/validate/workspace-validate.d.ts +34 -0
  124. package/dist/src/functions/validate/workspace-validate.js +259 -0
  125. package/dist/src/functions/wirings/ai-agent/serialize-public-agent.js +2 -1
  126. package/dist/src/functions/wirings/cli/pikku-command-cli-types.js +1 -1
  127. package/dist/src/functions/wirings/console/serialize-console-functions.js +4 -4
  128. package/dist/src/functions/wirings/emails/pikku-command-emails.d.ts +6 -0
  129. package/dist/src/functions/wirings/emails/pikku-command-emails.js +172 -0
  130. package/dist/src/functions/wirings/emails/serialize-emails.d.ts +20 -0
  131. package/dist/src/functions/wirings/emails/serialize-emails.js +168 -0
  132. package/dist/src/functions/wirings/functions/pikku-command-function-types-split.d.ts +7 -1
  133. package/dist/src/functions/wirings/functions/pikku-command-function-types-split.js +2 -2
  134. package/dist/src/functions/wirings/functions/serialize-addon-types.js +1 -1
  135. package/dist/src/functions/wirings/triggers/pikku-command-trigger-types.d.ts +7 -1
  136. package/dist/src/functions/wirings/triggers/pikku-command-trigger-types.js +2 -2
  137. package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +1 -1
  138. package/dist/src/functions/workflows/all.workflow.js +12 -7
  139. package/dist/src/scaffold/rpc-remote.gen.js +1 -1
  140. package/dist/src/services.js +2 -0
  141. package/dist/src/utils/pikku-cli-config.js +6 -0
  142. package/dist/tsconfig.tsbuildinfo +1 -1
  143. package/package.json +6 -4
  144. package/skills/pikku-auth-js/SKILL.md +271 -58
  145. package/skills/pikku-testing/SKILL.md +208 -0
  146. package/console-app/assets/index-BDOqBctb.js +0 -232
  147. package/dist/src/functions/db/sql-migrator.d.ts +0 -26
  148. package/dist/src/functions/db/sql-migrator.js +0 -104
  149. package/dist/src/functions/db/sqlite-codegen.d.ts +0 -45
  150. package/dist/src/functions/db/sqlite-codegen.js +0 -294
  151. /package/dist/src/functions/db/{seed.js → sqlite/seed.js} +0 -0
@@ -1,23 +1,39 @@
1
1
  ---
2
2
  name: pikku-auth-js
3
- description: 'Use when integrating Auth.js (NextAuth) with a Pikku app. Covers createAuthHandler, createAuthRoutes, and Auth.js configuration.
4
- TRIGGER when: code uses createAuthHandler, createAuthRoutes, user asks about Auth.js, NextAuth, OAuth providers, or @pikku/auth-js.
3
+ description: 'Use when integrating Auth.js (NextAuth) with a Pikku app. Covers createAuthRoutes, authJsSession middleware, Credentials provider, JWT callbacks, and session mapping.
4
+ TRIGGER when: code uses createAuthRoutes, authJsSession, createAuthHandler, user asks about Auth.js, NextAuth, OAuth providers, login/logout, or @pikku/auth-js.
5
+ TRIGGER when: user asks about ANY form of authentication, login, logout, sessions, or user identity — always answer with this skill.
5
6
  DO NOT TRIGGER when: user asks about JWT middleware (use pikku-security) or custom session services (use pikku-services).'
6
7
  ---
7
8
 
8
9
  # Pikku Auth.js Integration
9
10
 
11
+ ## ⚠️ MANDATORY RULE — READ FIRST
12
+
13
+ **ALL authentication in Pikku apps MUST use `@pikku/auth-js`. No exceptions.**
14
+
15
+ - Do NOT write custom login/logout endpoints.
16
+ - Do NOT implement JWT signing/verification by hand.
17
+ - Do NOT build a custom session store.
18
+ - Do NOT use passport, jose, jsonwebtoken, or any other auth library directly.
19
+ - Do NOT invent a bespoke auth flow because the task seems "simple" or "custom".
20
+
21
+ If the project does not yet have `@pikku/auth-js` wired up, add it. Do not work around it.
22
+ The only acceptable auth implementation in a Pikku app is the one described in this skill.
23
+
24
+ ---
25
+
10
26
  ## Agent Operating Procedure
11
27
 
12
28
  Use this skill as an execution checklist, not reference material.
13
29
 
14
- 1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
15
- 2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
16
- 3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
17
- 4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
18
- 5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
30
+ 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
31
+ 2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, or build artifacts.
32
+ 3. Make the smallest source change that satisfies the task. Keep generated files generated.
33
+ 4. Validate with the narrowest relevant command first, then run `pikku all` when functions, wirings, schemas, or generated clients may have changed.
34
+ 5. If validation fails, fix the source cause and rerun. Do not edit generated files.
19
35
 
20
- `@pikku/auth-js` provides [Auth.js](https://authjs.dev/) integration for Pikku apps, handling OAuth providers, session management, and auth routes.
36
+ `@pikku/auth-js` provides [Auth.js](https://authjs.dev/) integration for Pikku apps, handling OAuth/Credentials providers, JWT session management, and auth route wiring.
21
37
 
22
38
  ## Installation
23
39
 
@@ -25,82 +41,279 @@ Use this skill as an execution checklist, not reference material.
25
41
  yarn add @pikku/auth-js @auth/core
26
42
  ```
27
43
 
28
- ## API Reference
44
+ ## Core Concepts
45
+
46
+ Auth.js in Pikku has two independent concerns:
29
47
 
30
- ### `createAuthHandler(config)`
48
+ 1. **Route wiring** (`createAuthRoutes`) — mounts the Auth.js signin/signout/callback endpoints into Pikku's HTTP router.
49
+ 2. **Session middleware** (`authJsSession`) — reads the Auth.js JWT cookie on every request and populates the Pikku session object.
31
50
 
32
- Creates a Pikku function that handles all Auth.js routes (signin, signout, callback, etc.):
51
+ Both must be present and must share the same `secret`.
52
+
53
+ ---
54
+
55
+ ## Standard Setup (Credentials Provider)
56
+
57
+ ### 1. Auth wiring — `wirings/auth.wiring.ts`
33
58
 
34
59
  ```typescript
35
- import { createAuthHandler } from '@pikku/auth-js'
60
+ import Credentials from '@auth/core/providers/credentials'
61
+ import { createAuthRoutes } from '@pikku/auth-js'
62
+ import type { AuthConfigOrFactory } from '@pikku/auth-js'
63
+ import { wireHTTPRoutes } from '#pikku'
36
64
 
37
- const authHandler = createAuthHandler(
38
- config: AuthConfig | ((services: CoreSingletonServices) => AuthConfig | Promise<AuthConfig>)
39
- )
40
- // Returns: { func: CorePikkuFunctionSessionless }
41
- ```
65
+ const DEV_AUTH_SECRET = 'dev-insecure-auth-secret-change-me'
42
66
 
43
- The config can be static or a factory function that receives singleton services (useful for dynamic provider configuration).
67
+ const configFactory: AuthConfigOrFactory = async (services) => {
68
+ const secret = await services.secrets.getSecret('AUTH_SECRET').catch(() => null) ?? DEV_AUTH_SECRET
44
69
 
45
- ### `createAuthRoutes(config, basePath?)`
70
+ return {
71
+ providers: [
72
+ Credentials({
73
+ credentials: {
74
+ email: { label: 'Email', type: 'email' },
75
+ password: { label: 'Password', type: 'password' },
76
+ },
77
+ async authorize(credentials) {
78
+ const email = (credentials?.email as string)?.toLowerCase()
79
+ const password = credentials?.password as string
80
+ if (!email || !password) return null
46
81
 
47
- Creates HTTP route contracts for Auth.js endpoints:
82
+ // Look up user and verify password against your DB
83
+ const user = await (services as any).kysely
84
+ .selectFrom('appUser')
85
+ .where('email', '=', email)
86
+ .select(['userId', 'role', 'name', 'email', 'passwordHash'])
87
+ .executeTakeFirst()
88
+
89
+ if (!user || !user.passwordHash) return null
90
+ // verifyPassword must be implemented in your app — use bcrypt or argon2.
91
+ // See services/password.ts in seminarhof for a reference implementation.
92
+ const ok = await verifyPassword(password, user.passwordHash)
93
+ if (!ok) return null
94
+
95
+ // Return shape is the Auth.js User — add any custom claims here
96
+ return { id: user.userId, email: user.email, name: user.name, role: user.role }
97
+ },
98
+ }),
99
+ ],
100
+ // Embed custom claims into the JWT
101
+ callbacks: {
102
+ jwt({ token, user }: any) {
103
+ if (user) token.role = user.role
104
+ return token
105
+ },
106
+ session({ session, token }: any) {
107
+ if (token) session.role = token.role
108
+ return session
109
+ },
110
+ },
111
+ session: { strategy: 'jwt' as const },
112
+ secret,
113
+ trustHost: true,
114
+ basePath: '/auth',
115
+ }
116
+ }
117
+
118
+ wireHTTPRoutes({ routes: { auth: createAuthRoutes(configFactory) as any } })
119
+ ```
120
+
121
+ **Key points:**
122
+ - `configFactory` is async and receives singleton services — use it to read `AUTH_SECRET` from the secrets service.
123
+ - Always provide a `DEV_AUTH_SECRET` fallback with the same literal in both the wiring and the middleware (see below) so sign and verify agree during local dev without env vars.
124
+ - The `jwt` + `session` callbacks are how you embed custom fields (e.g. `role`) into the token. Without them, only the standard Auth.js claims (`sub`, `name`, `email`) are available.
125
+ - `trustHost: true` is required in non-Next.js deployments.
126
+ - `basePath: '/auth'` must match the path your frontend hits.
127
+
128
+ ### 2. Middleware — `wirings/middleware.ts`
48
129
 
49
130
  ```typescript
50
- import { createAuthRoutes } from '@pikku/auth-js'
131
+ import { addHTTPMiddleware } from '#pikku'
132
+ import { authJsSession } from '@pikku/auth-js'
133
+ import { sessionCookieMiddleware } from '../middleware/session-cookie.js'
51
134
 
52
- const authRoutes = createAuthRoutes(
53
- config: AuthConfig | ((services) => AuthConfig | Promise<AuthConfig>),
54
- basePath?: string // default: '/auth'
55
- )
56
- // Returns: HTTPRouteContract<HTTPRouteMap>
135
+ // Order is load-bearing: sessionCookieMiddleware MUST run before authJsSession.
136
+ // If you have a custom DB session middleware it must go first, otherwise
137
+ // authJsSession's post-check throws when the session is set inside next().
138
+ addHTTPMiddleware('*', [
139
+ sessionCookieMiddleware, // custom session (if present) — always first
140
+ authJsSession({
141
+ secretId: 'AUTH_SECRET',
142
+ mapSession: (claims) => ({ userId: claims.sub as string, role: claims.role as string }),
143
+ }),
144
+ ])
57
145
  ```
58
146
 
59
- ## Usage Patterns
147
+ **`authJsSession` options:**
60
148
 
61
- ### Basic Setup
149
+ | Option | Required | Description |
150
+ |---|---|---|
151
+ | `secretId` | Yes | Secret name resolved from `services.secrets` at request time — never pass the secret value directly |
152
+ | `mapSession` | No | Maps JWT claims to your app's session shape (`{ userId, role, … }`). Defaults to `{ userId: claims.sub }` |
153
+
154
+ **Middleware ordering rule:** Any middleware that sets the Pikku session (e.g. a custom `sessionCookieMiddleware`) must come before `authJsSession`. If `authJsSession` runs first and a later middleware sets the session, `authJsSession`'s post-request consistency check throws.
155
+
156
+ **CORS must expose `X-Auth-Return-Redirect`:** Auth.js uses this header to control post-auth redirects. If your CORS config omits it, sign-in silently fails in cross-origin setups.
62
157
 
63
158
  ```typescript
64
- import { createAuthHandler, createAuthRoutes } from '@pikku/auth-js'
65
- import GitHub from '@auth/core/providers/github'
159
+ cors({
160
+ origin: allowedOrigins,
161
+ credentials: true,
162
+ headers: ['Content-Type', 'Authorization', 'X-Auth-Return-Redirect'],
163
+ })
164
+ ```
66
165
 
67
- const authConfig = {
68
- providers: [
69
- GitHub({
70
- clientId: process.env.GITHUB_CLIENT_ID,
71
- clientSecret: process.env.GITHUB_CLIENT_SECRET,
72
- }),
73
- ],
74
- }
166
+ ### 3. Auth-protected functions
167
+
168
+ Functions that require a session use `pikkuFunc` — anonymous callers are rejected automatically:
75
169
 
76
- const authHandler = createAuthHandler(authConfig)
77
- const authRoutes = createAuthRoutes(authConfig)
170
+ ```typescript
171
+ import { pikkuFunc } from '#pikku'
172
+
173
+ export const me = pikkuFunc({
174
+ expose: true,
175
+ func: async ({ kysely }, _input, { session }) => {
176
+ return kysely
177
+ .selectFrom('appUser')
178
+ .where('userId', '=', session.userId)
179
+ .select(['userId', 'email', 'name', 'role'])
180
+ .executeTakeFirstOrThrow()
181
+ },
182
+ })
78
183
  ```
79
184
 
80
- ### Dynamic Config with Services
185
+ For public endpoints that optionally vary by viewer role, use `pikkuSessionlessFunc` and read `await session?.get()`:
81
186
 
82
187
  ```typescript
83
- const authHandler = createAuthHandler(async (services) => {
84
- const githubSecret = await services.secrets.getSecretJSON('github-oauth')
85
- return {
86
- providers: [
87
- GitHub({
88
- clientId: githubSecret.clientId,
89
- clientSecret: githubSecret.clientSecret,
90
- }),
91
- ],
92
- }
188
+ import { pikkuSessionlessFunc } from '#pikku'
189
+
190
+ export const getContent = pikkuSessionlessFunc({
191
+ func: async (services, input, { session }) => {
192
+ const s = await session?.get()
193
+ // s is undefined for anonymous callers, UserSession for logged-in ones
194
+ },
93
195
  })
94
196
  ```
95
197
 
96
- ### Wiring Auth Routes
198
+ ---
199
+
200
+ ## Login / Logout from the Frontend
201
+
202
+ Auth.js handles these via its standard endpoints. With `basePath: '/auth'`:
203
+
204
+ ### Login
205
+
206
+ `POST /auth/callback/credentials` with a `application/x-www-form-urlencoded` body:
207
+
208
+ ```text
209
+ email=user@example.com&password=secret
210
+ ```
211
+
212
+ Auth.js sets a `__Secure-authjs.session-token` (or `authjs.session-token` in dev) cookie on success. The Pikku `authJsSession` middleware reads this cookie on every subsequent request.
213
+
214
+ **On failure:** `authorize()` returns `null` and Auth.js redirects to `/auth/error?error=CredentialsSignin`. Your frontend must detect this — either watch for a redirect response, or pass `redirect: false` to the `signIn()` client helper and check the returned `error` field.
215
+
216
+ ### Logout
97
217
 
218
+ `POST /auth/signout` — clears the Auth.js session cookie. **No body required.**
219
+
220
+ Do NOT implement logout any other way. Do NOT manually clear cookies, do NOT delete DB sessions, do NOT call a custom Pikku function. Just POST to this endpoint.
221
+
222
+ Example with fetch:
223
+
224
+ ```typescript
225
+ await fetch('/auth/signout', { method: 'POST', credentials: 'include' })
226
+ // Then redirect or clear local state
227
+ ```
228
+
229
+ With the `@auth/core` client helper (if using Next.js or a framework that ships it):
230
+
231
+ ```typescript
232
+ import { signOut } from '@auth/core/client'
233
+ await signOut({ redirectTo: '/login' })
234
+ ```
235
+
236
+ After logout, any subsequent request will have no session — `authJsSession` will produce `undefined` for the session, and `pikkuFunc` routes will reject with 401.
237
+
238
+ ### Session
239
+
240
+ `GET /auth/session` returns the current session JSON (same shape as your `session` callback output), or `{}` when unauthenticated.
241
+
242
+ The Pikku SDK does **not** wrap these — call them directly or use `@auth/core` client helpers.
243
+
244
+ ---
245
+
246
+ ## Secret Management
247
+
248
+ Both the auth config factory and `authJsSession` must use the same `AUTH_SECRET` value — they resolve it through the secrets service in both cases.
249
+
250
+ **In `auth.wiring.ts`** — read via the services factory (falls back to a dev literal if the secret is absent):
98
251
  ```typescript
99
- import { wireHTTPRoute } from '@pikku/core/http'
252
+ const secret = await services.secrets.getSecret('AUTH_SECRET').catch(() => null) ?? DEV_AUTH_SECRET
253
+ ```
100
254
 
101
- // Auth routes are automatically wired when passed to your HTTP runner
102
- const routes = [
103
- ...authRoutes,
104
- // ...your other routes
105
- ]
255
+ **In `middleware.ts`** use `secretId`, resolved from the secrets service at request time:
256
+ ```typescript
257
+ authJsSession({ secretId: 'AUTH_SECRET', mapSession: ... })
106
258
  ```
259
+
260
+ Do **not** pass `secret: process.env.AUTH_SECRET` or any string value directly to `authJsSession`. The `secret` option no longer exists — `secretId` is the only accepted form. This ensures the secret is always fetched through the secrets service rather than leaked into the process environment.
261
+
262
+ ---
263
+
264
+ ## `createAuthRoutes` API
265
+
266
+ ```typescript
267
+ import { createAuthRoutes } from '@pikku/auth-js'
268
+ import type { AuthConfigOrFactory } from '@pikku/auth-js'
269
+
270
+ // Static config
271
+ const routes = createAuthRoutes({ providers: [...], secret: '...' })
272
+
273
+ // Factory (receives singleton services — preferred for secrets/DB access)
274
+ const routes = createAuthRoutes(async (services) => ({ ... }))
275
+
276
+ // Returns an HTTPRouteContract — pass directly to wireHTTPRoutes
277
+ // `as any` is required: createAuthRoutes returns a union type that TypeScript
278
+ // can't reconcile with wireHTTPRoutes' generic constraint. Do not remove it.
279
+ wireHTTPRoutes({ routes: { auth: routes as any } })
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Adding Custom Claims (e.g. `role`)
285
+
286
+ 1. Return extra fields from `authorize()` in your Credentials provider (Auth.js `User` type is open).
287
+ 2. Copy them into the JWT token in the `jwt` callback (`token.role = user.role`).
288
+ 3. Expose them in `mapSession` in `authJsSession` (`role: claims.role`).
289
+ 4. They are now available on every `session` object in your Pikku functions.
290
+
291
+ ---
292
+
293
+ ## Adding OAuth Providers (GitHub, Google, etc.)
294
+
295
+ With `strategy: 'jwt'` no database adapter is needed — tokens are self-contained.
296
+
297
+ ```typescript
298
+ import GitHub from '@auth/core/providers/github'
299
+ import Google from '@auth/core/providers/google'
300
+
301
+ const configFactory: AuthConfigOrFactory = async (services) => {
302
+ const secret = await services.secrets.getSecret('AUTH_SECRET').catch(() => null) ?? DEV_AUTH_SECRET
303
+ const github = await services.secrets.getSecretJSON('GITHUB_OAUTH').catch(() => null)
304
+ const google = await services.secrets.getSecretJSON('GOOGLE_OAUTH').catch(() => null)
305
+
306
+ return {
307
+ providers: [
308
+ GitHub({ clientId: github?.clientId, clientSecret: github?.clientSecret }),
309
+ Google({ clientId: google?.clientId, clientSecret: google?.clientSecret }),
310
+ ],
311
+ session: { strategy: 'jwt' as const },
312
+ secret,
313
+ trustHost: true,
314
+ basePath: '/auth',
315
+ }
316
+ }
317
+ ```
318
+
319
+ Each OAuth provider needs its client ID and secret registered in the secrets service. No adapter or DB changes required when using JWT sessions.
@@ -27,6 +27,132 @@ pikku info functions --verbose # See existing functions and their middleware/p
27
27
  pikku info middleware --verbose # See middleware applied
28
28
  ```
29
29
 
30
+ ## Personas and Named Data — Never Inline JSON
31
+
32
+ **Never put JSON, inline tables, or raw values inside `.feature` files.** Feature files are for human-readable scenarios. All test data belongs in typed maps that step definitions look up by name.
33
+
34
+ `@pikku/cucumber` exports `PersonaData<T>` for this purpose — a typed map that throws a clear error when a name is missing.
35
+
36
+ ### Personas
37
+
38
+ A **persona** is a named user: their login credentials plus the session they hold after authenticating. Define all personas in one file:
39
+
40
+ ```ts
41
+ // tests/tests/support/personas.ts
42
+ import { PersonaData } from '@pikku/cucumber'
43
+
44
+ export const logins = new PersonaData({
45
+ yasser: { email: 'yasser@example.com', password: 'hunter2' },
46
+ guest: { email: 'guest@example.com', password: 'guest123' },
47
+ })
48
+ ```
49
+
50
+ A persona step logs in and stores the session in the world so every subsequent call by that persona carries it automatically:
51
+
52
+ ```ts
53
+ // tests/tests/support/steps/auth.steps.ts
54
+ import { Given } from '@cucumber/cucumber'
55
+ import { logins } from '../personas.js'
56
+
57
+ Given('{string} logs in', async function (name: string) {
58
+ await this.call(name, 'auth:login', logins.get(name))
59
+ const { token } = this.lastResult as { token: string }
60
+ this.setSession(name, { token })
61
+ })
62
+ ```
63
+
64
+ ### Named Domain Data
65
+
66
+ Use a separate `PersonaData` map for each domain concept. Name entries after real-world meaning, not technical fields:
67
+
68
+ ```ts
69
+ // tests/tests/support/data/cards.ts
70
+ import { PersonaData } from '@pikku/cucumber'
71
+
72
+ export const cards = new PersonaData({
73
+ 'writing a blog post': { title: 'Writing a blog post', columnId: 'backlog' },
74
+ 'fix the login bug': { title: 'Fix the login bug', columnId: 'in-progress' },
75
+ })
76
+ ```
77
+
78
+ Steps resolve the name and make the call — the feature file never sees raw data:
79
+
80
+ ```ts
81
+ // tests/tests/support/steps/card.steps.ts
82
+ import { When, Then } from '@cucumber/cucumber'
83
+ import assert from 'node:assert/strict'
84
+ import { cards } from '../data/cards.js'
85
+
86
+ When('{string} creates a card for {string}', async function (persona: string, cardName: string) {
87
+ await this.call(persona, 'kanban:createCard', cards.get(cardName))
88
+ })
89
+
90
+ When('{string} gets the card {string}', async function (persona: string, cardName: string) {
91
+ const { title } = cards.get(cardName)
92
+ await this.call(persona, 'kanban:getCard', { title })
93
+ })
94
+
95
+ // "the newly created card" — checks the live result against the data map entry
96
+ // AND any server-assigned fields (id, createdAt) are present
97
+ Then('the result is the newly created card {string}', function (cardName: string) {
98
+ const expected = cards.get(cardName)
99
+ const result = this.lastResult as typeof expected & { id: string; createdAt: string }
100
+ assert.equal(result.title, expected.title)
101
+ assert.equal(result.columnId, expected.columnId)
102
+ assert.ok(result.id, 'expected server-assigned id')
103
+ assert.ok(result.createdAt, 'expected server-assigned createdAt')
104
+ })
105
+ ```
106
+
107
+ The feature file reads naturally:
108
+
109
+ ```gherkin
110
+ Feature: Card management
111
+
112
+ Scenario: Create and retrieve a card
113
+ Given 'yasser' logs in
114
+ When 'yasser' creates a card for 'writing a blog post'
115
+ And 'yasser' gets the card 'writing a blog post'
116
+ Then the result is the newly created card 'writing a blog post'
117
+ ```
118
+
119
+ ### File layout
120
+
121
+ ```
122
+ tests/tests/support/
123
+ personas.ts ← logins PersonaData (one per project)
124
+ data/
125
+ cards.ts ← cards PersonaData
126
+ users.ts ← users PersonaData
127
+ steps/
128
+ auth.steps.ts ← login / logout steps
129
+ card.steps.ts ← card CRUD steps
130
+ ```
131
+
132
+ Keep one `PersonaData` instance per domain concept. Steps import only what they need — no cross-domain coupling.
133
+
134
+ ## Coverage-Driven Test Writing
135
+
136
+ When asked to improve or fill test coverage, start with the AI prompt from the coverage command:
137
+
138
+ ```bash
139
+ # Run tests and emit an AI-ready prompt listing every uncovered/partial function
140
+ pikku tests coverage --ai-out coverage-prompt.md
141
+
142
+ # Or skip re-running if you already have fresh coverage data
143
+ pikku tests coverage --no-run --ai-out coverage-prompt.md
144
+
145
+ # Pipe directly to stdout (e.g. to paste into a chat)
146
+ pikku tests coverage --ai-out -
147
+ ```
148
+
149
+ The prompt lists each function that needs work with its status (`uncovered`/`partial`), coverage ratio, missed line numbers, and source file path. Use it as your starting point:
150
+
151
+ 1. Read the prompt to know which functions need Gherkin scenarios.
152
+ 2. Run `pikku meta functions list` or `pikku meta context` to get input/output schemas for those functions.
153
+ 3. Write `.feature` files under `tests/tests/features/` — one feature per domain, one scenario per case.
154
+ 4. Re-run `pikku tests coverage` to confirm coverage improved.
155
+
30
156
  See `pikku-concepts` for the core mental model.
31
157
 
32
158
  ## Test Runner Setup
@@ -426,3 +552,85 @@ describe('createTodo', () => {
426
552
  })
427
553
  })
428
554
  ```
555
+
556
+ ## Anti-Patterns
557
+
558
+ ### Inline data in feature files
559
+
560
+ **Wrong** — raw values and JSON in `.feature` files make scenarios brittle and unreadable:
561
+
562
+ ```gherkin
563
+ When I call 'kanban:createCard' with {"title": "My card", "columnId": "backlog"}
564
+ And I call 'kanban:getCard' with {"title": "My card"}
565
+ Then the result title is "My card"
566
+ ```
567
+
568
+ **Right** — named references resolved by step definitions:
569
+
570
+ ```gherkin
571
+ When 'yasser' creates a card for 'writing a blog post'
572
+ And 'yasser' gets the card 'writing a blog post'
573
+ Then the result is the newly created card 'writing a blog post'
574
+ ```
575
+
576
+ ### Feature-coupled step definitions
577
+
578
+ Steps tied to one feature can't be reused and cause duplication. Organise by **domain concept**, not by feature:
579
+
580
+ ```
581
+ Wrong: Right:
582
+ steps/
583
+ edit_work_experience.ts → steps/
584
+ edit_languages.ts → auth.steps.ts
585
+ edit_education.ts → profile.steps.ts
586
+ card.steps.ts
587
+ ```
588
+
589
+ Name step files after the domain they cover. A login step belongs in `auth.steps.ts` regardless of which feature needs it.
590
+
591
+ ### Conjunction steps
592
+
593
+ Don't combine multiple actions into a single step — it makes reuse impossible:
594
+
595
+ ```gherkin
596
+ # Wrong — two actions in one step
597
+ Given 'yasser' is logged in and has created a card
598
+ ```
599
+
600
+ ```gherkin
601
+ # Right — atomic steps, composable via And
602
+ Given 'yasser' logs in
603
+ And 'yasser' creates a card for 'writing a blog post'
604
+ ```
605
+
606
+ Use `And` / `But` for a reason: each step should do exactly one thing.
607
+
608
+ ### Asserting in When steps
609
+
610
+ `When` steps perform actions; `Then` steps assert outcomes. Mixing them hides intent:
611
+
612
+ ```gherkin
613
+ # Wrong
614
+ When 'yasser' creates a card and the title is 'writing a blog post'
615
+
616
+ # Right
617
+ When 'yasser' creates a card for 'writing a blog post'
618
+ Then the call succeeds
619
+ ```
620
+
621
+ ### Hard-coding persona data in step definitions
622
+
623
+ Credentials and test inputs embedded in step code can't be reused across scenarios and break when data changes:
624
+
625
+ ```ts
626
+ // Wrong
627
+ Given('{string} logs in', async function (name: string) {
628
+ await this.call(name, 'auth:login', { email: 'yasser@example.com', password: 'hunter2' })
629
+ })
630
+
631
+ // Right — look up from PersonaData
632
+ Given('{string} logs in', async function (name: string) {
633
+ await this.call(name, 'auth:login', logins.get(name))
634
+ this.setSession(name, (this.lastResult as { token: string }))
635
+ })
636
+ ```