@forge-ts/core 0.8.0 → 0.14.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/index.js CHANGED
@@ -1,7 +1,168 @@
1
+ // src/audit.ts
2
+ import { appendFileSync, existsSync, readFileSync } from "fs";
3
+ import { userInfo } from "os";
4
+ import { join } from "path";
5
+ var AUDIT_FILENAME = ".forge-audit.jsonl";
6
+ function getCurrentUser() {
7
+ try {
8
+ return userInfo().username;
9
+ } catch {
10
+ return "unknown";
11
+ }
12
+ }
13
+ function appendAuditEvent(rootDir, event) {
14
+ const filePath = join(rootDir, AUDIT_FILENAME);
15
+ appendFileSync(filePath, `${JSON.stringify(event)}
16
+ `, "utf-8");
17
+ }
18
+ function readAuditLog(rootDir, options) {
19
+ const filePath = join(rootDir, AUDIT_FILENAME);
20
+ if (!existsSync(filePath)) {
21
+ return [];
22
+ }
23
+ const raw = readFileSync(filePath, "utf-8");
24
+ const lines = raw.split("\n").filter((line) => line.trim().length > 0);
25
+ let events = lines.map((line) => JSON.parse(line));
26
+ if (options?.eventType) {
27
+ events = events.filter((e) => e.event === options.eventType);
28
+ }
29
+ events.reverse();
30
+ if (options?.limit !== void 0 && options.limit >= 0) {
31
+ events = events.slice(0, options.limit);
32
+ }
33
+ return events;
34
+ }
35
+ function formatAuditEvent(event) {
36
+ const reasonPart = event.reason ? ` \u2014 ${event.reason}` : "";
37
+ const detailKeys = Object.keys(event.details);
38
+ const detailPart = detailKeys.length > 0 ? ` ${JSON.stringify(event.details)}` : "";
39
+ return `[${event.timestamp}] ${event.event} by ${event.user}${reasonPart}${detailPart}`;
40
+ }
41
+
42
+ // src/bypass.ts
43
+ import { randomUUID } from "crypto";
44
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
45
+ import { userInfo as userInfo2 } from "os";
46
+ import { join as join2 } from "path";
47
+ var BYPASS_FILENAME = ".forge-bypass.json";
48
+ var DEFAULT_BYPASS_CONFIG = {
49
+ dailyBudget: 3,
50
+ durationHours: 24
51
+ };
52
+ function getCurrentUser2() {
53
+ try {
54
+ return userInfo2().username;
55
+ } catch {
56
+ return "unknown";
57
+ }
58
+ }
59
+ function readBypassFile(rootDir) {
60
+ const filePath = join2(rootDir, BYPASS_FILENAME);
61
+ if (!existsSync2(filePath)) {
62
+ return [];
63
+ }
64
+ try {
65
+ const raw = readFileSync2(filePath, "utf-8");
66
+ return JSON.parse(raw);
67
+ } catch {
68
+ return [];
69
+ }
70
+ }
71
+ function writeBypassFile(rootDir, records) {
72
+ const filePath = join2(rootDir, BYPASS_FILENAME);
73
+ writeFileSync(filePath, `${JSON.stringify(records, null, 2)}
74
+ `, "utf-8");
75
+ }
76
+ function resolveConfig(config) {
77
+ return {
78
+ ...DEFAULT_BYPASS_CONFIG,
79
+ ...config
80
+ };
81
+ }
82
+ function startOfToday() {
83
+ const now = /* @__PURE__ */ new Date();
84
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
85
+ }
86
+ function createBypass(rootDir, reason, rule, config) {
87
+ const resolved = resolveConfig(config);
88
+ const remaining = getRemainingBudget(rootDir, config);
89
+ if (remaining <= 0) {
90
+ throw new Error(
91
+ `Bypass budget exhausted: ${resolved.dailyBudget}/${resolved.dailyBudget} bypasses used today. Wait until tomorrow or increase bypass.dailyBudget in your forge-ts config.`
92
+ );
93
+ }
94
+ const now = /* @__PURE__ */ new Date();
95
+ const expiresAt = new Date(now.getTime() + resolved.durationHours * 60 * 60 * 1e3);
96
+ const record = {
97
+ id: randomUUID(),
98
+ createdAt: now.toISOString(),
99
+ expiresAt: expiresAt.toISOString(),
100
+ reason,
101
+ rule: rule ?? "all",
102
+ user: getCurrentUser2()
103
+ };
104
+ const records = readBypassFile(rootDir);
105
+ records.push(record);
106
+ writeBypassFile(rootDir, records);
107
+ appendAuditEvent(rootDir, {
108
+ timestamp: record.createdAt,
109
+ event: "bypass.create",
110
+ user: record.user,
111
+ reason,
112
+ details: {
113
+ bypassId: record.id,
114
+ rule: record.rule,
115
+ expiresAt: record.expiresAt,
116
+ durationHours: resolved.durationHours
117
+ }
118
+ });
119
+ return record;
120
+ }
121
+ function getActiveBypasses(rootDir) {
122
+ const records = readBypassFile(rootDir);
123
+ const now = /* @__PURE__ */ new Date();
124
+ return records.filter((r) => new Date(r.expiresAt) > now);
125
+ }
126
+ function isRuleBypassed(rootDir, ruleCode) {
127
+ const active = getActiveBypasses(rootDir);
128
+ return active.some((r) => r.rule === ruleCode || r.rule === "all");
129
+ }
130
+ function getRemainingBudget(rootDir, config) {
131
+ const resolved = resolveConfig(config);
132
+ const records = readBypassFile(rootDir);
133
+ const todayStart = startOfToday();
134
+ const todayCount = records.filter((r) => new Date(r.createdAt) >= todayStart).length;
135
+ return Math.max(0, resolved.dailyBudget - todayCount);
136
+ }
137
+ function expireOldBypasses(rootDir) {
138
+ const records = readBypassFile(rootDir);
139
+ const now = /* @__PURE__ */ new Date();
140
+ const active = records.filter((r) => new Date(r.expiresAt) > now);
141
+ const expired = records.filter((r) => new Date(r.expiresAt) <= now);
142
+ if (expired.length === 0) {
143
+ return 0;
144
+ }
145
+ writeBypassFile(rootDir, active);
146
+ for (const record of expired) {
147
+ appendAuditEvent(rootDir, {
148
+ timestamp: now.toISOString(),
149
+ event: "bypass.expire",
150
+ user: record.user,
151
+ details: {
152
+ bypassId: record.id,
153
+ rule: record.rule,
154
+ createdAt: record.createdAt,
155
+ expiresAt: record.expiresAt
156
+ }
157
+ });
158
+ }
159
+ return expired.length;
160
+ }
161
+
1
162
  // src/config.ts
