@nzila/sdk 0.1.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 +70 -0
- package/dist/deliver.d.ts +26 -0
- package/dist/deliver.d.ts.map +1 -0
- package/dist/deliver.js +52 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/jest-reporter.d.ts +41 -0
- package/dist/jest-reporter.d.ts.map +1 -0
- package/dist/jest-reporter.js +68 -0
- package/dist/mocha-reporter.d.ts +24 -0
- package/dist/mocha-reporter.d.ts.map +1 -0
- package/dist/mocha-reporter.js +58 -0
- package/dist/react/index.d.ts +11 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +10 -0
- package/dist/react/nzila-feature-boundary.d.ts +26 -0
- package/dist/react/nzila-feature-boundary.d.ts.map +1 -0
- package/dist/react/nzila-feature-boundary.js +31 -0
- package/dist/react/use-nzila-feature.d.ts +11 -0
- package/dist/react/use-nzila-feature.d.ts.map +1 -0
- package/dist/react/use-nzila-feature.js +63 -0
- package/dist/runtime/client.d.ts +36 -0
- package/dist/runtime/client.d.ts.map +1 -0
- package/dist/runtime/client.js +145 -0
- package/dist/runtime/extract-error.d.ts +12 -0
- package/dist/runtime/extract-error.d.ts.map +1 -0
- package/dist/runtime/extract-error.js +24 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/types.d.ts +46 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +1 -0
- package/dist/silent.d.ts +9 -0
- package/dist/silent.d.ts.map +1 -0
- package/dist/silent.js +22 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/vitest-reporter.d.ts +37 -0
- package/dist/vitest-reporter.d.ts.map +1 -0
- package/dist/vitest-reporter.js +95 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @nzila/sdk
|
|
2
|
+
|
|
3
|
+
Send test runs to Nzila and trace **live** errors on the same **feature** names as your Vitest suites.
|
|
4
|
+
|
|
5
|
+
**Silent by design:** webhook and runtime delivery never throw, never log to the console, and skip work when URL/key are missing — your app and CI are unaffected if Nzila is down or misconfigured.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @nzila/sdk
|
|
11
|
+
# Vitest projects also need vitest as a peer
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Exports
|
|
15
|
+
|
|
16
|
+
| Path | Purpose |
|
|
17
|
+
|------|---------|
|
|
18
|
+
| `@nzila/sdk` | Webhook delivery, reporters, runtime tracing |
|
|
19
|
+
| `@nzila/sdk/vitest` | Vitest reporter |
|
|
20
|
+
| `@nzila/sdk/jest` | Jest reporter |
|
|
21
|
+
| `@nzila/sdk/mocha` | Mocha reporter |
|
|
22
|
+
| `@nzila/sdk/react` | `useNzilaFeature`, error boundary |
|
|
23
|
+
| `@nzila/sdk/runtime` | `initNzilaRuntime`, `traceNzilaFeature` |
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
**Tests (CI):**
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import NzilaVitestReporter from "@nzila/sdk/vitest";
|
|
31
|
+
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
test: {
|
|
34
|
+
reporters: [
|
|
35
|
+
"default",
|
|
36
|
+
new NzilaVitestReporter({
|
|
37
|
+
webhookUrl: process.env.NZILA_WEBHOOK_URL!,
|
|
38
|
+
apiKey: process.env.NZILA_API_KEY!,
|
|
39
|
+
appName: "my-app",
|
|
40
|
+
mapFeature: (path) => path[0] ?? "general",
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**React (runtime):**
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { initNzilaRuntime } from "@nzila/sdk";
|
|
51
|
+
import { useNzilaFeature } from "@nzila/sdk/react";
|
|
52
|
+
|
|
53
|
+
initNzilaRuntime({ signalsUrl: "…/api/signals/runtime", apiKey: "nzl_…" });
|
|
54
|
+
|
|
55
|
+
function Checkout() {
|
|
56
|
+
const nzila = useNzilaFeature("Checkout");
|
|
57
|
+
// nzila.captureError(err) in catch; nzila.runApi("label", () => fetch(...))
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Backend:**
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { initNzilaRuntime, traceNzilaFeature } from "@nzila/sdk";
|
|
65
|
+
|
|
66
|
+
const nzila = traceNzilaFeature("Checkout");
|
|
67
|
+
await nzila.runApi("charge", () => charge());
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
See JSDoc on each export in `src/` and the monorepo guide [examples/COMPLEX-USAGE.md](../../examples/COMPLEX-USAGE.md).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NzilaRunPayload } from "./types.js";
|
|
2
|
+
/** Options for {@link sendNzilaRun}. */
|
|
3
|
+
export type DeliverOptions = {
|
|
4
|
+
/** Full URL to `POST /api/webhooks/tests` on your Nzila host. */
|
|
5
|
+
webhookUrl: string;
|
|
6
|
+
/** Project API key (`Authorization: Bearer …`). */
|
|
7
|
+
apiKey: string;
|
|
8
|
+
/** Retry count on non-2xx (default 3). */
|
|
9
|
+
maxRetries?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* POST a completed test run payload to Nzila.
|
|
13
|
+
* Never throws and never logs — failures are ignored so CI and apps stay unaffected.
|
|
14
|
+
*
|
|
15
|
+
* @param payload - Run metadata and `tests[]` from your runner.
|
|
16
|
+
* @param options - Webhook URL and API key.
|
|
17
|
+
*/
|
|
18
|
+
export declare function sendNzilaRun(payload: NzilaRunPayload, options: DeliverOptions): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve dashboard locale for summaries from explicit value or `NZILA_LOCALE` env.
|
|
21
|
+
*
|
|
22
|
+
* @param explicit - Reporter option or caller override.
|
|
23
|
+
* @returns A supported locale or `undefined` if unset/invalid.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveLocale(explicit?: string): "en" | "pt" | "fr" | "zh" | undefined;
|
|
26
|
+
//# sourceMappingURL=deliver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../src/deliver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlD,wCAAwC;AACxC,MAAM,MAAM,cAAc,GAAG;IAC3B,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAOvC"}
|
package/dist/deliver.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { nzilaSafeAsync } from "./silent.js";
|
|
2
|
+
/**
|
|
3
|
+
* POST a completed test run payload to Nzila.
|
|
4
|
+
* Never throws and never logs — failures are ignored so CI and apps stay unaffected.
|
|
5
|
+
*
|
|
6
|
+
* @param payload - Run metadata and `tests[]` from your runner.
|
|
7
|
+
* @param options - Webhook URL and API key.
|
|
8
|
+
*/
|
|
9
|
+
export async function sendNzilaRun(payload, options) {
|
|
10
|
+
await nzilaSafeAsync(async () => {
|
|
11
|
+
const webhookUrl = options?.webhookUrl?.trim();
|
|
12
|
+
const apiKey = options?.apiKey?.trim();
|
|
13
|
+
if (!webhookUrl || !apiKey)
|
|
14
|
+
return;
|
|
15
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
16
|
+
let attempt = 0;
|
|
17
|
+
while (attempt < maxRetries) {
|
|
18
|
+
attempt += 1;
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch(webhookUrl, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: {
|
|
23
|
+
Authorization: `Bearer ${apiKey}`,
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify(payload),
|
|
27
|
+
});
|
|
28
|
+
if (res.ok || res.status === 200 || res.status === 202) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* network — retry */
|
|
34
|
+
}
|
|
35
|
+
await new Promise((r) => setTimeout(r, attempt * 1000));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve dashboard locale for summaries from explicit value or `NZILA_LOCALE` env.
|
|
41
|
+
*
|
|
42
|
+
* @param explicit - Reporter option or caller override.
|
|
43
|
+
* @returns A supported locale or `undefined` if unset/invalid.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveLocale(explicit) {
|
|
46
|
+
const fromEnv = process.env?.NZILA_LOCALE;
|
|
47
|
+
const value = explicit ?? fromEnv;
|
|
48
|
+
if (value === "en" || value === "pt" || value === "fr" || value === "zh") {
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nzila/sdk — test webhooks (Vitest/Jest/Mocha) and runtime feature tracing.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export { NZILA_TEST_FRAMEWORKS, type NzilaTestFramework, type NzilaTestStatus, type NzilaLocale, type NzilaTestCase, type NzilaRunPayload, type NzilaClientOptions, } from "./types.js";
|
|
7
|
+
export { sendNzilaRun, resolveLocale, type DeliverOptions } from "./deliver.js";
|
|
8
|
+
export { initNzilaRuntime, isNzilaRuntimeReady, traceNzilaFeature, createFeatureHandle, reportFeatureError, captureFeatureError, extractErrorFields, type NzilaFeatureHandle, type NzilaRuntimeConfig, type NzilaRuntimeSignal, } from "./runtime/index.js";
|
|
9
|
+
export { default as NzilaVitestReporter } from "./vitest-reporter.js";
|
|
10
|
+
export { default as NzilaJestReporter } from "./jest-reporter.js";
|
|
11
|
+
export { createMochaNzilaReporter, default as mochaNzilaReporterFactory } from "./mocha-reporter.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nzila/sdk — test webhooks (Vitest/Jest/Mocha) and runtime feature tracing.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export { NZILA_TEST_FRAMEWORKS, } from "./types.js";
|
|
7
|
+
export { sendNzilaRun, resolveLocale } from "./deliver.js";
|
|
8
|
+
export { initNzilaRuntime, isNzilaRuntimeReady, traceNzilaFeature, createFeatureHandle, reportFeatureError, captureFeatureError, extractErrorFields, } from "./runtime/index.js";
|
|
9
|
+
export { default as NzilaVitestReporter } from "./vitest-reporter.js";
|
|
10
|
+
export { default as NzilaJestReporter } from "./jest-reporter.js";
|
|
11
|
+
export { createMochaNzilaReporter, default as mochaNzilaReporterFactory } from "./mocha-reporter.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { NzilaClientOptions } from "./types.js";
|
|
2
|
+
type JestAssertion = {
|
|
3
|
+
failureMessages?: string[];
|
|
4
|
+
};
|
|
5
|
+
type JestTestResult = {
|
|
6
|
+
title: string;
|
|
7
|
+
fullName: string;
|
|
8
|
+
status: "passed" | "failed" | "pending" | "skipped" | "todo" | "disabled";
|
|
9
|
+
duration?: number | null;
|
|
10
|
+
failureMessages?: string[];
|
|
11
|
+
ancestorTitles: string[];
|
|
12
|
+
};
|
|
13
|
+
type JestAggregated = {
|
|
14
|
+
testResults: {
|
|
15
|
+
testFilePath: string;
|
|
16
|
+
perfStats: {
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
};
|
|
20
|
+
testResults: JestTestResult[];
|
|
21
|
+
}[];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Jest reporter: aggregates results and POSTs one {@link sendNzilaRun} payload on `onRunComplete`.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```js
|
|
28
|
+
* reporters: [["@nzila/sdk/jest", { webhookUrl, apiKey, appName: "my-app" }]],
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export default class NzilaJestReporter {
|
|
32
|
+
private readonly options;
|
|
33
|
+
private readonly runId;
|
|
34
|
+
private sent;
|
|
35
|
+
constructor(options: NzilaClientOptions, _globalConfig?: unknown);
|
|
36
|
+
onRunComplete(_contexts: unknown, results: JestAggregated): void;
|
|
37
|
+
private mapStatus;
|
|
38
|
+
}
|
|
39
|
+
/** Unused — satisfies older Jest typings */
|
|
40
|
+
export type { JestAssertion };
|
|
41
|
+
//# sourceMappingURL=jest-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jest-reporter.d.ts","sourceRoot":"","sources":["../src/jest-reporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAInB,MAAM,YAAY,CAAC;AAEpB,KAAK,aAAa,GAAG;IAAE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEpD,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;IAC1E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,WAAW,EAAE;QACX,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;QAC1C,WAAW,EAAE,cAAc,EAAE,CAAC;KAC/B,EAAE,CAAC;CACL,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAiB;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,IAAI,CAAS;gBAET,OAAO,EAAE,kBAAkB,EAAE,aAAa,CAAC,EAAE,OAAO;IAKhE,aAAa,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc;IA8CzD,OAAO,CAAC,SAAS;CAQlB;AAED,4CAA4C;AAC5C,YAAY,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { sendNzilaRun, resolveLocale } from "./deliver.js";
|
|
3
|
+
/**
|
|
4
|
+
* Jest reporter: aggregates results and POSTs one {@link sendNzilaRun} payload on `onRunComplete`.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```js
|
|
8
|
+
* reporters: [["@nzila/sdk/jest", { webhookUrl, apiKey, appName: "my-app" }]],
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export default class NzilaJestReporter {
|
|
12
|
+
constructor(options, _globalConfig) {
|
|
13
|
+
this.sent = false;
|
|
14
|
+
this.options = options;
|
|
15
|
+
this.runId = options.runId ?? randomUUID();
|
|
16
|
+
}
|
|
17
|
+
onRunComplete(_contexts, results) {
|
|
18
|
+
if (this.sent)
|
|
19
|
+
return;
|
|
20
|
+
this.sent = true;
|
|
21
|
+
const framework = this.options.framework ?? "jest";
|
|
22
|
+
const tests = [];
|
|
23
|
+
let startedAt = Date.now();
|
|
24
|
+
let finishedAt = Date.now();
|
|
25
|
+
for (const file of results.testResults ?? []) {
|
|
26
|
+
startedAt = Math.min(startedAt, file.perfStats.start);
|
|
27
|
+
finishedAt = Math.max(finishedAt, file.perfStats.end);
|
|
28
|
+
for (const test of file.testResults) {
|
|
29
|
+
const describePath = test.ancestorTitles ?? [];
|
|
30
|
+
tests.push({
|
|
31
|
+
appName: this.options.appName,
|
|
32
|
+
framework,
|
|
33
|
+
startedAt: new Date(file.perfStats.start).toISOString(),
|
|
34
|
+
finishedAt: new Date(file.perfStats.end).toISOString(),
|
|
35
|
+
describePath,
|
|
36
|
+
feature: describePath[0],
|
|
37
|
+
testName: test.title,
|
|
38
|
+
status: this.mapStatus(test.status),
|
|
39
|
+
duration: test.duration ?? 0,
|
|
40
|
+
errors: (test.failureMessages ?? []).map((message) => ({ message })),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!tests.length)
|
|
45
|
+
return;
|
|
46
|
+
const locale = resolveLocale(this.options.locale);
|
|
47
|
+
const payload = {
|
|
48
|
+
runId: this.runId,
|
|
49
|
+
appName: this.options.appName,
|
|
50
|
+
framework,
|
|
51
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
52
|
+
finishedAt: new Date(finishedAt).toISOString(),
|
|
53
|
+
tests,
|
|
54
|
+
...(locale ? { locale } : {}),
|
|
55
|
+
};
|
|
56
|
+
void sendNzilaRun(payload, this.options);
|
|
57
|
+
}
|
|
58
|
+
mapStatus(status) {
|
|
59
|
+
if (status === "passed")
|
|
60
|
+
return "passed";
|
|
61
|
+
if (status === "failed")
|
|
62
|
+
return "failed";
|
|
63
|
+
if (status === "skipped" || status === "pending" || status === "todo" || status === "disabled") {
|
|
64
|
+
return status === "pending" || status === "todo" ? "pending" : "skipped";
|
|
65
|
+
}
|
|
66
|
+
return "pending";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { NzilaClientOptions } from "./types.js";
|
|
2
|
+
type MochaTest = {
|
|
3
|
+
title: string;
|
|
4
|
+
fullTitle: () => string;
|
|
5
|
+
parent?: MochaTest & {
|
|
6
|
+
title: string;
|
|
7
|
+
parent?: MochaTest;
|
|
8
|
+
};
|
|
9
|
+
duration?: number;
|
|
10
|
+
err?: Error;
|
|
11
|
+
state?: "passed" | "failed";
|
|
12
|
+
pending?: boolean;
|
|
13
|
+
};
|
|
14
|
+
type MochaRunner = {
|
|
15
|
+
on(event: "pass" | "fail" | "pending" | "end", fn: (test: MochaTest) => void): void;
|
|
16
|
+
stats?: {
|
|
17
|
+
start?: Date;
|
|
18
|
+
end?: Date;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export type MochaNzilaOptions = NzilaClientOptions;
|
|
22
|
+
export declare function createMochaNzilaReporter(options: MochaNzilaOptions): (runner: MochaRunner) => void;
|
|
23
|
+
export default createMochaNzilaReporter;
|
|
24
|
+
//# sourceMappingURL=mocha-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mocha-reporter.d.ts","sourceRoot":"","sources":["../src/mocha-reporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAmD,MAAM,YAAY,CAAC;AAEtG,KAAK,SAAS,GAAG;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,SAAS,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,CAAA;KAAE,CAAC;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,KAAK,CAAC;IACZ,KAAK,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI,CAAC;IACpF,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,IAAI,CAAC;QAAC,GAAG,CAAC,EAAE,IAAI,CAAA;KAAE,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAEnD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,iBAAiB,IAC9B,QAAQ,WAAW,UA4DvD;AAED,eAAe,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { sendNzilaRun, resolveLocale } from "./deliver.js";
|
|
3
|
+
export function createMochaNzilaReporter(options) {
|
|
4
|
+
return function mochaNzilaReporter(runner) {
|
|
5
|
+
const runId = options.runId ?? randomUUID();
|
|
6
|
+
const framework = options.framework ?? "mocha";
|
|
7
|
+
const cases = [];
|
|
8
|
+
const startedAt = new Date();
|
|
9
|
+
const describePath = (test) => {
|
|
10
|
+
const parts = [];
|
|
11
|
+
let node = test.parent;
|
|
12
|
+
while (node?.title) {
|
|
13
|
+
parts.unshift(node.title);
|
|
14
|
+
node = node.parent;
|
|
15
|
+
}
|
|
16
|
+
return parts;
|
|
17
|
+
};
|
|
18
|
+
const push = (test, status) => {
|
|
19
|
+
const path = describePath(test);
|
|
20
|
+
const finishedAt = new Date().toISOString();
|
|
21
|
+
cases.push({
|
|
22
|
+
appName: options.appName,
|
|
23
|
+
framework,
|
|
24
|
+
startedAt: startedAt.toISOString(),
|
|
25
|
+
finishedAt,
|
|
26
|
+
describePath: path,
|
|
27
|
+
feature: path[0],
|
|
28
|
+
testName: test.title,
|
|
29
|
+
status,
|
|
30
|
+
duration: test.duration ?? 0,
|
|
31
|
+
errors: test.err != null
|
|
32
|
+
? [{ message: test.err.message ?? String(test.err), stack: test.err.stack }]
|
|
33
|
+
: [],
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
runner.on("pass", (test) => push(test, "passed"));
|
|
37
|
+
runner.on("fail", (test) => push(test, "failed"));
|
|
38
|
+
runner.on("pending", (test) => push(test, "skipped"));
|
|
39
|
+
runner.on("end", () => {
|
|
40
|
+
if (!cases.length)
|
|
41
|
+
return;
|
|
42
|
+
const end = runner.stats?.end ?? new Date();
|
|
43
|
+
const start = runner.stats?.start ?? startedAt;
|
|
44
|
+
const locale = resolveLocale(options.locale);
|
|
45
|
+
const payload = {
|
|
46
|
+
runId,
|
|
47
|
+
appName: options.appName,
|
|
48
|
+
framework,
|
|
49
|
+
startedAt: start.toISOString(),
|
|
50
|
+
finishedAt: end.toISOString(),
|
|
51
|
+
tests: cases,
|
|
52
|
+
...(locale ? { locale } : {}),
|
|
53
|
+
};
|
|
54
|
+
void sendNzilaRun(payload, options);
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export default createMochaNzilaReporter;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React bindings: {@link useNzilaFeature} and {@link NzilaFeatureBoundary}.
|
|
3
|
+
* Call {@link initNzilaRuntime} once in your app entry (re-exported here).
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
export { useNzilaFeature, type UseNzilaFeatureOptions } from "./use-nzila-feature.js";
|
|
8
|
+
export { NzilaFeatureBoundary } from "./nzila-feature-boundary.js";
|
|
9
|
+
export { initNzilaRuntime, reportFeatureError, captureFeatureError, } from "../runtime/client.js";
|
|
10
|
+
export { extractErrorFields } from "../runtime/extract-error.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React bindings: {@link useNzilaFeature} and {@link NzilaFeatureBoundary}.
|
|
3
|
+
* Call {@link initNzilaRuntime} once in your app entry (re-exported here).
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
export { useNzilaFeature } from "./use-nzila-feature.js";
|
|
8
|
+
export { NzilaFeatureBoundary } from "./nzila-feature-boundary.js";
|
|
9
|
+
export { initNzilaRuntime, reportFeatureError, captureFeatureError, } from "../runtime/client.js";
|
|
10
|
+
export { extractErrorFields } from "../runtime/extract-error.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { NzilaFeatureHandle } from "../runtime/types.js";
|
|
3
|
+
type Props = {
|
|
4
|
+
feature: string;
|
|
5
|
+
nzila: NzilaFeatureHandle;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
fallback?: React.ReactNode;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Optional: wrap children to report React render errors for this feature.
|
|
11
|
+
* Pair with useNzilaFeature(feature) on the parent.
|
|
12
|
+
*/
|
|
13
|
+
export declare class NzilaFeatureBoundary extends React.Component<Props, {
|
|
14
|
+
error: Error | null;
|
|
15
|
+
}> {
|
|
16
|
+
state: {
|
|
17
|
+
error: Error | null;
|
|
18
|
+
};
|
|
19
|
+
static getDerivedStateFromError(error: Error): {
|
|
20
|
+
error: Error;
|
|
21
|
+
};
|
|
22
|
+
componentDidCatch(error: Error, info: React.ErrorInfo): void;
|
|
23
|
+
render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null | undefined;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=nzila-feature-boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nzila-feature-boundary.d.ts","sourceRoot":"","sources":["../../src/react/nzila-feature-boundary.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,KAAK,KAAK,GAAG;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,KAAK,CAAC,SAAS,CACvD,KAAK,EACL;IAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CAAE,CACxB;IACC,KAAK;eAAoB,KAAK,GAAG,IAAI;MAAG;IAExC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK;;;IAI5C,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS;IASrD,MAAM;CAYP"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { nzilaSafe } from "../silent.js";
|
|
5
|
+
/**
|
|
6
|
+
* Optional: wrap children to report React render errors for this feature.
|
|
7
|
+
* Pair with useNzilaFeature(feature) on the parent.
|
|
8
|
+
*/
|
|
9
|
+
export class NzilaFeatureBoundary extends React.Component {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
this.state = { error: null };
|
|
13
|
+
}
|
|
14
|
+
static getDerivedStateFromError(error) {
|
|
15
|
+
return { error };
|
|
16
|
+
}
|
|
17
|
+
componentDidCatch(error, info) {
|
|
18
|
+
nzilaSafe(() => {
|
|
19
|
+
this.props?.nzila?.captureError(error, {
|
|
20
|
+
source: "react.error_boundary",
|
|
21
|
+
componentStack: info?.componentStack,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
render() {
|
|
26
|
+
if (this.state.error) {
|
|
27
|
+
return (this.props.fallback ?? (_jsxs("div", { role: "alert", "data-nzila-feature": this.props.feature, children: ["Something went wrong in ", this.props.feature, "."] })));
|
|
28
|
+
}
|
|
29
|
+
return this.props.children;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NzilaFeatureHandle } from "../runtime/types.js";
|
|
2
|
+
export type UseNzilaFeatureOptions = {
|
|
3
|
+
/** Report unhandled errors/rejections while this component is mounted (default true). */
|
|
4
|
+
captureUnhandled?: boolean;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Call once at the top of a React component that maps to a tested Nzila feature.
|
|
8
|
+
* Safe when {@link initNzilaRuntime} was not called — telemetry is skipped silently.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useNzilaFeature(feature: string, options?: UseNzilaFeatureOptions): NzilaFeatureHandle;
|
|
11
|
+
//# sourceMappingURL=use-nzila-feature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-nzila-feature.d.ts","sourceRoot":"","sources":["../../src/react/use-nzila-feature.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,MAAM,MAAM,sBAAsB,GAAG;IACnC,yFAAyF;IACzF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,sBAAsB,GAC/B,kBAAkB,CAoEpB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
3
|
+
import { nzilaSafe } from "../silent.js";
|
|
4
|
+
import { createFeatureHandle, getRenderLoopLimits, isNzilaRuntimeReady, } from "../runtime/client.js";
|
|
5
|
+
/**
|
|
6
|
+
* Call once at the top of a React component that maps to a tested Nzila feature.
|
|
7
|
+
* Safe when {@link initNzilaRuntime} was not called — telemetry is skipped silently.
|
|
8
|
+
*/
|
|
9
|
+
export function useNzilaFeature(feature, options) {
|
|
10
|
+
const nzila = useMemo(() => createFeatureHandle(feature), [feature]);
|
|
11
|
+
const runtimeReady = isNzilaRuntimeReady();
|
|
12
|
+
const captureUnhandled = options?.captureUnhandled !== false;
|
|
13
|
+
const loopReportedRef = useRef(false);
|
|
14
|
+
const renderWindowRef = useRef({ count: 0, startedAt: 0 });
|
|
15
|
+
const { threshold, windowMs } = getRenderLoopLimits();
|
|
16
|
+
const now = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
17
|
+
if (runtimeReady) {
|
|
18
|
+
if (renderWindowRef.current.startedAt === 0) {
|
|
19
|
+
renderWindowRef.current = { count: 1, startedAt: now };
|
|
20
|
+
}
|
|
21
|
+
else if (now - renderWindowRef.current.startedAt > windowMs) {
|
|
22
|
+
renderWindowRef.current = { count: 1, startedAt: now };
|
|
23
|
+
loopReportedRef.current = false;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
renderWindowRef.current.count += 1;
|
|
27
|
+
if (!loopReportedRef.current &&
|
|
28
|
+
renderWindowRef.current.count >= threshold) {
|
|
29
|
+
loopReportedRef.current = true;
|
|
30
|
+
nzilaSafe(() => {
|
|
31
|
+
nzila.reportUi("render_loop", {
|
|
32
|
+
renderCount: renderWindowRef.current.count,
|
|
33
|
+
windowMs,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const onError = useCallback((event) => {
|
|
40
|
+
nzilaSafe(() => {
|
|
41
|
+
nzila.captureError(event?.error ?? event?.message, {
|
|
42
|
+
source: "window.error",
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}, [nzila]);
|
|
46
|
+
const onRejection = useCallback((event) => {
|
|
47
|
+
nzilaSafe(() => {
|
|
48
|
+
nzila.captureError(event?.reason, { source: "unhandledrejection" });
|
|
49
|
+
});
|
|
50
|
+
}, [nzila]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!captureUnhandled || !runtimeReady || typeof window === "undefined") {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
window.addEventListener("error", onError);
|
|
56
|
+
window.addEventListener("unhandledrejection", onRejection);
|
|
57
|
+
return () => {
|
|
58
|
+
window.removeEventListener("error", onError);
|
|
59
|
+
window.removeEventListener("unhandledrejection", onRejection);
|
|
60
|
+
};
|
|
61
|
+
}, [captureUnhandled, runtimeReady, onError, onRejection]);
|
|
62
|
+
return nzila;
|
|
63
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { NzilaFeatureHandle, NzilaRuntimeConfig, NzilaRuntimeSignal } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Configure runtime signal delivery. Call once before
|
|
4
|
+
* {@link traceNzilaFeature} or {@link useNzilaFeature}.
|
|
5
|
+
*/
|
|
6
|
+
export declare function initNzilaRuntime(config: NzilaRuntimeConfig): void;
|
|
7
|
+
/** True when {@link initNzilaRuntime} was called with a usable URL and key. */
|
|
8
|
+
export declare function isNzilaRuntimeReady(): boolean;
|
|
9
|
+
/** @internal — no throw; returns null if not configured. */
|
|
10
|
+
export declare function getNzilaRuntime(): NzilaRuntimeConfig | null;
|
|
11
|
+
/**
|
|
12
|
+
* Queue a signal (batched flush to `/api/signals/runtime`).
|
|
13
|
+
* No-op when runtime is not configured.
|
|
14
|
+
*/
|
|
15
|
+
export declare function enqueueSignal(signal: NzilaRuntimeSignal): void;
|
|
16
|
+
/**
|
|
17
|
+
* Create a handle bound to a feature name (low-level).
|
|
18
|
+
* Usually use {@link traceNzilaFeature} or the React hook instead.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createFeatureHandle(feature: string): NzilaFeatureHandle;
|
|
21
|
+
/**
|
|
22
|
+
* Report a caught error when you have the feature name but no handle in scope.
|
|
23
|
+
*/
|
|
24
|
+
export declare function reportFeatureError(feature: string, error: unknown, detail?: Record<string, unknown>): void;
|
|
25
|
+
/** Alias of {@link reportFeatureError}. */
|
|
26
|
+
export declare const captureFeatureError: typeof reportFeatureError;
|
|
27
|
+
/**
|
|
28
|
+
* Start tracing for a backend route or service method.
|
|
29
|
+
*/
|
|
30
|
+
export declare function traceNzilaFeature(feature: string): NzilaFeatureHandle;
|
|
31
|
+
/** Limits used by {@link useNzilaFeature} for render-loop detection. */
|
|
32
|
+
export declare function getRenderLoopLimits(): {
|
|
33
|
+
threshold: number;
|
|
34
|
+
windowMs: number;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/runtime/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAOpB;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAQjE;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,IAAI,OAAO,CAI7C;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,IAAI,kBAAkB,GAAG,IAAI,CAG3D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAS9D;AA0CD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,CA4CvE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI,CAEN;AAED,2CAA2C;AAC3C,eAAO,MAAM,mBAAmB,2BAAqB,CAAC;AAEtD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAErE;AAED,wEAAwE;AACxE,wBAAgB,mBAAmB,IAAI;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAM7E"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { extractErrorFields } from "./extract-error.js";
|
|
2
|
+
import { nzilaSafe, nzilaSafeAsync } from "../silent.js";
|
|
3
|
+
let globalConfig = null;
|
|
4
|
+
const queue = [];
|
|
5
|
+
let flushTimer = null;
|
|
6
|
+
/**
|
|
7
|
+
* Configure runtime signal delivery. Call once before
|
|
8
|
+
* {@link traceNzilaFeature} or {@link useNzilaFeature}.
|
|
9
|
+
*/
|
|
10
|
+
export function initNzilaRuntime(config) {
|
|
11
|
+
nzilaSafe(() => {
|
|
12
|
+
globalConfig = {
|
|
13
|
+
renderLoopThreshold: 40,
|
|
14
|
+
renderLoopWindowMs: 1000,
|
|
15
|
+
...config,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/** True when {@link initNzilaRuntime} was called with a usable URL and key. */
|
|
20
|
+
export function isNzilaRuntimeReady() {
|
|
21
|
+
const url = globalConfig?.signalsUrl?.trim();
|
|
22
|
+
const key = globalConfig?.apiKey?.trim();
|
|
23
|
+
return Boolean(url && key);
|
|
24
|
+
}
|
|
25
|
+
/** @internal — no throw; returns null if not configured. */
|
|
26
|
+
export function getNzilaRuntime() {
|
|
27
|
+
if (!isNzilaRuntimeReady())
|
|
28
|
+
return null;
|
|
29
|
+
return globalConfig;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Queue a signal (batched flush to `/api/signals/runtime`).
|
|
33
|
+
* No-op when runtime is not configured.
|
|
34
|
+
*/
|
|
35
|
+
export function enqueueSignal(signal) {
|
|
36
|
+
nzilaSafe(() => {
|
|
37
|
+
if (!isNzilaRuntimeReady())
|
|
38
|
+
return;
|
|
39
|
+
queue.push({
|
|
40
|
+
...signal,
|
|
41
|
+
occurredAt: signal.occurredAt ?? new Date().toISOString(),
|
|
42
|
+
});
|
|
43
|
+
scheduleFlush();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function scheduleFlush() {
|
|
47
|
+
if (flushTimer)
|
|
48
|
+
return;
|
|
49
|
+
flushTimer = setTimeout(() => {
|
|
50
|
+
flushTimer = null;
|
|
51
|
+
void nzilaSafeAsync(flushSignals);
|
|
52
|
+
}, 400);
|
|
53
|
+
}
|
|
54
|
+
async function flushSignals() {
|
|
55
|
+
const config = globalConfig;
|
|
56
|
+
if (!config?.signalsUrl?.trim() || !config?.apiKey?.trim() || queue.length === 0) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const batch = queue.splice(0, 200);
|
|
60
|
+
const maxRetries = config.maxRetries ?? 2;
|
|
61
|
+
let attempt = 0;
|
|
62
|
+
while (attempt < maxRetries) {
|
|
63
|
+
attempt += 1;
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetch(config.signalsUrl, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
69
|
+
"Content-Type": "application/json",
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
locale: config.locale,
|
|
73
|
+
signals: batch,
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
if (res.ok)
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
/* retry */
|
|
81
|
+
}
|
|
82
|
+
await new Promise((r) => setTimeout(r, attempt * 500));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Create a handle bound to a feature name (low-level).
|
|
87
|
+
* Usually use {@link traceNzilaFeature} or the React hook instead.
|
|
88
|
+
*/
|
|
89
|
+
export function createFeatureHandle(feature) {
|
|
90
|
+
const report = (type, success, payload) => {
|
|
91
|
+
enqueueSignal({ feature, type, success, payload });
|
|
92
|
+
};
|
|
93
|
+
const captureError = (error, detail) => {
|
|
94
|
+
nzilaSafe(() => {
|
|
95
|
+
report("error", false, { ...extractErrorFields(error), ...detail });
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const captureApiError = (error, label, detail) => {
|
|
99
|
+
nzilaSafe(() => {
|
|
100
|
+
report("api", false, { label, ...extractErrorFields(error), ...detail });
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
feature,
|
|
105
|
+
async runApi(label, fn) {
|
|
106
|
+
try {
|
|
107
|
+
const result = await fn();
|
|
108
|
+
nzilaSafe(() => report("api", true, { label }));
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
captureApiError(error, label);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
captureError,
|
|
117
|
+
reportError: captureError,
|
|
118
|
+
captureApiError,
|
|
119
|
+
reportUi(kind, detail, success = false) {
|
|
120
|
+
nzilaSafe(() => report("ui", success, { kind, ...detail }));
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Report a caught error when you have the feature name but no handle in scope.
|
|
126
|
+
*/
|
|
127
|
+
export function reportFeatureError(feature, error, detail) {
|
|
128
|
+
nzilaSafe(() => createFeatureHandle(feature).captureError(error, detail));
|
|
129
|
+
}
|
|
130
|
+
/** Alias of {@link reportFeatureError}. */
|
|
131
|
+
export const captureFeatureError = reportFeatureError;
|
|
132
|
+
/**
|
|
133
|
+
* Start tracing for a backend route or service method.
|
|
134
|
+
*/
|
|
135
|
+
export function traceNzilaFeature(feature) {
|
|
136
|
+
return createFeatureHandle(feature);
|
|
137
|
+
}
|
|
138
|
+
/** Limits used by {@link useNzilaFeature} for render-loop detection. */
|
|
139
|
+
export function getRenderLoopLimits() {
|
|
140
|
+
const c = globalConfig;
|
|
141
|
+
return {
|
|
142
|
+
threshold: c?.renderLoopThreshold ?? 40,
|
|
143
|
+
windowMs: c?.renderLoopWindowMs ?? 1000,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize unknown thrown values for Nzila runtime signals.
|
|
3
|
+
*
|
|
4
|
+
* @param error - `Error`, string, or any thrown value.
|
|
5
|
+
* @returns `message`, `name`, and optional `stack` for signal payloads.
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractErrorFields(error: unknown): {
|
|
8
|
+
message: string;
|
|
9
|
+
name: string;
|
|
10
|
+
stack?: string;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=extract-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-error.d.ts","sourceRoot":"","sources":["../../src/runtime/extract-error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAgBA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize unknown thrown values for Nzila runtime signals.
|
|
3
|
+
*
|
|
4
|
+
* @param error - `Error`, string, or any thrown value.
|
|
5
|
+
* @returns `message`, `name`, and optional `stack` for signal payloads.
|
|
6
|
+
*/
|
|
7
|
+
export function extractErrorFields(error) {
|
|
8
|
+
if (error instanceof Error) {
|
|
9
|
+
return {
|
|
10
|
+
message: error?.message ?? "Unknown error",
|
|
11
|
+
name: error?.name ?? "Error",
|
|
12
|
+
stack: error?.stack,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (typeof error === "string") {
|
|
16
|
+
return { message: error, name: "Error" };
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return { message: JSON.stringify(error), name: "Error" };
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return { message: String(error), name: "Error" };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { NzilaFeatureHandle, NzilaRuntimeConfig, NzilaRuntimeSignal, NzilaSignalType, } from "./types.js";
|
|
2
|
+
export { extractErrorFields } from "./extract-error.js";
|
|
3
|
+
export { captureFeatureError, createFeatureHandle, enqueueSignal, getNzilaRuntime, getRenderLoopLimits, initNzilaRuntime, isNzilaRuntimeReady, reportFeatureError, traceNzilaFeature, } from "./client.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { NzilaLocale } from "../types.js";
|
|
2
|
+
/** Runtime signal category accepted by `POST /api/signals/runtime`. */
|
|
3
|
+
export type NzilaSignalType = "navigation" | "api" | "ui" | "error" | "custom";
|
|
4
|
+
/** One runtime observation for a product feature. */
|
|
5
|
+
export type NzilaRuntimeSignal = {
|
|
6
|
+
/** Must match Vitest `mapFeature` / `describe` feature name. */
|
|
7
|
+
feature: string;
|
|
8
|
+
type: NzilaSignalType;
|
|
9
|
+
success: boolean;
|
|
10
|
+
occurredAt?: string;
|
|
11
|
+
payload?: Record<string, unknown>;
|
|
12
|
+
};
|
|
13
|
+
/** Global config for {@link initNzilaRuntime}. */
|
|
14
|
+
export type NzilaRuntimeConfig = {
|
|
15
|
+
/** e.g. `https://host/api/signals/runtime` */
|
|
16
|
+
signalsUrl: string;
|
|
17
|
+
apiKey: string;
|
|
18
|
+
locale?: NzilaLocale;
|
|
19
|
+
/** Renders in this window before a `ui` render_loop signal (default 40). */
|
|
20
|
+
renderLoopThreshold?: number;
|
|
21
|
+
renderLoopWindowMs?: number;
|
|
22
|
+
maxRetries?: number;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Scoped reporter for one feature. Returned by
|
|
26
|
+
* {@link traceNzilaFeature} and {@link useNzilaFeature}.
|
|
27
|
+
*/
|
|
28
|
+
export type NzilaFeatureHandle = {
|
|
29
|
+
feature: string;
|
|
30
|
+
/**
|
|
31
|
+
* Run an async call; records `api` success or failure.
|
|
32
|
+
* Re-throws the error after reporting.
|
|
33
|
+
*/
|
|
34
|
+
runApi: <T>(label: string, fn: () => Promise<T>) => Promise<T>;
|
|
35
|
+
/**
|
|
36
|
+
* Report a caught error (`type: error`). Use in `catch` blocks and error UI.
|
|
37
|
+
* Alias: {@link NzilaFeatureHandle.reportError}.
|
|
38
|
+
*/
|
|
39
|
+
captureError: (error: unknown, detail?: Record<string, unknown>) => void;
|
|
40
|
+
reportError: (error: unknown, detail?: Record<string, unknown>) => void;
|
|
41
|
+
/** Report a caught HTTP/upstream failure without using `runApi`. */
|
|
42
|
+
captureApiError: (error: unknown, label: string, detail?: Record<string, unknown>) => void;
|
|
43
|
+
/** Custom UI issues, e.g. `reportUi("render_loop", { renderCount: 50 })`. */
|
|
44
|
+
reportUi: (kind: string, detail?: Record<string, unknown>, success?: boolean) => void;
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/runtime/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,KAAK,GAAG,IAAI,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE/E,qDAAqD;AACrD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,CAAC;AAEF,kDAAkD;AAClD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,4EAA4E;IAC5E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/D;;;OAGG;IACH,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACzE,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACxE,oEAAoE;IACpE,eAAe,EAAE,CACf,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,IAAI,CAAC;IACV,6EAA6E;IAC7E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACvF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/silent.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nzila never logs or throws to the host app for telemetry failures.
|
|
3
|
+
* @internal
|
|
4
|
+
*/
|
|
5
|
+
/** Run sync telemetry code; swallow all errors. */
|
|
6
|
+
export declare function nzilaSafe<T>(fn: () => T): T | undefined;
|
|
7
|
+
/** Run async telemetry code; swallow all errors. */
|
|
8
|
+
export declare function nzilaSafeAsync(fn: () => Promise<void>): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=silent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"silent.d.ts","sourceRoot":"","sources":["../src/silent.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAMvD;AAED,oDAAoD;AACpD,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAM3E"}
|
package/dist/silent.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nzila never logs or throws to the host app for telemetry failures.
|
|
3
|
+
* @internal
|
|
4
|
+
*/
|
|
5
|
+
/** Run sync telemetry code; swallow all errors. */
|
|
6
|
+
export function nzilaSafe(fn) {
|
|
7
|
+
try {
|
|
8
|
+
return fn();
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Run async telemetry code; swallow all errors. */
|
|
15
|
+
export async function nzilaSafeAsync(fn) {
|
|
16
|
+
try {
|
|
17
|
+
await fn();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
/* silent */
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Supported test runner identifiers in webhook payloads. */
|
|
2
|
+
export declare const NZILA_TEST_FRAMEWORKS: readonly ["vitest", "jest", "mocha", "playwright", "jasmine", "cypress", "karma", "node-test", "other"];
|
|
3
|
+
export type NzilaTestFramework = (typeof NZILA_TEST_FRAMEWORKS)[number];
|
|
4
|
+
export type NzilaTestStatus = "passed" | "failed" | "skipped" | "pending";
|
|
5
|
+
/** Locales for AI summaries and failure copy on the dashboard. */
|
|
6
|
+
export type NzilaLocale = "en" | "pt" | "fr" | "zh";
|
|
7
|
+
/** One test case inside a completed run webhook. */
|
|
8
|
+
export type NzilaTestCase = {
|
|
9
|
+
appName: string;
|
|
10
|
+
framework: NzilaTestFramework;
|
|
11
|
+
startedAt: string;
|
|
12
|
+
finishedAt: string;
|
|
13
|
+
describePath: string[];
|
|
14
|
+
/** Overrides feature; default is first `describePath` segment or `mapFeature`. */
|
|
15
|
+
feature?: string;
|
|
16
|
+
context?: Record<string, unknown>;
|
|
17
|
+
testName: string;
|
|
18
|
+
status: NzilaTestStatus;
|
|
19
|
+
duration: number;
|
|
20
|
+
errors: {
|
|
21
|
+
message: string;
|
|
22
|
+
stack?: string;
|
|
23
|
+
}[];
|
|
24
|
+
};
|
|
25
|
+
/** Body sent to `POST /api/webhooks/tests` (one per finished run). */
|
|
26
|
+
export type NzilaRunPayload = {
|
|
27
|
+
/** Idempotent external id (CI build id, UUID, etc.). */
|
|
28
|
+
runId: string;
|
|
29
|
+
/** Must match the project App name in the Nzila dashboard. */
|
|
30
|
+
appName: string;
|
|
31
|
+
framework: NzilaTestFramework;
|
|
32
|
+
startedAt: string;
|
|
33
|
+
finishedAt: string;
|
|
34
|
+
tests: NzilaTestCase[];
|
|
35
|
+
locale?: NzilaLocale;
|
|
36
|
+
};
|
|
37
|
+
/** Options for Vitest/Jest/Mocha reporters and {@link sendNzilaRun}. */
|
|
38
|
+
export type NzilaClientOptions = {
|
|
39
|
+
webhookUrl: string;
|
|
40
|
+
apiKey: string;
|
|
41
|
+
appName: string;
|
|
42
|
+
framework?: NzilaTestFramework;
|
|
43
|
+
/** Defaults to a random UUID per reporter instance. */
|
|
44
|
+
runId?: string;
|
|
45
|
+
maxRetries?: number;
|
|
46
|
+
locale?: NzilaLocale;
|
|
47
|
+
/**
|
|
48
|
+
* Map describe suite titles to dashboard feature names.
|
|
49
|
+
* Default: `describePath[0] ?? "general"`.
|
|
50
|
+
*/
|
|
51
|
+
mapFeature?: (describePath: string[], testName: string) => string;
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,eAAO,MAAM,qBAAqB,yGAUxB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAE1E,kEAAkE;AAClE,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEpD,oDAAoD;AACpD,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,kBAAkB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC/C,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,eAAe,GAAG;IAC5B,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,kBAAkB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,wEAAwE;AACxE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CACnE,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { File } from "@vitest/runner";
|
|
2
|
+
import type { NzilaClientOptions } from "./types.js";
|
|
3
|
+
interface VitestReporter {
|
|
4
|
+
onFinished?: (files?: File[], errors?: unknown[]) => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Vitest reporter that POSTs one webhook per finished run via {@link sendNzilaRun}.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* reporters: [
|
|
12
|
+
* "default",
|
|
13
|
+
* new NzilaVitestReporter({
|
|
14
|
+
* webhookUrl: process.env.NZILA_WEBHOOK_URL!,
|
|
15
|
+
* apiKey: process.env.NZILA_API_KEY!,
|
|
16
|
+
* appName: "checkout-api",
|
|
17
|
+
* mapFeature: (path) => path[0] ?? "general",
|
|
18
|
+
* }),
|
|
19
|
+
* ],
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export default class NzilaVitestReporter implements VitestReporter {
|
|
23
|
+
private readonly options;
|
|
24
|
+
private startedAt;
|
|
25
|
+
private sent;
|
|
26
|
+
private readonly runId;
|
|
27
|
+
private tests;
|
|
28
|
+
/**
|
|
29
|
+
* @param options - Webhook URL, API key, and project `appName` (required).
|
|
30
|
+
*/
|
|
31
|
+
constructor(options: NzilaClientOptions);
|
|
32
|
+
onFinished(files?: File[], _errors?: unknown[]): void;
|
|
33
|
+
private walkTasks;
|
|
34
|
+
private mapStatus;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=vitest-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest-reporter.d.ts","sourceRoot":"","sources":["../src/vitest-reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAQ,MAAM,gBAAgB,CAAC;AAGjD,OAAO,KAAK,EAAE,kBAAkB,EAAmD,MAAM,YAAY,CAAC;AAEtG,UAAU,cAAc;IACtB,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CAC3D;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,OAAO,OAAO,mBAAoB,YAAW,cAAc;IAChE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,KAAK,CAAuB;IAEpC;;OAEG;gBACS,OAAO,EAAE,kBAAkB;IAKvC,UAAU,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE;IA2B9C,OAAO,CAAC,SAAS;IAuCjB,OAAO,CAAC,SAAS;CAMlB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { sendNzilaRun, resolveLocale } from "./deliver.js";
|
|
3
|
+
/**
|
|
4
|
+
* Vitest reporter that POSTs one webhook per finished run via {@link sendNzilaRun}.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* reporters: [
|
|
9
|
+
* "default",
|
|
10
|
+
* new NzilaVitestReporter({
|
|
11
|
+
* webhookUrl: process.env.NZILA_WEBHOOK_URL!,
|
|
12
|
+
* apiKey: process.env.NZILA_API_KEY!,
|
|
13
|
+
* appName: "checkout-api",
|
|
14
|
+
* mapFeature: (path) => path[0] ?? "general",
|
|
15
|
+
* }),
|
|
16
|
+
* ],
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export default class NzilaVitestReporter {
|
|
20
|
+
/**
|
|
21
|
+
* @param options - Webhook URL, API key, and project `appName` (required).
|
|
22
|
+
*/
|
|
23
|
+
constructor(options) {
|
|
24
|
+
this.startedAt = new Date();
|
|
25
|
+
this.sent = false;
|
|
26
|
+
this.tests = [];
|
|
27
|
+
this.options = options;
|
|
28
|
+
this.runId = options.runId ?? randomUUID();
|
|
29
|
+
}
|
|
30
|
+
onFinished(files, _errors) {
|
|
31
|
+
if (this.sent)
|
|
32
|
+
return;
|
|
33
|
+
this.sent = true;
|
|
34
|
+
const finishedAt = new Date();
|
|
35
|
+
for (const file of files ?? []) {
|
|
36
|
+
this.walkTasks(file.tasks, file.name.split("/").slice(0, -1));
|
|
37
|
+
}
|
|
38
|
+
if (!this.tests.length)
|
|
39
|
+
return;
|
|
40
|
+
const framework = this.options.framework ?? "vitest";
|
|
41
|
+
const locale = resolveLocale(this.options.locale);
|
|
42
|
+
const payload = {
|
|
43
|
+
runId: this.runId,
|
|
44
|
+
appName: this.options.appName,
|
|
45
|
+
framework,
|
|
46
|
+
startedAt: this.startedAt.toISOString(),
|
|
47
|
+
finishedAt: finishedAt.toISOString(),
|
|
48
|
+
tests: this.tests,
|
|
49
|
+
...(locale ? { locale } : {}),
|
|
50
|
+
};
|
|
51
|
+
void sendNzilaRun(payload, this.options);
|
|
52
|
+
}
|
|
53
|
+
walkTasks(tasks, describePath) {
|
|
54
|
+
const framework = this.options.framework ?? "vitest";
|
|
55
|
+
const now = new Date().toISOString();
|
|
56
|
+
for (const task of tasks) {
|
|
57
|
+
if (task.type === "suite") {
|
|
58
|
+
this.walkTasks(task.tasks, [...describePath, task.name]);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (task.type !== "test")
|
|
62
|
+
continue;
|
|
63
|
+
const result = task?.result;
|
|
64
|
+
const errors = result?.errors?.map((e) => ({
|
|
65
|
+
message: e?.message ?? String(e),
|
|
66
|
+
stack: e?.stack,
|
|
67
|
+
})) ?? [];
|
|
68
|
+
const feature = this.options.mapFeature?.(describePath, task.name) ??
|
|
69
|
+
describePath[0] ??
|
|
70
|
+
"general";
|
|
71
|
+
this.tests.push({
|
|
72
|
+
appName: this.options.appName,
|
|
73
|
+
framework,
|
|
74
|
+
startedAt: this.startedAt.toISOString(),
|
|
75
|
+
finishedAt: now,
|
|
76
|
+
describePath,
|
|
77
|
+
feature,
|
|
78
|
+
context: { layer: this.options.appName },
|
|
79
|
+
testName: task.name,
|
|
80
|
+
status: this.mapStatus(result?.state),
|
|
81
|
+
duration: result?.duration ?? 0,
|
|
82
|
+
errors,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
mapStatus(state) {
|
|
87
|
+
if (state === "pass")
|
|
88
|
+
return "passed";
|
|
89
|
+
if (state === "fail")
|
|
90
|
+
return "failed";
|
|
91
|
+
if (state === "skip")
|
|
92
|
+
return "skipped";
|
|
93
|
+
return "pending";
|
|
94
|
+
}
|
|
95
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nzila/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Send test runs to Nzila and trace live feature errors (Vitest, Jest, Mocha, React, Node).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/joel2011140/nzila.git",
|
|
10
|
+
"directory": "packages/nzila-sdk"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./vitest": {
|
|
27
|
+
"types": "./dist/vitest-reporter.d.ts",
|
|
28
|
+
"import": "./dist/vitest-reporter.js"
|
|
29
|
+
},
|
|
30
|
+
"./jest": {
|
|
31
|
+
"types": "./dist/jest-reporter.d.ts",
|
|
32
|
+
"import": "./dist/jest-reporter.js"
|
|
33
|
+
},
|
|
34
|
+
"./mocha": {
|
|
35
|
+
"types": "./dist/mocha-reporter.d.ts",
|
|
36
|
+
"import": "./dist/mocha-reporter.js"
|
|
37
|
+
},
|
|
38
|
+
"./runtime": {
|
|
39
|
+
"types": "./dist/runtime/index.d.ts",
|
|
40
|
+
"import": "./dist/runtime/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./react": {
|
|
43
|
+
"types": "./dist/react/index.d.ts",
|
|
44
|
+
"import": "./dist/react/index.js"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.build.json",
|
|
49
|
+
"prepublishOnly": "npm run build"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"react": ">=18.0.0",
|
|
53
|
+
"vitest": ">=1.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"vitest": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"react": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/react": "^19.2.14",
|
|
65
|
+
"react": "^19.2.6",
|
|
66
|
+
"typescript": "^5",
|
|
67
|
+
"vitest": "^3.2.4"
|
|
68
|
+
}
|
|
69
|
+
}
|