@pikku/cli 0.12.91 → 0.12.92
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/LICENSE +106 -0
- package/console-app/assets/{index-CpkvmAGb.js → index-DDpIMCpy.js} +149 -149
- package/console-app/index.html +1 -1
- 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.js +6 -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-contracts-meta.gen.json +15 -0
- 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 +21 -0
- 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 +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +35 -40
- package/dist/.pikku/function/pikku-functions.gen.js +1 -3
- 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 +2 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +2 -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 +1 -1
- 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 +4 -0
- package/dist/.pikku/scenarios/schemas/register.gen.js +4 -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 +5 -3
- package/dist/.pikku/schemas/schemas/FabricSecretsListOutput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/FabricSecretsRotateInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricSecretsRotateOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricSecretsSetOutput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
- package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
- 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.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 +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- 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 +2 -8
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -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 +6 -0
- package/dist/src/deploy/analyzer/analyzer.js +22 -5
- package/dist/src/deploy/build-pipeline.js +5 -1
- package/dist/src/fabric/fabric-commands.d.ts +37 -9
- package/dist/src/fabric/fabric-commands.js +12 -0
- package/dist/src/fabric/functions/domains-add.function.d.ts +4 -4
- package/dist/src/fabric/functions/secrets-list.function.d.ts +16 -4
- package/dist/src/fabric/functions/secrets-list.function.js +12 -10
- package/dist/src/fabric/functions/secrets-rotate.function.d.ts +24 -0
- package/dist/src/fabric/functions/secrets-rotate.function.js +36 -0
- package/dist/src/fabric/functions/secrets-set.function.d.ts +8 -4
- package/dist/src/fabric/functions/secrets-set.function.js +18 -7
- package/dist/src/fabric/lib/http.d.ts +7 -1
- package/dist/src/fabric/lib/sealed-box.d.ts +16 -0
- package/dist/src/fabric/lib/sealed-box.js +72 -0
- package/dist/src/functions/commands/dev.js +9 -0
- package/dist/src/functions/commands/pikku-command-bootstrap.js +1 -0
- package/dist/src/functions/commands/scenario-browser.d.ts +17 -2
- package/dist/src/functions/commands/scenario-browser.js +16 -8
- package/dist/src/functions/commands/scenario-environment.d.ts +32 -0
- package/dist/src/functions/commands/scenario-environment.js +61 -0
- package/dist/src/functions/commands/scenario.d.ts +6 -0
- package/dist/src/functions/commands/scenario.js +14 -9
- package/dist/src/functions/commands/skills.js +27 -61
- package/dist/src/functions/db/db-codegen.d.ts +6 -0
- package/dist/src/functions/db/db-codegen.js +9 -0
- package/dist/src/functions/db/db-migrator.js +14 -0
- package/dist/src/functions/db/local-db.d.ts +8 -0
- package/dist/src/functions/db/local-db.js +24 -2
- package/dist/src/functions/db/migration-identifiers.d.ts +58 -0
- package/dist/src/functions/db/migration-identifiers.js +262 -0
- package/dist/src/functions/db/migration-provenance.d.ts +35 -0
- package/dist/src/functions/db/migration-provenance.js +80 -0
- package/dist/src/functions/db/schema-sql.d.ts +43 -0
- package/dist/src/functions/db/schema-sql.js +135 -0
- package/dist/src/functions/db/sqlite/sqlite-kysely.js +22 -2
- package/dist/src/functions/wirings/functions/schemas.js +13 -1
- package/dist/src/functions/wirings/rpc/pikku-command-rpc.js +6 -1
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.d.ts +10 -0
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.js +102 -0
- package/dist/src/functions/wirings/scenarios/scenario-partition.d.ts +13 -0
- package/dist/src/functions/wirings/scenarios/scenario-partition.js +15 -2
- package/dist/src/functions/wirings/scenarios/scenario-schema-partition.d.ts +33 -0
- package/dist/src/functions/wirings/scenarios/scenario-schema-partition.js +49 -0
- package/dist/src/functions/workflows/all.workflow.js +2 -7
- package/dist/src/utils/file-writer.js +14 -2
- package/dist/src/utils/pikku-cli-config.d.ts +14 -0
- package/dist/src/utils/pikku-cli-config.js +34 -0
- package/dist/src/utils/remove-legacy-scaffold-file.d.ts +14 -0
- package/dist/src/utils/remove-legacy-scaffold-file.js +23 -0
- package/dist/src/utils/serialize-schemas.d.ts +3 -1
- package/dist/src/utils/serialize-schemas.js +39 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/dist/.pikku/schemas/schemas/PikkuScenarioFunctionsOutput.schema.json +0 -1
- package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.d.ts +0 -1
- package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.js +0 -31
- package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.d.ts +0 -10
- package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.js +0 -104
- package/skills/pikku-addon/SKILL.md +0 -243
- package/skills/pikku-addon/references/addon-package-manifest.md +0 -63
- package/skills/pikku-ai-agent/SKILL.md +0 -231
- package/skills/pikku-ai-vercel/SKILL.md +0 -82
- package/skills/pikku-ai-voice/SKILL.md +0 -88
- package/skills/pikku-audit/SKILL.md +0 -175
- package/skills/pikku-aws/SKILL.md +0 -111
- package/skills/pikku-backblaze/SKILL.md +0 -71
- package/skills/pikku-better-auth/SKILL.md +0 -298
- package/skills/pikku-cli/SKILL.md +0 -198
- package/skills/pikku-cli/references/complete-example.md +0 -82
- package/skills/pikku-concepts/SKILL.md +0 -250
- package/skills/pikku-concepts/references/concept-mapping.md +0 -556
- package/skills/pikku-concepts/references/packages.md +0 -29
- package/skills/pikku-config/SKILL.md +0 -212
- package/skills/pikku-cron/SKILL.md +0 -214
- package/skills/pikku-deploy-azure/SKILL.md +0 -71
- package/skills/pikku-deploy-cloudflare/SKILL.md +0 -82
- package/skills/pikku-deploy-express/SKILL.md +0 -86
- package/skills/pikku-deploy-fastify/SKILL.md +0 -73
- package/skills/pikku-deploy-lambda/SKILL.md +0 -110
- package/skills/pikku-deploy-nextjs/SKILL.md +0 -78
- package/skills/pikku-deploy-uws/SKILL.md +0 -88
- package/skills/pikku-deps/SKILL.md +0 -98
- package/skills/pikku-emails/SKILL.md +0 -163
- package/skills/pikku-fabric/SKILL.md +0 -318
- package/skills/pikku-fabric-debug/SKILL.md +0 -112
- package/skills/pikku-feature/SKILL.md +0 -258
- package/skills/pikku-gateway-slack/SKILL.md +0 -115
- package/skills/pikku-http/SKILL.md +0 -220
- package/skills/pikku-http/references/http-options.md +0 -55
- package/skills/pikku-i18n/SKILL.md +0 -137
- package/skills/pikku-info/SKILL.md +0 -100
- package/skills/pikku-jose/SKILL.md +0 -105
- package/skills/pikku-kysely/SKILL.md +0 -219
- package/skills/pikku-machine-auth/SKILL.md +0 -183
- package/skills/pikku-mcp/SKILL.md +0 -241
- package/skills/pikku-middleware/SKILL.md +0 -231
- package/skills/pikku-middleware/references/middleware-patterns.md +0 -61
- package/skills/pikku-mongodb/SKILL.md +0 -105
- package/skills/pikku-n8n-import/SKILL.md +0 -109
- package/skills/pikku-n8n-import/SPEC.md +0 -84
- package/skills/pikku-n8n-import/references/addon-mapping.md +0 -121
- package/skills/pikku-n8n-import/references/code-translation.md +0 -121
- package/skills/pikku-n8n-import/references/loops-and-control.md +0 -87
- package/skills/pikku-paraglide/SKILL.md +0 -117
- package/skills/pikku-permissions/SKILL.md +0 -192
- package/skills/pikku-pino/SKILL.md +0 -79
- package/skills/pikku-product-second-opinion/README.md +0 -43
- package/skills/pikku-product-second-opinion/SKILL.md +0 -158
- package/skills/pikku-product-second-opinion/example/sample-report.md +0 -90
- package/skills/pikku-product-second-opinion/references/report-template.md +0 -73
- package/skills/pikku-queue/SKILL.md +0 -240
- package/skills/pikku-react/SKILL.md +0 -212
- package/skills/pikku-react-query/SKILL.md +0 -242
- package/skills/pikku-realtime/SKILL.md +0 -236
- package/skills/pikku-realtime/references/other-routes.md +0 -23
- package/skills/pikku-redis/SKILL.md +0 -90
- package/skills/pikku-rpc/SKILL.md +0 -171
- package/skills/pikku-rtl/SKILL.md +0 -219
- package/skills/pikku-scenario/SKILL.md +0 -382
- package/skills/pikku-schedule/SKILL.md +0 -57
- package/skills/pikku-schema-ajv/SKILL.md +0 -62
- package/skills/pikku-schema-cfworker/SKILL.md +0 -63
- package/skills/pikku-security/SKILL.md +0 -108
- package/skills/pikku-services/SKILL.md +0 -248
- package/skills/pikku-services/references/audit-wire-service.md +0 -34
- package/skills/pikku-software-archaeology/README.md +0 -70
- package/skills/pikku-software-archaeology/SKILL.md +0 -186
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +0 -625
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +0 -49
- package/skills/pikku-software-archaeology/scripts/validate.mjs +0 -173
- package/skills/pikku-tag-middleware/SKILL.md +0 -13
- package/skills/pikku-template-clone/SKILL.md +0 -40
- package/skills/pikku-trigger/SKILL.md +0 -181
- package/skills/pikku-versioning/SKILL.md +0 -173
- package/skills/pikku-websocket/SKILL.md +0 -243
- package/skills/pikku-workflow/SKILL.md +0 -172
- package/skills/pikku-workflow/references/workflow-reference.md +0 -63
- package/skills/pikku-workflows-client/SKILL.md +0 -150
- package/skills/pikku-ws/SKILL.md +0 -47
|
@@ -451,37 +451,65 @@ export declare const fabricCommands: {
|
|
|
451
451
|
value?: string | undefined;
|
|
452
452
|
force?: boolean | undefined;
|
|
453
453
|
}, {
|
|
454
|
-
|
|
454
|
+
name: string;
|
|
455
|
+
keyId: string;
|
|
455
456
|
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
|
|
456
457
|
name: string;
|
|
457
458
|
branch: string;
|
|
458
459
|
value?: string | undefined;
|
|
459
460
|
force?: boolean | undefined;
|
|
460
461
|
}, {
|
|
461
|
-
|
|
462
|
+
name: string;
|
|
463
|
+
keyId: string;
|
|
462
464
|
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices> | import("../../.pikku/pikku-types.gen.js").PikkuFunction<{
|
|
463
465
|
name: string;
|
|
464
466
|
branch: string;
|
|
465
467
|
value?: string | undefined;
|
|
466
468
|
force?: boolean | undefined;
|
|
467
469
|
}, {
|
|
468
|
-
|
|
470
|
+
name: string;
|
|
471
|
+
keyId: string;
|
|
469
472
|
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>, import("../../.pikku/pikku-types.gen.js").PikkuMiddleware, (services: import("../../types/application-types.js").SingletonServices, data: any, session?: import("../../types/application-types.js").UserSession | undefined) => void | Promise<void>, string>;
|
|
470
473
|
list: import("@pikku/core/cli").CoreCLICommandConfig<import("../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
|
|
471
474
|
branch: string;
|
|
472
475
|
json?: boolean | undefined;
|
|
473
476
|
}, {
|
|
474
|
-
|
|
477
|
+
secrets: {
|
|
478
|
+
name: string;
|
|
479
|
+
updatedAt: string;
|
|
480
|
+
}[];
|
|
475
481
|
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
|
|
476
482
|
branch: string;
|
|
477
483
|
json?: boolean | undefined;
|
|
478
484
|
}, {
|
|
479
|
-
|
|
485
|
+
secrets: {
|
|
486
|
+
name: string;
|
|
487
|
+
updatedAt: string;
|
|
488
|
+
}[];
|
|
480
489
|
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices> | import("../../.pikku/pikku-types.gen.js").PikkuFunction<{
|
|
481
490
|
branch: string;
|
|
482
491
|
json?: boolean | undefined;
|
|
483
492
|
}, {
|
|
484
|
-
|
|
493
|
+
secrets: {
|
|
494
|
+
name: string;
|
|
495
|
+
updatedAt: string;
|
|
496
|
+
}[];
|
|
497
|
+
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>, import("../../.pikku/pikku-types.gen.js").PikkuMiddleware, (services: import("../../types/application-types.js").SingletonServices, data: any, session?: import("../../types/application-types.js").UserSession | undefined) => void | Promise<void>, string>;
|
|
498
|
+
rotate: import("@pikku/core/cli").CoreCLICommandConfig<import("../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
|
|
499
|
+
branch: string;
|
|
500
|
+
force?: boolean | undefined;
|
|
501
|
+
}, {
|
|
502
|
+
retiredKeyId: string;
|
|
503
|
+
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
|
|
504
|
+
branch: string;
|
|
505
|
+
force?: boolean | undefined;
|
|
506
|
+
}, {
|
|
507
|
+
retiredKeyId: string;
|
|
508
|
+
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices> | import("../../.pikku/pikku-types.gen.js").PikkuFunction<{
|
|
509
|
+
branch: string;
|
|
510
|
+
force?: boolean | undefined;
|
|
511
|
+
}, {
|
|
512
|
+
retiredKeyId: string;
|
|
485
513
|
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>, import("../../.pikku/pikku-types.gen.js").PikkuMiddleware, (services: import("../../types/application-types.js").SingletonServices, data: any, session?: import("../../types/application-types.js").UserSession | undefined) => void | Promise<void>, string>;
|
|
486
514
|
};
|
|
487
515
|
};
|
|
@@ -622,21 +650,21 @@ export declare const fabricCommands: {
|
|
|
622
650
|
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>, import("../../.pikku/pikku-types.gen.js").PikkuMiddleware, (services: import("../../types/application-types.js").SingletonServices, data: any, session?: import("../../types/application-types.js").UserSession | undefined) => void | Promise<void>, string>;
|
|
623
651
|
add: import("@pikku/core/cli").CoreCLICommandConfig<import("../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
|
|
624
652
|
hostname: string;
|
|
625
|
-
target?: "
|
|
653
|
+
target?: "app" | "api" | undefined;
|
|
626
654
|
apiUrl?: string | undefined;
|
|
627
655
|
}, {
|
|
628
656
|
hostname: string;
|
|
629
657
|
cnameTarget: string;
|
|
630
658
|
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
|
|
631
659
|
hostname: string;
|
|
632
|
-
target?: "
|
|
660
|
+
target?: "app" | "api" | undefined;
|
|
633
661
|
apiUrl?: string | undefined;
|
|
634
662
|
}, {
|
|
635
663
|
hostname: string;
|
|
636
664
|
cnameTarget: string;
|
|
637
665
|
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices> | import("../../.pikku/pikku-types.gen.js").PikkuFunction<{
|
|
638
666
|
hostname: string;
|
|
639
|
-
target?: "
|
|
667
|
+
target?: "app" | "api" | undefined;
|
|
640
668
|
apiUrl?: string | undefined;
|
|
641
669
|
}, {
|
|
642
670
|
hostname: string;
|
|
@@ -31,6 +31,7 @@ import { FabricDbSchema, renderDbSchema, } from './functions/db-schema.function.
|
|
|
31
31
|
import { FabricRollback } from './functions/rollback.function.js';
|
|
32
32
|
import { FabricSecretsSet } from './functions/secrets-set.function.js';
|
|
33
33
|
import { FabricSecretsList } from './functions/secrets-list.function.js';
|
|
34
|
+
import { FabricSecretsRotate } from './functions/secrets-rotate.function.js';
|
|
34
35
|
import { FabricLogs } from './functions/logs.function.js';
|
|
35
36
|
import { FabricMetrics } from './functions/metrics.function.js';
|
|
36
37
|
import { FabricTrace } from './functions/trace.function.js';
|
|
@@ -241,6 +242,17 @@ export const fabricCommands = defineCLICommands({
|
|
|
241
242
|
json: { description: 'Machine-readable output', default: false },
|
|
242
243
|
},
|
|
243
244
|
}),
|
|
245
|
+
rotate: pikkuCLICommand({
|
|
246
|
+
func: FabricSecretsRotate,
|
|
247
|
+
description: "Retire a stage's sealing key (secrets must be set again)",
|
|
248
|
+
options: {
|
|
249
|
+
branch: { description: 'Target branch', short: 'b' },
|
|
250
|
+
force: {
|
|
251
|
+
description: 'Confirm that existing secrets become unreadable',
|
|
252
|
+
default: false,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
}),
|
|
244
256
|
},
|
|
245
257
|
},
|
|
246
258
|
logs: pikkuCLICommand({
|
|
@@ -2,8 +2,8 @@ import { z } from 'zod';
|
|
|
2
2
|
export declare const FabricDomainsAddInput: z.ZodObject<{
|
|
3
3
|
hostname: z.ZodString;
|
|
4
4
|
target: z.ZodOptional<z.ZodEnum<{
|
|
5
|
-
api: "api";
|
|
6
5
|
app: "app";
|
|
6
|
+
api: "api";
|
|
7
7
|
}>>;
|
|
8
8
|
apiUrl: z.ZodOptional<z.ZodString>;
|
|
9
9
|
}, z.core.$strip>;
|
|
@@ -13,21 +13,21 @@ export declare const FabricDomainsAddOutput: z.ZodObject<{
|
|
|
13
13
|
}, z.core.$strip>;
|
|
14
14
|
export declare const FabricDomainsAdd: import("../../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
|
|
15
15
|
hostname: string;
|
|
16
|
-
target?: "
|
|
16
|
+
target?: "app" | "api" | undefined;
|
|
17
17
|
apiUrl?: string | undefined;
|
|
18
18
|
}, {
|
|
19
19
|
hostname: string;
|
|
20
20
|
cnameTarget: string;
|
|
21
21
|
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
|
|
22
22
|
hostname: string;
|
|
23
|
-
target?: "
|
|
23
|
+
target?: "app" | "api" | undefined;
|
|
24
24
|
apiUrl?: string | undefined;
|
|
25
25
|
}, {
|
|
26
26
|
hostname: string;
|
|
27
27
|
cnameTarget: string;
|
|
28
28
|
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices> | import("../../../.pikku/pikku-types.gen.js").PikkuFunction<{
|
|
29
29
|
hostname: string;
|
|
30
|
-
target?: "
|
|
30
|
+
target?: "app" | "api" | undefined;
|
|
31
31
|
apiUrl?: string | undefined;
|
|
32
32
|
}, {
|
|
33
33
|
hostname: string;
|
|
@@ -4,21 +4,33 @@ export declare const FabricSecretsListInput: z.ZodObject<{
|
|
|
4
4
|
json: z.ZodOptional<z.ZodBoolean>;
|
|
5
5
|
}, z.core.$strip>;
|
|
6
6
|
export declare const FabricSecretsListOutput: z.ZodObject<{
|
|
7
|
-
|
|
7
|
+
secrets: z.ZodArray<z.ZodObject<{
|
|
8
|
+
name: z.ZodString;
|
|
9
|
+
updatedAt: z.ZodString;
|
|
10
|
+
}, z.core.$strip>>;
|
|
8
11
|
}, z.core.$strip>;
|
|
9
12
|
export declare const FabricSecretsList: import("../../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
|
|
10
13
|
branch: string;
|
|
11
14
|
json?: boolean | undefined;
|
|
12
15
|
}, {
|
|
13
|
-
|
|
16
|
+
secrets: {
|
|
17
|
+
name: string;
|
|
18
|
+
updatedAt: string;
|
|
19
|
+
}[];
|
|
14
20
|
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
|
|
15
21
|
branch: string;
|
|
16
22
|
json?: boolean | undefined;
|
|
17
23
|
}, {
|
|
18
|
-
|
|
24
|
+
secrets: {
|
|
25
|
+
name: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
}[];
|
|
19
28
|
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices> | import("../../../.pikku/pikku-types.gen.js").PikkuFunction<{
|
|
20
29
|
branch: string;
|
|
21
30
|
json?: boolean | undefined;
|
|
22
31
|
}, {
|
|
23
|
-
|
|
32
|
+
secrets: {
|
|
33
|
+
name: string;
|
|
34
|
+
updatedAt: string;
|
|
35
|
+
}[];
|
|
24
36
|
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>;
|
|
@@ -2,13 +2,16 @@ import { z } from 'zod';
|
|
|
2
2
|
import { pikkuSessionlessFunc } from '../../../.pikku/pikku-types.gen.js';
|
|
3
3
|
import { resolveApiContext } from '../lib/config.js';
|
|
4
4
|
import { getFabricRPC } from '../lib/http.js';
|
|
5
|
+
import { resolveStageId } from '../lib/stage.js';
|
|
5
6
|
export const FabricSecretsListInput = z.object({
|
|
6
7
|
branch: z.string(),
|
|
7
8
|
json: z.boolean().optional(),
|
|
8
9
|
});
|
|
9
|
-
export const FabricSecretsListOutput = z.object({
|
|
10
|
+
export const FabricSecretsListOutput = z.object({
|
|
11
|
+
secrets: z.array(z.object({ name: z.string(), updatedAt: z.string() })),
|
|
12
|
+
});
|
|
10
13
|
export const FabricSecretsList = pikkuSessionlessFunc({
|
|
11
|
-
description: 'List
|
|
14
|
+
description: 'List the secrets set on a stage, by name. Values are sealed to the stage and never leave it.',
|
|
12
15
|
input: FabricSecretsListInput,
|
|
13
16
|
output: FabricSecretsListOutput,
|
|
14
17
|
func: async (_services, { branch, json }) => {
|
|
@@ -18,19 +21,18 @@ export const FabricSecretsList = pikkuSessionlessFunc({
|
|
|
18
21
|
if (!ctx.projectId)
|
|
19
22
|
throw new Error('No fabric project linked. Run `pikku fabric link` first.');
|
|
20
23
|
const rpc = getFabricRPC({ apiUrl: ctx.apiUrl, token: ctx.token });
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
branch,
|
|
24
|
-
});
|
|
24
|
+
const stageId = await resolveStageId(rpc, ctx.projectId, branch);
|
|
25
|
+
const result = await rpc.invoke('listStageSecretNames', { stageId });
|
|
25
26
|
if (json) {
|
|
26
|
-
console.log(JSON.stringify({ branch,
|
|
27
|
+
console.log(JSON.stringify({ branch, secrets: result.secrets }, null, 2));
|
|
27
28
|
}
|
|
28
|
-
else if (result.
|
|
29
|
+
else if (result.secrets.length === 0) {
|
|
29
30
|
console.log(`[fabric] no secrets set on ${branch}`);
|
|
30
31
|
}
|
|
31
32
|
else {
|
|
32
|
-
for (const name of result.
|
|
33
|
-
console.log(name);
|
|
33
|
+
for (const { name, updatedAt } of result.secrets) {
|
|
34
|
+
console.log(`${name}\t${updatedAt}`);
|
|
35
|
+
}
|
|
34
36
|
}
|
|
35
37
|
return result;
|
|
36
38
|
},
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const FabricSecretsRotateInput: z.ZodObject<{
|
|
3
|
+
branch: z.ZodString;
|
|
4
|
+
force: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
export declare const FabricSecretsRotateOutput: z.ZodObject<{
|
|
7
|
+
retiredKeyId: z.ZodString;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export declare const FabricSecretsRotate: import("../../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
|
|
10
|
+
branch: string;
|
|
11
|
+
force?: boolean | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
retiredKeyId: string;
|
|
14
|
+
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
|
|
15
|
+
branch: string;
|
|
16
|
+
force?: boolean | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
retiredKeyId: string;
|
|
19
|
+
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices> | import("../../../.pikku/pikku-types.gen.js").PikkuFunction<{
|
|
20
|
+
branch: string;
|
|
21
|
+
force?: boolean | undefined;
|
|
22
|
+
}, {
|
|
23
|
+
retiredKeyId: string;
|
|
24
|
+
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { pikkuSessionlessFunc } from '../../../.pikku/pikku-types.gen.js';
|
|
3
|
+
import { resolveApiContext } from '../lib/config.js';
|
|
4
|
+
import { getFabricRPC } from '../lib/http.js';
|
|
5
|
+
import { resolveStageId } from '../lib/stage.js';
|
|
6
|
+
export const FabricSecretsRotateInput = z.object({
|
|
7
|
+
branch: z.string(),
|
|
8
|
+
force: z.boolean().optional(),
|
|
9
|
+
});
|
|
10
|
+
export const FabricSecretsRotateOutput = z.object({
|
|
11
|
+
retiredKeyId: z.string(),
|
|
12
|
+
});
|
|
13
|
+
export const FabricSecretsRotate = pikkuSessionlessFunc({
|
|
14
|
+
description: "Retire a stage's sealing key so the next deploy issues a new one. Secrets sealed to the old key must be set again.",
|
|
15
|
+
input: FabricSecretsRotateInput,
|
|
16
|
+
output: FabricSecretsRotateOutput,
|
|
17
|
+
func: async (_services, { branch, force }) => {
|
|
18
|
+
const ctx = await resolveApiContext();
|
|
19
|
+
if (!ctx.token)
|
|
20
|
+
throw new Error('Not logged in. Run `pikku fabric login` first.');
|
|
21
|
+
if (!ctx.projectId)
|
|
22
|
+
throw new Error('No fabric project linked. Run `pikku fabric link` first.');
|
|
23
|
+
if (!force) {
|
|
24
|
+
// Not a confirmation for politeness — this is the one operation here
|
|
25
|
+
// that destroys access to data. Fabric cannot read the sealed values, so
|
|
26
|
+
// it cannot carry them over, and nobody can undo it afterwards.
|
|
27
|
+
throw new Error(`Rotating the sealing key on ${branch} makes every secret already set on it unreadable — fabric cannot re-seal values it cannot open, so each one must be set again after the next deploy. Re-run with --force if that is what you want.`);
|
|
28
|
+
}
|
|
29
|
+
const rpc = getFabricRPC({ apiUrl: ctx.apiUrl, token: ctx.token });
|
|
30
|
+
const stageId = await resolveStageId(rpc, ctx.projectId, branch);
|
|
31
|
+
const result = await rpc.invoke('rotateStageSealingKey', { stageId });
|
|
32
|
+
console.log(`[fabric] retired sealing key ${result.retiredKeyId} on ${branch}`);
|
|
33
|
+
console.log('[fabric] deploy the stage to issue a new key, then set its secrets again');
|
|
34
|
+
return result;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -6,7 +6,8 @@ export declare const FabricSecretsSetInput: z.ZodObject<{
|
|
|
6
6
|
force: z.ZodOptional<z.ZodBoolean>;
|
|
7
7
|
}, z.core.$strip>;
|
|
8
8
|
export declare const FabricSecretsSetOutput: z.ZodObject<{
|
|
9
|
-
|
|
9
|
+
name: z.ZodString;
|
|
10
|
+
keyId: z.ZodString;
|
|
10
11
|
}, z.core.$strip>;
|
|
11
12
|
export declare const FabricSecretsSet: import("../../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
|
|
12
13
|
name: string;
|
|
@@ -14,19 +15,22 @@ export declare const FabricSecretsSet: import("../../../.pikku/pikku-types.gen.j
|
|
|
14
15
|
value?: string | undefined;
|
|
15
16
|
force?: boolean | undefined;
|
|
16
17
|
}, {
|
|
17
|
-
|
|
18
|
+
name: string;
|
|
19
|
+
keyId: string;
|
|
18
20
|
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
|
|
19
21
|
name: string;
|
|
20
22
|
branch: string;
|
|
21
23
|
value?: string | undefined;
|
|
22
24
|
force?: boolean | undefined;
|
|
23
25
|
}, {
|
|
24
|
-
|
|
26
|
+
name: string;
|
|
27
|
+
keyId: string;
|
|
25
28
|
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices> | import("../../../.pikku/pikku-types.gen.js").PikkuFunction<{
|
|
26
29
|
name: string;
|
|
27
30
|
branch: string;
|
|
28
31
|
value?: string | undefined;
|
|
29
32
|
force?: boolean | undefined;
|
|
30
33
|
}, {
|
|
31
|
-
|
|
34
|
+
name: string;
|
|
35
|
+
keyId: string;
|
|
32
36
|
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>;
|
|
@@ -3,15 +3,20 @@ import { pikkuSessionlessFunc } from '../../../.pikku/pikku-types.gen.js';
|
|
|
3
3
|
import { resolveApiContext } from '../lib/config.js';
|
|
4
4
|
import { getFabricRPC } from '../lib/http.js';
|
|
5
5
|
import { promptSecret } from '../lib/prompt.js';
|
|
6
|
+
import { resolveStageId } from '../lib/stage.js';
|
|
7
|
+
import { seal, serializeSealedValue } from '../lib/sealed-box.js';
|
|
6
8
|
export const FabricSecretsSetInput = z.object({
|
|
7
9
|
name: z.string(),
|
|
8
10
|
branch: z.string(),
|
|
9
11
|
value: z.string().optional(),
|
|
10
12
|
force: z.boolean().optional(),
|
|
11
13
|
});
|
|
12
|
-
export const FabricSecretsSetOutput = z.object({
|
|
14
|
+
export const FabricSecretsSetOutput = z.object({
|
|
15
|
+
name: z.string(),
|
|
16
|
+
keyId: z.string(),
|
|
17
|
+
});
|
|
13
18
|
export const FabricSecretsSet = pikkuSessionlessFunc({
|
|
14
|
-
description: 'Set a stage-scoped secret.
|
|
19
|
+
description: 'Set a stage-scoped secret. Sealed here to the stage public key — the plaintext never leaves this machine.',
|
|
15
20
|
input: FabricSecretsSetInput,
|
|
16
21
|
output: FabricSecretsSetOutput,
|
|
17
22
|
func: async (_services, { name, branch, value }) => {
|
|
@@ -24,13 +29,19 @@ export const FabricSecretsSet = pikkuSessionlessFunc({
|
|
|
24
29
|
if (!plaintext)
|
|
25
30
|
throw new Error('Empty secret value — aborting.');
|
|
26
31
|
const rpc = getFabricRPC({ apiUrl: ctx.apiUrl, token: ctx.token });
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
const stageId = await resolveStageId(rpc, ctx.projectId, branch);
|
|
33
|
+
// Fabric holds only the public half of this keypair. The private half went
|
|
34
|
+
// to the stage's worker at deploy time and was dropped there, so the value
|
|
35
|
+
// sealed below can be opened by that worker and by nothing else — including
|
|
36
|
+
// by the fabric process that stores it.
|
|
37
|
+
const key = await rpc.invoke('getStageSealingKey', { stageId });
|
|
38
|
+
const sealed = seal(key.publicKey, key.keyId, plaintext);
|
|
39
|
+
const result = await rpc.invoke('setStageSealedSecret', {
|
|
40
|
+
stageId,
|
|
30
41
|
name,
|
|
31
|
-
|
|
42
|
+
sealedValue: serializeSealedValue(sealed),
|
|
32
43
|
});
|
|
33
|
-
console.log(`[fabric] ${name} set on ${branch}.`);
|
|
44
|
+
console.log(`[fabric] ${name} sealed to ${result.keyId} and set on ${branch}.`);
|
|
34
45
|
return result;
|
|
35
46
|
},
|
|
36
47
|
});
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe RPC client backed by the snapshot SDK in `../sdk/`. The map is
|
|
3
|
+
* a copy of fabric's generated rpc-map at the time of the addon's last
|
|
4
|
+
* release — versioning bumps are taken by republishing this package.
|
|
5
|
+
*/
|
|
6
|
+
import { PikkuRPC } from '../sdk/pikku-rpc.gen.js';
|
|
1
7
|
export declare function getFabricRPC(opts: {
|
|
2
8
|
apiUrl: string;
|
|
3
9
|
token: string | null;
|
|
4
|
-
}):
|
|
10
|
+
}): PikkuRPC;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Algorithm tag carried by every sealed value. Must match fabric's. */
|
|
2
|
+
export declare const SEALED_BOX_ALGORITHM = "sealed-x25519-v1";
|
|
3
|
+
export interface SealedValue {
|
|
4
|
+
alg: typeof SEALED_BOX_ALGORITHM;
|
|
5
|
+
/** The `encryption_key` row whose public key sealed this. */
|
|
6
|
+
kid: string;
|
|
7
|
+
/** Ephemeral public key, SPKI DER as base64. */
|
|
8
|
+
epk: string;
|
|
9
|
+
iv: string;
|
|
10
|
+
ct: string;
|
|
11
|
+
tag: string;
|
|
12
|
+
}
|
|
13
|
+
/** Seal a value to a stage's public key. */
|
|
14
|
+
export declare function seal(publicKey: string, keyId: string, plaintext: string): SealedValue;
|
|
15
|
+
/** Wire form. Fabric stores exactly this string. */
|
|
16
|
+
export declare function serializeSealedValue(sealed: SealedValue): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sealing a value to a stage's public key, here on the client.
|
|
3
|
+
*
|
|
4
|
+
* `pikku fabric secrets set` seals before it sends, so the plaintext never
|
|
5
|
+
* reaches fabric — not the ingress in front of it, not a request log, not the
|
|
6
|
+
* process handling the write. Fabric stores the sealed blob and hands it to the
|
|
7
|
+
* worker, which holds the only private key that opens it.
|
|
8
|
+
*
|
|
9
|
+
* This is the seal half of fabric's `sealed-x25519-v1`, and the two must stay
|
|
10
|
+
* byte-compatible: an ephemeral X25519 keypair, ECDH against the recipient,
|
|
11
|
+
* HKDF-SHA256 to a content key, AES-256-GCM to seal. Everything below is
|
|
12
|
+
* `node:crypto` on purpose — a sealing primitive that pulled in a dependency
|
|
13
|
+
* would be a supply-chain question sitting directly on the plaintext.
|
|
14
|
+
*
|
|
15
|
+
* There is deliberately no `unseal` here. The CLI has no private key and no
|
|
16
|
+
* business having one; opening a value happens in the worker.
|
|
17
|
+
*/
|
|
18
|
+
import { createCipheriv, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomBytes, } from 'node:crypto';
|
|
19
|
+
/** Algorithm tag carried by every sealed value. Must match fabric's. */
|
|
20
|
+
export const SEALED_BOX_ALGORITHM = 'sealed-x25519-v1';
|
|
21
|
+
/** HKDF domain separator. Must match fabric's byte for byte. */
|
|
22
|
+
const SEAL_SALT = 'pikkufabric/sealed-box/v1';
|
|
23
|
+
/** Seal a value to a stage's public key. */
|
|
24
|
+
export function seal(publicKey, keyId, plaintext) {
|
|
25
|
+
const recipient = importPublicKey(publicKey);
|
|
26
|
+
const ephemeral = generateKeyPairSync('x25519');
|
|
27
|
+
const epk = ephemeral.publicKey.export({ type: 'spki', format: 'der' });
|
|
28
|
+
const contentKey = deriveContentKey(diffieHellman({ privateKey: ephemeral.privateKey, publicKey: recipient }), epk, recipient.export({ type: 'spki', format: 'der' }));
|
|
29
|
+
const iv = randomBytes(12);
|
|
30
|
+
const cipher = createCipheriv('aes-256-gcm', contentKey, iv);
|
|
31
|
+
const ct = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
32
|
+
return {
|
|
33
|
+
alg: SEALED_BOX_ALGORITHM,
|
|
34
|
+
kid: keyId,
|
|
35
|
+
epk: epk.toString('base64'),
|
|
36
|
+
iv: iv.toString('base64'),
|
|
37
|
+
ct: ct.toString('base64'),
|
|
38
|
+
tag: cipher.getAuthTag().toString('base64'),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Wire form. Fabric stores exactly this string. */
|
|
42
|
+
export function serializeSealedValue(sealed) {
|
|
43
|
+
return JSON.stringify(sealed);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Both public keys go into the HKDF `info`, which is what stops a sealed value
|
|
47
|
+
* being redirected: change either the ephemeral key or the recipient and the
|
|
48
|
+
* derived content key changes with it.
|
|
49
|
+
*/
|
|
50
|
+
function deriveContentKey(shared, epk, recipientPublic) {
|
|
51
|
+
return Buffer.from(hkdfSync('sha256', shared, Buffer.from(SEAL_SALT), Buffer.concat([epk, recipientPublic]), 32));
|
|
52
|
+
}
|
|
53
|
+
function importPublicKey(publicKey) {
|
|
54
|
+
let key;
|
|
55
|
+
try {
|
|
56
|
+
key = createPublicKey({
|
|
57
|
+
key: Buffer.from(publicKey, 'base64'),
|
|
58
|
+
type: 'spki',
|
|
59
|
+
format: 'der',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new Error(`Not a valid X25519 public key (${error instanceof Error ? error.message : String(error)})`);
|
|
64
|
+
}
|
|
65
|
+
// A public key of the wrong curve would still ECDH, just against a key the
|
|
66
|
+
// stage's worker cannot match — the failure would surface as an unopenable
|
|
67
|
+
// secret much later, so refuse it at the point the type is still knowable.
|
|
68
|
+
if (key.asymmetricKeyType !== 'x25519') {
|
|
69
|
+
throw new Error(`Stage sealing key is ${key.asymmetricKeyType}, expected x25519 — this CLI is too old for it, run \`npm i -g @pikku/cli\``);
|
|
70
|
+
}
|
|
71
|
+
return key;
|
|
72
|
+
}
|
|
@@ -11,6 +11,7 @@ import { LocalContent, } from '@pikku/core/services/local-content';
|
|
|
11
11
|
import { InMemorySchedulerService } from '@pikku/schedule';
|
|
12
12
|
import { resolveDb, createKysely, parseDatabaseUrl, } from '../db/local-db.js';
|
|
13
13
|
import { loadUserBootstrap, loadUserModule } from './load-user-project.js';
|
|
14
|
+
import { registerScenarioInstrumentation } from '../wirings/scenarios/register-scenario-instrumentation.js';
|
|
14
15
|
import { startCoverageService } from './start-coverage.js';
|
|
15
16
|
import { createDevAIAgentRunner } from './dev-ai-runner.js';
|
|
16
17
|
import { resolveConsoleMount } from './serve-console.js';
|
|
@@ -115,6 +116,14 @@ export const dev = pikkuSessionlessFunc({
|
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
117
118
|
await loadUserBootstrap(pikkuDir);
|
|
119
|
+
// Scenario instrumentation exists only on a development server: the runner
|
|
120
|
+
// calls it over RPC to reset and snapshot coverage and stub calls. It is
|
|
121
|
+
// registered here, after the app bootstrap, rather than generated into the
|
|
122
|
+
// project — an app bootstrap is what a deployed bundle imports, and coverage
|
|
123
|
+
// endpoints have no business in one.
|
|
124
|
+
if (config.scaffold?.scenarios) {
|
|
125
|
+
registerScenarioInstrumentation(config.scaffold.scenarios === 'auth');
|
|
126
|
+
}
|
|
118
127
|
const configModule = await loadUserModule(pikkuConfigFactory.file);
|
|
119
128
|
const servicesModule = await loadUserModule(singletonServicesFactory.file);
|
|
120
129
|
const userCreateConfig = configModule[pikkuConfigFactory.variable];
|
|
@@ -70,6 +70,7 @@ export const pikkuBootstrap = pikkuSessionlessFunc({
|
|
|
70
70
|
// in place however this module is reached.
|
|
71
71
|
const scenarioBootstrapImports = [
|
|
72
72
|
config.bootstrapFile,
|
|
73
|
+
join(config.scenarioSchemaDirectory, 'register.gen.ts'),
|
|
73
74
|
config.scenarioStepsMetaFile,
|
|
74
75
|
config.scenarioStepsFile,
|
|
75
76
|
config.scenarioWiringsMetaFile,
|
|
@@ -29,9 +29,17 @@ export interface ScenarioBrowserDriver {
|
|
|
29
29
|
createScenarioBrowserProvider?: (options: ScenarioBrowserDriverOptions) => ScenarioBrowserProvider;
|
|
30
30
|
/** The class form the bundled driver exports. */
|
|
31
31
|
PlaywrightScenarioBrowserProvider?: new (options: ScenarioBrowserDriverOptions) => ScenarioBrowserProvider;
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Driver-specific config (headed, slowMo, timeouts) resolved from the env.
|
|
34
|
+
*
|
|
35
|
+
* A driver may resolve `appUrl` itself when the caller passes none — that is
|
|
36
|
+
* how a target known only at run time (a sandbox hostname in the env) is
|
|
37
|
+
* reached without a config file per deployment. A driver that answers with a
|
|
38
|
+
* placeholder rather than a real target must say so with
|
|
39
|
+
* `appUrlSource: 'default'`, which the runner reports as a missing appUrl.
|
|
40
|
+
*/
|
|
33
41
|
browserConfigFromEnv?: (overrides: {
|
|
34
|
-
appUrl
|
|
42
|
+
appUrl?: string;
|
|
35
43
|
apiUrl: string;
|
|
36
44
|
}) => unknown;
|
|
37
45
|
}
|
|
@@ -55,6 +63,13 @@ export interface ResolveScenarioBrowserProviderOptions {
|
|
|
55
63
|
* Both failure modes here are discovered before the first scenario starts. A
|
|
56
64
|
* missing `appUrl` or an uninstalled driver found mid-run costs a whole run to
|
|
57
65
|
* learn about.
|
|
66
|
+
*
|
|
67
|
+
* `appUrl` is checked after the driver has resolved its config rather than
|
|
68
|
+
* before, so a driver that knows the target from its own environment (a
|
|
69
|
+
* sandbox hostname) is allowed to supply it. The check is not dropped: a driver
|
|
70
|
+
* reporting `appUrlSource: 'default'` resolved nothing and fails the same way,
|
|
71
|
+
* because a run against a placeholder URL is worse than one that refuses to
|
|
72
|
+
* start.
|
|
58
73
|
*/
|
|
59
74
|
export declare const resolveScenarioBrowserProvider: ({ environment, apiUrl, appUrl, secret, actors, signInPath, failureDir, browserScenarios, driver, importDriver, }: ResolveScenarioBrowserProviderOptions) => Promise<ScenarioBrowserProvider>;
|
|
60
75
|
/**
|
|
@@ -4,12 +4,15 @@ export const DEFAULT_BROWSER_DRIVER = '@pikku/playwright';
|
|
|
4
4
|
* Both failure modes here are discovered before the first scenario starts. A
|
|
5
5
|
* missing `appUrl` or an uninstalled driver found mid-run costs a whole run to
|
|
6
6
|
* learn about.
|
|
7
|
+
*
|
|
8
|
+
* `appUrl` is checked after the driver has resolved its config rather than
|
|
9
|
+
* before, so a driver that knows the target from its own environment (a
|
|
10
|
+
* sandbox hostname) is allowed to supply it. The check is not dropped: a driver
|
|
11
|
+
* reporting `appUrlSource: 'default'` resolved nothing and fails the same way,
|
|
12
|
+
* because a run against a placeholder URL is worse than one that refuses to
|
|
13
|
+
* start.
|
|
7
14
|
*/
|
|
8
15
|
export const resolveScenarioBrowserProvider = async ({ environment, apiUrl, appUrl, secret, actors, signInPath, failureDir, browserScenarios, driver = DEFAULT_BROWSER_DRIVER, importDriver = (specifier) => import(specifier), }) => {
|
|
9
|
-
if (!appUrl) {
|
|
10
|
-
throw new Error(`Scenario environment '${environment}' has browser steps but no 'appUrl'. ` +
|
|
11
|
-
`Add it to scenarios.environments.${environment} in pikku.config.json, or run with --no-browser to skip them.`);
|
|
12
|
-
}
|
|
13
16
|
const module = await importDriver(driver).catch(() => {
|
|
14
17
|
const install = driver === DEFAULT_BROWSER_DRIVER
|
|
15
18
|
? `Run 'yarn add -D ${driver} @playwright/test'`
|
|
@@ -17,15 +20,20 @@ export const resolveScenarioBrowserProvider = async ({ environment, apiUrl, appU
|
|
|
17
20
|
throw new Error(`Scenarios ${browserScenarios.join(', ')} declare browser steps but '${driver}' could not be loaded. ` +
|
|
18
21
|
`${install}, or run with --no-browser to skip them.`);
|
|
19
22
|
});
|
|
23
|
+
const config = (module.browserConfigFromEnv?.({ appUrl, apiUrl }) ?? {
|
|
24
|
+
appUrl,
|
|
25
|
+
apiUrl,
|
|
26
|
+
});
|
|
27
|
+
if (!config.appUrl || config.appUrlSource === 'default') {
|
|
28
|
+
throw new Error(`Scenario environment '${environment}' has browser steps but no 'appUrl', and '${driver}' resolved none from the environment. ` +
|
|
29
|
+
`Add it to scenarios.environments.${environment} in pikku.config.json, pass --app-url for a target that only exists at run time, or run with --no-browser to skip them.`);
|
|
30
|
+
}
|
|
20
31
|
const options = {
|
|
21
32
|
secret,
|
|
22
33
|
actors,
|
|
23
34
|
signInPath,
|
|
24
35
|
failureDir,
|
|
25
|
-
config
|
|
26
|
-
appUrl,
|
|
27
|
-
apiUrl,
|
|
28
|
-
},
|
|
36
|
+
config,
|
|
29
37
|
};
|
|
30
38
|
if (module.createScenarioBrowserProvider) {
|
|
31
39
|
return module.createScenarioBrowserProvider(options);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving `pikku scenario run <environment>` to the URLs the run targets.
|
|
3
|
+
*
|
|
4
|
+
* The configured environment is the base; `--api-url`/`--app-url` override it
|
|
5
|
+
* for one invocation, which is what makes a suite runnable against a target
|
|
6
|
+
* that only exists at run time (a freshly provisioned sandbox) without writing
|
|
7
|
+
* a pikku.config.json per deployment.
|
|
8
|
+
*/
|
|
9
|
+
/** One entry of `scenarios.environments` in pikku.config.json. */
|
|
10
|
+
export interface ScenarioEnvironment {
|
|
11
|
+
apiUrl: string;
|
|
12
|
+
signInPath?: string;
|
|
13
|
+
rpcPath?: string;
|
|
14
|
+
appUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ResolveScenarioEnvironmentOptions {
|
|
17
|
+
/** The name given on the command line. Must exist in config. */
|
|
18
|
+
environment: string;
|
|
19
|
+
environments: Record<string, ScenarioEnvironment>;
|
|
20
|
+
/** `--api-url`, overriding the configured `apiUrl`. */
|
|
21
|
+
apiUrl?: string;
|
|
22
|
+
/** `--app-url`, overriding the configured `appUrl`. */
|
|
23
|
+
appUrl?: string;
|
|
24
|
+
/** `--spawn`, which is only coherent against a local `apiUrl`. */
|
|
25
|
+
spawn?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Applied once, so actors, step env, the browser driver and the spawned server
|
|
29
|
+
* all see the same target. The environment is still looked up by name: the
|
|
30
|
+
* flags override fields on a configured environment, they do not invent one.
|
|
31
|
+
*/
|
|
32
|
+
export declare const resolveScenarioEnvironment: ({ environment, environments, apiUrl, appUrl, spawn, }: ResolveScenarioEnvironmentOptions) => ScenarioEnvironment;
|