@trylayout/qa 0.1.3 → 0.1.5

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
@@ -1,5 +1,9 @@
1
1
  # Layout QA
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@trylayout/qa?label=npm)](https://www.npmjs.com/package/@trylayout/qa)
4
+ [![CI](https://github.com/Layout-App/layout-qa/actions/workflows/ci.yml/badge.svg)](https://github.com/Layout-App/layout-qa/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-black.svg)](LICENSE)
6
+
3
7
  Layout QA is a local browser QA protocol and runner for AI-built frontends. It runs deterministic flows against a local or preview URL, captures screenshots at meaningful checkpoints, checks browser health, and writes a static HTML report.
4
8
 
5
9
  The core loop is intentionally local:
@@ -12,6 +16,10 @@ npx @trylayout/qa run --target-url http://localhost:5173 --scenario happy_path -
12
16
 
13
17
  No account, upload, hosted service, or external docs are required.
14
18
 
19
+ ## Example Report
20
+
21
+ ![Layout QA HTML report for a synthetic CRM demo](docs/assets/layout-qa-report.png)
22
+
15
23
  Package names:
16
24
 
17
25
  - Canonical npm package: `@trylayout/qa`
@@ -29,8 +37,8 @@ npx layout-qa run --target-url http://localhost:5173 --scenario happy_path --ope
29
37
 
30
38
  Frontend agents can move faster when they have a visual feedback loop they can run themselves. Layout gives the agent a small protocol:
31
39
 
32
- - Wire deterministic API/auth mocks behind a local env flag such as `VITE_LAYOUT_QA_MOCKS=1`.
33
- - Switch mock states with `localStorage["layout.qa.scenario"]`.
40
+ - Wire deterministic API/auth responses behind a local env flag such as `VITE_LAYOUT_QA_MOCKS=1`.
41
+ - Switch response states with `localStorage["layout.qa.scenario"]`.
34
42
  - Declare high-value browser flows in `.layout/qa-flows.json`.
35
43
  - Run the CLI locally and inspect the generated screenshots/report.
36
44
 
@@ -71,7 +79,7 @@ Create a starter flow manifest:
71
79
  npx @trylayout/qa init
72
80
  ```
73
81
 
74
- Start your app with whatever mock flag your project uses:
82
+ Start your app with whatever local QA flag your project uses:
75
83
 
76
84
  ```bash
77
85
  VITE_LAYOUT_QA_MOCKS=1 npm run dev
@@ -111,7 +119,7 @@ Options:
111
119
 
112
120
  ```text
113
121
  --target-url <url> URL of the running frontend to test.
114
- --scenario <name> Mock scenario to activate. Defaults to happy_path.
122
+ --scenario <name> Scenario to activate. Defaults to happy_path.
115
123
  --flows <path> Flow manifest path. Defaults to .layout/qa-flows.json.
116
124
  --out <path> Artifact directory. Defaults to .layout/runs.
117
125
  --viewport <value> Viewport preset or size. Use desktop, tablet, mobile, or WIDTHxHEIGHT. Defaults to desktop.
@@ -241,7 +249,7 @@ Presets:
241
249
 
242
250
  The selected viewport is written to `result.json`, shown in the HTML report, and included in the run directory name.
243
251
 
244
- ## Mock Scenarios
252
+ ## Scenarios
245
253
 
246
254
  Before the app loads, the runner sets:
247
255
 
@@ -250,7 +258,7 @@ localStorage.setItem("layout.qa.scenario", "<scenario>");
250
258
  sessionStorage.setItem("layout.qa.runner", "1");
251
259
  ```
252
260
 
253
- Your app can use `layout.qa.scenario` to switch deterministic mock states:
261
+ Your app can use `layout.qa.scenario` to switch deterministic API/auth response states:
254
262
 
255
263
  - `happy_path`: normal populated data.
256
264
  - `empty`: successful responses with empty states.
@@ -271,14 +279,14 @@ Create a local-only browser QA loop that an agent can run while changing fronten
271
279
  Rules:
272
280
  - Do not add a standalone mock server.
273
281
  - Do not require a hosted Layout service.
274
- - Keep all mock data local to this app.
275
- - Gate mocks behind a local-only env flag such as VITE_LAYOUT_QA_MOCKS=1, NEXT_PUBLIC_LAYOUT_QA_MOCKS=1, or the framework-appropriate equivalent.
276
- - Use localStorage["layout.qa.scenario"] to select at least happy_path, empty, and error mock states.
282
+ - Keep all deterministic response fixtures local to this app.
283
+ - Gate deterministic API/auth responses behind a local-only env flag such as VITE_LAYOUT_QA_MOCKS=1, NEXT_PUBLIC_LAYOUT_QA_MOCKS=1, or the framework-appropriate equivalent.
284
+ - Use localStorage["layout.qa.scenario"] to select at least happy_path, empty, and error response states.
277
285
  - Hide any local QA switcher or debug controls when sessionStorage["layout.qa.runner"] === "1".
278
286
 
279
287
  Implementation:
280
288
  - Add deterministic API fixtures for the highest-value frontend route.
281
- - If the app has a central auth/session abstraction, add a deterministic mock user only when the Layout QA env flag is enabled.
289
+ - If the app has a central auth/session abstraction, add a deterministic QA user only when the Layout QA env flag is enabled.
282
290
  - If auth is scattered or provider-SDK-only, leave a clear note in the PR/code comments and start with public or logged-out flows.
283
291
  - Add .layout/qa-flows.json with one smoke flow for the most important page.
284
292
  - Prefer visible text and stable selectors.
@@ -332,3 +340,7 @@ This package is intentionally small:
332
340
  - It does not perform AI review by itself.
333
341
 
334
342
  Those hosted/reporting layers can be added later without changing the local protocol.
343
+
344
+ ## Feedback
345
+
346
+ Issues and examples are welcome in [GitHub Issues](https://github.com/Layout-App/layout-qa/issues). You can also reach me on X at [@tscepo](https://x.com/tscepo).
@@ -26,7 +26,7 @@ Commands:
26
26
 
27
27
  Options:
28
28
  --target-url <url> URL of the running frontend to test.
29
- --scenario <name> Mock scenario to activate. Defaults to happy_path.
29
+ --scenario <name> Scenario to activate. Defaults to happy_path.
30
30
  --flows <path> Flow manifest path. Defaults to .layout/qa-flows.json.
31
31
  --out <path> Artifact directory. Defaults to .layout/runs.
32
32
  --viewport <value> Viewport preset or size. Use desktop, tablet, mobile, or WIDTHxHEIGHT. Defaults to desktop.
package/build/report.js CHANGED
@@ -57,7 +57,7 @@ function renderStatusBadge(status) {
57
57
  function renderCheckList(result) {
58
58
  return result.checks
59
59
  .map(check => `<li class="row">
60
- <span class="status ${check.passed ? 'passed' : 'failed'}">${check.passed ? 'PASS' : 'FAIL'}</span>
60
+ <span class="status ${check.passed ? 'passed' : 'failed'}">${check.passed ? 'Pass' : 'Fail'}</span>
61
61
  <div>
62
62
  <p class="row-title">${escapeHtml(check.label)}</p>
63
63
  ${check.detail
@@ -123,81 +123,149 @@ function renderReport(input) {
123
123
  <style>
124
124
  :root {
125
125
  color-scheme: light;
126
- --bg: #f7f7f2;
127
- --panel: #fffdf8;
128
- --line: #e4e1d8;
129
- --text: #1b1a17;
130
- --muted: #65635d;
131
- --green: #2f7a45;
132
- --green-bg: #e6f4ea;
133
- --red: #9b2c2c;
134
- --red-bg: #fff0f0;
135
- --amber: #7a5c14;
136
- --amber-bg: #fff5d6;
126
+ --bg: #ffffff;
127
+ --surface: #f5f5f7;
128
+ --surface-strong: #fbfbfd;
129
+ --line: #d2d2d7;
130
+ --line-soft: #e8e8ed;
131
+ --text: #1d1d1f;
132
+ --muted: #6e6e73;
133
+ --blue: #06c;
134
+ --green: #248a3d;
135
+ --red: #d70015;
136
+ --amber: #b25000;
137
137
  }
138
138
  * { box-sizing: border-box; }
139
139
  body {
140
140
  margin: 0;
141
141
  background: var(--bg);
142
142
  color: var(--text);
143
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
144
- line-height: 1.5;
143
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
144
+ line-height: 1.47059;
145
+ -webkit-font-smoothing: antialiased;
146
+ text-rendering: optimizeLegibility;
147
+ }
148
+ main { max-width: 1180px; margin: 0 auto; padding: 56px 28px 72px; }
149
+ header.page {
150
+ display: flex;
151
+ justify-content: space-between;
152
+ gap: 32px;
153
+ align-items: flex-start;
154
+ border-bottom: 1px solid var(--line-soft);
155
+ padding-bottom: 36px;
145
156
  }
146
- main { max-width: 1120px; margin: 0 auto; padding: 32px 24px 56px; }
147
- header.page { display: flex; justify-content: space-between; gap: 24px; align-items: flex-start; border-bottom: 1px solid var(--line); padding-bottom: 24px; }
148
157
  h1, h2, h3, p { margin: 0; }
149
- h1 { font-size: clamp(2rem, 5vw, 4rem); line-height: 1; font-weight: 600; letter-spacing: 0; }
150
- h2 { font-size: 1rem; font-weight: 650; margin-bottom: 12px; }
151
- h3 { font-size: 1rem; font-weight: 650; }
152
- section { margin-top: 28px; }
153
- a { color: inherit; }
154
- .eyebrow { color: var(--muted); font-size: .78rem; font-weight: 650; text-transform: uppercase; letter-spacing: .04em; }
155
- .summary { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; margin-top: 24px; }
158
+ h1 {
159
+ font-size: clamp(2.75rem, 7vw, 5.5rem);
160
+ line-height: .95;
161
+ font-weight: 500;
162
+ letter-spacing: 0;
163
+ }
164
+ h2 { font-size: 1.375rem; line-height: 1.2; font-weight: 500; margin-bottom: 18px; }
165
+ h3 { font-size: 1.0625rem; line-height: 1.25; font-weight: 500; }
166
+ section { margin-top: 42px; }
167
+ a { color: var(--blue); text-decoration: none; }
168
+ a:hover { text-decoration: underline; }
169
+ .eyebrow {
170
+ color: var(--muted);
171
+ font-size: .8125rem;
172
+ font-weight: 500;
173
+ text-transform: uppercase;
174
+ letter-spacing: .04em;
175
+ margin-bottom: 10px;
176
+ }
177
+ .summary {
178
+ display: grid;
179
+ grid-template-columns: repeat(5, minmax(0, 1fr));
180
+ gap: 1px;
181
+ margin-top: 32px;
182
+ border: 1px solid var(--line-soft);
183
+ border-radius: 14px;
184
+ overflow: hidden;
185
+ background: var(--line-soft);
186
+ }
156
187
  .metric, .panel, .step, .issue {
157
- border: 1px solid var(--line);
158
- background: var(--panel);
159
- border-radius: 8px;
188
+ background: var(--surface-strong);
189
+ }
190
+ .metric { padding: 18px 20px; min-width: 0; }
191
+ .metric dt { color: var(--muted); font-size: .8125rem; }
192
+ .metric dd { margin: 5px 0 0; font-weight: 500; overflow-wrap: anywhere; }
193
+ .panel {
194
+ padding: 22px;
195
+ border: 1px solid var(--line-soft);
196
+ border-radius: 14px;
197
+ }
198
+ .stack { display: grid; gap: 0; list-style: none; margin: 0; padding: 0; }
199
+ .row {
200
+ display: grid;
201
+ grid-template-columns: 64px minmax(0, 1fr);
202
+ gap: 16px;
203
+ padding: 14px 0;
204
+ border-top: 1px solid var(--line-soft);
160
205
  }
161
- .metric { padding: 14px; min-width: 0; }
162
- .metric dt { color: var(--muted); font-size: .82rem; }
163
- .metric dd { margin: 4px 0 0; font-weight: 650; overflow-wrap: anywhere; }
164
- .panel { padding: 16px; }
165
- .stack { display: grid; gap: 10px; list-style: none; margin: 0; padding: 0; }
166
- .row { display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 12px; padding: 10px 0; border-top: 1px solid var(--line); }
167
206
  .row:first-child { border-top: 0; padding-top: 0; }
168
207
  .row:last-child { padding-bottom: 0; }
169
- .row-title { font-weight: 650; overflow-wrap: anywhere; }
170
- .muted { color: var(--muted); font-size: .92rem; overflow-wrap: anywhere; }
171
- .detail { margin-top: 8px; color: #3f3d38; overflow-wrap: anywhere; }
208
+ .row-title { font-weight: 500; overflow-wrap: anywhere; }
209
+ .muted { color: var(--muted); font-size: .9375rem; overflow-wrap: anywhere; }
210
+ .detail { margin-top: 10px; color: #424245; overflow-wrap: anywhere; }
172
211
  .break { word-break: break-all; }
173
- .status { font-size: .78rem; font-weight: 750; padding-top: 2px; }
212
+ .status { font-size: .8125rem; font-weight: 500; padding-top: 2px; }
174
213
  .status.passed { color: var(--green); }
175
214
  .status.failed { color: var(--red); }
176
215
  .badge {
177
216
  display: inline-flex;
178
217
  align-items: center;
179
- border-radius: 6px;
180
- border: 1px solid var(--line);
181
- padding: 4px 8px;
182
- font-size: .82rem;
183
- font-weight: 650;
218
+ border-radius: 999px;
219
+ border: 1px solid var(--line-soft);
220
+ padding: 5px 10px;
221
+ font-size: .875rem;
222
+ font-weight: 500;
184
223
  text-transform: capitalize;
185
224
  white-space: nowrap;
225
+ background: var(--surface);
226
+ }
227
+ .badge.passed { color: var(--green); }
228
+ .badge.failed { color: var(--red); }
229
+ .badge.skipped { color: var(--amber); }
230
+ .badge.hero { font-size: 1rem; padding: 8px 14px; }
231
+ .step {
232
+ padding: 22px;
233
+ margin-top: 16px;
234
+ border: 1px solid var(--line-soft);
235
+ border-radius: 14px;
186
236
  }
187
- .badge.passed { color: var(--green); background: var(--green-bg); border-color: #c8e6d0; }
188
- .badge.failed { color: var(--red); background: var(--red-bg); border-color: #f0c9c9; }
189
- .badge.skipped { color: var(--amber); background: var(--amber-bg); border-color: #f0dc9f; }
190
- .badge.hero { font-size: .95rem; padding: 8px 12px; }
191
- .step { padding: 16px; margin-top: 12px; }
192
237
  .step-header { display: flex; justify-content: space-between; gap: 16px; align-items: flex-start; }
193
- .screenshot-link { display: block; margin-top: 14px; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: white; }
238
+ .screenshot-link {
239
+ display: block;
240
+ margin-top: 18px;
241
+ border: 1px solid var(--line);
242
+ border-radius: 12px;
243
+ overflow: hidden;
244
+ background: white;
245
+ box-shadow: 0 12px 36px rgba(0, 0, 0, .06);
246
+ }
247
+ .screenshot-link:hover { text-decoration: none; }
194
248
  img { display: block; width: 100%; height: auto; }
195
- .issue { padding: 12px; border-color: #f0c9c9; background: #fff8f8; }
196
- .next { border-color: #d9d6cb; }
249
+ .issue {
250
+ padding: 16px;
251
+ border: 1px solid #ffd7d9;
252
+ border-radius: 12px;
253
+ background: #fff7f7;
254
+ }
255
+ .next { background: var(--surface); }
197
256
  .empty { color: var(--muted); }
198
- .footer { margin-top: 28px; color: var(--muted); font-size: .9rem; }
257
+ .footer {
258
+ margin-top: 42px;
259
+ padding-top: 20px;
260
+ border-top: 1px solid var(--line-soft);
261
+ color: var(--muted);
262
+ font-size: .9375rem;
263
+ overflow-wrap: anywhere;
264
+ }
265
+ ul:not(.stack) { margin: 14px 0 0; padding-left: 1.2rem; color: #424245; }
266
+ li + li { margin-top: 6px; }
199
267
  @media (max-width: 760px) {
200
- main { padding: 24px 16px 40px; }
268
+ main { padding: 34px 18px 48px; }
201
269
  header.page { display: grid; }
202
270
  .summary { grid-template-columns: 1fr; }
203
271
  }
package/build/runner.js CHANGED
@@ -81,7 +81,7 @@ function buildChecks(input) {
81
81
  },
82
82
  {
83
83
  id: 'scenario_ready',
84
- label: 'Mock scenario is available',
84
+ label: 'Scenario flag is available',
85
85
  passed: scenarioReady,
86
86
  detail: input.controlsPresent
87
87
  ? `Layout QA controls detected; requested ${input.scenario}.`
@@ -126,11 +126,11 @@ function buildNextAction(input) {
126
126
  return {
127
127
  category: 'target_unreachable',
128
128
  title: 'Target URL did not load cleanly',
129
- detail: 'Layout could not reach the target page well enough to evaluate mock states.',
129
+ detail: 'Layout could not reach the target page well enough to evaluate deterministic response states.',
130
130
  docsPath: flows_1.FLOW_MANIFEST_PATH,
131
131
  nextSteps: [
132
132
  'Start the app or deploy preview URL before running Layout QA.',
133
- 'Use the URL where the frontend is served with the Layout mock env flag enabled.',
133
+ 'Use the URL where the frontend is served with the Layout QA env flag enabled.',
134
134
  'Retry the same scenario after the target URL is reachable from the runner.',
135
135
  ],
136
136
  };
@@ -138,13 +138,13 @@ function buildNextAction(input) {
138
138
  if (failedCheckIds.has('scenario_ready')) {
139
139
  return {
140
140
  category: 'fixtures',
141
- title: 'Mock scenario was not active',
142
- detail: 'The page loaded, but Layout could not confirm that the requested mock scenario was available.',
141
+ title: 'Scenario flag was not active',
142
+ detail: 'The page loaded, but Layout could not confirm that the requested scenario was available.',
143
143
  docsPath: flows_1.FLOW_MANIFEST_PATH,
144
144
  nextSteps: [
145
- 'Confirm the target is running with the Layout mock env flag set to 1.',
145
+ 'Confirm the target is running with the Layout QA env flag set to 1.',
146
146
  'Check that the app reads localStorage["layout.qa.scenario"] before API calls run.',
147
- `Review ${flows_1.FLOW_MANIFEST_PATH}, the Layout QA docs, and the fixture file for missing handlers.`,
147
+ `Review ${flows_1.FLOW_MANIFEST_PATH}, the Layout QA docs, and the API/auth response fixtures for missing handlers.`,
148
148
  ],
149
149
  };
150
150
  }
@@ -154,11 +154,11 @@ function buildNextAction(input) {
154
154
  category: 'flow',
155
155
  title: 'Flow step needs review',
156
156
  detail: failedStep?.detail ||
157
- 'The target loaded with mocks, but a declared QA flow step failed.',
157
+ 'The target loaded with deterministic responses, but a declared QA flow step failed.',
158
158
  docsPath: flows_1.FLOW_MANIFEST_PATH,
159
159
  nextSteps: [
160
160
  `Inspect ${flows_1.FLOW_MANIFEST_PATH} and confirm the failing step still matches the app UI.`,
161
- 'Update selectors, visible text assertions, or scenario fixtures so the flow follows real user behavior.',
161
+ 'Update selectors, visible text assertions, or scenario responses so the flow follows real user behavior.',
162
162
  `Use ${flows_1.QA_DOCS_URL} for the supported flow step schema.`,
163
163
  ],
164
164
  };
@@ -169,24 +169,24 @@ function buildNextAction(input) {
169
169
  return {
170
170
  category: 'browser_errors',
171
171
  title: 'Browser errors need review',
172
- detail: 'The target loaded with mocks, but browser errors or failed requests were observed.',
172
+ detail: 'The target loaded with deterministic responses, but browser errors or failed requests were observed.',
173
173
  docsPath: flows_1.FLOW_MANIFEST_PATH,
174
174
  nextSteps: [
175
175
  'Inspect the issues captured on this QA run.',
176
- 'Add or correct fixtures for unhandled frontend API requests.',
177
- 'Fix app code that throws under the selected mock scenario, then rerun.',
176
+ 'Add or correct fixtures for unhandled frontend API/auth requests.',
177
+ 'Fix app code that throws under the selected scenario, then rerun.',
178
178
  ],
179
179
  };
180
180
  }
181
181
  if (appearsToBePublicOrAuthSurface(input.bodyTextSample)) {
182
182
  return {
183
183
  category: 'auth_boundary',
184
- title: 'Public surface reached; wire mock auth next',
185
- detail: 'The run passed the basic mock-browser checks, but the page appears to be a logged-out or public surface. Authenticated flows need a mockable auth boundary before Layout can test them end to end.',
184
+ title: 'Public surface reached; wire deterministic auth next',
185
+ detail: 'The run passed the basic browser checks, but the page appears to be a logged-out or public surface. Authenticated flows need a deterministic auth boundary before Layout can test them end to end.',
186
186
  docsPath: flows_1.FLOW_MANIFEST_PATH,
187
187
  nextSteps: [
188
- `Use ${flows_1.FLOW_MANIFEST_PATH} and the Layout QA docs to add or confirm a central mock auth boundary.`,
189
- 'Expose a deterministic mock user/session only when the Layout mock env flag is enabled.',
188
+ `Use ${flows_1.FLOW_MANIFEST_PATH} and the Layout QA docs to add or confirm a central auth boundary for QA runs.`,
189
+ 'Expose a deterministic user/session only when the Layout QA env flag is enabled.',
190
190
  'Point the next QA run at an authenticated route and rerun happy_path, empty, and error scenarios.',
191
191
  ],
192
192
  };
@@ -194,11 +194,11 @@ function buildNextAction(input) {
194
194
  return {
195
195
  category: 'ready',
196
196
  title: 'Ready for deeper flow coverage',
197
- detail: 'The target loaded with the requested mock scenario and no basic browser issues were detected.',
197
+ detail: 'The target loaded with the requested scenario and no basic browser issues were detected.',
198
198
  docsPath: flows_1.FLOW_MANIFEST_PATH,
199
199
  nextSteps: [
200
200
  `Add route-specific Playwright-style flow steps to ${flows_1.FLOW_MANIFEST_PATH} for the highest-value user path.`,
201
- 'Expand fixtures for any API calls encountered by that flow.',
201
+ 'Expand deterministic API/auth responses for any requests encountered by that flow.',
202
202
  'Run the same flow across happy_path, empty, and error scenarios.',
203
203
  ],
204
204
  };
@@ -635,7 +635,7 @@ function buildRunnerErrorResult(message, viewport = viewports_1.DEFAULT_VIEWPORT
635
635
  docsPath: flows_1.FLOW_MANIFEST_PATH,
636
636
  nextSteps: [
637
637
  'Confirm the target URL is reachable by the runner.',
638
- 'Confirm the app is served with the Layout mock env flag enabled.',
638
+ 'Confirm the app is served with the Layout QA env flag enabled.',
639
639
  'Retry after the target loads consistently in a browser.',
640
640
  `Viewport used for this run: ${(0, viewports_1.formatViewport)(viewport)}.`,
641
641
  ],
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trylayout/qa",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Local browser QA runner and HTML reports for AI-built frontends.",
5
5
  "license": "MIT",
6
6
  "author": "Layout",
@@ -26,6 +26,7 @@
26
26
  },
27
27
  "files": [
28
28
  "build",
29
+ "docs/assets/layout-qa-report.png",
29
30
  "README.md",
30
31
  "LICENSE"
31
32
  ],
@@ -38,6 +39,7 @@
38
39
  "scripts": {
39
40
  "build": "tsc",
40
41
  "check": "tsc --noEmit",
42
+ "publish:alias": "cd packages/layout-qa && npm publish --access public",
41
43
  "prepack": "npm run build"
42
44
  },
43
45
  "dependencies": {