@ankhorage/paradox 0.1.27 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +53 -61
  3. package/dist/analyze/analyze.d.ts +2 -2
  4. package/dist/analyze/analyze.js +51 -35
  5. package/dist/analyze/badges.d.ts +2 -1
  6. package/dist/analyze/badges.js +14 -4
  7. package/dist/analyze/components.js +12 -11
  8. package/dist/analyze/documentation/collectDocumentationCommentsAsync.d.ts +11 -0
  9. package/dist/analyze/documentation/collectDocumentationCommentsAsync.js +72 -0
  10. package/dist/analyze/documentation/findings.d.ts +5 -0
  11. package/dist/analyze/documentation/findings.js +17 -0
  12. package/dist/analyze/documentation/validateDocumentationPolicyAsync.d.ts +13 -0
  13. package/dist/analyze/documentation/validateDocumentationPolicyAsync.js +150 -0
  14. package/dist/analyze/documentation/validateReferencesAsync.d.ts +11 -0
  15. package/dist/analyze/documentation/validateReferencesAsync.js +148 -0
  16. package/dist/analyze/exports.d.ts +4 -0
  17. package/dist/analyze/exports.js +17 -15
  18. package/dist/analyze/modules.js +3 -8
  19. package/dist/analyze/readmeConfig.d.ts +1 -3
  20. package/dist/analyze/readmeConfig.js +8 -19
  21. package/dist/analyze/readmeUsage.d.ts +7 -10
  22. package/dist/analyze/readmeUsage.js +124 -48
  23. package/dist/analyze/semantic/docBlocks.js +18 -48
  24. package/dist/analyze/semantic/exports.js +3 -7
  25. package/dist/analyze/semantic/model.d.ts +0 -2
  26. package/dist/analyze/semantic/paradoxComment.d.ts +1 -11
  27. package/dist/analyze/semantic/paradoxComment.js +1 -43
  28. package/dist/analyze/semantic/tagRegistry.js +2 -1
  29. package/dist/analyze/sequenceScenarios.js +2 -4
  30. package/dist/analyze/sourceFunctions.js +4 -4
  31. package/dist/analyze/types.d.ts +31 -24
  32. package/dist/analyze/usage.d.ts +2 -2
  33. package/dist/analyze/usage.js +3 -33
  34. package/dist/analyze/utils/getExportMetadata.js +11 -40
  35. package/dist/analyze/utils/parseParadoxComment.d.ts +12 -9
  36. package/dist/analyze/utils/parseParadoxComment.js +66 -78
  37. package/dist/cli/index.d.ts +3 -2
  38. package/dist/cli/index.js +3 -2
  39. package/dist/cli/standalone.js +11 -0
  40. package/dist/config/defineParadoxConfig.d.ts +1 -1
  41. package/dist/doc-tags/registry.d.ts +28 -32
  42. package/dist/doc-tags/registry.js +35 -39
  43. package/dist/index.d.ts +1 -1
  44. package/dist/model/buildModel.d.ts +26 -19
  45. package/dist/model/buildModel.js +33 -99
  46. package/dist/model/types.d.ts +27 -20
  47. package/dist/paths/policy.d.ts +1 -1
  48. package/dist/render/renderers/diagrams.js +3 -11
  49. package/dist/render/renderers/html.js +89 -58
  50. package/dist/render/renderers/markdown.js +139 -86
  51. package/dist/render/toFileStem.d.ts +2 -0
  52. package/dist/render/toFileStem.js +8 -0
  53. package/dist/{config/types.d.ts → types/config.d.ts} +3 -5
  54. package/dist/write/write.d.ts +1 -1
  55. package/package.json +2 -1
  56. package/dist/analyze/readmeCli.d.ts +0 -9
  57. package/dist/analyze/readmeCli.js +0 -33
  58. package/dist/analyze/utils/getLeadingParadoxComment.d.ts +0 -10
  59. package/dist/analyze/utils/getLeadingParadoxComment.js +0 -16
  60. /package/dist/{config/types.js → types/config.js} +0 -0
