@pikku/cli 0.12.82 → 0.12.83
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/cli.schema.json +1 -1
- package/console-app/assets/{index-ChcXIEhS.js → index-CN_U0k_D.js} +154 -144
- package/console-app/assets/{index-DwyRdRuZ.css → index-CpHRlBt-.css} +1 -1
- package/console-app/index.html +2 -2
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +3 -2
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +21 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +60 -3
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +6 -12
- package/dist/.pikku/function/pikku-function-types.gen.js +3 -12
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +190 -12
- package/dist/.pikku/function/pikku-functions.gen.js +15 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +2 -24
- package/dist/.pikku/http/pikku-http-types.gen.js +2 -24
- package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +4 -1
- package/dist/.pikku/pikku-services.gen.js +3 -0
- package/dist/.pikku/pikku-types.gen.d.ts +2 -1
- package/dist/.pikku/pikku-types.gen.js +3 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +9 -0
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +13 -1
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuGraphWiringsOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuImportN8nInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuScopesInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuWebhookOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/ScopesAuditInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/ScopesPruneInput.schema.json +1 -0
- package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +5 -0
- package/dist/.pikku/scopes/pikku-scope-types.gen.js +4 -0
- package/dist/.pikku/scopes/pikku-scopes-meta.gen.json +1 -0
- package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +35 -0
- package/dist/.pikku/scopes/pikku-scopes.gen.js +27 -0
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets-meta.gen.json +1 -0
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +11 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +11 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables-meta.gen.json +1 -0
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +11 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +13 -3
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +48 -6
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +4 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/src/cli.wiring.js +47 -4
- package/dist/src/deploy/analyzer/analyzer.js +6 -0
- package/dist/src/fabric/fabric-commands.d.ts +3 -3
- package/dist/src/fabric/functions/llm-key.function.d.ts +4 -4
- package/dist/src/functions/commands/bootstrap.js +3 -0
- package/dist/src/functions/commands/dev.js +14 -3
- package/dist/src/functions/commands/enable.d.ts +1 -0
- package/dist/src/functions/commands/enable.js +10 -5
- package/dist/src/functions/commands/import-n8n.d.ts +10 -0
- package/dist/src/functions/commands/import-n8n.js +130 -0
- package/dist/src/functions/commands/info.js +0 -13
- package/dist/src/functions/commands/new-addon.js +33 -35
- package/dist/src/functions/commands/pikku-command-bootstrap.js +6 -0
- package/dist/src/functions/commands/scopes-audit.d.ts +1 -0
- package/dist/src/functions/commands/scopes-audit.js +37 -0
- package/dist/src/functions/commands/scopes-prune.d.ts +14 -0
- package/dist/src/functions/commands/scopes-prune.js +50 -0
- package/dist/src/functions/commands/scopes-shared.d.ts +39 -0
- package/dist/src/functions/commands/scopes-shared.js +83 -0
- package/dist/src/functions/commands/serve.js +4 -2
- package/dist/src/functions/commands/skills.js +18 -10
- package/dist/src/functions/wirings/ai-agent/serialize-ai-agent-types.js +2 -1
- package/dist/src/functions/wirings/console/serialize-console-functions.js +6 -0
- package/dist/src/functions/wirings/credentials/serialize-credentials-types.js +22 -0
- package/dist/src/functions/wirings/functions/pikku-command-function-types-split.js +1 -1
- package/dist/src/functions/wirings/functions/pikku-command-function-types.js +2 -2
- package/dist/src/functions/wirings/functions/serialize-function-types.d.ts +1 -1
- package/dist/src/functions/wirings/functions/serialize-function-types.js +14 -16
- package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.d.ts +1 -1
- package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.js +6 -2
- package/dist/src/functions/wirings/graph/pikku-command-graph-wirings.d.ts +1 -0
- package/dist/src/functions/wirings/graph/pikku-command-graph-wirings.js +21 -0
- package/dist/src/functions/wirings/graph/serialize-graph-wirings.d.ts +5 -0
- package/dist/src/functions/wirings/graph/serialize-graph-wirings.js +14 -0
- package/dist/src/functions/wirings/http/serialize-http-types.js +2 -30
- package/dist/src/functions/wirings/package/pikku-command-package-types.d.ts +1 -0
- package/dist/src/functions/wirings/package/pikku-command-package-types.js +14 -1
- package/dist/src/functions/wirings/package/serialize-package-types.d.ts +1 -0
- package/dist/src/functions/wirings/package/serialize-package-types.js +5 -0
- package/dist/src/functions/wirings/permissions/pikku-command-permissions.js +1 -3
- package/dist/src/functions/wirings/permissions/serialize-permissions-imports.d.ts +6 -1
- package/dist/src/functions/wirings/permissions/serialize-permissions-imports.js +7 -64
- package/dist/src/functions/wirings/rpc/pikku-command-rpc-map.d.ts +5 -0
- package/dist/src/functions/wirings/rpc/pikku-command-rpc-map.js +20 -0
- package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.d.ts +1 -0
- package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.js +8 -3
- package/dist/src/functions/wirings/scopes/pikku-command-scopes.d.ts +7 -0
- package/dist/src/functions/wirings/scopes/pikku-command-scopes.js +34 -0
- package/dist/src/functions/wirings/scopes/serialize-scopes-types.d.ts +15 -0
- package/dist/src/functions/wirings/scopes/serialize-scopes-types.js +86 -0
- package/dist/src/functions/wirings/secrets/serialize-secrets-types.js +13 -0
- package/dist/src/functions/wirings/variables/serialize-variables-types.js +15 -2
- package/dist/src/functions/wirings/webhook/pikku-command-webhook.d.ts +1 -0
- package/dist/src/functions/wirings/webhook/pikku-command-webhook.js +25 -0
- package/dist/src/functions/wirings/webhook/serialize-webhook.d.ts +14 -0
- package/dist/src/functions/wirings/webhook/serialize-webhook.js +34 -0
- package/dist/src/functions/wirings/workflow/serialize-workflow-routes.js +29 -0
- package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +3 -0
- package/dist/src/functions/workflows/all.workflow.js +23 -3
- package/dist/src/services.d.ts +1 -0
- package/dist/src/services.js +1 -0
- package/dist/src/utils/pikku-cli-config.js +20 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -8
- package/skills/pikku-addon/SKILL.md +7 -3
- package/skills/pikku-ai-agent/SKILL.md +6 -3
- package/skills/pikku-ai-vercel/SKILL.md +6 -3
- package/skills/pikku-ai-voice/SKILL.md +6 -3
- package/skills/pikku-audit/SKILL.md +10 -3
- package/skills/pikku-aws/SKILL.md +9 -5
- package/skills/pikku-backblaze/SKILL.md +4 -3
- package/skills/pikku-better-auth/SKILL.md +10 -5
- package/skills/pikku-cli/SKILL.md +6 -3
- package/skills/pikku-concepts/SKILL.md +9 -4
- package/skills/pikku-concepts/references/concept-mapping.md +12 -10
- package/skills/pikku-config/SKILL.md +9 -5
- package/skills/pikku-cron/SKILL.md +6 -3
- package/skills/pikku-deploy-azure/SKILL.md +5 -3
- package/skills/pikku-deploy-cloudflare/SKILL.md +5 -3
- package/skills/pikku-deploy-express/SKILL.md +6 -3
- package/skills/pikku-deploy-fastify/SKILL.md +6 -3
- package/skills/pikku-deploy-lambda/SKILL.md +6 -3
- package/skills/pikku-deploy-nextjs/SKILL.md +5 -3
- package/skills/pikku-deploy-uws/SKILL.md +7 -4
- package/skills/pikku-deps/SKILL.md +13 -6
- package/skills/pikku-emails/SKILL.md +10 -5
- package/skills/pikku-feature/SKILL.md +1 -1
- package/skills/pikku-gateway-slack/SKILL.md +6 -3
- package/skills/pikku-http/SKILL.md +15 -21
- package/skills/pikku-http/references/http-options.md +0 -2
- package/skills/pikku-info/SKILL.md +9 -4
- package/skills/pikku-jose/SKILL.md +6 -3
- package/skills/pikku-kysely/SKILL.md +13 -5
- package/skills/pikku-machine-auth/SKILL.md +9 -3
- package/skills/pikku-mcp/SKILL.md +7 -3
- package/skills/pikku-middleware/SKILL.md +11 -6
- package/skills/pikku-mongodb/SKILL.md +8 -5
- package/skills/pikku-n8n-import/SKILL.md +109 -0
- package/skills/pikku-n8n-import/SPEC.md +84 -0
- package/skills/pikku-n8n-import/references/addon-mapping.md +121 -0
- package/skills/pikku-n8n-import/references/code-translation.md +121 -0
- package/skills/pikku-n8n-import/references/loops-and-control.md +87 -0
- package/skills/pikku-permissions/SKILL.md +26 -15
- package/skills/pikku-pino/SKILL.md +5 -3
- package/skills/pikku-product-second-opinion/README.md +43 -0
- package/skills/pikku-product-second-opinion/SKILL.md +158 -0
- package/skills/pikku-product-second-opinion/example/sample-report.md +90 -0
- package/skills/pikku-product-second-opinion/references/report-template.md +73 -0
- package/skills/pikku-queue/SKILL.md +7 -4
- package/skills/pikku-redis/SKILL.md +9 -6
- package/skills/pikku-rpc/SKILL.md +7 -4
- package/skills/pikku-rtl/SKILL.md +1 -1
- package/skills/pikku-scenario/SKILL.md +215 -0
- package/skills/pikku-schedule/SKILL.md +6 -3
- package/skills/pikku-schema-ajv/SKILL.md +5 -3
- package/skills/pikku-schema-cfworker/SKILL.md +6 -3
- package/skills/pikku-security/SKILL.md +8 -4
- package/skills/pikku-services/SKILL.md +8 -4
- package/skills/pikku-software-archaeology/README.md +70 -0
- package/skills/pikku-software-archaeology/SKILL.md +186 -0
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +625 -0
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +49 -0
- package/skills/pikku-software-archaeology/scripts/validate.mjs +173 -0
- package/skills/pikku-template-clone/SKILL.md +1 -1
- package/skills/pikku-trigger/SKILL.md +7 -3
- package/skills/pikku-versioning/SKILL.md +7 -3
- package/skills/pikku-websocket/SKILL.md +8 -4
- package/skills/pikku-workflow/SKILL.md +7 -3
- package/skills/pikku-ws/SKILL.md +5 -3
- package/skills/pikku-n8n-addon-map/SKILL.md +0 -178
- package/skills/pikku-n8n-code-translate/SKILL.md +0 -166
- package/skills/pikku-testing/SKILL.md +0 -328
- package/skills/pikku-testing/references/cucumber-bdd-testing.md +0 -176
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-n8n-code-translate
|
|
3
|
-
description: "Use when translating an n8n Code node body into a real Pikku function body. Triggered when the user opens or points at a stub generated by @pikku/n8n-import (look for `STUB — generated from n8n Code node` in the file's JSDoc), or when the user says 'translate this n8n code', 'port this n8n code node', 'finish the codeStub__... function', etc. The stub file is a `pikkuSessionlessFunc` with a Zod input/output, a JSDoc preserving the original n8n JavaScript verbatim, and a `throw new Error('… — implement me')` body. DO NOT TRIGGER when: the stub is an n8n integration/service node such as gmailTool or slackTool (use pikku-n8n-addon-map), or the code is not an n8n-generated stub at all."
|
|
4
|
-
metadata:
|
|
5
|
-
version: 1.0.0
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# n8n Code Node → Pikku Function Translator
|
|
9
|
-
|
|
10
|
-
## Agent Operating Procedure
|
|
11
|
-
|
|
12
|
-
Use this skill as an execution checklist, not reference material.
|
|
13
|
-
|
|
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.
|
|
19
|
-
|
|
20
|
-
You are translating an n8n **Code node** body into a Pikku `pikkuSessionlessFunc` body. The original JavaScript is preserved verbatim in the JSDoc above the function. Your job: replace the `throw new Error(...)` body with a faithful TypeScript reimplementation, keep the function signature and the JSDoc intact, and only widen the Zod input/output if the original code's data shape demands it.
|
|
21
|
-
|
|
22
|
-
This is a **narrow, mechanical translation**. Do not "improve" the logic, refactor for style, add error handling, or invent fields. The goal is behavioral parity, not better code.
|
|
23
|
-
|
|
24
|
-
## Process
|
|
25
|
-
|
|
26
|
-
1. **Read the file** the user points you at. Identify:
|
|
27
|
-
- The exported input schema (e.g. `CodeStubCustomCodeInput`)
|
|
28
|
-
- The exported output schema (e.g. `CodeStubCustomCodeOutput`)
|
|
29
|
-
- The verbatim JavaScript inside the JSDoc block
|
|
30
|
-
- The function name and `pikkuSessionlessFunc` shape
|
|
31
|
-
2. **Determine the n8n mode** from the original n8n JSON if available (the importer leaves the JSON on disk in `fixtures/` or you can ask the user). The two modes:
|
|
32
|
-
- `runOnceForAllItems` (default) — code runs once with `items: Array<{ json, binary, pairedItem }>` in scope, returns an array of envelopes.
|
|
33
|
-
- `runOnceForEachItem` — code runs once per input item, with `$json` / `$input.item.json` in scope, returns a single envelope.
|
|
34
|
-
If you can't determine the mode, infer from the code: bare `items.X` → all-items; bare `$json.X` or `$input.item.X` → each-item.
|
|
35
|
-
3. **Apply the rubric below** to translate.
|
|
36
|
-
4. **Edit the file** to replace only the function body. Leave the import block, schema definitions, JSDoc, function name, description, and Zod refs untouched unless step 5 forces a change.
|
|
37
|
-
5. **If the schemas are wrong** (e.g. the code reads `$json.userId: string` but the input schema is `items: z.array(z.unknown())`), tighten the schemas with the smallest change that lets the code compile. Prefer `z.unknown()` over `z.any()`. Never widen output to `z.any()`.
|
|
38
|
-
6. **Add a one-line comment at the top of the body** noting the n8n mode you assumed: `// translated from n8n Code node, mode: runOnceForAllItems`. This is the _only_ comment you may add.
|
|
39
|
-
7. **Run the test/typecheck** if available (`yarn tsc` from the package root). Fix any type errors with the smallest viable change.
|
|
40
|
-
|
|
41
|
-
## Translation rubric
|
|
42
|
-
|
|
43
|
-
### Envelope unwrapping (all-items mode)
|
|
44
|
-
|
|
45
|
-
The n8n `items` is an array of `{ json, binary, pairedItem }` envelopes. In Pikku, the input is typed — `data.items` is the payload array. Translate by treating `items[i]` as the payload directly.
|
|
46
|
-
|
|
47
|
-
| n8n | Pikku |
|
|
48
|
-
| -------------------------- | ------------------------------------------------- |
|
|
49
|
-
| `items` | `(data.items ?? []) as any[]` (or typed if known) |
|
|
50
|
-
| `items[i].json.X` | `items[i].X` |
|
|
51
|
-
| `items[i].json` | `items[i]` |
|
|
52
|
-
| `items[i].binary` | **NOT supported** — leave a TODO and explain |
|
|
53
|
-
| `items.length` | `items.length` |
|
|
54
|
-
| `items.map(i => i.json.X)` | `items.map((i: any) => i.X)` |
|
|
55
|
-
|
|
56
|
-
### Envelope unwrapping (each-item mode)
|
|
57
|
-
|
|
58
|
-
| n8n | Pikku |
|
|
59
|
-
| -------------------------------- | ------------------------------------------------- |
|
|
60
|
-
| `$json.X` / `$input.item.json.X` | `data.X` (assuming input is the item itself) |
|
|
61
|
-
| `$input.item.json` | `data` |
|
|
62
|
-
| `$input.all()` | not available per-item — change to all-items mode |
|
|
63
|
-
|
|
64
|
-
### Return statement
|
|
65
|
-
|
|
66
|
-
| n8n | Pikku |
|
|
67
|
-
| ---------------------------------------- | ----------------------------------------------------------- |
|
|
68
|
-
| `return [{ json: X }]` | `return { items: [X] }` |
|
|
69
|
-
| `return items.map(i => ({ json: ... }))` | `return { items: items.map(...) }` |
|
|
70
|
-
| `return [{ json: X }, { json: Y }]` | `return { items: [X, Y] }` |
|
|
71
|
-
| `return { json: X }` (each-item) | `return X` |
|
|
72
|
-
| `return [...]` (already plain) | wrap in `{ items: [...] }` only if output schema expects it |
|
|
73
|
-
|
|
74
|
-
### n8n built-ins and helpers — do NOT auto-translate
|
|
75
|
-
|
|
76
|
-
If the code references **any** of the following, **stop**, leave the body as a stub, and ask the user how to handle it (or annotate with a `// TODO:` line and explain in your reply):
|
|
77
|
-
|
|
78
|
-
- `this.helpers.*` (binary buffers, HTTP requests, prepareBinaryData, etc.)
|
|
79
|
-
- `$node['Some Node'].json` (cross-node references — these need to be resolved via Pikku's `ref()` upstream wiring, not in the function body)
|
|
80
|
-
- `$workflow`, `$execution`, `$item()`, `$items('Other Node')`
|
|
81
|
-
- `$now`, `$today`, `$env.X` — translate to `new Date()`, `new Date()`, and `services.variables.get('X')` respectively, but only if the function signature lets you reach `services` (it does — first param). NOTE: `process.env` is forbidden per Pikku house rules; always use `services.variables.get()`.
|
|
82
|
-
- `getBinaryDataBuffer` / `getStaticData` — Pikku has no equivalent, leave a TODO
|
|
83
|
-
- `require()` / dynamic `import()` — flag and stop
|
|
84
|
-
|
|
85
|
-
### Async
|
|
86
|
-
|
|
87
|
-
- If the original uses `await`, the Pikku body is already `async` — keep all `await` calls intact.
|
|
88
|
-
- If the original references `this.helpers.httpRequest(...)`, **do not translate** — the user should use a separate `httpRequest` rpc node in the workflow, not embed the call. Leave a `// TODO:` and explain.
|
|
89
|
-
|
|
90
|
-
### Types
|
|
91
|
-
|
|
92
|
-
- Cast incoming `items` as `any[]` only if the schema is `z.array(z.unknown())`. If the user has tightened the schema, use the inferred type.
|
|
93
|
-
- Never use `as any` on the _return_ value. If the return doesn't match the output schema, the schema is wrong — fix it (step 5).
|
|
94
|
-
|
|
95
|
-
## Example
|
|
96
|
-
|
|
97
|
-
### Before (stub the importer emitted)
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
import { z } from 'zod'
|
|
101
|
-
import { pikkuSessionlessFunc } from '#pikku'
|
|
102
|
-
|
|
103
|
-
export const CodeStubCustomCodeInput = z.object({
|
|
104
|
-
items: z.array(z.unknown()),
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
export const CodeStubCustomCodeOutput = z.object({
|
|
108
|
-
items: z.array(z.unknown()),
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* STUB — generated from n8n Code node "Custom Code".
|
|
113
|
-
*
|
|
114
|
-
* Original n8n JavaScript (preserved verbatim for reference; rewrite for Pikku semantics):
|
|
115
|
-
*
|
|
116
|
-
* const total = items.reduce((acc, i) => acc + i.json.amount, 0);
|
|
117
|
-
* return [{ json: { total } }];
|
|
118
|
-
*
|
|
119
|
-
* TODO: re-implement in TypeScript. ...
|
|
120
|
-
*/
|
|
121
|
-
export const codeStubCustomCode = pikkuSessionlessFunc({
|
|
122
|
-
description: 'Stub: ported from n8n Code node "Custom Code"',
|
|
123
|
-
input: CodeStubCustomCodeInput,
|
|
124
|
-
output: CodeStubCustomCodeOutput,
|
|
125
|
-
func: async (_services, _data) => {
|
|
126
|
-
throw new Error(
|
|
127
|
-
'Stub: ported from n8n Code node "Custom Code" — implement me'
|
|
128
|
-
)
|
|
129
|
-
},
|
|
130
|
-
})
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### After (your output)
|
|
134
|
-
|
|
135
|
-
```ts
|
|
136
|
-
export const codeStubCustomCode = pikkuSessionlessFunc({
|
|
137
|
-
description: 'Ported from n8n Code node "Custom Code"',
|
|
138
|
-
input: CodeStubCustomCodeInput,
|
|
139
|
-
output: CodeStubCustomCodeOutput,
|
|
140
|
-
func: async (_services, data) => {
|
|
141
|
-
// translated from n8n Code node, mode: runOnceForAllItems
|
|
142
|
-
const items = (data.items ?? []) as any[]
|
|
143
|
-
const total = items.reduce((acc, i) => acc + i.amount, 0)
|
|
144
|
-
return { items: [{ total }] }
|
|
145
|
-
},
|
|
146
|
-
})
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## What you should report back to the user
|
|
150
|
-
|
|
151
|
-
A short summary, no fluff:
|
|
152
|
-
|
|
153
|
-
- The mode you inferred and why (one sentence).
|
|
154
|
-
- The literal translations you applied from the rubric (e.g. `items[i].json.amount → items[i].amount`).
|
|
155
|
-
- Anything you flagged as a TODO and why.
|
|
156
|
-
- Any schema tightening you did (with before → after).
|
|
157
|
-
|
|
158
|
-
Do **not**:
|
|
159
|
-
|
|
160
|
-
- Add tests
|
|
161
|
-
- Refactor surrounding code
|
|
162
|
-
- Edit other files unless the user asked
|
|
163
|
-
- "Improve" the logic
|
|
164
|
-
- Wrap things in `try/catch` unless the original did
|
|
165
|
-
|
|
166
|
-
If the original is empty, contains only comments, or is so dependent on n8n internals (binary, cross-node refs, helpers) that no honest translation is possible, leave the stub in place and tell the user which n8n features are blocking it.
|
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-testing
|
|
3
|
-
description: 'Use when writing tests for Pikku functions, middleware, permissions, or services. Covers unit testing with direct invocation, runPikkuFunc, service mocking, and integration testing with the HTTP runner.
|
|
4
|
-
TRIGGER when: user asks about testing, writing tests, test setup, mocking services, or integration testing Pikku functions.
|
|
5
|
-
DO NOT TRIGGER when: user asks about running the existing test suite (use Bash) or CI configuration (not a Pikku skill).'
|
|
6
|
-
installGroups: [core]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Pikku Testing
|
|
10
|
-
|
|
11
|
-
## Agent Operating Procedure
|
|
12
|
-
|
|
13
|
-
Use this skill as an execution checklist, not reference material.
|
|
14
|
-
|
|
15
|
-
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.
|
|
16
|
-
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.
|
|
17
|
-
3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
|
|
18
|
-
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.
|
|
19
|
-
5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
|
|
20
|
-
|
|
21
|
-
Pikku functions are pure business logic — no HTTP, no framework — making them easy to test. Test at three levels: direct function calls, `runPikkuFunc` (with middleware/permissions), and integration tests (full HTTP stack).
|
|
22
|
-
|
|
23
|
-
## Before You Start
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
pikku info functions --verbose # existing functions + their middleware/permissions
|
|
27
|
-
pikku info middleware --verbose # middleware applied
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Cucumber / BDD (feature files)
|
|
31
|
-
|
|
32
|
-
When writing `.feature` files, the golden rule: **never put JSON, inline tables, or raw values inside `.feature` files** — all test data goes in typed `PersonaData<T>` maps (from `@pikku/cucumber`) that step definitions look up by name. For personas, named domain data, the support file layout, and the full set of BDD anti-patterns, read `references/cucumber-bdd-testing.md`.
|
|
33
|
-
|
|
34
|
-
## Coverage-Driven Test Writing
|
|
35
|
-
|
|
36
|
-
When asked to improve or fill test coverage, start with the AI prompt from the coverage command:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
pikku tests coverage --ai-out coverage-prompt.md # run tests + emit AI-ready prompt of uncovered/partial functions
|
|
40
|
-
pikku tests coverage --no-run --ai-out coverage-prompt.md # skip re-running, use existing coverage data
|
|
41
|
-
pikku tests coverage --ai-out - # pipe to stdout
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
The prompt lists each function needing work with status (`uncovered`/`partial`), coverage ratio, missed line numbers, and source path. Use it as your starting point:
|
|
45
|
-
|
|
46
|
-
1. Read the prompt to know which functions need Gherkin scenarios.
|
|
47
|
-
2. Run `pikku meta functions list` or `pikku meta context` to get input/output schemas for those functions.
|
|
48
|
-
3. Write `.feature` files under `tests/tests/features/` — one feature per domain, one scenario per case.
|
|
49
|
-
4. Re-run `pikku tests coverage` to confirm coverage improved.
|
|
50
|
-
|
|
51
|
-
See `pikku-concepts` for the core mental model.
|
|
52
|
-
|
|
53
|
-
## Test Runner Setup
|
|
54
|
-
|
|
55
|
-
Pikku uses the Node.js built-in test runner with tsx for TypeScript:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
node --import tsx --test src/**/*.test.ts
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
import { describe, test, beforeEach } from 'node:test'
|
|
63
|
-
import assert from 'node:assert'
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
A reusable mock logger / singleton services bag used throughout the examples below:
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
const mockLogger = { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }
|
|
70
|
-
const mockSingletonServices = { logger: mockLogger /* + any services your funcs need */ } as any
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Level 1: Direct Function Invocation
|
|
74
|
-
|
|
75
|
-
The simplest approach — call `func` directly with mock services. Tests pure business logic: no middleware, permissions, or validation.
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
import { describe, test } from 'node:test'
|
|
79
|
-
import assert from 'node:assert'
|
|
80
|
-
|
|
81
|
-
describe('createTodo', () => {
|
|
82
|
-
test('should create a todo', async () => {
|
|
83
|
-
const mockServices = {
|
|
84
|
-
todoStore: { add: async (title: string) => ({ id: '1', title, completed: false }) },
|
|
85
|
-
}
|
|
86
|
-
const result = await createTodo.func(mockServices as any, { title: 'Buy milk' })
|
|
87
|
-
assert.equal(result.title, 'Buy milk')
|
|
88
|
-
assert.equal(result.completed, false)
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Level 2: `runPikkuFunc` (Full Pipeline)
|
|
94
|
-
|
|
95
|
-
Tests the function through Pikku's middleware, permissions, and schema-validation pipeline. Always `resetPikkuState()` in `beforeEach`. Register function metadata into `pikkuState(null, 'function', 'meta')`, register the function with `addFunction`, then invoke with `runPikkuFunc`.
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
import { runPikkuFunc, addFunction, addMiddleware, addPermission } from '@pikku/core'
|
|
99
|
-
import { resetPikkuState, pikkuState } from '@pikku/core'
|
|
100
|
-
|
|
101
|
-
beforeEach(() => resetPikkuState())
|
|
102
|
-
|
|
103
|
-
test('should run function with middleware', async () => {
|
|
104
|
-
pikkuState(null, 'function', 'meta')['myFunc'] = {
|
|
105
|
-
pikkuFuncId: 'myFunc', inputSchemaName: null, outputSchemaName: null,
|
|
106
|
-
}
|
|
107
|
-
addFunction('myFunc', {
|
|
108
|
-
func: async (services, data) => ({ greeting: `Hello ${data.name}` }),
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
const result = await runPikkuFunc('rpc', 'test-wire', 'myFunc', {
|
|
112
|
-
singletonServices: mockSingletonServices,
|
|
113
|
-
getAllServices: () => mockSingletonServices,
|
|
114
|
-
data: () => ({ name: 'World' }),
|
|
115
|
-
auth: false,
|
|
116
|
-
wire: {},
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
assert.deepEqual(result, { greeting: 'Hello World' })
|
|
120
|
-
})
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### Testing Middleware Execution Order
|
|
124
|
-
|
|
125
|
-
Middleware runs: wiring tags → wiring → func tags → func. Register tag middleware with `addMiddleware(tag, [...])`, reference func tags in the meta's `middleware: [{ type: 'tag', tag }]`, pass wiring middleware via `wireMiddleware` and inherited tags via `inheritedMiddleware`.
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
test('middleware runs in order: wiring tags -> wiring -> func tags -> func', async () => {
|
|
129
|
-
const order: string[] = []
|
|
130
|
-
const createMiddleware =
|
|
131
|
-
(name: string) => async (services: any, wire: any, next: Function) => {
|
|
132
|
-
order.push(name); await next()
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
addMiddleware('apiTag', [createMiddleware('apiTag')])
|
|
136
|
-
addMiddleware('funcTag', [createMiddleware('funcTag')])
|
|
137
|
-
|
|
138
|
-
pikkuState(null, 'function', 'meta')['myFunc'] = {
|
|
139
|
-
pikkuFuncId: 'myFunc', inputSchemaName: null, outputSchemaName: null,
|
|
140
|
-
middleware: [{ type: 'tag', tag: 'funcTag' }],
|
|
141
|
-
}
|
|
142
|
-
addFunction('myFunc', {
|
|
143
|
-
func: async () => { order.push('main'); return 'ok' },
|
|
144
|
-
middleware: [createMiddleware('funcMiddleware')],
|
|
145
|
-
tags: ['funcTag'],
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
await runPikkuFunc('rpc', 'test', 'myFunc', {
|
|
149
|
-
singletonServices: mockSingletonServices,
|
|
150
|
-
getAllServices: () => mockSingletonServices,
|
|
151
|
-
data: () => ({}),
|
|
152
|
-
wireMiddleware: [createMiddleware('wiringMiddleware')],
|
|
153
|
-
inheritedMiddleware: [{ type: 'tag', tag: 'apiTag' }],
|
|
154
|
-
auth: false,
|
|
155
|
-
wire: {},
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
assert.deepEqual(order, ['apiTag', 'wiringMiddleware', 'funcTag', 'funcMiddleware', 'main'])
|
|
159
|
-
})
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Testing Permissions
|
|
163
|
-
|
|
164
|
-
Register a denying permission with `addPermission(tag, [...])` and reference it in the meta's `permissions`.
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
test('should reject when permission fails', async () => {
|
|
168
|
-
addPermission('admin', [async () => false]) // always deny
|
|
169
|
-
|
|
170
|
-
pikkuState(null, 'function', 'meta')['adminFunc'] = {
|
|
171
|
-
pikkuFuncId: 'adminFunc', inputSchemaName: null, outputSchemaName: null,
|
|
172
|
-
permissions: [{ type: 'tag', tag: 'admin' }],
|
|
173
|
-
}
|
|
174
|
-
addFunction('adminFunc', { func: async () => 'secret' })
|
|
175
|
-
|
|
176
|
-
await assert.rejects(
|
|
177
|
-
runPikkuFunc('rpc', 'test', 'adminFunc', {
|
|
178
|
-
singletonServices: mockSingletonServices,
|
|
179
|
-
getAllServices: () => mockSingletonServices,
|
|
180
|
-
data: () => ({}),
|
|
181
|
-
auth: false,
|
|
182
|
-
wire: {},
|
|
183
|
-
}),
|
|
184
|
-
/Permission/
|
|
185
|
-
)
|
|
186
|
-
})
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
## Level 3: Integration Testing (HTTP)
|
|
190
|
-
|
|
191
|
-
Test the full HTTP stack using the `fetch` export. Set singleton services and factories into state, register route metadata + function, then `wireHTTP`.
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
import { fetch, wireHTTP } from '@pikku/core/http'
|
|
195
|
-
import { resetPikkuState, pikkuState, addFunction } from '@pikku/core'
|
|
196
|
-
|
|
197
|
-
const listTodos = { func: async () => ({ todos: [{ id: '1', title: 'Test todo' }] }) }
|
|
198
|
-
|
|
199
|
-
beforeEach(() => {
|
|
200
|
-
resetPikkuState()
|
|
201
|
-
pikkuState(null, 'package', 'singletonServices', mockSingletonServices)
|
|
202
|
-
pikkuState(null, 'package', 'factories', { createWireServices: async () => ({}) })
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
test('GET /todos returns todo list', async () => {
|
|
206
|
-
pikkuState(null, 'http', 'meta')['get'] = pikkuState(null, 'http', 'meta')['get'] || {}
|
|
207
|
-
pikkuState(null, 'http', 'meta')['get']['/todos'] = {
|
|
208
|
-
pikkuFuncId: 'listTodos', method: 'get', route: '/todos',
|
|
209
|
-
}
|
|
210
|
-
addFunction('listTodos', listTodos)
|
|
211
|
-
wireHTTP({ method: 'get', route: '/todos', func: listTodos })
|
|
212
|
-
|
|
213
|
-
const response = await fetch(new Request('http://localhost/todos'))
|
|
214
|
-
const data = await response.json()
|
|
215
|
-
assert.equal(response.status, 200)
|
|
216
|
-
assert.ok(Array.isArray(data.todos))
|
|
217
|
-
})
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
## Testing Services
|
|
221
|
-
|
|
222
|
-
Test custom services in isolation:
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
import { describe, test } from 'node:test'
|
|
226
|
-
import assert from 'node:assert'
|
|
227
|
-
import { LocalVariablesService } from '@pikku/core/services'
|
|
228
|
-
|
|
229
|
-
describe('LocalVariablesService', () => {
|
|
230
|
-
test('should get and set variables', () => {
|
|
231
|
-
const service = new LocalVariablesService({ API_KEY: 'test-key' })
|
|
232
|
-
assert.equal(service.get('API_KEY'), 'test-key')
|
|
233
|
-
service.set('NEW_KEY', 'value')
|
|
234
|
-
assert.equal(service.get('NEW_KEY'), 'value')
|
|
235
|
-
})
|
|
236
|
-
})
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
## Testing with Real Services (Verifier Pattern)
|
|
240
|
-
|
|
241
|
-
For integration testing with a running server, build real services via `pikkuServices`/`pikkuWireServices` and bootstrap a server:
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
// services.ts — real service setup for tests
|
|
245
|
-
import { pikkuServices, pikkuWireServices } from '#pikku'
|
|
246
|
-
import { LocalSecretService, LocalVariablesService } from '@pikku/core/services'
|
|
247
|
-
|
|
248
|
-
export const createSingletonServices = pikkuServices(async (config) => {
|
|
249
|
-
const variables = new LocalVariablesService()
|
|
250
|
-
const secrets = new LocalSecretService(variables)
|
|
251
|
-
return { config, variables, secrets, logger: new ConsoleLogger() }
|
|
252
|
-
})
|
|
253
|
-
export const createWireServices = pikkuWireServices(async () => ({}))
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
// start.ts — bootstrap server for tests
|
|
258
|
-
import './.pikku/pikku-bootstrap.gen.js'
|
|
259
|
-
import { createSingletonServices, createWireServices } from './services.js'
|
|
260
|
-
|
|
261
|
-
const config = {}
|
|
262
|
-
const singletonServices = await createSingletonServices(config)
|
|
263
|
-
const server = new PikkuFastifyServer(config, singletonServices, createWireServices)
|
|
264
|
-
await server.init()
|
|
265
|
-
await server.start()
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
## Common Patterns
|
|
269
|
-
|
|
270
|
-
- **Mock logger / singleton services** — see the reusable bag defined under "Test Runner Setup".
|
|
271
|
-
- **Reset state between tests** — always `resetPikkuState()` in `beforeEach` to isolate tests.
|
|
272
|
-
|
|
273
|
-
```typescript
|
|
274
|
-
import { resetPikkuState } from '@pikku/core'
|
|
275
|
-
beforeEach(() => resetPikkuState())
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
- **Async error assertions**:
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
await assert.rejects(
|
|
282
|
-
async () => await myFunc.func(services, { id: 'nonexistent' }),
|
|
283
|
-
{ message: 'Not found' }
|
|
284
|
-
)
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
## Complete Example
|
|
288
|
-
|
|
289
|
-
```typescript
|
|
290
|
-
// functions/todos.functions.ts
|
|
291
|
-
export const createTodo = pikkuSessionlessFunc({
|
|
292
|
-
description: 'Create a todo',
|
|
293
|
-
input: z.object({ title: z.string().min(1) }),
|
|
294
|
-
output: z.object({ id: z.string(), title: z.string() }),
|
|
295
|
-
func: async ({ todoStore }, { title }) => todoStore.add(title),
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
// functions/todos.test.ts
|
|
299
|
-
import { describe, test, beforeEach } from 'node:test'
|
|
300
|
-
import assert from 'node:assert'
|
|
301
|
-
|
|
302
|
-
class MockTodoStore {
|
|
303
|
-
private todos: any[] = []
|
|
304
|
-
async add(title: string) {
|
|
305
|
-
const todo = { id: String(this.todos.length + 1), title, completed: false }
|
|
306
|
-
this.todos.push(todo)
|
|
307
|
-
return todo
|
|
308
|
-
}
|
|
309
|
-
async list() { return this.todos }
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
describe('createTodo', () => {
|
|
313
|
-
let todoStore: MockTodoStore
|
|
314
|
-
beforeEach(() => { todoStore = new MockTodoStore() })
|
|
315
|
-
|
|
316
|
-
test('creates a todo with the given title', async () => {
|
|
317
|
-
const result = await createTodo.func({ todoStore } as any, { title: 'Buy milk' })
|
|
318
|
-
assert.equal(result.id, '1')
|
|
319
|
-
assert.equal(result.title, 'Buy milk')
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
test('increments IDs', async () => {
|
|
323
|
-
await createTodo.func({ todoStore } as any, { title: 'First' })
|
|
324
|
-
const second = await createTodo.func({ todoStore } as any, { title: 'Second' })
|
|
325
|
-
assert.equal(second.id, '2')
|
|
326
|
-
})
|
|
327
|
-
})
|
|
328
|
-
```
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
# Cucumber / BDD Testing with `@pikku/cucumber`
|
|
2
|
-
|
|
3
|
-
## Personas and Named Data — Never Inline JSON
|
|
4
|
-
|
|
5
|
-
**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.
|
|
6
|
-
|
|
7
|
-
`@pikku/cucumber` exports `PersonaData<T>` — a typed map that throws a clear error when a name is missing.
|
|
8
|
-
|
|
9
|
-
### Personas
|
|
10
|
-
|
|
11
|
-
A **persona** is a named user: login credentials plus the session held after authenticating. Define all personas in one file:
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
// tests/tests/support/personas.ts
|
|
15
|
-
import { PersonaData } from '@pikku/cucumber'
|
|
16
|
-
|
|
17
|
-
export const logins = new PersonaData({
|
|
18
|
-
yasser: { email: 'yasser@example.com', password: 'hunter2' },
|
|
19
|
-
guest: { email: 'guest@example.com', password: 'guest123' },
|
|
20
|
-
})
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
A persona step logs in and stores the session in the world so every subsequent call by that persona carries it automatically:
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
// tests/tests/support/steps/auth.steps.ts
|
|
27
|
-
import { Given } from '@cucumber/cucumber'
|
|
28
|
-
import { logins } from '../personas.js'
|
|
29
|
-
|
|
30
|
-
Given('{string} logs in', async function (name: string) {
|
|
31
|
-
await this.call(name, 'auth:login', logins.get(name))
|
|
32
|
-
const { token } = this.lastResult as { token: string }
|
|
33
|
-
this.setSession(name, { token })
|
|
34
|
-
})
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Named Domain Data
|
|
38
|
-
|
|
39
|
-
Use a separate `PersonaData` map per domain concept. Name entries after real-world meaning, not technical fields:
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
// tests/tests/support/data/cards.ts
|
|
43
|
-
import { PersonaData } from '@pikku/cucumber'
|
|
44
|
-
|
|
45
|
-
export const cards = new PersonaData({
|
|
46
|
-
'writing a blog post': { title: 'Writing a blog post', columnId: 'backlog' },
|
|
47
|
-
'fix the login bug': { title: 'Fix the login bug', columnId: 'in-progress' },
|
|
48
|
-
})
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Steps resolve the name and make the call — the feature file never sees raw data:
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
// tests/tests/support/steps/card.steps.ts
|
|
55
|
-
import { When, Then } from '@cucumber/cucumber'
|
|
56
|
-
import assert from 'node:assert/strict'
|
|
57
|
-
import { cards } from '../data/cards.js'
|
|
58
|
-
|
|
59
|
-
When('{string} creates a card for {string}', async function (persona: string, cardName: string) {
|
|
60
|
-
await this.call(persona, 'kanban:createCard', cards.get(cardName))
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
When('{string} gets the card {string}', async function (persona: string, cardName: string) {
|
|
64
|
-
const { title } = cards.get(cardName)
|
|
65
|
-
await this.call(persona, 'kanban:getCard', { title })
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// "the newly created card" — checks the live result against the data map entry
|
|
69
|
-
// AND any server-assigned fields (id, createdAt) are present
|
|
70
|
-
Then('the result is the newly created card {string}', function (cardName: string) {
|
|
71
|
-
const expected = cards.get(cardName)
|
|
72
|
-
const result = this.lastResult as typeof expected & { id: string; createdAt: string }
|
|
73
|
-
assert.equal(result.title, expected.title)
|
|
74
|
-
assert.equal(result.columnId, expected.columnId)
|
|
75
|
-
assert.ok(result.id, 'expected server-assigned id')
|
|
76
|
-
assert.ok(result.createdAt, 'expected server-assigned createdAt')
|
|
77
|
-
})
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
The feature file reads naturally:
|
|
81
|
-
|
|
82
|
-
```gherkin
|
|
83
|
-
Feature: Card management
|
|
84
|
-
|
|
85
|
-
Scenario: Create and retrieve a card
|
|
86
|
-
Given 'yasser' logs in
|
|
87
|
-
When 'yasser' creates a card for 'writing a blog post'
|
|
88
|
-
And 'yasser' gets the card 'writing a blog post'
|
|
89
|
-
Then the result is the newly created card 'writing a blog post'
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### File layout
|
|
93
|
-
|
|
94
|
-
```
|
|
95
|
-
tests/tests/support/
|
|
96
|
-
personas.ts ← logins PersonaData (one per project)
|
|
97
|
-
data/
|
|
98
|
-
cards.ts ← cards PersonaData
|
|
99
|
-
users.ts ← users PersonaData
|
|
100
|
-
steps/
|
|
101
|
-
auth.steps.ts ← login / logout steps
|
|
102
|
-
card.steps.ts ← card CRUD steps
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
Keep one `PersonaData` instance per domain concept. Steps import only what they need — no cross-domain coupling.
|
|
106
|
-
|
|
107
|
-
## Anti-Patterns
|
|
108
|
-
|
|
109
|
-
### Inline data in feature files
|
|
110
|
-
|
|
111
|
-
Raw values/JSON in `.feature` files make scenarios brittle and unreadable. Use named references resolved by step definitions instead.
|
|
112
|
-
|
|
113
|
-
```gherkin
|
|
114
|
-
# Wrong
|
|
115
|
-
When I call 'kanban:createCard' with {"title": "My card", "columnId": "backlog"}
|
|
116
|
-
Then the result title is "My card"
|
|
117
|
-
|
|
118
|
-
# Right
|
|
119
|
-
When 'yasser' creates a card for 'writing a blog post'
|
|
120
|
-
Then the result is the newly created card 'writing a blog post'
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### Feature-coupled step definitions
|
|
124
|
-
|
|
125
|
-
Steps tied to one feature can't be reused and cause duplication. Organise by **domain concept**, not by feature. Name step files after the domain they cover — a login step belongs in `auth.steps.ts` regardless of which feature needs it.
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
Wrong: Right:
|
|
129
|
-
steps/ steps/
|
|
130
|
-
edit_work_experience.ts auth.steps.ts
|
|
131
|
-
edit_languages.ts profile.steps.ts
|
|
132
|
-
edit_education.ts card.steps.ts
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Conjunction steps
|
|
136
|
-
|
|
137
|
-
Don't combine multiple actions into a single step — it makes reuse impossible. Use `And` / `But`: each step does exactly one thing.
|
|
138
|
-
|
|
139
|
-
```gherkin
|
|
140
|
-
# Wrong — two actions in one step
|
|
141
|
-
Given 'yasser' is logged in and has created a card
|
|
142
|
-
|
|
143
|
-
# Right — atomic, composable
|
|
144
|
-
Given 'yasser' logs in
|
|
145
|
-
And 'yasser' creates a card for 'writing a blog post'
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Asserting in When steps
|
|
149
|
-
|
|
150
|
-
`When` steps perform actions; `Then` steps assert outcomes. Mixing them hides intent.
|
|
151
|
-
|
|
152
|
-
```gherkin
|
|
153
|
-
# Wrong
|
|
154
|
-
When 'yasser' creates a card and the title is 'writing a blog post'
|
|
155
|
-
|
|
156
|
-
# Right
|
|
157
|
-
When 'yasser' creates a card for 'writing a blog post'
|
|
158
|
-
Then the call succeeds
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### Hard-coding persona data in step definitions
|
|
162
|
-
|
|
163
|
-
Credentials/test inputs embedded in step code can't be reused and break when data changes — look them up from `PersonaData`.
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
// Wrong
|
|
167
|
-
Given('{string} logs in', async function (name: string) {
|
|
168
|
-
await this.call(name, 'auth:login', { email: 'yasser@example.com', password: 'hunter2' })
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
// Right
|
|
172
|
-
Given('{string} logs in', async function (name: string) {
|
|
173
|
-
await this.call(name, 'auth:login', logins.get(name))
|
|
174
|
-
this.setSession(name, (this.lastResult as { token: string }))
|
|
175
|
-
})
|
|
176
|
-
```
|