@pikku/cli 0.12.89 → 0.12.91
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/README.md +25 -2
- package/console-app/assets/{index-BaOGIr2O.js → index-CpkvmAGb.js} +148 -148
- package/console-app/assets/{index-CSzCJzBb.css → index-D0HG8q0B.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.js +21 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +6 -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 +52 -1
- 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 +73 -13
- package/dist/.pikku/function/pikku-functions.gen.js +9 -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 +8 -0
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +8 -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 +4 -0
- 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/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 +9 -1
- package/dist/.pikku/schemas/schemas/DbBaselineInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/DbCheckInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/DbCodegenInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/DbExportInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricValidateInput.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/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 +42 -0
- package/dist/src/fabric/fabric-commands.d.ts +9 -3
- package/dist/src/fabric/fabric-commands.js +6 -0
- package/dist/src/fabric/functions/validate.function.d.ts +15 -5
- package/dist/src/fabric/functions/validate.function.js +69 -16
- package/dist/src/fabric/lib/config.d.ts +11 -1
- package/dist/src/fabric/lib/config.js +39 -1
- package/dist/src/fabric/lib/frontend-typecheck.d.ts +46 -0
- package/dist/src/fabric/lib/frontend-typecheck.js +139 -0
- package/dist/src/functions/commands/db-baseline.d.ts +13 -0
- package/dist/src/functions/commands/db-baseline.js +48 -0
- package/dist/src/functions/commands/db-check.d.ts +12 -0
- package/dist/src/functions/commands/db-check.js +64 -0
- package/dist/src/functions/commands/db-codegen.d.ts +11 -0
- package/dist/src/functions/commands/db-codegen.js +39 -0
- package/dist/src/functions/commands/db-export.d.ts +12 -0
- package/dist/src/functions/commands/db-export.js +31 -0
- package/dist/src/functions/commands/db-generate.js +19 -26
- package/dist/src/functions/commands/dev.js +2 -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 +12 -0
- package/dist/src/functions/commands/scenario-browser.d.ts +69 -0
- package/dist/src/functions/commands/scenario-browser.js +60 -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 +18 -0
- package/dist/src/functions/commands/scenario.js +305 -38
- package/dist/src/functions/commands/serve.js +2 -0
- package/dist/src/functions/db/db-migrator.d.ts +35 -4
- package/dist/src/functions/db/db-migrator.js +67 -10
- package/dist/src/functions/db/local-db.d.ts +235 -13
- package/dist/src/functions/db/local-db.js +430 -45
- package/dist/src/functions/db/postgres/postgres-introspector.js +19 -1
- package/dist/src/functions/db/postgres/postgres-migrator.d.ts +1 -0
- package/dist/src/functions/db/postgres/postgres-migrator.js +4 -1
- package/dist/src/functions/db/sqlite/sqlite-introspector.js +2 -1
- package/dist/src/functions/db/sqlite/sqlite-migrator.d.ts +1 -0
- package/dist/src/functions/db/sqlite/sqlite-migrator.js +6 -1
- package/dist/src/functions/wirings/functions/pikku-command-functions.js +26 -8
- package/dist/src/functions/wirings/functions/serialize-function-types.js +3 -3
- package/dist/src/functions/wirings/scenarios/scenario-partition.d.ts +29 -0
- package/dist/src/functions/wirings/scenarios/scenario-partition.js +58 -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/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/meta-diff.js +5 -2
- package/dist/src/utils/pikku-cli-config.js +26 -0
- package/dist/src/utils/resolve-scenario-actors.d.ts +23 -0
- package/dist/src/utils/resolve-scenario-actors.js +74 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -6
- package/skills/pikku-i18n/SKILL.md +53 -6
- package/skills/pikku-scenario/SKILL.md +196 -29
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* This file was generated by @pikku/cli@0.12.
|
|
2
|
+
* This file was generated by @pikku/cli@0.12.91
|
|
3
3
|
*/
|
|
4
4
|
import { WorkflowCancelledException } from '@pikku/core/workflow';
|
|
5
5
|
import { template } from '@pikku/core/workflow';
|
|
6
6
|
import { type PikkuWorkflowGraphConfig, type PikkuWorkflowGraphResult } from '@pikku/core/workflow';
|
|
7
|
-
import type { PikkuWorkflowWire, PikkuScenarioWire, WorkflowStepOptions } from '@pikku/core/workflow';
|
|
7
|
+
import type { PikkuWorkflowWire, PikkuScenarioWire, WorkflowStepOptions, ScenarioStepOptions } from '@pikku/core/workflow';
|
|
8
8
|
export { WorkflowCancelledException };
|
|
9
9
|
import type { PikkuFunctionSessionless, PikkuFunctionConfig } from '../function/pikku-function-types.gen.js';
|
|
10
10
|
import type { FlattenedRPCMap } from '../rpc/pikku-rpc-wirings-map.internal.gen.d.js';
|
|
11
11
|
import type { FlattenedWorkflowMap } from './pikku-workflow-map.gen.d.js';
|
|
12
12
|
import type { AgentMap as FlattenedAgentMap } from '../agent/pikku-agent-map.gen.d.js';
|
|
13
|
+
import type { FlattenedScenarioStepMap } from './pikku-scenario-step-map.gen.d.js';
|
|
14
|
+
import type { TypedScenarioActors } from './pikku-scenario-actors.gen.js';
|
|
15
|
+
export type { TypedScenarioActors };
|
|
13
16
|
export { template };
|
|
14
17
|
export interface TypedWorkflow extends PikkuWorkflowWire {
|
|
15
18
|
do<K extends keyof FlattenedRPCMap>(stepName: string, rpcName: K, data: FlattenedRPCMap[K]['input'], options?: WorkflowStepOptions): Promise<FlattenedRPCMap[K]['output']>;
|
|
16
19
|
do<K extends keyof FlattenedWorkflowMap>(stepName: string, workflowName: K, data: FlattenedWorkflowMap[K]['input'], options?: WorkflowStepOptions): Promise<FlattenedWorkflowMap[K]['output']>;
|
|
17
20
|
do<T>(stepName: string, fn: () => T | Promise<T>, options?: WorkflowStepOptions): Promise<T>;
|
|
18
21
|
}
|
|
19
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The typed half of a scenario wire: `step` and its `given`/`when`/`then`
|
|
24
|
+
* sugar, narrowed to the names declared by `pikkuScenarioStep` in this project.
|
|
25
|
+
* The phase only changes the prose the reporter renders.
|
|
26
|
+
*/
|
|
27
|
+
export interface TypedScenarioSteps {
|
|
28
|
+
step<K extends keyof FlattenedScenarioStepMap>(stepName: string, stepFunc: K, data?: FlattenedScenarioStepMap[K]['input'], options?: ScenarioStepOptions): Promise<FlattenedScenarioStepMap[K]['output']>;
|
|
29
|
+
given<K extends keyof FlattenedScenarioStepMap>(stepName: string, stepFunc: K, data?: FlattenedScenarioStepMap[K]['input'], options?: ScenarioStepOptions): Promise<FlattenedScenarioStepMap[K]['output']>;
|
|
30
|
+
when<K extends keyof FlattenedScenarioStepMap>(stepName: string, stepFunc: K, data?: FlattenedScenarioStepMap[K]['input'], options?: ScenarioStepOptions): Promise<FlattenedScenarioStepMap[K]['output']>;
|
|
31
|
+
then<K extends keyof FlattenedScenarioStepMap>(stepName: string, stepFunc: K, data?: FlattenedScenarioStepMap[K]['input'], options?: ScenarioStepOptions): Promise<FlattenedScenarioStepMap[K]['output']>;
|
|
32
|
+
}
|
|
33
|
+
export type TypedScenario = TypedWorkflow & Omit<PikkuScenarioWire, keyof PikkuWorkflowWire | keyof TypedScenarioSteps> & TypedScenarioSteps;
|
|
20
34
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
21
35
|
import type { InferSchemaOutput, PikkuPermission, PikkuMiddleware, NodeConfig, PikkuApprovalDescription } from '../function/pikku-function-types.gen.js';
|
|
22
36
|
import type { ScopeId } from '../scopes/pikku-scopes.gen.js';
|
|
@@ -58,7 +72,39 @@ export declare function pikkuWorkflowComplexFunc<InputSchema extends StandardSch
|
|
|
58
72
|
export declare function pikkuWorkflowComplexFunc<In, Out = unknown>(func: PikkuFunctionWorkflow<In, Out> | PikkuFunctionConfig<In, Out, 'workflow', PikkuFunctionWorkflow<In, Out>>): PikkuFunctionConfig<In, Out, 'workflow'>;
|
|
59
73
|
export type PikkuScenarioConfigWithSchema<InputSchema extends StandardSchemaV1 | undefined = undefined, OutputSchema extends StandardSchemaV1 | undefined = undefined> = Omit<PikkuWorkflowConfigWithSchema<InputSchema, OutputSchema>, 'func' | 'auth' | 'scopes' | 'permissions'> & {
|
|
60
74
|
func: PikkuFunctionScenario<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown>;
|
|
75
|
+
/**
|
|
76
|
+
* Runs before the scenario body. Same signature as `func`, but its return
|
|
77
|
+
* value is discarded — a hook is setup, not a step, so it is never recorded
|
|
78
|
+
* on the ladder. Throwing skips the body and fails the run; `after` still
|
|
79
|
+
* runs.
|
|
80
|
+
*/
|
|
81
|
+
before?: PikkuScenarioHook<InputSchema>;
|
|
82
|
+
/**
|
|
83
|
+
* Always runs after the scenario body, in a `finally`, whether it passed or
|
|
84
|
+
* failed. Throwing fails a run that would otherwise have passed; on an
|
|
85
|
+
* already-failed run it attaches as the `cause` and never replaces the
|
|
86
|
+
* original error.
|
|
87
|
+
*/
|
|
88
|
+
after?: PikkuScenarioHook<InputSchema>;
|
|
89
|
+
/**
|
|
90
|
+
* Why this scenario is held out of a default run, stated where the scenario
|
|
91
|
+
* is. The scenario still appears in the plan and is reported as skipped
|
|
92
|
+
* rather than quietly omitted; naming it directly with `--flows` runs it
|
|
93
|
+
* anyway.
|
|
94
|
+
*/
|
|
95
|
+
skip?: string;
|
|
61
96
|
};
|
|
97
|
+
/**
|
|
98
|
+
* A scenario lifecycle hook: the scenario's own `(services, data, wire)`
|
|
99
|
+
* signature with its result discarded.
|
|
100
|
+
*/
|
|
101
|
+
export type PikkuScenarioHook<InputSchema extends StandardSchemaV1 | undefined = undefined> = PikkuFunctionScenario<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, void>;
|
|
102
|
+
/**
|
|
103
|
+
* Declares a scenario hook. Returns the function verbatim — a hook is never
|
|
104
|
+
* registered, so this exists purely to give an inline hook a call site to be
|
|
105
|
+
* contextually typed from, the way every other pikku primitive is.
|
|
106
|
+
*/
|
|
107
|
+
export declare function pikkuScenarioHook<In = unknown>(hook: PikkuFunctionScenario<In, void>): PikkuFunctionScenario<In, void>;
|
|
62
108
|
/**
|
|
63
109
|
* A scenario: a complex workflow that drives the app the way users do.
|
|
64
110
|
* Steps run as actors over the REAL transport — `scenario.do(step, rpc,
|
|
@@ -67,6 +113,109 @@ export type PikkuScenarioConfigWithSchema<InputSchema extends StandardSchemaV1 |
|
|
|
67
113
|
*/
|
|
68
114
|
export declare function pikkuScenario<InputSchema extends StandardSchemaV1 | undefined = undefined, OutputSchema extends StandardSchemaV1 | undefined = undefined>(config: PikkuScenarioConfigWithSchema<InputSchema, OutputSchema>): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'scenario' | 'actors', PikkuFunctionScenario<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown>, InputSchema, OutputSchema>;
|
|
69
115
|
export declare function pikkuScenario<In, Out = unknown>(func: PikkuFunctionScenario<In, Out> | PikkuFunctionConfig<In, Out, 'scenario' | 'actors', PikkuFunctionScenario<In, Out>>): PikkuFunctionConfig<In, Out, 'scenario' | 'actors'>;
|
|
116
|
+
/**
|
|
117
|
+
* A scenario as a feature references it. Any `pikkuScenario` export is
|
|
118
|
+
* assignable; `In` is recovered from it so the paired form's `data` is
|
|
119
|
+
* checked against that scenario's own input.
|
|
120
|
+
*/
|
|
121
|
+
export type PikkuScenarioRef<In = any, Out = any> = PikkuFunctionConfig<In, Out, 'scenario' | 'actors', PikkuFunctionScenario<In, Out>, any, any>;
|
|
122
|
+
/**
|
|
123
|
+
* One entry in a feature's `scenarios` list, validated against itself: a bare
|
|
124
|
+
* scenario, or a scenario paired with the input to run it with. The paired form
|
|
125
|
+
* is gherkin's `Examples:` written as an ordinary loop.
|
|
126
|
+
*/
|
|
127
|
+
export type PikkuFeatureEntry<Entry> = Entry extends {
|
|
128
|
+
scenario: PikkuScenarioRef<infer In>;
|
|
129
|
+
} ? {
|
|
130
|
+
scenario: PikkuScenarioRef<In>;
|
|
131
|
+
data: In;
|
|
132
|
+
} : Entry extends PikkuScenarioRef ? Entry : never;
|
|
133
|
+
export type PikkuFeatureConfig<Scenarios extends readonly unknown[]> = {
|
|
134
|
+
/** Human-readable name. The export identifier is the id. */
|
|
135
|
+
name: string;
|
|
136
|
+
description?: string;
|
|
137
|
+
tags?: string[];
|
|
138
|
+
scenarios: {
|
|
139
|
+
[K in keyof Scenarios]: PikkuFeatureEntry<Scenarios[K]>;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Runs ONCE before the whole group — not before each scenario. Per-scenario
|
|
143
|
+
* setup is the scenario's own `before`; gherkin's `Background:` is
|
|
144
|
+
* deliberately not expressible here.
|
|
145
|
+
*/
|
|
146
|
+
before?: PikkuScenarioHook;
|
|
147
|
+
after?: PikkuScenarioHook;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* A feature: an ordered group of scenarios, mirroring gherkin's Feature ↔
|
|
151
|
+
* Scenario structure. Scenarios are referenced by imported identifier, so a
|
|
152
|
+
* renamed or deleted scenario is a compile error rather than a silent skip.
|
|
153
|
+
*
|
|
154
|
+
* A scenario does not have to belong to a feature — a void-input scenario
|
|
155
|
+
* still runs standalone.
|
|
156
|
+
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* export const credentialFeature = pikkuFeature({
|
|
159
|
+
* name: 'Credential API',
|
|
160
|
+
* tags: ['credential'],
|
|
161
|
+
* before: startsMockOAuthServer,
|
|
162
|
+
* after: stopsMockOAuthServer,
|
|
163
|
+
* scenarios: [
|
|
164
|
+
* credentialLazyLoadScenario,
|
|
165
|
+
* ...['stripe', 'google'].map((name) => ({
|
|
166
|
+
* scenario: credentialRoundTripScenario,
|
|
167
|
+
* data: { name },
|
|
168
|
+
* })),
|
|
169
|
+
* ],
|
|
170
|
+
* })
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export declare function pikkuFeature<const Scenarios extends readonly unknown[]>(config: PikkuFeatureConfig<Scenarios>): PikkuFeatureConfig<Scenarios>;
|
|
174
|
+
export type PikkuFunctionScenarioStep<In = unknown, Out = never, B extends boolean = false> = PikkuFunctionSessionless<In, Out, B extends true ? 'scenarioStep' | 'browser' : 'scenarioStep'>;
|
|
175
|
+
export type PikkuScenarioStepConfigWithSchema<InputSchema extends StandardSchemaV1 | undefined = undefined, OutputSchema extends StandardSchemaV1 | undefined = undefined, B extends boolean = false> = {
|
|
176
|
+
/** Registered name — this is the string `scenario.step()` references. */
|
|
177
|
+
name: string;
|
|
178
|
+
/**
|
|
179
|
+
* What this step does, for the console and for whoever reads the source. It
|
|
180
|
+
* is also the fallback prose when no `template` is declared, in which case a
|
|
181
|
+
* reporter renders "Given the shopper buys an apple". Defaults to the call
|
|
182
|
+
* site's step name.
|
|
183
|
+
*/
|
|
184
|
+
description?: string;
|
|
185
|
+
/**
|
|
186
|
+
* The prose a reporter renders for this step, with `{placeholders}` filled
|
|
187
|
+
* from the input the step was called with — `'sees {packageName}'` reports as
|
|
188
|
+
* "Then the admin sees @pikku/addon-todos". Every input field should appear,
|
|
189
|
+
* so the report names the values under test rather than repeating one
|
|
190
|
+
* sentence per call site.
|
|
191
|
+
*/
|
|
192
|
+
template?: string;
|
|
193
|
+
/**
|
|
194
|
+
* This step drives a browser. The runner provisions one for the step's actor
|
|
195
|
+
* before calling it — which also makes an actor mandatory — and `wire.browser`
|
|
196
|
+
* is non-optional inside `func`.
|
|
197
|
+
*/
|
|
198
|
+
browser?: B;
|
|
199
|
+
title?: string;
|
|
200
|
+
tags?: string[];
|
|
201
|
+
input?: InputSchema;
|
|
202
|
+
output?: OutputSchema;
|
|
203
|
+
errors?: Array<typeof PikkuError>;
|
|
204
|
+
func: PikkuFunctionScenarioStep<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, B>;
|
|
205
|
+
};
|
|
206
|
+
export type PikkuScenarioStepConfig<In, Out, B extends boolean = false> = Omit<PikkuScenarioStepConfigWithSchema<undefined, undefined, B>, 'func' | 'input' | 'output'> & {
|
|
207
|
+
func: PikkuFunctionScenarioStep<In, Out, B>;
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* A named, reusable scenario step. Unlike `scenario.do`, which can only name an
|
|
211
|
+
* RPC, a step's body is an ordinary pikku function — so it may drive a browser,
|
|
212
|
+
* invoke RPCs as its actor, or run a workflow.
|
|
213
|
+
*
|
|
214
|
+
* Steps are deliberately NOT registered as RPCs: a browser-driving step must
|
|
215
|
+
* never be network-callable.
|
|
216
|
+
*/
|
|
217
|
+
export declare function pikkuScenarioStep<InputSchema extends StandardSchemaV1 | undefined = undefined, OutputSchema extends StandardSchemaV1 | undefined = undefined, B extends boolean = false>(config: PikkuScenarioStepConfigWithSchema<InputSchema, OutputSchema, B>): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, B extends true ? 'scenarioStep' | 'browser' : 'scenarioStep', PikkuFunctionScenarioStep<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, B>, InputSchema, OutputSchema>;
|
|
218
|
+
export declare function pikkuScenarioStep<In, Out = unknown, B extends boolean = false>(config: PikkuScenarioStepConfig<In, Out, B>): PikkuFunctionConfig<In, Out, B extends true ? 'scenarioStep' | 'browser' : 'scenarioStep'>;
|
|
70
219
|
type TypedRef<T> = {
|
|
71
220
|
$ref: string;
|
|
72
221
|
path?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* This file was generated by @pikku/cli@0.12.
|
|
2
|
+
* This file was generated by @pikku/cli@0.12.91
|
|
3
3
|
*/
|
|
4
4
|
import { WorkflowCancelledException } from '@pikku/core/workflow';
|
|
5
5
|
import { template } from '@pikku/core/workflow';
|
|
@@ -12,9 +12,47 @@ export function pikkuWorkflowFunc(func) {
|
|
|
12
12
|
export function pikkuWorkflowComplexFunc(func) {
|
|
13
13
|
return typeof func === 'function' ? { func } : func;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Declares a scenario hook. Returns the function verbatim — a hook is never
|
|
17
|
+
* registered, so this exists purely to give an inline hook a call site to be
|
|
18
|
+
* contextually typed from, the way every other pikku primitive is.
|
|
19
|
+
*/
|
|
20
|
+
export function pikkuScenarioHook(hook) {
|
|
21
|
+
return hook;
|
|
22
|
+
}
|
|
15
23
|
export function pikkuScenario(func) {
|
|
16
24
|
return typeof func === 'function' ? { func } : func;
|
|
17
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* A feature: an ordered group of scenarios, mirroring gherkin's Feature ↔
|
|
28
|
+
* Scenario structure. Scenarios are referenced by imported identifier, so a
|
|
29
|
+
* renamed or deleted scenario is a compile error rather than a silent skip.
|
|
30
|
+
*
|
|
31
|
+
* A scenario does not have to belong to a feature — a void-input scenario
|
|
32
|
+
* still runs standalone.
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* export const credentialFeature = pikkuFeature({
|
|
36
|
+
* name: 'Credential API',
|
|
37
|
+
* tags: ['credential'],
|
|
38
|
+
* before: startsMockOAuthServer,
|
|
39
|
+
* after: stopsMockOAuthServer,
|
|
40
|
+
* scenarios: [
|
|
41
|
+
* credentialLazyLoadScenario,
|
|
42
|
+
* ...['stripe', 'google'].map((name) => ({
|
|
43
|
+
* scenario: credentialRoundTripScenario,
|
|
44
|
+
* data: { name },
|
|
45
|
+
* })),
|
|
46
|
+
* ],
|
|
47
|
+
* })
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function pikkuFeature(config) {
|
|
51
|
+
return config;
|
|
52
|
+
}
|
|
53
|
+
export function pikkuScenarioStep(config) {
|
|
54
|
+
return config;
|
|
55
|
+
}
|
|
18
56
|
export function pikkuWorkflowGraph(config) {
|
|
19
57
|
return corePikkuWorkflowGraph(config);
|
|
20
58
|
}
|
package/dist/bin/pikku-bin.mjs
CHANGED
|
@@ -11,8 +11,8 @@ async function checkForUpdate() {
|
|
|
11
11
|
})
|
|
12
12
|
if (!res.ok) return
|
|
13
13
|
const { version: latest } = await res.json()
|
|
14
|
-
if (latest !== '0.12.
|
|
15
|
-
process.stderr.write(`\n Update available 0.12.
|
|
14
|
+
if (latest !== '0.12.91') {
|
|
15
|
+
process.stderr.write(`\n Update available 0.12.91 → ${latest}\n brew upgrade pikku or npm install -g @pikku/cli\n\n`)
|
|
16
16
|
}
|
|
17
17
|
} catch {}
|
|
18
18
|
}
|
package/dist/src/cli.wiring.js
CHANGED
|
@@ -18,6 +18,10 @@ import { dev } from './functions/commands/dev.js';
|
|
|
18
18
|
import { serve } from './functions/commands/serve.js';
|
|
19
19
|
import { dbMigrate } from './functions/commands/db-migrate.js';
|
|
20
20
|
import { dbGenerate } from './functions/commands/db-generate.js';
|
|
21
|
+
import { dbCodegen } from './functions/commands/db-codegen.js';
|
|
22
|
+
import { dbCheck } from './functions/commands/db-check.js';
|
|
23
|
+
import { dbBaseline } from './functions/commands/db-baseline.js';
|
|
24
|
+
import { dbExport } from './functions/commands/db-export.js';
|
|
21
25
|
import { dbSeed } from './functions/commands/db-seed.js';
|
|
22
26
|
import { dbReset } from './functions/commands/db-reset.js';
|
|
23
27
|
import { dbAudit } from './functions/commands/db-audit.js';
|
|
@@ -321,6 +325,22 @@ wireCLI({
|
|
|
321
325
|
func: dbGenerate,
|
|
322
326
|
description: 'Generate SQL migrations from detected schema changes',
|
|
323
327
|
}),
|
|
328
|
+
check: pikkuCLICommand({
|
|
329
|
+
func: dbCheck,
|
|
330
|
+
description: 'Report how the configured database differs from the schema its migrations define',
|
|
331
|
+
}),
|
|
332
|
+
baseline: pikkuCLICommand({
|
|
333
|
+
func: dbBaseline,
|
|
334
|
+
description: 'Record pending migrations as applied, for a database that already has what they describe',
|
|
335
|
+
}),
|
|
336
|
+
codegen: pikkuCLICommand({
|
|
337
|
+
func: dbCodegen,
|
|
338
|
+
description: 'Regenerate db/schema.gen.ts and db/zod.gen.ts from the migration files, without connecting to a database',
|
|
339
|
+
}),
|
|
340
|
+
export: pikkuCLICommand({
|
|
341
|
+
func: dbExport,
|
|
342
|
+
description: "Publish this package's schema so projects consuming it as an addon can create its tables",
|
|
343
|
+
}),
|
|
324
344
|
seed: pikkuCLICommand({
|
|
325
345
|
func: dbSeed,
|
|
326
346
|
description: 'Apply db/seed.sql to the dev database',
|
|
@@ -377,14 +397,36 @@ wireCLI({
|
|
|
377
397
|
description: 'Comma-separated flow names to run (default: all)',
|
|
378
398
|
short: 'f',
|
|
379
399
|
},
|
|
400
|
+
features: {
|
|
401
|
+
description: 'Comma-separated feature names to run (pikkuFeature exports) — the feature is the run unit, so its hooks run once around the group',
|
|
402
|
+
},
|
|
380
403
|
tags: {
|
|
381
404
|
description: 'Comma-separated tags — run flows matching any',
|
|
382
405
|
short: 't',
|
|
383
406
|
},
|
|
407
|
+
excludeTags: {
|
|
408
|
+
description: 'Comma-separated tags to hold back — flows matching any are not run, unless named directly with --flows',
|
|
409
|
+
},
|
|
384
410
|
coverage: {
|
|
385
411
|
description: 'Reset/snapshot server coverage per scenario (target must run with --coverage); writes coverage/scenario-coverage.json',
|
|
386
412
|
default: false,
|
|
387
413
|
},
|
|
414
|
+
browser: {
|
|
415
|
+
description: 'Run scenarios with browser steps. --no-browser skips them (they are reported as skipped, not failed)',
|
|
416
|
+
default: true,
|
|
417
|
+
},
|
|
418
|
+
spawn: {
|
|
419
|
+
description: "Start `pikku dev` on the environment's apiUrl for the run and stop it afterwards. Without it, the environment must already be serving",
|
|
420
|
+
default: false,
|
|
421
|
+
},
|
|
422
|
+
keepAlive: {
|
|
423
|
+
description: 'With --spawn, leave the server running after the run (dev loop)',
|
|
424
|
+
default: false,
|
|
425
|
+
},
|
|
426
|
+
trace: {
|
|
427
|
+
description: 'Keep every stack frame on a failure. Without it, only the project’s own frames are shown',
|
|
428
|
+
default: false,
|
|
429
|
+
},
|
|
388
430
|
},
|
|
389
431
|
}),
|
|
390
432
|
list: pikkuCLICommand({
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export declare const fabricCommands: {
|
|
2
|
-
validate: import("@pikku/core/cli").CoreCLICommandConfig<import("../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<
|
|
2
|
+
validate: import("@pikku/core/cli").CoreCLICommandConfig<import("../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
|
|
3
|
+
skipTypecheck?: boolean | undefined;
|
|
4
|
+
}, {
|
|
3
5
|
ok: boolean;
|
|
4
6
|
root: string;
|
|
5
7
|
findings: {
|
|
@@ -9,7 +11,9 @@ export declare const fabricCommands: {
|
|
|
9
11
|
path: string;
|
|
10
12
|
fixHint: string;
|
|
11
13
|
}[];
|
|
12
|
-
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<
|
|
14
|
+
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
|
|
15
|
+
skipTypecheck?: boolean | undefined;
|
|
16
|
+
}, {
|
|
13
17
|
ok: boolean;
|
|
14
18
|
root: string;
|
|
15
19
|
findings: {
|
|
@@ -19,7 +23,9 @@ export declare const fabricCommands: {
|
|
|
19
23
|
path: string;
|
|
20
24
|
fixHint: string;
|
|
21
25
|
}[];
|
|
22
|
-
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices> | import("../../.pikku/pikku-types.gen.js").PikkuFunction<
|
|
26
|
+
}, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices> | import("../../.pikku/pikku-types.gen.js").PikkuFunction<{
|
|
27
|
+
skipTypecheck?: boolean | undefined;
|
|
28
|
+
}, {
|
|
23
29
|
ok: boolean;
|
|
24
30
|
root: string;
|
|
25
31
|
findings: {
|
|
@@ -48,6 +48,12 @@ export const fabricCommands = defineCLICommands({
|
|
|
48
48
|
func: FabricValidate,
|
|
49
49
|
render: renderValidate,
|
|
50
50
|
description: 'Check the project structure for fabric compatibility — prints all missing or misconfigured items with fix hints',
|
|
51
|
+
options: {
|
|
52
|
+
skipTypecheck: {
|
|
53
|
+
description: 'Skip the frontend type-check the build container runs (structural checks only)',
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
51
57
|
}),
|
|
52
58
|
smoke: pikkuCLICommand({
|
|
53
59
|
func: FabricSmoke,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export declare const FabricValidateInput: z.ZodObject<{
|
|
2
|
+
export declare const FabricValidateInput: z.ZodObject<{
|
|
3
|
+
skipTypecheck: z.ZodOptional<z.ZodBoolean>;
|
|
4
|
+
}, z.core.$strip>;
|
|
3
5
|
export declare const FabricValidateOutput: z.ZodObject<{
|
|
4
6
|
ok: z.ZodBoolean;
|
|
5
7
|
root: z.ZodString;
|
|
@@ -15,8 +17,12 @@ export declare const FabricValidateOutput: z.ZodObject<{
|
|
|
15
17
|
fixHint: z.ZodString;
|
|
16
18
|
}, z.core.$strip>>;
|
|
17
19
|
}, z.core.$strip>;
|
|
18
|
-
export declare function runValidate(startDir?: string
|
|
19
|
-
|
|
20
|
+
export declare function runValidate(startDir?: string, opts?: {
|
|
21
|
+
skipTypecheck?: boolean;
|
|
22
|
+
}): Promise<z.infer<typeof FabricValidateOutput>>;
|
|
23
|
+
export declare const FabricValidate: import("../../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
|
|
24
|
+
skipTypecheck?: boolean | undefined;
|
|
25
|
+
}, {
|
|
20
26
|
ok: boolean;
|
|
21
27
|
root: string;
|
|
22
28
|
findings: {
|
|
@@ -26,7 +32,9 @@ export declare const FabricValidate: import("../../../.pikku/pikku-types.gen.js"
|
|
|
26
32
|
path: string;
|
|
27
33
|
fixHint: string;
|
|
28
34
|
}[];
|
|
29
|
-
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<
|
|
35
|
+
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
|
|
36
|
+
skipTypecheck?: boolean | undefined;
|
|
37
|
+
}, {
|
|
30
38
|
ok: boolean;
|
|
31
39
|
root: string;
|
|
32
40
|
findings: {
|
|
@@ -36,7 +44,9 @@ export declare const FabricValidate: import("../../../.pikku/pikku-types.gen.js"
|
|
|
36
44
|
path: string;
|
|
37
45
|
fixHint: string;
|
|
38
46
|
}[];
|
|
39
|
-
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices> | import("../../../.pikku/pikku-types.gen.js").PikkuFunction<
|
|
47
|
+
}, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices> | import("../../../.pikku/pikku-types.gen.js").PikkuFunction<{
|
|
48
|
+
skipTypecheck?: boolean | undefined;
|
|
49
|
+
}, {
|
|
40
50
|
ok: boolean;
|
|
41
51
|
root: string;
|
|
42
52
|
findings: {
|
|
@@ -5,6 +5,7 @@ import { dirname, join } from 'node:path';
|
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
6
|
import { pikkuSessionlessFunc } from '../../../.pikku/pikku-types.gen.js';
|
|
7
7
|
import { added, changed, removed, dim } from '../lib/output.js';
|
|
8
|
+
import { typeCheckFrontends } from '../lib/frontend-typecheck.js';
|
|
8
9
|
const FindingSchema = z.object({
|
|
9
10
|
id: z.string(),
|
|
10
11
|
severity: z.enum(['error', 'warn', 'info']),
|
|
@@ -12,7 +13,12 @@ const FindingSchema = z.object({
|
|
|
12
13
|
path: z.string(),
|
|
13
14
|
fixHint: z.string(),
|
|
14
15
|
});
|
|
15
|
-
export const FabricValidateInput = z.object({
|
|
16
|
+
export const FabricValidateInput = z.object({
|
|
17
|
+
skipTypecheck: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Skip the frontend type-check stage (structural checks only, much faster)'),
|
|
21
|
+
});
|
|
16
22
|
export const FabricValidateOutput = z.object({
|
|
17
23
|
ok: z.boolean(),
|
|
18
24
|
root: z.string(),
|
|
@@ -182,7 +188,7 @@ const POSTGRES_SQL_PATTERNS = [
|
|
|
182
188
|
{ re: /\bTSVECTOR\b/i, label: 'TSVECTOR' },
|
|
183
189
|
{ re: /\bARRAY\s*\[/i, label: 'ARRAY[…]' },
|
|
184
190
|
];
|
|
185
|
-
export async function runValidate(startDir = process.cwd()) {
|
|
191
|
+
export async function runValidate(startDir = process.cwd(), opts = {}) {
|
|
186
192
|
const root = await findProjectRoot(startDir);
|
|
187
193
|
const findings = [];
|
|
188
194
|
const e = (id, message, path, fixHint) => {
|
|
@@ -838,19 +844,47 @@ export async function runValidate(startDir = process.cwd()) {
|
|
|
838
844
|
info('config-missing', 'packages/functions/src/config.ts not found', join(fnDir, 'src', 'config.ts'), "Create src/config.ts: export const createConfig = pikkuConfig(async () => ({ sqliteDb: '.pikku-runtime/dev.db' }))");
|
|
839
845
|
}
|
|
840
846
|
}
|
|
847
|
+
// ── declared frontends ────────────────────────────────────────────────
|
|
848
|
+
// pikkufabric.config.json is unvalidated JSON, so every field below is a
|
|
849
|
+
// claim, not a guarantee: a null entry or a non-string cwd used to throw on
|
|
850
|
+
// property access and take down the whole validation run — the one thing that
|
|
851
|
+
// was supposed to report the broken config. Shape-check the entries once here
|
|
852
|
+
// and let the apps/ and type-check passes consume the narrowed list.
|
|
853
|
+
const declaredFrontends = [];
|
|
854
|
+
/** every syntactically valid cwd, including ones whose directory is missing */
|
|
855
|
+
const declaredCwdList = [];
|
|
856
|
+
const rawFrontends = fabricConfig?.frontends;
|
|
857
|
+
if (rawFrontends !== undefined &&
|
|
858
|
+
(!rawFrontends || typeof rawFrontends !== 'object')) {
|
|
859
|
+
e('frontends-invalid', 'pikkufabric.config.json "frontends" is not an object — no frontend will be built or type-checked', fabricConfigPath, `Set "frontends" to an object keyed by slug: { "app": { "cwd": "apps/app", "kind": "ssr" } }`);
|
|
860
|
+
}
|
|
861
|
+
else if (rawFrontends) {
|
|
862
|
+
for (const [slug, entry] of Object.entries(rawFrontends)) {
|
|
863
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
864
|
+
e(`frontend-entry-invalid-${slug}`, `pikkufabric.config.json frontend "${slug}" is not an object`, fabricConfigPath, `Give "${slug}" an object value: { "cwd": "apps/${slug}", "kind": "ssr" }`);
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
const { cwd, deploy } = entry;
|
|
868
|
+
if (typeof cwd !== 'string' || cwd.trim() === '') {
|
|
869
|
+
e(`frontend-cwd-invalid-${slug}`, `pikkufabric.config.json frontend "${slug}" has no string "cwd" — the build container has nothing to build`, fabricConfigPath, `Set "cwd" to the app directory, e.g. { "${slug}": { "cwd": "apps/${slug}" } }`);
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
const rel = cwd.replace(/^\.\//, '');
|
|
873
|
+
declaredCwdList.push(rel);
|
|
874
|
+
const dir = join(root, rel);
|
|
875
|
+
if (!existsSync(dir)) {
|
|
876
|
+
// Reported here rather than inside the apps/ block below: a project
|
|
877
|
+
// without an apps/ directory used to validate cleanly while declaring a
|
|
878
|
+
// deployable frontend that does not exist.
|
|
879
|
+
e(`frontend-cwd-missing-${slug}`, `fabric.config.json frontend "${slug}" declares cwd "${rel}" but that directory does not exist`, dir, `Create the directory or update the cwd in fabric.config.json`);
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
declaredFrontends.push({ slug, cwd: rel, dir, deploy: deploy !== false });
|
|
883
|
+
}
|
|
884
|
+
}
|
|
841
885
|
// ── apps/ vs fabric.config.json frontends ─────────────────────────────
|
|
842
886
|
const appsDir = join(root, 'apps');
|
|
843
887
|
if (existsSync(appsDir)) {
|
|
844
|
-
// Check declared frontends have a real directory on disk
|
|
845
|
-
if (fabricConfig) {
|
|
846
|
-
const frontends = (fabricConfig.frontends ?? {});
|
|
847
|
-
for (const [slug, fe] of Object.entries(frontends)) {
|
|
848
|
-
const cwd = fe.cwd?.replace(/^\.\//, '');
|
|
849
|
-
if (cwd && !existsSync(join(root, cwd))) {
|
|
850
|
-
e(`frontend-cwd-missing-${slug}`, `fabric.config.json frontend "${slug}" declares cwd "${cwd}" but that directory does not exist`, join(root, cwd), `Create the directory or update the cwd in fabric.config.json`);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
888
|
// Check each app/ subdir is declared and has correct local deps
|
|
855
889
|
let appEntries = [];
|
|
856
890
|
try {
|
|
@@ -861,9 +895,7 @@ export async function runValidate(startDir = process.cwd()) {
|
|
|
861
895
|
catch {
|
|
862
896
|
/* ignore */
|
|
863
897
|
}
|
|
864
|
-
const declaredCwds = fabricConfig
|
|
865
|
-
? new Set(Object.values((fabricConfig.frontends ?? {})).map((f) => f.cwd?.replace(/^\.\//, '') ?? ''))
|
|
866
|
-
: null;
|
|
898
|
+
const declaredCwds = fabricConfig ? new Set(declaredCwdList) : null;
|
|
867
899
|
for (const name of appEntries) {
|
|
868
900
|
const appPath = join(appsDir, name);
|
|
869
901
|
const cwd = `apps/${name}`;
|
|
@@ -1119,6 +1151,27 @@ export async function runValidate(startDir = process.cwd()) {
|
|
|
1119
1151
|
if (!existsSync(sdkDir)) {
|
|
1120
1152
|
info('functions-sdk-missing', 'packages/functions-sdk/ not found — generated RPC client and React Query hooks will not be available', sdkDir, lines('Create a workspace at `packages/functions-sdk/`.', 'Minimum structure:', '- packages/functions-sdk/package.json', '- packages/functions-sdk/src/pikku/', 'Point `pikku.config.json` clientFiles to:', '- packages/functions-sdk/src/pikku/rpc-map.gen.d.ts', '- packages/functions-sdk/src/pikku/api.gen.ts', 'This package is the home for generated client artifacts, not handwritten server code.'));
|
|
1121
1153
|
}
|
|
1154
|
+
// ── frontend type-check (what the build container actually runs) ───────
|
|
1155
|
+
// Every check above is a heuristic standing in for this compile. The build
|
|
1156
|
+
// container type-checks each deployable frontend and aborts the deploy on a
|
|
1157
|
+
// non-zero exit, so running the same compile here is the difference between
|
|
1158
|
+
// a 20-second local failure and a burned deploy out of the daily 10.
|
|
1159
|
+
if (!opts.skipTypecheck) {
|
|
1160
|
+
const frontends = declaredFrontends
|
|
1161
|
+
.filter((fe) => fe.deploy)
|
|
1162
|
+
.map((fe) => ({ name: fe.slug, dir: fe.dir }));
|
|
1163
|
+
for (const result of await typeCheckFrontends(root, frontends)) {
|
|
1164
|
+
if (result.skipped) {
|
|
1165
|
+
w(`frontend-typecheck-skipped-${result.name}`, `could not type-check frontend "${result.name}" — ${result.skipped}`, result.dir, lines('The build container type-checks every deployable frontend and aborts the deploy if it fails.', 'Give the app a tsconfig.json and a "tsc" script so the same check runs locally.'));
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
if (result.errors.length === 0)
|
|
1169
|
+
continue;
|
|
1170
|
+
const shown = result.errors.slice(0, 20);
|
|
1171
|
+
const extra = result.errors.length - shown.length;
|
|
1172
|
+
e(`frontend-typecheck-${result.name}`, `frontend "${result.name}" does not type-check — the build container aborts the deploy on this`, result.dir, lines(...shown, ...(extra > 0 ? [`… and ${extra} more`] : []), '', `Reproduce with: cd ${result.dir.slice(root.length + 1)} && ${result.command}`));
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1122
1175
|
const ok = !findings.some((f) => f.severity === 'error');
|
|
1123
1176
|
return { ok, root, findings };
|
|
1124
1177
|
}
|
|
@@ -1126,7 +1179,7 @@ export const FabricValidate = pikkuSessionlessFunc({
|
|
|
1126
1179
|
description: 'Check the current project structure for fabric compatibility. Prints all missing or misconfigured items with fix hints so an AI agent or developer can resolve them.',
|
|
1127
1180
|
input: FabricValidateInput,
|
|
1128
1181
|
output: FabricValidateOutput,
|
|
1129
|
-
func: async (_services) => runValidate(),
|
|
1182
|
+
func: async (_services, { skipTypecheck }) => runValidate(process.cwd(), { skipTypecheck }),
|
|
1130
1183
|
});
|
|
1131
1184
|
export const renderValidate = (_s, { ok, root, findings }) => {
|
|
1132
1185
|
if (findings.length === 0) {
|
|
@@ -50,7 +50,17 @@ export declare function findProjectConfig(startDir?: string): Promise<{
|
|
|
50
50
|
* placeholder to the API as if it were a real id.
|
|
51
51
|
*/
|
|
52
52
|
export declare function isLinkedProjectId(projectId?: string | null): boolean;
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Merge into the existing config rather than replacing it. `link` and `init`
|
|
55
|
+
* only know the projectId, so a plain write silently deleted every other key —
|
|
56
|
+
* including `frontends`, which is what tells the build container an app exists.
|
|
57
|
+
* The symptom is a deploy that succeeds having built no frontend at all, long
|
|
58
|
+
* after the link that caused it. Unknown keys are preserved too; nothing here
|
|
59
|
+
* has any business dropping config it does not understand.
|
|
60
|
+
*/
|
|
61
|
+
export declare function writeProjectConfig(cwd: string, config: Partial<ProjectConfig> & {
|
|
62
|
+
projectId: string;
|
|
63
|
+
}): Promise<string>;
|
|
54
64
|
export declare function readAuthFile(): Promise<AuthFile>;
|
|
55
65
|
export declare function writeAuthFile(file: AuthFile): Promise<void>;
|
|
56
66
|
export interface ResolvedApiContext {
|
|
@@ -31,10 +31,48 @@ export function isLinkedProjectId(projectId) {
|
|
|
31
31
|
return false;
|
|
32
32
|
return !/^__.*__$/.test(projectId);
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Merge into the existing config rather than replacing it. `link` and `init`
|
|
36
|
+
* only know the projectId, so a plain write silently deleted every other key —
|
|
37
|
+
* including `frontends`, which is what tells the build container an app exists.
|
|
38
|
+
* The symptom is a deploy that succeeds having built no frontend at all, long
|
|
39
|
+
* after the link that caused it. Unknown keys are preserved too; nothing here
|
|
40
|
+
* has any business dropping config it does not understand.
|
|
41
|
+
*/
|
|
34
42
|
export async function writeProjectConfig(cwd, config) {
|
|
35
43
|
const path = join(cwd, projectConfigName);
|
|
44
|
+
let existing = {};
|
|
45
|
+
if (existsSync(path)) {
|
|
46
|
+
// A read that fails for any reason other than the file's contents —
|
|
47
|
+
// permissions, a transient I/O error — tells us nothing about what the file
|
|
48
|
+
// holds. Rewriting it then is the exact clobber this function exists to
|
|
49
|
+
// prevent, so refuse the write instead.
|
|
50
|
+
let raw;
|
|
51
|
+
try {
|
|
52
|
+
raw = await readFile(path, 'utf8');
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw new Error(`Cannot read ${projectConfigName} at ${path} (${error.message}) — refusing to overwrite it`);
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(raw);
|
|
59
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
60
|
+
existing = parsed;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Valid JSON that is not an object holds nothing we can merge, but it is
|
|
64
|
+
// still the user's file — say so rather than dropping it silently.
|
|
65
|
+
console.warn(`[fabric] ${projectConfigName} is not a JSON object — rewriting it`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// An unparseable config is not a reason to lose the link; warn and start
|
|
70
|
+
// fresh rather than throwing away the command the user just ran.
|
|
71
|
+
console.warn(`[fabric] ${projectConfigName} is not valid JSON (${error.message}) — rewriting it`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
36
74
|
await mkdir(dirname(path), { recursive: true });
|
|
37
|
-
await writeFile(path, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
75
|
+
await writeFile(path, JSON.stringify({ ...existing, ...config }, null, 2) + '\n', 'utf8');
|
|
38
76
|
return path;
|
|
39
77
|
}
|
|
40
78
|
export async function readAuthFile() {
|