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/cli/cli.cjs
CHANGED
|
@@ -23,18 +23,18 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
mod
|
|
24
24
|
));
|
|
25
25
|
|
|
26
|
-
// src/scan/cli.ts
|
|
26
|
+
// src/scanner/scan/cli.ts
|
|
27
27
|
var fs7 = __toESM(require("fs"), 1);
|
|
28
28
|
var path9 = __toESM(require("path"), 1);
|
|
29
29
|
|
|
30
|
-
// src/scan/ai/index.ts
|
|
30
|
+
// src/scanner/scan/ai/index.ts
|
|
31
31
|
var p = __toESM(require("@clack/prompts"), 1);
|
|
32
32
|
|
|
33
|
-
// src/scan/ai/providers/claude.ts
|
|
33
|
+
// src/scanner/scan/ai/providers/claude.ts
|
|
34
34
|
var fs2 = __toESM(require("fs"), 1);
|
|
35
35
|
var path2 = __toESM(require("path"), 1);
|
|
36
36
|
|
|
37
|
-
// src/scan/ai/templates.ts
|
|
37
|
+
// src/scanner/scan/ai/templates.ts
|
|
38
38
|
var fs = __toESM(require("fs"), 1);
|
|
39
39
|
var path = __toESM(require("path"), 1);
|
|
40
40
|
function templatePath(rel) {
|
|
@@ -61,15 +61,16 @@ function readTemplate(rel) {
|
|
|
61
61
|
return fs.readFileSync(templatePath(rel), "utf8");
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// src/scan/ai/providers/claude.ts
|
|
64
|
+
// src/scanner/scan/ai/providers/claude.ts
|
|
65
65
|
var CLAUDE_FILES = [
|
|
66
66
|
{ dest: ".claude/rules/uidex.md", template: "claude/rules.md" },
|
|
67
|
-
{ dest: ".claude/commands/uidex/audit.md", template: "claude/audit.md" }
|
|
67
|
+
{ dest: ".claude/commands/uidex/audit.md", template: "claude/audit.md" },
|
|
68
|
+
{ dest: ".claude/commands/uidex/api.md", template: "claude/api.md" }
|
|
68
69
|
];
|
|
69
70
|
var claudeProvider = {
|
|
70
71
|
id: "claude",
|
|
71
72
|
label: "Claude Code",
|
|
72
|
-
description: "Adds .claude/rules/uidex.md and
|
|
73
|
+
description: "Adds .claude/rules/uidex.md, /uidex:audit, and /uidex:api slash commands.",
|
|
73
74
|
async install({ cwd, force }) {
|
|
74
75
|
const changes = [];
|
|
75
76
|
for (const file of CLAUDE_FILES) {
|
|
@@ -117,16 +118,16 @@ function cleanupEmpty(dir) {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
// src/scan/ai/providers/index.ts
|
|
121
|
+
// src/scanner/scan/ai/providers/index.ts
|
|
121
122
|
var PROVIDERS = [claudeProvider];
|
|
122
123
|
function getProvider(id) {
|
|
123
124
|
return PROVIDERS.find((p2) => p2.id === id);
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
// src/scan/ai/index.ts
|
|
127
|
+
// src/scanner/scan/ai/index.ts
|
|
127
128
|
async function runAiCommand(opts) {
|
|
128
|
-
const { cwd, argv } = opts;
|
|
129
|
-
const sub =
|
|
129
|
+
const { cwd, argv: argv2 } = opts;
|
|
130
|
+
const sub = argv2[0];
|
|
130
131
|
if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
|
|
131
132
|
return out(0, helpText());
|
|
132
133
|
}
|
|
@@ -141,18 +142,18 @@ async function runAiCommand(opts) {
|
|
|
141
142
|
|
|
142
143
|
${helpText()}`);
|
|
143
144
|
}
|
|
144
|
-
const flags = parseFlags(
|
|
145
|
+
const flags = parseFlags(argv2.slice(1));
|
|
145
146
|
const provider = await selectProvider(opts, flags.provider);
|
|
146
147
|
if (!provider) return out(0, "Cancelled.\n");
|
|
147
148
|
if (sub === "install") {
|
|
148
|
-
const
|
|
149
|
+
const result3 = await provider.install({
|
|
149
150
|
cwd,
|
|
150
151
|
force: flags.force === true
|
|
151
152
|
});
|
|
152
|
-
return out(0, formatChanges(provider, "Installed",
|
|
153
|
+
return out(0, formatChanges(provider, "Installed", result3));
|
|
153
154
|
}
|
|
154
|
-
const
|
|
155
|
-
return out(0, formatChanges(provider, "Uninstalled",
|
|
155
|
+
const result2 = await provider.uninstall({ cwd });
|
|
156
|
+
return out(0, formatChanges(provider, "Uninstalled", result2));
|
|
156
157
|
}
|
|
157
158
|
async function selectProvider(opts, explicit) {
|
|
158
159
|
if (explicit) {
|
|
@@ -195,9 +196,9 @@ function parseFlags(args) {
|
|
|
195
196
|
}
|
|
196
197
|
return flags;
|
|
197
198
|
}
|
|
198
|
-
function formatChanges(provider, verb,
|
|
199
|
+
function formatChanges(provider, verb, result2) {
|
|
199
200
|
const lines = [`${verb} ${provider.label}:`];
|
|
200
|
-
for (const c of
|
|
201
|
+
for (const c of result2.changes) lines.push(` ${describe(c)}`);
|
|
201
202
|
return lines.join("\n") + "\n";
|
|
202
203
|
}
|
|
203
204
|
function describe(c) {
|
|
@@ -234,11 +235,11 @@ function err(exitCode, stderr) {
|
|
|
234
235
|
return { exitCode, stdout: "", stderr };
|
|
235
236
|
}
|
|
236
237
|
|
|
237
|
-
// src/scan/discover.ts
|
|
238
|
+
// src/scanner/scan/discover.ts
|
|
238
239
|
var fs3 = __toESM(require("fs"), 1);
|
|
239
240
|
var path3 = __toESM(require("path"), 1);
|
|
240
241
|
|
|
241
|
-
// src/scan/config.ts
|
|
242
|
+
// src/scanner/scan/config.ts
|
|
242
243
|
var DEFAULT_TYPE_MODE = "strict";
|
|
243
244
|
var WELL_KNOWN_FILES = {
|
|
244
245
|
page: "uidex.page.ts",
|
|
@@ -402,7 +403,7 @@ var DEFAULT_CONVENTIONS = {
|
|
|
402
403
|
regions: "landmarks"
|
|
403
404
|
};
|
|
404
405
|
|
|
405
|
-
// src/scan/discover.ts
|
|
406
|
+
// src/scanner/scan/discover.ts
|
|
406
407
|
var CONFIG_FILENAME = ".uidex.json";
|
|
407
408
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
408
409
|
"node_modules",
|
|
@@ -461,14 +462,14 @@ function discover(options = {}) {
|
|
|
461
462
|
return results.sort((a, b) => a.configPath.localeCompare(b.configPath));
|
|
462
463
|
}
|
|
463
464
|
|
|
464
|
-
// src/scan/pipeline.ts
|
|
465
|
+
// src/scanner/scan/pipeline.ts
|
|
465
466
|
var fs5 = __toESM(require("fs"), 1);
|
|
466
467
|
var path7 = __toESM(require("path"), 1);
|
|
467
468
|
|
|
468
|
-
// src/scan/audit.ts
|
|
469
|
+
// src/scanner/scan/audit.ts
|
|
469
470
|
var path4 = __toESM(require("path"), 1);
|
|
470
471
|
|
|
471
|
-
// src/entities/types.ts
|
|
472
|
+
// src/shared/entities/types.ts
|
|
472
473
|
var ENTITY_KINDS = [
|
|
473
474
|
"route",
|
|
474
475
|
"page",
|
|
@@ -501,7 +502,7 @@ function assertEntityKind(kind) {
|
|
|
501
502
|
if (!KIND_SET.has(kind)) throw new UnknownEntityKindError(kind);
|
|
502
503
|
}
|
|
503
504
|
|
|
504
|
-
// src/entities/registry.ts
|
|
505
|
+
// src/shared/entities/registry.ts
|
|
505
506
|
function emptyStore() {
|
|
506
507
|
return {
|
|
507
508
|
route: /* @__PURE__ */ new Map(),
|
|
@@ -565,11 +566,11 @@ function createRegistry() {
|
|
|
565
566
|
};
|
|
566
567
|
const query = (predicate) => {
|
|
567
568
|
const flows = getFlows();
|
|
568
|
-
const
|
|
569
|
+
const result2 = [];
|
|
569
570
|
for (const entity of allEntities()) {
|
|
570
|
-
if (predicate(entity))
|
|
571
|
+
if (predicate(entity)) result2.push(freezeEntity(entity, flows));
|
|
571
572
|
}
|
|
572
|
-
return
|
|
573
|
+
return result2;
|
|
573
574
|
};
|
|
574
575
|
const byScope = (scope) => query(
|
|
575
576
|
(entity) => "scopes" in entity && Array.isArray(entity.scopes) && entity.scopes.includes(scope)
|
|
@@ -583,10 +584,33 @@ function createRegistry() {
|
|
|
583
584
|
return ids.has(entity.id);
|
|
584
585
|
});
|
|
585
586
|
};
|
|
586
|
-
|
|
587
|
+
const reports = /* @__PURE__ */ new Map();
|
|
588
|
+
const reportsCbs = /* @__PURE__ */ new Set();
|
|
589
|
+
const setReports = (kind, id, records) => {
|
|
590
|
+
reports.set(`${kind}:${id}`, records);
|
|
591
|
+
for (const cb of reportsCbs) cb();
|
|
592
|
+
};
|
|
593
|
+
const getReports = (kind, id) => reports.get(`${kind}:${id}`) ?? [];
|
|
594
|
+
const listReportKeys = () => Array.from(reports.keys());
|
|
595
|
+
const onReportsChange = (cb) => {
|
|
596
|
+
reportsCbs.add(cb);
|
|
597
|
+
return () => reportsCbs.delete(cb);
|
|
598
|
+
};
|
|
599
|
+
return {
|
|
600
|
+
add,
|
|
601
|
+
get,
|
|
602
|
+
list,
|
|
603
|
+
query,
|
|
604
|
+
byScope,
|
|
605
|
+
touchedBy,
|
|
606
|
+
setReports,
|
|
607
|
+
getReports,
|
|
608
|
+
listReportKeys,
|
|
609
|
+
onReportsChange
|
|
610
|
+
};
|
|
587
611
|
}
|
|
588
612
|
|
|
589
|
-
// src/scan/audit.ts
|
|
613
|
+
// src/scanner/scan/audit.ts
|
|
590
614
|
var MARKER_FILENAMES = ["UIDEX_PAGE.md", "UIDEX_FEATURE.md"];
|
|
591
615
|
function audit(opts) {
|
|
592
616
|
const diagnostics = [];
|
|
@@ -714,18 +738,39 @@ function audit(opts) {
|
|
|
714
738
|
}
|
|
715
739
|
}
|
|
716
740
|
}
|
|
741
|
+
if (lint) {
|
|
742
|
+
const dynamicAttrRe = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*\{/g;
|
|
743
|
+
for (const f of files) {
|
|
744
|
+
let m;
|
|
745
|
+
dynamicAttrRe.lastIndex = 0;
|
|
746
|
+
while ((m = dynamicAttrRe.exec(f.content)) !== null) {
|
|
747
|
+
const kind = m[1] ?? "element";
|
|
748
|
+
let line = 1;
|
|
749
|
+
for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
|
|
750
|
+
const attrName = m[1] ? `data-uidex-${m[1]}` : "data-uidex";
|
|
751
|
+
diagnostics.push({
|
|
752
|
+
code: "dynamic-attr",
|
|
753
|
+
severity: "warning",
|
|
754
|
+
message: `\`${attrName}={\u2026}\` uses a dynamic expression; the scanner cannot resolve the ${kind} id statically`,
|
|
755
|
+
file: f.displayPath,
|
|
756
|
+
line,
|
|
757
|
+
hint: dynamicAttrHint(kind)
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
717
762
|
if (lint) {
|
|
718
763
|
for (const f of files) {
|
|
719
|
-
const
|
|
720
|
-
const candidateRe = /<(button|a|input|select|textarea)(?=[\s/>])([^>]*)>/g;
|
|
764
|
+
const tagRe = /<(button|a|input|select|textarea)(?=[\s/>])/g;
|
|
721
765
|
let m;
|
|
722
|
-
while ((m =
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
766
|
+
while ((m = tagRe.exec(f.content)) !== null) {
|
|
767
|
+
const afterTag = m.index + m[0].length;
|
|
768
|
+
const closeIdx = findJsxOpeningEnd(f.content, afterTag);
|
|
769
|
+
if (closeIdx === -1) continue;
|
|
770
|
+
const attrs = f.content.slice(afterTag, closeIdx);
|
|
771
|
+
if (attrs.includes("data-uidex")) continue;
|
|
726
772
|
let line = 1;
|
|
727
|
-
for (let i = 0; i <
|
|
728
|
-
void lines;
|
|
773
|
+
for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
|
|
729
774
|
diagnostics.push({
|
|
730
775
|
code: "missing-element-annotation",
|
|
731
776
|
severity: "info",
|
|
@@ -740,6 +785,15 @@ function audit(opts) {
|
|
|
740
785
|
const primitives = registry.list("primitive");
|
|
741
786
|
const byName = /* @__PURE__ */ new Map();
|
|
742
787
|
for (const p2 of primitives) byName.set(p2.id, p2);
|
|
788
|
+
const declaredFeatures = /* @__PURE__ */ new Map();
|
|
789
|
+
for (const ef of extracted) {
|
|
790
|
+
if (!ef.metadata) continue;
|
|
791
|
+
for (const m of ef.metadata) {
|
|
792
|
+
if (m.features && m.features.length > 0) {
|
|
793
|
+
declaredFeatures.set(ef.file.displayPath, new Set(m.features));
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
743
797
|
for (const f of files) {
|
|
744
798
|
const importRe = /import\s+(?:[^'"]+)\s+from\s+['"]([^'"]+)['"]/g;
|
|
745
799
|
let m;
|
|
@@ -754,14 +808,19 @@ function audit(opts) {
|
|
|
754
808
|
if (!scope) continue;
|
|
755
809
|
const [kind, id] = scope.split(":");
|
|
756
810
|
const importerSegments = f.displayPath.split("/");
|
|
757
|
-
if (
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
});
|
|
811
|
+
if (importerSegments.includes(id) && importerSegments.includes(kind + "s")) {
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
if (kind === "feature" && importerSegments.includes(id)) continue;
|
|
815
|
+
if (kind === "feature" && declaredFeatures.get(f.displayPath)?.has(id)) {
|
|
816
|
+
continue;
|
|
764
817
|
}
|
|
818
|
+
diagnostics.push({
|
|
819
|
+
code: "scope-leak",
|
|
820
|
+
severity: "warning",
|
|
821
|
+
message: `Primitive "${primitive.id}" is scoped to ${scope} but is imported from ${f.displayPath}`,
|
|
822
|
+
file: f.displayPath
|
|
823
|
+
});
|
|
765
824
|
}
|
|
766
825
|
}
|
|
767
826
|
}
|
|
@@ -921,6 +980,63 @@ function extractEntitiesArray(source) {
|
|
|
921
980
|
}
|
|
922
981
|
return null;
|
|
923
982
|
}
|
|
983
|
+
function findJsxOpeningEnd(src, start) {
|
|
984
|
+
let i = start;
|
|
985
|
+
while (i < src.length) {
|
|
986
|
+
const ch = src[i];
|
|
987
|
+
if (ch === ">" || ch === "/" && src[i + 1] === ">") return i;
|
|
988
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
989
|
+
i = skipString(src, i);
|
|
990
|
+
} else if (ch === "{") {
|
|
991
|
+
i = skipBraces(src, i);
|
|
992
|
+
} else {
|
|
993
|
+
i++;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return -1;
|
|
997
|
+
}
|
|
998
|
+
function skipString(src, start) {
|
|
999
|
+
const quote = src[start];
|
|
1000
|
+
let i = start + 1;
|
|
1001
|
+
while (i < src.length) {
|
|
1002
|
+
if (src[i] === "\\" && quote !== "`") {
|
|
1003
|
+
i += 2;
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
if (quote === "`" && src[i] === "$" && src[i + 1] === "{") {
|
|
1007
|
+
i = skipBraces(src, i + 1);
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (src[i] === quote) return i + 1;
|
|
1011
|
+
i++;
|
|
1012
|
+
}
|
|
1013
|
+
return i;
|
|
1014
|
+
}
|
|
1015
|
+
function skipBraces(src, start) {
|
|
1016
|
+
let depth = 1;
|
|
1017
|
+
let i = start + 1;
|
|
1018
|
+
while (i < src.length && depth > 0) {
|
|
1019
|
+
const ch = src[i];
|
|
1020
|
+
if (ch === "{") {
|
|
1021
|
+
depth++;
|
|
1022
|
+
i++;
|
|
1023
|
+
} else if (ch === "}") {
|
|
1024
|
+
depth--;
|
|
1025
|
+
i++;
|
|
1026
|
+
} else if (ch === '"' || ch === "'" || ch === "`") {
|
|
1027
|
+
i = skipString(src, i);
|
|
1028
|
+
} else {
|
|
1029
|
+
i++;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return i;
|
|
1033
|
+
}
|
|
1034
|
+
function dynamicAttrHint(kind) {
|
|
1035
|
+
if (kind === "region") {
|
|
1036
|
+
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`;
|
|
1037
|
+
}
|
|
1038
|
+
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`;
|
|
1039
|
+
}
|
|
924
1040
|
function stableStringify(value) {
|
|
925
1041
|
return JSON.stringify(value, stableReplacer);
|
|
926
1042
|
}
|
|
@@ -935,7 +1051,7 @@ function stableReplacer(_key, value) {
|
|
|
935
1051
|
return value;
|
|
936
1052
|
}
|
|
937
1053
|
|
|
938
|
-
// src/scan/emit.ts
|
|
1054
|
+
// src/scanner/scan/emit.ts
|
|
939
1055
|
function sortById(arr) {
|
|
940
1056
|
return [...arr].sort((a, b) => a.id.localeCompare(b.id));
|
|
941
1057
|
}
|
|
@@ -1059,6 +1175,7 @@ function emit(opts) {
|
|
|
1059
1175
|
lines.push(" export interface Feature {");
|
|
1060
1176
|
lines.push(" feature: FeatureId | false");
|
|
1061
1177
|
lines.push(" name?: string");
|
|
1178
|
+
lines.push(" features?: readonly FeatureId[]");
|
|
1062
1179
|
lines.push(" acceptance?: readonly string[]");
|
|
1063
1180
|
lines.push(" description?: string");
|
|
1064
1181
|
lines.push(" }");
|
|
@@ -1073,6 +1190,11 @@ function emit(opts) {
|
|
|
1073
1190
|
lines.push(" acceptance?: readonly string[]");
|
|
1074
1191
|
lines.push(" description?: string");
|
|
1075
1192
|
lines.push(" }");
|
|
1193
|
+
lines.push(" export interface Region {");
|
|
1194
|
+
lines.push(" region: RegionId | false");
|
|
1195
|
+
lines.push(" name?: string");
|
|
1196
|
+
lines.push(" description?: string");
|
|
1197
|
+
lines.push(" }");
|
|
1076
1198
|
lines.push(" export interface Flow {");
|
|
1077
1199
|
lines.push(" flow: FlowId");
|
|
1078
1200
|
lines.push(" name?: string");
|
|
@@ -1115,12 +1237,13 @@ function emit(opts) {
|
|
|
1115
1237
|
return lines.join("\n");
|
|
1116
1238
|
}
|
|
1117
1239
|
|
|
1118
|
-
// src/scan/extract-uidex-export.ts
|
|
1240
|
+
// src/scanner/scan/extract-uidex-export.ts
|
|
1119
1241
|
var KIND_DISCRIMINATORS = [
|
|
1120
1242
|
"page",
|
|
1121
1243
|
"feature",
|
|
1122
1244
|
"primitive",
|
|
1123
1245
|
"widget",
|
|
1246
|
+
"region",
|
|
1124
1247
|
"flow",
|
|
1125
1248
|
"notFlow"
|
|
1126
1249
|
];
|
|
@@ -1133,15 +1256,23 @@ var ALLOWED_FIELDS = {
|
|
|
1133
1256
|
"acceptance",
|
|
1134
1257
|
"description"
|
|
1135
1258
|
]),
|
|
1136
|
-
feature: /* @__PURE__ */ new Set([
|
|
1259
|
+
feature: /* @__PURE__ */ new Set([
|
|
1260
|
+
"feature",
|
|
1261
|
+
"name",
|
|
1262
|
+
"features",
|
|
1263
|
+
"acceptance",
|
|
1264
|
+
"description"
|
|
1265
|
+
]),
|
|
1137
1266
|
primitive: /* @__PURE__ */ new Set(["primitive", "name", "description"]),
|
|
1138
1267
|
widget: /* @__PURE__ */ new Set(["widget", "name", "acceptance", "description"]),
|
|
1268
|
+
region: /* @__PURE__ */ new Set(["region", "name", "description"]),
|
|
1139
1269
|
flow: /* @__PURE__ */ new Set(["flow", "notFlow", "name", "description"])
|
|
1140
1270
|
};
|
|
1141
1271
|
var FALSEABLE = /* @__PURE__ */ new Set([
|
|
1142
1272
|
"page",
|
|
1143
1273
|
"feature",
|
|
1144
|
-
"primitive"
|
|
1274
|
+
"primitive",
|
|
1275
|
+
"region"
|
|
1145
1276
|
]);
|
|
1146
1277
|
var ExtractError = class extends Error {
|
|
1147
1278
|
code;
|
|
@@ -1869,7 +2000,7 @@ function buildMetadata(value, file, headerPos, diagnostics) {
|
|
|
1869
2000
|
line: pos.line
|
|
1870
2001
|
});
|
|
1871
2002
|
}
|
|
1872
|
-
const features = kind === "page" ? readStringArrayField(byKey, "features") : void 0;
|
|
2003
|
+
const features = kind === "page" || kind === "feature" ? readStringArrayField(byKey, "features") : void 0;
|
|
1873
2004
|
const widgets = kind === "page" ? readStringArrayField(byKey, "widgets") : void 0;
|
|
1874
2005
|
const notFlow = kind === "flow" && discriminator === "notFlow" ? true : void 0;
|
|
1875
2006
|
const metadata = {
|
|
@@ -1964,7 +2095,7 @@ function posAt(content, offset) {
|
|
|
1964
2095
|
return { offset, line, column: offset - lineStart + 1 };
|
|
1965
2096
|
}
|
|
1966
2097
|
|
|
1967
|
-
// src/scan/jsx-ancestry.ts
|
|
2098
|
+
// src/scanner/scan/jsx-ancestry.ts
|
|
1968
2099
|
var DATA_ATTR_RE = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
1969
2100
|
function parseDataAttrs(tagSource) {
|
|
1970
2101
|
if (!tagSource.includes("data-uidex")) return [];
|
|
@@ -2008,7 +2139,7 @@ function collectJSXAncestry(content) {
|
|
|
2008
2139
|
continue;
|
|
2009
2140
|
}
|
|
2010
2141
|
if (c === '"' || c === "'") {
|
|
2011
|
-
const next =
|
|
2142
|
+
const next = skipString2(content, i, c);
|
|
2012
2143
|
advanceLines(i, next);
|
|
2013
2144
|
i = next;
|
|
2014
2145
|
continue;
|
|
@@ -2068,7 +2199,7 @@ function collectJSXAncestry(content) {
|
|
|
2068
2199
|
}
|
|
2069
2200
|
return out2;
|
|
2070
2201
|
}
|
|
2071
|
-
function
|
|
2202
|
+
function skipString2(content, start, quote) {
|
|
2072
2203
|
const N = content.length;
|
|
2073
2204
|
let i = start + 1;
|
|
2074
2205
|
while (i < N) {
|
|
@@ -2098,7 +2229,7 @@ function skipTemplate(content, start) {
|
|
|
2098
2229
|
while (i < N && depth > 0) {
|
|
2099
2230
|
const cj = content[i];
|
|
2100
2231
|
if (cj === '"' || cj === "'") {
|
|
2101
|
-
i =
|
|
2232
|
+
i = skipString2(content, i, cj);
|
|
2102
2233
|
continue;
|
|
2103
2234
|
}
|
|
2104
2235
|
if (cj === "`") {
|
|
@@ -2121,7 +2252,7 @@ function findTagEnd(content, start) {
|
|
|
2121
2252
|
while (i < N) {
|
|
2122
2253
|
const c = content[i];
|
|
2123
2254
|
if (c === '"' || c === "'") {
|
|
2124
|
-
i =
|
|
2255
|
+
i = skipString2(content, i, c);
|
|
2125
2256
|
continue;
|
|
2126
2257
|
}
|
|
2127
2258
|
if (c === "`") {
|
|
@@ -2134,7 +2265,7 @@ function findTagEnd(content, start) {
|
|
|
2134
2265
|
while (i < N && depth > 0) {
|
|
2135
2266
|
const cj = content[i];
|
|
2136
2267
|
if (cj === '"' || cj === "'") {
|
|
2137
|
-
i =
|
|
2268
|
+
i = skipString2(content, i, cj);
|
|
2138
2269
|
continue;
|
|
2139
2270
|
}
|
|
2140
2271
|
if (cj === "`") {
|
|
@@ -2153,7 +2284,7 @@ function findTagEnd(content, start) {
|
|
|
2153
2284
|
return -1;
|
|
2154
2285
|
}
|
|
2155
2286
|
|
|
2156
|
-
// src/scan/extract.ts
|
|
2287
|
+
// src/scanner/scan/extract.ts
|
|
2157
2288
|
var JSDOC_BLOCK = /\/\*\*([\s\S]*?)\*\//g;
|
|
2158
2289
|
function lineAt(content, index) {
|
|
2159
2290
|
let line = 1;
|
|
@@ -2256,7 +2387,7 @@ function extractOne(file) {
|
|
|
2256
2387
|
return annotations;
|
|
2257
2388
|
}
|
|
2258
2389
|
|
|
2259
|
-
// src/scan/git.ts
|
|
2390
|
+
// src/scanner/scan/git.ts
|
|
2260
2391
|
var import_node_child_process = require("child_process");
|
|
2261
2392
|
function runGit(args, cwd) {
|
|
2262
2393
|
try {
|
|
@@ -2288,10 +2419,10 @@ function parseGitHubRef(ref) {
|
|
|
2288
2419
|
return m ? m[1] : null;
|
|
2289
2420
|
}
|
|
2290
2421
|
|
|
2291
|
-
// src/scan/resolve.ts
|
|
2422
|
+
// src/scanner/scan/resolve.ts
|
|
2292
2423
|
var path6 = __toESM(require("path"), 1);
|
|
2293
2424
|
|
|
2294
|
-
// src/scan/routes.ts
|
|
2425
|
+
// src/scanner/scan/routes.ts
|
|
2295
2426
|
var PAGE_BASENAME = /^page\.(tsx|ts|jsx|js|mjs|cjs)$/;
|
|
2296
2427
|
var PAGES_ROUTER_BASENAME = /\.(tsx|ts|jsx|js|mjs|cjs)$/;
|
|
2297
2428
|
var ROUTE_BASENAME = /^route\.(tsx|ts|jsx|js|mjs|cjs)$/;
|
|
@@ -2357,7 +2488,7 @@ function pathToId(routePath) {
|
|
|
2357
2488
|
return routePath.replace(/^\/+/, "").replace(/\[\.{3}([^\]]+)\]/g, "$1").replace(/\[([^\]]+)\]/g, "$1").replace(/\//g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2358
2489
|
}
|
|
2359
2490
|
|
|
2360
|
-
// src/scan/walk.ts
|
|
2491
|
+
// src/scanner/scan/walk.ts
|
|
2361
2492
|
var fs4 = __toESM(require("fs"), 1);
|
|
2362
2493
|
var path5 = __toESM(require("path"), 1);
|
|
2363
2494
|
var DEFAULT_INCLUDES = ["**/*.{ts,tsx,js,jsx,mjs,cjs}"];
|
|
@@ -2482,7 +2613,7 @@ function* walkDir(root, dir) {
|
|
|
2482
2613
|
}
|
|
2483
2614
|
}
|
|
2484
2615
|
|
|
2485
|
-
// src/scan/resolve.ts
|
|
2616
|
+
// src/scanner/scan/resolve.ts
|
|
2486
2617
|
var DOM_ATTR_KINDS = /* @__PURE__ */ new Set([
|
|
2487
2618
|
"element",
|
|
2488
2619
|
"region",
|
|
@@ -2789,6 +2920,21 @@ function resolve3(ctx) {
|
|
|
2789
2920
|
};
|
|
2790
2921
|
registry.add(region);
|
|
2791
2922
|
}
|
|
2923
|
+
for (const ef of ctx.extracted) {
|
|
2924
|
+
const exp = exportFor(ef.file.displayPath, "region");
|
|
2925
|
+
if (!exp) continue;
|
|
2926
|
+
if (exp.id === false) continue;
|
|
2927
|
+
if (typeof exp.id === "string" && !registry.get("region", exp.id)) {
|
|
2928
|
+
const meta = buildMetaFromExport(exp);
|
|
2929
|
+
const region = {
|
|
2930
|
+
kind: "region",
|
|
2931
|
+
id: exp.id,
|
|
2932
|
+
loc: { file: ef.file.displayPath, line: exp.loc.line },
|
|
2933
|
+
...meta ? { meta } : {}
|
|
2934
|
+
};
|
|
2935
|
+
registry.add(region);
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2792
2938
|
const primitiveConventions = conventions.primitives;
|
|
2793
2939
|
for (const ef of ctx.extracted) {
|
|
2794
2940
|
const file = ef.file.displayPath;
|
|
@@ -2967,7 +3113,7 @@ function dedupe(arr) {
|
|
|
2967
3113
|
return Array.from(new Set(arr));
|
|
2968
3114
|
}
|
|
2969
3115
|
|
|
2970
|
-
// src/scan/pipeline.ts
|
|
3116
|
+
// src/scanner/scan/pipeline.ts
|
|
2971
3117
|
function runScan(opts = {}) {
|
|
2972
3118
|
const cwd = opts.cwd ?? process.cwd();
|
|
2973
3119
|
const configs = opts.configs ?? discover({ cwd });
|
|
@@ -3034,12 +3180,12 @@ function runOne(dc, opts) {
|
|
|
3034
3180
|
outputPath
|
|
3035
3181
|
};
|
|
3036
3182
|
}
|
|
3037
|
-
function writeScanResult(
|
|
3038
|
-
fs5.mkdirSync(path7.dirname(
|
|
3039
|
-
fs5.writeFileSync(
|
|
3183
|
+
function writeScanResult(result2) {
|
|
3184
|
+
fs5.mkdirSync(path7.dirname(result2.outputPath), { recursive: true });
|
|
3185
|
+
fs5.writeFileSync(result2.outputPath, result2.generated, "utf8");
|
|
3040
3186
|
}
|
|
3041
3187
|
|
|
3042
|
-
// src/scan/scaffold.ts
|
|
3188
|
+
// src/scanner/scan/scaffold.ts
|
|
3043
3189
|
var fs6 = __toESM(require("fs"), 1);
|
|
3044
3190
|
var path8 = __toESM(require("path"), 1);
|
|
3045
3191
|
function scaffoldWidgetSpec(opts) {
|
|
@@ -3102,8 +3248,8 @@ function renderSpec(args) {
|
|
|
3102
3248
|
return lines.join("\n");
|
|
3103
3249
|
}
|
|
3104
3250
|
|
|
3105
|
-
// src/
|
|
3106
|
-
function
|
|
3251
|
+
// src/scanner/cli/parse-args.ts
|
|
3252
|
+
function parseArgs(args) {
|
|
3107
3253
|
const positional = [];
|
|
3108
3254
|
const flags = {};
|
|
3109
3255
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -3127,13 +3273,15 @@ function parseFlags2(args) {
|
|
|
3127
3273
|
}
|
|
3128
3274
|
return { positional, flags };
|
|
3129
3275
|
}
|
|
3276
|
+
|
|
3277
|
+
// src/scanner/scan/cli.ts
|
|
3130
3278
|
async function run(opts) {
|
|
3131
3279
|
const cwd = opts.cwd ?? process.cwd();
|
|
3132
|
-
const { positional, flags } =
|
|
3133
|
-
const
|
|
3280
|
+
const { positional, flags } = parseArgs(opts.argv);
|
|
3281
|
+
const command2 = positional[0] ?? "help";
|
|
3134
3282
|
const writer = createWriter();
|
|
3135
3283
|
try {
|
|
3136
|
-
switch (
|
|
3284
|
+
switch (command2) {
|
|
3137
3285
|
case "help":
|
|
3138
3286
|
case "--help":
|
|
3139
3287
|
case "-h":
|
|
@@ -3146,16 +3294,16 @@ async function run(opts) {
|
|
|
3146
3294
|
case "scaffold":
|
|
3147
3295
|
return runScaffold(cwd, positional.slice(1), flags, writer);
|
|
3148
3296
|
case "ai": {
|
|
3149
|
-
const
|
|
3297
|
+
const result2 = await runAiCommand({
|
|
3150
3298
|
cwd,
|
|
3151
3299
|
argv: opts.argv.slice(1)
|
|
3152
3300
|
});
|
|
3153
|
-
if (
|
|
3154
|
-
if (
|
|
3155
|
-
return writer.result(
|
|
3301
|
+
if (result2.stdout) writer.out(result2.stdout.replace(/\n$/, ""));
|
|
3302
|
+
if (result2.stderr) writer.err(result2.stderr.replace(/\n$/, ""));
|
|
3303
|
+
return writer.result(result2.exitCode);
|
|
3156
3304
|
}
|
|
3157
3305
|
default:
|
|
3158
|
-
writer.err(`Unknown command: ${
|
|
3306
|
+
writer.err(`Unknown command: ${command2}`);
|
|
3159
3307
|
writer.err(helpText2());
|
|
3160
3308
|
return writer.result(1);
|
|
3161
3309
|
}
|
|
@@ -3173,6 +3321,10 @@ function helpText2() {
|
|
|
3173
3321
|
" scan [flags] Run the scanner pipeline",
|
|
3174
3322
|
" scaffold widget <id> Emit a Playwright spec from a widget's acceptance",
|
|
3175
3323
|
" ai <install|uninstall|providers> Manage AI assistant integrations",
|
|
3324
|
+
" api <METHOD> <PATH> Call the uidex API",
|
|
3325
|
+
" api --list Show available API routes",
|
|
3326
|
+
" api login Authenticate via browser",
|
|
3327
|
+
" api login --token <tok> Store an auth token directly",
|
|
3176
3328
|
"",
|
|
3177
3329
|
"Flags:",
|
|
3178
3330
|
" --check Verify the on-disk gen file matches a fresh scan; exit non-zero on drift (read-only)",
|
|
@@ -3275,17 +3427,17 @@ function runScaffold(cwd, args, flags, w) {
|
|
|
3275
3427
|
const widget = r.registry.get("widget", id);
|
|
3276
3428
|
if (!widget) continue;
|
|
3277
3429
|
const outDir = path9.resolve(r.configDir, "e2e");
|
|
3278
|
-
const
|
|
3430
|
+
const result2 = scaffoldWidgetSpec({
|
|
3279
3431
|
registry: r.registry,
|
|
3280
3432
|
widgetId: id,
|
|
3281
3433
|
outDir,
|
|
3282
3434
|
force: Boolean(flags.force)
|
|
3283
3435
|
});
|
|
3284
|
-
if (
|
|
3285
|
-
w.err(
|
|
3436
|
+
if (result2.skipped) {
|
|
3437
|
+
w.err(result2.reason ?? "skipped");
|
|
3286
3438
|
return w.result(1);
|
|
3287
3439
|
}
|
|
3288
|
-
w.out(`Wrote ${
|
|
3440
|
+
w.out(`Wrote ${result2.outputPath}`);
|
|
3289
3441
|
return w.result(0);
|
|
3290
3442
|
}
|
|
3291
3443
|
w.err(`Widget "${id}" not found in registry`);
|
|
@@ -3307,12 +3459,884 @@ function createWriter() {
|
|
|
3307
3459
|
};
|
|
3308
3460
|
}
|
|
3309
3461
|
|
|
3310
|
-
// src/cli/
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3462
|
+
// src/scanner/cli/auth.ts
|
|
3463
|
+
function createMemoryTokenStorage(initial) {
|
|
3464
|
+
let token = initial ?? null;
|
|
3465
|
+
return {
|
|
3466
|
+
get: () => token,
|
|
3467
|
+
set: (t) => {
|
|
3468
|
+
token = t;
|
|
3469
|
+
},
|
|
3470
|
+
clear: () => {
|
|
3471
|
+
token = null;
|
|
3472
|
+
}
|
|
3473
|
+
};
|
|
3474
|
+
}
|
|
3475
|
+
function createFileTokenStorage(options) {
|
|
3476
|
+
const nodeFs = require("fs");
|
|
3477
|
+
const nodePath = require("path");
|
|
3478
|
+
const file = options.path;
|
|
3479
|
+
function read() {
|
|
3480
|
+
if (!nodeFs.existsSync(file)) return null;
|
|
3481
|
+
try {
|
|
3482
|
+
const raw = nodeFs.readFileSync(file, "utf8");
|
|
3483
|
+
const parsed = JSON.parse(raw);
|
|
3484
|
+
if (parsed && typeof parsed === "object") {
|
|
3485
|
+
return parsed;
|
|
3486
|
+
}
|
|
3487
|
+
} catch {
|
|
3488
|
+
return null;
|
|
3489
|
+
}
|
|
3490
|
+
return null;
|
|
3491
|
+
}
|
|
3492
|
+
return {
|
|
3493
|
+
get: () => read()?.token ?? null,
|
|
3494
|
+
set: (token) => {
|
|
3495
|
+
nodeFs.mkdirSync(nodePath.dirname(file), { recursive: true });
|
|
3496
|
+
nodeFs.writeFileSync(file, JSON.stringify({ token }, null, 2));
|
|
3497
|
+
},
|
|
3498
|
+
clear: () => {
|
|
3499
|
+
if (nodeFs.existsSync(file)) nodeFs.unlinkSync(file);
|
|
3500
|
+
}
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
function defaultTokenPath() {
|
|
3504
|
+
const os = require("os");
|
|
3505
|
+
const path10 = require("path");
|
|
3506
|
+
return path10.join(os.homedir(), ".uidex", "cloud-token.json");
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
// src/scanner/cli/http.ts
|
|
3510
|
+
function createHttpClient(options) {
|
|
3511
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
3512
|
+
async function request(path10, opts = {}) {
|
|
3513
|
+
let url = `${baseUrl}${path10.startsWith("/") ? path10 : `/${path10}`}`;
|
|
3514
|
+
if (opts.query) {
|
|
3515
|
+
url += (url.includes("?") ? "&" : "?") + opts.query;
|
|
3516
|
+
}
|
|
3517
|
+
const headers = {
|
|
3518
|
+
Accept: "application/json",
|
|
3519
|
+
...opts.headers
|
|
3520
|
+
};
|
|
3521
|
+
const token = options.token();
|
|
3522
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
3523
|
+
if (opts.body !== void 0) {
|
|
3524
|
+
headers["Content-Type"] ??= "application/json";
|
|
3525
|
+
}
|
|
3526
|
+
const res = await fetch(url, {
|
|
3527
|
+
method: opts.method ?? (opts.body ? "POST" : "GET"),
|
|
3528
|
+
headers,
|
|
3529
|
+
body: opts.body
|
|
3530
|
+
});
|
|
3531
|
+
const raw = await res.text();
|
|
3532
|
+
let body = raw;
|
|
3533
|
+
try {
|
|
3534
|
+
body = JSON.parse(raw);
|
|
3535
|
+
} catch {
|
|
3536
|
+
}
|
|
3537
|
+
return { status: res.status, body, raw };
|
|
3538
|
+
}
|
|
3539
|
+
return { request };
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
// src/scanner/cli/json-highlight.ts
|
|
3543
|
+
var RESET = "\x1B[0m";
|
|
3544
|
+
var BOLD = "\x1B[1m";
|
|
3545
|
+
var DIM = "\x1B[2m";
|
|
3546
|
+
var RED = "\x1B[31m";
|
|
3547
|
+
var CYAN = "\x1B[36m";
|
|
3548
|
+
var GREEN = "\x1B[32m";
|
|
3549
|
+
var YELLOW = "\x1B[33m";
|
|
3550
|
+
var MAGENTA = "\x1B[35m";
|
|
3551
|
+
function highlightJson(value, color) {
|
|
3552
|
+
const json = JSON.stringify(value, null, 2);
|
|
3553
|
+
if (!color) return json;
|
|
3554
|
+
return json.replace(
|
|
3555
|
+
/("(?:\\.|[^"\\])*")\s*:|("(?:\\.|[^"\\])*")|(\b(?:true|false|null)\b)|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g,
|
|
3556
|
+
(match, key, str, literal, num) => {
|
|
3557
|
+
if (key) return `${CYAN}${key}${RESET}:`;
|
|
3558
|
+
if (str) return `${GREEN}${str}${RESET}`;
|
|
3559
|
+
if (literal) return `${MAGENTA}${literal}${RESET}`;
|
|
3560
|
+
if (num) return `${YELLOW}${num}${RESET}`;
|
|
3561
|
+
return match;
|
|
3562
|
+
}
|
|
3563
|
+
);
|
|
3564
|
+
}
|
|
3565
|
+
function formatStatus(status, color) {
|
|
3566
|
+
if (!color) return `HTTP ${status}`;
|
|
3567
|
+
const code = status >= 400 ? RED : status >= 300 ? YELLOW : GREEN;
|
|
3568
|
+
return `${code}HTTP ${status}${RESET}`;
|
|
3569
|
+
}
|
|
3570
|
+
function boldText(text, color) {
|
|
3571
|
+
if (!color) return text;
|
|
3572
|
+
return `${BOLD}${text}${RESET}`;
|
|
3573
|
+
}
|
|
3574
|
+
function dimText(text, color) {
|
|
3575
|
+
if (!color) return text;
|
|
3576
|
+
return `${DIM}${text}${RESET}`;
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
// src/scanner/cli/api-routes.gen.ts
|
|
3580
|
+
var API_ROUTES = [
|
|
3581
|
+
{
|
|
3582
|
+
"method": "POST",
|
|
3583
|
+
"path": "/api/ingest",
|
|
3584
|
+
"operationId": "submitReport",
|
|
3585
|
+
"summary": "Submit feedback from the SDK.",
|
|
3586
|
+
"tag": "Ingest",
|
|
3587
|
+
"params": []
|
|
3588
|
+
},
|
|
3589
|
+
{
|
|
3590
|
+
"method": "GET",
|
|
3591
|
+
"path": "/api/ingest/config",
|
|
3592
|
+
"operationId": "getIngestConfig",
|
|
3593
|
+
"summary": "Get integration configuration for the project.",
|
|
3594
|
+
"tag": "Ingest",
|
|
3595
|
+
"params": []
|
|
3596
|
+
},
|
|
3597
|
+
{
|
|
3598
|
+
"method": "GET",
|
|
3599
|
+
"path": "/api/ingest/reports",
|
|
3600
|
+
"operationId": "listIngestReports",
|
|
3601
|
+
"summary": "List feedback for the project (SDK view).",
|
|
3602
|
+
"tag": "Ingest",
|
|
3603
|
+
"params": []
|
|
3604
|
+
},
|
|
3605
|
+
{
|
|
3606
|
+
"method": "GET",
|
|
3607
|
+
"path": "/api/ingest/pins",
|
|
3608
|
+
"operationId": "listPins",
|
|
3609
|
+
"summary": "List pins by route and/or entity.",
|
|
3610
|
+
"tag": "Ingest",
|
|
3611
|
+
"params": []
|
|
3612
|
+
},
|
|
3613
|
+
{
|
|
3614
|
+
"method": "POST",
|
|
3615
|
+
"path": "/api/ingest/pins/archive",
|
|
3616
|
+
"operationId": "archivePin",
|
|
3617
|
+
"summary": "Archive a pin.",
|
|
3618
|
+
"tag": "Ingest",
|
|
3619
|
+
"params": []
|
|
3620
|
+
},
|
|
3621
|
+
{
|
|
3622
|
+
"method": "POST",
|
|
3623
|
+
"path": "/api/cli-auth/authorize",
|
|
3624
|
+
"operationId": "authorizeCli",
|
|
3625
|
+
"summary": "Authorize a CLI session by delivering a token.",
|
|
3626
|
+
"tag": "CLI Auth",
|
|
3627
|
+
"params": []
|
|
3628
|
+
},
|
|
3629
|
+
{
|
|
3630
|
+
"method": "GET",
|
|
3631
|
+
"path": "/api/organizations",
|
|
3632
|
+
"operationId": "listOrganizations",
|
|
3633
|
+
"summary": "List organizations for the current user.",
|
|
3634
|
+
"tag": "Organizations",
|
|
3635
|
+
"params": []
|
|
3636
|
+
},
|
|
3637
|
+
{
|
|
3638
|
+
"method": "POST",
|
|
3639
|
+
"path": "/api/organizations",
|
|
3640
|
+
"operationId": "createOrganization",
|
|
3641
|
+
"summary": "Create an organization.",
|
|
3642
|
+
"tag": "Organizations",
|
|
3643
|
+
"params": []
|
|
3644
|
+
},
|
|
3645
|
+
{
|
|
3646
|
+
"method": "POST",
|
|
3647
|
+
"path": "/api/organizations/switch",
|
|
3648
|
+
"operationId": "switchOrganization",
|
|
3649
|
+
"summary": "Switch the active organization.",
|
|
3650
|
+
"tag": "Organizations",
|
|
3651
|
+
"params": []
|
|
3652
|
+
},
|
|
3653
|
+
{
|
|
3654
|
+
"method": "GET",
|
|
3655
|
+
"path": "/api/organizations/{orgId}",
|
|
3656
|
+
"operationId": "getOrganization",
|
|
3657
|
+
"summary": "Get organization details.",
|
|
3658
|
+
"tag": "Organizations",
|
|
3659
|
+
"params": [
|
|
3660
|
+
"orgId"
|
|
3661
|
+
]
|
|
3662
|
+
},
|
|
3663
|
+
{
|
|
3664
|
+
"method": "PATCH",
|
|
3665
|
+
"path": "/api/organizations/{orgId}",
|
|
3666
|
+
"operationId": "updateOrganization",
|
|
3667
|
+
"summary": "Update an organization.",
|
|
3668
|
+
"tag": "Organizations",
|
|
3669
|
+
"params": [
|
|
3670
|
+
"orgId"
|
|
3671
|
+
]
|
|
3672
|
+
},
|
|
3673
|
+
{
|
|
3674
|
+
"method": "DELETE",
|
|
3675
|
+
"path": "/api/organizations/{orgId}",
|
|
3676
|
+
"operationId": "deleteOrganization",
|
|
3677
|
+
"summary": "Delete an organization.",
|
|
3678
|
+
"tag": "Organizations",
|
|
3679
|
+
"params": [
|
|
3680
|
+
"orgId"
|
|
3681
|
+
]
|
|
3682
|
+
},
|
|
3683
|
+
{
|
|
3684
|
+
"method": "GET",
|
|
3685
|
+
"path": "/api/organizations/{orgId}/members",
|
|
3686
|
+
"operationId": "listMembers",
|
|
3687
|
+
"summary": "List organization members.",
|
|
3688
|
+
"tag": "Members",
|
|
3689
|
+
"params": [
|
|
3690
|
+
"orgId"
|
|
3691
|
+
]
|
|
3692
|
+
},
|
|
3693
|
+
{
|
|
3694
|
+
"method": "DELETE",
|
|
3695
|
+
"path": "/api/organizations/{orgId}/members",
|
|
3696
|
+
"operationId": "removeMember",
|
|
3697
|
+
"summary": "Remove a member from the organization.",
|
|
3698
|
+
"tag": "Members",
|
|
3699
|
+
"params": [
|
|
3700
|
+
"orgId"
|
|
3701
|
+
]
|
|
3702
|
+
},
|
|
3703
|
+
{
|
|
3704
|
+
"method": "PATCH",
|
|
3705
|
+
"path": "/api/organizations/{orgId}/members/{memberId}",
|
|
3706
|
+
"operationId": "updateMember",
|
|
3707
|
+
"summary": "Update a member's role.",
|
|
3708
|
+
"tag": "Members",
|
|
3709
|
+
"params": [
|
|
3710
|
+
"orgId",
|
|
3711
|
+
"memberId"
|
|
3712
|
+
]
|
|
3713
|
+
},
|
|
3714
|
+
{
|
|
3715
|
+
"method": "GET",
|
|
3716
|
+
"path": "/api/organizations/{orgId}/invitations",
|
|
3717
|
+
"operationId": "listInvitations",
|
|
3718
|
+
"summary": "List organization invitations.",
|
|
3719
|
+
"tag": "Invitations",
|
|
3720
|
+
"params": [
|
|
3721
|
+
"orgId"
|
|
3722
|
+
]
|
|
3723
|
+
},
|
|
3724
|
+
{
|
|
3725
|
+
"method": "POST",
|
|
3726
|
+
"path": "/api/organizations/{orgId}/invitations",
|
|
3727
|
+
"operationId": "createInvitation",
|
|
3728
|
+
"summary": "Create an invitation link.",
|
|
3729
|
+
"tag": "Invitations",
|
|
3730
|
+
"params": [
|
|
3731
|
+
"orgId"
|
|
3732
|
+
]
|
|
3733
|
+
},
|
|
3734
|
+
{
|
|
3735
|
+
"method": "DELETE",
|
|
3736
|
+
"path": "/api/organizations/{orgId}/invitations/{inviteId}",
|
|
3737
|
+
"operationId": "deleteInvitation",
|
|
3738
|
+
"summary": "Revoke an invitation.",
|
|
3739
|
+
"tag": "Invitations",
|
|
3740
|
+
"params": [
|
|
3741
|
+
"orgId",
|
|
3742
|
+
"inviteId"
|
|
3743
|
+
]
|
|
3744
|
+
},
|
|
3745
|
+
{
|
|
3746
|
+
"method": "GET",
|
|
3747
|
+
"path": "/api/organizations/{orgId}/projects",
|
|
3748
|
+
"operationId": "listProjects",
|
|
3749
|
+
"summary": "List projects in an organization.",
|
|
3750
|
+
"tag": "Projects",
|
|
3751
|
+
"params": [
|
|
3752
|
+
"orgId"
|
|
3753
|
+
]
|
|
3754
|
+
},
|
|
3755
|
+
{
|
|
3756
|
+
"method": "POST",
|
|
3757
|
+
"path": "/api/organizations/{orgId}/projects",
|
|
3758
|
+
"operationId": "createProject",
|
|
3759
|
+
"summary": "Create a project.",
|
|
3760
|
+
"tag": "Projects",
|
|
3761
|
+
"params": [
|
|
3762
|
+
"orgId"
|
|
3763
|
+
]
|
|
3764
|
+
},
|
|
3765
|
+
{
|
|
3766
|
+
"method": "GET",
|
|
3767
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}",
|
|
3768
|
+
"operationId": "getProject",
|
|
3769
|
+
"summary": "Get project details.",
|
|
3770
|
+
"tag": "Projects",
|
|
3771
|
+
"params": [
|
|
3772
|
+
"orgId",
|
|
3773
|
+
"projectId"
|
|
3774
|
+
]
|
|
3775
|
+
},
|
|
3776
|
+
{
|
|
3777
|
+
"method": "PATCH",
|
|
3778
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}",
|
|
3779
|
+
"operationId": "updateProject",
|
|
3780
|
+
"summary": "Update a project.",
|
|
3781
|
+
"tag": "Projects",
|
|
3782
|
+
"params": [
|
|
3783
|
+
"orgId",
|
|
3784
|
+
"projectId"
|
|
3785
|
+
]
|
|
3786
|
+
},
|
|
3787
|
+
{
|
|
3788
|
+
"method": "DELETE",
|
|
3789
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}",
|
|
3790
|
+
"operationId": "deleteProject",
|
|
3791
|
+
"summary": "Delete a project.",
|
|
3792
|
+
"tag": "Projects",
|
|
3793
|
+
"params": [
|
|
3794
|
+
"orgId",
|
|
3795
|
+
"projectId"
|
|
3796
|
+
]
|
|
3797
|
+
},
|
|
3798
|
+
{
|
|
3799
|
+
"method": "GET",
|
|
3800
|
+
"path": "/api/projects/resolve",
|
|
3801
|
+
"operationId": "resolveProject",
|
|
3802
|
+
"summary": "Resolve an API key to its project and organization.",
|
|
3803
|
+
"tag": "Projects",
|
|
3804
|
+
"params": []
|
|
3805
|
+
},
|
|
3806
|
+
{
|
|
3807
|
+
"method": "GET",
|
|
3808
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/api-keys",
|
|
3809
|
+
"operationId": "listApiKeys",
|
|
3810
|
+
"summary": "List API keys for a project.",
|
|
3811
|
+
"tag": "API Keys",
|
|
3812
|
+
"params": [
|
|
3813
|
+
"orgId",
|
|
3814
|
+
"projectId"
|
|
3815
|
+
]
|
|
3816
|
+
},
|
|
3817
|
+
{
|
|
3818
|
+
"method": "POST",
|
|
3819
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/api-keys",
|
|
3820
|
+
"operationId": "createApiKey",
|
|
3821
|
+
"summary": "Create an API key.",
|
|
3822
|
+
"tag": "API Keys",
|
|
3823
|
+
"params": [
|
|
3824
|
+
"orgId",
|
|
3825
|
+
"projectId"
|
|
3826
|
+
]
|
|
3827
|
+
},
|
|
3828
|
+
{
|
|
3829
|
+
"method": "PATCH",
|
|
3830
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/api-keys/{keyId}",
|
|
3831
|
+
"operationId": "revokeApiKey",
|
|
3832
|
+
"summary": "Revoke an API key.",
|
|
3833
|
+
"tag": "API Keys",
|
|
3834
|
+
"params": [
|
|
3835
|
+
"orgId",
|
|
3836
|
+
"projectId",
|
|
3837
|
+
"keyId"
|
|
3838
|
+
]
|
|
3839
|
+
},
|
|
3840
|
+
{
|
|
3841
|
+
"method": "DELETE",
|
|
3842
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/api-keys/{keyId}",
|
|
3843
|
+
"operationId": "deleteApiKey",
|
|
3844
|
+
"summary": "Permanently delete an API key.",
|
|
3845
|
+
"tag": "API Keys",
|
|
3846
|
+
"params": [
|
|
3847
|
+
"orgId",
|
|
3848
|
+
"projectId",
|
|
3849
|
+
"keyId"
|
|
3850
|
+
]
|
|
3851
|
+
},
|
|
3852
|
+
{
|
|
3853
|
+
"method": "GET",
|
|
3854
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/reports",
|
|
3855
|
+
"operationId": "listProjectReports",
|
|
3856
|
+
"summary": "List feedback for a project.",
|
|
3857
|
+
"tag": "Reports",
|
|
3858
|
+
"params": [
|
|
3859
|
+
"orgId",
|
|
3860
|
+
"projectId"
|
|
3861
|
+
]
|
|
3862
|
+
},
|
|
3863
|
+
{
|
|
3864
|
+
"method": "GET",
|
|
3865
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/feedback/{reportId}",
|
|
3866
|
+
"operationId": "getReport",
|
|
3867
|
+
"summary": "Get feedback details.",
|
|
3868
|
+
"tag": "Reports",
|
|
3869
|
+
"params": [
|
|
3870
|
+
"orgId",
|
|
3871
|
+
"projectId",
|
|
3872
|
+
"reportId"
|
|
3873
|
+
]
|
|
3874
|
+
},
|
|
3875
|
+
{
|
|
3876
|
+
"method": "PATCH",
|
|
3877
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/feedback/{reportId}",
|
|
3878
|
+
"operationId": "updateReport",
|
|
3879
|
+
"summary": "Update feedback status, priority, or assignment.",
|
|
3880
|
+
"tag": "Reports",
|
|
3881
|
+
"params": [
|
|
3882
|
+
"orgId",
|
|
3883
|
+
"projectId",
|
|
3884
|
+
"reportId"
|
|
3885
|
+
]
|
|
3886
|
+
},
|
|
3887
|
+
{
|
|
3888
|
+
"method": "DELETE",
|
|
3889
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/feedback/{reportId}",
|
|
3890
|
+
"operationId": "deleteReport",
|
|
3891
|
+
"summary": "Delete a feedback record.",
|
|
3892
|
+
"tag": "Reports",
|
|
3893
|
+
"params": [
|
|
3894
|
+
"orgId",
|
|
3895
|
+
"projectId",
|
|
3896
|
+
"reportId"
|
|
3897
|
+
]
|
|
3898
|
+
},
|
|
3899
|
+
{
|
|
3900
|
+
"method": "POST",
|
|
3901
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/reports/archive",
|
|
3902
|
+
"operationId": "bulkArchiveReport",
|
|
3903
|
+
"summary": "Archive multiple feedback records.",
|
|
3904
|
+
"tag": "Reports",
|
|
3905
|
+
"params": [
|
|
3906
|
+
"orgId",
|
|
3907
|
+
"projectId"
|
|
3908
|
+
]
|
|
3909
|
+
},
|
|
3910
|
+
{
|
|
3911
|
+
"method": "GET",
|
|
3912
|
+
"path": "/api/organizations/{orgId}/integrations",
|
|
3913
|
+
"operationId": "listIntegrations",
|
|
3914
|
+
"summary": "List integrations for an organization.",
|
|
3915
|
+
"tag": "Integrations",
|
|
3916
|
+
"params": [
|
|
3917
|
+
"orgId"
|
|
3918
|
+
]
|
|
3919
|
+
},
|
|
3920
|
+
{
|
|
3921
|
+
"method": "POST",
|
|
3922
|
+
"path": "/api/organizations/{orgId}/integrations",
|
|
3923
|
+
"operationId": "createIntegration",
|
|
3924
|
+
"summary": "Create an integration.",
|
|
3925
|
+
"tag": "Integrations",
|
|
3926
|
+
"params": [
|
|
3927
|
+
"orgId"
|
|
3928
|
+
]
|
|
3929
|
+
},
|
|
3930
|
+
{
|
|
3931
|
+
"method": "GET",
|
|
3932
|
+
"path": "/api/organizations/{orgId}/integrations/{integrationId}",
|
|
3933
|
+
"operationId": "getIntegration",
|
|
3934
|
+
"summary": "Get integration details.",
|
|
3935
|
+
"tag": "Integrations",
|
|
3936
|
+
"params": [
|
|
3937
|
+
"orgId",
|
|
3938
|
+
"integrationId"
|
|
3939
|
+
]
|
|
3940
|
+
},
|
|
3941
|
+
{
|
|
3942
|
+
"method": "DELETE",
|
|
3943
|
+
"path": "/api/organizations/{orgId}/integrations/{integrationId}",
|
|
3944
|
+
"operationId": "deleteIntegration",
|
|
3945
|
+
"summary": "Delete an integration.",
|
|
3946
|
+
"tag": "Integrations",
|
|
3947
|
+
"params": [
|
|
3948
|
+
"orgId",
|
|
3949
|
+
"integrationId"
|
|
3950
|
+
]
|
|
3951
|
+
},
|
|
3952
|
+
{
|
|
3953
|
+
"method": "GET",
|
|
3954
|
+
"path": "/api/organizations/{orgId}/integrations/{integrationId}/targets",
|
|
3955
|
+
"operationId": "listIntegrationTargets",
|
|
3956
|
+
"summary": "List available targets (e.g. Jira projects/boards).",
|
|
3957
|
+
"tag": "Integrations",
|
|
3958
|
+
"params": [
|
|
3959
|
+
"orgId",
|
|
3960
|
+
"integrationId"
|
|
3961
|
+
]
|
|
3962
|
+
},
|
|
3963
|
+
{
|
|
3964
|
+
"method": "POST",
|
|
3965
|
+
"path": "/api/organizations/{orgId}/integrations/{integrationId}/test",
|
|
3966
|
+
"operationId": "testIntegration",
|
|
3967
|
+
"summary": "Test integration connectivity.",
|
|
3968
|
+
"tag": "Integrations",
|
|
3969
|
+
"params": [
|
|
3970
|
+
"orgId",
|
|
3971
|
+
"integrationId"
|
|
3972
|
+
]
|
|
3973
|
+
},
|
|
3974
|
+
{
|
|
3975
|
+
"method": "GET",
|
|
3976
|
+
"path": "/api/organizations/{orgId}/external-issues",
|
|
3977
|
+
"operationId": "listExternalIssues",
|
|
3978
|
+
"summary": "List external issues, optionally filtered by feedback.",
|
|
3979
|
+
"tag": "External Issues",
|
|
3980
|
+
"params": [
|
|
3981
|
+
"orgId"
|
|
3982
|
+
]
|
|
3983
|
+
},
|
|
3984
|
+
{
|
|
3985
|
+
"method": "GET",
|
|
3986
|
+
"path": "/api/organizations/{orgId}/issue-drafts",
|
|
3987
|
+
"operationId": "listIssueDrafts",
|
|
3988
|
+
"summary": "List issue drafts for a project.",
|
|
3989
|
+
"tag": "Issue Drafts",
|
|
3990
|
+
"params": [
|
|
3991
|
+
"orgId"
|
|
3992
|
+
]
|
|
3993
|
+
},
|
|
3994
|
+
{
|
|
3995
|
+
"method": "POST",
|
|
3996
|
+
"path": "/api/organizations/{orgId}/issue-drafts",
|
|
3997
|
+
"operationId": "createIssueDraft",
|
|
3998
|
+
"summary": "Manually create an issue draft.",
|
|
3999
|
+
"tag": "Issue Drafts",
|
|
4000
|
+
"params": [
|
|
4001
|
+
"orgId"
|
|
4002
|
+
]
|
|
4003
|
+
},
|
|
4004
|
+
{
|
|
4005
|
+
"method": "PATCH",
|
|
4006
|
+
"path": "/api/organizations/{orgId}/issue-drafts/{draftId}",
|
|
4007
|
+
"operationId": "updateIssueDraft",
|
|
4008
|
+
"summary": "Update an issue draft.",
|
|
4009
|
+
"tag": "Issue Drafts",
|
|
4010
|
+
"params": [
|
|
4011
|
+
"orgId",
|
|
4012
|
+
"draftId"
|
|
4013
|
+
]
|
|
4014
|
+
},
|
|
4015
|
+
{
|
|
4016
|
+
"method": "DELETE",
|
|
4017
|
+
"path": "/api/organizations/{orgId}/issue-drafts/{draftId}",
|
|
4018
|
+
"operationId": "deleteIssueDraft",
|
|
4019
|
+
"summary": "Delete an issue draft.",
|
|
4020
|
+
"tag": "Issue Drafts",
|
|
4021
|
+
"params": [
|
|
4022
|
+
"orgId",
|
|
4023
|
+
"draftId"
|
|
4024
|
+
]
|
|
4025
|
+
},
|
|
4026
|
+
{
|
|
4027
|
+
"method": "POST",
|
|
4028
|
+
"path": "/api/organizations/{orgId}/issue-drafts/{draftId}/submit",
|
|
4029
|
+
"operationId": "submitIssueDraft",
|
|
4030
|
+
"summary": "Submit a draft to the configured integration.",
|
|
4031
|
+
"tag": "Issue Drafts",
|
|
4032
|
+
"params": [
|
|
4033
|
+
"orgId",
|
|
4034
|
+
"draftId"
|
|
4035
|
+
]
|
|
4036
|
+
},
|
|
4037
|
+
{
|
|
4038
|
+
"method": "POST",
|
|
4039
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/triage",
|
|
4040
|
+
"operationId": "runTriage",
|
|
4041
|
+
"summary": "Run LLM-powered triage on untriaged feedback.",
|
|
4042
|
+
"tag": "Triage",
|
|
4043
|
+
"params": [
|
|
4044
|
+
"orgId",
|
|
4045
|
+
"projectId"
|
|
4046
|
+
]
|
|
4047
|
+
},
|
|
4048
|
+
{
|
|
4049
|
+
"method": "GET",
|
|
4050
|
+
"path": "/api/user/tokens",
|
|
4051
|
+
"operationId": "listUserTokens",
|
|
4052
|
+
"summary": "List personal access tokens.",
|
|
4053
|
+
"tag": "User Tokens",
|
|
4054
|
+
"params": []
|
|
4055
|
+
},
|
|
4056
|
+
{
|
|
4057
|
+
"method": "POST",
|
|
4058
|
+
"path": "/api/user/tokens",
|
|
4059
|
+
"operationId": "createUserToken",
|
|
4060
|
+
"summary": "Create a personal access token.",
|
|
4061
|
+
"tag": "User Tokens",
|
|
4062
|
+
"params": []
|
|
4063
|
+
},
|
|
4064
|
+
{
|
|
4065
|
+
"method": "DELETE",
|
|
4066
|
+
"path": "/api/user/tokens/{tokenId}",
|
|
4067
|
+
"operationId": "deleteUserToken",
|
|
4068
|
+
"summary": "Revoke and delete a personal access token.",
|
|
4069
|
+
"tag": "User Tokens",
|
|
4070
|
+
"params": [
|
|
4071
|
+
"tokenId"
|
|
4072
|
+
]
|
|
4073
|
+
}
|
|
4074
|
+
];
|
|
4075
|
+
|
|
4076
|
+
// src/scanner/cli/api.ts
|
|
4077
|
+
var METHODS = /* @__PURE__ */ new Set([
|
|
4078
|
+
"GET",
|
|
4079
|
+
"POST",
|
|
4080
|
+
"PUT",
|
|
4081
|
+
"PATCH",
|
|
4082
|
+
"DELETE",
|
|
4083
|
+
"HEAD",
|
|
4084
|
+
"OPTIONS"
|
|
4085
|
+
]);
|
|
4086
|
+
var HELP = `uidex api \u2014 call the uidex API
|
|
4087
|
+
|
|
4088
|
+
Usage:
|
|
4089
|
+
uidex api <METHOD> <PATH> [flags] Make an API request
|
|
4090
|
+
uidex api --list [--tag <tag>] List available routes
|
|
4091
|
+
uidex api login Authenticate via browser
|
|
4092
|
+
uidex api login --token <tok> Store an auth token directly
|
|
4093
|
+
uidex api status Check auth state
|
|
4094
|
+
|
|
4095
|
+
Flags:
|
|
4096
|
+
--body <json> Request body (JSON string)
|
|
4097
|
+
--query <params> Query string (key=val&key2=val2)
|
|
4098
|
+
--base-url <url> Override the API base URL
|
|
4099
|
+
--token <tok> Use a specific token (instead of stored)
|
|
4100
|
+
--raw Disable JSON pretty-printing
|
|
4101
|
+
--no-color Disable colored output
|
|
4102
|
+
--list List available API routes
|
|
4103
|
+
--tag <tag> Filter --list by tag
|
|
4104
|
+
--json Emit --list as JSON
|
|
4105
|
+
`;
|
|
4106
|
+
async function runApiCommand(opts) {
|
|
4107
|
+
const { positional, flags } = parseArgs(opts.argv);
|
|
4108
|
+
const stdout = [];
|
|
4109
|
+
const stderr = [];
|
|
4110
|
+
const color = opts.color ?? (flags["no-color"] ? false : process.stdout.isTTY ?? false);
|
|
4111
|
+
const ctx = { flags, opts, color, stdout, stderr };
|
|
4112
|
+
const sub = positional[0];
|
|
4113
|
+
if (!sub && !flags.list) {
|
|
4114
|
+
stdout.push(HELP);
|
|
4115
|
+
return result(0, stdout, stderr);
|
|
4116
|
+
}
|
|
4117
|
+
if (flags.list) return listRoutes(ctx);
|
|
4118
|
+
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
4119
|
+
stdout.push(HELP);
|
|
4120
|
+
return result(0, stdout, stderr);
|
|
4121
|
+
}
|
|
4122
|
+
if (sub === "login") return runLogin(ctx);
|
|
4123
|
+
if (sub === "status") return runStatus(ctx);
|
|
4124
|
+
const method = sub?.toUpperCase();
|
|
4125
|
+
const path10 = positional[1];
|
|
4126
|
+
if (!method || !METHODS.has(method)) {
|
|
4127
|
+
stderr.push(`Unknown command or method: ${sub}`);
|
|
4128
|
+
stderr.push(HELP);
|
|
4129
|
+
return result(1, stdout, stderr);
|
|
4130
|
+
}
|
|
4131
|
+
if (!path10) {
|
|
4132
|
+
stderr.push("Missing path. Usage: uidex api GET /api/organizations");
|
|
4133
|
+
return result(1, stdout, stderr);
|
|
4134
|
+
}
|
|
4135
|
+
return runRequest(ctx, method, path10);
|
|
4136
|
+
}
|
|
4137
|
+
function listRoutes(ctx) {
|
|
4138
|
+
const { flags, color, stdout, stderr } = ctx;
|
|
4139
|
+
let routes = API_ROUTES;
|
|
4140
|
+
const tagFilter = flags.tag;
|
|
4141
|
+
if (typeof tagFilter === "string") {
|
|
4142
|
+
routes = routes.filter(
|
|
4143
|
+
(r) => r.tag.toLowerCase() === tagFilter.toLowerCase()
|
|
4144
|
+
);
|
|
4145
|
+
}
|
|
4146
|
+
if (flags.json) {
|
|
4147
|
+
stdout.push(JSON.stringify(routes, null, 2));
|
|
4148
|
+
return result(0, stdout, stderr);
|
|
4149
|
+
}
|
|
4150
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4151
|
+
for (const r of routes) {
|
|
4152
|
+
const tag = r.tag || "Other";
|
|
4153
|
+
let list = grouped.get(tag);
|
|
4154
|
+
if (!list) {
|
|
4155
|
+
list = [];
|
|
4156
|
+
grouped.set(tag, list);
|
|
4157
|
+
}
|
|
4158
|
+
list.push(r);
|
|
4159
|
+
}
|
|
4160
|
+
const methodPad = 7;
|
|
4161
|
+
for (const [tag, tagRoutes] of grouped) {
|
|
4162
|
+
stdout.push("");
|
|
4163
|
+
stdout.push(boldText(tag, color));
|
|
4164
|
+
for (const r of tagRoutes) {
|
|
4165
|
+
const m = r.method.padEnd(methodPad);
|
|
4166
|
+
const desc = r.summary ? dimText(` ${r.operationId} \u2014 ${r.summary}`, color) : dimText(` ${r.operationId}`, color);
|
|
4167
|
+
stdout.push(` ${m} ${r.path}${desc}`);
|
|
4168
|
+
}
|
|
4169
|
+
}
|
|
4170
|
+
stdout.push("");
|
|
4171
|
+
return result(0, stdout, stderr);
|
|
4172
|
+
}
|
|
4173
|
+
async function runLogin(ctx) {
|
|
4174
|
+
const { flags, opts, color, stdout, stderr } = ctx;
|
|
4175
|
+
const token = flags.token;
|
|
4176
|
+
if (typeof token === "string" && token.length > 0) {
|
|
4177
|
+
const storage = resolveTokenStorage(opts);
|
|
4178
|
+
storage.set(token);
|
|
4179
|
+
stdout.push("Token stored.");
|
|
4180
|
+
return result(0, stdout, stderr);
|
|
4181
|
+
}
|
|
4182
|
+
return runBrowserLogin(ctx);
|
|
4183
|
+
}
|
|
4184
|
+
async function runBrowserLogin(ctx) {
|
|
4185
|
+
const { flags, opts, color, stdout, stderr } = ctx;
|
|
4186
|
+
const http = await import("http");
|
|
4187
|
+
const crypto = await import("crypto");
|
|
4188
|
+
const { exec } = await import("child_process");
|
|
4189
|
+
const baseUrl = resolveBaseUrl(flags, opts);
|
|
4190
|
+
const code = crypto.randomBytes(3).toString("hex").toUpperCase();
|
|
4191
|
+
return new Promise((resolve7) => {
|
|
4192
|
+
let settled = false;
|
|
4193
|
+
const settle = (exitCode) => {
|
|
4194
|
+
if (settled) return;
|
|
4195
|
+
settled = true;
|
|
4196
|
+
server.close();
|
|
4197
|
+
clearTimeout(timeout);
|
|
4198
|
+
resolve7(result(exitCode, stdout, stderr));
|
|
4199
|
+
};
|
|
4200
|
+
const timeout = setTimeout(() => {
|
|
4201
|
+
stderr.push("Timed out waiting for browser authorization.");
|
|
4202
|
+
settle(1);
|
|
4203
|
+
}, 12e4);
|
|
4204
|
+
const server = http.createServer((req, res) => {
|
|
4205
|
+
if (!req.url?.startsWith("/callback")) {
|
|
4206
|
+
res.writeHead(404);
|
|
4207
|
+
res.end();
|
|
4208
|
+
return;
|
|
4209
|
+
}
|
|
4210
|
+
const url = new URL(req.url, `http://127.0.0.1`);
|
|
4211
|
+
const receivedToken = url.searchParams.get("token");
|
|
4212
|
+
const receivedCode = url.searchParams.get("code");
|
|
4213
|
+
if (receivedCode !== code) {
|
|
4214
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
4215
|
+
res.end(JSON.stringify({ error: "Code mismatch" }));
|
|
4216
|
+
stderr.push("Received callback with mismatched code. Aborting.");
|
|
4217
|
+
settle(1);
|
|
4218
|
+
return;
|
|
4219
|
+
}
|
|
4220
|
+
if (!receivedToken) {
|
|
4221
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
4222
|
+
res.end(JSON.stringify({ error: "Missing token" }));
|
|
4223
|
+
stderr.push("Received callback without token. Aborting.");
|
|
4224
|
+
settle(1);
|
|
4225
|
+
return;
|
|
4226
|
+
}
|
|
4227
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4228
|
+
res.end(JSON.stringify({ ok: true }));
|
|
4229
|
+
const storage = resolveTokenStorage(opts);
|
|
4230
|
+
storage.set(receivedToken);
|
|
4231
|
+
stdout.push(dimText("Authenticated successfully.", color));
|
|
4232
|
+
settle(0);
|
|
4233
|
+
});
|
|
4234
|
+
server.listen(0, "127.0.0.1", () => {
|
|
4235
|
+
const addr = server.address();
|
|
4236
|
+
if (!addr || typeof addr === "string") {
|
|
4237
|
+
stderr.push("Failed to start local server.");
|
|
4238
|
+
settle(1);
|
|
4239
|
+
return;
|
|
4240
|
+
}
|
|
4241
|
+
const port = addr.port;
|
|
4242
|
+
const authUrl = `${baseUrl}/cli-auth?code=${encodeURIComponent(code)}&port=${port}`;
|
|
4243
|
+
stdout.push(dimText("Opening browser to authorize...", color));
|
|
4244
|
+
stdout.push("");
|
|
4245
|
+
stdout.push(` Confirmation code: ${boldText(code, color)}`);
|
|
4246
|
+
stdout.push("");
|
|
4247
|
+
stdout.push(
|
|
4248
|
+
dimText("If the browser doesn't open, visit this URL manually:", color)
|
|
4249
|
+
);
|
|
4250
|
+
stdout.push(` ${authUrl}`);
|
|
4251
|
+
stdout.push("");
|
|
4252
|
+
process.stdout.write(result(0, stdout, stderr).stdout);
|
|
4253
|
+
stdout.length = 0;
|
|
4254
|
+
openBrowser(authUrl);
|
|
4255
|
+
});
|
|
4256
|
+
server.on("error", (err2) => {
|
|
4257
|
+
stderr.push(`Failed to start local server: ${err2.message}`);
|
|
4258
|
+
settle(1);
|
|
4259
|
+
});
|
|
4260
|
+
});
|
|
4261
|
+
}
|
|
4262
|
+
function openBrowser(url) {
|
|
4263
|
+
const { exec } = require("child_process");
|
|
4264
|
+
const platform = process.platform;
|
|
4265
|
+
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
4266
|
+
exec(`${cmd} ${JSON.stringify(url)}`);
|
|
4267
|
+
}
|
|
4268
|
+
function runStatus(ctx) {
|
|
4269
|
+
const { flags, opts, stdout, stderr } = ctx;
|
|
4270
|
+
const storage = resolveTokenStorage(opts);
|
|
4271
|
+
const baseUrl = resolveBaseUrl(flags, opts);
|
|
4272
|
+
const token = storage.get();
|
|
4273
|
+
stdout.push(`baseUrl: ${baseUrl}`);
|
|
4274
|
+
stdout.push(`auth: ${token ? "authenticated" : "not authenticated"}`);
|
|
4275
|
+
return result(token ? 0 : 1, stdout, stderr);
|
|
4276
|
+
}
|
|
4277
|
+
async function runRequest(ctx, method, path10) {
|
|
4278
|
+
const { flags, opts, color, stdout, stderr } = ctx;
|
|
4279
|
+
const tokenFromFlag = flags.token;
|
|
4280
|
+
const token = typeof tokenFromFlag === "string" ? tokenFromFlag : resolveTokenStorage(opts).get();
|
|
4281
|
+
if (!token) {
|
|
4282
|
+
stderr.push("Not authenticated. Run: uidex api login --token <value>");
|
|
4283
|
+
return result(1, stdout, stderr);
|
|
4284
|
+
}
|
|
4285
|
+
const baseUrl = resolveBaseUrl(flags, opts);
|
|
4286
|
+
const http = opts.http ?? createHttpClient({ baseUrl, token: () => token });
|
|
4287
|
+
const bodyStr = typeof flags.body === "string" ? flags.body : void 0;
|
|
4288
|
+
if (bodyStr !== void 0) {
|
|
4289
|
+
try {
|
|
4290
|
+
JSON.parse(bodyStr);
|
|
4291
|
+
} catch {
|
|
4292
|
+
stderr.push("Invalid JSON in --body");
|
|
4293
|
+
return result(1, stdout, stderr);
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
const res = await http.request(path10, {
|
|
4297
|
+
method,
|
|
4298
|
+
body: bodyStr,
|
|
4299
|
+
query: typeof flags.query === "string" ? flags.query : void 0
|
|
4300
|
+
});
|
|
4301
|
+
const isSuccess = res.status >= 200 && res.status < 300;
|
|
4302
|
+
if (!isSuccess) {
|
|
4303
|
+
stderr.push(formatStatus(res.status, color));
|
|
4304
|
+
}
|
|
4305
|
+
if (res.body !== void 0 && res.body !== null && res.body !== "") {
|
|
4306
|
+
if (flags.raw) {
|
|
4307
|
+
stdout.push(res.raw);
|
|
4308
|
+
} else {
|
|
4309
|
+
stdout.push(highlightJson(res.body, color));
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
return result(isSuccess ? 0 : 1, stdout, stderr);
|
|
4313
|
+
}
|
|
4314
|
+
function resolveTokenStorage(opts) {
|
|
4315
|
+
if (opts.tokenStorage) return opts.tokenStorage;
|
|
4316
|
+
const envToken = opts.env?.UIDEX_TOKEN;
|
|
4317
|
+
if (envToken) return createMemoryTokenStorage(envToken);
|
|
4318
|
+
return createFileTokenStorage({ path: defaultTokenPath() });
|
|
4319
|
+
}
|
|
4320
|
+
function resolveBaseUrl(flags, opts) {
|
|
4321
|
+
return (typeof flags["base-url"] === "string" ? flags["base-url"] : void 0) ?? opts.baseUrl ?? opts.env?.UIDEX_API_URL ?? "https://app.uidex.dev";
|
|
4322
|
+
}
|
|
4323
|
+
function result(exitCode, stdout, stderr) {
|
|
4324
|
+
return {
|
|
4325
|
+
exitCode,
|
|
4326
|
+
stdout: stdout.join("\n") + (stdout.length ? "\n" : ""),
|
|
4327
|
+
stderr: stderr.join("\n") + (stderr.length ? "\n" : "")
|
|
4328
|
+
};
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
// src/scanner/cli/cli.ts
|
|
4332
|
+
var argv = process.argv.slice(2);
|
|
4333
|
+
var command = argv[0];
|
|
4334
|
+
var resultP = command === "api" ? runApiCommand({ argv: argv.slice(1) }) : run({ argv });
|
|
4335
|
+
resultP.then(
|
|
4336
|
+
(result2) => {
|
|
4337
|
+
if (result2.stdout) process.stdout.write(result2.stdout);
|
|
4338
|
+
if (result2.stderr) process.stderr.write(result2.stderr);
|
|
4339
|
+
process.exit(result2.exitCode);
|
|
3316
4340
|
},
|
|
3317
4341
|
(error) => {
|
|
3318
4342
|
process.stderr.write(
|