@ripplo/testing 0.3.9 → 0.4.0

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/dist/actions.d.ts CHANGED
@@ -191,6 +191,11 @@ interface HandleDialogOptions {
191
191
  readonly promptText: string | undefined;
192
192
  readonly uiOnly?: boolean;
193
193
  }
194
+ /**
195
+ * Respond to a native browser dialog (`alert`, `confirm`, `prompt`). Set
196
+ * `uiOnly: true` only when the dialog has no backend side effect — if it
197
+ * triggers a mutation, wire an observer and keep `uiOnly` off.
198
+ */
194
199
  declare function handleDialog({ action, promptText, uiOnly }: HandleDialogOptions): UnlabeledStep<{
195
200
  action: "accept" | "dismiss";
196
201
  promptText: string | undefined;
@@ -202,6 +207,12 @@ interface ClipboardOptions {
202
207
  readonly target: Variable<string> | undefined;
203
208
  readonly value: StringOrVariable | undefined;
204
209
  }
210
+ /**
211
+ * Read from or write to the browser clipboard. Use when a flow depends on
212
+ * copy/paste (share links, invite tokens) — pair `write` to seed the
213
+ * clipboard before a paste action, or `read` into a `variable()` to assert
214
+ * what the app copied.
215
+ */
205
216
  declare function clipboard({ action, target, value }: ClipboardOptions): UnlabeledStep<{
206
217
  action: "read" | "write";
207
218
  type: "clipboard";
@@ -212,6 +223,10 @@ interface SetPermissionOptions {
212
223
  readonly permission: string;
213
224
  readonly state: "granted" | "prompt";
214
225
  }
226
+ /**
227
+ * Pre-answer a browser permission prompt (geolocation, camera, notifications,
228
+ * clipboard-read, etc.) so the test doesn't stall on the native dialog.
229
+ */
215
230
  declare function setPermission({ permission, state }: SetPermissionOptions): UnlabeledStep<{
216
231
  permission: string;
217
232
  state: "granted" | "prompt";
@@ -221,6 +236,11 @@ interface SetViewportOptions {
221
236
  readonly height: number;
222
237
  readonly width: number;
223
238
  }
239
+ /**
240
+ * Resize the browser viewport mid-test. Use to cover responsive breakpoints
241
+ * (mobile nav, collapsed sidebars) within the same flow rather than
242
+ * duplicating the test per size.
243
+ */
224
244
  declare function setViewport({ height, width }: SetViewportOptions): UnlabeledStep<{
225
245
  height: number;
226
246
  type: "setViewport";
package/dist/assert.d.ts CHANGED
@@ -1,8 +1,7 @@
1
- import { O as ObserverHandle, a as ObserverInput, b as ObserverBudgetTier } from './types-Do4o4Y_c.js';
1
+ import { O as ObserverHandle, a as ObserverInput, b as ObserverBudgetTier } from './types-B7YljrTz.js';
2
2
  import { Variable, StaticStringRef, VariableRef } from './control.js';
3
3
  import { U as UnlabeledStep } from './step-De52hTLd.js';
4
4
  import { CheckLocator, AnyLocator } from './locators.js';
5
- import 'zod';
6
5
  import '@ripplo/spec';
7
6
 
8
7
  type StringOrVariable = string | Variable<string>;
@@ -67,6 +66,12 @@ declare const assert: {
67
66
  operator: "equals";
68
67
  type: "assertAttribute";
69
68
  }>;
69
+ /**
70
+ * Verify a backend side effect by polling an observer. The server-side
71
+ * observer impl returns `ctx.retry(reason)` for transient states (keep
72
+ * polling) and `ctx.fail(reason)` only for invariant violations (stop
73
+ * early). The observer's `.budget()` governs poll interval and timeout.
74
+ */
70
75
  backend<THandle extends ObserverHandle>(observer: THandle, params: ObserverInput<THandle> & Record<string, string>): UnlabeledStep<{
71
76
  budget: ObserverBudgetTier;
72
77
  observer: string;
package/dist/assert.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  readObserverBudget,
3
3
  readObserverName
4
- } from "./chunk-D7DRM7AG.js";
4
+ } from "./chunk-SBZJDJP4.js";
5
5
  import {
6
6
  toSpecLocator,
7
7
  toStringValueRef
@@ -37,6 +37,13 @@ var assert = {
37
37
  type: "assertAttribute"
38
38
  });
39
39
  },
40
+ // eslint-disable-next-line no-comments/disallowComments
41
+ /**
42
+ * Verify a backend side effect by polling an observer. The server-side
43
+ * observer impl returns `ctx.retry(reason)` for transient states (keep
44
+ * polling) and `ctx.fail(reason)` only for invariant violations (stop
45
+ * early). The observer's `.budget()` governs poll interval and timeout.
46
+ */
40
47
  backend(observer, params) {
41
48
  return createStep({
42
49
  budget: readObserverBudget(observer),
@@ -1,4 +1,4 @@
1
- import { O as ObserverHandle, P as Precondition, l as PreconditionData, j as TestDefinition, e as DslConfig, g as ObserverDefinition, k as PreconditionDefinition, U as UnimplementedItems, f as ObserverContext, h as ObserverOutcome, S as SetupContext, m as TestValue, T as TeardownContext, n as UserDslConfig } from './types-Do4o4Y_c.js';
1
+ import { O as ObserverHandle, P as Precondition, k as PreconditionData, i as TestDefinition, f as ObserverDefinition, j as PreconditionDefinition, U as UnimplementedItems, e as ObserverContext, g as ObserverOutcome, S as SetupContext, l as TestValue, T as TeardownContext } from './types-B7YljrTz.js';
2
2
  import { ObserverBudget } from '@ripplo/spec';
3
3
  import { S as Step } from './step-De52hTLd.js';
4
4
 
@@ -6,6 +6,12 @@ interface ObserverNeedsDescription {
6
6
  readonly description: (text: string) => ObserverNeedsBudget;
7
7
  }
8
8
  interface ObserverNeedsBudget {
9
+ /**
10
+ * Polling envelope: interval + total timeout the runner uses when calling
11
+ * `assert.backend()`. Pick by where the side effect lands — fast for
12
+ * same-request DB reads, slow for job-queue settle, async for
13
+ * webhook/email delivery. Too tight = flaky; too loose = slow suite.
14
+ */
9
15
  readonly budget: (tier: ObserverBudget) => ObserverNeedsInput;
10
16
  }
11
17
  interface ObserverNeedsInput {
@@ -37,6 +43,13 @@ interface TestNeedsName {
37
43
  }
38
44
  interface TestNeedsRequires {
39
45
  readonly description: (text: string) => TestNeedsRequires;
46
+ /**
47
+ * Declare the preconditions this test needs. The object keys become the
48
+ * destructured parameter in `.startsAt` / `.steps`, and each precondition's
49
+ * return data flows through as a typed proxy — interpolate proxy fields
50
+ * directly (e.g. `` `/workspaces/${workspace.id}` ``); never hand-write
51
+ * `{{namespace.key}}` template strings (lint blocks this).
52
+ */
40
53
  readonly requires: <TReqs extends PreconditionRecord>(reqs: TReqs) => TestNeedsOutcome<ResolveDeps<TReqs>>;
41
54
  }
42
55
  interface TestNeedsOutcome<TVars extends Record<string, Record<string, string>>> {
@@ -44,13 +57,33 @@ interface TestNeedsOutcome<TVars extends Record<string, Record<string, string>>>
44
57
  }
45
58
 
46
59
  interface TestNeedsStartsAt<TVars extends Record<string, Record<string, string>>> {
60
+ /**
61
+ * Skip implementation — compiles as a planning stub and is reported in
62
+ * `getUnimplemented()`. The test appears in the lockfile but is not run.
63
+ */
47
64
  readonly notImplemented: () => TestDefinition;
65
+ /**
66
+ * Starting URL for the test, derived from precondition data. The function
67
+ * receives the `.requires()` object destructured as typed proxies; it runs
68
+ * at compile time, not at test-author time.
69
+ */
48
70
  readonly startsAt: (fn: (vars: TVars) => string) => TestNeedsSteps<TVars>;
49
71
  }
50
72
  interface TestNeedsSteps<TVars extends Record<string, Record<string, string>>> {
73
+ /**
74
+ * Declare the ordered step list. Receives destructured precondition data as
75
+ * typed proxies (same as `.startsAt`). Every step must be labeled with
76
+ * `.as("…")` — lint enforces it.
77
+ */
51
78
  readonly steps: (fn: (vars: TVars) => ReadonlyArray<Step>) => TestNeedsCoverage;
52
79
  }
53
80
  interface TestNeedsCoverage {
81
+ /**
82
+ * Finalize the test and declare which mutation/AST branches it covers.
83
+ * Coverage IDs feed LLM-driven test scoping on diffs so only relevant tests
84
+ * run for a change. `.coverage()` (or `.notImplemented()`) is the only way
85
+ * to terminate the chain into a runnable `TestDefinition`.
86
+ */
54
87
  readonly coverage: (...ids: ReadonlyArray<CoverageStatementId>) => TestDefinition;
55
88
  }
56
89
 
@@ -73,20 +106,44 @@ interface RipploRegistries<P extends PreconditionRegistry, O extends ObserverReg
73
106
  readonly tests: ReadonlyArray<TestDefinition>;
74
107
  }
75
108
  interface RipploInstance<P extends PreconditionRegistry = PreconditionRegistry, O extends ObserverRegistry = ObserverRegistry> {
76
- readonly config: DslConfig;
77
109
  readonly observers: O;
78
110
  readonly preconditions: P;
79
111
  readonly tests: ReadonlyArray<TestDefinition>;
80
- readonly getConfig: () => DslConfig;
81
112
  readonly getObservers: () => ReadonlyArray<ObserverDefinition>;
82
113
  readonly getPreconditions: () => ReadonlyArray<PreconditionDefinition>;
83
114
  readonly getTests: () => ReadonlyArray<TestDefinition>;
84
115
  readonly getUnimplemented: () => UnimplementedItems;
85
116
  }
86
117
  type RipploBuilder = RipploInstance;
118
+ /**
119
+ * Declare a precondition — a named, reusable piece of test data setup. Returns
120
+ * a pure handle (no side effects at definition time); the setup/teardown impl
121
+ * is wired once in `createEngine`. Depend on other preconditions via
122
+ * `.requires()` so the DAG can resolve independents in parallel, giving every
123
+ * test a clean, isolated slate.
124
+ */
87
125
  declare function precondition(name: string): PreconditionNeedsSetup;
126
+ /**
127
+ * Declare a backend assertion — checked from tests via `assert.backend()`.
128
+ * The impl runs on your server (where DB/queue access lives) and is polled
129
+ * until pass/fail; `.budget()` picks the polling envelope. Use this instead
130
+ * of sleeps when verifying async side effects (jobs, webhooks, emails, DB rows).
131
+ */
88
132
  declare function observer(name: string): ObserverNeedsDescription;
133
+ /**
134
+ * Start a test definition. `id` is a stable kebab-case key persisted in the
135
+ * lockfile and used for run history — changing it renames the test and loses
136
+ * history. Set `options.uiOnly` only for genuinely read-only flows (no
137
+ * mutations); do not use it to silence observer lint on flows that mutate
138
+ * state — wire the observer instead.
139
+ */
89
140
  declare function test(id: string, options?: TestOptions): TestNeedsName;
90
- declare function createRipplo<P extends PreconditionRegistry, O extends ObserverRegistry>(rawConfig: UserDslConfig, registries: RipploRegistries<P, O>): RipploInstance<P, O>;
141
+ /**
142
+ * The single registration point for the DSL graph. Collects the three
143
+ * registries into an instance consumed both by `ripplo compile` (to write
144
+ * `.ripplo/ripplo.lock`) and by `createEngine` on the server, where
145
+ * TypeScript exhaustiveness-checks that every handle has exactly one impl.
146
+ */
147
+ declare function createRipplo<P extends PreconditionRegistry, O extends ObserverRegistry>(registries: RipploRegistries<P, O>): RipploInstance<P, O>;
91
148
 
92
149
  export { type CoverageRegistry as C, type ObserverImplFn as O, type PreconditionImpl as P, type ResolveDeps as R, type ObserverRegistry as a, type PreconditionRecord as b, type PreconditionRegistry as c, type RipploBuilder as d, type RipploInstance as e, type RipploRegistries as f, createRipplo as g, observer as o, precondition as p, test as t };
@@ -25,7 +25,7 @@ function compile(ripplo) {
25
25
  };
26
26
  });
27
27
  const tests = testDefs.map((def) => compileTest(def, preconditionDefs));
28
- return { config: ripplo.getConfig(), observers, preconditions, tests };
28
+ return { observers, preconditions, tests };
29
29
  }
30
30
  function validateUniqueIds(defs) {
31
31
  const seen = /* @__PURE__ */ new Map();
@@ -1,5 +1,4 @@
1
1
  // src/types.ts
2
- import { z } from "zod";
3
2
  var DEFAULT_WATCH_PATHS = [
4
3
  "**/src/**",
5
4
  "**/app/**",
@@ -60,17 +59,6 @@ var DEFAULT_IGNORE_PATHS = [
60
59
  "**/prisma/migrations/**",
61
60
  "**/scripts/**"
62
61
  ];
63
- var dslConfigSchema = z.object({
64
- appUrl: z.string(),
65
- engineUrl: z.string(),
66
- ignorePaths: z.array(z.string()).optional(),
67
- projectId: z.string(),
68
- watchPaths: z.array(z.string()).optional(),
69
- webhookSecret: z.string()
70
- });
71
- var userDslConfigSchema = dslConfigSchema.extend({
72
- webhookSecret: z.string().optional()
73
- });
74
62
  function readTestValue(value) {
75
63
  return value.value;
76
64
  }
@@ -109,7 +97,6 @@ function makeObserverHandle({
109
97
  export {
110
98
  DEFAULT_WATCH_PATHS,
111
99
  DEFAULT_IGNORE_PATHS,
112
- userDslConfigSchema,
113
100
  readTestValue,
114
101
  createTestValue,
115
102
  readPreconditionName,
@@ -1,6 +1,15 @@
1
1
  // src/adapters/shared.ts
2
2
  import { Webhook, WebhookVerificationError } from "standardwebhooks";
3
3
  import { z } from "zod";
4
+ function readAdapterWebhookSecret() {
5
+ const value = process.env.RIPPLO_WEBHOOK_SECRET;
6
+ if (value == null || value.length === 0) {
7
+ throw new Error(
8
+ "ripplo: RIPPLO_WEBHOOK_SECRET is not set. The adapter needs it to verify precondition calls. Run `ripplo init` to generate one, or unset `enabled` to disable the adapter."
9
+ );
10
+ }
11
+ return value;
12
+ }
4
13
  var batchRequestSchema = z.object({
5
14
  preconditions: z.array(z.string().min(1))
6
15
  });
@@ -75,6 +84,7 @@ function buildSetCookieHeader(cookie) {
75
84
  }
76
85
 
77
86
  export {
87
+ readAdapterWebhookSecret,
78
88
  batchRequestSchema,
79
89
  teardownRequestSchema,
80
90
  observerRequestSchema,
@@ -1,11 +1,9 @@
1
1
  import { Observer, Precondition, WorkflowSpec } from '@ripplo/spec';
2
- import { d as RipploBuilder } from './builder-CNJmzJVe.js';
3
- import { e as DslConfig } from './types-Do4o4Y_c.js';
2
+ import { d as RipploBuilder } from './builder-0oT23S0W.js';
3
+ import './types-B7YljrTz.js';
4
4
  import './step-De52hTLd.js';
5
- import 'zod';
6
5
 
7
6
  interface CompileResult {
8
- readonly config: DslConfig;
9
7
  readonly observers: Record<string, Observer>;
10
8
  readonly preconditions: Record<string, Precondition>;
11
9
  readonly tests: ReadonlyArray<CompiledTest>;
package/dist/compiler.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  compile
3
- } from "./chunk-5AGV4KQA.js";
3
+ } from "./chunk-GWSEDWEF.js";
4
4
  import "./chunk-MGATMMCZ.js";
5
5
  import "./chunk-4MGIQFAJ.js";
6
6
  export {
package/dist/control.d.ts CHANGED
@@ -6,6 +6,12 @@ declare const VARIABLE_INTERNAL: unique symbol;
6
6
  interface Variable<_TName extends string> {
7
7
  readonly [VARIABLE_INTERNAL]: _TName;
8
8
  }
9
+ /**
10
+ * Declare a runtime placeholder, bound later by `extract()` and consumed by
11
+ * actions/assertions. Use for values produced mid-test (e.g. an ID rendered
12
+ * into the DOM). Precondition data is already typed and injected — reach for
13
+ * `variable` only when the value isn't known until the test runs.
14
+ */
9
15
  declare function variable<TName extends string>(name: TName): Variable<TName>;
10
16
  declare function readVariable(v: Variable<string>): string;
11
17
  declare function isVariable(v: unknown): v is Variable<string>;
@@ -18,6 +24,11 @@ interface VariableRef {
18
24
  readonly type: "variable";
19
25
  }
20
26
  declare function toStringValueRef(value: string | Variable<string>): StaticStringRef | VariableRef;
27
+ /**
28
+ * Capture the text of the matched element into `target` (a `variable()`).
29
+ * Pairs by name — downstream steps referencing the same `variable` see the
30
+ * extracted value.
31
+ */
21
32
  declare function extract(locator: AnyLocator, target: Variable<string>): UnlabeledStep<{
22
33
  locator: {
23
34
  by: "testId";
package/dist/elysia.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { Elysia } from 'elysia';
2
- import { R as RipploEngine } from './engine-U84UJ0sj.js';
3
- import './builder-CNJmzJVe.js';
4
- import './types-Do4o4Y_c.js';
5
- import 'zod';
2
+ import { R as RipploEngine } from './engine-BdKDGBYw.js';
3
+ import './builder-0oT23S0W.js';
4
+ import './types-B7YljrTz.js';
6
5
  import './step-De52hTLd.js';
7
6
  import '@ripplo/spec';
8
7
 
package/dist/elysia.js CHANGED
@@ -2,10 +2,11 @@ import {
2
2
  batchRequestSchema,
3
3
  buildSetCookieHeader,
4
4
  observerRequestSchema,
5
+ readAdapterWebhookSecret,
5
6
  serializeCookie,
6
7
  teardownRequestSchema,
7
8
  verifyWebhookSignature
8
- } from "./chunk-TO3T2D2Y.js";
9
+ } from "./chunk-UFHSNW4E.js";
9
10
  import "./chunk-4MGIQFAJ.js";
10
11
 
11
12
  // src/adapters/elysia.ts
@@ -18,7 +19,7 @@ function build({ enabled, engine }) {
18
19
  if (!enabled) {
19
20
  return app.put("/execute-preconditions", () => notFoundResponse()).put("/execute-observer", () => notFoundResponse()).put("/teardown-preconditions", () => notFoundResponse());
20
21
  }
21
- const webhookSecret = engine.getConfig().webhookSecret;
22
+ const webhookSecret = readAdapterWebhookSecret();
22
23
  return app.put("/execute-preconditions", async ({ request }) => {
23
24
  const gate = await verifyAndReadBody(request, webhookSecret);
24
25
  if ("response" in gate) {
@@ -1,5 +1,5 @@
1
- import { c as PreconditionRegistry, a as ObserverRegistry, O as ObserverImplFn, P as PreconditionImpl, e as RipploInstance } from './builder-CNJmzJVe.js';
2
- import { h as ObserverOutcome, C as CookieEntry, e as DslConfig, g as ObserverDefinition, k as PreconditionDefinition, U as UnimplementedItems, O as ObserverHandle, P as Precondition } from './types-Do4o4Y_c.js';
1
+ import { c as PreconditionRegistry, a as ObserverRegistry, O as ObserverImplFn, P as PreconditionImpl, e as RipploInstance } from './builder-0oT23S0W.js';
2
+ import { g as ObserverOutcome, C as CookieEntry, f as ObserverDefinition, j as PreconditionDefinition, U as UnimplementedItems, O as ObserverHandle, P as Precondition } from './types-B7YljrTz.js';
3
3
 
4
4
  interface EngineResult {
5
5
  readonly cookies: ReadonlyArray<CookieEntry>;
@@ -20,7 +20,6 @@ interface ObserverExecutionResult {
20
20
  interface RipploEngine {
21
21
  readonly executeObserver: (name: string, params: Record<string, string>) => Promise<ObserverExecutionResult>;
22
22
  readonly executePreconditions: (names: ReadonlyArray<string>, options?: ExecuteBatchOptions) => Promise<EngineResult>;
23
- readonly getConfig: () => DslConfig;
24
23
  readonly getObservers: () => ReadonlyArray<ObserverDefinition>;
25
24
  readonly getPreconditions: () => ReadonlyArray<PreconditionDefinition>;
26
25
  readonly getUnimplemented: () => UnimplementedItems;
package/dist/express.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { Router } from 'express';
2
- import { R as RipploEngine } from './engine-U84UJ0sj.js';
3
- import './builder-CNJmzJVe.js';
4
- import './types-Do4o4Y_c.js';
5
- import 'zod';
2
+ import { R as RipploEngine } from './engine-BdKDGBYw.js';
3
+ import './builder-0oT23S0W.js';
4
+ import './types-B7YljrTz.js';
6
5
  import './step-De52hTLd.js';
7
6
  import '@ripplo/spec';
8
7
 
package/dist/express.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  batchRequestSchema,
3
3
  observerRequestSchema,
4
+ readAdapterWebhookSecret,
4
5
  serializeCookie,
5
6
  teardownRequestSchema,
6
7
  verifyWebhookSignature
7
- } from "./chunk-TO3T2D2Y.js";
8
+ } from "./chunk-UFHSNW4E.js";
8
9
  import "./chunk-4MGIQFAJ.js";
9
10
 
10
11
  // src/adapters/express.ts
@@ -14,13 +15,9 @@ function createExpressHandler({ enabled, engine }) {
14
15
  if (!enabled) {
15
16
  return router;
16
17
  }
17
- const webhookSecret = engine.getConfig().webhookSecret;
18
+ const webhookSecret = readAdapterWebhookSecret();
18
19
  router.use(json());
19
20
  router.use((req, res, next) => {
20
- if (webhookSecret.length === 0) {
21
- res.status(403).json({ error: "Webhook secret not configured" });
22
- return;
23
- }
24
21
  const payload = JSON.stringify(req.body);
25
22
  const headers = extractWebhookHeaders(req);
26
23
  if (!verifyWebhookSignature(payload, headers, webhookSecret)) {
package/dist/fastify.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { FastifyInstance } from 'fastify';
2
- import { R as RipploEngine } from './engine-U84UJ0sj.js';
3
- import './builder-CNJmzJVe.js';
4
- import './types-Do4o4Y_c.js';
5
- import 'zod';
2
+ import { R as RipploEngine } from './engine-BdKDGBYw.js';
3
+ import './builder-0oT23S0W.js';
4
+ import './types-B7YljrTz.js';
6
5
  import './step-De52hTLd.js';
7
6
  import '@ripplo/spec';
8
7
 
package/dist/fastify.js CHANGED
@@ -2,10 +2,11 @@ import {
2
2
  batchRequestSchema,
3
3
  buildSetCookieHeader,
4
4
  observerRequestSchema,
5
+ readAdapterWebhookSecret,
5
6
  serializeCookie,
6
7
  teardownRequestSchema,
7
8
  verifyWebhookSignature
8
- } from "./chunk-TO3T2D2Y.js";
9
+ } from "./chunk-UFHSNW4E.js";
9
10
  import "./chunk-4MGIQFAJ.js";
10
11
 
11
12
  // src/adapters/fastify.ts
@@ -17,12 +18,9 @@ function registerFastifyHandler({
17
18
  return async () => {
18
19
  };
19
20
  }
20
- const webhookSecret = engine.getConfig().webhookSecret;
21
+ const webhookSecret = readAdapterWebhookSecret();
21
22
  return async (fastify) => {
22
23
  fastify.addHook("preHandler", async (req, reply) => {
23
- if (webhookSecret.length === 0) {
24
- return reply.code(403).send({ error: "Webhook secret not configured" });
25
- }
26
24
  const payload = JSON.stringify(req.body);
27
25
  const headers = {
28
26
  "webhook-id": extractHeader(req, "webhook-id"),
package/dist/hono.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { Hono } from 'hono';
2
- import { R as RipploEngine } from './engine-U84UJ0sj.js';
3
- import './builder-CNJmzJVe.js';
4
- import './types-Do4o4Y_c.js';
5
- import 'zod';
2
+ import { R as RipploEngine } from './engine-BdKDGBYw.js';
3
+ import './builder-0oT23S0W.js';
4
+ import './types-B7YljrTz.js';
6
5
  import './step-De52hTLd.js';
7
6
  import '@ripplo/spec';
8
7
 
package/dist/hono.js CHANGED
@@ -2,10 +2,11 @@ import {
2
2
  batchRequestSchema,
3
3
  buildSetCookieHeader,
4
4
  observerRequestSchema,
5
+ readAdapterWebhookSecret,
5
6
  serializeCookie,
6
7
  teardownRequestSchema,
7
8
  verifyWebhookSignature
8
- } from "./chunk-TO3T2D2Y.js";
9
+ } from "./chunk-UFHSNW4E.js";
9
10
  import "./chunk-4MGIQFAJ.js";
10
11
 
11
12
  // src/adapters/hono.ts
@@ -15,7 +16,7 @@ function createHonoHandler({ enabled, engine }) {
15
16
  if (!enabled) {
16
17
  return app;
17
18
  }
18
- const webhookSecret = engine.getConfig().webhookSecret;
19
+ const webhookSecret = readAdapterWebhookSecret();
19
20
  app.use("*", createWebhookMiddleware(webhookSecret));
20
21
  app.put("/execute-preconditions", async (c) => {
21
22
  const body = tryParseJson(c.get("rawBody"));
package/dist/index.d.ts CHANGED
@@ -1,12 +1,11 @@
1
- export { C as CoverageRegistry, O as ObserverImplFn, a as ObserverRegistry, P as PreconditionImpl, b as PreconditionRecord, c as PreconditionRegistry, R as ResolveDeps, d as RipploBuilder, e as RipploInstance, f as RipploRegistries, g as createRipplo, o as observer, p as precondition, t as test } from './builder-CNJmzJVe.js';
1
+ export { C as CoverageRegistry, O as ObserverImplFn, a as ObserverRegistry, P as PreconditionImpl, b as PreconditionRecord, c as PreconditionRegistry, R as ResolveDeps, d as RipploBuilder, e as RipploInstance, f as RipploRegistries, g as createRipplo, o as observer, p as precondition, t as test } from './builder-0oT23S0W.js';
2
2
  import { CompileResult } from './compiler.js';
3
3
  export { CompiledTest, compile } from './compiler.js';
4
- export { E as EngineImpls, a as EngineResult, b as ExecuteBatchOptions, N as NotImplemented, O as ObserverImplFnFor, P as PreconditionImplFor, R as RipploEngine, c as createEngine, n as notImplemented } from './engine-U84UJ0sj.js';
5
- import { C as CookieEntry } from './types-Do4o4Y_c.js';
6
- export { c as CookieOptions, D as DEFAULT_IGNORE_PATHS, d as DEFAULT_WATCH_PATHS, e as DslConfig, f as ObserverContext, g as ObserverDefinition, O as ObserverHandle, a as ObserverInput, h as ObserverOutcome, P as Precondition, i as PreconditionDeps, S as SetupContext, T as TeardownContext, j as TestDefinition } from './types-Do4o4Y_c.js';
4
+ export { E as EngineImpls, a as EngineResult, b as ExecuteBatchOptions, N as NotImplemented, O as ObserverImplFnFor, P as PreconditionImplFor, R as RipploEngine, c as createEngine, n as notImplemented } from './engine-BdKDGBYw.js';
5
+ import { C as CookieEntry } from './types-B7YljrTz.js';
6
+ export { c as CookieOptions, D as DEFAULT_IGNORE_PATHS, d as DEFAULT_WATCH_PATHS, e as ObserverContext, f as ObserverDefinition, O as ObserverHandle, a as ObserverInput, g as ObserverOutcome, P as Precondition, h as PreconditionDeps, S as SetupContext, T as TeardownContext, i as TestDefinition } from './types-B7YljrTz.js';
7
7
  export { D as DslNodeInput } from './step-De52hTLd.js';
8
8
  import '@ripplo/spec';
9
- import 'zod';
10
9
 
11
10
  interface LintDiagnostic {
12
11
  readonly message: string;
package/dist/index.js CHANGED
@@ -10,18 +10,17 @@ import {
10
10
  readPreconditionDependsOn,
11
11
  readPreconditionDescription,
12
12
  readPreconditionName,
13
- readTestValue,
14
- userDslConfigSchema
15
- } from "./chunk-D7DRM7AG.js";
13
+ readTestValue
14
+ } from "./chunk-SBZJDJP4.js";
16
15
  import {
17
16
  compile
18
- } from "./chunk-5AGV4KQA.js";
17
+ } from "./chunk-GWSEDWEF.js";
19
18
  import "./chunk-MGATMMCZ.js";
20
19
  import {
21
20
  buildSetCookieHeader,
22
21
  serializeCookie,
23
22
  verifyWebhookSignature
24
- } from "./chunk-TO3T2D2Y.js";
23
+ } from "./chunk-UFHSNW4E.js";
25
24
  import "./chunk-4MGIQFAJ.js";
26
25
 
27
26
  // src/observer.ts
@@ -63,26 +62,16 @@ function test(id, options) {
63
62
  validateTestId(id);
64
63
  return buildTestName(id, options?.uiOnly);
65
64
  }
66
- function createRipplo(rawConfig, registries) {
67
- const parsed = userDslConfigSchema.parse(rawConfig);
68
- const webhookSecret = parsed.webhookSecret ?? process.env["RIPPLO_WEBHOOK_SECRET"] ?? "";
69
- if (webhookSecret.length === 0) {
70
- throw new Error(
71
- "RIPPLO_WEBHOOK_SECRET is required. Set it in .ripplo/.env or pass webhookSecret to createRipplo()."
72
- );
73
- }
74
- const config = { ...parsed, webhookSecret };
65
+ function createRipplo(registries) {
75
66
  const { observers, preconditions, tests } = registries;
76
67
  validateUniqueNames(preconditions, observers, tests);
77
68
  const preconditionDefs = Object.values(preconditions).map((p) => stubPreconditionDef(p));
78
69
  const observerDefs = Object.values(observers).map((o) => stubObserverDef(o));
79
70
  const testDefs = [...tests];
80
71
  return {
81
- config,
82
72
  observers,
83
73
  preconditions,
84
74
  tests: testDefs,
85
- getConfig: () => config,
86
75
  getObservers: () => observerDefs,
87
76
  getPreconditions: () => preconditionDefs,
88
77
  getTests: () => testDefs,
@@ -777,7 +766,6 @@ function createEngine(ripplo, impls) {
777
766
  return {
778
767
  executeObserver: (name, params) => executeObserver(observersByName, name, params),
779
768
  executePreconditions: (names, options) => executePreconditions({ defsByName: preconditionsByName, names, options }),
780
- getConfig: () => ripplo.getConfig(),
781
769
  getObservers: () => observerDefs,
782
770
  getPreconditions: () => preconditionDefs,
783
771
  getUnimplemented: () => ({
package/dist/koa.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { Middleware } from 'koa';
2
- import { R as RipploEngine } from './engine-U84UJ0sj.js';
3
- import './builder-CNJmzJVe.js';
4
- import './types-Do4o4Y_c.js';
5
- import 'zod';
2
+ import { R as RipploEngine } from './engine-BdKDGBYw.js';
3
+ import './builder-0oT23S0W.js';
4
+ import './types-B7YljrTz.js';
6
5
  import './step-De52hTLd.js';
7
6
  import '@ripplo/spec';
8
7
 
package/dist/koa.js CHANGED
@@ -2,10 +2,11 @@ import {
2
2
  batchRequestSchema,
3
3
  buildSetCookieHeader,
4
4
  observerRequestSchema,
5
+ readAdapterWebhookSecret,
5
6
  serializeCookie,
6
7
  teardownRequestSchema,
7
8
  verifyWebhookSignature
8
- } from "./chunk-TO3T2D2Y.js";
9
+ } from "./chunk-UFHSNW4E.js";
9
10
  import "./chunk-4MGIQFAJ.js";
10
11
 
11
12
  // src/adapters/koa.ts
@@ -15,7 +16,7 @@ function createKoaHandler({ enabled, engine }) {
15
16
  await next();
16
17
  };
17
18
  }
18
- const webhookSecret = engine.getConfig().webhookSecret;
19
+ const webhookSecret = readAdapterWebhookSecret();
19
20
  return async (ctx, next) => {
20
21
  if (ctx.method !== "PUT") {
21
22
  await next();
@@ -26,11 +27,6 @@ function createKoaHandler({ enabled, engine }) {
26
27
  await next();
27
28
  return;
28
29
  }
29
- if (webhookSecret.length === 0) {
30
- ctx.status = 403;
31
- ctx.body = { error: "Webhook secret not configured" };
32
- return;
33
- }
34
30
  const body = await readBody(ctx);
35
31
  const headers = {
36
32
  "webhook-id": headerString(ctx.get("webhook-id")),
@@ -24,7 +24,17 @@ type AnyLocator = Locator<"role" | "testId">;
24
24
  type InputLocator = Locator<"testId"> | RoleLocator<"combobox" | "searchbox" | "spinbutton" | "textbox">;
25
25
  type SelectLocator = Locator<"testId"> | RoleLocator<"combobox" | "listbox">;
26
26
  type CheckLocator = Locator<"testId"> | RoleLocator<"checkbox" | "switch">;
27
+ /**
28
+ * Preferred locator: targets an element by its ARIA role and accessible name.
29
+ * Survives DOM churn and doubles as an accessibility check — if a role query
30
+ * can't find your element, real users likely can't either.
31
+ */
27
32
  declare function role<TRole extends AriaRole>(ariaRole: TRole, name?: string): RoleLocator<TRole>;
33
+ /**
34
+ * Escape hatch for elements without a semantic role (decorative wrappers,
35
+ * non-interactive panels). Prefer `role()` when the element has one; a
36
+ * test-id locator has no accessibility signal.
37
+ */
28
38
  declare function testId(id: string): Locator<"testId">;
29
39
 
30
40
  export { type AnyLocator, type AriaRole, type CheckLocator, type InputLocator, type Locator, type LocatorSpec, type RoleLocator, type SelectLocator, readLocator, role, testId };
@@ -1,8 +1,8 @@
1
1
  import { Codec } from '@ripplo/spec';
2
2
  import { z } from 'zod';
3
3
  import { CompileResult } from './compiler.js';
4
- import './builder-CNJmzJVe.js';
5
- import './types-Do4o4Y_c.js';
4
+ import './builder-0oT23S0W.js';
5
+ import './types-B7YljrTz.js';
6
6
  import './step-De52hTLd.js';
7
7
 
8
8
  declare const LOCKFILE_RELATIVE_PATH = ".ripplo/ripplo.lock";