@kaptures/storybook 0.1.0 → 0.2.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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Typed Storybook 10 lifecycle adapter for Kapture visual review.
4
4
 
5
5
  ```bash
6
- bun add --dev --exact @kaptures/storybook@0.0.6
6
+ bun add --dev --exact @kaptures/storybook@0.2.0
7
7
  ```
8
8
 
9
9
  Register the adapter in `.storybook/preview.ts` with Storybook's CSF factory API.
@@ -22,7 +22,7 @@ export default definePreview({
22
22
  });
23
23
  ```
24
24
 
25
- Named `kapture.viewports` are resolved against `parameters.viewport.options`. Kapture does not read Chromatic parameters or Storybook's private preview globals.
25
+ Named `kapture.captureViewports` are resolved against `parameters.viewport.options`. Kapture does not read Chromatic parameters or Storybook's private preview globals.
26
26
 
27
27
  To emit the dependency graph used by `kapture plan`, register the build preset in
28
28
  `.storybook/main.ts`. `projectRoot` is relative to the Storybook build working directory. Files
package/dist/index.d.ts CHANGED
@@ -4,10 +4,10 @@ interface KaptureViewport {
4
4
  width: number;
5
5
  height: number;
6
6
  }
7
- type KaptureWaitForState = "attached" | "visible" | "hidden" | "detached";
8
- interface KaptureWaitForOptions {
7
+ type KaptureElementState = "attached" | "visible" | "hidden" | "detached";
8
+ interface KaptureElementCondition {
9
9
  selector: string;
10
- state?: KaptureWaitForState;
10
+ state?: KaptureElementState;
11
11
  timeoutMs?: number;
12
12
  }
13
13
  interface KaptureFontAssertion {
@@ -16,28 +16,29 @@ interface KaptureFontAssertion {
16
16
  style?: string;
17
17
  weight?: string | number;
18
18
  }
19
- type KaptureAnimation = "disabled" | "start" | "end" | {
19
+ type KaptureAnimationState = "disabled" | "start" | "end" | {
20
20
  atMs: number;
21
21
  };
22
22
  interface KaptureStoryParameters {
23
- disabled?: boolean;
24
- viewports?: ReadonlyArray<string | KaptureViewport>;
25
- /** Capture only the configured viewport instead of the full rendered page. */
26
- cropToViewport?: boolean;
27
- delayMs?: number;
28
- waitFor?: string | KaptureWaitForOptions;
29
- animation?: KaptureAnimation;
23
+ /** Set to false to skip this story. Defaults to true. */
24
+ captureEnabled?: boolean;
25
+ captureViewports?: ReadonlyArray<string | KaptureViewport>;
26
+ /** Defaults to full-page. */
27
+ captureArea?: "full-page" | "viewport";
28
+ captureDelayMs?: number;
29
+ waitForElement?: KaptureElementCondition;
30
+ animationState?: KaptureAnimationState;
30
31
  /** Hide matching nodes in the captured base/head images. */
31
- mask?: readonly string[];
32
+ maskSelectors?: readonly string[];
32
33
  /** Keep matching nodes visible, but ignore their interior pixels during diff. */
33
- ignoreSelectors?: readonly string[];
34
+ diffIgnoreSelectors?: readonly string[];
34
35
  /** Wait for the document font set to finish loading before capture. */
35
36
  waitForFonts?: boolean;
36
37
  /** Fail capture when a specific font family or sampled glyph set is unavailable. */
37
38
  fontAssertions?: ReadonlyArray<KaptureFontAssertion>;
38
39
  diff?: {
39
- pixel?: number;
40
- ratio?: number;
40
+ pixelColorThreshold?: number;
41
+ maxChangedPixelRatio?: number;
41
42
  };
42
43
  }
43
44
  interface KaptureAddonTypes {
@@ -52,4 +53,4 @@ declare function createKapturePreviewAddon(): import("storybook/internal/csf").P
52
53
  //#region src/index.d.ts
53
54
  declare const kapture: typeof createKapturePreviewAddon;
54
55
  //#endregion
55
- export { type KaptureAddonTypes, type KaptureAnimation, type KaptureFontAssertion, type KaptureStoryParameters, type KaptureViewport, type KaptureWaitForOptions, type KaptureWaitForState, createKapturePreviewAddon as default, kapture };
56
+ export { type KaptureAddonTypes, type KaptureAnimationState, type KaptureElementCondition, type KaptureElementState, type KaptureFontAssertion, type KaptureStoryParameters, type KaptureViewport, createKapturePreviewAddon as default, kapture };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{PLAY_FUNCTION_THREW_EXCEPTION as e,STORY_FINISHED as t,STORY_RENDER_PHASE_CHANGED as n,UNHANDLED_ERRORS_WHILE_PLAYING as r}from"storybook/internal/core-events";import{definePreviewAddon as i}from"storybook/internal/csf";import{addons as a}from"storybook/preview-api";function o(e){let t=m(e,`parameters`);return w({disabled:v(t.disabled,`disabled`),viewports:s(t.viewports),cropToViewport:v(t.cropToViewport,`cropToViewport`),delayMs:x(t.delayMs,`delayMs`),waitFor:l(t.waitFor),animation:u(t.animation),mask:p(t.mask,`mask`),ignoreSelectors:p(t.ignoreSelectors,`ignoreSelectors`),waitForFonts:v(t.waitForFonts,`waitForFonts`),fontAssertions:t.fontAssertions===void 0?void 0:h(t.fontAssertions,`fontAssertions`).map((e,t)=>d(e,`fontAssertions[${t}]`)),diff:f(t.diff)})}function s(e){if(e===void 0)return;let t=h(e,`viewports`);if(t.length===0)throw Error(`Kapture viewports must contain at least one viewport; use disabled: true to skip`);return t.map((e,t)=>c(e,`viewports[${t}]`))}function c(e,t){let n=m(e,t);return{name:g(n.name,`${t}.name`),width:S(n.width,`${t}.width`),height:S(n.height,`${t}.height`)}}function l(e){if(e===void 0)return;if(typeof e==`string`)return g(e,`waitFor`);let t=m(e,`waitFor`),n=t.state;if(n!==void 0&&n!==`attached`&&n!==`visible`&&n!==`hidden`&&n!==`detached`)throw Error(`Invalid Kapture waitFor state: ${String(n)}`);return w({selector:g(t.selector,`waitFor.selector`),state:n,timeoutMs:x(t.timeoutMs,`waitFor.timeoutMs`)})}function u(e){if(e!==void 0)return e===`disabled`||e===`start`||e===`end`?e:{atMs:b(m(e,`animation`).atMs,`animation.atMs`)}}function d(e,t){let n=m(e,t),r=n.weight;if(r!==void 0&&typeof r!=`string`&&typeof r!=`number`)throw Error(`${t}.weight must be a string or number`);return w({family:g(n.family,`${t}.family`),glyphs:g(n.glyphs,`${t}.glyphs`),style:_(n.style,`${t}.style`),weight:r})}function f(e){if(e===void 0)return;let t=m(e,`diff`);return w({pixel:C(t.pixel,`diff.pixel`),ratio:C(t.ratio,`diff.ratio`)})}function p(e,t){if(e!==void 0)return h(e,t).map((e,n)=>g(e,`${t}[${n}]`))}function m(e,t){if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`Kapture ${t} must be an object`);return e}function h(e,t){if(!Array.isArray(e))throw Error(`Kapture ${t} must be an array`);return e}function g(e,t){if(typeof e!=`string`||!e.trim())throw Error(`Kapture ${t} must be a non-empty string`);return e}function _(e,t){return e===void 0?void 0:g(e,t)}function v(e,t){if(e!==void 0){if(typeof e!=`boolean`)throw Error(`Kapture ${t} must be a boolean`);return e}}function y(e,t){if(e!==void 0){if(typeof e!=`number`||!Number.isFinite(e))throw Error(`Kapture ${t} must be a finite number`);return e}}function b(e,t){let n=y(e,t);if(n===void 0||n<0)throw Error(`Kapture ${t} must be a non-negative number`);return n}function x(e,t){return e===void 0?void 0:b(e,t)}function S(e,t){let n=y(e,t);if(n===void 0||n<=0)throw Error(`Kapture ${t} must be positive`);return n}function C(e,t){if(e===void 0)return;let n=b(e,t);if(n>1)throw Error(`Kapture ${t} must be between 0 and 1`);return n}function w(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0))}var T=`0.1.0`;const E={set:e=>setTimeout(e,0),clear:e=>clearTimeout(e)};function D(e,t,n=E){let r=0,i,a,o=new Map,s=[],c=()=>{a!==void 0&&n.clear(a),a=void 0},l=()=>{if(!i)return;let{configurationError:n,storybookError:r,...a}=i;t({protocolVersion:2,adapterVersion:e,state:`rendering`,...a})},u=(e,t)=>{let n={storyId:e,generation:++r,renderId:t};return o.set(n.generation,n),i=n,n},d=(r,o)=>{c(),a=n.set(()=>{if(a=void 0,!(!i||i.generation!==r.generation)&&!(r.configurationError||r.storybookError)){if(o===`error`){t({protocolVersion:2,adapterVersion:e,state:`error`,storyId:r.storyId,generation:r.generation,renderId:r.renderId,error:{kind:`storybook`,message:`Storybook failed while rendering, playing, or finishing this story.`}});return}if(r.renderId===void 0||r.parameters===void 0){t({protocolVersion:2,adapterVersion:e,state:`error`,storyId:r.storyId,generation:r.generation,renderId:r.renderId,error:{kind:`configuration`,message:`Kapture did not receive resolved story parameters before Storybook finished.`}});return}t({protocolVersion:2,adapterVersion:e,state:`ready`,storyId:r.storyId,generation:r.generation,renderId:r.renderId,parameters:r.parameters})}})};return{boot(){c(),r=0,i=void 0,o.clear(),s.length=0,t({protocolVersion:2,adapterVersion:e,state:`booting`})},startStory(e,t){return c(),(!i||i.storyId!==e||i.parameters!==void 0||i.configurationError!==void 0||i.storybookError!==void 0)&&u(e),i.parameters=t,l(),i.generation},completeStory(e,t){let n=o.get(t);!n||n.storyId!==e||n.configurationError||n.storybookError||s.includes(t)||s.push(t)},failConfiguration(n,r){c(),(!i||i.storyId!==n||i.parameters!==void 0)&&u(n),i.configurationError=r.message,t({protocolVersion:2,adapterVersion:e,state:`error`,storyId:n,generation:i.generation,renderId:i.renderId,error:{kind:`configuration`,message:r.message}})},failStorybook(n){c(),i&&(i.storybookError=n.message,t({protocolVersion:2,adapterVersion:e,state:`error`,storyId:i.storyId,generation:i.generation,renderId:i.renderId,error:{kind:`storybook`,message:n.message}}))},handlePhase(e){if(e.newPhase===`loading`){c(),u(e.storyId,e.renderId),l();return}i?.storyId===e.storyId&&(i.renderId=e.renderId)},handleFinished(e){let t=s.findIndex(t=>o.get(t)?.storyId===e.storyId),n=t<0?void 0:s.splice(t,1)[0],r=n===void 0?i:o.get(n);!r||r.storyId!==e.storyId||r.configurationError||r.storybookError||(e.status!==`success`||n!==void 0)&&d(r,e.status)},dispose(){c(),i=void 0,o.clear(),s.length=0}}}function O(e,t){try{let e=M(t,`Storybook parameters`),n=e.kapture===void 0?{}:M(e.kapture,`parameters.kapture`),r=n.viewports?.map((t,n)=>typeof t==`string`?k(t,e.viewport):A(t,n));return o({disabled:n.disabled,viewports:r,cropToViewport:n.cropToViewport,delayMs:n.delayMs,waitFor:n.waitFor,animation:n.animation,mask:n.mask,ignoreSelectors:n.ignoreSelectors,waitForFonts:n.waitForFonts,fontAssertions:n.fontAssertions,diff:n.diff})}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Kapture story ${e}: ${n}`,{cause:t})}}function k(e,t){if(!e.trim())throw Error(`viewports entries must not be empty`);let n=M(M(M(t,`parameters.viewport`).options,`parameters.viewport.options`)[e],`viewport option ${e}`);return{name:e,width:j(n.styles?.width,`${e}.styles.width`),height:j(n.styles?.height,`${e}.styles.height`)}}function A(e,t){if(typeof e!=`object`||!e)throw Error(`viewports[${t}] must be a viewport name or object`);return{name:e.name,width:e.width,height:e.height}}function j(e,t){if(typeof e!=`string`)throw Error(`${t} must be an exact pixel dimension`);let n=/^(\d+)px$/.exec(e.trim());if(!n?.[1])throw Error(`${t} must use an exact pixel dimension such as 360px`);let r=Number(n[1]);if(r<=0)throw Error(`${t} must be positive`);return r}function M(e,t){if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`${t} must be an object`);return e}function N(e){globalThis.__KAPTURE_STORYBOOK__=e}function P(){let o=D(T,N),s=new WeakMap;return i({beforeAll(){o.boot();let i=a.getChannel(),s=e=>o.handlePhase(e),c=e=>o.handleFinished(e),l=e=>o.failStorybook(F(e));return i.on(n,s),i.on(t,c),i.on(e,l),i.on(r,l),()=>{i.off(n,s),i.off(t,c),i.off(e,l),i.off(r,l),o.dispose()}},beforeEach(e){try{let t=o.startStory(e.id,O(e.id,e.parameters));s.set(e,t)}catch(t){let n=t instanceof Error?t:Error(String(t));throw o.failConfiguration(e.id,n),n}},afterEach(e){let t=s.get(e);t!==void 0&&o.completeStory(e.id,t)}})}function F(e){let t=Array.isArray(e)?e[0]:e;if(typeof t==`object`&&t&&`message`in t){let e=t.message;if(typeof e==`string`&&e.trim())return Error(`Storybook play function failed: ${e}`)}return Error(`Storybook play function failed.`)}const I=P;var L=P;export{L as default,I as kapture};
1
+ import{PLAY_FUNCTION_THREW_EXCEPTION as e,STORY_FINISHED as t,STORY_RENDER_PHASE_CHANGED as n,UNHANDLED_ERRORS_WHILE_PLAYING as r}from"storybook/internal/core-events";import{definePreviewAddon as i}from"storybook/internal/csf";import{addons as a}from"storybook/preview-api";function o(e){let t=h(e,`parameters`);return T({captureEnabled:y(t.captureEnabled,`captureEnabled`),captureViewports:s(t.captureViewports),captureArea:c(t.captureArea),captureDelayMs:S(t.captureDelayMs,`captureDelayMs`),waitForElement:u(t.waitForElement),animationState:d(t.animationState),maskSelectors:m(t.maskSelectors,`maskSelectors`),diffIgnoreSelectors:m(t.diffIgnoreSelectors,`diffIgnoreSelectors`),waitForFonts:y(t.waitForFonts,`waitForFonts`),fontAssertions:t.fontAssertions===void 0?void 0:g(t.fontAssertions,`fontAssertions`).map((e,t)=>f(e,`fontAssertions[${t}]`)),diff:p(t.diff)})}function s(e){if(e===void 0)return;let t=g(e,`captureViewports`);if(t.length===0)throw Error(`Kapture captureViewports must contain at least one viewport; use captureEnabled: false to skip`);return t.map((e,t)=>l(e,`captureViewports[${t}]`))}function c(e){if(e===void 0||e===`full-page`||e===`viewport`)return e;throw Error(`Invalid Kapture captureArea: ${String(e)}`)}function l(e,t){let n=h(e,t);return{name:_(n.name,`${t}.name`),width:C(n.width,`${t}.width`),height:C(n.height,`${t}.height`)}}function u(e){if(e===void 0)return;let t=h(e,`waitForElement`),n=t.state;if(n!==void 0&&n!==`attached`&&n!==`visible`&&n!==`hidden`&&n!==`detached`)throw Error(`Invalid Kapture waitForElement state: ${String(n)}`);return T({selector:_(t.selector,`waitForElement.selector`),state:n,timeoutMs:S(t.timeoutMs,`waitForElement.timeoutMs`)})}function d(e){if(e!==void 0)return e===`disabled`||e===`start`||e===`end`?e:{atMs:x(h(e,`animationState`).atMs,`animationState.atMs`)}}function f(e,t){let n=h(e,t),r=n.weight;if(r!==void 0&&typeof r!=`string`&&typeof r!=`number`)throw Error(`${t}.weight must be a string or number`);return T({family:_(n.family,`${t}.family`),glyphs:_(n.glyphs,`${t}.glyphs`),style:v(n.style,`${t}.style`),weight:r})}function p(e){if(e===void 0)return;let t=h(e,`diff`);return T({pixelColorThreshold:w(t.pixelColorThreshold,`diff.pixelColorThreshold`),maxChangedPixelRatio:w(t.maxChangedPixelRatio,`diff.maxChangedPixelRatio`)})}function m(e,t){if(e!==void 0)return g(e,t).map((e,n)=>_(e,`${t}[${n}]`))}function h(e,t){if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`Kapture ${t} must be an object`);return e}function g(e,t){if(!Array.isArray(e))throw Error(`Kapture ${t} must be an array`);return e}function _(e,t){if(typeof e!=`string`||!e.trim())throw Error(`Kapture ${t} must be a non-empty string`);return e}function v(e,t){return e===void 0?void 0:_(e,t)}function y(e,t){if(e!==void 0){if(typeof e!=`boolean`)throw Error(`Kapture ${t} must be a boolean`);return e}}function b(e,t){if(e!==void 0){if(typeof e!=`number`||!Number.isFinite(e))throw Error(`Kapture ${t} must be a finite number`);return e}}function x(e,t){let n=b(e,t);if(n===void 0||n<0)throw Error(`Kapture ${t} must be a non-negative number`);return n}function S(e,t){return e===void 0?void 0:x(e,t)}function C(e,t){let n=b(e,t);if(n===void 0||n<=0)throw Error(`Kapture ${t} must be positive`);return n}function w(e,t){if(e===void 0)return;let n=x(e,t);if(n>1)throw Error(`Kapture ${t} must be between 0 and 1`);return n}function T(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0))}var E=`0.2.0`;const D={set:e=>setTimeout(e,0),clear:e=>clearTimeout(e)};function O(e,t,n=D){let r=0,i,a,o=new Map,s=[],c=()=>{a!==void 0&&n.clear(a),a=void 0},l=()=>{if(!i)return;let{configurationError:n,storybookError:r,...a}=i;t({protocolVersion:3,adapterVersion:e,state:`rendering`,...a})},u=(e,t)=>{let n={storyId:e,generation:++r,renderId:t};return o.set(n.generation,n),i=n,n},d=(r,o)=>{c(),a=n.set(()=>{if(a=void 0,!(!i||i.generation!==r.generation)&&!(r.configurationError||r.storybookError)){if(o===`error`){t({protocolVersion:3,adapterVersion:e,state:`error`,storyId:r.storyId,generation:r.generation,renderId:r.renderId,error:{kind:`storybook`,message:`Storybook failed while rendering, playing, or finishing this story.`}});return}if(r.renderId===void 0||r.parameters===void 0){t({protocolVersion:3,adapterVersion:e,state:`error`,storyId:r.storyId,generation:r.generation,renderId:r.renderId,error:{kind:`configuration`,message:`Kapture did not receive resolved story parameters before Storybook finished.`}});return}t({protocolVersion:3,adapterVersion:e,state:`ready`,storyId:r.storyId,generation:r.generation,renderId:r.renderId,parameters:r.parameters})}})};return{boot(){c(),r=0,i=void 0,o.clear(),s.length=0,t({protocolVersion:3,adapterVersion:e,state:`booting`})},startStory(e,t){return c(),(!i||i.storyId!==e||i.parameters!==void 0||i.configurationError!==void 0||i.storybookError!==void 0)&&u(e),i.parameters=t,l(),i.generation},completeStory(e,t){let n=o.get(t);!n||n.storyId!==e||n.configurationError||n.storybookError||s.includes(t)||s.push(t)},failConfiguration(n,r){c(),(!i||i.storyId!==n||i.parameters!==void 0)&&u(n),i.configurationError=r.message,t({protocolVersion:3,adapterVersion:e,state:`error`,storyId:n,generation:i.generation,renderId:i.renderId,error:{kind:`configuration`,message:r.message}})},failStorybook(n){c(),i&&(i.storybookError=n.message,t({protocolVersion:3,adapterVersion:e,state:`error`,storyId:i.storyId,generation:i.generation,renderId:i.renderId,error:{kind:`storybook`,message:n.message}}))},handlePhase(e){if(e.newPhase===`loading`){c(),u(e.storyId,e.renderId),l();return}i?.storyId===e.storyId&&(i.renderId=e.renderId)},handleFinished(e){let t=s.findIndex(t=>o.get(t)?.storyId===e.storyId),n=t<0?void 0:s.splice(t,1)[0],r=n===void 0?i:o.get(n);!r||r.storyId!==e.storyId||r.configurationError||r.storybookError||(e.status!==`success`||n!==void 0)&&d(r,e.status)},dispose(){c(),i=void 0,o.clear(),s.length=0}}}function k(e,t){try{let e=N(t,`Storybook parameters`),n=e.kapture===void 0?{}:N(e.kapture,`parameters.kapture`),r=n.captureViewports?.map((t,n)=>typeof t==`string`?A(t,e.viewport):j(t,n));return o({captureEnabled:n.captureEnabled,captureViewports:r,captureArea:n.captureArea,captureDelayMs:n.captureDelayMs,waitForElement:n.waitForElement,animationState:n.animationState,maskSelectors:n.maskSelectors,diffIgnoreSelectors:n.diffIgnoreSelectors,waitForFonts:n.waitForFonts,fontAssertions:n.fontAssertions,diff:n.diff})}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Kapture story ${e}: ${n}`,{cause:t})}}function A(e,t){if(!e.trim())throw Error(`captureViewports entries must not be empty`);let n=N(N(N(t,`parameters.viewport`).options,`parameters.viewport.options`)[e],`viewport option ${e}`);return{name:e,width:M(n.styles?.width,`${e}.styles.width`),height:M(n.styles?.height,`${e}.styles.height`)}}function j(e,t){if(typeof e!=`object`||!e)throw Error(`captureViewports[${t}] must be a viewport name or object`);return{name:e.name,width:e.width,height:e.height}}function M(e,t){if(typeof e!=`string`)throw Error(`${t} must be an exact pixel dimension`);let n=/^(\d+)px$/.exec(e.trim());if(!n?.[1])throw Error(`${t} must use an exact pixel dimension such as 360px`);let r=Number(n[1]);if(r<=0)throw Error(`${t} must be positive`);return r}function N(e,t){if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`${t} must be an object`);return e}function P(e){globalThis.__KAPTURE_STORYBOOK__=e}function F(){let o=O(E,P),s=new WeakMap;return i({beforeAll(){o.boot();let i=a.getChannel(),s=e=>o.handlePhase(e),c=e=>o.handleFinished(e),l=e=>o.failStorybook(I(e));return i.on(n,s),i.on(t,c),i.on(e,l),i.on(r,l),()=>{i.off(n,s),i.off(t,c),i.off(e,l),i.off(r,l),o.dispose()}},beforeEach(e){try{let t=o.startStory(e.id,k(e.id,e.parameters));s.set(e,t)}catch(t){let n=t instanceof Error?t:Error(String(t));throw o.failConfiguration(e.id,n),n}},afterEach(e){let t=s.get(e);t!==void 0&&o.completeStory(e.id,t)}})}function I(e){let t=Array.isArray(e)?e[0]:e;if(typeof t==`object`&&t&&`message`in t){let e=t.message;if(typeof e==`string`&&e.trim())return Error(`Storybook play function failed: ${e}`)}return Error(`Storybook play function failed.`)}const L=F;var R=F;export{R as default,L as kapture};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaptures/storybook",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Typed Storybook lifecycle adapter for Kapture visual review",
5
5
  "type": "module",
6
6
  "license": "MIT",