@@ -1,60 +1,108 @@
1
- import { readFile } from 'node:fs/promises';
2
- import { extname, isAbsolute, join, relative } from 'node:path';
1
+ import { readdir } from 'node:fs/promises';
2
+ import { extname, join, relative } from 'node:path';
3
+ import { DOCUMENTATION_POLICY } from '@ankhorage/policy/documentation';
4
+ import { Project } from 'ts-morph';
5
+ import { getParadoxComment } from './utils/getParadoxComment.js';
3
6
  import { parseParadoxComment } from './utils/parseParadoxComment.js';
4
- const USAGE_TAG = `${String.fromCharCode(64)}usage`;
7
+ const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
5
8
  /***
6
- * Collects README usage examples from configured real source files.
9
+ * Collects every canonical usage declaration from examples and CLI source roots.
7
10
  */
8
11
  export async function analyzeReadmeUsage(options) {
9
- const entries = await Promise.all(options.entrypoints.map(async (entrypoint) => analyzeUsageEntrypoint(options.root, entrypoint)));
10
- return entries.flat().sort((left, right) => left.sourcePath.localeCompare(right.sourcePath));
12
+ const project = new Project({ skipAddingFilesFromTsConfig: true });
13
+ const files = (await Promise.all(DOCUMENTATION_POLICY.paths.usageRoots.map((usageRoot) => collectSourceFilesAsync(join(options.root, usageRoot)))))
14
+ .flat()
15
+ .sort((left, right) => left.localeCompare(right));
16
+ return files.flatMap((filePath) => analyzeUsageFile(options.root, project, filePath));
11
17
  }
