uidex 0.2.1 → 0.3.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 +263 -263
- package/dist/cli/cli.cjs +3243 -0
- package/dist/cli/cli.cjs.map +1 -0
- package/dist/cloud/index.cjs +149 -0
- package/dist/cloud/index.cjs.map +1 -0
- package/dist/cloud/index.d.cts +108 -0
- package/dist/cloud/index.d.ts +108 -0
- package/dist/cloud/index.js +120 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/headless/index.cjs +3580 -0
- package/dist/headless/index.cjs.map +1 -0
- package/dist/headless/index.d.cts +214 -0
- package/dist/headless/index.d.ts +214 -0
- package/dist/headless/index.js +3562 -0
- package/dist/headless/index.js.map +1 -0
- package/dist/index.cjs +7977 -3301
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +898 -108
- package/dist/index.d.ts +898 -108
- package/dist/index.js +7934 -3270
- package/dist/index.js.map +1 -1
- package/dist/playwright/index.cjs +164 -24
- package/dist/playwright/index.cjs.map +1 -1
- package/dist/playwright/index.d.cts +32 -55
- package/dist/playwright/index.d.ts +32 -55
- package/dist/playwright/index.js +148 -21
- package/dist/playwright/index.js.map +1 -1
- package/dist/playwright/reporter.cjs +62 -28
- package/dist/playwright/reporter.cjs.map +1 -1
- package/dist/playwright/reporter.d.cts +24 -12
- package/dist/playwright/reporter.d.ts +24 -12
- package/dist/playwright/reporter.js +62 -28
- package/dist/playwright/reporter.js.map +1 -1
- package/dist/react/index.cjs +7970 -3267
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +670 -108
- package/dist/react/index.d.ts +670 -108
- package/dist/react/index.js +8016 -3274
- package/dist/react/index.js.map +1 -1
- package/dist/scan/index.cjs +3281 -0
- package/dist/scan/index.cjs.map +1 -0
- package/dist/scan/index.d.cts +373 -0
- package/dist/scan/index.d.ts +373 -0
- package/dist/scan/index.js +3224 -0
- package/dist/scan/index.js.map +1 -0
- package/package.json +74 -56
- package/templates/claude/audit.md +37 -0
- package/templates/claude/rules.md +212 -0
- package/claude/audit-command.md +0 -16
- package/claude/rules.md +0 -88
- package/dist/core/index.cjs +0 -3490
- package/dist/core/index.cjs.map +0 -1
- package/dist/core/index.d.cts +0 -441
- package/dist/core/index.d.ts +0 -441
- package/dist/core/index.global.js +0 -3469
- package/dist/core/index.global.js.map +0 -1
- package/dist/core/index.js +0 -3444
- package/dist/core/index.js.map +0 -1
- package/dist/core/style.css +0 -971
- package/dist/scripts/cli.cjs +0 -1168
- package/uidex.schema.json +0 -93
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/cloud/index.ts
|
|
21
|
+
var cloud_exports = {};
|
|
22
|
+
__export(cloud_exports, {
|
|
23
|
+
CloudError: () => CloudError,
|
|
24
|
+
DEFAULT_CLOUD_ENDPOINT: () => DEFAULT_CLOUD_ENDPOINT,
|
|
25
|
+
cloud: () => cloud
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(cloud_exports);
|
|
28
|
+
|
|
29
|
+
// src/cloud/types.ts
|
|
30
|
+
var DEFAULT_CLOUD_ENDPOINT = "https://app.uidex.dev";
|
|
31
|
+
var CloudError = class extends Error {
|
|
32
|
+
status;
|
|
33
|
+
retryAfter;
|
|
34
|
+
details;
|
|
35
|
+
constructor(message, options) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "CloudError";
|
|
38
|
+
this.status = options.status;
|
|
39
|
+
this.retryAfter = options.retryAfter;
|
|
40
|
+
this.details = options.details;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/cloud/client.ts
|
|
45
|
+
function resolveFetch(override) {
|
|
46
|
+
if (override) return override;
|
|
47
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.fetch === "function") {
|
|
48
|
+
return globalThis.fetch.bind(globalThis);
|
|
49
|
+
}
|
|
50
|
+
throw new Error(
|
|
51
|
+
"uidex/cloud: global fetch is not available; pass a `fetch` override"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
function trimEndpoint(endpoint) {
|
|
55
|
+
return endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
56
|
+
}
|
|
57
|
+
function parseRetryAfter(header) {
|
|
58
|
+
if (!header) return void 0;
|
|
59
|
+
const seconds = Number(header);
|
|
60
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds;
|
|
61
|
+
const date = Date.parse(header);
|
|
62
|
+
if (Number.isFinite(date)) {
|
|
63
|
+
const delta = Math.ceil((date - Date.now()) / 1e3);
|
|
64
|
+
return delta > 0 ? delta : 0;
|
|
65
|
+
}
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
async function readBody(response) {
|
|
69
|
+
const text = await response.text();
|
|
70
|
+
if (!text) return void 0;
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(text);
|
|
73
|
+
} catch {
|
|
74
|
+
return text;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function errorMessage(body, fallback) {
|
|
78
|
+
if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
|
|
79
|
+
return body.error;
|
|
80
|
+
}
|
|
81
|
+
return fallback;
|
|
82
|
+
}
|
|
83
|
+
function cloud(options) {
|
|
84
|
+
const projectKey = options.projectKey;
|
|
85
|
+
if (!projectKey) {
|
|
86
|
+
throw new Error("uidex/cloud: `projectKey` is required");
|
|
87
|
+
}
|
|
88
|
+
const endpoint = trimEndpoint(options.endpoint ?? DEFAULT_CLOUD_ENDPOINT);
|
|
89
|
+
const fetchImpl = resolveFetch(options.fetch);
|
|
90
|
+
const authHeader = `Bearer ${projectKey}`;
|
|
91
|
+
async function submit(payload) {
|
|
92
|
+
const response = await fetchImpl(`${endpoint}/api/ingest`, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: {
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
Authorization: authHeader
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify(payload)
|
|
99
|
+
});
|
|
100
|
+
const body = await readBody(response);
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
const retryAfter = response.status === 429 ? parseRetryAfter(response.headers.get("Retry-After")) : void 0;
|
|
103
|
+
throw new CloudError(
|
|
104
|
+
errorMessage(body, `Feedback submission failed (${response.status})`),
|
|
105
|
+
{ status: response.status, retryAfter, details: body }
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (!body || typeof body !== "object") {
|
|
109
|
+
throw new CloudError("Feedback submission returned an empty response", {
|
|
110
|
+
status: response.status
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return body;
|
|
114
|
+
}
|
|
115
|
+
async function getConfig() {
|
|
116
|
+
const response = await fetchImpl(`${endpoint}/api/ingest/config`, {
|
|
117
|
+
method: "GET",
|
|
118
|
+
headers: {
|
|
119
|
+
Accept: "application/json",
|
|
120
|
+
Authorization: authHeader
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const body = await readBody(response);
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
const retryAfter = response.status === 429 ? parseRetryAfter(response.headers.get("Retry-After")) : void 0;
|
|
126
|
+
throw new CloudError(
|
|
127
|
+
errorMessage(body, `Ingest config request failed (${response.status})`),
|
|
128
|
+
{ status: response.status, retryAfter, details: body }
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (!body || typeof body !== "object") {
|
|
132
|
+
throw new CloudError("Ingest config returned an empty response", {
|
|
133
|
+
status: response.status
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return body;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
feedback: { submit },
|
|
140
|
+
integrations: { getConfig }
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
144
|
+
0 && (module.exports = {
|
|
145
|
+
CloudError,
|
|
146
|
+
DEFAULT_CLOUD_ENDPOINT,
|
|
147
|
+
cloud
|
|
148
|
+
});
|
|
149
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cloud/index.ts","../../src/cloud/types.ts","../../src/cloud/client.ts"],"sourcesContent":["export { cloud } from \"./client\"\nexport {\n CloudError,\n DEFAULT_CLOUD_ENDPOINT,\n type CloudAdapter,\n type CloudOptions,\n type FeedbackExternalLink,\n type FeedbackPayload,\n type FeedbackResult,\n type FeedbackSuggestedTarget,\n type IngestConfig,\n type IngestConfigEpic,\n type SourceRef,\n} from \"./types\"\n","export type {\n CloudAdapter,\n FeedbackExternalLink,\n FeedbackPayload,\n FeedbackResult,\n FeedbackSuggestedTarget,\n IngestConfig,\n IngestConfigEpic,\n SourceRef,\n} from \"../ingest/feedback-contract\"\n\nexport const DEFAULT_CLOUD_ENDPOINT = \"https://app.uidex.dev\"\n\nexport interface CloudOptions {\n projectKey: string\n endpoint?: string\n fetch?: typeof fetch\n}\n\nexport class CloudError extends Error {\n readonly status: number\n readonly retryAfter?: number\n readonly details?: unknown\n\n constructor(\n message: string,\n options: { status: number; retryAfter?: number; details?: unknown }\n ) {\n super(message)\n this.name = \"CloudError\"\n this.status = options.status\n this.retryAfter = options.retryAfter\n this.details = options.details\n }\n}\n","import {\n CloudError,\n DEFAULT_CLOUD_ENDPOINT,\n type CloudAdapter,\n type CloudOptions,\n type FeedbackPayload,\n type FeedbackResult,\n type IngestConfig,\n} from \"./types\"\n\nfunction resolveFetch(override?: typeof fetch): typeof fetch {\n if (override) return override\n if (\n typeof globalThis !== \"undefined\" &&\n typeof globalThis.fetch === \"function\"\n ) {\n return globalThis.fetch.bind(globalThis)\n }\n throw new Error(\n \"uidex/cloud: global fetch is not available; pass a `fetch` override\"\n )\n}\n\nfunction trimEndpoint(endpoint: string): string {\n return endpoint.endsWith(\"/\") ? endpoint.slice(0, -1) : endpoint\n}\n\nfunction parseRetryAfter(header: string | null): number | undefined {\n if (!header) return undefined\n const seconds = Number(header)\n if (Number.isFinite(seconds) && seconds >= 0) return seconds\n const date = Date.parse(header)\n if (Number.isFinite(date)) {\n const delta = Math.ceil((date - Date.now()) / 1000)\n return delta > 0 ? delta : 0\n }\n return undefined\n}\n\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text()\n if (!text) return undefined\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n\nfunction errorMessage(body: unknown, fallback: string): string {\n if (\n body &&\n typeof body === \"object\" &&\n \"error\" in body &&\n typeof (body as { error: unknown }).error === \"string\"\n ) {\n return (body as { error: string }).error\n }\n return fallback\n}\n\nexport function cloud(options: CloudOptions): CloudAdapter {\n const projectKey = options.projectKey\n if (!projectKey) {\n throw new Error(\"uidex/cloud: `projectKey` is required\")\n }\n const endpoint = trimEndpoint(options.endpoint ?? DEFAULT_CLOUD_ENDPOINT)\n const fetchImpl = resolveFetch(options.fetch)\n const authHeader = `Bearer ${projectKey}`\n\n async function submit(payload: FeedbackPayload): Promise<FeedbackResult> {\n const response = await fetchImpl(`${endpoint}/api/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: authHeader,\n },\n body: JSON.stringify(payload),\n })\n\n const body = await readBody(response)\n\n if (!response.ok) {\n const retryAfter =\n response.status === 429\n ? parseRetryAfter(response.headers.get(\"Retry-After\"))\n : undefined\n throw new CloudError(\n errorMessage(body, `Feedback submission failed (${response.status})`),\n { status: response.status, retryAfter, details: body }\n )\n }\n\n if (!body || typeof body !== \"object\") {\n throw new CloudError(\"Feedback submission returned an empty response\", {\n status: response.status,\n })\n }\n return body as FeedbackResult\n }\n\n async function getConfig(): Promise<IngestConfig> {\n const response = await fetchImpl(`${endpoint}/api/ingest/config`, {\n method: \"GET\",\n headers: {\n Accept: \"application/json\",\n Authorization: authHeader,\n },\n })\n\n const body = await readBody(response)\n\n if (!response.ok) {\n const retryAfter =\n response.status === 429\n ? parseRetryAfter(response.headers.get(\"Retry-After\"))\n : undefined\n throw new CloudError(\n errorMessage(body, `Ingest config request failed (${response.status})`),\n { status: response.status, retryAfter, details: body }\n )\n }\n\n if (!body || typeof body !== \"object\") {\n throw new CloudError(\"Ingest config returned an empty response\", {\n status: response.status,\n })\n }\n return body as IngestConfig\n }\n\n return {\n feedback: { submit },\n integrations: { getConfig },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWO,IAAM,yBAAyB;AAQ/B,IAAM,aAAN,cAAyB,MAAM;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EAET,YACE,SACA,SACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS,QAAQ;AACtB,SAAK,aAAa,QAAQ;AAC1B,SAAK,UAAU,QAAQ;AAAA,EACzB;AACF;;;ACxBA,SAAS,aAAa,UAAuC;AAC3D,MAAI,SAAU,QAAO;AACrB,MACE,OAAO,eAAe,eACtB,OAAO,WAAW,UAAU,YAC5B;AACA,WAAO,WAAW,MAAM,KAAK,UAAU;AAAA,EACzC;AACA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,aAAa,UAA0B;AAC9C,SAAO,SAAS,SAAS,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI;AAC1D;AAEA,SAAS,gBAAgB,QAA2C;AAClE,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,OAAO,MAAM;AAC7B,MAAI,OAAO,SAAS,OAAO,KAAK,WAAW,EAAG,QAAO;AACrD,QAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,MAAI,OAAO,SAAS,IAAI,GAAG;AACzB,UAAM,QAAQ,KAAK,MAAM,OAAO,KAAK,IAAI,KAAK,GAAI;AAClD,WAAO,QAAQ,IAAI,QAAQ;AAAA,EAC7B;AACA,SAAO;AACT;AAEA,eAAe,SAAS,UAAsC;AAC5D,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,aAAa,MAAe,UAA0B;AAC7D,MACE,QACA,OAAO,SAAS,YAChB,WAAW,QACX,OAAQ,KAA4B,UAAU,UAC9C;AACA,WAAQ,KAA2B;AAAA,EACrC;AACA,SAAO;AACT;AAEO,SAAS,MAAM,SAAqC;AACzD,QAAM,aAAa,QAAQ;AAC3B,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AACA,QAAM,WAAW,aAAa,QAAQ,YAAY,sBAAsB;AACxE,QAAM,YAAY,aAAa,QAAQ,KAAK;AAC5C,QAAM,aAAa,UAAU,UAAU;AAEvC,iBAAe,OAAO,SAAmD;AACvE,UAAM,WAAW,MAAM,UAAU,GAAG,QAAQ,eAAe;AAAA,MACzD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,eAAe;AAAA,MACjB;AAAA,MACA,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAED,UAAM,OAAO,MAAM,SAAS,QAAQ;AAEpC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,aACJ,SAAS,WAAW,MAChB,gBAAgB,SAAS,QAAQ,IAAI,aAAa,CAAC,IACnD;AACN,YAAM,IAAI;AAAA,QACR,aAAa,MAAM,+BAA+B,SAAS,MAAM,GAAG;AAAA,QACpE,EAAE,QAAQ,SAAS,QAAQ,YAAY,SAAS,KAAK;AAAA,MACvD;AAAA,IACF;AAEA,QAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,YAAM,IAAI,WAAW,kDAAkD;AAAA,QACrE,QAAQ,SAAS;AAAA,MACnB,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAEA,iBAAe,YAAmC;AAChD,UAAM,WAAW,MAAM,UAAU,GAAG,QAAQ,sBAAsB;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,QAAQ;AAAA,QACR,eAAe;AAAA,MACjB;AAAA,IACF,CAAC;AAED,UAAM,OAAO,MAAM,SAAS,QAAQ;AAEpC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,aACJ,SAAS,WAAW,MAChB,gBAAgB,SAAS,QAAQ,IAAI,aAAa,CAAC,IACnD;AACN,YAAM,IAAI;AAAA,QACR,aAAa,MAAM,iCAAiC,SAAS,MAAM,GAAG;AAAA,QACtE,EAAE,QAAQ,SAAS,QAAQ,YAAY,SAAS,KAAK;AAAA,MACvD;AAAA,IACF;AAEA,QAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,YAAM,IAAI,WAAW,4CAA4C;AAAA,QAC/D,QAAQ,SAAS;AAAA,MACnB,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,UAAU,EAAE,OAAO;AAAA,IACnB,cAAc,EAAE,UAAU;AAAA,EAC5B;AACF;","names":[]}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
interface ConsoleLogEntry {
|
|
2
|
+
level: "log" | "warn" | "error" | "info";
|
|
3
|
+
message: string;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
}
|
|
6
|
+
interface NetworkErrorEntry {
|
|
7
|
+
url: string;
|
|
8
|
+
method: string;
|
|
9
|
+
status: number | null;
|
|
10
|
+
statusText: string | null;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
}
|
|
13
|
+
interface Viewport {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}
|
|
17
|
+
interface SourceRef {
|
|
18
|
+
filePath: string;
|
|
19
|
+
line: number;
|
|
20
|
+
}
|
|
21
|
+
interface FeedbackSuggestedTarget {
|
|
22
|
+
integrationId: string;
|
|
23
|
+
targetConfig: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
interface FeedbackPayload {
|
|
26
|
+
type: "bug" | "feature" | "improvement" | "question";
|
|
27
|
+
severity: "low" | "medium" | "high" | "critical";
|
|
28
|
+
title?: string;
|
|
29
|
+
description: string;
|
|
30
|
+
componentId: string;
|
|
31
|
+
element?: string | null;
|
|
32
|
+
sources?: SourceRef[];
|
|
33
|
+
url: string;
|
|
34
|
+
path: string;
|
|
35
|
+
route?: string | null;
|
|
36
|
+
pageTitle?: string;
|
|
37
|
+
sessionId?: string;
|
|
38
|
+
reporterEmail?: string;
|
|
39
|
+
reporterName?: string;
|
|
40
|
+
timestamp: string;
|
|
41
|
+
viewport: Viewport;
|
|
42
|
+
screen: Viewport;
|
|
43
|
+
userAgent: string;
|
|
44
|
+
locale?: string;
|
|
45
|
+
environment?: string;
|
|
46
|
+
appVersion?: string;
|
|
47
|
+
consoleLogs?: ConsoleLogEntry[];
|
|
48
|
+
networkErrors?: NetworkErrorEntry[];
|
|
49
|
+
metadata?: Record<string, string>;
|
|
50
|
+
screenshot?: string;
|
|
51
|
+
suggestedTarget?: FeedbackSuggestedTarget;
|
|
52
|
+
}
|
|
53
|
+
interface FeedbackExternalLink {
|
|
54
|
+
ok: boolean;
|
|
55
|
+
url?: string;
|
|
56
|
+
key?: string;
|
|
57
|
+
error?: string;
|
|
58
|
+
}
|
|
59
|
+
interface FeedbackResult {
|
|
60
|
+
id: string;
|
|
61
|
+
sequenceNumber: number;
|
|
62
|
+
externalLink?: FeedbackExternalLink;
|
|
63
|
+
}
|
|
64
|
+
interface IngestConfigEpic {
|
|
65
|
+
key: string;
|
|
66
|
+
summary: string;
|
|
67
|
+
status: string;
|
|
68
|
+
}
|
|
69
|
+
interface IngestConfig {
|
|
70
|
+
hasJira: boolean;
|
|
71
|
+
integrationId?: string;
|
|
72
|
+
epics?: IngestConfigEpic[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Generic so third-party adapters (e.g. `uidex-cloud`) can plug in their own
|
|
76
|
+
* payload/result/integration shapes. SDK-bundled `cloud()` returns the
|
|
77
|
+
* defaulted form. `ViewContext.cloud` uses the fully-open variant; consumers
|
|
78
|
+
* that need the SDK shape narrow with `as CloudAdapter`.
|
|
79
|
+
*/
|
|
80
|
+
interface CloudAdapter<TPayload = FeedbackPayload, TResult = FeedbackResult, TIntegrations = {
|
|
81
|
+
getConfig(): Promise<IngestConfig>;
|
|
82
|
+
}> {
|
|
83
|
+
readonly feedback: {
|
|
84
|
+
submit(payload: TPayload): Promise<TResult>;
|
|
85
|
+
};
|
|
86
|
+
readonly integrations: TIntegrations;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
declare const DEFAULT_CLOUD_ENDPOINT = "https://app.uidex.dev";
|
|
90
|
+
interface CloudOptions {
|
|
91
|
+
projectKey: string;
|
|
92
|
+
endpoint?: string;
|
|
93
|
+
fetch?: typeof fetch;
|
|
94
|
+
}
|
|
95
|
+
declare class CloudError extends Error {
|
|
96
|
+
readonly status: number;
|
|
97
|
+
readonly retryAfter?: number;
|
|
98
|
+
readonly details?: unknown;
|
|
99
|
+
constructor(message: string, options: {
|
|
100
|
+
status: number;
|
|
101
|
+
retryAfter?: number;
|
|
102
|
+
details?: unknown;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
declare function cloud(options: CloudOptions): CloudAdapter;
|
|
107
|
+
|
|
108
|
+
export { type CloudAdapter, CloudError, type CloudOptions, DEFAULT_CLOUD_ENDPOINT, type FeedbackExternalLink, type FeedbackPayload, type FeedbackResult, type FeedbackSuggestedTarget, type IngestConfig, type IngestConfigEpic, type SourceRef, cloud };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
interface ConsoleLogEntry {
|
|
2
|
+
level: "log" | "warn" | "error" | "info";
|
|
3
|
+
message: string;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
}
|
|
6
|
+
interface NetworkErrorEntry {
|
|
7
|
+
url: string;
|
|
8
|
+
method: string;
|
|
9
|
+
status: number | null;
|
|
10
|
+
statusText: string | null;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
}
|
|
13
|
+
interface Viewport {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}
|
|
17
|
+
interface SourceRef {
|
|
18
|
+
filePath: string;
|
|
19
|
+
line: number;
|
|
20
|
+
}
|
|
21
|
+
interface FeedbackSuggestedTarget {
|
|
22
|
+
integrationId: string;
|
|
23
|
+
targetConfig: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
interface FeedbackPayload {
|
|
26
|
+
type: "bug" | "feature" | "improvement" | "question";
|
|
27
|
+
severity: "low" | "medium" | "high" | "critical";
|
|
28
|
+
title?: string;
|
|
29
|
+
description: string;
|
|
30
|
+
componentId: string;
|
|
31
|
+
element?: string | null;
|
|
32
|
+
sources?: SourceRef[];
|
|
33
|
+
url: string;
|
|
34
|
+
path: string;
|
|
35
|
+
route?: string | null;
|
|
36
|
+
pageTitle?: string;
|
|
37
|
+
sessionId?: string;
|
|
38
|
+
reporterEmail?: string;
|
|
39
|
+
reporterName?: string;
|
|
40
|
+
timestamp: string;
|
|
41
|
+
viewport: Viewport;
|
|
42
|
+
screen: Viewport;
|
|
43
|
+
userAgent: string;
|
|
44
|
+
locale?: string;
|
|
45
|
+
environment?: string;
|
|
46
|
+
appVersion?: string;
|
|
47
|
+
consoleLogs?: ConsoleLogEntry[];
|
|
48
|
+
networkErrors?: NetworkErrorEntry[];
|
|
49
|
+
metadata?: Record<string, string>;
|
|
50
|
+
screenshot?: string;
|
|
51
|
+
suggestedTarget?: FeedbackSuggestedTarget;
|
|
52
|
+
}
|
|
53
|
+
interface FeedbackExternalLink {
|
|
54
|
+
ok: boolean;
|
|
55
|
+
url?: string;
|
|
56
|
+
key?: string;
|
|
57
|
+
error?: string;
|
|
58
|
+
}
|
|
59
|
+
interface FeedbackResult {
|
|
60
|
+
id: string;
|
|
61
|
+
sequenceNumber: number;
|
|
62
|
+
externalLink?: FeedbackExternalLink;
|
|
63
|
+
}
|
|
64
|
+
interface IngestConfigEpic {
|
|
65
|
+
key: string;
|
|
66
|
+
summary: string;
|
|
67
|
+
status: string;
|
|
68
|
+
}
|
|
69
|
+
interface IngestConfig {
|
|
70
|
+
hasJira: boolean;
|
|
71
|
+
integrationId?: string;
|
|
72
|
+
epics?: IngestConfigEpic[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Generic so third-party adapters (e.g. `uidex-cloud`) can plug in their own
|
|
76
|
+
* payload/result/integration shapes. SDK-bundled `cloud()` returns the
|
|
77
|
+
* defaulted form. `ViewContext.cloud` uses the fully-open variant; consumers
|
|
78
|
+
* that need the SDK shape narrow with `as CloudAdapter`.
|
|
79
|
+
*/
|
|
80
|
+
interface CloudAdapter<TPayload = FeedbackPayload, TResult = FeedbackResult, TIntegrations = {
|
|
81
|
+
getConfig(): Promise<IngestConfig>;
|
|
82
|
+
}> {
|
|
83
|
+
readonly feedback: {
|
|
84
|
+
submit(payload: TPayload): Promise<TResult>;
|
|
85
|
+
};
|
|
86
|
+
readonly integrations: TIntegrations;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
declare const DEFAULT_CLOUD_ENDPOINT = "https://app.uidex.dev";
|
|
90
|
+
interface CloudOptions {
|
|
91
|
+
projectKey: string;
|
|
92
|
+
endpoint?: string;
|
|
93
|
+
fetch?: typeof fetch;
|
|
94
|
+
}
|
|
95
|
+
declare class CloudError extends Error {
|
|
96
|
+
readonly status: number;
|
|
97
|
+
readonly retryAfter?: number;
|
|
98
|
+
readonly details?: unknown;
|
|
99
|
+
constructor(message: string, options: {
|
|
100
|
+
status: number;
|
|
101
|
+
retryAfter?: number;
|
|
102
|
+
details?: unknown;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
declare function cloud(options: CloudOptions): CloudAdapter;
|
|
107
|
+
|
|
108
|
+
export { type CloudAdapter, CloudError, type CloudOptions, DEFAULT_CLOUD_ENDPOINT, type FeedbackExternalLink, type FeedbackPayload, type FeedbackResult, type FeedbackSuggestedTarget, type IngestConfig, type IngestConfigEpic, type SourceRef, cloud };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/cloud/types.ts
|
|
2
|
+
var DEFAULT_CLOUD_ENDPOINT = "https://app.uidex.dev";
|
|
3
|
+
var CloudError = class extends Error {
|
|
4
|
+
status;
|
|
5
|
+
retryAfter;
|
|
6
|
+
details;
|
|
7
|
+
constructor(message, options) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "CloudError";
|
|
10
|
+
this.status = options.status;
|
|
11
|
+
this.retryAfter = options.retryAfter;
|
|
12
|
+
this.details = options.details;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/cloud/client.ts
|
|
17
|
+
function resolveFetch(override) {
|
|
18
|
+
if (override) return override;
|
|
19
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.fetch === "function") {
|
|
20
|
+
return globalThis.fetch.bind(globalThis);
|
|
21
|
+
}
|
|
22
|
+
throw new Error(
|
|
23
|
+
"uidex/cloud: global fetch is not available; pass a `fetch` override"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
function trimEndpoint(endpoint) {
|
|
27
|
+
return endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
28
|
+
}
|
|
29
|
+
function parseRetryAfter(header) {
|
|
30
|
+
if (!header) return void 0;
|
|
31
|
+
const seconds = Number(header);
|
|
32
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds;
|
|
33
|
+
const date = Date.parse(header);
|
|
34
|
+
if (Number.isFinite(date)) {
|
|
35
|
+
const delta = Math.ceil((date - Date.now()) / 1e3);
|
|
36
|
+
return delta > 0 ? delta : 0;
|
|
37
|
+
}
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
async function readBody(response) {
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
if (!text) return void 0;
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(text);
|
|
45
|
+
} catch {
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function errorMessage(body, fallback) {
|
|
50
|
+
if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
|
|
51
|
+
return body.error;
|
|
52
|
+
}
|
|
53
|
+
return fallback;
|
|
54
|
+
}
|
|
55
|
+
function cloud(options) {
|
|
56
|
+
const projectKey = options.projectKey;
|
|
57
|
+
if (!projectKey) {
|
|
58
|
+
throw new Error("uidex/cloud: `projectKey` is required");
|
|
59
|
+
}
|
|
60
|
+
const endpoint = trimEndpoint(options.endpoint ?? DEFAULT_CLOUD_ENDPOINT);
|
|
61
|
+
const fetchImpl = resolveFetch(options.fetch);
|
|
62
|
+
const authHeader = `Bearer ${projectKey}`;
|
|
63
|
+
async function submit(payload) {
|
|
64
|
+
const response = await fetchImpl(`${endpoint}/api/ingest`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
Authorization: authHeader
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify(payload)
|
|
71
|
+
});
|
|
72
|
+
const body = await readBody(response);
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
const retryAfter = response.status === 429 ? parseRetryAfter(response.headers.get("Retry-After")) : void 0;
|
|
75
|
+
throw new CloudError(
|
|
76
|
+
errorMessage(body, `Feedback submission failed (${response.status})`),
|
|
77
|
+
{ status: response.status, retryAfter, details: body }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (!body || typeof body !== "object") {
|
|
81
|
+
throw new CloudError("Feedback submission returned an empty response", {
|
|
82
|
+
status: response.status
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return body;
|
|
86
|
+
}
|
|
87
|
+
async function getConfig() {
|
|
88
|
+
const response = await fetchImpl(`${endpoint}/api/ingest/config`, {
|
|
89
|
+
method: "GET",
|
|
90
|
+
headers: {
|
|
91
|
+
Accept: "application/json",
|
|
92
|
+
Authorization: authHeader
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const body = await readBody(response);
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const retryAfter = response.status === 429 ? parseRetryAfter(response.headers.get("Retry-After")) : void 0;
|
|
98
|
+
throw new CloudError(
|
|
99
|
+
errorMessage(body, `Ingest config request failed (${response.status})`),
|
|
100
|
+
{ status: response.status, retryAfter, details: body }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (!body || typeof body !== "object") {
|
|
104
|
+
throw new CloudError("Ingest config returned an empty response", {
|
|
105
|
+
status: response.status
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return body;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
feedback: { submit },
|
|
112
|
+
integrations: { getConfig }
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
CloudError,
|
|
117
|
+
DEFAULT_CLOUD_ENDPOINT,
|
|
118
|
+
cloud
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cloud/types.ts","../../src/cloud/client.ts"],"sourcesContent":["export type {\n CloudAdapter,\n FeedbackExternalLink,\n FeedbackPayload,\n FeedbackResult,\n FeedbackSuggestedTarget,\n IngestConfig,\n IngestConfigEpic,\n SourceRef,\n} from \"../ingest/feedback-contract\"\n\nexport const DEFAULT_CLOUD_ENDPOINT = \"https://app.uidex.dev\"\n\nexport interface CloudOptions {\n projectKey: string\n endpoint?: string\n fetch?: typeof fetch\n}\n\nexport class CloudError extends Error {\n readonly status: number\n readonly retryAfter?: number\n readonly details?: unknown\n\n constructor(\n message: string,\n options: { status: number; retryAfter?: number; details?: unknown }\n ) {\n super(message)\n this.name = \"CloudError\"\n this.status = options.status\n this.retryAfter = options.retryAfter\n this.details = options.details\n }\n}\n","import {\n CloudError,\n DEFAULT_CLOUD_ENDPOINT,\n type CloudAdapter,\n type CloudOptions,\n type FeedbackPayload,\n type FeedbackResult,\n type IngestConfig,\n} from \"./types\"\n\nfunction resolveFetch(override?: typeof fetch): typeof fetch {\n if (override) return override\n if (\n typeof globalThis !== \"undefined\" &&\n typeof globalThis.fetch === \"function\"\n ) {\n return globalThis.fetch.bind(globalThis)\n }\n throw new Error(\n \"uidex/cloud: global fetch is not available; pass a `fetch` override\"\n )\n}\n\nfunction trimEndpoint(endpoint: string): string {\n return endpoint.endsWith(\"/\") ? endpoint.slice(0, -1) : endpoint\n}\n\nfunction parseRetryAfter(header: string | null): number | undefined {\n if (!header) return undefined\n const seconds = Number(header)\n if (Number.isFinite(seconds) && seconds >= 0) return seconds\n const date = Date.parse(header)\n if (Number.isFinite(date)) {\n const delta = Math.ceil((date - Date.now()) / 1000)\n return delta > 0 ? delta : 0\n }\n return undefined\n}\n\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text()\n if (!text) return undefined\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n\nfunction errorMessage(body: unknown, fallback: string): string {\n if (\n body &&\n typeof body === \"object\" &&\n \"error\" in body &&\n typeof (body as { error: unknown }).error === \"string\"\n ) {\n return (body as { error: string }).error\n }\n return fallback\n}\n\nexport function cloud(options: CloudOptions): CloudAdapter {\n const projectKey = options.projectKey\n if (!projectKey) {\n throw new Error(\"uidex/cloud: `projectKey` is required\")\n }\n const endpoint = trimEndpoint(options.endpoint ?? DEFAULT_CLOUD_ENDPOINT)\n const fetchImpl = resolveFetch(options.fetch)\n const authHeader = `Bearer ${projectKey}`\n\n async function submit(payload: FeedbackPayload): Promise<FeedbackResult> {\n const response = await fetchImpl(`${endpoint}/api/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: authHeader,\n },\n body: JSON.stringify(payload),\n })\n\n const body = await readBody(response)\n\n if (!response.ok) {\n const retryAfter =\n response.status === 429\n ? parseRetryAfter(response.headers.get(\"Retry-After\"))\n : undefined\n throw new CloudError(\n errorMessage(body, `Feedback submission failed (${response.status})`),\n { status: response.status, retryAfter, details: body }\n )\n }\n\n if (!body || typeof body !== \"object\") {\n throw new CloudError(\"Feedback submission returned an empty response\", {\n status: response.status,\n })\n }\n return body as FeedbackResult\n }\n\n async function getConfig(): Promise<IngestConfig> {\n const response = await fetchImpl(`${endpoint}/api/ingest/config`, {\n method: \"GET\",\n headers: {\n Accept: \"application/json\",\n Authorization: authHeader,\n },\n })\n\n const body = await readBody(response)\n\n if (!response.ok) {\n const retryAfter =\n response.status === 429\n ? parseRetryAfter(response.headers.get(\"Retry-After\"))\n : undefined\n throw new CloudError(\n errorMessage(body, `Ingest config request failed (${response.status})`),\n { status: response.status, retryAfter, details: body }\n )\n }\n\n if (!body || typeof body !== \"object\") {\n throw new CloudError(\"Ingest config returned an empty response\", {\n status: response.status,\n })\n }\n return body as IngestConfig\n }\n\n return {\n feedback: { submit },\n integrations: { getConfig },\n }\n}\n"],"mappings":"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","names":[]}
|