uidex 0.4.0 → 0.5.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.
Files changed (42) hide show
  1. package/dist/cli/cli.cjs +996 -73
  2. package/dist/cli/cli.cjs.map +1 -1
  3. package/dist/cloud/index.cjs +375 -72
  4. package/dist/cloud/index.cjs.map +1 -1
  5. package/dist/cloud/index.d.cts +82 -0
  6. package/dist/cloud/index.d.ts +82 -0
  7. package/dist/cloud/index.js +376 -71
  8. package/dist/cloud/index.js.map +1 -1
  9. package/dist/headless/index.cjs +620 -469
  10. package/dist/headless/index.cjs.map +1 -1
  11. package/dist/headless/index.d.cts +77 -75
  12. package/dist/headless/index.d.ts +77 -75
  13. package/dist/headless/index.js +624 -469
  14. package/dist/headless/index.js.map +1 -1
  15. package/dist/index.cjs +4255 -2884
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +275 -234
  18. package/dist/index.d.ts +275 -234
  19. package/dist/index.js +4277 -2890
  20. package/dist/index.js.map +1 -1
  21. package/dist/playwright/index.cjs +4 -4
  22. package/dist/playwright/index.cjs.map +1 -1
  23. package/dist/playwright/index.js +3 -3
  24. package/dist/playwright/index.js.map +1 -1
  25. package/dist/playwright/reporter.cjs +3 -3
  26. package/dist/playwright/reporter.cjs.map +1 -1
  27. package/dist/playwright/reporter.js +3 -3
  28. package/dist/playwright/reporter.js.map +1 -1
  29. package/dist/react/index.cjs +4298 -2908
  30. package/dist/react/index.cjs.map +1 -1
  31. package/dist/react/index.d.cts +206 -200
  32. package/dist/react/index.d.ts +206 -200
  33. package/dist/react/index.js +4334 -2924
  34. package/dist/react/index.js.map +1 -1
  35. package/dist/scan/index.cjs +91 -40
  36. package/dist/scan/index.cjs.map +1 -1
  37. package/dist/scan/index.d.cts +26 -0
  38. package/dist/scan/index.d.ts +26 -0
  39. package/dist/scan/index.js +90 -39
  40. package/dist/scan/index.js.map +1 -1
  41. package/package.json +23 -22
  42. package/templates/claude/api.md +110 -0
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
- // src/scan/index.ts
30
+ // src/scanner/scan/index.ts
31
31
  var scan_exports = {};