2
- import { existsSync } from "fs";
163
+ import { existsSync as existsSync3 } from "fs";
3
164
  import { readFile } from "fs/promises";
4
- import { join, resolve } from "path";
165
+ import { join as join3, resolve } from "path";
5
166
  import { pathToFileURL } from "url";
6
167
 
7
168
  // src/types.ts
@@ -17,8 +178,8 @@ var Visibility = /* @__PURE__ */ ((Visibility2) => {
17
178
  function defaultConfig(rootDir) {
18
179
  return {
19
180
  rootDir,
20
- tsconfig: join(rootDir, "tsconfig.json"),
21
- outDir: join(rootDir, "docs"),
181
+ tsconfig: join3(rootDir, "tsconfig.json"),
182
+ outDir: join3(rootDir, "docs"),
22
183
  enforce: {
23
184
  enabled: true,
24
185
  minVisibility: "public" /* Public */,
@@ -30,17 +191,30 @@ function defaultConfig(rootDir) {
30
191
  "require-example": "error",
31
192
  "require-package-doc": "warn",
32
193
  "require-class-member-doc": "error",
33
- "require-interface-member-doc": "error"
194
+ "require-interface-member-doc": "error",
195
+ "require-tsdoc-syntax": "warn",
196
+ "require-remarks": "error",
197
+ "require-default-value": "warn",
198
+ "require-type-param": "error",
199
+ "require-see": "warn",
200
+ "require-release-tag": "error",
201
+ "require-fresh-guides": "warn",
202
+ "require-guide-coverage": "warn",
203
+ "require-internal-boundary": "error",
204
+ "require-route-response": "warn",
205
+ "require-inheritdoc-source": "warn",
206
+ "require-migration-path": "warn",
207
+ "require-since": "warn"
34
208
  }
35
209
  },
36
210
  doctest: {
37
211
  enabled: true,
38
- cacheDir: join(rootDir, ".cache", "doctest")
212
+ cacheDir: join3(rootDir, ".cache", "doctest")
39
213
  },
40
214
  api: {
41
215
  enabled: false,
42
216
  openapi: false,
43
- openapiPath: join(rootDir, "docs", "openapi.json")
217
+ openapiPath: join3(rootDir, "docs", "openapi.json")
44
218
  },
45
219
  gen: {
46
220
  enabled: true,
@@ -49,6 +223,39 @@ function defaultConfig(rootDir) {
49
223
  readmeSync: false
50
224
  },
51
225
  skill: {},
226
+ bypass: {
227
+ dailyBudget: 3,
228
+ durationHours: 24
229
+ },
230
+ tsdoc: {
231
+ writeConfig: true,
232
+ customTags: [],
233
+ enforce: {
234
+ core: "error",
235
+ extended: "warn",
236
+ discretionary: "off"
237
+ }
238
+ },
239
+ guides: {
240
+ enabled: true,
241
+ autoDiscover: true,
242
+ custom: []
243
+ },
244
+ guards: {
245
+ tsconfig: {
246
+ enabled: true,
247
+ requiredFlags: ["strict", "strictNullChecks", "noImplicitAny"]
248
+ },
249
+ biome: {
250
+ enabled: false,
251
+ lockedRules: []
252
+ },
253
+ packageJson: {
254
+ enabled: true,
255
+ minNodeVersion: "22.0.0",
256
+ requiredFields: ["type", "engines"]
257
+ }
258
+ },
52
259
  project: {}
53
260
  };
54
261
  }
@@ -61,6 +268,10 @@ var KNOWN_TOP_KEYS = /* @__PURE__ */ new Set([
61
268
  "api",
62
269
  "gen",
63
270
  "skill",
271
+ "bypass",
272
+ "tsdoc",
273
+ "guides",
274
+ "guards",
64
275
  "project"
65
276
  ]);
66
277
  var KNOWN_RULE_KEYS = /* @__PURE__ */ new Set([
@@ -70,8 +281,35 @@ var KNOWN_RULE_KEYS = /* @__PURE__ */ new Set([
70
281
  "require-example",
71
282
  "require-package-doc",
72
283
  "require-class-member-doc",
73
- "require-interface-member-doc"
284
+ "require-interface-member-doc",
285
+ "require-tsdoc-syntax",
286
+ "require-remarks",
287
+ "require-default-value",
288
+ "require-type-param",
289
+ "require-see",
290
+ "require-release-tag",
291
+ "require-fresh-guides",
292
+ "require-guide-coverage",
293
+ "require-internal-boundary",
294
+ "require-route-response",
295
+ "require-inheritdoc-source",
296
+ "require-migration-path",
297
+ "require-since"
74
298
  ]);
299
+ var KNOWN_TSDOC_KEYS = /* @__PURE__ */ new Set(["writeConfig", "customTags", "enforce"]);
300
+ var KNOWN_TSDOC_ENFORCE_KEYS = /* @__PURE__ */ new Set(["core", "extended", "discretionary"]);
301
+ var KNOWN_GUIDES_KEYS = /* @__PURE__ */ new Set(["enabled", "autoDiscover", "custom"]);
302
+ var KNOWN_GUARDS_KEYS = /* @__PURE__ */ new Set(["tsconfig", "biome", "packageJson"]);
303
+ var KNOWN_GUARDS_TSCONFIG_KEYS = /* @__PURE__ */ new Set(["enabled", "requiredFlags"]);
304
+ var KNOWN_GUARDS_BIOME_KEYS = /* @__PURE__ */ new Set(["enabled", "lockedRules"]);
305
+ var KNOWN_GUARDS_PACKAGE_JSON_KEYS = /* @__PURE__ */ new Set(["enabled", "minNodeVersion", "requiredFields"]);
306
+ function validateKnownKeys(obj, knownKeys, section, warnings) {
307
+ for (const key of Object.keys(obj)) {
308
+ if (!knownKeys.has(key)) {
309
+ warnings.push(`Unknown key "${key}" in ${section} \u2014 ignored.`);
310
+ }
311
+ }
312
+ }
75
313
  function collectUnknownKeyWarnings(partial) {
76
314
  const warnings = [];
77
315
  for (const key of Object.keys(partial)) {
@@ -88,6 +326,62 @@ function collectUnknownKeyWarnings(partial) {
88
326
  }
89
327
  }
90
328
  }
329
+ if (partial.tsdoc) {
330
+ validateKnownKeys(
331
+ partial.tsdoc,
332
+ KNOWN_TSDOC_KEYS,
333
+ "tsdoc",
334
+ warnings
335
+ );
336
+ if (partial.tsdoc.enforce) {
337
+ validateKnownKeys(
338
+ partial.tsdoc.enforce,
339
+ KNOWN_TSDOC_ENFORCE_KEYS,
340
+ "tsdoc.enforce",
341
+ warnings
342
+ );
343
+ }
344
+ }
345
+ if (partial.guides) {
346
+ validateKnownKeys(
347
+ partial.guides,
348
+ KNOWN_GUIDES_KEYS,
349
+ "guides",
350
+ warnings
351
+ );
352
+ }
353
+ if (partial.guards) {
354
+ validateKnownKeys(
355
+ partial.guards,
356
+ KNOWN_GUARDS_KEYS,
357
+ "guards",
358
+ warnings
359
+ );
360
+ if (partial.guards.tsconfig) {
361
+ validateKnownKeys(
362
+ partial.guards.tsconfig,
363
+ KNOWN_GUARDS_TSCONFIG_KEYS,
364
+ "guards.tsconfig",
365
+ warnings
366
+ );
367
+ }
368
+ if (partial.guards.biome) {
369
+ validateKnownKeys(
370
+ partial.guards.biome,
371
+ KNOWN_GUARDS_BIOME_KEYS,
372
+ "guards.biome",
373
+ warnings
374
+ );
375
+ }
376
+ if (partial.guards.packageJson) {
377
+ validateKnownKeys(
378
+ partial.guards.packageJson,
379
+ KNOWN_GUARDS_PACKAGE_JSON_KEYS,
380
+ "guards.packageJson",
381
+ warnings
382
+ );
383
+ }
384
+ }
91
385
  for (const w of warnings) {
92
386
  console.error(`[forge-ts] warning: ${w}`);
93
387
  }
@@ -108,6 +402,20 @@ function mergeWithDefaults(rootDir, partial) {
108
402
  api: { ...defaults.api, ...partial.api },
109
403
  gen: { ...defaults.gen, ...partial.gen },
110
404
  skill: { ...defaults.skill, ...partial.skill },
405
+ bypass: { ...defaults.bypass, ...partial.bypass },
406
+ guides: { ...defaults.guides, ...partial.guides },
407
+ tsdoc: {
408
+ ...defaults.tsdoc,
409
+ ...partial.tsdoc,
410
+ enforce: { ...defaults.tsdoc.enforce, ...partial.tsdoc?.enforce }
411
+ },
412
+ guards: {
413
+ ...defaults.guards,
414
+ ...partial.guards,
415
+ tsconfig: { ...defaults.guards.tsconfig, ...partial.guards?.tsconfig },
416
+ biome: { ...defaults.guards.biome, ...partial.guards?.biome },
417
+ packageJson: { ...defaults.guards.packageJson, ...partial.guards?.packageJson }
418
+ },
111
419
  project: { ...defaults.project, ...partial.project }
112
420
  };
113
421
  if (warnings.length > 0) {
@@ -150,11 +458,11 @@ async function loadPackageJsonConfig(pkgPath) {
150
458
  async function loadConfig(rootDir) {
151
459
  const root = resolve(rootDir ?? process.cwd());
152
460
  let config;
153
- const candidates = [join(root, "forge-ts.config.ts"), join(root, "forge-ts.config.js")];
461
+ const candidates = [join3(root, "forge-ts.config.ts"), join3(root, "forge-ts.config.js")];
154
462
  let found = false;
155
463
  const loadWarnings = [];
156
464
  for (const candidate of candidates) {
157
- if (existsSync(candidate)) {
465
+ if (existsSync3(candidate)) {
158
466
  const partial = await loadModuleConfig(candidate);
159
467
  if (partial) {
160
468
  config = mergeWithDefaults(root, partial);
@@ -167,8 +475,8 @@ async function loadConfig(rootDir) {
167
475
  }
168
476
  }
169
477
  if (!found) {
170
- const pkgPath2 = join(root, "package.json");
171
- if (existsSync(pkgPath2)) {
478
+ const pkgPath2 = join3(root, "package.json");
479
+ if (existsSync3(pkgPath2)) {
172
480
  const partial = await loadPackageJsonConfig(pkgPath2);
173
481
  if (partial) {
174
482
  config = mergeWithDefaults(root, partial);
@@ -181,8 +489,8 @@ async function loadConfig(rootDir) {
181
489
  } else {
182
490
  config = config;
183
491
  }
184
- const pkgPath = join(root, "package.json");
185
- if (existsSync(pkgPath)) {
492
+ const pkgPath = join3(root, "package.json");
493
+ if (existsSync3(pkgPath)) {
186
494
  try {
187
495
  const raw = await readFile(pkgPath, "utf8");
188
496
  const pkg = JSON.parse(raw);
@@ -224,6 +532,135 @@ async function loadConfig(rootDir) {
224
532
  return config;
225
533
  }
226
534
 
535
+ // src/lock.ts
536
+ import { existsSync as existsSync4, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
537
+ import { join as join4 } from "path";
538
+ var LOCK_FILE_NAME = ".forge-lock.json";
539
+ function readLockFile(rootDir) {
540
+ const lockPath = join4(rootDir, LOCK_FILE_NAME);
541
+ if (!existsSync4(lockPath)) {
542
+ return null;
543
+ }
544
+ try {
545
+ const raw = readFileSync3(lockPath, "utf8");
546
+ return JSON.parse(raw);
547
+ } catch {
548
+ return null;
549
+ }
550
+ }
551
+ function writeLockFile(rootDir, manifest) {
552
+ const lockPath = join4(rootDir, LOCK_FILE_NAME);
553
+ writeFileSync2(lockPath, `${JSON.stringify(manifest, null, 2)}
554
+ `, "utf8");
555
+ }
556
+ function removeLockFile(rootDir) {
557
+ const lockPath = join4(rootDir, LOCK_FILE_NAME);
558
+ if (!existsSync4(lockPath)) {
559
+ return false;
560
+ }
561
+ try {
562
+ unlinkSync(lockPath);
563
+ return true;
564
+ } catch {
565
+ return false;
566
+ }
567
+ }
568
+ function createLockManifest(config, lockedBy = "forge-ts lock") {
569
+ const rules = {};
570
+ for (const [key, value] of Object.entries(config.enforce.rules)) {
571
+ rules[key] = value;
572
+ }
573
+ const lockConfig = { rules };
574
+ if (config.guards.tsconfig.enabled) {
575
+ lockConfig.tsconfig = {
576
+ enabled: config.guards.tsconfig.enabled,
577
+ requiredFlags: config.guards.tsconfig.requiredFlags
578
+ };
579
+ }
580
+ if (config.guards.biome.enabled) {
581
+ lockConfig.biome = {
582
+ enabled: config.guards.biome.enabled,
583
+ lockedRules: config.guards.biome.lockedRules
584
+ };
585
+ }
586
+ return {
587
+ version: "1.0.0",
588
+ lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
589
+ lockedBy,
590
+ config: lockConfig
591
+ };
592
+ }
593
+ function validateAgainstLock(config, lock) {
594
+ const violations = [];
595
+ const severityRank = {
596
+ off: 0,
597
+ warn: 1,
598
+ error: 2
599
+ };
600
+ for (const [ruleName, lockedSeverity] of Object.entries(lock.config.rules)) {
601
+ const currentSeverity = config.enforce.rules[ruleName] ?? "off";
602
+ const lockedRank = severityRank[lockedSeverity] ?? 0;
603
+ const currentRank = severityRank[currentSeverity] ?? 0;
604
+ if (currentRank < lockedRank) {
605
+ violations.push({
606
+ field: `rules.${ruleName}`,
607
+ locked: lockedSeverity,
608
+ current: currentSeverity,
609
+ message: `Rule "${ruleName}" was weakened from "${lockedSeverity}" to "${currentSeverity}". Locked settings cannot be weakened without running "forge-ts unlock --reason=...".`
610
+ });
611
+ }
612
+ }
613
+ if (lock.config.tsconfig) {
614
+ const lockedTsconfig = lock.config.tsconfig;
615
+ if (lockedTsconfig.enabled && !config.guards.tsconfig.enabled) {
616
+ violations.push({
617
+ field: "guards.tsconfig.enabled",
618
+ locked: "true",
619
+ current: "false",
620
+ message: 'tsconfig guard was disabled. Locked settings cannot be weakened without running "forge-ts unlock --reason=...".'
621
+ });
622
+ }
623
+ if (lockedTsconfig.requiredFlags && config.guards.tsconfig.enabled) {
624
+ const currentFlags = new Set(config.guards.tsconfig.requiredFlags);
625
+ for (const flag of lockedTsconfig.requiredFlags) {
626
+ if (!currentFlags.has(flag)) {
627
+ violations.push({
628
+ field: `guards.tsconfig.requiredFlags.${flag}`,
629
+ locked: flag,
630
+ current: "(removed)",
631
+ message: `tsconfig required flag "${flag}" was removed. Locked settings cannot be weakened without running "forge-ts unlock --reason=...".`
632
+ });
633
+ }
634
+ }
635
+ }
636
+ }
637
+ if (lock.config.biome) {
638
+ const lockedBiome = lock.config.biome;
639
+ if (lockedBiome.enabled && !config.guards.biome.enabled) {
640
+ violations.push({
641
+ field: "guards.biome.enabled",
642
+ locked: "true",
643
+ current: "false",
644
+ message: 'Biome guard was disabled. Locked settings cannot be weakened without running "forge-ts unlock --reason=...".'
645
+ });
646
+ }
647
+ if (lockedBiome.lockedRules && config.guards.biome.enabled) {
648
+ const currentRules = new Set(config.guards.biome.lockedRules);
649
+ for (const rule of lockedBiome.lockedRules) {
650
+ if (!currentRules.has(rule)) {
651
+ violations.push({
652
+ field: `guards.biome.lockedRules.${rule}`,
653
+ locked: rule,
654
+ current: "(removed)",
655
+ message: `Biome locked rule "${rule}" was removed. Locked settings cannot be weakened without running "forge-ts unlock --reason=...".`
656
+ });
657
+ }
658
+ }
659
+ }
660
+ }
661
+ return violations;
662
+ }
663
+
227
664
  // src/visibility.ts
228
665
  function resolveVisibility(tags) {
229
666
  if (!tags) return "public" /* Public */;
@@ -246,15 +683,34 @@ function filterByVisibility(symbols, minVisibility) {
246
683
  }
247
684
 
248
685
  // src/walker.ts
249
- import { readFileSync } from "fs";
250
- import { resolve as resolve2 } from "path";
686
+ import { readFileSync as readFileSync4 } from "fs";
687
+ import { dirname, resolve as resolve2 } from "path";
251
688
  import {
252
689
  DocNodeKind,
253
690
  StandardTags,
254
691
  TSDocConfiguration,
255
692
  TSDocParser
256
693
  } from "@microsoft/tsdoc";
694
+ import { TSDocConfigFile } from "@microsoft/tsdoc-config";
257
695
  import ts from "typescript";
696
+ var tsdocConfigCache = /* @__PURE__ */ new Map();
697
+ function clearTSDocConfigCache() {
698
+ tsdocConfigCache.clear();
699
+ }
700
+ function loadTSDocConfiguration(folderPath) {
701
+ const cached = tsdocConfigCache.get(folderPath);
702
+ if (cached) return cached;
703
+ const configuration = new TSDocConfiguration();
704
+ try {
705
+ const configFile = TSDocConfigFile.loadForFolder(folderPath);
706
+ if (!configFile.fileNotFound && !configFile.hasErrors) {
707
+ configFile.configureParser(configuration);
708
+ }
709
+ } catch {
710
+ }
711
+ tsdocConfigCache.set(folderPath, configuration);
712
+ return configuration;
713
+ }
258
714
  function renderInlineNodes(nodes) {
259
715
  const parts = [];
260
716
  for (const node of nodes) {
@@ -309,8 +765,8 @@ function extractExamples(comment, startLine) {
309
765
  }
310
766
  return examples;
311
767
  }
312
- function parseTSDoc(rawComment, startLine) {
313
- const configuration = new TSDocConfiguration();
768
+ function parseTSDoc(rawComment, startLine, folderPath) {
769
+ const configuration = folderPath !== void 0 ? loadTSDocConfiguration(folderPath) : new TSDocConfiguration();
314
770
  const parser = new TSDocParser(configuration);
315
771
  const result = parser.parseString(rawComment);
316
772
  const comment = result.docComment;
@@ -362,6 +818,139 @@ function parseTSDoc(rawComment, startLine) {
362
818
  }
363
819
  }
364
820
  }
821
+ if (comment.remarksBlock) {
822
+ const remarksText = renderBlock(comment.remarksBlock).trim();
823
+ tags.remarks = remarksText ? [remarksText] : [];
824
+ }
825
+ if (comment.seeBlocks.length > 0) {
826
+ tags.see = comment.seeBlocks.map((block) => renderBlock(block).trim()).filter(Boolean);
827
+ }
828
+ if (comment.typeParams.count > 0) {
829
+ tags.typeParam = comment.typeParams.blocks.map(
830
+ (block) => `${block.parameterName} - ${renderBlock(block).trim()}`
831
+ );
832
+ }
833
+ for (const block of comment.customBlocks) {
834
+ if (block.blockTag.tagName.toLowerCase() === "@defaultvalue") {
835
+ const dvText = renderBlock(block).trim();
836
+ if (!tags.defaultValue) tags.defaultValue = [];
837
+ tags.defaultValue.push(dvText);
838
+ }
839
+ }
840
+ for (const block of comment.customBlocks) {
841
+ if (block.blockTag.tagName.toLowerCase() === "@concept") {
842
+ const conceptText = renderBlock(block).trim();
843
+ if (conceptText) {
844
+ if (!tags.concept) tags.concept = [];
845
+ tags.concept.push(conceptText);
846
+ }
847
+ }
848
+ }
849
+ for (const block of comment.customBlocks) {
850
+ if (block.blockTag.tagName.toLowerCase() === "@guide") {
851
+ const guideText = renderBlock(block).trim();
852
+ if (guideText) {
853
+ if (!tags.guide) tags.guide = [];
854
+ tags.guide.push(guideText);
855
+ }
856
+ }
857
+ }
858
+ for (const block of comment.customBlocks) {
859
+ if (block.blockTag.tagName.toLowerCase() === "@category") {
860
+ const categoryText = renderBlock(block).trim();
861
+ if (categoryText) {
862
+ if (!tags.category) tags.category = [];
863
+ tags.category.push(categoryText);
864
+ }
865
+ }
866
+ }
867
+ for (const block of comment.customBlocks) {
868
+ if (block.blockTag.tagName.toLowerCase() === "@since") {
869
+ const sinceText = renderBlock(block).trim();
870
+ if (sinceText) {
871
+ if (!tags.since) tags.since = [];
872
+ tags.since.push(sinceText);
873
+ }
874
+ }
875
+ }
876
+ for (const block of comment.customBlocks) {
877
+ if (block.blockTag.tagName.toLowerCase() === "@response") {
878
+ const responseText = renderBlock(block).trim();
879
+ if (responseText) {
880
+ if (!tags.response) tags.response = [];
881
+ tags.response.push(responseText);
882
+ }
883
+ }
884
+ }
885
+ for (const block of comment.customBlocks) {
886
+ if (block.blockTag.tagName.toLowerCase() === "@query") {
887
+ const queryText = renderBlock(block).trim();
888
+ if (queryText) {
889
+ if (!tags.query) tags.query = [];
890
+ tags.query.push(queryText);
891
+ }
892
+ }
893
+ }
894
+ for (const block of comment.customBlocks) {
895
+ if (block.blockTag.tagName.toLowerCase() === "@header") {
896
+ const headerText = renderBlock(block).trim();
897
+ if (headerText) {
898
+ if (!tags.header) tags.header = [];
899
+ tags.header.push(headerText);
900
+ }
901
+ }
902
+ }
903
+ for (const block of comment.customBlocks) {
904
+ if (block.blockTag.tagName.toLowerCase() === "@body") {
905
+ const bodyText = renderBlock(block).trim();
906
+ if (bodyText) {
907
+ if (!tags.body) tags.body = [];
908
+ tags.body.push(bodyText);
909
+ }
910
+ }
911
+ }
912
+ for (const block of comment.customBlocks) {
913
+ if (block.blockTag.tagName.toLowerCase() === "@faq") {
914
+ const faqText = renderBlock(block).trim();
915
+ if (faqText) {
916
+ if (!tags.faq) tags.faq = [];
917
+ tags.faq.push(faqText);
918
+ }
919
+ }
920
+ }
921
+ for (const block of comment.customBlocks) {
922
+ if (block.blockTag.tagName.toLowerCase() === "@breaking") {
923
+ const breakingText = renderBlock(block).trim();
924
+ if (breakingText) {
925
+ if (!tags.breaking) tags.breaking = [];
926
+ tags.breaking.push(breakingText);
927
+ }
928
+ }
929
+ }
930
+ for (const block of comment.customBlocks) {
931
+ if (block.blockTag.tagName.toLowerCase() === "@migration") {
932
+ const migrationText = renderBlock(block).trim();
933
+ if (migrationText) {
934
+ if (!tags.migration) tags.migration = [];
935
+ tags.migration.push(migrationText);
936
+ }
937
+ }
938
+ }
939
+ for (const block of comment.customBlocks) {
940
+ if (block.blockTag.tagName.toLowerCase() === "@complexity") {
941
+ const complexityText = renderBlock(block).trim();
942
+ if (complexityText) {
943
+ if (!tags.complexity) tags.complexity = [];
944
+ tags.complexity.push(complexityText);
945
+ }
946
+ }
947
+ }
948
+ for (const tag of comment.modifierTagSet.nodes) {
949
+ if (tag.tagName.toLowerCase() === "@quickstart") {
950
+ tags.quickstart = [];
951
+ break;
952
+ }
953
+ }
365
954
  const examples = extractExamples(comment, startLine);
366
955
  const links = [];
367
956
  function walkForLinks(node) {
@@ -379,6 +968,22 @@ function parseTSDoc(rawComment, startLine) {
379
968
  }
380
969
  }
381
970
  walkForLinks(comment);
971
+ if (comment.inheritDocTag?.declarationReference) {
972
+ const ref = comment.inheritDocTag.declarationReference;
973
+ const target = ref.memberReferences.map((r) => r.memberIdentifier?.identifier ?? "").filter(Boolean).join(".");
974
+ if (target) {
975
+ if (!tags.inheritDoc) tags.inheritDoc = [];
976
+ tags.inheritDoc.push(target);
977
+ }
978
+ }
979
+ const parseMessages = [];
980
+ for (const msg of result.log.messages) {
981
+ parseMessages.push({
982
+ messageId: msg.messageId,
983
+ text: msg.unformattedText,
984
+ line: startLine
985
+ });
986
+ }
382
987
  const summary = renderDocSection(comment.summarySection);
383
988
  return {
384
989
  summary: summary || void 0,
@@ -388,7 +993,8 @@ function parseTSDoc(rawComment, startLine) {
388
993
  examples: examples.length > 0 ? examples : void 0,
389
994
  tags: Object.keys(tags).length > 0 ? tags : void 0,
390
995
  deprecated,
391
- links: links.length > 0 ? links : void 0
996
+ links: links.length > 0 ? links : void 0,
997
+ parseMessages: parseMessages.length > 0 ? parseMessages : void 0
392
998
  };
393
999
  }
394
1000
  function getLeadingComment(node, sourceFile) {
@@ -439,9 +1045,10 @@ function buildSignature(node, checker) {
439
1045
  return void 0;
440
1046
  }
441
1047
  }
442
- function extractSymbolsFromFile(sourceFile, checker, _tsdocParser) {
1048
+ function extractSymbolsFromFile(sourceFile, checker) {
443
1049
  const symbols = [];
444
1050
  const filePath = sourceFile.fileName;
1051
+ const fileDir = dirname(filePath);
445
1052
  function visit(node, parentExported) {
446
1053
  const isExported = parentExported || (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0;
447
1054
  if (ts.isExportDeclaration(node)) {
@@ -458,7 +1065,7 @@ function extractSymbolsFromFile(sourceFile, checker, _tsdocParser) {
458
1065
  const name2 = decl.name.getText(sourceFile);
459
1066
  const pos2 = sourceFile.getLineAndCharacterOfPosition(decl.getStart());
460
1067
  const rawComment2 = getLeadingComment(node, sourceFile);
461
- const documentation2 = rawComment2 ? parseTSDoc(rawComment2, pos2.line + 1) : void 0;
1068
+ const documentation2 = rawComment2 ? parseTSDoc(rawComment2, pos2.line + 1, fileDir) : void 0;
462
1069
  const tags2 = documentation2?.tags;
463
1070
  const visibility2 = resolveVisibility(tags2);
464
1071
  symbols.push({
@@ -488,7 +1095,7 @@ function extractSymbolsFromFile(sourceFile, checker, _tsdocParser) {
488
1095
  const name = nameNode.getText(sourceFile);
489
1096
  const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart());
490
1097
  const rawComment = getLeadingComment(node, sourceFile);
491
- const documentation = rawComment ? parseTSDoc(rawComment, pos.line + 1) : void 0;
1098
+ const documentation = rawComment ? parseTSDoc(rawComment, pos.line + 1, fileDir) : void 0;
492
1099
  const tags = documentation?.tags;
493
1100
  const visibility = resolveVisibility(tags);
494
1101
  const children = [];
@@ -499,7 +1106,7 @@ function extractSymbolsFromFile(sourceFile, checker, _tsdocParser) {
499
1106
  const memberName = member.name?.getText(sourceFile) ?? "";
500
1107
  const memberPos = sourceFile.getLineAndCharacterOfPosition(member.getStart());
501
1108
  const memberComment = getLeadingComment(member, sourceFile);
502
- const memberDoc = memberComment ? parseTSDoc(memberComment, memberPos.line + 1) : void 0;
1109
+ const memberDoc = memberComment ? parseTSDoc(memberComment, memberPos.line + 1, fileDir) : void 0;
503
1110
  const memberTags = memberDoc?.tags;
504
1111
  const memberVisibility = resolveVisibility(memberTags);
505
1112
  children.push({
@@ -535,7 +1142,7 @@ function createWalker(config) {
535
1142
  return {
536
1143
  walk() {
537
1144
  const tsconfigPath = resolve2(config.tsconfig);
538
- const configFile = ts.readConfigFile(tsconfigPath, (path) => readFileSync(path, "utf8"));
1145
+ const configFile = ts.readConfigFile(tsconfigPath, (path) => readFileSync4(path, "utf8"));
539
1146
  if (configFile.error) {
540
1147
  throw new Error(
541
1148
  `Failed to read tsconfig at ${tsconfigPath}: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
@@ -551,14 +1158,12 @@ function createWalker(config) {
551
1158
  options: parsedCommandLine.options
552
1159
  });
553
1160
  const checker = program.getTypeChecker();
554
- const tsdocConfiguration = new TSDocConfiguration();
555
- const tsdocParser = new TSDocParser(tsdocConfiguration);
556
1161
  const allSymbols = [];
557
1162
  for (const sourceFile of program.getSourceFiles()) {
558
1163
  if (sourceFile.isDeclarationFile || sourceFile.fileName.includes("node_modules")) {
559
1164
  continue;
560
1165
  }
561
- const fileSymbols = extractSymbolsFromFile(sourceFile, checker, tsdocParser);
1166
+ const fileSymbols = extractSymbolsFromFile(sourceFile, checker);
562
1167
  allSymbols.push(...fileSymbols);
563
1168
  }
564
1169
  return allSymbols;
@@ -567,11 +1172,27 @@ function createWalker(config) {
567
1172
  }
568
1173
  export {
569
1174
  Visibility,
1175
+ appendAuditEvent,
1176
+ clearTSDocConfigCache,
1177
+ createBypass,
1178
+ createLockManifest,
570
1179
  createWalker,
571
1180
  defaultConfig,
1181
+ expireOldBypasses,
572
1182
  filterByVisibility,
1183
+ formatAuditEvent,
1184
+ getActiveBypasses,
1185
+ getCurrentUser,
1186
+ getRemainingBudget,
1187
+ isRuleBypassed,
573
1188
  loadConfig,
1189
+ loadTSDocConfiguration,
574
1190
  meetsVisibility,
575
- resolveVisibility
1191
+ readAuditLog,
1192
+ readLockFile,
1193
+ removeLockFile,
1194
+ resolveVisibility,
1195
+ validateAgainstLock,
1196
+ writeLockFile
576
1197
  };
577
1198
  //# sourceMappingURL=index.js.map