@pikku/cli 0.12.92 → 0.12.96
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/console-app/assets/{index-DDpIMCpy.js → index-CeQodYfg.js} +151 -149
- package/console-app/assets/{index-D0HG8q0B.css → index-CyYHTGWZ.css} +1 -1
- package/console-app/index.html +2 -2
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- 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.d.ts +13 -15
- package/dist/.pikku/cli/pikku-cli-channel.js +72 -3
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +14 -4
- package/dist/.pikku/cli/pikku-cli-client.gen.js +20 -10
- 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 +156 -2
- 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 +55 -20
- package/dist/.pikku/function/pikku-function-types.gen.js +27 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +176 -13
- package/dist/.pikku/function/pikku-functions.gen.js +11 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.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-scenarios.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.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 +1 -1
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -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/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.js +1 -1
- 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 +21 -3
- package/dist/.pikku/schemas/schemas/KnowledgeIndexInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeIndexOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeValidateInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeValidateOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PersonaRunInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PersonaSyncInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuPersonasInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuRolesInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/RolesAuditInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/RolesPruneInput.schema.json +1 -0
- package/dist/.pikku/scopes/pikku-personas-meta.gen.json +1 -0
- package/dist/.pikku/scopes/pikku-personas.gen.d.ts +36 -0
- package/dist/.pikku/scopes/pikku-personas.gen.js +17 -0
- package/dist/.pikku/scopes/pikku-roles-meta.gen.json +1 -0
- package/dist/.pikku/scopes/pikku-roles.gen.d.ts +30 -0
- package/dist/.pikku/scopes/pikku-roles.gen.js +18 -0
- package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +4 -2
- package/dist/.pikku/scopes/pikku-scope-types.gen.js +3 -2
- package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +2 -2
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +2 -2
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -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 +2 -2
- package/dist/.pikku/variables/pikku-variable-types.gen.js +2 -2
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +27 -3
- package/dist/.pikku/workflow/pikku-personas.gen.d.ts +15 -0
- package/dist/.pikku/workflow/pikku-personas.gen.js +17 -0
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +96 -19
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +22 -2
- 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/bin/pikku.js +0 -0
- package/dist/src/cli.wiring.js +124 -2
- package/dist/src/deploy/analyzer/analyzer.js +14 -0
- package/dist/src/deploy/analyzer/manifest.d.ts +11 -0
- package/dist/src/fabric/functions/validate.function.js +20 -11
- package/dist/src/functions/commands/bootstrap.js +3 -0
- package/dist/src/functions/commands/dev-ai-runner.d.ts +16 -4
- package/dist/src/functions/commands/dev-ai-runner.js +63 -29
- package/dist/src/functions/commands/dev.js +9 -4
- package/dist/src/functions/commands/enable.js +15 -19
- package/dist/src/functions/commands/{scenario-environment.d.ts → environment.d.ts} +9 -6
- package/dist/src/functions/commands/{scenario-environment.js → environment.js} +5 -4
- package/dist/src/functions/commands/knowledge-index.d.ts +30 -0
- package/dist/src/functions/commands/knowledge-index.js +11 -0
- package/dist/src/functions/commands/knowledge-validate.d.ts +33 -0
- package/dist/src/functions/commands/knowledge-validate.js +11 -0
- package/dist/src/functions/commands/new-addon.d.ts +18 -0
- package/dist/src/functions/commands/new-addon.js +52 -36
- package/dist/src/functions/commands/persona-sync.d.ts +33 -0
- package/dist/src/functions/commands/persona-sync.js +151 -0
- package/dist/src/functions/commands/persona.d.ts +58 -0
- package/dist/src/functions/commands/persona.js +217 -0
- package/dist/src/functions/commands/roles-audit.d.ts +1 -0
- package/dist/src/functions/commands/roles-audit.js +37 -0
- package/dist/src/functions/commands/roles-prune.d.ts +16 -0
- package/dist/src/functions/commands/roles-prune.js +52 -0
- package/dist/src/functions/commands/roles-shared.d.ts +40 -0
- package/dist/src/functions/commands/roles-shared.js +77 -0
- package/dist/src/functions/commands/scenario-browser.d.ts +3 -3
- package/dist/src/functions/commands/scenario-browser.js +1 -1
- package/dist/src/functions/commands/scenario-ladder.d.ts +44 -2
- package/dist/src/functions/commands/scenario-ladder.js +51 -2
- package/dist/src/functions/commands/scenario.d.ts +7 -0
- package/dist/src/functions/commands/scenario.js +79 -17
- package/dist/src/functions/commands/scopes-shared.d.ts +8 -0
- package/dist/src/functions/commands/scopes-shared.js +1 -1
- package/dist/src/functions/commands/serve.js +7 -3
- package/dist/src/functions/commands/virtual-user-formatter.d.ts +30 -0
- package/dist/src/functions/commands/virtual-user-formatter.js +65 -0
- package/dist/src/functions/db/local-db.js +38 -1
- package/dist/src/functions/knowledge/render.d.ts +3 -0
- package/dist/src/functions/knowledge/render.js +66 -0
- package/dist/src/functions/knowledge/schemas.d.ts +51 -0
- package/dist/src/functions/knowledge/schemas.js +25 -0
- package/dist/src/functions/validate/workspace-validate.js +1 -1
- package/dist/src/functions/wirings/ai-agent/pikku-command-public-agent.js +2 -1
- package/dist/src/functions/wirings/ai-agent/serialize-public-agent.js +1 -0
- package/dist/src/functions/wirings/auth/serialize-auth-gen.d.ts +2 -2
- package/dist/src/functions/wirings/auth/serialize-auth-gen.js +8 -8
- package/dist/src/functions/wirings/cli/serialize-channel-cli-client.d.ts +7 -3
- package/dist/src/functions/wirings/cli/serialize-channel-cli-client.js +32 -18
- package/dist/src/functions/wirings/cli/serialize-channel-cli.js +63 -6
- package/dist/src/functions/wirings/console/serialize-console-functions.js +4 -7
- package/dist/src/functions/wirings/credentials/serialize-credentials-types.js +8 -1
- package/dist/src/functions/wirings/functions/serialize-function-types.js +88 -28
- package/dist/src/functions/wirings/middleware/serialize-middleware-imports.js +25 -9
- package/dist/src/functions/wirings/package/serialize-package-types.js +5 -3
- package/dist/src/functions/wirings/personas/pikku-command-personas.d.ts +7 -0
- package/dist/src/functions/wirings/personas/pikku-command-personas.js +43 -0
- package/dist/src/functions/wirings/personas/serialize-personas-types.d.ts +18 -0
- package/dist/src/functions/wirings/personas/serialize-personas-types.js +76 -0
- package/dist/src/functions/wirings/realtime/pikku-command-events-scaffold.js +2 -1
- package/dist/src/functions/wirings/roles/pikku-command-roles.d.ts +7 -0
- package/dist/src/functions/wirings/roles/pikku-command-roles.js +33 -0
- package/dist/src/functions/wirings/roles/serialize-roles-types.d.ts +14 -0
- package/dist/src/functions/wirings/roles/serialize-roles-types.js +55 -0
- package/dist/src/functions/wirings/rpc/pikku-command-public-rpc.js +2 -1
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.d.ts +3 -3
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.js +3 -3
- package/dist/src/functions/wirings/scopes/serialize-scopes-types.d.ts +1 -1
- package/dist/src/functions/wirings/scopes/serialize-scopes-types.js +1 -1
- package/dist/src/functions/wirings/secrets/serialize-secrets-types.js +10 -3
- package/dist/src/functions/wirings/user-admin/pikku-command-user-admin-functions.js +2 -1
- package/dist/src/functions/wirings/user-admin/serialize-user-admin-functions.js +2 -2
- package/dist/src/functions/wirings/variables/serialize-variables-types.js +8 -1
- package/dist/src/functions/wirings/workflow/pikku-command-workflow-routes.js +2 -1
- package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +13 -13
- package/dist/src/functions/wirings/workflow/serialize-personas.d.ts +10 -0
- package/dist/src/functions/wirings/workflow/serialize-personas.js +46 -0
- package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.js +11 -2
- package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +165 -29
- package/dist/src/functions/workflows/all.workflow.js +7 -1
- package/dist/src/server/content-signing-jwt.d.ts +13 -0
- package/dist/src/server/content-signing-jwt.js +51 -0
- package/dist/src/server/dev-server-runner.interface.d.ts +10 -2
- package/dist/src/server/node-server-runner.d.ts +2 -2
- package/dist/src/server/node-server-runner.js +2 -1
- package/dist/src/services.d.ts +3 -1
- package/dist/src/services.js +2 -1
- package/dist/src/utils/pikku-cli-config.js +45 -2
- package/dist/src/utils/resolve-personas.d.ts +32 -0
- package/dist/src/utils/resolve-personas.js +46 -0
- package/dist/src/utils/resolve-scaffold-feature.d.ts +19 -0
- package/dist/src/utils/resolve-scaffold-feature.js +49 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -13
- package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +0 -1
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +0 -19
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +0 -17
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.d.ts +0 -2
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.js +0 -40
- package/dist/src/utils/resolve-scenario-actors.d.ts +0 -23
- package/dist/src/utils/resolve-scenario-actors.js +0 -74
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const serializeWorkflowTypes = (functionTypesImportPath, rpcMapImportPath, workflowMapImportPath, agentMapImportPath, scopesImportPath, scenarioStepMapImportPath = './pikku-scenario-step-map.gen.js', scenarioActorsImportPath = './pikku-
|
|
1
|
+
export const serializeWorkflowTypes = (functionTypesImportPath, rpcMapImportPath, workflowMapImportPath, agentMapImportPath, scopesImportPath, scenarioStepMapImportPath = './pikku-scenario-step-map.gen.js', scenarioActorsImportPath = './pikku-personas.gen.js') => {
|
|
2
2
|
return `import { WorkflowCancelledException } from '@pikku/core/workflow'
|
|
3
3
|
import { template } from '@pikku/core/workflow'
|
|
4
4
|
import {
|
|
@@ -14,9 +14,9 @@ import type { FlattenedRPCMap } from '${rpcMapImportPath}'
|
|
|
14
14
|
import type { FlattenedWorkflowMap } from '${workflowMapImportPath}'
|
|
15
15
|
import type { AgentMap as FlattenedAgentMap } from '${agentMapImportPath}'
|
|
16
16
|
import type { FlattenedScenarioStepMap } from '${scenarioStepMapImportPath}'
|
|
17
|
-
import type {
|
|
17
|
+
import type { TypedPersonas } from '${scenarioActorsImportPath}'
|
|
18
18
|
|
|
19
|
-
export type {
|
|
19
|
+
export type { TypedPersonas }
|
|
20
20
|
|
|
21
21
|
export { template }
|
|
22
22
|
|
|
@@ -308,16 +308,26 @@ export function pikkuFeature<const Scenarios extends readonly unknown[]>(
|
|
|
308
308
|
return config
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
/**
|
|
312
|
+
* One surface's implementation of a step.
|
|
313
|
+
*
|
|
314
|
+
* Each binding is typed independently, so a browser binding gets a non-optional
|
|
315
|
+
* \`wire.browser\` and a cli binding a non-optional \`wire.cli\` without either
|
|
316
|
+
* leaking into the other.
|
|
317
|
+
*/
|
|
311
318
|
export type PikkuFunctionScenarioStep<
|
|
312
319
|
In = unknown,
|
|
313
320
|
Out = never,
|
|
314
|
-
|
|
315
|
-
> = PikkuFunctionSessionless<
|
|
321
|
+
Surface extends 'browser' | 'cli' | 'default' = 'default'
|
|
322
|
+
> = PikkuFunctionSessionless<
|
|
323
|
+
In,
|
|
324
|
+
Out,
|
|
325
|
+
Surface extends 'default' ? 'scenarioStep' : 'scenarioStep' | Surface
|
|
326
|
+
>
|
|
316
327
|
|
|
317
328
|
export type PikkuScenarioStepConfigWithSchema<
|
|
318
329
|
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
|
319
|
-
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
|
320
|
-
B extends boolean = false
|
|
330
|
+
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
|
321
331
|
> = {
|
|
322
332
|
/** Registered name — this is the string \`scenario.step()\` references. */
|
|
323
333
|
name: string
|
|
@@ -336,49 +346,175 @@ export type PikkuScenarioStepConfigWithSchema<
|
|
|
336
346
|
* sentence per call site.
|
|
337
347
|
*/
|
|
338
348
|
template?: string
|
|
339
|
-
/**
|
|
340
|
-
* This step drives a browser. The runner provisions one for the step's actor
|
|
341
|
-
* before calling it — which also makes an actor mandatory — and \`wire.browser\`
|
|
342
|
-
* is non-optional inside \`func\`.
|
|
343
|
-
*/
|
|
344
|
-
browser?: B
|
|
345
349
|
title?: string
|
|
346
350
|
tags?: string[]
|
|
347
351
|
input?: InputSchema
|
|
348
352
|
output?: OutputSchema
|
|
349
353
|
errors?: Array<typeof PikkuError>
|
|
350
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Drive this step through a real browser, as a human would. The runner
|
|
356
|
+
* provisions a window for the step's actor first — which makes an actor
|
|
357
|
+
* mandatory — and \`wire.browser\` is non-optional inside it.
|
|
358
|
+
*
|
|
359
|
+
* Compose shared utilities here rather than writing raw clicks: the step says
|
|
360
|
+
* what the actor is doing, the utilities say how.
|
|
361
|
+
*/
|
|
362
|
+
browser?: PikkuFunctionScenarioStep<
|
|
363
|
+
InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown,
|
|
364
|
+
OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
|
|
365
|
+
'browser'
|
|
366
|
+
>
|
|
367
|
+
/** Drive this step remotely, over the websocket. */
|
|
368
|
+
cli?: PikkuFunctionScenarioStep<
|
|
351
369
|
InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown,
|
|
352
370
|
OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
|
|
353
|
-
|
|
371
|
+
'cli'
|
|
372
|
+
>
|
|
373
|
+
/**
|
|
374
|
+
* The server-side path — the fast suite, and the floor every other surface
|
|
375
|
+
* falls back to.
|
|
376
|
+
*
|
|
377
|
+
* On a \`then\`, this is not an alternative to the other bindings but a second
|
|
378
|
+
* **witness**: the run executes every binding it has and fails if they
|
|
379
|
+
* disagree, because "the row says paid" and "the page says paid" are different
|
|
380
|
+
* claims and the gap between them is the bug nobody catches.
|
|
381
|
+
*/
|
|
382
|
+
default?: PikkuFunctionScenarioStep<
|
|
383
|
+
InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown,
|
|
384
|
+
OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
|
|
385
|
+
'default'
|
|
354
386
|
>
|
|
355
387
|
}
|
|
356
388
|
|
|
357
|
-
export type PikkuScenarioStepConfig<In, Out
|
|
358
|
-
Omit<PikkuScenarioStepConfigWithSchema<undefined, undefined
|
|
359
|
-
|
|
389
|
+
export type PikkuScenarioStepConfig<In, Out> =
|
|
390
|
+
Omit<PikkuScenarioStepConfigWithSchema<undefined, undefined>, 'browser' | 'cli' | 'default' | 'input' | 'output'> & {
|
|
391
|
+
browser?: PikkuFunctionScenarioStep<In, Out, 'browser'>
|
|
392
|
+
cli?: PikkuFunctionScenarioStep<In, Out, 'cli'>
|
|
393
|
+
default?: PikkuFunctionScenarioStep<In, Out, 'default'>
|
|
360
394
|
}
|
|
361
395
|
|
|
362
396
|
/**
|
|
363
|
-
* A named, reusable scenario step
|
|
364
|
-
*
|
|
365
|
-
*
|
|
397
|
+
* A named, reusable scenario step, declaring one implementation per surface an
|
|
398
|
+
* actor can drive it through.
|
|
399
|
+
*
|
|
400
|
+
* The step's identity is what the actor is trying to do; which surface carries
|
|
401
|
+
* it out is a binding. That is what lets one ladder run through a real browser,
|
|
402
|
+
* over the websocket, or entirely server-side — and what makes "how much of this
|
|
403
|
+
* flow can a human actually reach" a number rather than a guess.
|
|
366
404
|
*
|
|
367
405
|
* Steps are deliberately NOT registered as RPCs: a browser-driving step must
|
|
368
406
|
* never be network-callable.
|
|
369
407
|
*/
|
|
370
408
|
export function pikkuScenarioStep<
|
|
371
409
|
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
|
372
|
-
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
|
373
|
-
B extends boolean = false
|
|
410
|
+
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
|
374
411
|
>(
|
|
375
|
-
config: PikkuScenarioStepConfigWithSchema<InputSchema, OutputSchema
|
|
376
|
-
): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
|
|
377
|
-
export function pikkuScenarioStep<In, Out = unknown
|
|
378
|
-
config: PikkuScenarioStepConfig<In, Out
|
|
379
|
-
): PikkuFunctionConfig<In, Out,
|
|
412
|
+
config: PikkuScenarioStepConfigWithSchema<InputSchema, OutputSchema>
|
|
413
|
+
): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'scenarioStep' | 'browser' | 'cli', PikkuFunctionScenarioStep<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'browser' | 'cli' | 'default'>, InputSchema, OutputSchema>
|
|
414
|
+
export function pikkuScenarioStep<In, Out = unknown>(
|
|
415
|
+
config: PikkuScenarioStepConfig<In, Out>
|
|
416
|
+
): PikkuFunctionConfig<In, Out, 'scenarioStep' | 'browser' | 'cli'>
|
|
380
417
|
export function pikkuScenarioStep(config: any) {
|
|
381
|
-
|
|
418
|
+
const surfaces = (['browser', 'cli', 'default'] as const).filter(
|
|
419
|
+
(surface) => typeof config[surface] === 'function'
|
|
420
|
+
)
|
|
421
|
+
if (surfaces.length === 0) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
\`[scenario] step '\${config.name}' declares no surface bindings. Add at least a \\\`default\\\` (server-side) implementation.\`
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
...config,
|
|
428
|
+
surfaces,
|
|
429
|
+
// The runner sets the surface on the wire and calls this once per binding it
|
|
430
|
+
// resolved — every witness of a \`then\`, exactly one for an action step.
|
|
431
|
+
func: (services: any, data: any, wire: any) => {
|
|
432
|
+
const surface = wire?.scenarioStep?.surface ?? 'default'
|
|
433
|
+
const binding = config[surface] ?? config.default
|
|
434
|
+
return binding(services, data, wire)
|
|
435
|
+
},
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* What a step declares when nobody drives it: one implementation, and no
|
|
441
|
+
* surfaces to choose between.
|
|
442
|
+
*
|
|
443
|
+
* \`func\` rather than \`default\` is the point. \`default\` means *the fallback when
|
|
444
|
+
* no other surface applies*, which implies others could exist; \`func\` says
|
|
445
|
+
* structurally that there is one way this happens. That also keeps the phase
|
|
446
|
+
* rule coherent — an assertion runs every witness it has and fails if they
|
|
447
|
+
* disagree, and this has exactly one by construction.
|
|
448
|
+
*/
|
|
449
|
+
export type PikkuSubjectScenarioStepConfigWithSchema<
|
|
450
|
+
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
|
451
|
+
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
|
452
|
+
> = Omit<
|
|
453
|
+
PikkuScenarioStepConfigWithSchema<InputSchema, OutputSchema>,
|
|
454
|
+
'browser' | 'cli' | 'default'
|
|
455
|
+
> & {
|
|
456
|
+
func: PikkuFunctionScenarioStep<
|
|
457
|
+
InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown,
|
|
458
|
+
OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
|
|
459
|
+
'default'
|
|
460
|
+
>
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export type PikkuSubjectScenarioStepConfig<In, Out> =
|
|
464
|
+
Omit<PikkuScenarioStepConfig<In, Out>, 'browser' | 'cli' | 'default'> & {
|
|
465
|
+
func: PikkuFunctionScenarioStep<In, Out, 'default'>
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* A step in which the app acts on itself — "Given the platform has expired the
|
|
470
|
+
* trial".
|
|
471
|
+
*
|
|
472
|
+
* The grammatical subject of that sentence is not a user of your app; it **is**
|
|
473
|
+
* your app, which is why it is its own declaration rather than a persona with an
|
|
474
|
+
* asterisk. A persona is a person.
|
|
475
|
+
*
|
|
476
|
+
* Local-test-only, and never in a virtual user's catalogue: a virtual user that
|
|
477
|
+
* could expire its own trial is manufacturing the outcome it exists to discover.
|
|
478
|
+
*/
|
|
479
|
+
export function pikkuPlatformScenarioStep<
|
|
480
|
+
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
|
481
|
+
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
|
482
|
+
>(
|
|
483
|
+
config: PikkuSubjectScenarioStepConfigWithSchema<InputSchema, OutputSchema>
|
|
484
|
+
): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'scenarioStep', PikkuFunctionScenarioStep<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'default'>, InputSchema, OutputSchema>
|
|
485
|
+
export function pikkuPlatformScenarioStep<In, Out = unknown>(
|
|
486
|
+
config: PikkuSubjectScenarioStepConfig<In, Out>
|
|
487
|
+
): PikkuFunctionConfig<In, Out, 'scenarioStep'>
|
|
488
|
+
export function pikkuPlatformScenarioStep(config: any) {
|
|
489
|
+
return { ...config, surfaces: ['default'] }
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* A step in which a third-party system acts — "Given Stripe's webhook arrives",
|
|
494
|
+
* "When Mailgun bounces it".
|
|
495
|
+
*
|
|
496
|
+
* \`addon\` names the addon that wraps that service, the same name its
|
|
497
|
+
* \`wireAddon\` declares. These steps *are* the mock its consumers currently
|
|
498
|
+
* hand-write: shipped by the addon author, maintained with the addon, and the
|
|
499
|
+
* same artifact that appears in the prose.
|
|
500
|
+
*
|
|
501
|
+
* Note that arranging and asserting are different — "Stripe's webhook arrives"
|
|
502
|
+
* stubs, "Then Stripe was charged" asserts, and only the first is a stub.
|
|
503
|
+
*
|
|
504
|
+
* Local-test-only, and never in a virtual user's catalogue: one that could
|
|
505
|
+
* invoke this would forge its own payment success.
|
|
506
|
+
*/
|
|
507
|
+
export function pikkuAddonScenarioStep<
|
|
508
|
+
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
|
509
|
+
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
|
510
|
+
>(
|
|
511
|
+
config: PikkuSubjectScenarioStepConfigWithSchema<InputSchema, OutputSchema> & { addon: string }
|
|
512
|
+
): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'scenarioStep', PikkuFunctionScenarioStep<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'default'>, InputSchema, OutputSchema>
|
|
513
|
+
export function pikkuAddonScenarioStep<In, Out = unknown>(
|
|
514
|
+
config: PikkuSubjectScenarioStepConfig<In, Out> & { addon: string }
|
|
515
|
+
): PikkuFunctionConfig<In, Out, 'scenarioStep'>
|
|
516
|
+
export function pikkuAddonScenarioStep(config: any) {
|
|
517
|
+
return { ...config, surfaces: ['default'] }
|
|
382
518
|
}
|
|
383
519
|
|
|
384
520
|
type TypedRef<T> = { $ref: string; path?: string } & { __phantomType?: T }
|
|
@@ -53,6 +53,10 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
|
|
|
53
53
|
// schemas, for one — fails until these two files exist.
|
|
54
54
|
await workflow.do('Bootstrap scope definition types', 'pikkuScopeDefinitionTypes', null);
|
|
55
55
|
await workflow.do('Bootstrap scopes', 'pikkuScopes', { bootstrap: true });
|
|
56
|
+
await workflow.do('Bootstrap roles', 'pikkuRoles', { bootstrap: true });
|
|
57
|
+
await workflow.do('Bootstrap personas', 'pikkuPersonas', {
|
|
58
|
+
bootstrap: true,
|
|
59
|
+
});
|
|
56
60
|
await workflow.do('Bootstrap function types split', 'pikkuFunctionTypesSplit', { bootstrap: true });
|
|
57
61
|
// Pre-write a stub auth.types.ts (if the project uses better-auth) so the
|
|
58
62
|
// pikkuBetterAuth re-export resolves before any user file is imported.
|
|
@@ -155,6 +159,8 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
|
|
|
155
159
|
workflow.do('Credentials', 'pikkuCredentials', null),
|
|
156
160
|
workflow.do('Scope definition types', 'pikkuScopeDefinitionTypes', null),
|
|
157
161
|
workflow.do('Scopes', 'pikkuScopes', {}),
|
|
162
|
+
workflow.do('Roles', 'pikkuRoles', {}),
|
|
163
|
+
workflow.do('Personas', 'pikkuPersonas', {}),
|
|
158
164
|
workflow.do('Variable definition types', 'pikkuVariableDefinitionTypes', null),
|
|
159
165
|
workflow.do('Variables', 'pikkuVariables', null),
|
|
160
166
|
workflow.do('Addon types', 'pikkuAddonTypes', null),
|
|
@@ -165,7 +171,7 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
|
|
|
165
171
|
if (agents || !config.addon) {
|
|
166
172
|
await workflow.do('Re-inspect after agents', async () => getInspectorState(true));
|
|
167
173
|
}
|
|
168
|
-
// pikkuAuth generates auth-secrets.gen.ts (with
|
|
174
|
+
// pikkuAuth generates auth-secrets.gen.ts (with defineSecret calls) inside the
|
|
169
175
|
// Promise.all above, but pikkuSecrets runs concurrently from the pre-auth
|
|
170
176
|
// inspector state. Re-run secret/credential/variable codegen now so that the
|
|
171
177
|
// freshly-generated auth-secrets.gen.ts (just picked up by Re-inspect above)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pikku dev` and `pikku serve` construct the local content service themselves,
|
|
3
|
+
* so they must supply the key its signed asset URLs are signed and verified
|
|
4
|
+
* with — a project is not required to wire a JWTService just to serve uploads
|
|
5
|
+
* in development.
|
|
6
|
+
*
|
|
7
|
+
* The key is random and per-process: URLs signed by one dev server mean
|
|
8
|
+
* nothing to the next. That is the right lifetime for a development secret,
|
|
9
|
+
* and it lets the runtime reject any signature it cannot verify instead of
|
|
10
|
+
* waving through requests when no key is configured.
|
|
11
|
+
*/
|
|
12
|
+
import type { JWTService } from '@pikku/core/services';
|
|
13
|
+
export declare const createEphemeralContentSigningJWT: () => JWTService;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pikku dev` and `pikku serve` construct the local content service themselves,
|
|
3
|
+
* so they must supply the key its signed asset URLs are signed and verified
|
|
4
|
+
* with — a project is not required to wire a JWTService just to serve uploads
|
|
5
|
+
* in development.
|
|
6
|
+
*
|
|
7
|
+
* The key is random and per-process: URLs signed by one dev server mean
|
|
8
|
+
* nothing to the next. That is the right lifetime for a development secret,
|
|
9
|
+
* and it lets the runtime reject any signature it cannot verify instead of
|
|
10
|
+
* waving through requests when no key is configured.
|
|
11
|
+
*/
|
|
12
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
13
|
+
import { getRelativeTimeOffsetFromNow } from '@pikku/core';
|
|
14
|
+
export const createEphemeralContentSigningJWT = () => {
|
|
15
|
+
const key = randomBytes(32);
|
|
16
|
+
const sign = (body) => createHmac('sha256', key).update(body).digest('base64url');
|
|
17
|
+
return {
|
|
18
|
+
encode: async (expiresIn, payload) => {
|
|
19
|
+
const envelope = {
|
|
20
|
+
exp: getRelativeTimeOffsetFromNow(expiresIn).getTime(),
|
|
21
|
+
payload,
|
|
22
|
+
};
|
|
23
|
+
const body = Buffer.from(JSON.stringify(envelope), 'utf8').toString('base64url');
|
|
24
|
+
return `${body}.${sign(body)}`;
|
|
25
|
+
},
|
|
26
|
+
decode: async (hash, invalidHashError) => {
|
|
27
|
+
const invalid = invalidHashError ?? new Error('Invalid content signature');
|
|
28
|
+
const [body, signature] = hash.split('.');
|
|
29
|
+
if (!body || !signature) {
|
|
30
|
+
throw invalid;
|
|
31
|
+
}
|
|
32
|
+
const expected = Buffer.from(sign(body), 'utf8');
|
|
33
|
+
const actual = Buffer.from(signature, 'utf8');
|
|
34
|
+
if (expected.length !== actual.length ||
|
|
35
|
+
!timingSafeEqual(expected, actual)) {
|
|
36
|
+
throw invalid;
|
|
37
|
+
}
|
|
38
|
+
let envelope;
|
|
39
|
+
try {
|
|
40
|
+
envelope = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw invalid;
|
|
44
|
+
}
|
|
45
|
+
if (typeof envelope.exp !== 'number' || Date.now() > envelope.exp) {
|
|
46
|
+
throw invalid;
|
|
47
|
+
}
|
|
48
|
+
return envelope.payload;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `services.ts` and injected — no `typeof Bun` checks in the dev command.
|
|
10
10
|
*/
|
|
11
11
|
import type { EventHubService } from '@pikku/core';
|
|
12
|
-
import type { Logger } from '@pikku/core/services';
|
|
12
|
+
import type { JWTService, Logger } from '@pikku/core/services';
|
|
13
13
|
import type { NodeHTTPServerConfig } from '@pikku/node-http-server';
|
|
14
14
|
export interface DevServerInstance {
|
|
15
15
|
init(): Promise<void>;
|
|
@@ -19,6 +19,14 @@ export interface DevServerInstance {
|
|
|
19
19
|
}
|
|
20
20
|
/** Config shape both runtimes accept (Core config + host/port/content). */
|
|
21
21
|
export type DevServerConfig = NodeHTTPServerConfig;
|
|
22
|
+
export type DevServerOptions = {
|
|
23
|
+
/**
|
|
24
|
+
* The JWTService the dev server's content service signs asset URLs with.
|
|
25
|
+
* The transport verifies signed asset reads with it, so it must be the same
|
|
26
|
+
* instance — otherwise every signed URL is rejected.
|
|
27
|
+
*/
|
|
28
|
+
contentSigningJWT?: JWTService;
|
|
29
|
+
};
|
|
22
30
|
export interface DevServerRunner {
|
|
23
31
|
/**
|
|
24
32
|
* Create the EventHub for this runtime. Shared into singleton services so
|
|
@@ -27,5 +35,5 @@ export interface DevServerRunner {
|
|
|
27
35
|
*/
|
|
28
36
|
createEventHub(): Promise<EventHubService<any>>;
|
|
29
37
|
/** Create the dev HTTP/WS server. Must be called after `createEventHub`. */
|
|
30
|
-
createServer(config: DevServerConfig, logger: Logger): DevServerInstance;
|
|
38
|
+
createServer(config: DevServerConfig, logger: Logger, options?: DevServerOptions): DevServerInstance;
|
|
31
39
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { EventHubService } from '@pikku/core';
|
|
7
7
|
import type { Logger } from '@pikku/core/services';
|
|
8
|
-
import type { DevServerRunner, DevServerInstance, DevServerConfig } from './dev-server-runner.interface.js';
|
|
8
|
+
import type { DevServerRunner, DevServerInstance, DevServerConfig, DevServerOptions } from './dev-server-runner.interface.js';
|
|
9
9
|
export declare class NodeServerRunner implements DevServerRunner {
|
|
10
10
|
createEventHub(): Promise<EventHubService<any>>;
|
|
11
|
-
createServer(config: DevServerConfig, logger: Logger): DevServerInstance;
|
|
11
|
+
createServer(config: DevServerConfig, logger: Logger, options?: DevServerOptions): DevServerInstance;
|
|
12
12
|
}
|
|
@@ -11,12 +11,13 @@ export class NodeServerRunner {
|
|
|
11
11
|
async createEventHub() {
|
|
12
12
|
return new LocalEventHubService();
|
|
13
13
|
}
|
|
14
|
-
createServer(config, logger) {
|
|
14
|
+
createServer(config, logger, options = {}) {
|
|
15
15
|
const wss = new WebSocketServer({ noServer: true });
|
|
16
16
|
const server = new PikkuNodeHTTPServer(config, logger, {
|
|
17
17
|
configureServer: (httpServer) => {
|
|
18
18
|
pikkuWebsocketHandler({ server: httpServer, wss, logger });
|
|
19
19
|
},
|
|
20
|
+
contentSigningJWT: options.contentSigningJWT,
|
|
20
21
|
});
|
|
21
22
|
return {
|
|
22
23
|
init: () => server.init(),
|
package/dist/src/services.d.ts
CHANGED
|
@@ -12,7 +12,9 @@ export declare function processDiagnostics(diagnostics: InspectorDiagnostic[], l
|
|
|
12
12
|
*/
|
|
13
13
|
export declare const defaultCLIRenderer: import("@pikku/core/cli").CorePikkuCLIRender<ForwardedLogMessage, import("@pikku/core").CoreSingletonServices<{
|
|
14
14
|
logLevel?: LogLevel;
|
|
15
|
-
secrets?: {
|
|
15
|
+
secrets?: {
|
|
16
|
+
requireAllowedHosts?: boolean;
|
|
17
|
+
};
|
|
16
18
|
workflow?: import("@pikku/core/workflow").WorkflowServiceConfig;
|
|
17
19
|
webhook?: import("@pikku/core/services").WebhookServiceConfig;
|
|
18
20
|
postgres?: import("@pikku/core").PostgresConfig;
|
package/dist/src/services.js
CHANGED
|
@@ -216,7 +216,7 @@ export const createSingletonServices = async (config) => {
|
|
|
216
216
|
config.publicRpcFile,
|
|
217
217
|
config.publicAgentFile,
|
|
218
218
|
// The auth scaffold (catch-all routes + session middleware) and its
|
|
219
|
-
// sibling secrets file (
|
|
219
|
+
// sibling secrets file (defineSecret per provider) are generated into the
|
|
220
220
|
// scaffold dir, which may live outside srcDirectories (e.g. a project's
|
|
221
221
|
// `pikku/` dir). Add them explicitly so their wirings are inspected.
|
|
222
222
|
config.authFile,
|
|
@@ -259,6 +259,7 @@ export const createSingletonServices = async (config) => {
|
|
|
259
259
|
// data-classification leaks. Off by default — it's the dominant codegen
|
|
260
260
|
// cost. Never runs during a plain `pikku all`.
|
|
261
261
|
classificationCheck: !setupOnly && !!config.security,
|
|
262
|
+
strictMeta: !!config.strictMeta,
|
|
262
263
|
schemaConfig: !setupOnly
|
|
263
264
|
? {
|
|
264
265
|
tsconfig: config.tsconfig,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join, dirname, resolve, isAbsolute, parse as parsePath } from 'path';
|
|
2
2
|
import { readdir, readFile } from 'fs/promises';
|
|
3
|
+
import { resolveScaffoldFeature } from './resolve-scaffold-feature.js';
|
|
3
4
|
const CLIENT_FILE_KEYS = [
|
|
4
5
|
'fetchFile',
|
|
5
6
|
'websocketFile',
|
|
@@ -231,6 +232,32 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
|
|
|
231
232
|
const resolvedScaffoldDir = isAbsolute(scaffoldDir)
|
|
232
233
|
? scaffoldDir
|
|
233
234
|
: join(result.rootDir, scaffoldDir);
|
|
235
|
+
// Read every scaffold feature once, here, so a legacy 'auth'/'no-auth'
|
|
236
|
+
// fails at load with the migration named, rather than downstream where the
|
|
237
|
+
// value has already been coerced into something plausible.
|
|
238
|
+
//
|
|
239
|
+
// An explicit `path` is written onto the feature's output field before the
|
|
240
|
+
// derivations below run. Each of those is guarded by `!result.<field>`, so
|
|
241
|
+
// setting it here wins and the pikkuDir-derived default is skipped.
|
|
242
|
+
const SCAFFOLD_OUTPUT_FIELDS = {
|
|
243
|
+
rpc: 'publicRpcFile',
|
|
244
|
+
agent: 'publicAgentFile',
|
|
245
|
+
console: 'consoleFunctionsFile',
|
|
246
|
+
scenarios: 'scenariosFunctionsFile',
|
|
247
|
+
userAdmin: 'userAdminFunctionsFile',
|
|
248
|
+
workflow: 'workflowRoutesFile',
|
|
249
|
+
events: 'eventsChannelFile',
|
|
250
|
+
remoteRpc: 'remoteRpcWorkersFile',
|
|
251
|
+
};
|
|
252
|
+
const scaffoldBlock = result.scaffold;
|
|
253
|
+
for (const [feature, outputField] of Object.entries(SCAFFOLD_OUTPUT_FIELDS)) {
|
|
254
|
+
const resolved = resolveScaffoldFeature(feature, scaffoldBlock?.[feature]);
|
|
255
|
+
if (!resolved.enabled || !resolved.path)
|
|
256
|
+
continue;
|
|
257
|
+
result[outputField] = isAbsolute(resolved.path)
|
|
258
|
+
? resolved.path
|
|
259
|
+
: join(result.rootDir, resolved.path);
|
|
260
|
+
}
|
|
234
261
|
if (result.scaffold?.remoteRpc && !result.remoteRpcWorkersFile) {
|
|
235
262
|
result.remoteRpcWorkersFile = join(resolvedScaffoldDir, 'rpc', 'rpc-remote.gen.ts');
|
|
236
263
|
}
|
|
@@ -373,8 +400,8 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
|
|
|
373
400
|
if (!result.workflowMetaDir) {
|
|
374
401
|
result.workflowMetaDir = join(workflowDir, 'meta');
|
|
375
402
|
}
|
|
376
|
-
if (!result.
|
|
377
|
-
result.
|
|
403
|
+
if (!result.personasWiringFile) {
|
|
404
|
+
result.personasWiringFile = join(workflowDir, 'pikku-personas.gen.ts');
|
|
378
405
|
}
|
|
379
406
|
// Scenarios
|
|
380
407
|
const scenarioDir = join(result.outDir, 'scenarios');
|
|
@@ -527,6 +554,22 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
|
|
|
527
554
|
if (!result.scopesMetaJsonFile) {
|
|
528
555
|
result.scopesMetaJsonFile = join(scopesDir, 'pikku-scopes-meta.gen.json');
|
|
529
556
|
}
|
|
557
|
+
// System roles (SystemRoleName union + declared role set). Kept beside the
|
|
558
|
+
// scopes they are composed from — a role is unreadable without them.
|
|
559
|
+
if (!result.rolesFile) {
|
|
560
|
+
result.rolesFile = join(scopesDir, 'pikku-roles.gen.ts');
|
|
561
|
+
}
|
|
562
|
+
if (!result.rolesMetaJsonFile) {
|
|
563
|
+
result.rolesMetaJsonFile = join(scopesDir, 'pikku-roles-meta.gen.json');
|
|
564
|
+
}
|
|
565
|
+
// Personas. Beside the roles they are checked against, for the same reason
|
|
566
|
+
// roles sit beside their scopes.
|
|
567
|
+
if (!result.personasFile) {
|
|
568
|
+
result.personasFile = join(scopesDir, 'pikku-personas.gen.ts');
|
|
569
|
+
}
|
|
570
|
+
if (!result.personasMetaJsonFile) {
|
|
571
|
+
result.personasMetaJsonFile = join(scopesDir, 'pikku-personas-meta.gen.json');
|
|
572
|
+
}
|
|
530
573
|
// Variables (typed wrapper for VariablesService)
|
|
531
574
|
const variablesDir = join(result.outDir, 'variables');
|
|
532
575
|
if (!result.variableTypesFile) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PersonaDefinitions } from '@pikku/core/persona';
|
|
2
|
+
import type { ResolvedPersona } from '@pikku/core/services';
|
|
3
|
+
/**
|
|
4
|
+
* Where a persona's mail lands when the project has not said.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately a domain nobody can own rather than something plausible: an app
|
|
7
|
+
* that never sends mail runs fine against it, and one that does fails loudly at
|
|
8
|
+
* the first send instead of quietly delivering a magic link to a stranger.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_PERSONA_EMAIL_DOMAIN = "personas.invalid";
|
|
11
|
+
/**
|
|
12
|
+
* The personas a run actually sees: every declaration, with its address filled
|
|
13
|
+
* in.
|
|
14
|
+
*
|
|
15
|
+
* Every consumer resolves through here rather than reading the definitions
|
|
16
|
+
* directly, because the address is the part nobody declares and everybody
|
|
17
|
+
* needs — codegen writes it into `personaConfigs`, the seed creates the user
|
|
18
|
+
* rows from it, and both `scenario run` and `persona run` sign in with it.
|
|
19
|
+
* Computing it in three places is how two of them end up disagreeing.
|
|
20
|
+
*/
|
|
21
|
+
export declare const resolvePersonas: (definitions: PersonaDefinitions, domain?: string, runId?: string) => Record<string, ResolvedPersona>;
|
|
22
|
+
/**
|
|
23
|
+
* The scopes a persona holds, resolved through its roles.
|
|
24
|
+
*
|
|
25
|
+
* Roles are the only thing a persona declares; scopes are what a function
|
|
26
|
+
* checks. Narrowing a virtual user's catalogue needs the second, so the
|
|
27
|
+
* expansion happens once, here, against the same `defineSystemRole` definitions
|
|
28
|
+
* the seed grants from.
|
|
29
|
+
*/
|
|
30
|
+
export declare const personaScopes: (persona: {
|
|
31
|
+
roles?: readonly string[];
|
|
32
|
+
}, roleScopes: Record<string, readonly string[]>) => string[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { personaEmails, validateAndBuildPersonasMeta, } from '@pikku/core/persona';
|
|
2
|
+
/**
|
|
3
|
+
* Where a persona's mail lands when the project has not said.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately a domain nobody can own rather than something plausible: an app
|
|
6
|
+
* that never sends mail runs fine against it, and one that does fails loudly at
|
|
7
|
+
* the first send instead of quietly delivering a magic link to a stranger.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_PERSONA_EMAIL_DOMAIN = 'personas.invalid';
|
|
10
|
+
/**
|
|
11
|
+
* The personas a run actually sees: every declaration, with its address filled
|
|
12
|
+
* in.
|
|
13
|
+
*
|
|
14
|
+
* Every consumer resolves through here rather than reading the definitions
|
|
15
|
+
* directly, because the address is the part nobody declares and everybody
|
|
16
|
+
* needs — codegen writes it into `personaConfigs`, the seed creates the user
|
|
17
|
+
* rows from it, and both `scenario run` and `persona run` sign in with it.
|
|
18
|
+
* Computing it in three places is how two of them end up disagreeing.
|
|
19
|
+
*/
|
|
20
|
+
export const resolvePersonas = (definitions, domain = DEFAULT_PERSONA_EMAIL_DOMAIN, runId) => {
|
|
21
|
+
const meta = validateAndBuildPersonasMeta(definitions);
|
|
22
|
+
const ids = Object.keys(meta);
|
|
23
|
+
const emails = personaEmails(ids, domain, runId);
|
|
24
|
+
const resolved = {};
|
|
25
|
+
for (const id of ids) {
|
|
26
|
+
resolved[id] = { ...meta[id], email: emails[id] };
|
|
27
|
+
}
|
|
28
|
+
return resolved;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The scopes a persona holds, resolved through its roles.
|
|
32
|
+
*
|
|
33
|
+
* Roles are the only thing a persona declares; scopes are what a function
|
|
34
|
+
* checks. Narrowing a virtual user's catalogue needs the second, so the
|
|
35
|
+
* expansion happens once, here, against the same `defineSystemRole` definitions
|
|
36
|
+
* the seed grants from.
|
|
37
|
+
*/
|
|
38
|
+
export const personaScopes = (persona, roleScopes) => {
|
|
39
|
+
const scopes = new Set();
|
|
40
|
+
for (const role of persona.roles ?? []) {
|
|
41
|
+
for (const scope of roleScopes[role] ?? []) {
|
|
42
|
+
scopes.add(scope);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return [...scopes].sort();
|
|
46
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PikkuScaffoldFeature } from '../../types/config.js';
|
|
2
|
+
export type ResolvedScaffoldFeature = {
|
|
3
|
+
/** Whether to generate this surface at all. */
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
/** Whether the generated surface requires a session. */
|
|
6
|
+
auth: boolean;
|
|
7
|
+
/** An explicit output path, or undefined to derive one from `pikkuDir`. */
|
|
8
|
+
path?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Reads one `scaffold.<feature>` value.
|
|
12
|
+
*
|
|
13
|
+
* `true` means enabled **and authenticated**. A surface only becomes public by
|
|
14
|
+
* writing `{ auth: false }`, so omitting the field can never open anything —
|
|
15
|
+
* the failure mode of a forgotten flag is a locked door, not an open one.
|
|
16
|
+
*
|
|
17
|
+
* knowledge: decisions/security/scaffold-features-are-authenticated-unless-opted-out.md
|
|
18
|
+
*/
|
|
19
|
+
export declare const resolveScaffoldFeature: (feature: string, value: PikkuScaffoldFeature | undefined) => ResolvedScaffoldFeature;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const LEGACY_VALUES = new Set(['auth', 'no-auth']);
|
|
2
|
+
/**
|
|
3
|
+
* Reads one `scaffold.<feature>` value.
|
|
4
|
+
*
|
|
5
|
+
* `true` means enabled **and authenticated**. A surface only becomes public by
|
|
6
|
+
* writing `{ auth: false }`, so omitting the field can never open anything —
|
|
7
|
+
* the failure mode of a forgotten flag is a locked door, not an open one.
|
|
8
|
+
*
|
|
9
|
+
* knowledge: decisions/security/scaffold-features-are-authenticated-unless-opted-out.md
|
|
10
|
+
*/
|
|
11
|
+
export const resolveScaffoldFeature = (feature, value) => {
|
|
12
|
+
if (value === undefined || value === false) {
|
|
13
|
+
return { enabled: false, auth: true };
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === 'string') {
|
|
16
|
+
throw new Error(legacyValueMessage(feature, value));
|
|
17
|
+
}
|
|
18
|
+
if (value === true) {
|
|
19
|
+
return { enabled: true, auth: true };
|
|
20
|
+
}
|
|
21
|
+
if (typeof value !== 'object') {
|
|
22
|
+
throw new Error(`pikku.config.json: scaffold.${feature} must be true, false, or an object ` +
|
|
23
|
+
`like { "auth": false } — received ${JSON.stringify(value)}.`);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
enabled: true,
|
|
27
|
+
auth: value.auth !== false,
|
|
28
|
+
path: value.path,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A legacy `'auth'`/`'no-auth'` is refused by name rather than coerced. The
|
|
33
|
+
* value it would coerce to is exactly the one that caused the incident this
|
|
34
|
+
* change came out of, and under the new shape a bare string is a plausible
|
|
35
|
+
* path — so guessing would be wrong twice over.
|
|
36
|
+
*/
|
|
37
|
+
const legacyValueMessage = (feature, value) => {
|
|
38
|
+
const migrated = value === 'no-auth' ? '{ "auth": false }' : LEGACY_VALUES.has(value) ? 'true' : null;
|
|
39
|
+
if (!migrated) {
|
|
40
|
+
return (`pikku.config.json: scaffold.${feature} must be true, false, or an object ` +
|
|
41
|
+
`like { "auth": false } — received the string ${JSON.stringify(value)}.`);
|
|
42
|
+
}
|
|
43
|
+
return (`pikku.config.json: scaffold.${feature} is ${JSON.stringify(value)}, which is no longer a ` +
|
|
44
|
+
`mode. Authentication is declared on the function or on the addon, and the ` +
|
|
45
|
+
`scaffold flag only says whether the surface exists.\n` +
|
|
46
|
+
` Replace it with: "${feature}": ${migrated}\n` +
|
|
47
|
+
` \`true\` enables the surface with a session required; ` +
|
|
48
|
+
`\`{ "auth": false }\` makes it public.`);
|
|
49
|
+
};
|