32
32
  __export(scan_exports, {
33
33
  CONFIG_FILENAME: () => CONFIG_FILENAME,
@@ -54,11 +54,11 @@ __export(scan_exports, {
54
54
  });
55
55
  module.exports = __toCommonJS(scan_exports);
56
56
 
57
- // src/scan/discover.ts
57
+ // src/scanner/scan/discover.ts
58
58
  var fs = __toESM(require("fs"), 1);
59
59
  var path = __toESM(require("path"), 1);
60
60
 
61
- // src/scan/config.ts
61
+ // src/scanner/scan/config.ts
62
62
  var DEFAULT_TYPE_MODE = "strict";
63
63
  var WELL_KNOWN_FILES = {
64
64
  page: "uidex.page.ts",
@@ -222,7 +222,7 @@ var DEFAULT_CONVENTIONS = {
222
222
  regions: "landmarks"
223
223
  };
224
224
 
225
- // src/scan/discover.ts
225
+ // src/scanner/scan/discover.ts
226
226
  var CONFIG_FILENAME = ".uidex.json";
227
227
  var SKIP_DIRS = /* @__PURE__ */ new Set([
228
228
  "node_modules",
@@ -281,7 +281,7 @@ function discover(options = {}) {
281
281
  return results.sort((a, b) => a.configPath.localeCompare(b.configPath));
282
282
  }
283
283
 
284
- // src/scan/walk.ts
284
+ // src/scanner/scan/walk.ts
285
285
  var fs2 = __toESM(require("fs"), 1);
286
286
  var path2 = __toESM(require("path"), 1);
287
287
  var DEFAULT_INCLUDES = ["**/*.{ts,tsx,js,jsx,mjs,cjs}"];
@@ -406,7 +406,7 @@ function* walkDir(root, dir) {
406
406
  }
407
407
  }
408
408
 
409
- // src/scan/extract-uidex-export.ts
409
+ // src/scanner/scan/extract-uidex-export.ts
410
410
  var KIND_DISCRIMINATORS = [
411
411
  "page",
412
412
  "feature",
@@ -424,7 +424,13 @@ var ALLOWED_FIELDS = {
424
424
  "acceptance",
425
425
  "description"
426
426
  ]),
427
- feature: /* @__PURE__ */ new Set(["feature", "name", "acceptance", "description"]),
427
+ feature: /* @__PURE__ */ new Set([
428
+ "feature",
429
+ "name",
430
+ "features",
431
+ "acceptance",
432
+ "description"
433
+ ]),
428
434
  primitive: /* @__PURE__ */ new Set(["primitive", "name", "description"]),
429
435
  widget: /* @__PURE__ */ new Set(["widget", "name", "acceptance", "description"]),
430
436
  flow: /* @__PURE__ */ new Set(["flow", "notFlow", "name", "description"])
@@ -1160,7 +1166,7 @@ function buildMetadata(value, file, headerPos, diagnostics) {
1160
1166
  line: pos.line
1161
1167
  });
1162
1168
  }
1163
- const features = kind === "page" ? readStringArrayField(byKey, "features") : void 0;
1169
+ const features = kind === "page" || kind === "feature" ? readStringArrayField(byKey, "features") : void 0;
1164
1170
  const widgets = kind === "page" ? readStringArrayField(byKey, "widgets") : void 0;
1165
1171
  const notFlow = kind === "flow" && discriminator === "notFlow" ? true : void 0;
1166
1172
  const metadata = {
@@ -1255,7 +1261,7 @@ function posAt(content, offset) {
1255
1261
  return { offset, line, column: offset - lineStart + 1 };
1256
1262
  }
1257
1263
 
1258
- // src/scan/jsx-ancestry.ts
1264
+ // src/scanner/scan/jsx-ancestry.ts
1259
1265
  var DATA_ATTR_RE = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
1260
1266
  function parseDataAttrs(tagSource) {
1261
1267
  if (!tagSource.includes("data-uidex")) return [];
@@ -1444,7 +1450,7 @@ function findTagEnd(content, start) {
1444
1450
  return -1;
1445
1451
  }
1446
1452
 
1447
- // src/scan/extract.ts
1453
+ // src/scanner/scan/extract.ts
1448
1454
  var JSDOC_BLOCK = /\/\*\*([\s\S]*?)\*\//g;
1449
1455
  function lineAt(content, index) {
1450
1456
  let line = 1;
@@ -1547,10 +1553,10 @@ function extractOne(file) {
1547
1553
  return annotations;
1548
1554
  }
1549
1555
 
1550
- // src/scan/resolve.ts
1556
+ // src/scanner/scan/resolve.ts
1551
1557
  var path3 = __toESM(require("path"), 1);
1552
1558
 
1553
- // src/entities/types.ts
1559
+ // src/shared/entities/types.ts
1554
1560
  var ENTITY_KINDS = [
1555
1561
  "route",
1556
1562
  "page",
@@ -1583,7 +1589,7 @@ function assertEntityKind(kind) {
1583
1589
  if (!KIND_SET.has(kind)) throw new UnknownEntityKindError(kind);
1584
1590
  }
1585
1591
 
1586
- // src/entities/registry.ts
1592
+ // src/shared/entities/registry.ts
1587
1593
  function emptyStore() {
1588
1594
  return {
1589
1595
  route: /* @__PURE__ */ new Map(),
@@ -1665,10 +1671,33 @@ function createRegistry() {
1665
1671
  return ids.has(entity.id);
1666
1672
  });
1667
1673
  };
1668
- return { add, get, list, query, byScope, touchedBy };
1674
+ const reports = /* @__PURE__ */ new Map();
1675
+ const reportsCbs = /* @__PURE__ */ new Set();
1676
+ const setReports = (kind, id, records) => {
1677
+ reports.set(`${kind}:${id}`, records);
1678
+ for (const cb of reportsCbs) cb();
1679
+ };
1680
+ const getReports = (kind, id) => reports.get(`${kind}:${id}`) ?? [];
1681
+ const listReportKeys = () => Array.from(reports.keys());
1682
+ const onReportsChange = (cb) => {
1683
+ reportsCbs.add(cb);
1684
+ return () => reportsCbs.delete(cb);
1685
+ };
1686
+ return {
1687
+ add,
1688
+ get,
1689
+ list,
1690
+ query,
1691
+ byScope,
1692
+ touchedBy,
1693
+ setReports,
1694
+ getReports,
1695
+ listReportKeys,
1696
+ onReportsChange
1697
+ };
1669
1698
  }
1670
1699
 
1671
- // src/scan/routes.ts
1700
+ // src/scanner/scan/routes.ts
1672
1701
  var PAGE_BASENAME = /^page\.(tsx|ts|jsx|js|mjs|cjs)$/;
1673
1702
  var PAGES_ROUTER_BASENAME = /\.(tsx|ts|jsx|js|mjs|cjs)$/;
1674
1703
  var ROUTE_BASENAME = /^route\.(tsx|ts|jsx|js|mjs|cjs)$/;
@@ -1734,7 +1763,7 @@ function pathToId(routePath) {
1734
1763
  return routePath.replace(/^\/+/, "").replace(/\[\.{3}([^\]]+)\]/g, "$1").replace(/\[([^\]]+)\]/g, "$1").replace(/\//g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1735
1764
  }
1736
1765
 
1737
- // src/scan/resolve.ts
1766
+ // src/scanner/scan/resolve.ts
1738
1767
  var DOM_ATTR_KINDS = /* @__PURE__ */ new Set([
1739
1768
  "element",
1740
1769
  "region",
@@ -2219,7 +2248,7 @@ function dedupe(arr) {
2219
2248
  return Array.from(new Set(arr));
2220
2249
  }
2221
2250
 
2222
- // src/scan/audit.ts
2251
+ // src/scanner/scan/audit.ts
2223
2252
  var path4 = __toESM(require("path"), 1);
2224
2253
  var MARKER_FILENAMES = ["UIDEX_PAGE.md", "UIDEX_FEATURE.md"];
2225
2254
  function audit(opts) {
@@ -2374,6 +2403,15 @@ function audit(opts) {
2374
2403
  const primitives = registry.list("primitive");
2375
2404
  const byName = /* @__PURE__ */ new Map();
2376
2405
  for (const p2 of primitives) byName.set(p2.id, p2);
2406
+ const declaredFeatures = /* @__PURE__ */ new Map();
2407
+ for (const ef of extracted) {
2408
+ if (!ef.metadata) continue;
2409
+ for (const m of ef.metadata) {
2410
+ if (m.features && m.features.length > 0) {
2411
+ declaredFeatures.set(ef.file.displayPath, new Set(m.features));
2412
+ }
2413
+ }
2414
+ }
2377
2415
  for (const f of files) {
2378
2416
  const importRe = /import\s+(?:[^'"]+)\s+from\s+['"]([^'"]+)['"]/g;
2379
2417
  let m;
@@ -2388,14 +2426,19 @@ function audit(opts) {
2388
2426
  if (!scope) continue;
2389
2427
  const [kind, id] = scope.split(":");
2390
2428
  const importerSegments = f.displayPath.split("/");
2391
- if (!importerSegments.includes(id) || !importerSegments.includes(kind + "s")) {
2392
- diagnostics.push({
2393
- code: "scope-leak",
2394
- severity: "warning",
2395
- message: `Primitive "${primitive.id}" is scoped to ${scope} but is imported from ${f.displayPath}`,
2396
- file: f.displayPath
2397
- });
2429
+ if (importerSegments.includes(id) && importerSegments.includes(kind + "s")) {
2430
+ continue;
2431
+ }
2432
+ if (kind === "feature" && importerSegments.includes(id)) continue;
2433
+ if (kind === "feature" && declaredFeatures.get(f.displayPath)?.has(id)) {
2434
+ continue;
2398
2435
  }
2436
+ diagnostics.push({
2437
+ code: "scope-leak",
2438
+ severity: "warning",
2439
+ message: `Primitive "${primitive.id}" is scoped to ${scope} but is imported from ${f.displayPath}`,
2440
+ file: f.displayPath
2441
+ });
2399
2442
  }
2400
2443
  }
2401
2444
  }
@@ -2569,7 +2612,7 @@ function stableReplacer(_key, value) {
2569
2612
  return value;
2570
2613
  }
2571
2614
 
2572
- // src/scan/emit.ts
2615
+ // src/scanner/scan/emit.ts
2573
2616
  function sortById(arr) {
2574
2617
  return [...arr].sort((a, b) => a.id.localeCompare(b.id));
2575
2618
  }
@@ -2693,6 +2736,7 @@ function emit(opts) {
2693
2736
  lines.push(" export interface Feature {");
2694
2737
  lines.push(" feature: FeatureId | false");
2695
2738
  lines.push(" name?: string");
2739
+ lines.push(" features?: readonly FeatureId[]");
2696
2740
  lines.push(" acceptance?: readonly string[]");
2697
2741
  lines.push(" description?: string");
2698
2742
  lines.push(" }");
@@ -2749,7 +2793,7 @@ function emit(opts) {
2749
2793
  return lines.join("\n");
2750
2794
  }
2751
2795
 
2752
- // src/scan/git.ts
2796
+ // src/scanner/scan/git.ts
2753
2797
  var import_node_child_process = require("child_process");
2754
2798
  function runGit(args, cwd) {
2755
2799
  try {
@@ -2781,7 +2825,7 @@ function parseGitHubRef(ref) {
2781
2825
  return m ? m[1] : null;
2782
2826
  }
2783
2827
 
2784
- // src/scan/scaffold.ts
2828
+ // src/scanner/scan/scaffold.ts
2785
2829
  var fs3 = __toESM(require("fs"), 1);
2786
2830
  var path5 = __toESM(require("path"), 1);
2787
2831
  function scaffoldWidgetSpec(opts) {
@@ -2844,7 +2888,7 @@ function renderSpec(args) {
2844
2888
  return lines.join("\n");
2845
2889
  }
2846
2890
 
2847
- // src/scan/pipeline.ts
2891
+ // src/scanner/scan/pipeline.ts
2848
2892
  var fs4 = __toESM(require("fs"), 1);
2849
2893
  var path6 = __toESM(require("path"), 1);
2850
2894
  function runScan(opts = {}) {
@@ -2918,18 +2962,18 @@ function writeScanResult(result) {
2918
2962
  fs4.writeFileSync(result.outputPath, result.generated, "utf8");
2919
2963
  }
2920
2964
 
2921
- // src/scan/cli.ts
2965
+ // src/scanner/scan/cli.ts
2922
2966
  var fs7 = __toESM(require("fs"), 1);
2923
2967
  var path9 = __toESM(require("path"), 1);
2924
2968
 
2925
- // src/scan/ai/index.ts
2969
+ // src/scanner/scan/ai/index.ts
2926
2970
  var p = __toESM(require("@clack/prompts"), 1);
2927
2971
 
2928
- // src/scan/ai/providers/claude.ts
2972
+ // src/scanner/scan/ai/providers/claude.ts
2929
2973
  var fs6 = __toESM(require("fs"), 1);
2930
2974
  var path8 = __toESM(require("path"), 1);
2931
2975
 
2932
- // src/scan/ai/templates.ts
2976
+ // src/scanner/scan/ai/templates.ts
2933
2977
  var fs5 = __toESM(require("fs"), 1);
2934
2978
  var path7 = __toESM(require("path"), 1);
2935
2979
  function templatePath(rel) {
@@ -2956,15 +3000,16 @@ function readTemplate(rel) {
2956
3000
  return fs5.readFileSync(templatePath(rel), "utf8");
2957
3001
  }
2958
3002
 
2959
- // src/scan/ai/providers/claude.ts
3003
+ // src/scanner/scan/ai/providers/claude.ts
2960
3004
  var CLAUDE_FILES = [
2961
3005
  { dest: ".claude/rules/uidex.md", template: "claude/rules.md" },
2962
- { dest: ".claude/commands/uidex/audit.md", template: "claude/audit.md" }
3006
+ { dest: ".claude/commands/uidex/audit.md", template: "claude/audit.md" },
3007
+ { dest: ".claude/commands/uidex/api.md", template: "claude/api.md" }
2963
3008
  ];
2964
3009
  var claudeProvider = {
2965
3010
  id: "claude",
2966
3011
  label: "Claude Code",
2967
- description: "Adds .claude/rules/uidex.md and the /uidex:audit slash command.",
3012
+ description: "Adds .claude/rules/uidex.md, /uidex:audit, and /uidex:api slash commands.",
2968
3013
  async install({ cwd, force }) {
2969
3014
  const changes = [];
2970
3015
  for (const file of CLAUDE_FILES) {
@@ -3012,13 +3057,13 @@ function cleanupEmpty(dir) {
3012
3057
  }
3013
3058
  }
3014
3059
 
3015
- // src/scan/ai/providers/index.ts
3060
+ // src/scanner/scan/ai/providers/index.ts
3016
3061
  var PROVIDERS = [claudeProvider];
3017
3062
  function getProvider(id) {
3018
3063
  return PROVIDERS.find((p2) => p2.id === id);
3019
3064
  }
3020
3065
 
3021
- // src/scan/ai/index.ts
3066
+ // src/scanner/scan/ai/index.ts
3022
3067
  async function runAiCommand(opts) {
3023
3068
  const { cwd, argv } = opts;
3024
3069
  const sub = argv[0];
@@ -3129,8 +3174,8 @@ function err(exitCode, stderr) {
3129
3174
  return { exitCode, stdout: "", stderr };
3130
3175
  }
3131
3176
 
3132
- // src/scan/cli.ts
3133
- function parseFlags2(args) {
3177
+ // src/scanner/cli/parse-args.ts
3178
+ function parseArgs(args) {
3134
3179
  const positional = [];
3135
3180
  const flags = {};
3136
3181
  for (let i = 0; i < args.length; i++) {
@@ -3154,9 +3199,11 @@ function parseFlags2(args) {
3154
3199
  }
3155
3200
  return { positional, flags };
3156
3201
  }
3202
+
3203
+ // src/scanner/scan/cli.ts
3157
3204
  async function run(opts) {
3158
3205
  const cwd = opts.cwd ?? process.cwd();
3159
- const { positional, flags } = parseFlags2(opts.argv);
3206
+ const { positional, flags } = parseArgs(opts.argv);
3160
3207
  const command = positional[0] ?? "help";
3161
3208
  const writer = createWriter();
3162
3209
  try {
@@ -3200,6 +3247,10 @@ function helpText2() {
3200
3247
  " scan [flags] Run the scanner pipeline",
3201
3248
  " scaffold widget <id> Emit a Playwright spec from a widget's acceptance",
3202
3249
  " ai <install|uninstall|providers> Manage AI assistant integrations",
3250
+ " api <METHOD> <PATH> Call the uidex API",
3251
+ " api --list Show available API routes",
3252
+ " api login Authenticate via browser",
3253
+ " api login --token <tok> Store an auth token directly",
3203
3254
  "",
3204
3255
  "Flags:",
3205
3256
  " --check Verify the on-disk gen file matches a fresh scan; exit non-zero on drift (read-only)",