uidex 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/cli.cjs +1111 -87
- package/dist/cli/cli.cjs.map +1 -1
- package/dist/cloud/index.cjs +375 -72
- package/dist/cloud/index.cjs.map +1 -1
- package/dist/cloud/index.d.cts +82 -0
- package/dist/cloud/index.d.ts +82 -0
- package/dist/cloud/index.js +376 -71
- package/dist/cloud/index.js.map +1 -1
- package/dist/headless/index.cjs +623 -469
- package/dist/headless/index.cjs.map +1 -1
- package/dist/headless/index.d.cts +77 -75
- package/dist/headless/index.d.ts +77 -75
- package/dist/headless/index.js +627 -469
- package/dist/headless/index.js.map +1 -1
- package/dist/index.cjs +4258 -2884
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +275 -234
- package/dist/index.d.ts +275 -234
- package/dist/index.js +4280 -2890
- package/dist/index.js.map +1 -1
- package/dist/playwright/index.cjs +4 -4
- package/dist/playwright/index.cjs.map +1 -1
- package/dist/playwright/index.js +3 -3
- package/dist/playwright/index.js.map +1 -1
- package/dist/playwright/reporter.cjs +3 -3
- package/dist/playwright/reporter.cjs.map +1 -1
- package/dist/playwright/reporter.js +3 -3
- package/dist/playwright/reporter.js.map +1 -1
- package/dist/react/index.cjs +4299 -2906
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +206 -200
- package/dist/react/index.d.ts +206 -200
- package/dist/react/index.js +4339 -2926
- package/dist/react/index.js.map +1 -1
- package/dist/scan/index.cjs +201 -49
- package/dist/scan/index.cjs.map +1 -1
- package/dist/scan/index.d.cts +27 -1
- package/dist/scan/index.d.ts +27 -1
- package/dist/scan/index.js +200 -48
- package/dist/scan/index.js.map +1 -1
- package/package.json +8 -14
- package/templates/claude/api.md +110 -0
package/dist/scan/index.d.cts
CHANGED
|
@@ -65,6 +65,26 @@ type EntityByKind<K extends EntityKind> = Extract<Entity, {
|
|
|
65
65
|
kind: K;
|
|
66
66
|
}>;
|
|
67
67
|
|
|
68
|
+
interface ReportRecord {
|
|
69
|
+
id: string;
|
|
70
|
+
entity?: string;
|
|
71
|
+
reporter?: {
|
|
72
|
+
id?: string;
|
|
73
|
+
email?: string;
|
|
74
|
+
name?: string;
|
|
75
|
+
};
|
|
76
|
+
title?: string;
|
|
77
|
+
body: string;
|
|
78
|
+
type: string;
|
|
79
|
+
severity: string;
|
|
80
|
+
status: string;
|
|
81
|
+
labels?: string[];
|
|
82
|
+
url: string;
|
|
83
|
+
route?: string;
|
|
84
|
+
pageTitle?: string;
|
|
85
|
+
screenshot?: string;
|
|
86
|
+
createdAt: string;
|
|
87
|
+
}
|
|
68
88
|
interface Registry {
|
|
69
89
|
add(entity: Entity): void;
|
|
70
90
|
get<K extends EntityKind>(kind: K, id: string): EntityByKind<K> | undefined;
|
|
@@ -72,6 +92,11 @@ interface Registry {
|
|
|
72
92
|
query(predicate: (entity: Entity) => boolean): Entity[];
|
|
73
93
|
byScope(scope: Scope): Entity[];
|
|
74
94
|
touchedBy(flowId: string): Entity[];
|
|
95
|
+
setReports(kind: EntityKind, id: string, reports: readonly ReportRecord[]): void;
|
|
96
|
+
getReports(kind: EntityKind, id: string): readonly ReportRecord[];
|
|
97
|
+
listReportKeys(): readonly string[];
|
|
98
|
+
archiveReport?: (reportId: string, reason?: string) => void | Promise<void>;
|
|
99
|
+
onReportsChange(cb: () => void): () => void;
|
|
75
100
|
}
|
|
76
101
|
|
|
77
102
|
interface SourceConfig {
|
|
@@ -130,7 +155,7 @@ interface Annotation {
|
|
|
130
155
|
/** JSX ancestor chain, outermost to innermost. Only populated for DOM-attribute kinds. */
|
|
131
156
|
ancestors?: AnnotationAncestor[];
|
|
132
157
|
}
|
|
133
|
-
type MetadataExportKind = "page" | "feature" | "primitive" | "widget" | "flow";
|
|
158
|
+
type MetadataExportKind = "page" | "feature" | "primitive" | "widget" | "region" | "flow";
|
|
134
159
|
interface MetadataExport {
|
|
135
160
|
source: "ts-export";
|
|
136
161
|
kind: MetadataExportKind;
|
|
@@ -351,6 +376,7 @@ declare namespace Uidex {
|
|
|
351
376
|
interface Feature<FeatureIds extends string = string> {
|
|
352
377
|
feature: FeatureIds | false;
|
|
353
378
|
name?: string;
|
|
379
|
+
features?: readonly FeatureIds[];
|
|
354
380
|
acceptance?: readonly string[];
|
|
355
381
|
description?: string;
|
|
356
382
|
}
|
package/dist/scan/index.d.ts
CHANGED
|
@@ -65,6 +65,26 @@ type EntityByKind<K extends EntityKind> = Extract<Entity, {
|
|
|
65
65
|
kind: K;
|
|
66
66
|
}>;
|
|
67
67
|
|
|
68
|
+
interface ReportRecord {
|
|
69
|
+
id: string;
|
|
70
|
+
entity?: string;
|
|
71
|
+
reporter?: {
|
|
72
|
+
id?: string;
|
|
73
|
+
email?: string;
|
|
74
|
+
name?: string;
|
|
75
|
+
};
|
|
76
|
+
title?: string;
|
|
77
|
+
body: string;
|
|
78
|
+
type: string;
|
|
79
|
+
severity: string;
|
|
80
|
+
status: string;
|
|
81
|
+
labels?: string[];
|
|
82
|
+
url: string;
|
|
83
|
+
route?: string;
|
|
84
|
+
pageTitle?: string;
|
|
85
|
+
screenshot?: string;
|
|
86
|
+
createdAt: string;
|
|
87
|
+
}
|
|
68
88
|
interface Registry {
|
|
69
89
|
add(entity: Entity): void;
|
|
70
90
|
get<K extends EntityKind>(kind: K, id: string): EntityByKind<K> | undefined;
|
|
@@ -72,6 +92,11 @@ interface Registry {
|
|
|
72
92
|
query(predicate: (entity: Entity) => boolean): Entity[];
|
|
73
93
|
byScope(scope: Scope): Entity[];
|
|
74
94
|
touchedBy(flowId: string): Entity[];
|
|
95
|
+
setReports(kind: EntityKind, id: string, reports: readonly ReportRecord[]): void;
|
|
96
|
+
getReports(kind: EntityKind, id: string): readonly ReportRecord[];
|
|
97
|
+
listReportKeys(): readonly string[];
|
|
98
|
+
archiveReport?: (reportId: string, reason?: string) => void | Promise<void>;
|
|
99
|
+
onReportsChange(cb: () => void): () => void;
|
|
75
100
|
}
|
|
76
101
|
|
|
77
102
|
interface SourceConfig {
|
|
@@ -130,7 +155,7 @@ interface Annotation {
|
|
|
130
155
|
/** JSX ancestor chain, outermost to innermost. Only populated for DOM-attribute kinds. */
|
|
131
156
|
ancestors?: AnnotationAncestor[];
|
|
132
157
|
}
|
|
133
|
-
type MetadataExportKind = "page" | "feature" | "primitive" | "widget" | "flow";
|
|
158
|
+
type MetadataExportKind = "page" | "feature" | "primitive" | "widget" | "region" | "flow";
|
|
134
159
|
interface MetadataExport {
|
|
135
160
|
source: "ts-export";
|
|
136
161
|
kind: MetadataExportKind;
|
|
@@ -351,6 +376,7 @@ declare namespace Uidex {
|
|
|
351
376
|
interface Feature<FeatureIds extends string = string> {
|
|
352
377
|
feature: FeatureIds | false;
|
|
353
378
|
name?: string;
|
|
379
|
+
features?: readonly FeatureIds[];
|
|
354
380
|
acceptance?: readonly string[];
|
|
355
381
|
description?: string;
|
|
356
382
|
}
|
package/dist/scan/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// src/scan/discover.ts
|
|
1
|
+
// src/scanner/scan/discover.ts
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
|
|
5
|
-
// src/scan/config.ts
|
|
5
|
+
// src/scanner/scan/config.ts
|
|
6
6
|
var DEFAULT_TYPE_MODE = "strict";
|
|
7
7
|
var WELL_KNOWN_FILES = {
|
|
8
8
|
page: "uidex.page.ts",
|
|
@@ -166,7 +166,7 @@ var DEFAULT_CONVENTIONS = {
|
|
|
166
166
|
regions: "landmarks"
|
|
167
167
|
};
|
|
168
168
|
|
|
169
|
-
// src/scan/discover.ts
|
|
169
|
+
// src/scanner/scan/discover.ts
|
|
170
170
|
var CONFIG_FILENAME = ".uidex.json";
|
|
171
171
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
172
172
|
"node_modules",
|
|
@@ -225,7 +225,7 @@ function discover(options = {}) {
|
|
|
225
225
|
return results.sort((a, b) => a.configPath.localeCompare(b.configPath));
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
// src/scan/walk.ts
|
|
228
|
+
// src/scanner/scan/walk.ts
|
|
229
229
|
import * as fs2 from "fs";
|
|
230
230
|
import * as path2 from "path";
|
|
231
231
|
var DEFAULT_INCLUDES = ["**/*.{ts,tsx,js,jsx,mjs,cjs}"];
|
|
@@ -350,12 +350,13 @@ function* walkDir(root, dir) {
|
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
// src/scan/extract-uidex-export.ts
|
|
353
|
+
// src/scanner/scan/extract-uidex-export.ts
|
|
354
354
|
var KIND_DISCRIMINATORS = [
|
|
355
355
|
"page",
|
|
356
356
|
"feature",
|
|
357
357
|
"primitive",
|
|
358
358
|
"widget",
|
|
359
|
+
"region",
|
|
359
360
|
"flow",
|
|
360
361
|
"notFlow"
|
|
361
362
|
];
|
|
@@ -368,15 +369,23 @@ var ALLOWED_FIELDS = {
|
|
|
368
369
|
"acceptance",
|
|
369
370
|
"description"
|
|
370
371
|
]),
|
|
371
|
-
feature: /* @__PURE__ */ new Set([
|
|
372
|
+
feature: /* @__PURE__ */ new Set([
|
|
373
|
+
"feature",
|
|
374
|
+
"name",
|
|
375
|
+
"features",
|
|
376
|
+
"acceptance",
|
|
377
|
+
"description"
|
|
378
|
+
]),
|
|
372
379
|
primitive: /* @__PURE__ */ new Set(["primitive", "name", "description"]),
|
|
373
380
|
widget: /* @__PURE__ */ new Set(["widget", "name", "acceptance", "description"]),
|
|
381
|
+
region: /* @__PURE__ */ new Set(["region", "name", "description"]),
|
|
374
382
|
flow: /* @__PURE__ */ new Set(["flow", "notFlow", "name", "description"])
|
|
375
383
|
};
|
|
376
384
|
var FALSEABLE = /* @__PURE__ */ new Set([
|
|
377
385
|
"page",
|
|
378
386
|
"feature",
|
|
379
|
-
"primitive"
|
|
387
|
+
"primitive",
|
|
388
|
+
"region"
|
|
380
389
|
]);
|
|
381
390
|
var ExtractError = class extends Error {
|
|
382
391
|
code;
|
|
@@ -1104,7 +1113,7 @@ function buildMetadata(value, file, headerPos, diagnostics) {
|
|
|
1104
1113
|
line: pos.line
|
|
1105
1114
|
});
|
|
1106
1115
|
}
|
|
1107
|
-
const features = kind === "page" ? readStringArrayField(byKey, "features") : void 0;
|
|
1116
|
+
const features = kind === "page" || kind === "feature" ? readStringArrayField(byKey, "features") : void 0;
|
|
1108
1117
|
const widgets = kind === "page" ? readStringArrayField(byKey, "widgets") : void 0;
|
|
1109
1118
|
const notFlow = kind === "flow" && discriminator === "notFlow" ? true : void 0;
|
|
1110
1119
|
const metadata = {
|
|
@@ -1199,7 +1208,7 @@ function posAt(content, offset) {
|
|
|
1199
1208
|
return { offset, line, column: offset - lineStart + 1 };
|
|
1200
1209
|
}
|
|
1201
1210
|
|
|
1202
|
-
// src/scan/jsx-ancestry.ts
|
|
1211
|
+
// src/scanner/scan/jsx-ancestry.ts
|
|
1203
1212
|
var DATA_ATTR_RE = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
1204
1213
|
function parseDataAttrs(tagSource) {
|
|
1205
1214
|
if (!tagSource.includes("data-uidex")) return [];
|
|
@@ -1388,7 +1397,7 @@ function findTagEnd(content, start) {
|
|
|
1388
1397
|
return -1;
|
|
1389
1398
|
}
|
|
1390
1399
|
|
|
1391
|
-
// src/scan/extract.ts
|
|
1400
|
+
// src/scanner/scan/extract.ts
|
|
1392
1401
|
var JSDOC_BLOCK = /\/\*\*([\s\S]*?)\*\//g;
|
|
1393
1402
|
function lineAt(content, index) {
|
|
1394
1403
|
let line = 1;
|
|
@@ -1491,10 +1500,10 @@ function extractOne(file) {
|
|
|
1491
1500
|
return annotations;
|
|
1492
1501
|
}
|
|
1493
1502
|
|
|
1494
|
-
// src/scan/resolve.ts
|
|
1503
|
+
// src/scanner/scan/resolve.ts
|
|
1495
1504
|
import * as path3 from "path";
|
|
1496
1505
|
|
|
1497
|
-
// src/entities/types.ts
|
|
1506
|
+
// src/shared/entities/types.ts
|
|
1498
1507
|
var ENTITY_KINDS = [
|
|
1499
1508
|
"route",
|
|
1500
1509
|
"page",
|
|
@@ -1527,7 +1536,7 @@ function assertEntityKind(kind) {
|
|
|
1527
1536
|
if (!KIND_SET.has(kind)) throw new UnknownEntityKindError(kind);
|
|
1528
1537
|
}
|
|
1529
1538
|
|
|
1530
|
-
// src/entities/registry.ts
|
|
1539
|
+
// src/shared/entities/registry.ts
|
|
1531
1540
|
function emptyStore() {
|
|
1532
1541
|
return {
|
|
1533
1542
|
route: /* @__PURE__ */ new Map(),
|
|
@@ -1609,10 +1618,33 @@ function createRegistry() {
|
|
|
1609
1618
|
return ids.has(entity.id);
|
|
1610
1619
|
});
|
|
1611
1620
|
};
|
|
1612
|
-
|
|
1621
|
+
const reports = /* @__PURE__ */ new Map();
|
|
1622
|
+
const reportsCbs = /* @__PURE__ */ new Set();
|
|
1623
|
+
const setReports = (kind, id, records) => {
|
|
1624
|
+
reports.set(`${kind}:${id}`, records);
|
|
1625
|
+
for (const cb of reportsCbs) cb();
|
|
1626
|
+
};
|
|
1627
|
+
const getReports = (kind, id) => reports.get(`${kind}:${id}`) ?? [];
|
|
1628
|
+
const listReportKeys = () => Array.from(reports.keys());
|
|
1629
|
+
const onReportsChange = (cb) => {
|
|
1630
|
+
reportsCbs.add(cb);
|
|
1631
|
+
return () => reportsCbs.delete(cb);
|
|
1632
|
+
};
|
|
1633
|
+
return {
|
|
1634
|
+
add,
|
|
1635
|
+
get,
|
|
1636
|
+
list,
|
|
1637
|
+
query,
|
|
1638
|
+
byScope,
|
|
1639
|
+
touchedBy,
|
|
1640
|
+
setReports,
|
|
1641
|
+
getReports,
|
|
1642
|
+
listReportKeys,
|
|
1643
|
+
onReportsChange
|
|
1644
|
+
};
|
|
1613
1645
|
}
|
|
1614
1646
|
|
|
1615
|
-
// src/scan/routes.ts
|
|
1647
|
+
// src/scanner/scan/routes.ts
|
|
1616
1648
|
var PAGE_BASENAME = /^page\.(tsx|ts|jsx|js|mjs|cjs)$/;
|
|
1617
1649
|
var PAGES_ROUTER_BASENAME = /\.(tsx|ts|jsx|js|mjs|cjs)$/;
|
|
1618
1650
|
var ROUTE_BASENAME = /^route\.(tsx|ts|jsx|js|mjs|cjs)$/;
|
|
@@ -1678,7 +1710,7 @@ function pathToId(routePath) {
|
|
|
1678
1710
|
return routePath.replace(/^\/+/, "").replace(/\[\.{3}([^\]]+)\]/g, "$1").replace(/\[([^\]]+)\]/g, "$1").replace(/\//g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1679
1711
|
}
|
|
1680
1712
|
|
|
1681
|
-
// src/scan/resolve.ts
|
|
1713
|
+
// src/scanner/scan/resolve.ts
|
|
1682
1714
|
var DOM_ATTR_KINDS = /* @__PURE__ */ new Set([
|
|
1683
1715
|
"element",
|
|
1684
1716
|
"region",
|
|
@@ -1985,6 +2017,21 @@ function resolve2(ctx) {
|
|
|
1985
2017
|
};
|
|
1986
2018
|
registry.add(region);
|
|
1987
2019
|
}
|
|
2020
|
+
for (const ef of ctx.extracted) {
|
|
2021
|
+
const exp = exportFor(ef.file.displayPath, "region");
|
|
2022
|
+
if (!exp) continue;
|
|
2023
|
+
if (exp.id === false) continue;
|
|
2024
|
+
if (typeof exp.id === "string" && !registry.get("region", exp.id)) {
|
|
2025
|
+
const meta = buildMetaFromExport(exp);
|
|
2026
|
+
const region = {
|
|
2027
|
+
kind: "region",
|
|
2028
|
+
id: exp.id,
|
|
2029
|
+
loc: { file: ef.file.displayPath, line: exp.loc.line },
|
|
2030
|
+
...meta ? { meta } : {}
|
|
2031
|
+
};
|
|
2032
|
+
registry.add(region);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
1988
2035
|
const primitiveConventions = conventions.primitives;
|
|
1989
2036
|
for (const ef of ctx.extracted) {
|
|
1990
2037
|
const file = ef.file.displayPath;
|
|
@@ -2163,7 +2210,7 @@ function dedupe(arr) {
|
|
|
2163
2210
|
return Array.from(new Set(arr));
|
|
2164
2211
|
}
|
|
2165
2212
|
|
|
2166
|
-
// src/scan/audit.ts
|
|
2213
|
+
// src/scanner/scan/audit.ts
|
|
2167
2214
|
import * as path4 from "path";
|
|
2168
2215
|
var MARKER_FILENAMES = ["UIDEX_PAGE.md", "UIDEX_FEATURE.md"];
|
|
2169
2216
|
function audit(opts) {
|
|
@@ -2293,17 +2340,38 @@ function audit(opts) {
|
|
|
2293
2340
|
}
|
|
2294
2341
|
}
|
|
2295
2342
|
if (lint) {
|
|
2343
|
+
const dynamicAttrRe = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*\{/g;
|
|
2296
2344
|
for (const f of files) {
|
|
2297
|
-
const lines = f.content.split("\n");
|
|
2298
|
-
const candidateRe = /<(button|a|input|select|textarea)(?=[\s/>])([^>]*)>/g;
|
|
2299
2345
|
let m;
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
const idx = m.index;
|
|
2346
|
+
dynamicAttrRe.lastIndex = 0;
|
|
2347
|
+
while ((m = dynamicAttrRe.exec(f.content)) !== null) {
|
|
2348
|
+
const kind = m[1] ?? "element";
|
|
2304
2349
|
let line = 1;
|
|
2305
|
-
for (let i = 0; i <
|
|
2306
|
-
|
|
2350
|
+
for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
|
|
2351
|
+
const attrName = m[1] ? `data-uidex-${m[1]}` : "data-uidex";
|
|
2352
|
+
diagnostics.push({
|
|
2353
|
+
code: "dynamic-attr",
|
|
2354
|
+
severity: "warning",
|
|
2355
|
+
message: `\`${attrName}={\u2026}\` uses a dynamic expression; the scanner cannot resolve the ${kind} id statically`,
|
|
2356
|
+
file: f.displayPath,
|
|
2357
|
+
line,
|
|
2358
|
+
hint: dynamicAttrHint(kind)
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
if (lint) {
|
|
2364
|
+
for (const f of files) {
|
|
2365
|
+
const tagRe = /<(button|a|input|select|textarea)(?=[\s/>])/g;
|
|
2366
|
+
let m;
|
|
2367
|
+
while ((m = tagRe.exec(f.content)) !== null) {
|
|
2368
|
+
const afterTag = m.index + m[0].length;
|
|
2369
|
+
const closeIdx = findJsxOpeningEnd(f.content, afterTag);
|
|
2370
|
+
if (closeIdx === -1) continue;
|
|
2371
|
+
const attrs = f.content.slice(afterTag, closeIdx);
|
|
2372
|
+
if (attrs.includes("data-uidex")) continue;
|
|
2373
|
+
let line = 1;
|
|
2374
|
+
for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
|
|
2307
2375
|
diagnostics.push({
|
|
2308
2376
|
code: "missing-element-annotation",
|
|
2309
2377
|
severity: "info",
|
|
@@ -2318,6 +2386,15 @@ function audit(opts) {
|
|
|
2318
2386
|
const primitives = registry.list("primitive");
|
|
2319
2387
|
const byName = /* @__PURE__ */ new Map();
|
|
2320
2388
|
for (const p2 of primitives) byName.set(p2.id, p2);
|
|
2389
|
+
const declaredFeatures = /* @__PURE__ */ new Map();
|
|
2390
|
+
for (const ef of extracted) {
|
|
2391
|
+
if (!ef.metadata) continue;
|
|
2392
|
+
for (const m of ef.metadata) {
|
|
2393
|
+
if (m.features && m.features.length > 0) {
|
|
2394
|
+
declaredFeatures.set(ef.file.displayPath, new Set(m.features));
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2321
2398
|
for (const f of files) {
|
|
2322
2399
|
const importRe = /import\s+(?:[^'"]+)\s+from\s+['"]([^'"]+)['"]/g;
|
|
2323
2400
|
let m;
|
|
@@ -2332,14 +2409,19 @@ function audit(opts) {
|
|
|
2332
2409
|
if (!scope) continue;
|
|
2333
2410
|
const [kind, id] = scope.split(":");
|
|
2334
2411
|
const importerSegments = f.displayPath.split("/");
|
|
2335
|
-
if (
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
});
|
|
2412
|
+
if (importerSegments.includes(id) && importerSegments.includes(kind + "s")) {
|
|
2413
|
+
continue;
|
|
2414
|
+
}
|
|
2415
|
+
if (kind === "feature" && importerSegments.includes(id)) continue;
|
|
2416
|
+
if (kind === "feature" && declaredFeatures.get(f.displayPath)?.has(id)) {
|
|
2417
|
+
continue;
|
|
2342
2418
|
}
|
|
2419
|
+
diagnostics.push({
|
|
2420
|
+
code: "scope-leak",
|
|
2421
|
+
severity: "warning",
|
|
2422
|
+
message: `Primitive "${primitive.id}" is scoped to ${scope} but is imported from ${f.displayPath}`,
|
|
2423
|
+
file: f.displayPath
|
|
2424
|
+
});
|
|
2343
2425
|
}
|
|
2344
2426
|
}
|
|
2345
2427
|
}
|
|
@@ -2499,6 +2581,63 @@ function extractEntitiesArray(source) {
|
|
|
2499
2581
|
}
|
|
2500
2582
|
return null;
|
|
2501
2583
|
}
|
|
2584
|
+
function findJsxOpeningEnd(src, start) {
|
|
2585
|
+
let i = start;
|
|
2586
|
+
while (i < src.length) {
|
|
2587
|
+
const ch = src[i];
|
|
2588
|
+
if (ch === ">" || ch === "/" && src[i + 1] === ">") return i;
|
|
2589
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
2590
|
+
i = skipString2(src, i);
|
|
2591
|
+
} else if (ch === "{") {
|
|
2592
|
+
i = skipBraces(src, i);
|
|
2593
|
+
} else {
|
|
2594
|
+
i++;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
return -1;
|
|
2598
|
+
}
|
|
2599
|
+
function skipString2(src, start) {
|
|
2600
|
+
const quote = src[start];
|
|
2601
|
+
let i = start + 1;
|
|
2602
|
+
while (i < src.length) {
|
|
2603
|
+
if (src[i] === "\\" && quote !== "`") {
|
|
2604
|
+
i += 2;
|
|
2605
|
+
continue;
|
|
2606
|
+
}
|
|
2607
|
+
if (quote === "`" && src[i] === "$" && src[i + 1] === "{") {
|
|
2608
|
+
i = skipBraces(src, i + 1);
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
if (src[i] === quote) return i + 1;
|
|
2612
|
+
i++;
|
|
2613
|
+
}
|
|
2614
|
+
return i;
|
|
2615
|
+
}
|
|
2616
|
+
function skipBraces(src, start) {
|
|
2617
|
+
let depth = 1;
|
|
2618
|
+
let i = start + 1;
|
|
2619
|
+
while (i < src.length && depth > 0) {
|
|
2620
|
+
const ch = src[i];
|
|
2621
|
+
if (ch === "{") {
|
|
2622
|
+
depth++;
|
|
2623
|
+
i++;
|
|
2624
|
+
} else if (ch === "}") {
|
|
2625
|
+
depth--;
|
|
2626
|
+
i++;
|
|
2627
|
+
} else if (ch === '"' || ch === "'" || ch === "`") {
|
|
2628
|
+
i = skipString2(src, i);
|
|
2629
|
+
} else {
|
|
2630
|
+
i++;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
return i;
|
|
2634
|
+
}
|
|
2635
|
+
function dynamicAttrHint(kind) {
|
|
2636
|
+
if (kind === "region") {
|
|
2637
|
+
return `Use a string literal: \`data-uidex-region="id"\`, or declare the region via \`export const uidex = { region: "id" } as const satisfies Uidex.Region\` on the file that passes the region value`;
|
|
2638
|
+
}
|
|
2639
|
+
return `The scanner requires string-literal attribute values. If this component forwards the annotation via a prop, restructure so the caller provides the annotated element directly (e.g. via a slot or render prop) with a string-literal \`data-uidex\` attribute`;
|
|
2640
|
+
}
|
|
2502
2641
|
function stableStringify(value) {
|
|
2503
2642
|
return JSON.stringify(value, stableReplacer);
|
|
2504
2643
|
}
|
|
@@ -2513,7 +2652,7 @@ function stableReplacer(_key, value) {
|
|
|
2513
2652
|
return value;
|
|
2514
2653
|
}
|
|
2515
2654
|
|
|
2516
|
-
// src/scan/emit.ts
|
|
2655
|
+
// src/scanner/scan/emit.ts
|
|
2517
2656
|
function sortById(arr) {
|
|
2518
2657
|
return [...arr].sort((a, b) => a.id.localeCompare(b.id));
|
|
2519
2658
|
}
|
|
@@ -2637,6 +2776,7 @@ function emit(opts) {
|
|
|
2637
2776
|
lines.push(" export interface Feature {");
|
|
2638
2777
|
lines.push(" feature: FeatureId | false");
|
|
2639
2778
|
lines.push(" name?: string");
|
|
2779
|
+
lines.push(" features?: readonly FeatureId[]");
|
|
2640
2780
|
lines.push(" acceptance?: readonly string[]");
|
|
2641
2781
|
lines.push(" description?: string");
|
|
2642
2782
|
lines.push(" }");
|
|
@@ -2651,6 +2791,11 @@ function emit(opts) {
|
|
|
2651
2791
|
lines.push(" acceptance?: readonly string[]");
|
|
2652
2792
|
lines.push(" description?: string");
|
|
2653
2793
|
lines.push(" }");
|
|
2794
|
+
lines.push(" export interface Region {");
|
|
2795
|
+
lines.push(" region: RegionId | false");
|
|
2796
|
+
lines.push(" name?: string");
|
|
2797
|
+
lines.push(" description?: string");
|
|
2798
|
+
lines.push(" }");
|
|
2654
2799
|
lines.push(" export interface Flow {");
|
|
2655
2800
|
lines.push(" flow: FlowId");
|
|
2656
2801
|
lines.push(" name?: string");
|
|
@@ -2693,7 +2838,7 @@ function emit(opts) {
|
|
|
2693
2838
|
return lines.join("\n");
|
|
2694
2839
|
}
|
|
2695
2840
|
|
|
2696
|
-
// src/scan/git.ts
|
|
2841
|
+
// src/scanner/scan/git.ts
|
|
2697
2842
|
import { execSync } from "child_process";
|
|
2698
2843
|
function runGit(args, cwd) {
|
|
2699
2844
|
try {
|
|
@@ -2725,7 +2870,7 @@ function parseGitHubRef(ref) {
|
|
|
2725
2870
|
return m ? m[1] : null;
|
|
2726
2871
|
}
|
|
2727
2872
|
|
|
2728
|
-
// src/scan/scaffold.ts
|
|
2873
|
+
// src/scanner/scan/scaffold.ts
|
|
2729
2874
|
import * as fs3 from "fs";
|
|
2730
2875
|
import * as path5 from "path";
|
|
2731
2876
|
function scaffoldWidgetSpec(opts) {
|
|
@@ -2788,7 +2933,7 @@ function renderSpec(args) {
|
|
|
2788
2933
|
return lines.join("\n");
|
|
2789
2934
|
}
|
|
2790
2935
|
|
|
2791
|
-
// src/scan/pipeline.ts
|
|
2936
|
+
// src/scanner/scan/pipeline.ts
|
|
2792
2937
|
import * as fs4 from "fs";
|
|
2793
2938
|
import * as path6 from "path";
|
|
2794
2939
|
function runScan(opts = {}) {
|
|
@@ -2862,18 +3007,18 @@ function writeScanResult(result) {
|
|
|
2862
3007
|
fs4.writeFileSync(result.outputPath, result.generated, "utf8");
|
|
2863
3008
|
}
|
|
2864
3009
|
|
|
2865
|
-
// src/scan/cli.ts
|
|
3010
|
+
// src/scanner/scan/cli.ts
|
|
2866
3011
|
import * as fs7 from "fs";
|
|
2867
3012
|
import * as path9 from "path";
|
|
2868
3013
|
|
|
2869
|
-
// src/scan/ai/index.ts
|
|
3014
|
+
// src/scanner/scan/ai/index.ts
|
|
2870
3015
|
import * as p from "@clack/prompts";
|
|
2871
3016
|
|
|
2872
|
-
// src/scan/ai/providers/claude.ts
|
|
3017
|
+
// src/scanner/scan/ai/providers/claude.ts
|
|
2873
3018
|
import * as fs6 from "fs";
|
|
2874
3019
|
import * as path8 from "path";
|
|
2875
3020
|
|
|
2876
|
-
// src/scan/ai/templates.ts
|
|
3021
|
+
// src/scanner/scan/ai/templates.ts
|
|
2877
3022
|
import * as fs5 from "fs";
|
|
2878
3023
|
import * as path7 from "path";
|
|
2879
3024
|
function templatePath(rel) {
|
|
@@ -2900,15 +3045,16 @@ function readTemplate(rel) {
|
|
|
2900
3045
|
return fs5.readFileSync(templatePath(rel), "utf8");
|
|
2901
3046
|
}
|
|
2902
3047
|
|
|
2903
|
-
// src/scan/ai/providers/claude.ts
|
|
3048
|
+
// src/scanner/scan/ai/providers/claude.ts
|
|
2904
3049
|
var CLAUDE_FILES = [
|
|
2905
3050
|
{ dest: ".claude/rules/uidex.md", template: "claude/rules.md" },
|
|
2906
|
-
{ dest: ".claude/commands/uidex/audit.md", template: "claude/audit.md" }
|
|
3051
|
+
{ dest: ".claude/commands/uidex/audit.md", template: "claude/audit.md" },
|
|
3052
|
+
{ dest: ".claude/commands/uidex/api.md", template: "claude/api.md" }
|
|
2907
3053
|
];
|
|
2908
3054
|
var claudeProvider = {
|
|
2909
3055
|
id: "claude",
|
|
2910
3056
|
label: "Claude Code",
|
|
2911
|
-
description: "Adds .claude/rules/uidex.md and
|
|
3057
|
+
description: "Adds .claude/rules/uidex.md, /uidex:audit, and /uidex:api slash commands.",
|
|
2912
3058
|
async install({ cwd, force }) {
|
|
2913
3059
|
const changes = [];
|
|
2914
3060
|
for (const file of CLAUDE_FILES) {
|
|
@@ -2956,13 +3102,13 @@ function cleanupEmpty(dir) {
|
|
|
2956
3102
|
}
|
|
2957
3103
|
}
|
|
2958
3104
|
|
|
2959
|
-
// src/scan/ai/providers/index.ts
|
|
3105
|
+
// src/scanner/scan/ai/providers/index.ts
|
|
2960
3106
|
var PROVIDERS = [claudeProvider];
|
|
2961
3107
|
function getProvider(id) {
|
|
2962
3108
|
return PROVIDERS.find((p2) => p2.id === id);
|
|
2963
3109
|
}
|
|
2964
3110
|
|
|
2965
|
-
// src/scan/ai/index.ts
|
|
3111
|
+
// src/scanner/scan/ai/index.ts
|
|
2966
3112
|
async function runAiCommand(opts) {
|
|
2967
3113
|
const { cwd, argv } = opts;
|
|
2968
3114
|
const sub = argv[0];
|
|
@@ -3073,8 +3219,8 @@ function err(exitCode, stderr) {
|
|
|
3073
3219
|
return { exitCode, stdout: "", stderr };
|
|
3074
3220
|
}
|
|
3075
3221
|
|
|
3076
|
-
// src/
|
|
3077
|
-
function
|
|
3222
|
+
// src/scanner/cli/parse-args.ts
|
|
3223
|
+
function parseArgs(args) {
|
|
3078
3224
|
const positional = [];
|
|
3079
3225
|
const flags = {};
|
|
3080
3226
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -3098,9 +3244,11 @@ function parseFlags2(args) {
|
|
|
3098
3244
|
}
|
|
3099
3245
|
return { positional, flags };
|
|
3100
3246
|
}
|
|
3247
|
+
|
|
3248
|
+
// src/scanner/scan/cli.ts
|
|
3101
3249
|
async function run(opts) {
|
|
3102
3250
|
const cwd = opts.cwd ?? process.cwd();
|
|
3103
|
-
const { positional, flags } =
|
|
3251
|
+
const { positional, flags } = parseArgs(opts.argv);
|
|
3104
3252
|
const command = positional[0] ?? "help";
|
|
3105
3253
|
const writer = createWriter();
|
|
3106
3254
|
try {
|
|
@@ -3144,6 +3292,10 @@ function helpText2() {
|
|
|
3144
3292
|
" scan [flags] Run the scanner pipeline",
|
|
3145
3293
|
" scaffold widget <id> Emit a Playwright spec from a widget's acceptance",
|
|
3146
3294
|
" ai <install|uninstall|providers> Manage AI assistant integrations",
|
|
3295
|
+
" api <METHOD> <PATH> Call the uidex API",
|
|
3296
|
+
" api --list Show available API routes",
|
|
3297
|
+
" api login Authenticate via browser",
|
|
3298
|
+
" api login --token <tok> Store an auth token directly",
|
|
3147
3299
|
"",
|
|
3148
3300
|
"Flags:",
|
|
3149
3301
|
" --check Verify the on-disk gen file matches a fresh scan; exit non-zero on drift (read-only)",
|