@pikku/cli 0.12.90 → 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/README.md +25 -2
- package/console-app/assets/{index-CSzCJzBb.css → index-D0HG8q0B.css} +1 -1
- package/console-app/assets/{index-C5Bd44e4.js → index-DDpIMCpy.js} +149 -149
- 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.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 +43 -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 +4 -4
- 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 +39 -44
- 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 +9 -0
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +9 -0
- 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.d.ts +1 -0
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +10 -0
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.json +1 -0
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +4 -0
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.js +1 -0
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.d.ts +1 -0
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +10 -0
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +4 -0
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.js +1 -0
- 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 +19 -0
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +17 -0
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +152 -3
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +39 -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 +28 -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 +11 -0
- package/dist/src/functions/commands/load-user-project.d.ts +7 -0
- package/dist/src/functions/commands/load-user-project.js +21 -0
- package/dist/src/functions/commands/pikku-command-bootstrap.js +13 -0
- package/dist/src/functions/commands/scenario-browser.d.ts +84 -0
- package/dist/src/functions/commands/scenario-browser.js +68 -0
- 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-formatter.d.ts +81 -0
- package/dist/src/functions/commands/scenario-formatter.js +120 -0
- package/dist/src/functions/commands/scenario-ladder.d.ts +66 -0
- package/dist/src/functions/commands/scenario-ladder.js +132 -0
- package/dist/src/functions/commands/scenario-plan.d.ts +61 -0
- package/dist/src/functions/commands/scenario-plan.js +106 -0
- package/dist/src/functions/commands/scenario.d.ts +24 -0
- package/dist/src/functions/commands/scenario.js +322 -50
- package/dist/src/functions/commands/serve.js +2 -0
- 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/pikku-command-functions.js +26 -8
- package/dist/src/functions/wirings/functions/schemas.js +13 -1
- package/dist/src/functions/wirings/functions/serialize-function-types.js +3 -3
- 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 +42 -0
- package/dist/src/functions/wirings/scenarios/scenario-partition.js +71 -0
- 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/wirings/scenarios/serialize-feature-meta.d.ts +14 -0
- package/dist/src/functions/wirings/scenarios/serialize-feature-meta.js +29 -0
- package/dist/src/functions/wirings/scenarios/serialize-scenario-meta.d.ts +12 -0
- package/dist/src/functions/wirings/scenarios/serialize-scenario-meta.js +56 -0
- package/dist/src/functions/wirings/scenarios/serialize-scenario-registration.d.ts +7 -0
- package/dist/src/functions/wirings/scenarios/serialize-scenario-registration.js +40 -0
- package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +43 -8
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.d.ts +1 -1
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.js +13 -2
- package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.d.ts +4 -0
- package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.js +62 -0
- package/dist/src/functions/wirings/workflow/serialize-workflow-bootstrap-map.js +10 -3
- package/dist/src/functions/wirings/workflow/serialize-workflow-map.js +10 -3
- package/dist/src/functions/wirings/workflow/serialize-workflow-meta.js +4 -4
- package/dist/src/functions/wirings/workflow/serialize-workflow-types.d.ts +1 -1
- package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +232 -3
- package/dist/src/functions/workflows/all.workflow.js +2 -7
- package/dist/src/server/server-ready.d.ts +12 -0
- package/dist/src/server/server-ready.js +12 -0
- package/dist/src/server/spawn-dev-server.d.ts +50 -0
- package/dist/src/server/spawn-dev-server.js +112 -0
- package/dist/src/services.js +12 -1
- package/dist/src/utils/file-writer.js +14 -2
- package/dist/src/utils/meta-diff.js +5 -2
- package/dist/src/utils/pikku-cli-config.d.ts +14 -0
- package/dist/src/utils/pikku-cli-config.js +60 -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/resolve-scenario-actors.d.ts +23 -0
- package/dist/src/utils/resolve-scenario-actors.js +74 -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 +18 -8
- 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 -215
- 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
|
@@ -2,8 +2,17 @@ import { resolve, join } from 'node:path';
|
|
|
2
2
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { pikkuSessionlessFunc } from '#pikku';
|
|
4
4
|
import { InMemoryWorkflowService, createHttpScenarioActors, } from '@pikku/core/services';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { PikkuScenarioService } from '@pikku/core/scenario';
|
|
6
|
+
import { pikkuState, getAllPackageStates } from '@pikku/core/internal';
|
|
7
|
+
import { resolveFeatureScenarios } from '@pikku/core/workflow';
|
|
8
|
+
import { loadScenarioBootstrap } from './load-user-project.js';
|
|
9
|
+
import { collectScenarioStepProse, scenarioBrowserSteps, scenarioFailureFromSteps, scenarioStepRows, } from './scenario-ladder.js';
|
|
10
|
+
import { formatScenarioReport } from './scenario-formatter.js';
|
|
11
|
+
import { resolveScenarioBrowserProvider, scenarioBrowserLifecycle, } from './scenario-browser.js';
|
|
12
|
+
import { resolveScenarioActors } from '../../utils/resolve-scenario-actors.js';
|
|
13
|
+
import { spawnDevServer } from '../../server/spawn-dev-server.js';
|
|
14
|
+
import { buildScenarioPlan } from './scenario-plan.js';
|
|
15
|
+
import { resolveScenarioEnvironment } from './scenario-environment.js';
|
|
7
16
|
const isScenario = (wf) => wf?.scenario === true;
|
|
8
17
|
const listScenarios = (state) => Object.entries(state.workflows?.meta ?? {})
|
|
9
18
|
.filter(([, wf]) => isScenario(wf))
|
|
@@ -12,50 +21,140 @@ const listScenarios = (state) => Object.entries(state.workflows?.meta ?? {})
|
|
|
12
21
|
name: wf.name ?? id,
|
|
13
22
|
description: wf.description ?? wf.summary ?? wf.title ?? null,
|
|
14
23
|
tags: wf.tags ?? [],
|
|
24
|
+
skip: wf.skip,
|
|
15
25
|
}));
|
|
26
|
+
/**
|
|
27
|
+
* Features and scenario registrations, merged across the main package and any
|
|
28
|
+
* addon packages. A feature holds the very config objects the registrations
|
|
29
|
+
* hold, which is what makes membership resolvable by identity.
|
|
30
|
+
*/
|
|
31
|
+
const collectRegisteredWirings = () => {
|
|
32
|
+
const features = new Map();
|
|
33
|
+
const registrations = new Map();
|
|
34
|
+
for (const [packageName] of getAllPackageStates()) {
|
|
35
|
+
const scope = packageName === '__main__' ? null : packageName;
|
|
36
|
+
for (const [id, feature] of pikkuState(scope, 'workflows', 'features')) {
|
|
37
|
+
features.set(id, feature);
|
|
38
|
+
}
|
|
39
|
+
for (const [name, registration] of pikkuState(scope, 'workflows', 'registrations')) {
|
|
40
|
+
registrations.set(name, registration);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { features, registrations };
|
|
44
|
+
};
|
|
16
45
|
export const scenarioList = pikkuSessionlessFunc({
|
|
17
|
-
func: async ({ logger, getInspectorState }) => {
|
|
18
|
-
const state = await getInspectorState();
|
|
46
|
+
func: async ({ logger, config, getInspectorState }) => {
|
|
47
|
+
const state = await getInspectorState(false, false, false, true);
|
|
19
48
|
const flows = listScenarios(state);
|
|
20
49
|
if (flows.length === 0) {
|
|
21
50
|
logger.info('No scenarios found (pikkuScenario exports).');
|
|
22
51
|
return;
|
|
23
52
|
}
|
|
53
|
+
await loadScenarioBootstrap(resolve(config.rootDir, config.outDir));
|
|
54
|
+
const { features, registrations } = collectRegisteredWirings();
|
|
55
|
+
const { entries, unresolved } = resolveFeatureScenarios(features, registrations);
|
|
56
|
+
for (const { featureId, index } of unresolved) {
|
|
57
|
+
logger.error(`Feature '${featureId}' scenario #${index} is not a registered scenario — it will not run.`);
|
|
58
|
+
}
|
|
59
|
+
const describe = (name) => flows.find((flow) => flow.name === name)?.description;
|
|
60
|
+
for (const [featureId, feature] of features) {
|
|
61
|
+
const featureTags = feature.tags?.length
|
|
62
|
+
? ` [${feature.tags.join(', ')}]`
|
|
63
|
+
: '';
|
|
64
|
+
logger.info(`${feature.name ?? featureId}${featureTags}`);
|
|
65
|
+
if (feature.description) {
|
|
66
|
+
logger.info(` ${feature.description}`);
|
|
67
|
+
}
|
|
68
|
+
for (const entry of entries.filter((e) => e.featureId === featureId)) {
|
|
69
|
+
const data = entry.data ? ` ${JSON.stringify(entry.data)}` : '';
|
|
70
|
+
logger.info(` - ${entry.scenarioName}${data}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const inAFeature = new Set(entries.map((entry) => entry.scenarioName));
|
|
24
74
|
for (const flow of flows) {
|
|
75
|
+
if (inAFeature.has(flow.name))
|
|
76
|
+
continue;
|
|
25
77
|
const tags = flow.tags.length ? ` [${flow.tags.join(', ')}]` : '';
|
|
26
78
|
logger.info(`${flow.name}${tags}`);
|
|
27
|
-
|
|
28
|
-
|
|
79
|
+
const description = describe(flow.name);
|
|
80
|
+
if (description) {
|
|
81
|
+
logger.info(` ${description}`);
|
|
29
82
|
}
|
|
30
83
|
}
|
|
31
84
|
},
|
|
32
85
|
});
|
|
33
86
|
export const scenarioRun = pikkuSessionlessFunc({
|
|
34
|
-
func: async ({ logger, config, getInspectorState, variables }, { environment, flows, tags, coverage }) => {
|
|
35
|
-
const state = await getInspectorState(true);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
87
|
+
func: async ({ logger, config, getInspectorState, variables }, { environment, flows, features, tags, excludeTags, coverage, browser = true, spawn = false, keepAlive = false, trace = false, apiUrl, appUrl, }) => {
|
|
88
|
+
const state = await getInspectorState(true, false, false, true);
|
|
89
|
+
// Resolved once, so actors, step env, the browser driver and any spawned
|
|
90
|
+
// server all target the same place — including when the target only exists
|
|
91
|
+
// at run time and arrives through --api-url/--app-url.
|
|
92
|
+
const env = resolveScenarioEnvironment({
|
|
93
|
+
environment,
|
|
94
|
+
environments: config.scenarios?.environments ?? {},
|
|
95
|
+
apiUrl,
|
|
96
|
+
appUrl,
|
|
97
|
+
spawn,
|
|
98
|
+
});
|
|
99
|
+
if (apiUrl || appUrl) {
|
|
100
|
+
logger.info(`Overriding '${environment}': apiUrl ${env.apiUrl}${env.appUrl ? `, appUrl ${env.appUrl}` : ''}`);
|
|
101
|
+
}
|
|
102
|
+
if (spawn) {
|
|
103
|
+
const { hostname, port } = new URL(env.apiUrl);
|
|
104
|
+
const resolvedPort = Number(port || 80);
|
|
105
|
+
logger.info(`Starting a server for '${environment}' on ${env.apiUrl}`);
|
|
106
|
+
const server = await spawnDevServer({
|
|
107
|
+
cwd: config.rootDir,
|
|
108
|
+
port: resolvedPort,
|
|
109
|
+
hostname,
|
|
110
|
+
coverage,
|
|
111
|
+
env: { API_URL: env.apiUrl },
|
|
112
|
+
onOutput: (text) => process.stdout.write(text),
|
|
113
|
+
});
|
|
114
|
+
// Registered rather than wrapped in a try/finally: the run below sets
|
|
115
|
+
// process.exitCode and can throw, and an exit handler covers both without
|
|
116
|
+
// the whole command body having to nest inside one block.
|
|
117
|
+
if (!keepAlive) {
|
|
118
|
+
process.once('exit', server.stop);
|
|
119
|
+
process.once('SIGINT', () => {
|
|
120
|
+
server.stop();
|
|
121
|
+
process.exit(1);
|
|
122
|
+
});
|
|
123
|
+
process.once('SIGTERM', () => {
|
|
124
|
+
server.stop();
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
await server.waitUntilReady();
|
|
129
|
+
}
|
|
130
|
+
// Features live in runtime state, not inspector meta — their scenario lists
|
|
131
|
+
// may be built by an ordinary loop — so the project has to be loaded before
|
|
132
|
+
// anything can be selected.
|
|
133
|
+
await loadScenarioBootstrap(resolve(config.rootDir, config.outDir));
|
|
134
|
+
const { features: registeredFeatures, registrations } = collectRegisteredWirings();
|
|
135
|
+
const split = (value) => value ? value.split(',').map((part) => part.trim()) : undefined;
|
|
136
|
+
let { groups, unresolved } = buildScenarioPlan({
|
|
137
|
+
scenarios: listScenarios(state).map(({ name, tags: flowTags, skip }) => ({
|
|
138
|
+
name,
|
|
139
|
+
tags: flowTags,
|
|
140
|
+
skip,
|
|
141
|
+
})),
|
|
142
|
+
features: registeredFeatures,
|
|
143
|
+
registrations,
|
|
144
|
+
flows: split(flows),
|
|
145
|
+
featureIds: split(features),
|
|
146
|
+
tags: split(tags),
|
|
147
|
+
excludeTags: split(excludeTags),
|
|
148
|
+
});
|
|
149
|
+
if (unresolved.length > 0) {
|
|
150
|
+
for (const { featureId, index } of unresolved) {
|
|
151
|
+
logger.error(`Feature '${featureId}' scenario #${index} is not a registered scenario. ` +
|
|
152
|
+
`A feature references scenarios by imported identifier — a scenario built inline inside a feature is never registered.`);
|
|
153
|
+
}
|
|
154
|
+
process.exitCode = 1;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (groups.length === 0) {
|
|
59
158
|
logger.error('No scenarios matched.');
|
|
60
159
|
process.exitCode = 1;
|
|
61
160
|
return;
|
|
@@ -65,15 +164,57 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
65
164
|
throw new Error('SCENARIO_ACTOR_SECRET is not set — scenario actors cannot sign in. ' +
|
|
66
165
|
'Export it in the environment running this command (never put it in pikku.config.json).');
|
|
67
166
|
}
|
|
167
|
+
// Declared actors plus one per persona nobody declared a body for. Resolved
|
|
168
|
+
// once: the HTTP actors and the Playwright provider below must see the same
|
|
169
|
+
// registry, and codegen resolved the same way to type `ScenarioActorName`.
|
|
170
|
+
const scenarioActors = resolveScenarioActors(config.scenarios);
|
|
68
171
|
const actors = createHttpScenarioActors({
|
|
69
172
|
apiUrl: env.apiUrl,
|
|
70
173
|
secret,
|
|
71
|
-
actors:
|
|
174
|
+
actors: scenarioActors,
|
|
72
175
|
signInPath: env.signInPath,
|
|
73
176
|
rpcPath: env.rpcPath,
|
|
74
177
|
});
|
|
75
|
-
|
|
178
|
+
const functionsMeta = state.functions?.meta ?? {};
|
|
179
|
+
const scenarioNames = new Set(groups.flatMap((group) => group.entries.map((e) => e.scenarioName)));
|
|
180
|
+
const browserStepsByFlow = new Map([...scenarioNames]
|
|
181
|
+
.map((name) => [
|
|
182
|
+
name,
|
|
183
|
+
scenarioBrowserSteps(state.workflows?.meta?.[name], functionsMeta),
|
|
184
|
+
])
|
|
185
|
+
.filter(([, steps]) => steps.length > 0));
|
|
186
|
+
// Two reasons a scenario is held back, both reported as SKIP rather than
|
|
187
|
+
// failed. `--no-browser` is the direct replacement for cucumber's `@console`
|
|
188
|
+
// tag, so a standard run stays green on a machine with no browser; a `skip`
|
|
189
|
+
// on the scenario itself is the project quarantining it, and the plan has
|
|
190
|
+
// already cleared that reason for anything named directly with `--flows`.
|
|
191
|
+
const skipReasonFor = (entry) => {
|
|
192
|
+
if (entry.skip)
|
|
193
|
+
return entry.skip;
|
|
194
|
+
if (!browser && browserStepsByFlow.has(entry.scenarioName)) {
|
|
195
|
+
return 'browser steps, --no-browser';
|
|
196
|
+
}
|
|
197
|
+
return undefined;
|
|
198
|
+
};
|
|
199
|
+
const skipped = [];
|
|
200
|
+
groups = groups
|
|
201
|
+
.map((group) => ({
|
|
202
|
+
...group,
|
|
203
|
+
entries: group.entries.filter((entry) => {
|
|
204
|
+
const reason = skipReasonFor(entry);
|
|
205
|
+
if (!reason)
|
|
206
|
+
return true;
|
|
207
|
+
skipped.push({ name: entry.scenarioName, reason });
|
|
208
|
+
return false;
|
|
209
|
+
}),
|
|
210
|
+
}))
|
|
211
|
+
.filter((group) => group.entries.length > 0);
|
|
76
212
|
const workflowService = new InMemoryWorkflowService();
|
|
213
|
+
const scenarioService = workflowService.setRunExtension((engine) => new PikkuScenarioService(engine));
|
|
214
|
+
scenarioService.setScenarioEnvironment({
|
|
215
|
+
apiUrl: env.apiUrl,
|
|
216
|
+
appUrl: env.appUrl,
|
|
217
|
+
});
|
|
77
218
|
pikkuState(null, 'package', 'singletonServices', {
|
|
78
219
|
logger,
|
|
79
220
|
workflowService,
|
|
@@ -86,7 +227,51 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
86
227
|
`'${environment}' (${env.apiUrl}), not local services.`);
|
|
87
228
|
},
|
|
88
229
|
};
|
|
230
|
+
const needsBrowser = groups.some((group) => group.entries.some((entry) => browserStepsByFlow.has(entry.scenarioName)));
|
|
231
|
+
const failureDir = join(resolve(config.rootDir, config.outDir), 'scenario-failures');
|
|
232
|
+
const browserLifecycle = scenarioBrowserLifecycle(needsBrowser
|
|
233
|
+
? await (async () => {
|
|
234
|
+
const provider = await resolveScenarioBrowserProvider({
|
|
235
|
+
environment,
|
|
236
|
+
apiUrl: env.apiUrl,
|
|
237
|
+
appUrl: env.appUrl,
|
|
238
|
+
secret,
|
|
239
|
+
actors: scenarioActors,
|
|
240
|
+
signInPath: env.signInPath,
|
|
241
|
+
failureDir,
|
|
242
|
+
browserScenarios: [...browserStepsByFlow.keys()],
|
|
243
|
+
driver: config.scenarios?.browserDriver,
|
|
244
|
+
});
|
|
245
|
+
scenarioService.setScenarioBrowserProvider(provider);
|
|
246
|
+
return provider;
|
|
247
|
+
})()
|
|
248
|
+
: undefined);
|
|
89
249
|
const results = [];
|
|
250
|
+
/**
|
|
251
|
+
* The step ladder is read back off the recorded run, so it needs no live
|
|
252
|
+
* step events — it is the same data the console renders. Joining it to the
|
|
253
|
+
* declared prose happens here, where the inspector state is; laying it out
|
|
254
|
+
* is the formatter's job.
|
|
255
|
+
*/
|
|
256
|
+
const readRunSteps = async (service, runId, flowName) => {
|
|
257
|
+
const prose = collectScenarioStepProse(state.workflows?.meta?.[flowName], functionsMeta);
|
|
258
|
+
const steps = (await service.getRunSteps(runId)).map((step) => ({
|
|
259
|
+
stepName: step.stepName,
|
|
260
|
+
status: step.status,
|
|
261
|
+
durationMs: step.succeededAt
|
|
262
|
+
? step.succeededAt.getTime() - step.createdAt.getTime()
|
|
263
|
+
: undefined,
|
|
264
|
+
error: step.error?.message,
|
|
265
|
+
stack: step.error?.stack,
|
|
266
|
+
expected: step.error?.expected,
|
|
267
|
+
input: step.data,
|
|
268
|
+
stepFunc: step.rpcName,
|
|
269
|
+
}));
|
|
270
|
+
return {
|
|
271
|
+
rows: scenarioStepRows(steps, prose),
|
|
272
|
+
failure: scenarioFailureFromSteps(steps, prose),
|
|
273
|
+
};
|
|
274
|
+
};
|
|
90
275
|
const coverageActor = coverage ? Object.values(actors)[0] : undefined;
|
|
91
276
|
let coverageActive = Boolean(coverageActor);
|
|
92
277
|
if (coverage && !coverageActor) {
|
|
@@ -106,8 +291,22 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
106
291
|
return null;
|
|
107
292
|
}
|
|
108
293
|
};
|
|
109
|
-
|
|
294
|
+
/**
|
|
295
|
+
* A feature hook is not a pikku function and not a run — the feature is a
|
|
296
|
+
* grouping, not something durable. It gets the same three arguments a
|
|
297
|
+
* scenario body gets, the CLI's own singletons included, and its result is
|
|
298
|
+
* discarded.
|
|
299
|
+
*/
|
|
300
|
+
const singletonServices = pikkuState(null, 'package', 'singletonServices');
|
|
301
|
+
const runFeatureHook = async (hook) => {
|
|
302
|
+
await hook(singletonServices, undefined, { actors });
|
|
303
|
+
};
|
|
304
|
+
const hookFailures = [];
|
|
305
|
+
const runEntry = async (label, scenarioName, data) => {
|
|
110
306
|
const startedAt = Date.now();
|
|
307
|
+
// Before the scenario, not after it: the last scenario's window is left
|
|
308
|
+
// open for headed debugging, while this one still starts clean.
|
|
309
|
+
await browserLifecycle.reset();
|
|
111
310
|
if (coverageActive) {
|
|
112
311
|
const reset = await invokeCoverage('pikkuScenarioResetLiveCoverage');
|
|
113
312
|
if (reset && reset.enabled === false) {
|
|
@@ -119,20 +318,27 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
119
318
|
await coverageActor?.invoke('pikkuScenarioResetStubs', null);
|
|
120
319
|
}
|
|
121
320
|
catch { }
|
|
321
|
+
let runId;
|
|
322
|
+
let runError;
|
|
122
323
|
try {
|
|
123
|
-
|
|
324
|
+
// The id comes back through the callback rather than the return value
|
|
325
|
+
// because a failing scenario throws instead of returning — and a failed
|
|
326
|
+
// run is exactly the one whose steps are worth reading.
|
|
327
|
+
;
|
|
328
|
+
({ runId } = await workflowService.startWorkflow(scenarioName, data, { type: 'cli' }, guardRpc, { actors, onRunCreated: (id) => (runId = id) }));
|
|
124
329
|
const run = await workflowService.getRun(runId);
|
|
125
330
|
if (run?.status === 'completed') {
|
|
126
331
|
results.push({
|
|
127
|
-
name:
|
|
332
|
+
name: label,
|
|
128
333
|
status: 'passed',
|
|
129
334
|
durationMs: Date.now() - startedAt,
|
|
130
335
|
output: run.output,
|
|
131
336
|
});
|
|
132
337
|
}
|
|
133
338
|
else {
|
|
339
|
+
runError = run?.error;
|
|
134
340
|
results.push({
|
|
135
|
-
name:
|
|
341
|
+
name: label,
|
|
136
342
|
status: 'failed',
|
|
137
343
|
durationMs: Date.now() - startedAt,
|
|
138
344
|
error: run?.error?.message ?? `status: ${run?.status}`,
|
|
@@ -140,22 +346,90 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
140
346
|
}
|
|
141
347
|
}
|
|
142
348
|
catch (e) {
|
|
349
|
+
runError = { stack: e?.stack };
|
|
143
350
|
results.push({
|
|
144
|
-
name:
|
|
351
|
+
name: label,
|
|
145
352
|
status: 'failed',
|
|
146
353
|
durationMs: Date.now() - startedAt,
|
|
147
354
|
error: e?.message ?? String(e),
|
|
148
355
|
});
|
|
149
356
|
}
|
|
357
|
+
const result = results[results.length - 1];
|
|
358
|
+
let stepFailure;
|
|
359
|
+
if (runId) {
|
|
360
|
+
const read = await readRunSteps(workflowService, runId, scenarioName);
|
|
361
|
+
result.steps = read.rows;
|
|
362
|
+
stepFailure = read.failure;
|
|
363
|
+
}
|
|
364
|
+
if (result.status === 'failed') {
|
|
365
|
+
result.failure = {
|
|
366
|
+
// A scenario can also fail outside any step — a hook, or the start
|
|
367
|
+
// itself — and then the run's own error is all there is to report.
|
|
368
|
+
...(stepFailure ?? {
|
|
369
|
+
message: result.error ?? 'unknown failure',
|
|
370
|
+
stack: runError?.stack,
|
|
371
|
+
expected: runError?.expected,
|
|
372
|
+
}),
|
|
373
|
+
browser: await browserLifecycle.captureFailure(label),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
150
376
|
if (coverageActive) {
|
|
151
377
|
const report = await invokeCoverage('pikkuScenarioTakeLiveCoverage');
|
|
152
378
|
if (report) {
|
|
153
|
-
scenarioCoverage[
|
|
379
|
+
scenarioCoverage[label] = report;
|
|
154
380
|
const covered = report.functions?.filter((f) => f.status === 'covered' || f.status === 'partial');
|
|
155
|
-
logger.info(` coverage: ${covered?.length ?? 0}/${report.summary?.total ?? 0} functions exercised by '${
|
|
381
|
+
logger.info(` coverage: ${covered?.length ?? 0}/${report.summary?.total ?? 0} functions exercised by '${label}'`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
for (const group of groups) {
|
|
386
|
+
const groupName = group.featureName ?? group.featureId;
|
|
387
|
+
const label = (entry) => {
|
|
388
|
+
const data = entry.data ? ` ${JSON.stringify(entry.data)}` : '';
|
|
389
|
+
return groupName
|
|
390
|
+
? `${groupName} › ${entry.scenarioName}${data}`
|
|
391
|
+
: entry.scenarioName;
|
|
392
|
+
};
|
|
393
|
+
let beforeError;
|
|
394
|
+
if (group.before) {
|
|
395
|
+
try {
|
|
396
|
+
await runFeatureHook(group.before);
|
|
397
|
+
}
|
|
398
|
+
catch (e) {
|
|
399
|
+
beforeError = e;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
if (beforeError) {
|
|
404
|
+
// Setup failed, so nothing in the group ran. Reporting them as failed
|
|
405
|
+
// rather than skipped is the honest reading: they did not pass.
|
|
406
|
+
for (const entry of group.entries) {
|
|
407
|
+
results.push({
|
|
408
|
+
name: label(entry),
|
|
409
|
+
status: 'failed',
|
|
410
|
+
durationMs: 0,
|
|
411
|
+
error: `feature '${groupName}' before hook failed: ${beforeError?.message ?? beforeError}`,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
for (const entry of group.entries) {
|
|
417
|
+
await runEntry(label(entry), entry.scenarioName, entry.data);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
if (group.after) {
|
|
423
|
+
try {
|
|
424
|
+
await runFeatureHook(group.after);
|
|
425
|
+
}
|
|
426
|
+
catch (e) {
|
|
427
|
+
hookFailures.push(`feature '${groupName}' after hook failed: ${e?.message ?? e}`);
|
|
428
|
+
}
|
|
156
429
|
}
|
|
157
430
|
}
|
|
158
431
|
}
|
|
432
|
+
await browserLifecycle.close();
|
|
159
433
|
if (coverage && Object.keys(scenarioCoverage).length > 0) {
|
|
160
434
|
const coverageDir = join(resolve(config.rootDir, config.outDir), 'coverage');
|
|
161
435
|
mkdirSync(coverageDir, { recursive: true });
|
|
@@ -167,17 +441,15 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
167
441
|
}, null, 2) + '\n');
|
|
168
442
|
logger.info(`Scenario coverage → ${outFile}`);
|
|
169
443
|
}
|
|
170
|
-
const
|
|
171
|
-
for (const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
logger.error(`FAIL ${r.name} (${r.durationMs}ms): ${r.error}`);
|
|
177
|
-
}
|
|
444
|
+
const report = { environment, results, skipped, hookFailures };
|
|
445
|
+
for (const { level, text } of formatScenarioReport(report, {
|
|
446
|
+
trace,
|
|
447
|
+
projectRoot: config.rootDir,
|
|
448
|
+
})) {
|
|
449
|
+
logger[level](text);
|
|
178
450
|
}
|
|
179
|
-
|
|
180
|
-
if (failed.length > 0) {
|
|
451
|
+
const failed = results.filter((r) => r.status === 'failed');
|
|
452
|
+
if (failed.length > 0 || hookFailures.length > 0) {
|
|
181
453
|
process.exitCode = 1;
|
|
182
454
|
}
|
|
183
455
|
},
|
|
@@ -11,6 +11,7 @@ import { resolveDb, createKysely, parseDatabaseUrl, } from '../db/local-db.js';
|
|
|
11
11
|
import { loadUserBootstrap, loadUserModule } from './load-user-project.js';
|
|
12
12
|
import { createDevAIAgentRunner } from './dev-ai-runner.js';
|
|
13
13
|
import { resolveConsoleMount } from './serve-console.js';
|
|
14
|
+
import { serverReadyLine } from '../../server/server-ready.js';
|
|
14
15
|
export const serve = pikkuSessionlessFunc({
|
|
15
16
|
remote: true,
|
|
16
17
|
func: async ({ logger, config, getInspectorState, variables, devServerRunner }, { port, console: serveConsole }) => {
|
|
@@ -136,6 +137,7 @@ export const serve = pikkuSessionlessFunc({
|
|
|
136
137
|
if (consoleMount) {
|
|
137
138
|
logger.info(`Pikku Console available at http://${hostname}:${resolvedPort}${consoleMount.urlPrefix}`);
|
|
138
139
|
}
|
|
140
|
+
logger.info(serverReadyLine(hostname, resolvedPort));
|
|
139
141
|
process.once('SIGINT', async () => {
|
|
140
142
|
logger.info('Stopping server...');
|
|
141
143
|
try {
|
|
@@ -1,49 +1,25 @@
|
|
|
1
|
-
import { readdir,
|
|
1
|
+
import { readdir, mkdir, writeFile } from 'fs/promises';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { dirname, join, sep } from 'path';
|
|
4
|
+
import { listSkillFiles, listSkillNames, readSkillFile } from '@pikku/skills';
|
|
5
5
|
import { pikkuSessionlessFunc } from '#pikku';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
7
|
+
* Write one skill's files out to `dest`, reading through @pikku/skills rather
|
|
8
|
+
* than off a directory we resolved ourselves. That package reads from its own
|
|
9
|
+
* `skills/` folder when there is one and from its embedded manifest otherwise,
|
|
10
|
+
* which is what makes this work inside the `bun --compile` binaries — they carry
|
|
11
|
+
* no filesystem copy, so the old "walk up to the @pikku/cli package root"
|
|
12
|
+
* lookup threw on every Homebrew install.
|
|
11
13
|
*/
|
|
12
|
-
async function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (existsSync(skills))
|
|
22
|
-
return skills;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
catch { }
|
|
26
|
-
}
|
|
27
|
-
const parent = dirname(dir);
|
|
28
|
-
if (parent === dir)
|
|
29
|
-
break;
|
|
30
|
-
dir = parent;
|
|
31
|
-
}
|
|
32
|
-
throw new Error('Could not locate bundled skills directory. Was @pikku/cli installed correctly?');
|
|
33
|
-
}
|
|
34
|
-
async function copyDir(src, dest) {
|
|
35
|
-
await mkdir(dest, { recursive: true });
|
|
36
|
-
const entries = await readdir(src, { withFileTypes: true });
|
|
37
|
-
for (const entry of entries) {
|
|
38
|
-
const s = join(src, entry.name);
|
|
39
|
-
const d = join(dest, entry.name);
|
|
40
|
-
if (entry.isDirectory()) {
|
|
41
|
-
await copyDir(s, d);
|
|
42
|
-
}
|
|
43
|
-
else if (entry.isFile()) {
|
|
44
|
-
const content = await readFile(s);
|
|
45
|
-
await writeFile(d, content);
|
|
46
|
-
}
|
|
14
|
+
async function writeSkill(name, dest) {
|
|
15
|
+
for (const path of await listSkillFiles(name)) {
|
|
16
|
+
const contents = await readSkillFile(path);
|
|
17
|
+
if (contents === null)
|
|
18
|
+
continue;
|
|
19
|
+
// Paths are relative to the skills root, so drop the leading skill name.
|
|
20
|
+
const target = join(dest, ...path.split('/').slice(1));
|
|
21
|
+
await mkdir(dirname(target), { recursive: true });
|
|
22
|
+
await writeFile(target, contents, 'utf-8');
|
|
47
23
|
}
|
|
48
24
|
}
|
|
49
25
|
/**
|
|
@@ -82,17 +58,16 @@ function parseInstallGroups(frontmatter) {
|
|
|
82
58
|
}
|
|
83
59
|
return [];
|
|
84
60
|
}
|
|
85
|
-
async function readSkillInstallGroups(
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
61
|
+
async function readSkillInstallGroups(skillName) {
|
|
62
|
+
const content = await readSkillFile(`${skillName}/SKILL.md`);
|
|
63
|
+
if (content === null)
|
|
88
64
|
return [];
|
|
89
|
-
const content = await readFile(skillPath, 'utf-8');
|
|
90
65
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
91
66
|
if (!match)
|
|
92
67
|
return [];
|
|
93
68
|
return parseInstallGroups(match[1]);
|
|
94
69
|
}
|
|
95
|
-
async function getWantedSkills(
|
|
70
|
+
async function getWantedSkills(allEntries, { only, core = false, fabric = false, }) {
|
|
96
71
|
const wanted = new Set();
|
|
97
72
|
if (only) {
|
|
98
73
|
for (const name of only
|
|
@@ -108,7 +83,7 @@ async function getWantedSkills(skillsDir, allEntries, { only, core = false, fabr
|
|
|
108
83
|
];
|
|
109
84
|
if (requestedGroups.length > 0) {
|
|
110
85
|
for (const name of allEntries) {
|
|
111
|
-
const groups = await readSkillInstallGroups(
|
|
86
|
+
const groups = await readSkillInstallGroups(name);
|
|
112
87
|
if (requestedGroups.some((group) => groups.includes(group))) {
|
|
113
88
|
wanted.add(name);
|
|
114
89
|
}
|
|
@@ -118,11 +93,7 @@ async function getWantedSkills(skillsDir, allEntries, { only, core = false, fabr
|
|
|
118
93
|
}
|
|
119
94
|
export const pikkuSkillsList = pikkuSessionlessFunc({
|
|
120
95
|
func: async ({ logger }) => {
|
|
121
|
-
const
|
|
122
|
-
const entries = (await readdir(skillsDir, { withFileTypes: true }))
|
|
123
|
-
.filter((e) => e.isDirectory())
|
|
124
|
-
.map((e) => e.name)
|
|
125
|
-
.sort();
|
|
96
|
+
const entries = await listSkillNames();
|
|
126
97
|
const installed = new Set();
|
|
127
98
|
for (const relative of Object.values(AGENT_SKILL_DIRS)) {
|
|
128
99
|
const dir = join(process.cwd(), relative);
|
|
@@ -154,12 +125,8 @@ export const pikkuSkillsInstall = pikkuSessionlessFunc({
|
|
|
154
125
|
process.exitCode = 1;
|
|
155
126
|
return;
|
|
156
127
|
}
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
.filter((e) => e.isDirectory())
|
|
160
|
-
.map((e) => e.name)
|
|
161
|
-
.sort();
|
|
162
|
-
const wanted = await getWantedSkills(skillsDir, allEntries, {
|
|
128
|
+
const allEntries = await listSkillNames();
|
|
129
|
+
const wanted = await getWantedSkills(allEntries, {
|
|
163
130
|
only,
|
|
164
131
|
core,
|
|
165
132
|
fabric,
|
|
@@ -175,13 +142,12 @@ export const pikkuSkillsInstall = pikkuSessionlessFunc({
|
|
|
175
142
|
let installed = 0;
|
|
176
143
|
let skipped = 0;
|
|
177
144
|
for (const name of wanted) {
|
|
178
|
-
const src = join(skillsDir, name);
|
|
179
145
|
const dest = join(installRoot, name);
|
|
180
146
|
if (existsSync(dest) && !update) {
|
|
181
147
|
skipped++;
|
|
182
148
|
continue;
|
|
183
149
|
}
|
|
184
|
-
await
|
|
150
|
+
await writeSkill(name, dest);
|
|
185
151
|
installed++;
|
|
186
152
|
}
|
|
187
153
|
const destLabel = `${AGENT_SKILL_DIRS[agent].split(sep).join('/')}/`;
|
|
@@ -19,6 +19,12 @@ export interface CodegenOptions {
|
|
|
19
19
|
rootDir?: string;
|
|
20
20
|
/** DB dialect — drives real-type-aware date typing. Defaults to 'sqlite'. */
|
|
21
21
|
dialect?: Dialect;
|
|
22
|
+
/**
|
|
23
|
+
* Where the project's migrations live. Only used to attribute each table in
|
|
24
|
+
* `schemaJsonFile` to whoever declared it; omitting it makes every table read
|
|
25
|
+
* as app-owned, which is what a project with no generated migrations is.
|
|
26
|
+
*/
|
|
27
|
+
migrationsDir?: string;
|
|
22
28
|
}
|
|
23
29
|
export interface CodegenResult {
|
|
24
30
|
outFile: string;
|