uidex 0.3.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.
- package/dist/cli/cli.cjs +1116 -112
- package/dist/cli/cli.cjs.map +1 -1
- package/dist/cloud/index.cjs +395 -72
- package/dist/cloud/index.cjs.map +1 -1
- package/dist/cloud/index.d.cts +60 -86
- package/dist/cloud/index.d.ts +60 -86
- package/dist/cloud/index.js +396 -71
- package/dist/cloud/index.js.map +1 -1
- package/dist/headless/index.cjs +1505 -791
- package/dist/headless/index.cjs.map +1 -1
- package/dist/headless/index.d.cts +83 -75
- package/dist/headless/index.d.ts +83 -75
- package/dist/headless/index.js +1514 -791
- package/dist/headless/index.js.map +1 -1
- package/dist/index.cjs +6281 -3190
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +337 -229
- package/dist/index.d.ts +337 -229
- package/dist/index.js +6362 -3231
- 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 +6291 -3206
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +239 -186
- package/dist/react/index.d.ts +239 -186
- package/dist/react/index.js +6338 -3208
- package/dist/react/index.js.map +1 -1
- package/dist/scan/index.cjs +212 -82
- package/dist/scan/index.cjs.map +1 -1
- package/dist/scan/index.d.cts +31 -0
- package/dist/scan/index.d.ts +31 -0
- package/dist/scan/index.js +211 -81
- package/dist/scan/index.js.map +1 -1
- package/package.json +10 -8
- package/templates/claude/api.md +110 -0
- package/templates/claude/audit.md +8 -2
- package/templates/claude/rules.md +15 -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
|
-
var
|
|
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,12 +235,16 @@ 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";
|
|
244
|
+
var WELL_KNOWN_FILES = {
|
|
245
|
+
page: "uidex.page.ts",
|
|
246
|
+
feature: "uidex.feature.ts"
|
|
247
|
+
};
|
|
243
248
|
var ConfigError = class extends Error {
|
|
244
249
|
constructor(message) {
|
|
245
250
|
super(message);
|
|
@@ -277,14 +282,14 @@ var ALLOWED_AUDIT_KEYS = /* @__PURE__ */ new Set(["scopeLeak", "coverage", "acce
|
|
|
277
282
|
function fail(msg) {
|
|
278
283
|
throw new ConfigError(`Invalid .uidex.json: ${msg}`);
|
|
279
284
|
}
|
|
280
|
-
function assertObject(value,
|
|
285
|
+
function assertObject(value, path10) {
|
|
281
286
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
282
|
-
fail(`${
|
|
287
|
+
fail(`${path10} must be an object`);
|
|
283
288
|
}
|
|
284
289
|
}
|
|
285
|
-
function assertStringArray(value,
|
|
290
|
+
function assertStringArray(value, path10) {
|
|
286
291
|
if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
|
|
287
|
-
fail(`${
|
|
292
|
+
fail(`${path10} must be a string[]`);
|
|
288
293
|
}
|
|
289
294
|
}
|
|
290
295
|
function validateConfig(raw) {
|
|
@@ -398,7 +403,7 @@ var DEFAULT_CONVENTIONS = {
|
|
|
398
403
|
regions: "landmarks"
|
|
399
404
|
};
|
|
400
405
|
|
|
401
|
-
// src/scan/discover.ts
|
|
406
|
+
// src/scanner/scan/discover.ts
|
|
402
407
|
var CONFIG_FILENAME = ".uidex.json";
|
|
403
408
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
404
409
|
"node_modules",
|
|
@@ -457,11 +462,14 @@ function discover(options = {}) {
|
|
|
457
462
|
return results.sort((a, b) => a.configPath.localeCompare(b.configPath));
|
|
458
463
|
}
|
|
459
464
|
|
|
460
|
-
// src/scan/pipeline.ts
|
|
465
|
+
// src/scanner/scan/pipeline.ts
|
|
461
466
|
var fs5 = __toESM(require("fs"), 1);
|
|
462
|
-
var
|
|
467
|
+
var path7 = __toESM(require("path"), 1);
|
|
468
|
+
|
|
469
|
+
// src/scanner/scan/audit.ts
|
|
470
|
+
var path4 = __toESM(require("path"), 1);
|
|
463
471
|
|
|
464
|
-
// src/entities/types.ts
|
|
472
|
+
// src/shared/entities/types.ts
|
|
465
473
|
var ENTITY_KINDS = [
|
|
466
474
|
"route",
|
|
467
475
|
"page",
|
|
@@ -494,7 +502,7 @@ function assertEntityKind(kind) {
|
|
|
494
502
|
if (!KIND_SET.has(kind)) throw new UnknownEntityKindError(kind);
|
|
495
503
|
}
|
|
496
504
|
|
|
497
|
-
// src/entities/registry.ts
|
|
505
|
+
// src/shared/entities/registry.ts
|
|
498
506
|
function emptyStore() {
|
|
499
507
|
return {
|
|
500
508
|
route: /* @__PURE__ */ new Map(),
|
|
@@ -558,11 +566,11 @@ function createRegistry() {
|
|
|
558
566
|
};
|
|
559
567
|
const query = (predicate) => {
|
|
560
568
|
const flows = getFlows();
|
|
561
|
-
const
|
|
569
|
+
const result2 = [];
|
|
562
570
|
for (const entity of allEntities()) {
|
|
563
|
-
if (predicate(entity))
|
|
571
|
+
if (predicate(entity)) result2.push(freezeEntity(entity, flows));
|
|
564
572
|
}
|
|
565
|
-
return
|
|
573
|
+
return result2;
|
|
566
574
|
};
|
|
567
575
|
const byScope = (scope) => query(
|
|
568
576
|
(entity) => "scopes" in entity && Array.isArray(entity.scopes) && entity.scopes.includes(scope)
|
|
@@ -576,10 +584,33 @@ function createRegistry() {
|
|
|
576
584
|
return ids.has(entity.id);
|
|
577
585
|
});
|
|
578
586
|
};
|
|
579
|
-
|
|
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
|
+
};
|
|
580
611
|
}
|
|
581
612
|
|
|
582
|
-
// src/scan/audit.ts
|
|
613
|
+
// src/scanner/scan/audit.ts
|
|
583
614
|
var MARKER_FILENAMES = ["UIDEX_PAGE.md", "UIDEX_FEATURE.md"];
|
|
584
615
|
function audit(opts) {
|
|
585
616
|
const diagnostics = [];
|
|
@@ -681,6 +712,32 @@ function audit(opts) {
|
|
|
681
712
|
}
|
|
682
713
|
}
|
|
683
714
|
}
|
|
715
|
+
if (lint) {
|
|
716
|
+
const scannedPaths = new Set(files.map((f) => f.displayPath));
|
|
717
|
+
for (const ef of extracted) {
|
|
718
|
+
if (!ef.metadata) continue;
|
|
719
|
+
for (const m of ef.metadata) {
|
|
720
|
+
if (m.kind !== "page" && m.kind !== "feature") continue;
|
|
721
|
+
if (typeof m.id !== "string") continue;
|
|
722
|
+
const filePath = ef.file.displayPath;
|
|
723
|
+
const wellKnownName = WELL_KNOWN_FILES[m.kind];
|
|
724
|
+
if (path4.posix.basename(filePath) === wellKnownName) continue;
|
|
725
|
+
const dir = path4.posix.dirname(filePath);
|
|
726
|
+
const wellKnownPath = dir === "." ? wellKnownName : `${dir}/${wellKnownName}`;
|
|
727
|
+
if (scannedPaths.has(wellKnownPath)) continue;
|
|
728
|
+
const kindLabel = m.kind === "page" ? "Page" : "Feature";
|
|
729
|
+
diagnostics.push({
|
|
730
|
+
code: "prefer-well-known-file",
|
|
731
|
+
severity: "info",
|
|
732
|
+
message: `${kindLabel} "${m.id}" metadata lives on ${filePath}; prefer ${wellKnownPath}`,
|
|
733
|
+
file: filePath,
|
|
734
|
+
line: m.loc.line,
|
|
735
|
+
entity: { kind: m.kind, id: m.id },
|
|
736
|
+
hint: `Move the \`export const uidex\` block to ${wellKnownPath} and remove it from ${filePath}.`
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
684
741
|
if (lint) {
|
|
685
742
|
for (const f of files) {
|
|
686
743
|
const lines = f.content.split("\n");
|
|
@@ -707,6 +764,15 @@ function audit(opts) {
|
|
|
707
764
|
const primitives = registry.list("primitive");
|
|
708
765
|
const byName = /* @__PURE__ */ new Map();
|
|
709
766
|
for (const p2 of primitives) byName.set(p2.id, p2);
|
|
767
|
+
const declaredFeatures = /* @__PURE__ */ new Map();
|
|
768
|
+
for (const ef of extracted) {
|
|
769
|
+
if (!ef.metadata) continue;
|
|
770
|
+
for (const m of ef.metadata) {
|
|
771
|
+
if (m.features && m.features.length > 0) {
|
|
772
|
+
declaredFeatures.set(ef.file.displayPath, new Set(m.features));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
710
776
|
for (const f of files) {
|
|
711
777
|
const importRe = /import\s+(?:[^'"]+)\s+from\s+['"]([^'"]+)['"]/g;
|
|
712
778
|
let m;
|
|
@@ -717,18 +783,23 @@ function audit(opts) {
|
|
|
717
783
|
baseName2.replace(/\.(tsx|ts|jsx|js|mjs|cjs)$/, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()
|
|
718
784
|
);
|
|
719
785
|
if (!primitive) continue;
|
|
720
|
-
const scope = primitive.scopes?.[0]
|
|
721
|
-
if (scope
|
|
786
|
+
const scope = primitive.scopes?.[0];
|
|
787
|
+
if (!scope) continue;
|
|
722
788
|
const [kind, id] = scope.split(":");
|
|
723
789
|
const importerSegments = f.displayPath.split("/");
|
|
724
|
-
if (
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
});
|
|
790
|
+
if (importerSegments.includes(id) && importerSegments.includes(kind + "s")) {
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (kind === "feature" && importerSegments.includes(id)) continue;
|
|
794
|
+
if (kind === "feature" && declaredFeatures.get(f.displayPath)?.has(id)) {
|
|
795
|
+
continue;
|
|
731
796
|
}
|
|
797
|
+
diagnostics.push({
|
|
798
|
+
code: "scope-leak",
|
|
799
|
+
severity: "warning",
|
|
800
|
+
message: `Primitive "${primitive.id}" is scoped to ${scope} but is imported from ${f.displayPath}`,
|
|
801
|
+
file: f.displayPath
|
|
802
|
+
});
|
|
732
803
|
}
|
|
733
804
|
}
|
|
734
805
|
}
|
|
@@ -902,7 +973,7 @@ function stableReplacer(_key, value) {
|
|
|
902
973
|
return value;
|
|
903
974
|
}
|
|
904
975
|
|
|
905
|
-
// src/scan/emit.ts
|
|
976
|
+
// src/scanner/scan/emit.ts
|
|
906
977
|
function sortById(arr) {
|
|
907
978
|
return [...arr].sort((a, b) => a.id.localeCompare(b.id));
|
|
908
979
|
}
|
|
@@ -1026,6 +1097,7 @@ function emit(opts) {
|
|
|
1026
1097
|
lines.push(" export interface Feature {");
|
|
1027
1098
|
lines.push(" feature: FeatureId | false");
|
|
1028
1099
|
lines.push(" name?: string");
|
|
1100
|
+
lines.push(" features?: readonly FeatureId[]");
|
|
1029
1101
|
lines.push(" acceptance?: readonly string[]");
|
|
1030
1102
|
lines.push(" description?: string");
|
|
1031
1103
|
lines.push(" }");
|
|
@@ -1082,7 +1154,7 @@ function emit(opts) {
|
|
|
1082
1154
|
return lines.join("\n");
|
|
1083
1155
|
}
|
|
1084
1156
|
|
|
1085
|
-
// src/scan/extract-uidex-export.ts
|
|
1157
|
+
// src/scanner/scan/extract-uidex-export.ts
|
|
1086
1158
|
var KIND_DISCRIMINATORS = [
|
|
1087
1159
|
"page",
|
|
1088
1160
|
"feature",
|
|
@@ -1100,7 +1172,13 @@ var ALLOWED_FIELDS = {
|
|
|
1100
1172
|
"acceptance",
|
|
1101
1173
|
"description"
|
|
1102
1174
|
]),
|
|
1103
|
-
feature: /* @__PURE__ */ new Set([
|
|
1175
|
+
feature: /* @__PURE__ */ new Set([
|
|
1176
|
+
"feature",
|
|
1177
|
+
"name",
|
|
1178
|
+
"features",
|
|
1179
|
+
"acceptance",
|
|
1180
|
+
"description"
|
|
1181
|
+
]),
|
|
1104
1182
|
primitive: /* @__PURE__ */ new Set(["primitive", "name", "description"]),
|
|
1105
1183
|
widget: /* @__PURE__ */ new Set(["widget", "name", "acceptance", "description"]),
|
|
1106
1184
|
flow: /* @__PURE__ */ new Set(["flow", "notFlow", "name", "description"])
|
|
@@ -1836,7 +1914,7 @@ function buildMetadata(value, file, headerPos, diagnostics) {
|
|
|
1836
1914
|
line: pos.line
|
|
1837
1915
|
});
|
|
1838
1916
|
}
|
|
1839
|
-
const features = kind === "page" ? readStringArrayField(byKey, "features") : void 0;
|
|
1917
|
+
const features = kind === "page" || kind === "feature" ? readStringArrayField(byKey, "features") : void 0;
|
|
1840
1918
|
const widgets = kind === "page" ? readStringArrayField(byKey, "widgets") : void 0;
|
|
1841
1919
|
const notFlow = kind === "flow" && discriminator === "notFlow" ? true : void 0;
|
|
1842
1920
|
const metadata = {
|
|
@@ -1931,7 +2009,7 @@ function posAt(content, offset) {
|
|
|
1931
2009
|
return { offset, line, column: offset - lineStart + 1 };
|
|
1932
2010
|
}
|
|
1933
2011
|
|
|
1934
|
-
// src/scan/jsx-ancestry.ts
|
|
2012
|
+
// src/scanner/scan/jsx-ancestry.ts
|
|
1935
2013
|
var DATA_ATTR_RE = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
1936
2014
|
function parseDataAttrs(tagSource) {
|
|
1937
2015
|
if (!tagSource.includes("data-uidex")) return [];
|
|
@@ -2120,7 +2198,7 @@ function findTagEnd(content, start) {
|
|
|
2120
2198
|
return -1;
|
|
2121
2199
|
}
|
|
2122
2200
|
|
|
2123
|
-
// src/scan/extract.ts
|
|
2201
|
+
// src/scanner/scan/extract.ts
|
|
2124
2202
|
var JSDOC_BLOCK = /\/\*\*([\s\S]*?)\*\//g;
|
|
2125
2203
|
function lineAt(content, index) {
|
|
2126
2204
|
let line = 1;
|
|
@@ -2223,7 +2301,7 @@ function extractOne(file) {
|
|
|
2223
2301
|
return annotations;
|
|
2224
2302
|
}
|
|
2225
2303
|
|
|
2226
|
-
// src/scan/git.ts
|
|
2304
|
+
// src/scanner/scan/git.ts
|
|
2227
2305
|
var import_node_child_process = require("child_process");
|
|
2228
2306
|
function runGit(args, cwd) {
|
|
2229
2307
|
try {
|
|
@@ -2255,10 +2333,10 @@ function parseGitHubRef(ref) {
|
|
|
2255
2333
|
return m ? m[1] : null;
|
|
2256
2334
|
}
|
|
2257
2335
|
|
|
2258
|
-
// src/scan/resolve.ts
|
|
2259
|
-
var
|
|
2336
|
+
// src/scanner/scan/resolve.ts
|
|
2337
|
+
var path6 = __toESM(require("path"), 1);
|
|
2260
2338
|
|
|
2261
|
-
// src/scan/routes.ts
|
|
2339
|
+
// src/scanner/scan/routes.ts
|
|
2262
2340
|
var PAGE_BASENAME = /^page\.(tsx|ts|jsx|js|mjs|cjs)$/;
|
|
2263
2341
|
var PAGES_ROUTER_BASENAME = /\.(tsx|ts|jsx|js|mjs|cjs)$/;
|
|
2264
2342
|
var ROUTE_BASENAME = /^route\.(tsx|ts|jsx|js|mjs|cjs)$/;
|
|
@@ -2324,9 +2402,9 @@ function pathToId(routePath) {
|
|
|
2324
2402
|
return routePath.replace(/^\/+/, "").replace(/\[\.{3}([^\]]+)\]/g, "$1").replace(/\[([^\]]+)\]/g, "$1").replace(/\//g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2325
2403
|
}
|
|
2326
2404
|
|
|
2327
|
-
// src/scan/walk.ts
|
|
2405
|
+
// src/scanner/scan/walk.ts
|
|
2328
2406
|
var fs4 = __toESM(require("fs"), 1);
|
|
2329
|
-
var
|
|
2407
|
+
var path5 = __toESM(require("path"), 1);
|
|
2330
2408
|
var DEFAULT_INCLUDES = ["**/*.{ts,tsx,js,jsx,mjs,cjs}"];
|
|
2331
2409
|
var BASE_EXCLUDES = [
|
|
2332
2410
|
"**/node_modules/**",
|
|
@@ -2390,7 +2468,7 @@ function globToRegExp(glob) {
|
|
|
2390
2468
|
return new RegExp(`^${out2}$`);
|
|
2391
2469
|
}
|
|
2392
2470
|
function toPosix(p2) {
|
|
2393
|
-
return p2.split(
|
|
2471
|
+
return p2.split(path5.sep).join("/");
|
|
2394
2472
|
}
|
|
2395
2473
|
function matchesAny(rel, patterns) {
|
|
2396
2474
|
return patterns.some((g) => globToRegExp(g).test(rel));
|
|
@@ -2406,9 +2484,9 @@ function walk(sources, options) {
|
|
|
2406
2484
|
...globalExcludes,
|
|
2407
2485
|
...source.exclude ?? []
|
|
2408
2486
|
];
|
|
2409
|
-
const absRoot =
|
|
2487
|
+
const absRoot = path5.resolve(cwd, source.rootDir);
|
|
2410
2488
|
for (const filePath of walkDir(absRoot, absRoot)) {
|
|
2411
|
-
const rel = toPosix(
|
|
2489
|
+
const rel = toPosix(path5.relative(absRoot, filePath));
|
|
2412
2490
|
if (matchesAny(rel, excludes)) continue;
|
|
2413
2491
|
if (!matchesAny(rel, includes)) continue;
|
|
2414
2492
|
let content;
|
|
@@ -2417,7 +2495,7 @@ function walk(sources, options) {
|
|
|
2417
2495
|
} catch {
|
|
2418
2496
|
continue;
|
|
2419
2497
|
}
|
|
2420
|
-
const relFromCwd = toPosix(
|
|
2498
|
+
const relFromCwd = toPosix(path5.relative(cwd, filePath));
|
|
2421
2499
|
const displayPath = source.prefix ? `${source.prefix.replace(/\/$/, "")}/${rel}` : relFromCwd;
|
|
2422
2500
|
out2.push({
|
|
2423
2501
|
sourcePath: filePath,
|
|
@@ -2437,7 +2515,7 @@ function* walkDir(root, dir) {
|
|
|
2437
2515
|
return;
|
|
2438
2516
|
}
|
|
2439
2517
|
for (const entry of entries) {
|
|
2440
|
-
const full =
|
|
2518
|
+
const full = path5.join(dir, entry.name);
|
|
2441
2519
|
if (entry.isDirectory()) {
|
|
2442
2520
|
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === "build" || entry.name === ".next") {
|
|
2443
2521
|
continue;
|
|
@@ -2449,7 +2527,7 @@ function* walkDir(root, dir) {
|
|
|
2449
2527
|
}
|
|
2450
2528
|
}
|
|
2451
2529
|
|
|
2452
|
-
// src/scan/resolve.ts
|
|
2530
|
+
// src/scanner/scan/resolve.ts
|
|
2453
2531
|
var DOM_ATTR_KINDS = /* @__PURE__ */ new Set([
|
|
2454
2532
|
"element",
|
|
2455
2533
|
"region",
|
|
@@ -2469,7 +2547,7 @@ function kebab(str) {
|
|
|
2469
2547
|
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").toLowerCase();
|
|
2470
2548
|
}
|
|
2471
2549
|
function baseName(file) {
|
|
2472
|
-
const b =
|
|
2550
|
+
const b = path6.posix.basename(file);
|
|
2473
2551
|
return b.replace(/\.(tsx|ts|jsx|js|mjs|cjs)$/, "");
|
|
2474
2552
|
}
|
|
2475
2553
|
var LANDMARK_RE = /<(header|nav|main|aside|footer)(\s[^>]*)?>|role=["']region["']/gi;
|
|
@@ -2544,7 +2622,24 @@ function resolve3(ctx) {
|
|
|
2544
2622
|
const routes = conventions.pages === "auto" ? detectRoutes(ctx.extracted.map((e) => e.file)) : [];
|
|
2545
2623
|
const handledPageFiles = /* @__PURE__ */ new Set();
|
|
2546
2624
|
for (const route of routes) {
|
|
2547
|
-
const
|
|
2625
|
+
const routeDir = path6.posix.dirname(route.file);
|
|
2626
|
+
const wellKnownPath = `${routeDir}/${WELL_KNOWN_FILES.page}`;
|
|
2627
|
+
const wellKnownExp = exportFor(wellKnownPath, "page");
|
|
2628
|
+
const routeExp = exportFor(route.file, "page");
|
|
2629
|
+
const exp = wellKnownExp ?? routeExp;
|
|
2630
|
+
const locFile = wellKnownExp ? wellKnownPath : route.file;
|
|
2631
|
+
if (wellKnownExp) handledPageFiles.add(wellKnownPath);
|
|
2632
|
+
handledPageFiles.add(route.file);
|
|
2633
|
+
if (wellKnownExp && routeExp) {
|
|
2634
|
+
diagnostics.push({
|
|
2635
|
+
code: "competing-uidex-export",
|
|
2636
|
+
severity: "warning",
|
|
2637
|
+
message: `Page metadata declared in both ${wellKnownPath} and ${route.file}; ${wellKnownPath} takes precedence.`,
|
|
2638
|
+
file: route.file,
|
|
2639
|
+
line: routeExp.loc.line,
|
|
2640
|
+
hint: `Remove the export from ${route.file} or delete ${wellKnownPath}.`
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2548
2643
|
if (exp && exp.id === false) continue;
|
|
2549
2644
|
const effectiveId = exp && typeof exp.id === "string" ? exp.id : route.id;
|
|
2550
2645
|
const meta = exp ? buildMetaFromExport(exp) : void 0;
|
|
@@ -2552,11 +2647,10 @@ function resolve3(ctx) {
|
|
|
2552
2647
|
const page = {
|
|
2553
2648
|
kind: "page",
|
|
2554
2649
|
id: effectiveId,
|
|
2555
|
-
loc: { file:
|
|
2650
|
+
loc: { file: locFile, line: exp?.loc.line },
|
|
2556
2651
|
...meta ? { meta } : {}
|
|
2557
2652
|
};
|
|
2558
2653
|
registry.add(page);
|
|
2559
|
-
handledPageFiles.add(route.file);
|
|
2560
2654
|
}
|
|
2561
2655
|
for (const ef of ctx.extracted) {
|
|
2562
2656
|
const exp = exportFor(ef.file.displayPath, "page");
|
|
@@ -2572,7 +2666,8 @@ function resolve3(ctx) {
|
|
|
2572
2666
|
}
|
|
2573
2667
|
const featureGlob = typeof conventions.features === "string" ? conventions.features : null;
|
|
2574
2668
|
const conventionalFeatureDirs = /* @__PURE__ */ new Set();
|
|
2575
|
-
const
|
|
2669
|
+
const featureExportFilesByDir = /* @__PURE__ */ new Map();
|
|
2670
|
+
const wellKnownFeatureFileByDir = /* @__PURE__ */ new Map();
|
|
2576
2671
|
const suppressedFeatureDirs = /* @__PURE__ */ new Set();
|
|
2577
2672
|
if (featureGlob) {
|
|
2578
2673
|
const re = globToRegExp(featureGlob + "/**");
|
|
@@ -2581,17 +2676,44 @@ function resolve3(ctx) {
|
|
|
2581
2676
|
const dir = extractFeatureDir(ef.file.displayPath, featureGlob);
|
|
2582
2677
|
if (!dir) continue;
|
|
2583
2678
|
conventionalFeatureDirs.add(dir);
|
|
2679
|
+
const isWellKnown = path6.posix.basename(ef.file.displayPath) === WELL_KNOWN_FILES.feature;
|
|
2680
|
+
if (isWellKnown) wellKnownFeatureFileByDir.set(dir, ef.file.displayPath);
|
|
2584
2681
|
const exp = exportFor(ef.file.displayPath, "feature");
|
|
2585
2682
|
if (exp) {
|
|
2586
2683
|
if (exp.id === false) suppressedFeatureDirs.add(dir);
|
|
2587
|
-
else
|
|
2588
|
-
|
|
2684
|
+
else {
|
|
2685
|
+
let arr = featureExportFilesByDir.get(dir);
|
|
2686
|
+
if (!arr) {
|
|
2687
|
+
arr = [];
|
|
2688
|
+
featureExportFilesByDir.set(dir, arr);
|
|
2689
|
+
}
|
|
2690
|
+
arr.push({ file: ef.file.displayPath, exp });
|
|
2691
|
+
}
|
|
2589
2692
|
}
|
|
2590
2693
|
}
|
|
2591
2694
|
for (const dir of conventionalFeatureDirs) {
|
|
2592
2695
|
if (suppressedFeatureDirs.has(dir)) continue;
|
|
2593
|
-
const
|
|
2594
|
-
const
|
|
2696
|
+
const allExports = featureExportFilesByDir.get(dir) ?? [];
|
|
2697
|
+
const wellKnownPath = wellKnownFeatureFileByDir.get(dir);
|
|
2698
|
+
const wellKnownEntry = wellKnownPath ? allExports.find((e) => e.file === wellKnownPath) : void 0;
|
|
2699
|
+
let exp;
|
|
2700
|
+
if (wellKnownEntry) {
|
|
2701
|
+
exp = wellKnownEntry.exp;
|
|
2702
|
+
for (const other of allExports) {
|
|
2703
|
+
if (other.file === wellKnownEntry.file) continue;
|
|
2704
|
+
diagnostics.push({
|
|
2705
|
+
code: "competing-uidex-export",
|
|
2706
|
+
severity: "warning",
|
|
2707
|
+
message: `Feature metadata declared in both ${wellKnownEntry.file} and ${other.file}; ${wellKnownEntry.file} takes precedence.`,
|
|
2708
|
+
file: other.file,
|
|
2709
|
+
line: other.exp.loc.line,
|
|
2710
|
+
hint: `Remove the export from ${other.file} or delete ${wellKnownEntry.file}.`
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
} else if (allExports.length > 0) {
|
|
2714
|
+
exp = allExports[0].exp;
|
|
2715
|
+
}
|
|
2716
|
+
const id = exp && typeof exp.id === "string" ? exp.id : path6.posix.basename(dir);
|
|
2595
2717
|
const meta = exp ? buildMetaFromExport(exp) : void 0;
|
|
2596
2718
|
const feature = {
|
|
2597
2719
|
kind: "feature",
|
|
@@ -2723,11 +2845,12 @@ function resolve3(ctx) {
|
|
|
2723
2845
|
exp.id,
|
|
2724
2846
|
buildMetaFromExport(exp)
|
|
2725
2847
|
);
|
|
2848
|
+
const scope = computeScope(file);
|
|
2726
2849
|
const primitive = {
|
|
2727
2850
|
kind: "primitive",
|
|
2728
2851
|
id: exp.id,
|
|
2729
2852
|
loc: { file, line: exp.loc.line },
|
|
2730
|
-
scopes: [
|
|
2853
|
+
...scope ? { scopes: [scope] } : {},
|
|
2731
2854
|
...meta ? { meta } : {}
|
|
2732
2855
|
};
|
|
2733
2856
|
registry.add(primitive);
|
|
@@ -2739,11 +2862,12 @@ function resolve3(ctx) {
|
|
|
2739
2862
|
if (domPrimitives.length > 0) {
|
|
2740
2863
|
for (const p2 of domPrimitives) {
|
|
2741
2864
|
const meta = metaWithComposes("primitive", p2.id);
|
|
2865
|
+
const domScope = computeScope(p2.file);
|
|
2742
2866
|
const primitive = {
|
|
2743
2867
|
kind: "primitive",
|
|
2744
2868
|
id: p2.id,
|
|
2745
2869
|
loc: { file: p2.file, line: p2.line },
|
|
2746
|
-
scopes: [
|
|
2870
|
+
...domScope ? { scopes: [domScope] } : {},
|
|
2747
2871
|
...meta ? { meta } : {}
|
|
2748
2872
|
};
|
|
2749
2873
|
registry.add(primitive);
|
|
@@ -2753,13 +2877,13 @@ function resolve3(ctx) {
|
|
|
2753
2877
|
if (primitiveConventions && fileMatchesAny(file, primitiveConventions)) {
|
|
2754
2878
|
const name = kebab(baseName(file));
|
|
2755
2879
|
if (!name) continue;
|
|
2756
|
-
const
|
|
2880
|
+
const convScope = computeScope(file);
|
|
2757
2881
|
const meta = metaWithComposes("primitive", name);
|
|
2758
2882
|
const primitive = {
|
|
2759
2883
|
kind: "primitive",
|
|
2760
2884
|
id: name,
|
|
2761
2885
|
loc: { file },
|
|
2762
|
-
scopes: [
|
|
2886
|
+
...convScope ? { scopes: [convScope] } : {},
|
|
2763
2887
|
...meta ? { meta } : {}
|
|
2764
2888
|
};
|
|
2765
2889
|
registry.add(primitive);
|
|
@@ -2789,7 +2913,8 @@ function resolve3(ctx) {
|
|
|
2789
2913
|
kind: "flow",
|
|
2790
2914
|
id: flowExport.id,
|
|
2791
2915
|
loc: base.loc,
|
|
2792
|
-
touches: base.touches
|
|
2916
|
+
touches: base.touches,
|
|
2917
|
+
steps: base.steps
|
|
2793
2918
|
};
|
|
2794
2919
|
registry.add(flow);
|
|
2795
2920
|
} else {
|
|
@@ -2834,7 +2959,7 @@ function computeScope(displayPath) {
|
|
|
2834
2959
|
if (pagesIdx !== -1 && parts[pagesIdx + 1]) {
|
|
2835
2960
|
return `page:${parts[pagesIdx + 1]}`;
|
|
2836
2961
|
}
|
|
2837
|
-
return
|
|
2962
|
+
return null;
|
|
2838
2963
|
}
|
|
2839
2964
|
function extractFlowsFromSource(file) {
|
|
2840
2965
|
const flows = [];
|
|
@@ -2868,17 +2993,18 @@ function extractFlowsFromSource(file) {
|
|
|
2868
2993
|
kind: "flow",
|
|
2869
2994
|
id,
|
|
2870
2995
|
loc: { file: file.displayPath, line },
|
|
2871
|
-
touches: dedupe(touches.map((t) => t.id))
|
|
2996
|
+
touches: dedupe(touches.map((t) => t.id)),
|
|
2997
|
+
steps: touches.filter((t) => t.action).map((t) => ({ entityId: t.id, action: t.action }))
|
|
2872
2998
|
});
|
|
2873
2999
|
}
|
|
2874
3000
|
return flows;
|
|
2875
3001
|
}
|
|
2876
3002
|
function captureUidexIds(body) {
|
|
2877
3003
|
const out2 = [];
|
|
2878
|
-
const re = /uidex\(\s*(?:'([^']+)'|"([^"]+)"|`([^`$]+)`)\s*\)
|
|
3004
|
+
const re = /uidex\(\s*(?:'([^']+)'|"([^"]+)"|`([^`$]+)`)\s*\)(?:\.(\w+)\s*\()?/g;
|
|
2879
3005
|
let m;
|
|
2880
3006
|
while ((m = re.exec(body)) !== null) {
|
|
2881
|
-
out2.push({ id: m[1] || m[2] || m[3] });
|
|
3007
|
+
out2.push({ id: m[1] || m[2] || m[3], action: m[4] });
|
|
2882
3008
|
}
|
|
2883
3009
|
return out2;
|
|
2884
3010
|
}
|
|
@@ -2886,7 +3012,7 @@ function dedupe(arr) {
|
|
|
2886
3012
|
return Array.from(new Set(arr));
|
|
2887
3013
|
}
|
|
2888
3014
|
|
|
2889
|
-
// src/scan/pipeline.ts
|
|
3015
|
+
// src/scanner/scan/pipeline.ts
|
|
2890
3016
|
function runScan(opts = {}) {
|
|
2891
3017
|
const cwd = opts.cwd ?? process.cwd();
|
|
2892
3018
|
const configs = opts.configs ?? discover({ cwd });
|
|
@@ -2918,7 +3044,7 @@ function runOne(dc, opts) {
|
|
|
2918
3044
|
gitContext,
|
|
2919
3045
|
typeMode: config.typeMode
|
|
2920
3046
|
});
|
|
2921
|
-
const outputPath =
|
|
3047
|
+
const outputPath = path7.resolve(configDir, config.output);
|
|
2922
3048
|
const outputRel = config.output;
|
|
2923
3049
|
let existingOnDisk = null;
|
|
2924
3050
|
if (opts.check) {
|
|
@@ -2953,14 +3079,14 @@ function runOne(dc, opts) {
|
|
|
2953
3079
|
outputPath
|
|
2954
3080
|
};
|
|
2955
3081
|
}
|
|
2956
|
-
function writeScanResult(
|
|
2957
|
-
fs5.mkdirSync(
|
|
2958
|
-
fs5.writeFileSync(
|
|
3082
|
+
function writeScanResult(result2) {
|
|
3083
|
+
fs5.mkdirSync(path7.dirname(result2.outputPath), { recursive: true });
|
|
3084
|
+
fs5.writeFileSync(result2.outputPath, result2.generated, "utf8");
|
|
2959
3085
|
}
|
|
2960
3086
|
|
|
2961
|
-
// src/scan/scaffold.ts
|
|
3087
|
+
// src/scanner/scan/scaffold.ts
|
|
2962
3088
|
var fs6 = __toESM(require("fs"), 1);
|
|
2963
|
-
var
|
|
3089
|
+
var path8 = __toESM(require("path"), 1);
|
|
2964
3090
|
function scaffoldWidgetSpec(opts) {
|
|
2965
3091
|
const {
|
|
2966
3092
|
registry,
|
|
@@ -2975,7 +3101,7 @@ function scaffoldWidgetSpec(opts) {
|
|
|
2975
3101
|
}
|
|
2976
3102
|
const criteria = widget.meta?.acceptance ?? [];
|
|
2977
3103
|
const filename = `widget-${widgetId}.spec.ts`;
|
|
2978
|
-
const outputPath =
|
|
3104
|
+
const outputPath = path8.resolve(outDir, filename);
|
|
2979
3105
|
if (fs6.existsSync(outputPath) && !force) {
|
|
2980
3106
|
return {
|
|
2981
3107
|
outputPath,
|
|
@@ -2989,7 +3115,7 @@ function scaffoldWidgetSpec(opts) {
|
|
|
2989
3115
|
criteria,
|
|
2990
3116
|
fixtureImport
|
|
2991
3117
|
});
|
|
2992
|
-
fs6.mkdirSync(
|
|
3118
|
+
fs6.mkdirSync(path8.dirname(outputPath), { recursive: true });
|
|
2993
3119
|
fs6.writeFileSync(outputPath, content, "utf8");
|
|
2994
3120
|
return { outputPath, written: true, skipped: false };
|
|
2995
3121
|
}
|
|
@@ -3021,8 +3147,8 @@ function renderSpec(args) {
|
|
|
3021
3147
|
return lines.join("\n");
|
|
3022
3148
|
}
|
|
3023
3149
|
|
|
3024
|
-
// src/
|
|
3025
|
-
function
|
|
3150
|
+
// src/scanner/cli/parse-args.ts
|
|
3151
|
+
function parseArgs(args) {
|
|
3026
3152
|
const positional = [];
|
|
3027
3153
|
const flags = {};
|
|
3028
3154
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -3046,13 +3172,15 @@ function parseFlags2(args) {
|
|
|
3046
3172
|
}
|
|
3047
3173
|
return { positional, flags };
|
|
3048
3174
|
}
|
|
3175
|
+
|
|
3176
|
+
// src/scanner/scan/cli.ts
|
|
3049
3177
|
async function run(opts) {
|
|
3050
3178
|
const cwd = opts.cwd ?? process.cwd();
|
|
3051
|
-
const { positional, flags } =
|
|
3052
|
-
const
|
|
3179
|
+
const { positional, flags } = parseArgs(opts.argv);
|
|
3180
|
+
const command2 = positional[0] ?? "help";
|
|
3053
3181
|
const writer = createWriter();
|
|
3054
3182
|
try {
|
|
3055
|
-
switch (
|
|
3183
|
+
switch (command2) {
|
|
3056
3184
|
case "help":
|
|
3057
3185
|
case "--help":
|
|
3058
3186
|
case "-h":
|
|
@@ -3065,16 +3193,16 @@ async function run(opts) {
|
|
|
3065
3193
|
case "scaffold":
|
|
3066
3194
|
return runScaffold(cwd, positional.slice(1), flags, writer);
|
|
3067
3195
|
case "ai": {
|
|
3068
|
-
const
|
|
3196
|
+
const result2 = await runAiCommand({
|
|
3069
3197
|
cwd,
|
|
3070
3198
|
argv: opts.argv.slice(1)
|
|
3071
3199
|
});
|
|
3072
|
-
if (
|
|
3073
|
-
if (
|
|
3074
|
-
return writer.result(
|
|
3200
|
+
if (result2.stdout) writer.out(result2.stdout.replace(/\n$/, ""));
|
|
3201
|
+
if (result2.stderr) writer.err(result2.stderr.replace(/\n$/, ""));
|
|
3202
|
+
return writer.result(result2.exitCode);
|
|
3075
3203
|
}
|
|
3076
3204
|
default:
|
|
3077
|
-
writer.err(`Unknown command: ${
|
|
3205
|
+
writer.err(`Unknown command: ${command2}`);
|
|
3078
3206
|
writer.err(helpText2());
|
|
3079
3207
|
return writer.result(1);
|
|
3080
3208
|
}
|
|
@@ -3092,6 +3220,10 @@ function helpText2() {
|
|
|
3092
3220
|
" scan [flags] Run the scanner pipeline",
|
|
3093
3221
|
" scaffold widget <id> Emit a Playwright spec from a widget's acceptance",
|
|
3094
3222
|
" ai <install|uninstall|providers> Manage AI assistant integrations",
|
|
3223
|
+
" api <METHOD> <PATH> Call the uidex API",
|
|
3224
|
+
" api --list Show available API routes",
|
|
3225
|
+
" api login Authenticate via browser",
|
|
3226
|
+
" api login --token <tok> Store an auth token directly",
|
|
3095
3227
|
"",
|
|
3096
3228
|
"Flags:",
|
|
3097
3229
|
" --check Verify the on-disk gen file matches a fresh scan; exit non-zero on drift (read-only)",
|
|
@@ -3103,7 +3235,7 @@ function helpText2() {
|
|
|
3103
3235
|
].join("\n");
|
|
3104
3236
|
}
|
|
3105
3237
|
function runInit(cwd, w) {
|
|
3106
|
-
const configPath =
|
|
3238
|
+
const configPath = path9.join(cwd, CONFIG_FILENAME);
|
|
3107
3239
|
if (fs7.existsSync(configPath)) {
|
|
3108
3240
|
w.err(`.uidex.json already exists at ${configPath}`);
|
|
3109
3241
|
return w.result(1);
|
|
@@ -3115,7 +3247,7 @@ function runInit(cwd, w) {
|
|
|
3115
3247
|
};
|
|
3116
3248
|
fs7.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
3117
3249
|
w.out(`Created ${configPath}`);
|
|
3118
|
-
const gitignorePath =
|
|
3250
|
+
const gitignorePath = path9.join(cwd, ".gitignore");
|
|
3119
3251
|
const entry = "*.gen.ts";
|
|
3120
3252
|
if (fs7.existsSync(gitignorePath)) {
|
|
3121
3253
|
const existing = fs7.readFileSync(gitignorePath, "utf8");
|
|
@@ -3193,18 +3325,18 @@ function runScaffold(cwd, args, flags, w) {
|
|
|
3193
3325
|
for (const r of results) {
|
|
3194
3326
|
const widget = r.registry.get("widget", id);
|
|
3195
3327
|
if (!widget) continue;
|
|
3196
|
-
const outDir =
|
|
3197
|
-
const
|
|
3328
|
+
const outDir = path9.resolve(r.configDir, "e2e");
|
|
3329
|
+
const result2 = scaffoldWidgetSpec({
|
|
3198
3330
|
registry: r.registry,
|
|
3199
3331
|
widgetId: id,
|
|
3200
3332
|
outDir,
|
|
3201
3333
|
force: Boolean(flags.force)
|
|
3202
3334
|
});
|
|
3203
|
-
if (
|
|
3204
|
-
w.err(
|
|
3335
|
+
if (result2.skipped) {
|
|
3336
|
+
w.err(result2.reason ?? "skipped");
|
|
3205
3337
|
return w.result(1);
|
|
3206
3338
|
}
|
|
3207
|
-
w.out(`Wrote ${
|
|
3339
|
+
w.out(`Wrote ${result2.outputPath}`);
|
|
3208
3340
|
return w.result(0);
|
|
3209
3341
|
}
|
|
3210
3342
|
w.err(`Widget "${id}" not found in registry`);
|
|
@@ -3226,12 +3358,884 @@ function createWriter() {
|
|
|
3226
3358
|
};
|
|
3227
3359
|
}
|
|
3228
3360
|
|
|
3229
|
-
// src/cli/
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3361
|
+
// src/scanner/cli/auth.ts
|
|
3362
|
+
function createMemoryTokenStorage(initial) {
|
|
3363
|
+
let token = initial ?? null;
|
|
3364
|
+
return {
|
|
3365
|
+
get: () => token,
|
|
3366
|
+
set: (t) => {
|
|
3367
|
+
token = t;
|
|
3368
|
+
},
|
|
3369
|
+
clear: () => {
|
|
3370
|
+
token = null;
|
|
3371
|
+
}
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
function createFileTokenStorage(options) {
|
|
3375
|
+
const nodeFs = require("fs");
|
|
3376
|
+
const nodePath = require("path");
|
|
3377
|
+
const file = options.path;
|
|
3378
|
+
function read() {
|
|
3379
|
+
if (!nodeFs.existsSync(file)) return null;
|
|
3380
|
+
try {
|
|
3381
|
+
const raw = nodeFs.readFileSync(file, "utf8");
|
|
3382
|
+
const parsed = JSON.parse(raw);
|
|
3383
|
+
if (parsed && typeof parsed === "object") {
|
|
3384
|
+
return parsed;
|
|
3385
|
+
}
|
|
3386
|
+
} catch {
|
|
3387
|
+
return null;
|
|
3388
|
+
}
|
|
3389
|
+
return null;
|
|
3390
|
+
}
|
|
3391
|
+
return {
|
|
3392
|
+
get: () => read()?.token ?? null,
|
|
3393
|
+
set: (token) => {
|
|
3394
|
+
nodeFs.mkdirSync(nodePath.dirname(file), { recursive: true });
|
|
3395
|
+
nodeFs.writeFileSync(file, JSON.stringify({ token }, null, 2));
|
|
3396
|
+
},
|
|
3397
|
+
clear: () => {
|
|
3398
|
+
if (nodeFs.existsSync(file)) nodeFs.unlinkSync(file);
|
|
3399
|
+
}
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
function defaultTokenPath() {
|
|
3403
|
+
const os = require("os");
|
|
3404
|
+
const path10 = require("path");
|
|
3405
|
+
return path10.join(os.homedir(), ".uidex", "cloud-token.json");
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
// src/scanner/cli/http.ts
|
|
3409
|
+
function createHttpClient(options) {
|
|
3410
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
3411
|
+
async function request(path10, opts = {}) {
|
|
3412
|
+
let url = `${baseUrl}${path10.startsWith("/") ? path10 : `/${path10}`}`;
|
|
3413
|
+
if (opts.query) {
|
|
3414
|
+
url += (url.includes("?") ? "&" : "?") + opts.query;
|
|
3415
|
+
}
|
|
3416
|
+
const headers = {
|
|
3417
|
+
Accept: "application/json",
|
|
3418
|
+
...opts.headers
|
|
3419
|
+
};
|
|
3420
|
+
const token = options.token();
|
|
3421
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
3422
|
+
if (opts.body !== void 0) {
|
|
3423
|
+
headers["Content-Type"] ??= "application/json";
|
|
3424
|
+
}
|
|
3425
|
+
const res = await fetch(url, {
|
|
3426
|
+
method: opts.method ?? (opts.body ? "POST" : "GET"),
|
|
3427
|
+
headers,
|
|
3428
|
+
body: opts.body
|
|
3429
|
+
});
|
|
3430
|
+
const raw = await res.text();
|
|
3431
|
+
let body = raw;
|
|
3432
|
+
try {
|
|
3433
|
+
body = JSON.parse(raw);
|
|
3434
|
+
} catch {
|
|
3435
|
+
}
|
|
3436
|
+
return { status: res.status, body, raw };
|
|
3437
|
+
}
|
|
3438
|
+
return { request };
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
// src/scanner/cli/json-highlight.ts
|
|
3442
|
+
var RESET = "\x1B[0m";
|
|
3443
|
+
var BOLD = "\x1B[1m";
|
|
3444
|
+
var DIM = "\x1B[2m";
|
|
3445
|
+
var RED = "\x1B[31m";
|
|
3446
|
+
var CYAN = "\x1B[36m";
|
|
3447
|
+
var GREEN = "\x1B[32m";
|
|
3448
|
+
var YELLOW = "\x1B[33m";
|
|
3449
|
+
var MAGENTA = "\x1B[35m";
|
|
3450
|
+
function highlightJson(value, color) {
|
|
3451
|
+
const json = JSON.stringify(value, null, 2);
|
|
3452
|
+
if (!color) return json;
|
|
3453
|
+
return json.replace(
|
|
3454
|
+
/("(?:\\.|[^"\\])*")\s*:|("(?:\\.|[^"\\])*")|(\b(?:true|false|null)\b)|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g,
|
|
3455
|
+
(match, key, str, literal, num) => {
|
|
3456
|
+
if (key) return `${CYAN}${key}${RESET}:`;
|
|
3457
|
+
if (str) return `${GREEN}${str}${RESET}`;
|
|
3458
|
+
if (literal) return `${MAGENTA}${literal}${RESET}`;
|
|
3459
|
+
if (num) return `${YELLOW}${num}${RESET}`;
|
|
3460
|
+
return match;
|
|
3461
|
+
}
|
|
3462
|
+
);
|
|
3463
|
+
}
|
|
3464
|
+
function formatStatus(status, color) {
|
|
3465
|
+
if (!color) return `HTTP ${status}`;
|
|
3466
|
+
const code = status >= 400 ? RED : status >= 300 ? YELLOW : GREEN;
|
|
3467
|
+
return `${code}HTTP ${status}${RESET}`;
|
|
3468
|
+
}
|
|
3469
|
+
function boldText(text, color) {
|
|
3470
|
+
if (!color) return text;
|
|
3471
|
+
return `${BOLD}${text}${RESET}`;
|
|
3472
|
+
}
|
|
3473
|
+
function dimText(text, color) {
|
|
3474
|
+
if (!color) return text;
|
|
3475
|
+
return `${DIM}${text}${RESET}`;
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
// src/scanner/cli/api-routes.gen.ts
|
|
3479
|
+
var API_ROUTES = [
|
|
3480
|
+
{
|
|
3481
|
+
"method": "POST",
|
|
3482
|
+
"path": "/api/ingest",
|
|
3483
|
+
"operationId": "submitReport",
|
|
3484
|
+
"summary": "Submit feedback from the SDK.",
|
|
3485
|
+
"tag": "Ingest",
|
|
3486
|
+
"params": []
|
|
3487
|
+
},
|
|
3488
|
+
{
|
|
3489
|
+
"method": "GET",
|
|
3490
|
+
"path": "/api/ingest/config",
|
|
3491
|
+
"operationId": "getIngestConfig",
|
|
3492
|
+
"summary": "Get integration configuration for the project.",
|
|
3493
|
+
"tag": "Ingest",
|
|
3494
|
+
"params": []
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
"method": "GET",
|
|
3498
|
+
"path": "/api/ingest/reports",
|
|
3499
|
+
"operationId": "listIngestReports",
|
|
3500
|
+
"summary": "List feedback for the project (SDK view).",
|
|
3501
|
+
"tag": "Ingest",
|
|
3502
|
+
"params": []
|
|
3503
|
+
},
|
|
3504
|
+
{
|
|
3505
|
+
"method": "GET",
|
|
3506
|
+
"path": "/api/ingest/pins",
|
|
3507
|
+
"operationId": "listPins",
|
|
3508
|
+
"summary": "List pins by route and/or entity.",
|
|
3509
|
+
"tag": "Ingest",
|
|
3510
|
+
"params": []
|
|
3511
|
+
},
|
|
3512
|
+
{
|
|
3513
|
+
"method": "POST",
|
|
3514
|
+
"path": "/api/ingest/pins/archive",
|
|
3515
|
+
"operationId": "archivePin",
|
|
3516
|
+
"summary": "Archive a pin.",
|
|
3517
|
+
"tag": "Ingest",
|
|
3518
|
+
"params": []
|
|
3519
|
+
},
|
|
3520
|
+
{
|
|
3521
|
+
"method": "POST",
|
|
3522
|
+
"path": "/api/cli-auth/authorize",
|
|
3523
|
+
"operationId": "authorizeCli",
|
|
3524
|
+
"summary": "Authorize a CLI session by delivering a token.",
|
|
3525
|
+
"tag": "CLI Auth",
|
|
3526
|
+
"params": []
|
|
3527
|
+
},
|
|
3528
|
+
{
|
|
3529
|
+
"method": "GET",
|
|
3530
|
+
"path": "/api/organizations",
|
|
3531
|
+
"operationId": "listOrganizations",
|
|
3532
|
+
"summary": "List organizations for the current user.",
|
|
3533
|
+
"tag": "Organizations",
|
|
3534
|
+
"params": []
|
|
3535
|
+
},
|
|
3536
|
+
{
|
|
3537
|
+
"method": "POST",
|
|
3538
|
+
"path": "/api/organizations",
|
|
3539
|
+
"operationId": "createOrganization",
|
|
3540
|
+
"summary": "Create an organization.",
|
|
3541
|
+
"tag": "Organizations",
|
|
3542
|
+
"params": []
|
|
3543
|
+
},
|
|
3544
|
+
{
|
|
3545
|
+
"method": "POST",
|
|
3546
|
+
"path": "/api/organizations/switch",
|
|
3547
|
+
"operationId": "switchOrganization",
|
|
3548
|
+
"summary": "Switch the active organization.",
|
|
3549
|
+
"tag": "Organizations",
|
|
3550
|
+
"params": []
|
|
3551
|
+
},
|
|
3552
|
+
{
|
|
3553
|
+
"method": "GET",
|
|
3554
|
+
"path": "/api/organizations/{orgId}",
|
|
3555
|
+
"operationId": "getOrganization",
|
|
3556
|
+
"summary": "Get organization details.",
|
|
3557
|
+
"tag": "Organizations",
|
|
3558
|
+
"params": [
|
|
3559
|
+
"orgId"
|
|
3560
|
+
]
|
|
3561
|
+
},
|
|
3562
|
+
{
|
|
3563
|
+
"method": "PATCH",
|
|
3564
|
+
"path": "/api/organizations/{orgId}",
|
|
3565
|
+
"operationId": "updateOrganization",
|
|
3566
|
+
"summary": "Update an organization.",
|
|
3567
|
+
"tag": "Organizations",
|
|
3568
|
+
"params": [
|
|
3569
|
+
"orgId"
|
|
3570
|
+
]
|
|
3571
|
+
},
|
|
3572
|
+
{
|
|
3573
|
+
"method": "DELETE",
|
|
3574
|
+
"path": "/api/organizations/{orgId}",
|
|
3575
|
+
"operationId": "deleteOrganization",
|
|
3576
|
+
"summary": "Delete an organization.",
|
|
3577
|
+
"tag": "Organizations",
|
|
3578
|
+
"params": [
|
|
3579
|
+
"orgId"
|
|
3580
|
+
]
|
|
3581
|
+
},
|
|
3582
|
+
{
|
|
3583
|
+
"method": "GET",
|
|
3584
|
+
"path": "/api/organizations/{orgId}/members",
|
|
3585
|
+
"operationId": "listMembers",
|
|
3586
|
+
"summary": "List organization members.",
|
|
3587
|
+
"tag": "Members",
|
|
3588
|
+
"params": [
|
|
3589
|
+
"orgId"
|
|
3590
|
+
]
|
|
3591
|
+
},
|
|
3592
|
+
{
|
|
3593
|
+
"method": "DELETE",
|
|
3594
|
+
"path": "/api/organizations/{orgId}/members",
|
|
3595
|
+
"operationId": "removeMember",
|
|
3596
|
+
"summary": "Remove a member from the organization.",
|
|
3597
|
+
"tag": "Members",
|
|
3598
|
+
"params": [
|
|
3599
|
+
"orgId"
|
|
3600
|
+
]
|
|
3601
|
+
},
|
|
3602
|
+
{
|
|
3603
|
+
"method": "PATCH",
|
|
3604
|
+
"path": "/api/organizations/{orgId}/members/{memberId}",
|
|
3605
|
+
"operationId": "updateMember",
|
|
3606
|
+
"summary": "Update a member's role.",
|
|
3607
|
+
"tag": "Members",
|
|
3608
|
+
"params": [
|
|
3609
|
+
"orgId",
|
|
3610
|
+
"memberId"
|
|
3611
|
+
]
|
|
3612
|
+
},
|
|
3613
|
+
{
|
|
3614
|
+
"method": "GET",
|
|
3615
|
+
"path": "/api/organizations/{orgId}/invitations",
|
|
3616
|
+
"operationId": "listInvitations",
|
|
3617
|
+
"summary": "List organization invitations.",
|
|
3618
|
+
"tag": "Invitations",
|
|
3619
|
+
"params": [
|
|
3620
|
+
"orgId"
|
|
3621
|
+
]
|
|
3622
|
+
},
|
|
3623
|
+
{
|
|
3624
|
+
"method": "POST",
|
|
3625
|
+
"path": "/api/organizations/{orgId}/invitations",
|
|
3626
|
+
"operationId": "createInvitation",
|
|
3627
|
+
"summary": "Create an invitation link.",
|
|
3628
|
+
"tag": "Invitations",
|
|
3629
|
+
"params": [
|
|
3630
|
+
"orgId"
|
|
3631
|
+
]
|
|
3632
|
+
},
|
|
3633
|
+
{
|
|
3634
|
+
"method": "DELETE",
|
|
3635
|
+
"path": "/api/organizations/{orgId}/invitations/{inviteId}",
|
|
3636
|
+
"operationId": "deleteInvitation",
|
|
3637
|
+
"summary": "Revoke an invitation.",
|
|
3638
|
+
"tag": "Invitations",
|
|
3639
|
+
"params": [
|
|
3640
|
+
"orgId",
|
|
3641
|
+
"inviteId"
|
|
3642
|
+
]
|
|
3643
|
+
},
|
|
3644
|
+
{
|
|
3645
|
+
"method": "GET",
|
|
3646
|
+
"path": "/api/organizations/{orgId}/projects",
|
|
3647
|
+
"operationId": "listProjects",
|
|
3648
|
+
"summary": "List projects in an organization.",
|
|
3649
|
+
"tag": "Projects",
|
|
3650
|
+
"params": [
|
|
3651
|
+
"orgId"
|
|
3652
|
+
]
|
|
3653
|
+
},
|
|
3654
|
+
{
|
|
3655
|
+
"method": "POST",
|
|
3656
|
+
"path": "/api/organizations/{orgId}/projects",
|
|
3657
|
+
"operationId": "createProject",
|
|
3658
|
+
"summary": "Create a project.",
|
|
3659
|
+
"tag": "Projects",
|
|
3660
|
+
"params": [
|
|
3661
|
+
"orgId"
|
|
3662
|
+
]
|
|
3663
|
+
},
|
|
3664
|
+
{
|
|
3665
|
+
"method": "GET",
|
|
3666
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}",
|
|
3667
|
+
"operationId": "getProject",
|
|
3668
|
+
"summary": "Get project details.",
|
|
3669
|
+
"tag": "Projects",
|
|
3670
|
+
"params": [
|
|
3671
|
+
"orgId",
|
|
3672
|
+
"projectId"
|
|
3673
|
+
]
|
|
3674
|
+
},
|
|
3675
|
+
{
|
|
3676
|
+
"method": "PATCH",
|
|
3677
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}",
|
|
3678
|
+
"operationId": "updateProject",
|
|
3679
|
+
"summary": "Update a project.",
|
|
3680
|
+
"tag": "Projects",
|
|
3681
|
+
"params": [
|
|
3682
|
+
"orgId",
|
|
3683
|
+
"projectId"
|
|
3684
|
+
]
|
|
3685
|
+
},
|
|
3686
|
+
{
|
|
3687
|
+
"method": "DELETE",
|
|
3688
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}",
|
|
3689
|
+
"operationId": "deleteProject",
|
|
3690
|
+
"summary": "Delete a project.",
|
|
3691
|
+
"tag": "Projects",
|
|
3692
|
+
"params": [
|
|
3693
|
+
"orgId",
|
|
3694
|
+
"projectId"
|
|
3695
|
+
]
|
|
3696
|
+
},
|
|
3697
|
+
{
|
|
3698
|
+
"method": "GET",
|
|
3699
|
+
"path": "/api/projects/resolve",
|
|
3700
|
+
"operationId": "resolveProject",
|
|
3701
|
+
"summary": "Resolve an API key to its project and organization.",
|
|
3702
|
+
"tag": "Projects",
|
|
3703
|
+
"params": []
|
|
3704
|
+
},
|
|
3705
|
+
{
|
|
3706
|
+
"method": "GET",
|
|
3707
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/api-keys",
|
|
3708
|
+
"operationId": "listApiKeys",
|
|
3709
|
+
"summary": "List API keys for a project.",
|
|
3710
|
+
"tag": "API Keys",
|
|
3711
|
+
"params": [
|
|
3712
|
+
"orgId",
|
|
3713
|
+
"projectId"
|
|
3714
|
+
]
|
|
3715
|
+
},
|
|
3716
|
+
{
|
|
3717
|
+
"method": "POST",
|
|
3718
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/api-keys",
|
|
3719
|
+
"operationId": "createApiKey",
|
|
3720
|
+
"summary": "Create an API key.",
|
|
3721
|
+
"tag": "API Keys",
|
|
3722
|
+
"params": [
|
|
3723
|
+
"orgId",
|
|
3724
|
+
"projectId"
|
|
3725
|
+
]
|
|
3726
|
+
},
|
|
3727
|
+
{
|
|
3728
|
+
"method": "PATCH",
|
|
3729
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/api-keys/{keyId}",
|
|
3730
|
+
"operationId": "revokeApiKey",
|
|
3731
|
+
"summary": "Revoke an API key.",
|
|
3732
|
+
"tag": "API Keys",
|
|
3733
|
+
"params": [
|
|
3734
|
+
"orgId",
|
|
3735
|
+
"projectId",
|
|
3736
|
+
"keyId"
|
|
3737
|
+
]
|
|
3738
|
+
},
|
|
3739
|
+
{
|
|
3740
|
+
"method": "DELETE",
|
|
3741
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/api-keys/{keyId}",
|
|
3742
|
+
"operationId": "deleteApiKey",
|
|
3743
|
+
"summary": "Permanently delete an API key.",
|
|
3744
|
+
"tag": "API Keys",
|
|
3745
|
+
"params": [
|
|
3746
|
+
"orgId",
|
|
3747
|
+
"projectId",
|
|
3748
|
+
"keyId"
|
|
3749
|
+
]
|
|
3750
|
+
},
|
|
3751
|
+
{
|
|
3752
|
+
"method": "GET",
|
|
3753
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/reports",
|
|
3754
|
+
"operationId": "listProjectReports",
|
|
3755
|
+
"summary": "List feedback for a project.",
|
|
3756
|
+
"tag": "Reports",
|
|
3757
|
+
"params": [
|
|
3758
|
+
"orgId",
|
|
3759
|
+
"projectId"
|
|
3760
|
+
]
|
|
3761
|
+
},
|
|
3762
|
+
{
|
|
3763
|
+
"method": "GET",
|
|
3764
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/feedback/{reportId}",
|
|
3765
|
+
"operationId": "getReport",
|
|
3766
|
+
"summary": "Get feedback details.",
|
|
3767
|
+
"tag": "Reports",
|
|
3768
|
+
"params": [
|
|
3769
|
+
"orgId",
|
|
3770
|
+
"projectId",
|
|
3771
|
+
"reportId"
|
|
3772
|
+
]
|
|
3773
|
+
},
|
|
3774
|
+
{
|
|
3775
|
+
"method": "PATCH",
|
|
3776
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/feedback/{reportId}",
|
|
3777
|
+
"operationId": "updateReport",
|
|
3778
|
+
"summary": "Update feedback status, priority, or assignment.",
|
|
3779
|
+
"tag": "Reports",
|
|
3780
|
+
"params": [
|
|
3781
|
+
"orgId",
|
|
3782
|
+
"projectId",
|
|
3783
|
+
"reportId"
|
|
3784
|
+
]
|
|
3785
|
+
},
|
|
3786
|
+
{
|
|
3787
|
+
"method": "DELETE",
|
|
3788
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/feedback/{reportId}",
|
|
3789
|
+
"operationId": "deleteReport",
|
|
3790
|
+
"summary": "Delete a feedback record.",
|
|
3791
|
+
"tag": "Reports",
|
|
3792
|
+
"params": [
|
|
3793
|
+
"orgId",
|
|
3794
|
+
"projectId",
|
|
3795
|
+
"reportId"
|
|
3796
|
+
]
|
|
3797
|
+
},
|
|
3798
|
+
{
|
|
3799
|
+
"method": "POST",
|
|
3800
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/reports/archive",
|
|
3801
|
+
"operationId": "bulkArchiveReport",
|
|
3802
|
+
"summary": "Archive multiple feedback records.",
|
|
3803
|
+
"tag": "Reports",
|
|
3804
|
+
"params": [
|
|
3805
|
+
"orgId",
|
|
3806
|
+
"projectId"
|
|
3807
|
+
]
|
|
3808
|
+
},
|
|
3809
|
+
{
|
|
3810
|
+
"method": "GET",
|
|
3811
|
+
"path": "/api/organizations/{orgId}/integrations",
|
|
3812
|
+
"operationId": "listIntegrations",
|
|
3813
|
+
"summary": "List integrations for an organization.",
|
|
3814
|
+
"tag": "Integrations",
|
|
3815
|
+
"params": [
|
|
3816
|
+
"orgId"
|
|
3817
|
+
]
|
|
3818
|
+
},
|
|
3819
|
+
{
|
|
3820
|
+
"method": "POST",
|
|
3821
|
+
"path": "/api/organizations/{orgId}/integrations",
|
|
3822
|
+
"operationId": "createIntegration",
|
|
3823
|
+
"summary": "Create an integration.",
|
|
3824
|
+
"tag": "Integrations",
|
|
3825
|
+
"params": [
|
|
3826
|
+
"orgId"
|
|
3827
|
+
]
|
|
3828
|
+
},
|
|
3829
|
+
{
|
|
3830
|
+
"method": "GET",
|
|
3831
|
+
"path": "/api/organizations/{orgId}/integrations/{integrationId}",
|
|
3832
|
+
"operationId": "getIntegration",
|
|
3833
|
+
"summary": "Get integration details.",
|
|
3834
|
+
"tag": "Integrations",
|
|
3835
|
+
"params": [
|
|
3836
|
+
"orgId",
|
|
3837
|
+
"integrationId"
|
|
3838
|
+
]
|
|
3839
|
+
},
|
|
3840
|
+
{
|
|
3841
|
+
"method": "DELETE",
|
|
3842
|
+
"path": "/api/organizations/{orgId}/integrations/{integrationId}",
|
|
3843
|
+
"operationId": "deleteIntegration",
|
|
3844
|
+
"summary": "Delete an integration.",
|
|
3845
|
+
"tag": "Integrations",
|
|
3846
|
+
"params": [
|
|
3847
|
+
"orgId",
|
|
3848
|
+
"integrationId"
|
|
3849
|
+
]
|
|
3850
|
+
},
|
|
3851
|
+
{
|
|
3852
|
+
"method": "GET",
|
|
3853
|
+
"path": "/api/organizations/{orgId}/integrations/{integrationId}/targets",
|
|
3854
|
+
"operationId": "listIntegrationTargets",
|
|
3855
|
+
"summary": "List available targets (e.g. Jira projects/boards).",
|
|
3856
|
+
"tag": "Integrations",
|
|
3857
|
+
"params": [
|
|
3858
|
+
"orgId",
|
|
3859
|
+
"integrationId"
|
|
3860
|
+
]
|
|
3861
|
+
},
|
|
3862
|
+
{
|
|
3863
|
+
"method": "POST",
|
|
3864
|
+
"path": "/api/organizations/{orgId}/integrations/{integrationId}/test",
|
|
3865
|
+
"operationId": "testIntegration",
|
|
3866
|
+
"summary": "Test integration connectivity.",
|
|
3867
|
+
"tag": "Integrations",
|
|
3868
|
+
"params": [
|
|
3869
|
+
"orgId",
|
|
3870
|
+
"integrationId"
|
|
3871
|
+
]
|
|
3872
|
+
},
|
|
3873
|
+
{
|
|
3874
|
+
"method": "GET",
|
|
3875
|
+
"path": "/api/organizations/{orgId}/external-issues",
|
|
3876
|
+
"operationId": "listExternalIssues",
|
|
3877
|
+
"summary": "List external issues, optionally filtered by feedback.",
|
|
3878
|
+
"tag": "External Issues",
|
|
3879
|
+
"params": [
|
|
3880
|
+
"orgId"
|
|
3881
|
+
]
|
|
3882
|
+
},
|
|
3883
|
+
{
|
|
3884
|
+
"method": "GET",
|
|
3885
|
+
"path": "/api/organizations/{orgId}/issue-drafts",
|
|
3886
|
+
"operationId": "listIssueDrafts",
|
|
3887
|
+
"summary": "List issue drafts for a project.",
|
|
3888
|
+
"tag": "Issue Drafts",
|
|
3889
|
+
"params": [
|
|
3890
|
+
"orgId"
|
|
3891
|
+
]
|
|
3892
|
+
},
|
|
3893
|
+
{
|
|
3894
|
+
"method": "POST",
|
|
3895
|
+
"path": "/api/organizations/{orgId}/issue-drafts",
|
|
3896
|
+
"operationId": "createIssueDraft",
|
|
3897
|
+
"summary": "Manually create an issue draft.",
|
|
3898
|
+
"tag": "Issue Drafts",
|
|
3899
|
+
"params": [
|
|
3900
|
+
"orgId"
|
|
3901
|
+
]
|
|
3902
|
+
},
|
|
3903
|
+
{
|
|
3904
|
+
"method": "PATCH",
|
|
3905
|
+
"path": "/api/organizations/{orgId}/issue-drafts/{draftId}",
|
|
3906
|
+
"operationId": "updateIssueDraft",
|
|
3907
|
+
"summary": "Update an issue draft.",
|
|
3908
|
+
"tag": "Issue Drafts",
|
|
3909
|
+
"params": [
|
|
3910
|
+
"orgId",
|
|
3911
|
+
"draftId"
|
|
3912
|
+
]
|
|
3913
|
+
},
|
|
3914
|
+
{
|
|
3915
|
+
"method": "DELETE",
|
|
3916
|
+
"path": "/api/organizations/{orgId}/issue-drafts/{draftId}",
|
|
3917
|
+
"operationId": "deleteIssueDraft",
|
|
3918
|
+
"summary": "Delete an issue draft.",
|
|
3919
|
+
"tag": "Issue Drafts",
|
|
3920
|
+
"params": [
|
|
3921
|
+
"orgId",
|
|
3922
|
+
"draftId"
|
|
3923
|
+
]
|
|
3924
|
+
},
|
|
3925
|
+
{
|
|
3926
|
+
"method": "POST",
|
|
3927
|
+
"path": "/api/organizations/{orgId}/issue-drafts/{draftId}/submit",
|
|
3928
|
+
"operationId": "submitIssueDraft",
|
|
3929
|
+
"summary": "Submit a draft to the configured integration.",
|
|
3930
|
+
"tag": "Issue Drafts",
|
|
3931
|
+
"params": [
|
|
3932
|
+
"orgId",
|
|
3933
|
+
"draftId"
|
|
3934
|
+
]
|
|
3935
|
+
},
|
|
3936
|
+
{
|
|
3937
|
+
"method": "POST",
|
|
3938
|
+
"path": "/api/organizations/{orgId}/projects/{projectId}/triage",
|
|
3939
|
+
"operationId": "runTriage",
|
|
3940
|
+
"summary": "Run LLM-powered triage on untriaged feedback.",
|
|
3941
|
+
"tag": "Triage",
|
|
3942
|
+
"params": [
|
|
3943
|
+
"orgId",
|
|
3944
|
+
"projectId"
|
|
3945
|
+
]
|
|
3946
|
+
},
|
|
3947
|
+
{
|
|
3948
|
+
"method": "GET",
|
|
3949
|
+
"path": "/api/user/tokens",
|
|
3950
|
+
"operationId": "listUserTokens",
|
|
3951
|
+
"summary": "List personal access tokens.",
|
|
3952
|
+
"tag": "User Tokens",
|
|
3953
|
+
"params": []
|
|
3954
|
+
},
|
|
3955
|
+
{
|
|
3956
|
+
"method": "POST",
|
|
3957
|
+
"path": "/api/user/tokens",
|
|
3958
|
+
"operationId": "createUserToken",
|
|
3959
|
+
"summary": "Create a personal access token.",
|
|
3960
|
+
"tag": "User Tokens",
|
|
3961
|
+
"params": []
|
|
3962
|
+
},
|
|
3963
|
+
{
|
|
3964
|
+
"method": "DELETE",
|
|
3965
|
+
"path": "/api/user/tokens/{tokenId}",
|
|
3966
|
+
"operationId": "deleteUserToken",
|
|
3967
|
+
"summary": "Revoke and delete a personal access token.",
|
|
3968
|
+
"tag": "User Tokens",
|
|
3969
|
+
"params": [
|
|
3970
|
+
"tokenId"
|
|
3971
|
+
]
|
|
3972
|
+
}
|
|
3973
|
+
];
|
|
3974
|
+
|
|
3975
|
+
// src/scanner/cli/api.ts
|
|
3976
|
+
var METHODS = /* @__PURE__ */ new Set([
|
|
3977
|
+
"GET",
|
|
3978
|
+
"POST",
|
|
3979
|
+
"PUT",
|
|
3980
|
+
"PATCH",
|
|
3981
|
+
"DELETE",
|
|
3982
|
+
"HEAD",
|
|
3983
|
+
"OPTIONS"
|
|
3984
|
+
]);
|
|
3985
|
+
var HELP = `uidex api \u2014 call the uidex API
|
|
3986
|
+
|
|
3987
|
+
Usage:
|
|
3988
|
+
uidex api <METHOD> <PATH> [flags] Make an API request
|
|
3989
|
+
uidex api --list [--tag <tag>] List available routes
|
|
3990
|
+
uidex api login Authenticate via browser
|
|
3991
|
+
uidex api login --token <tok> Store an auth token directly
|
|
3992
|
+
uidex api status Check auth state
|
|
3993
|
+
|
|
3994
|
+
Flags:
|
|
3995
|
+
--body <json> Request body (JSON string)
|
|
3996
|
+
--query <params> Query string (key=val&key2=val2)
|
|
3997
|
+
--base-url <url> Override the API base URL
|
|
3998
|
+
--token <tok> Use a specific token (instead of stored)
|
|
3999
|
+
--raw Disable JSON pretty-printing
|
|
4000
|
+
--no-color Disable colored output
|
|
4001
|
+
--list List available API routes
|
|
4002
|
+
--tag <tag> Filter --list by tag
|
|
4003
|
+
--json Emit --list as JSON
|
|
4004
|
+
`;
|
|
4005
|
+
async function runApiCommand(opts) {
|
|
4006
|
+
const { positional, flags } = parseArgs(opts.argv);
|
|
4007
|
+
const stdout = [];
|
|
4008
|
+
const stderr = [];
|
|
4009
|
+
const color = opts.color ?? (flags["no-color"] ? false : process.stdout.isTTY ?? false);
|
|
4010
|
+
const ctx = { flags, opts, color, stdout, stderr };
|
|
4011
|
+
const sub = positional[0];
|
|
4012
|
+
if (!sub && !flags.list) {
|
|
4013
|
+
stdout.push(HELP);
|
|
4014
|
+
return result(0, stdout, stderr);
|
|
4015
|
+
}
|
|
4016
|
+
if (flags.list) return listRoutes(ctx);
|
|
4017
|
+
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
4018
|
+
stdout.push(HELP);
|
|
4019
|
+
return result(0, stdout, stderr);
|
|
4020
|
+
}
|
|
4021
|
+
if (sub === "login") return runLogin(ctx);
|
|
4022
|
+
if (sub === "status") return runStatus(ctx);
|
|
4023
|
+
const method = sub?.toUpperCase();
|
|
4024
|
+
const path10 = positional[1];
|
|
4025
|
+
if (!method || !METHODS.has(method)) {
|
|
4026
|
+
stderr.push(`Unknown command or method: ${sub}`);
|
|
4027
|
+
stderr.push(HELP);
|
|
4028
|
+
return result(1, stdout, stderr);
|
|
4029
|
+
}
|
|
4030
|
+
if (!path10) {
|
|
4031
|
+
stderr.push("Missing path. Usage: uidex api GET /api/organizations");
|
|
4032
|
+
return result(1, stdout, stderr);
|
|
4033
|
+
}
|
|
4034
|
+
return runRequest(ctx, method, path10);
|
|
4035
|
+
}
|
|
4036
|
+
function listRoutes(ctx) {
|
|
4037
|
+
const { flags, color, stdout, stderr } = ctx;
|
|
4038
|
+
let routes = API_ROUTES;
|
|
4039
|
+
const tagFilter = flags.tag;
|
|
4040
|
+
if (typeof tagFilter === "string") {
|
|
4041
|
+
routes = routes.filter(
|
|
4042
|
+
(r) => r.tag.toLowerCase() === tagFilter.toLowerCase()
|
|
4043
|
+
);
|
|
4044
|
+
}
|
|
4045
|
+
if (flags.json) {
|
|
4046
|
+
stdout.push(JSON.stringify(routes, null, 2));
|
|
4047
|
+
return result(0, stdout, stderr);
|
|
4048
|
+
}
|
|
4049
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4050
|
+
for (const r of routes) {
|
|
4051
|
+
const tag = r.tag || "Other";
|
|
4052
|
+
let list = grouped.get(tag);
|
|
4053
|
+
if (!list) {
|
|
4054
|
+
list = [];
|
|
4055
|
+
grouped.set(tag, list);
|
|
4056
|
+
}
|
|
4057
|
+
list.push(r);
|
|
4058
|
+
}
|
|
4059
|
+
const methodPad = 7;
|
|
4060
|
+
for (const [tag, tagRoutes] of grouped) {
|
|
4061
|
+
stdout.push("");
|
|
4062
|
+
stdout.push(boldText(tag, color));
|
|
4063
|
+
for (const r of tagRoutes) {
|
|
4064
|
+
const m = r.method.padEnd(methodPad);
|
|
4065
|
+
const desc = r.summary ? dimText(` ${r.operationId} \u2014 ${r.summary}`, color) : dimText(` ${r.operationId}`, color);
|
|
4066
|
+
stdout.push(` ${m} ${r.path}${desc}`);
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
stdout.push("");
|
|
4070
|
+
return result(0, stdout, stderr);
|
|
4071
|
+
}
|
|
4072
|
+
async function runLogin(ctx) {
|
|
4073
|
+
const { flags, opts, color, stdout, stderr } = ctx;
|
|
4074
|
+
const token = flags.token;
|
|
4075
|
+
if (typeof token === "string" && token.length > 0) {
|
|
4076
|
+
const storage = resolveTokenStorage(opts);
|
|
4077
|
+
storage.set(token);
|
|
4078
|
+
stdout.push("Token stored.");
|
|
4079
|
+
return result(0, stdout, stderr);
|
|
4080
|
+
}
|
|
4081
|
+
return runBrowserLogin(ctx);
|
|
4082
|
+
}
|
|
4083
|
+
async function runBrowserLogin(ctx) {
|
|
4084
|
+
const { flags, opts, color, stdout, stderr } = ctx;
|
|
4085
|
+
const http = await import("http");
|
|
4086
|
+
const crypto = await import("crypto");
|
|
4087
|
+
const { exec } = await import("child_process");
|
|
4088
|
+
const baseUrl = resolveBaseUrl(flags, opts);
|
|
4089
|
+
const code = crypto.randomBytes(3).toString("hex").toUpperCase();
|
|
4090
|
+
return new Promise((resolve7) => {
|
|
4091
|
+
let settled = false;
|
|
4092
|
+
const settle = (exitCode) => {
|
|
4093
|
+
if (settled) return;
|
|
4094
|
+
settled = true;
|
|
4095
|
+
server.close();
|
|
4096
|
+
clearTimeout(timeout);
|
|
4097
|
+
resolve7(result(exitCode, stdout, stderr));
|
|
4098
|
+
};
|
|
4099
|
+
const timeout = setTimeout(() => {
|
|
4100
|
+
stderr.push("Timed out waiting for browser authorization.");
|
|
4101
|
+
settle(1);
|
|
4102
|
+
}, 12e4);
|
|
4103
|
+
const server = http.createServer((req, res) => {
|
|
4104
|
+
if (!req.url?.startsWith("/callback")) {
|
|
4105
|
+
res.writeHead(404);
|
|
4106
|
+
res.end();
|
|
4107
|
+
return;
|
|
4108
|
+
}
|
|
4109
|
+
const url = new URL(req.url, `http://127.0.0.1`);
|
|
4110
|
+
const receivedToken = url.searchParams.get("token");
|
|
4111
|
+
const receivedCode = url.searchParams.get("code");
|
|
4112
|
+
if (receivedCode !== code) {
|
|
4113
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
4114
|
+
res.end(JSON.stringify({ error: "Code mismatch" }));
|
|
4115
|
+
stderr.push("Received callback with mismatched code. Aborting.");
|
|
4116
|
+
settle(1);
|
|
4117
|
+
return;
|
|
4118
|
+
}
|
|
4119
|
+
if (!receivedToken) {
|
|
4120
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
4121
|
+
res.end(JSON.stringify({ error: "Missing token" }));
|
|
4122
|
+
stderr.push("Received callback without token. Aborting.");
|
|
4123
|
+
settle(1);
|
|
4124
|
+
return;
|
|
4125
|
+
}
|
|
4126
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4127
|
+
res.end(JSON.stringify({ ok: true }));
|
|
4128
|
+
const storage = resolveTokenStorage(opts);
|
|
4129
|
+
storage.set(receivedToken);
|
|
4130
|
+
stdout.push(dimText("Authenticated successfully.", color));
|
|
4131
|
+
settle(0);
|
|
4132
|
+
});
|
|
4133
|
+
server.listen(0, "127.0.0.1", () => {
|
|
4134
|
+
const addr = server.address();
|
|
4135
|
+
if (!addr || typeof addr === "string") {
|
|
4136
|
+
stderr.push("Failed to start local server.");
|
|
4137
|
+
settle(1);
|
|
4138
|
+
return;
|
|
4139
|
+
}
|
|
4140
|
+
const port = addr.port;
|
|
4141
|
+
const authUrl = `${baseUrl}/cli-auth?code=${encodeURIComponent(code)}&port=${port}`;
|
|
4142
|
+
stdout.push(dimText("Opening browser to authorize...", color));
|
|
4143
|
+
stdout.push("");
|
|
4144
|
+
stdout.push(` Confirmation code: ${boldText(code, color)}`);
|
|
4145
|
+
stdout.push("");
|
|
4146
|
+
stdout.push(
|
|
4147
|
+
dimText("If the browser doesn't open, visit this URL manually:", color)
|
|
4148
|
+
);
|
|
4149
|
+
stdout.push(` ${authUrl}`);
|
|
4150
|
+
stdout.push("");
|
|
4151
|
+
process.stdout.write(result(0, stdout, stderr).stdout);
|
|
4152
|
+
stdout.length = 0;
|
|
4153
|
+
openBrowser(authUrl);
|
|
4154
|
+
});
|
|
4155
|
+
server.on("error", (err2) => {
|
|
4156
|
+
stderr.push(`Failed to start local server: ${err2.message}`);
|
|
4157
|
+
settle(1);
|
|
4158
|
+
});
|
|
4159
|
+
});
|
|
4160
|
+
}
|
|
4161
|
+
function openBrowser(url) {
|
|
4162
|
+
const { exec } = require("child_process");
|
|
4163
|
+
const platform = process.platform;
|
|
4164
|
+
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
4165
|
+
exec(`${cmd} ${JSON.stringify(url)}`);
|
|
4166
|
+
}
|
|
4167
|
+
function runStatus(ctx) {
|
|
4168
|
+
const { flags, opts, stdout, stderr } = ctx;
|
|
4169
|
+
const storage = resolveTokenStorage(opts);
|
|
4170
|
+
const baseUrl = resolveBaseUrl(flags, opts);
|
|
4171
|
+
const token = storage.get();
|
|
4172
|
+
stdout.push(`baseUrl: ${baseUrl}`);
|
|
4173
|
+
stdout.push(`auth: ${token ? "authenticated" : "not authenticated"}`);
|
|
4174
|
+
return result(token ? 0 : 1, stdout, stderr);
|
|
4175
|
+
}
|
|
4176
|
+
async function runRequest(ctx, method, path10) {
|
|
4177
|
+
const { flags, opts, color, stdout, stderr } = ctx;
|
|
4178
|
+
const tokenFromFlag = flags.token;
|
|
4179
|
+
const token = typeof tokenFromFlag === "string" ? tokenFromFlag : resolveTokenStorage(opts).get();
|
|
4180
|
+
if (!token) {
|
|
4181
|
+
stderr.push("Not authenticated. Run: uidex api login --token <value>");
|
|
4182
|
+
return result(1, stdout, stderr);
|
|
4183
|
+
}
|
|
4184
|
+
const baseUrl = resolveBaseUrl(flags, opts);
|
|
4185
|
+
const http = opts.http ?? createHttpClient({ baseUrl, token: () => token });
|
|
4186
|
+
const bodyStr = typeof flags.body === "string" ? flags.body : void 0;
|
|
4187
|
+
if (bodyStr !== void 0) {
|
|
4188
|
+
try {
|
|
4189
|
+
JSON.parse(bodyStr);
|
|
4190
|
+
} catch {
|
|
4191
|
+
stderr.push("Invalid JSON in --body");
|
|
4192
|
+
return result(1, stdout, stderr);
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
const res = await http.request(path10, {
|
|
4196
|
+
method,
|
|
4197
|
+
body: bodyStr,
|
|
4198
|
+
query: typeof flags.query === "string" ? flags.query : void 0
|
|
4199
|
+
});
|
|
4200
|
+
const isSuccess = res.status >= 200 && res.status < 300;
|
|
4201
|
+
if (!isSuccess) {
|
|
4202
|
+
stderr.push(formatStatus(res.status, color));
|
|
4203
|
+
}
|
|
4204
|
+
if (res.body !== void 0 && res.body !== null && res.body !== "") {
|
|
4205
|
+
if (flags.raw) {
|
|
4206
|
+
stdout.push(res.raw);
|
|
4207
|
+
} else {
|
|
4208
|
+
stdout.push(highlightJson(res.body, color));
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4211
|
+
return result(isSuccess ? 0 : 1, stdout, stderr);
|
|
4212
|
+
}
|
|
4213
|
+
function resolveTokenStorage(opts) {
|
|
4214
|
+
if (opts.tokenStorage) return opts.tokenStorage;
|
|
4215
|
+
const envToken = opts.env?.UIDEX_TOKEN;
|
|
4216
|
+
if (envToken) return createMemoryTokenStorage(envToken);
|
|
4217
|
+
return createFileTokenStorage({ path: defaultTokenPath() });
|
|
4218
|
+
}
|
|
4219
|
+
function resolveBaseUrl(flags, opts) {
|
|
4220
|
+
return (typeof flags["base-url"] === "string" ? flags["base-url"] : void 0) ?? opts.baseUrl ?? opts.env?.UIDEX_API_URL ?? "https://app.uidex.dev";
|
|
4221
|
+
}
|
|
4222
|
+
function result(exitCode, stdout, stderr) {
|
|
4223
|
+
return {
|
|
4224
|
+
exitCode,
|
|
4225
|
+
stdout: stdout.join("\n") + (stdout.length ? "\n" : ""),
|
|
4226
|
+
stderr: stderr.join("\n") + (stderr.length ? "\n" : "")
|
|
4227
|
+
};
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
// src/scanner/cli/cli.ts
|
|
4231
|
+
var argv = process.argv.slice(2);
|
|
4232
|
+
var command = argv[0];
|
|
4233
|
+
var resultP = command === "api" ? runApiCommand({ argv: argv.slice(1) }) : run({ argv });
|
|
4234
|
+
resultP.then(
|
|
4235
|
+
(result2) => {
|
|
4236
|
+
if (result2.stdout) process.stdout.write(result2.stdout);
|
|
4237
|
+
if (result2.stderr) process.stderr.write(result2.stderr);
|
|
4238
|
+
process.exit(result2.exitCode);
|
|
3235
4239
|
},
|
|
3236
4240
|
(error) => {
|
|
3237
4241
|
process.stderr.write(
|