12
- async function analyzeUsageEntrypoint(root, entrypoint) {
13
- const absolutePath = isAbsolute(entrypoint) ? entrypoint : join(root, entrypoint);
14
- const source = await readFile(absolutePath, 'utf-8');
15
- const sourcePath = toPosixPath(relative(root, absolutePath));
16
- const matches = findUsageComments(source);
17
- return matches.map((match) => {
18
- const parsed = parseParadoxComment(match.comment);
19
- return {
20
- title: getUsageTitle(parsed.description, sourcePath),
21
- description: parsed.description,
22
- language: getLanguage(sourcePath),
23
- code: removeRange(source, match.start, match.end).trim(),
24
- sourcePath,
25
- };
18
+ /***
19
+ * Collects supported source files recursively below one canonical usage root.
20
+ */
21
+ async function collectSourceFilesAsync(root) {
22
+ let entries;
23
+ try {
24
+ entries = await readdir(root, { withFileTypes: true });
25
+ }
26
+ catch (error) {
27
+ if (isMissingPathError(error))
28
+ return [];
29
+ throw error;
30
+ }
31
+ const nested = await Promise.all(entries.map(async (entry) => {
32
+ const path = join(root, entry.name);
33
+ if (entry.isDirectory())
34
+ return collectSourceFilesAsync(path);
35
+ return entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name)) ? [path] : [];
36
+ }));
37
+ return nested.flat();
38
+ }
39
+ /***
40
+ * Extracts usage-marked top-level statements from one real source file.
41
+ */
42
+ function analyzeUsageFile(root, project, filePath) {
43
+ const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
44
+ const sourcePath = toPosixPath(relative(root, filePath));
45
+ return sourceFile.getStatements().flatMap((statement) => {
46
+ const comment = getParadoxComment(statement);
47
+ if (comment === null)
48
+ return [];
49
+ const parsed = parseParadoxComment(comment);
50
+ if (!parsed.isUsage)
51
+ return [];
52
+ return [
53
+ {
54
+ area: sourcePath.startsWith(`${DOCUMENTATION_POLICY.paths.examplesRoot}/`)
55
+ ? 'examples'
56
+ : 'cli',
57
+ title: parsed.title ?? deriveUsageTitle(sourcePath),
58
+ description: parsed.description,
59
+ language: getLanguage(sourcePath),
60
+ code: getStatementCode(statement),
61
+ sourcePath,
62
+ isReadme: parsed.isReadme,
63
+ see: parsed.see,
64
+ security: parsed.security,
65
+ },
66
+ ];
26
67
  });
27
68
  }
28
- function findUsageComments(source) {
29
- const matches = [];
30
- const pattern = /\/\*\*\*[\s\S]*?\*\//g;
31
- for (const match of source.matchAll(pattern)) {
32
- const [comment] = match;
33
- if (!comment.includes(USAGE_TAG))
34
- continue;
35
- matches.push({
36
- comment,
37
- start: match.index,
38
- end: match.index + comment.length,
39
- });
40
- }
41
- return matches;
69
+ /***
70
+ * Returns the exact source statement owned by a usage comment.
71
+ */
72
+ function getStatementCode(statement) {
73
+ return statement.getText().trim();
42
74
  }
43
- function removeRange(source, start, end) {
44
- const before = source.slice(0, start).trimEnd();
45
- const after = source.slice(end).trimStart();
46
- if (before.length === 0)
47
- return after;
48
- if (after.length === 0)
49
- return before;
50
- return `${before}\n\n${after}`;
75
+ /***
76
+ * Derives a deterministic title from the canonical example or CLI structure.
77
+ */
78
+ function deriveUsageTitle(sourcePath) {
79
+ const parts = sourcePath.split('/');
80
+ if (parts[0] === DOCUMENTATION_POLICY.paths.examplesRoot) {
81
+ return titleCase(parts[1] ?? 'usage');
82
+ }
83
+ if (sourcePath === `${DOCUMENTATION_POLICY.paths.cliRoot}/index.ts`) {
84
+ return 'CLI';
85
+ }
86
+ const commandsIndex = parts.indexOf('commands');
87
+ const commandParts = commandsIndex === -1 ? [parts.at(-1) ?? 'cli'] : parts.slice(commandsIndex + 1);
88
+ return commandParts
89
+ .map((part) => part.replace(/\.[^.]+$/, ''))
90
+ .map(titleCase)
91
+ .join(' ');
51
92
  }
52
- function getUsageTitle(description, sourcePath) {
53
- if (description === null)
54
- return sourcePath;
55
- const [firstLine = sourcePath] = description.split('\n');
56
- return firstLine.trim() || sourcePath;
93
+ /***
94
+ * Converts a kebab-case structural segment into presentation words.
95
+ */
96
+ function titleCase(value) {
97
+ return value
98
+ .split('-')
99
+ .filter((word) => word.length > 0)
100
+ .map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)
101
+ .join(' ');
57
102
  }
103
+ /***
104
+ * Returns the Markdown fence language for a usage source path.
105
+ */
58
106
  function getLanguage(sourcePath) {
59
107
  const extension = extname(sourcePath).toLowerCase();
60
108
  if (extension === '.tsx')
@@ -63,10 +111,38 @@ function getLanguage(sourcePath) {
63
111
  return 'ts';
64
112
  if (extension === '.jsx')
65
113
  return 'jsx';
66
- if (extension === '.js')
114
+ if (extension === '.js' || extension === '.mjs' || extension === '.cjs')
67
115
  return 'js';
68
116
  return '';
69
117
  }
118
+ /***
119
+ * Counts real example directories directly below the canonical examples root.
120
+ */
121
+ export async function countExampleDirectoriesAsync(root) {
122
+ try {
123
+ const entries = await readdir(join(root, DOCUMENTATION_POLICY.paths.examplesRoot), {
124
+ withFileTypes: true,
125
+ });
126
+ return entries.filter((entry) => entry.isDirectory()).length;
127
+ }
128
+ catch (error) {
129
+ if (isMissingPathError(error))
130
+ return 0;
131
+ throw error;
132
+ }
133
+ }
134
+ /***
135
+ * Checks whether a filesystem error reports a missing path.
136
+ */
137
+ function isMissingPathError(error) {
138
+ return (error instanceof Error &&
139
+ 'code' in error &&
140
+ typeof error.code === 'string' &&
141
+ error.code === 'ENOENT');
142
+ }
143
+ /***
144
+ * Normalizes filesystem separators for stable documentation paths.
145
+ */
70
146
  function toPosixPath(path) {
71
147
  return path.replaceAll('\\', '/');
72
148
  }
@@ -16,9 +16,8 @@ export function collectDocBlocks(sourceFile, options) {
16
16
  const end = start + raw.length;
17
17
  const { line, column } = sourceFile.getLineAndColumnAtPos(start);
18
18
  const parsed = parseDocBlock(raw, tagRegistry);
19
- const id = `${sourcePath}:${line}:${column}`;
20
19
  blocks.push({
21
- id,
20
+ id: `${sourcePath}:${line}:${column}`,
22
21
  sourcePath,
23
22
  start,
24
23
  end,
@@ -26,8 +25,6 @@ export function collectDocBlocks(sourceFile, options) {
26
25
  column,
27
26
  raw,
28
27
  description: parsed.description,
29
- params: parsed.params,
30
- returns: parsed.returns,
31
28
  tags: parsed.tags,
32
29
  });
33
30
  }
@@ -37,62 +34,35 @@ export function collectDocBlocks(sourceFile, options) {
37
34
  * Extracts registered tags from a doc block.
38
35
  */
39
36
  function collectTags(raw, tagRegistry = defaultTagRegistry) {
40
- const tags = [];
41
- const lines = normalizeDocBlock(raw);
42
- for (const line of lines) {
43
- const trimmed = line.trim();
44
- if (!trimmed.startsWith('@'))
45
- continue;
46
- const [tagName, ...rest] = trimmed.slice(1).split(/\s+/);
47
- if (!tagName || !tagRegistry.has(tagName))
48
- continue;
49
- const value = rest.join(' ').trim();
50
- tags.push({
51
- name: tagName,
52
- value: value.length > 0 ? value : null,
53
- });
54
- }
55
- return tags;
37
+ return normalizeDocBlock(raw).flatMap((line) => {
38
+ const match = /^@([A-Za-z][A-Za-z0-9-]*)(?:\s+(.*))?$/.exec(line.trim());
39
+ if (match === null)
40
+ return [];
41
+ const [, name] = match;
42
+ if (!tagRegistry.has(name))
43
+ return [];
44
+ const value = match.slice(2).join('').trim();
45
+ return [{ name, value: value.length > 0 ? value : null }];
46
+ });
56
47
  }
48
+ /***
49
+ * Parses semantic description and supported tags without interpreting unsupported tag syntax.
50
+ */
57
51
  function parseDocBlock(raw, tagRegistry) {
58
52
  const lines = normalizeDocBlock(raw);
59
53
  const tags = collectTags(raw, tagRegistry);
60
- const params = {};
61
- let returns = null;
62
54
  const description = lines
63
- .filter((line) => {
64
- const trimmed = line.trim();
65
- if (!trimmed.startsWith('@'))
66
- return true;
67
- const [tagName, ...rest] = trimmed.slice(1).split(/\s+/);
68
- if (!tagName)
69
- return false;
70
- if (tagName === 'param') {
71
- const [name, ...descParts] = rest;
72
- if (name) {
73
- params[name] = descParts.join(' ').trim();
74
- }
75
- return false;
76
- }
77
- if (tagName === 'returns' || tagName === 'return') {
78
- const returnBody = rest.join(' ').trim();
79
- returns = returnBody.length > 0 ? returnBody : null;
80
- return false;
81
- }
82
- if (tagRegistry.has(tagName)) {
83
- return false;
84
- }
85
- return true;
86
- })
55
+ .filter((line) => !/^@[A-Za-z][A-Za-z0-9-]*(?:\s|$)/.test(line.trim()))
87
56
  .join('\n')
88
57
  .trim();
89
58
  return {
90
59
  description: description.length > 0 ? description : null,
91
60
  tags,
92
- params,
93
- returns,
94
61
  };
95
62
  }
63
+ /***
64
+ * Removes Paradox comment delimiters while preserving prose content.
65
+ */
96
66
  function normalizeDocBlock(raw) {
97
67
  return raw
98
68
  .replace(/^\/\*\*\*/, '')
@@ -1,3 +1,4 @@
1
+ import { uniqueSortedStrings } from '@ankhorage/utility/array';
1
2
  import { Node as MorphNode, TypeFormatFlags } from 'ts-morph';
2
3
  import { isReactComponent } from './isReactComponent.js';
3
4
  import { getParadoxComment, parseParadoxComment } from './paradoxComment.js';
@@ -21,7 +22,7 @@ export function collectExports(program) {
21
22
  const sourcePath = relativeToRoot(program.root, declaration.getSourceFile().getFilePath());
22
23
  const existing = exportsByName.get(name);
23
24
  const exportPaths = existing
24
- ? uniqueSorted([...existing.exportPaths, entrypointPath])
25
+ ? uniqueSortedStrings([...existing.exportPaths, entrypointPath])
25
26
  : [entrypointPath];
26
27
  const kind = existing?.kind ?? detectExportKind(declaration);
27
28
  exportsByName.set(name, {
@@ -226,9 +227,7 @@ function collectMembersFromType(program, reference, inheritedFrom, depth) {
226
227
  return [];
227
228
  const propertyType = property.getTypeAtLocation(declaration);
228
229
  const rawComment = getParadoxComment(declaration);
229
- const parsed = rawComment
230
- ? parseParadoxComment(rawComment)
231
- : { description: null, isConfig: false, params: {}, returns: null };
230
+ const parsed = rawComment ? parseParadoxComment(rawComment) : { description: null };
232
231
  const required = !property.isOptional() && !isUndefinedUnion(propertyType);
233
232
  const childReference = shouldExpandType(program, propertyType)
234
233
  ? resolveTypeReference(program, propertyType)
@@ -380,9 +379,6 @@ function getCallableNode(node) {
380
379
  }
381
380
  return null;
382
381
  }
383
- function uniqueSorted(values) {
384
- return [...new Set(values)].sort((left, right) => left.localeCompare(right));
385
- }
386
382
  const IGNORED_RELATED_SYMBOLS = new Set([
387
383
  'Array',
388
384
  'Boolean',
@@ -20,8 +20,6 @@ export interface AnalyzedDocBlock {
20
20
  column: number;
21
21
  raw: string;
22
22
  description: string | null;
23
- params: Record<string, string>;
24
- returns: string | null;
25
23
  tags: AnalyzedTag[];
26
24
  }
27
25
  export interface AnalyzedTag {
@@ -1,16 +1,6 @@
1
1
  import type { Node } from 'ts-morph';
2
+ export { parseParadoxComment } from '../utils/parseParadoxComment.js';
2
3
  /***
3
4
  * Reads the nearest Paradox doc comment attached to a declaration.
4
5
  */
5
6
  export declare function getParadoxComment(node: Node | undefined): string | null;
6
- interface ParsedParadoxComment {
7
- description: string | null;
8
- isConfig: boolean;
9
- params: Record<string, string>;
10
- returns: string | null;
11
- }
12
- /***
13
- * Parses a Paradox doc comment into structured metadata.
14
- */
15
- export declare function parseParadoxComment(rawComment: string): ParsedParadoxComment;
16
- export {};
@@ -1,3 +1,4 @@
1
+ export { parseParadoxComment } from '../utils/parseParadoxComment.js';
1
2
  /***
2
3
  * Reads the nearest Paradox doc comment attached to a declaration.
3
4
  */
@@ -19,46 +20,3 @@ export function getParadoxComment(node) {
19
20
  return null;
20
21
  return text.slice(commentStart, commentEnd + 2);
21
22
  }
22
- /***
23
- * Parses a Paradox doc comment into structured metadata.
24
- */
25
- export function parseParadoxComment(rawComment) {
26
- const lines = rawComment
27
- .replace(/^\/\*\*\*/, '')
28
- .replace(/\*\/$/, '')
29
- .split('\n')
30
- .map((line) => line.replace(/^\s*\*\s?/, '').trimEnd());
31
- let isConfig = false;
32
- const params = {};
33
- let returns = null;
34
- const description = lines
35
- .filter((line) => {
36
- const trimmed = line.trimStart();
37
- if (trimmed.startsWith('@config')) {
38
- isConfig = true;
39
- return false;
40
- }
41
- if (trimmed.startsWith('@param ')) {
42
- const paramBody = trimmed.slice('@param '.length).trim();
43
- const [name, ...descriptionParts] = paramBody.split(/\s+/);
44
- if (name) {
45
- params[name] = descriptionParts.join(' ').trim();
46
- }
47
- return false;
48
- }
49
- if (trimmed.startsWith('@returns') || trimmed.startsWith('@return')) {
50
- const returnBody = trimmed.replace(/^@returns?/, '').trim();
51
- returns = returnBody.length > 0 ? returnBody : null;
52
- return false;
53
- }
54
- return true;
55
- })
56
- .join('\n')
57
- .trim();
58
- return {
59
- description: description.length > 0 ? description : null,
60
- isConfig,
61
- params,
62
- returns,
63
- };
64
- }
@@ -1 +1,2 @@
1
- export const defaultTagRegistry = new Set(['readme', 'config']);
1
+ import { DOCUMENTATION_POLICY } from '@ankhorage/policy/documentation';
2
+ export const defaultTagRegistry = new Set(DOCUMENTATION_POLICY.tags.map((tag) => tag.name));
@@ -1,4 +1,5 @@
1
1
  import { isAbsolute, join, normalize } from 'node:path';
2
+ import { uniqueSortedStrings } from '@ankhorage/utility/array';
2
3
  import { Node as MorphNode, } from 'ts-morph';
3
4
  import { relativeToRoot, toPosixPath } from './semantic/utils.js';
4
5
  import { getParadoxComment } from './utils/getParadoxComment.js';
@@ -85,7 +86,7 @@ function getBinSourceCandidates(targetPath) {
85
86
  candidates.push(normalized.replace(/\.jsx?$/, '.ts'));
86
87
  candidates.push(normalized.replace(/\.jsx?$/, '.tsx'));
87
88
  }
88
- return uniqueSorted(candidates);
89
+ return uniqueSortedStrings(candidates);
89
90
  }
90
91
  function getSourceFileByRelativePath(project, root, relativePath) {
91
92
  const absolutePath = normalize(isAbsolute(relativePath) ? relativePath : join(root, relativePath));
@@ -169,6 +170,3 @@ function uniqueByFunctionName(declarations) {
169
170
  return true;
170
171
  });
171
172
  }
172
- function uniqueSorted(values) {
173
- return [...new Set(values)].sort((left, right) => left.localeCompare(right));
174
- }
@@ -43,12 +43,12 @@ function createSourceFunction(name, node, root) {
43
43
  const sourceFile = node.getSourceFile();
44
44
  const { column, line } = sourceFile.getLineAndColumnAtPos(node.getStart(false));
45
45
  const rawComment = getParadoxComment(node);
46
- const parsedComment = rawComment
47
- ? parseParadoxComment(rawComment)
48
- : { description: null, isReadme: false };
46
+ const parsedComment = rawComment === null ? null : parseParadoxComment(rawComment);
49
47
  return {
50
48
  name,
51
- description: parsedComment.description,
49
+ description: parsedComment?.description ?? null,
50
+ see: parsedComment?.see ?? [],
51
+ security: parsedComment?.security ?? [],
52
52
  sourceLocation: {
53
53
  filePath: toPosixPath(relative(root, sourceFile.getFilePath())),
54
54
  line,
@@ -1,9 +1,5 @@
1
+ import type { PolicySeverity } from '@ankhorage/policy/status';
1
2
  import type { Node } from 'ts-morph';
2
- interface AnalysisExample {
3
- title: string | null;
4
- language: string | null;
5
- code: string;
6
- }
7
3
  /***
8
4
  * Describes one exported declaration discovered in a package.
9
5
  */
@@ -40,9 +36,11 @@ export interface AnalysisStructuredRow {
40
36
  export interface AnalysisExport {
41
37
  name: string;
42
38
  node: Node;
39
+ title: string | null;
43
40
  description: string | null;
44
41
  isReadme: boolean;
45
- examples: AnalysisExample[];
42
+ see: string[];
43
+ security: string[];
46
44
  kind: 'function' | 'type' | 'value' | 'unknown';
47
45
  modulePath: string;
48
46
  sourceLocation: AnalysisSourceLocation;
@@ -53,13 +51,14 @@ export interface AnalysisExport {
53
51
  structuredRows: AnalysisStructuredRow[];
54
52
  }
55
53
  /***
56
- * Describes one React component and its extracted props.
54
+ * Describes one React component and its props.
57
55
  */
58
56
  export interface AnalysisComponent {
59
57
  name: string;
60
58
  description: string | null;
61
59
  isReadme: boolean;
62
- examples: AnalysisExample[];
60
+ see: string[];
61
+ security: string[];
63
62
  modulePath: string;
64
63
  sourceLocation: AnalysisSourceLocation;
65
64
  exportPaths: string[];
@@ -73,28 +72,30 @@ export interface AnalysisComponent {
73
72
  }
74
73
  export interface AnalysisUsage {
75
74
  packageName: string;
76
- commands: AnalysisUsageCommand[];
77
- }
78
- interface AnalysisDonation {
79
- account: string;
80
- }
81
- interface AnalysisUsageCommand {
82
- name: string;
83
75
  command: string;
84
76
  }
85
- interface AnalysisReadmeUsage {
77
+ export interface AnalysisUsageEntry {
78
+ area: 'cli' | 'examples';
86
79
  title: string | null;
87
80
  description: string | null;
88
81
  language: string;
89
82
  code: string;
90
83
  sourcePath: string;
84
+ isReadme: boolean;
85
+ see: string[];
86
+ security: string[];
91
87
  }
92
- interface AnalysisReadmeCli {
93
- description: string | null;
94
- sourcePath: string;
88
+ export interface AnalysisDocumentationFinding {
89
+ ruleId: string;
90
+ severity: PolicySeverity;
91
+ message: string;
92
+ sourcePath: string | null;
93
+ line: number | null;
94
+ }
95
+ interface AnalysisDonation {
96
+ account: string;
95
97
  }
96
98
  interface AnalysisReadmeConfig {
97
- description: string | null;
98
99
  language: string;
99
100
  code: string;
100
101
  sourcePath: string;
@@ -122,6 +123,8 @@ export interface AnalysisSequenceScenario {
122
123
  export interface AnalysisSourceFunction {
123
124
  name: string;
124
125
  description: string | null;
126
+ see: string[];
127
+ security: string[];
125
128
  sourceLocation: AnalysisSourceLocation;
126
129
  }
127
130
  interface AnalysisTypeMember {
@@ -177,14 +180,18 @@ export interface AnalysisResult {
177
180
  modules: AnalysisModule[];
178
181
  badges: AnalysisBadge[];
179
182
  sequenceScenarios: AnalysisSequenceScenario[];
180
- usage: AnalysisUsage | null;
181
- readmeUsageDescription: string | null;
182
- readmeUsage: AnalysisReadmeUsage[];
183
- readmeCli: AnalysisReadmeCli | null;
183
+ usage: AnalysisUsage;
184
+ usageEntries: AnalysisUsageEntry[];
185
+ exampleCount: number;
186
+ findings: AnalysisDocumentationFinding[];
184
187
  readmeConfig: AnalysisReadmeConfig | null;
185
188
  config: {
186
189
  exportName: string;
190
+ title: string | null;
191
+ description: string | null;
187
192
  isReadme: boolean;
193
+ see: string[];
194
+ security: string[];
188
195
  members: AnalysisTypeMember[];
189
196
  } | null;
190
197
  graphs: AnalysisGraphs;
@@ -11,6 +11,6 @@ export interface PackageJsonModel {
11
11
  prettier?: unknown;
12
12
  }
13
13
  /***
14
- * Builds executable CLI commands from package metadata.
14
+ * Builds the canonical Ankh package help command from package metadata.
15
15
  */
16
- export declare function createUsageFromPackageJson(pkg: PackageJsonModel): AnalysisUsage | null;
16
+ export declare function createUsageFromPackageJson(pkg: PackageJsonModel): AnalysisUsage;
@@ -1,41 +1,11 @@
1
1
  /***
2
- * Builds executable CLI commands from package metadata.
2
+ * Builds the canonical Ankh package help command from package metadata.
3
3
  */
4
4
  export function createUsageFromPackageJson(pkg) {
5
- if (pkg.bin == null)
6
- return null;
7
- if (typeof pkg.bin === 'string') {
8
- return {
9
- packageName: pkg.name,
10
- commands: [
11
- {
12
- name: getPackageBaseName(pkg.name),
13
- command: `bunx ${pkg.name}`,
14
- },
15
- ],
16
- };
17
- }
18
- const entries = Object.keys(pkg.bin).sort((a, b) => a.localeCompare(b));
19
- if (entries.length === 0)
20
- return null;
21
- if (entries.length === 1) {
22
- const [name] = entries;
23
- return {
24
- packageName: pkg.name,
25
- commands: [
26
- {
27
- name,
28
- command: `bunx ${pkg.name}`,
29
- },
30
- ],
31
- };
32
- }
5
+ const packageName = getPackageBaseName(pkg.name);
33
6
  return {
34
7
  packageName: pkg.name,
35
- commands: entries.map((name) => ({
36
- name,
37
- command: `bunx ${pkg.name} ${name}`,
38
- })),
8
+ command: `ankh ${packageName} --help`,
39
9
  };
40
10
  }
41
11
  /***