@ankhorage/paradox 0.0.6 → 0.0.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 5246138: Generate deterministic README quality badges from local repository metadata.
8
+
9
+ Paradox now computes README badge metadata from local package, TypeScript, linting, formatting, workflow, test, coverage, and documentation signals, then renders stable local SVG badge assets under `paradox/badges/` and includes them in generated README output without relying on external badge services.
10
+
3
11
  ## 0.0.6
4
12
 
5
13
  ### Patch Changes
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @ankhorage/paradox
2
2
 
3
+ ![license: MIT](./paradox/badges/license.svg) ![npm: v0.0.6](./paradox/badges/npm.svg) ![runtime: bun](./paradox/badges/runtime.svg) ![typescript: strict](./paradox/badges/typescript.svg) ![eslint: checked](./paradox/badges/eslint.svg) ![prettier: checked](./paradox/badges/prettier.svg) ![build: checked](./paradox/badges/build.svg) ![tests: checked](./paradox/badges/tests.svg) ![docs: paradox](./paradox/badges/docs.svg)
4
+
3
5
  Deterministic documentation generator for TypeScript packages.
4
6
 
5
7
  ## Usage
@@ -39,6 +41,7 @@ graph TD
39
41
  package__ankhorage_paradox --> entrypoint_src_index_ts
40
42
  module_src_analyze_analyze_ts["src/analyze/analyze.ts"]
41
43
  package__ankhorage_paradox -.-> module_src_analyze_analyze_ts
44
+ module_src_analyze_analyze_ts --> module_src_analyze_badges_ts
42
45
  module_src_analyze_analyze_ts --> module_src_analyze_components_ts
43
46
  module_src_analyze_analyze_ts --> module_src_analyze_exports_ts
44
47
  module_src_analyze_analyze_ts --> module_src_analyze_modules_ts
@@ -46,6 +49,10 @@ graph TD
46
49
  module_src_analyze_analyze_ts --> module_src_analyze_types_ts
47
50
  module_src_analyze_analyze_ts --> module_src_analyze_usage_ts
48
51
  module_src_analyze_analyze_ts --> module_src_config_types_ts
52
+ module_src_analyze_badges_ts["src/analyze/badges.ts"]
53
+ package__ankhorage_paradox -.-> module_src_analyze_badges_ts
54
+ module_src_analyze_badges_ts --> module_src_analyze_types_ts
55
+ module_src_analyze_badges_ts --> module_src_analyze_usage_ts
49
56
  module_src_analyze_components_ts["src/analyze/components.ts"]
50
57
  package__ankhorage_paradox -.-> module_src_analyze_components_ts
51
58
  module_src_analyze_components_ts --> module_src_analyze_types_ts
@@ -113,10 +120,15 @@ graph TD
113
120
  module_src_render_render_ts["src/render/render.ts"]
114
121
  package__ankhorage_paradox -.-> module_src_render_render_ts
115
122
  module_src_render_render_ts --> module_src_model_types_ts
123
+ module_src_render_render_ts --> module_src_render_renderers_badges_ts
116
124
  module_src_render_render_ts --> module_src_render_renderers_diagrams_ts
117
125
  module_src_render_render_ts --> module_src_render_renderers_html_ts
118
126
  module_src_render_render_ts --> module_src_render_renderers_markdown_ts
119
127
  module_src_render_render_ts --> module_src_render_types_ts
128
+ module_src_render_renderers_badges_ts["src/render/renderers/badges.ts"]
129
+ package__ankhorage_paradox -.-> module_src_render_renderers_badges_ts
130
+ module_src_render_renderers_badges_ts --> module_src_model_types_ts
131
+ module_src_render_renderers_badges_ts --> module_src_render_types_ts
120
132
  module_src_render_renderers_diagrams_ts["src/render/renderers/diagrams.ts"]
121
133
  package__ankhorage_paradox -.-> module_src_render_renderers_diagrams_ts
122
134
  module_src_render_renderers_diagrams_ts --> module_src_model_types_ts
@@ -1,5 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
+ import { analyzeBadges } from './badges.js';
3
4
  import { analyzeComponents } from './components.js';
4
5
  import { analyzeExports } from './exports.js';
5
6
  import { analyzeModules } from './modules.js';
@@ -12,6 +13,7 @@ export async function analyze(config, runtime) {
12
13
  const root = runtime.packageRoot;
13
14
  const pkg = await readPackageJson(root);
14
15
  const usage = createUsageFromPackageJson(pkg);
16
+ const badges = await analyzeBadges(root, pkg);
15
17
  const project = createProject(root);
16
18
  const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
17
19
  const { config: configMetadata, exports } = analyzeExports(project, {
@@ -31,6 +33,7 @@ export async function analyze(config, runtime) {
31
33
  components,
32
34
  entrypoints: entrypoints.map((entrypoint) => entrypoint.replaceAll('\\', '/')).sort(),
33
35
  modules,
36
+ badges,
34
37
  usage,
35
38
  config: configMetadata,
36
39
  };
@@ -0,0 +1,6 @@
1
+ import type { AnalysisBadge } from './types.js';
2
+ import type { PackageJsonModel } from './usage.js';
3
+ /***
4
+ * Derives deterministic repository metadata badges from local repository files.
5
+ */
6
+ export declare function analyzeBadges(root: string, pkg: PackageJsonModel): Promise<AnalysisBadge[]>;
@@ -0,0 +1,256 @@
1
+ import { access, readdir, readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ const ESLINT_CONFIG_FILES = [
4
+ 'eslint.config.js',
5
+ 'eslint.config.mjs',
6
+ 'eslint.config.cjs',
7
+ 'eslint.config.ts',
8
+ '.eslintrc',
9
+ '.eslintrc.js',
10
+ '.eslintrc.cjs',
11
+ '.eslintrc.json',
12
+ '.eslintrc.yaml',
13
+ '.eslintrc.yml',
14
+ ];
15
+ const PRETTIER_CONFIG_FILES = [
16
+ '.prettierrc',
17
+ '.prettierrc.js',
18
+ '.prettierrc.cjs',
19
+ '.prettierrc.json',
20
+ '.prettierrc.yaml',
21
+ '.prettierrc.yml',
22
+ 'prettier.config.js',
23
+ 'prettier.config.cjs',
24
+ 'prettier.config.mjs',
25
+ 'prettier.config.ts',
26
+ ];
27
+ const BADGE_ORDER = [
28
+ 'license',
29
+ 'npm',
30
+ 'runtime',
31
+ 'typescript',
32
+ 'eslint',
33
+ 'prettier',
34
+ 'build',
35
+ 'tests',
36
+ 'coverage',
37
+ 'docs',
38
+ ];
39
+ /***
40
+ * Derives deterministic repository metadata badges from local repository files.
41
+ */
42
+ export async function analyzeBadges(root, pkg) {
43
+ const workflowFiles = await readWorkflowFiles(root);
44
+ const badges = [];
45
+ if ((await hasAnyFile(root, ESLINT_CONFIG_FILES)) ||
46
+ pkg.eslintConfig !== undefined ||
47
+ scriptContains(pkg, 'lint', 'eslint')) {
48
+ badges.push({
49
+ id: 'eslint',
50
+ label: 'eslint',
51
+ value: workflowRunsScript(workflowFiles, 'lint') ? 'checked' : 'configured',
52
+ color: workflowRunsScript(workflowFiles, 'lint') ? '0a7f3f' : '2563eb',
53
+ });
54
+ }
55
+ if ((await hasAnyFile(root, PRETTIER_CONFIG_FILES)) ||
56
+ pkg.prettier !== undefined ||
57
+ scriptContains(pkg, 'format', 'prettier') ||
58
+ scriptContains(pkg, 'format:check', 'prettier')) {
59
+ const prettierChecked = workflowRunsScript(workflowFiles, 'format') ||
60
+ workflowRunsScript(workflowFiles, 'format:check');
61
+ badges.push({
62
+ id: 'prettier',
63
+ label: 'prettier',
64
+ value: prettierChecked ? 'checked' : 'configured',
65
+ color: prettierChecked ? '0a7f3f' : '2563eb',
66
+ });
67
+ }
68
+ if (hasScript(pkg, 'build')) {
69
+ const buildChecked = workflowRunsScript(workflowFiles, 'build');
70
+ badges.push({
71
+ id: 'build',
72
+ label: 'build',
73
+ value: buildChecked ? 'checked' : 'configured',
74
+ color: buildChecked ? '0a7f3f' : '6f42c1',
75
+ });
76
+ }
77
+ if (hasScript(pkg, 'test')) {
78
+ const testsChecked = workflowRunsScript(workflowFiles, 'test');
79
+ badges.push({
80
+ id: 'tests',
81
+ label: 'tests',
82
+ value: testsChecked ? 'checked' : 'configured',
83
+ color: testsChecked ? '0a7f3f' : '6f42c1',
84
+ });
85
+ }
86
+ if (await hasTypeScriptStrictMode(root)) {
87
+ badges.push({
88
+ id: 'typescript',
89
+ label: 'typescript',
90
+ value: 'strict',
91
+ color: '2563eb',
92
+ });
93
+ }
94
+ if (supportsBun(pkg, workflowFiles)) {
95
+ badges.push({
96
+ id: 'runtime',
97
+ label: 'runtime',
98
+ value: 'bun',
99
+ color: 'f59e0b',
100
+ });
101
+ }
102
+ const coverage = await readCoveragePercent(root);
103
+ if (coverage !== null) {
104
+ badges.push({
105
+ id: 'coverage',
106
+ label: 'coverage',
107
+ value: `${formatPercent(coverage)}%`,
108
+ color: getCoverageColor(coverage),
109
+ });
110
+ }
111
+ if (typeof pkg.license === 'string' && pkg.license.length > 0) {
112
+ badges.push({
113
+ id: 'license',
114
+ label: 'license',
115
+ value: pkg.license,
116
+ color: '2563eb',
117
+ });
118
+ }
119
+ if (typeof pkg.version === 'string' && pkg.version.length > 0) {
120
+ badges.push({
121
+ id: 'npm',
122
+ label: 'npm',
123
+ value: `v${pkg.version}`,
124
+ color: 'cb3837',
125
+ });
126
+ }
127
+ badges.push({
128
+ id: 'docs',
129
+ label: 'docs',
130
+ value: 'paradox',
131
+ color: '0f766e',
132
+ });
133
+ return sortBadges(badges);
134
+ }
135
+ async function hasAnyFile(root, paths) {
136
+ for (const relativePath of paths) {
137
+ if (await fileExists(join(root, relativePath))) {
138
+ return true;
139
+ }
140
+ }
141
+ return false;
142
+ }
143
+ async function hasTypeScriptStrictMode(root) {
144
+ const tsconfig = await readJsonFile(join(root, 'tsconfig.json'));
145
+ return tsconfig?.compilerOptions?.strict === true;
146
+ }
147
+ async function readCoveragePercent(root) {
148
+ const coverage = await readJsonFile(join(root, 'coverage', 'coverage-summary.json'));
149
+ const value = coverage?.total?.lines?.pct;
150
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
151
+ }
152
+ function formatPercent(value) {
153
+ return Number.isInteger(value) ? `${value}` : value.toFixed(1);
154
+ }
155
+ function getCoverageColor(value) {
156
+ if (value >= 90)
157
+ return '0a7f3f';
158
+ if (value >= 75)
159
+ return '65a30d';
160
+ if (value >= 50)
161
+ return 'ca8a04';
162
+ return 'dc2626';
163
+ }
164
+ function hasScript(pkg, name) {
165
+ return typeof pkg.scripts?.[name] === 'string' && pkg.scripts[name].length > 0;
166
+ }
167
+ function scriptContains(pkg, name, command) {
168
+ return pkg.scripts?.[name]?.includes(command) ?? false;
169
+ }
170
+ function supportsBun(pkg, workflowFiles) {
171
+ if (pkg.packageManager?.startsWith('bun@') ?? false) {
172
+ return true;
173
+ }
174
+ if (Object.values(pkg.scripts ?? {}).some((script) => script.includes('bun') || script.includes('bunx'))) {
175
+ return true;
176
+ }
177
+ return workflowFiles.some((workflow) => workflow.includes('oven-sh/setup-bun') ||
178
+ workflow.includes('bun install') ||
179
+ /\bbun(?:x)?\b/.test(workflow));
180
+ }
181
+ function workflowRunsScript(workflowFiles, scriptName) {
182
+ const escaped = escapeRegExp(scriptName);
183
+ const patterns = [
184
+ new RegExp(`\\bbun\\s+run\\s+${escaped}(?=\\s|$)`, 'i'),
185
+ new RegExp(`\\bnpm\\s+run\\s+${escaped}(?=\\s|$)`, 'i'),
186
+ new RegExp(`\\bpnpm\\s+${escaped}(?=\\s|$)`, 'i'),
187
+ new RegExp(`\\byarn\\s+${escaped}(?=\\s|$)`, 'i'),
188
+ ];
189
+ return workflowFiles.some((workflow) => patterns.some((pattern) => pattern.test(workflow)));
190
+ }
191
+ function escapeRegExp(value) {
192
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&');
193
+ }
194
+ async function readWorkflowFiles(root) {
195
+ const workflowsPath = join(root, '.github', 'workflows');
196
+ const entries = await readDirectory(workflowsPath);
197
+ const contents = await Promise.all(entries
198
+ .filter((entry) => entry.endsWith('.yml') || entry.endsWith('.yaml'))
199
+ .sort((a, b) => a.localeCompare(b))
200
+ .map((entry) => readFile(join(workflowsPath, entry), 'utf-8')));
201
+ return contents;
202
+ }
203
+ async function readDirectory(path) {
204
+ try {
205
+ return await readdir(path);
206
+ }
207
+ catch (error) {
208
+ if (isNotFoundError(error)) {
209
+ return [];
210
+ }
211
+ throw new Error(`Unable to read workflow directory: ${path}`, { cause: error });
212
+ }
213
+ }
214
+ function isNotFoundError(error) {
215
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
216
+ }
217
+ async function readJsonFile(path) {
218
+ let content;
219
+ try {
220
+ content = await readFile(path, 'utf-8');
221
+ }
222
+ catch (error) {
223
+ if (isNotFoundError(error)) {
224
+ return null;
225
+ }
226
+ throw new Error(`Unable to read JSON metadata file: ${path}`, { cause: error });
227
+ }
228
+ try {
229
+ return JSON.parse(content);
230
+ }
231
+ catch (error) {
232
+ throw new Error(`Unable to parse JSON metadata file: ${path}`, { cause: error });
233
+ }
234
+ }
235
+ async function fileExists(path) {
236
+ try {
237
+ await access(path);
238
+ return true;
239
+ }
240
+ catch (error) {
241
+ if (isNotFoundError(error)) {
242
+ return false;
243
+ }
244
+ throw error;
245
+ }
246
+ }
247
+ function sortBadges(badges) {
248
+ return [...badges].sort((left, right) => {
249
+ const order = getBadgeOrder(left.id) - getBadgeOrder(right.id);
250
+ return order !== 0 ? order : left.id.localeCompare(right.id);
251
+ });
252
+ }
253
+ function getBadgeOrder(id) {
254
+ const index = BADGE_ORDER.indexOf(id);
255
+ return index === -1 ? BADGE_ORDER.length : index;
256
+ }
@@ -62,6 +62,12 @@ export interface AnalysisUsageCommand {
62
62
  name: string;
63
63
  command: string;
64
64
  }
65
+ export interface AnalysisBadge {
66
+ id: string;
67
+ label: string;
68
+ value: string;
69
+ color: string;
70
+ }
65
71
  export interface AnalysisModule {
66
72
  path: string;
67
73
  isEntrypoint: boolean;
@@ -79,6 +85,7 @@ export interface AnalysisResult {
79
85
  components: AnalysisComponent[];
80
86
  entrypoints: string[];
81
87
  modules: AnalysisModule[];
88
+ badges: AnalysisBadge[];
82
89
  usage: AnalysisUsage | null;
83
90
  config: {
84
91
  exportName: string;
@@ -2,6 +2,12 @@ import type { AnalysisUsage } from './types.js';
2
2
  export interface PackageJsonModel {
3
3
  name: string;
4
4
  description?: string;
5
+ version?: string;
6
+ license?: string;
7
+ packageManager?: string;
5
8
  bin?: string | Record<string, string>;
9
+ scripts?: Record<string, string>;
10
+ eslintConfig?: unknown;
11
+ prettier?: unknown;
6
12
  }
7
13
  export declare function createUsageFromPackageJson(pkg: PackageJsonModel): AnalysisUsage | null;
@@ -3,6 +3,12 @@ interface BuildModelInput {
3
3
  packageName: string;
4
4
  packageId: string;
5
5
  description: string | null;
6
+ badges: {
7
+ id: string;
8
+ label: string;
9
+ value: string;
10
+ color: string;
11
+ }[];
6
12
  exports: {
7
13
  name: string;
8
14
  description: string | null;
@@ -9,6 +9,12 @@ export function buildModel(analysis) {
9
9
  packageName: analysis.packageName,
10
10
  packageId: analysis.packageId,
11
11
  description: analysis.description,
12
+ badges: analysis.badges.map((badge) => ({
13
+ id: badge.id,
14
+ label: badge.label,
15
+ value: badge.value,
16
+ color: badge.color,
17
+ })),
12
18
  usage: analysis.usage !== null
13
19
  ? {
14
20
  packageName: analysis.usage.packageName,
@@ -5,6 +5,7 @@ export interface DocumentationModel {
5
5
  packageName: string;
6
6
  packageId: string;
7
7
  description: string | null;
8
+ badges: GeneratedBadge[];
8
9
  usage: UsageModel | null;
9
10
  config: ConfigModel | null;
10
11
  entrypoints: string[];
@@ -12,6 +13,12 @@ export interface DocumentationModel {
12
13
  exports: ExportModel[];
13
14
  components: ComponentModel[];
14
15
  }
16
+ export interface GeneratedBadge {
17
+ id: string;
18
+ label: string;
19
+ value: string;
20
+ color: string;
21
+ }
15
22
  export interface UsageModel {
16
23
  packageName: string;
17
24
  commands: UsageCommandModel[];
@@ -1,3 +1,4 @@
1
+ import { renderBadgeArtifacts } from './renderers/badges.js';
1
2
  import { renderDiagramArtifacts } from './renderers/diagrams.js';
2
3
  import { renderHtml } from './renderers/html.js';
3
4
  import { renderMarkdown } from './renderers/markdown.js';
@@ -5,6 +6,7 @@ import { renderMarkdown } from './renderers/markdown.js';
5
6
  * Renders the documentation model into README and artifact files.
6
7
  */
7
8
  export function render(model, options = {}) {
9
+ const badges = renderBadgeArtifacts(model);
8
10
  const diagrams = renderDiagramArtifacts(model);
9
11
  const outputDir = options.outputDir ?? 'paradox';
10
12
  const result = {
@@ -14,11 +16,13 @@ export function render(model, options = {}) {
14
16
  exportsJson: `${JSON.stringify(model.exports, null, 2)}\n`,
15
17
  paradoxJson: `${JSON.stringify(model, null, 2)}\n`,
16
18
  indexHtml: '',
19
+ badges,
17
20
  diagrams,
18
21
  };
19
22
  const context = {
20
23
  model,
21
24
  outputDir,
25
+ badges,
22
26
  diagrams,
23
27
  result,
24
28
  };
@@ -0,0 +1,6 @@
1
+ import type { DocumentationModel } from '../../model/types.js';
2
+ import type { BadgeArtifact } from '../types.js';
3
+ /***
4
+ * Renders deterministic local SVG badges for repository metadata.
5
+ */
6
+ export declare function renderBadgeArtifacts(model: DocumentationModel): BadgeArtifact[];
@@ -0,0 +1,35 @@
1
+ /***
2
+ * Renders deterministic local SVG badges for repository metadata.
3
+ */
4
+ export function renderBadgeArtifacts(model) {
5
+ return model.badges.map((badge) => ({
6
+ path: `badges/${badge.id}.svg`,
7
+ content: renderBadgeSvg(badge),
8
+ }));
9
+ }
10
+ function renderBadgeSvg(badge) {
11
+ const labelWidth = getTextWidth(badge.label);
12
+ const valueWidth = getTextWidth(badge.value);
13
+ const totalWidth = labelWidth + valueWidth;
14
+ return [
15
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="20" role="img" aria-label="${escapeXml(`${badge.label}: ${badge.value}`)}">`,
16
+ `<title>${escapeXml(`${badge.label}: ${badge.value}`)}</title>`,
17
+ `<rect width="${labelWidth}" height="20" fill="#374151"/>`,
18
+ `<rect x="${labelWidth}" width="${valueWidth}" height="20" fill="#${badge.color}"/>`,
19
+ `<text x="${labelWidth / 2}" y="14" fill="#ffffff" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11" text-anchor="middle">${escapeXml(badge.label)}</text>`,
20
+ `<text x="${labelWidth + valueWidth / 2}" y="14" fill="#ffffff" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11" text-anchor="middle">${escapeXml(badge.value)}</text>`,
21
+ '</svg>',
22
+ '',
23
+ ].join('\n');
24
+ }
25
+ function getTextWidth(value) {
26
+ return Math.max(38, value.length * 7 + 10);
27
+ }
28
+ function escapeXml(value) {
29
+ return value
30
+ .replaceAll('&', '&amp;')
31
+ .replaceAll('<', '&lt;')
32
+ .replaceAll('>', '&gt;')
33
+ .replaceAll('"', '&quot;')
34
+ .replaceAll("'", '&apos;');
35
+ }
@@ -2,4 +2,4 @@ import type { RenderContext } from '../types.js';
2
2
  /***
3
3
  * Renders markdown artifacts from the documentation model.
4
4
  */
5
- export declare function renderMarkdown({ diagrams, model, outputDir, }: RenderContext): Pick<RenderContext['result'], 'components' | 'exportsMarkdown' | 'readme'>;
5
+ export declare function renderMarkdown({ badges, diagrams, model, outputDir, }: RenderContext): Pick<RenderContext['result'], 'components' | 'exportsMarkdown' | 'readme'>;
@@ -1,15 +1,20 @@
1
1
  /***
2
2
  * Renders markdown artifacts from the documentation model.
3
3
  */
4
- export function renderMarkdown({ diagrams, model, outputDir, }) {
4
+ export function renderMarkdown({ badges, diagrams, model, outputDir, }) {
5
5
  return {
6
- readme: renderReadme(model, outputDir, diagrams),
6
+ readme: renderReadme(model, outputDir, badges, diagrams),
7
7
  exportsMarkdown: renderExports(model),
8
8
  components: renderComponents(model),
9
9
  };
10
10
  }
11
- function renderReadme(model, outputDir, diagrams) {
11
+ function renderReadme(model, outputDir, badges, diagrams) {
12
12
  const lines = [`# ${model.packageName}`, ''];
13
+ if (badges.length > 0) {
14
+ lines.push(badges
15
+ .map((badge) => `![${badgeLabel(model, badge.path)}](./${outputDir}/${badge.path})`)
16
+ .join(' '), '');
17
+ }
13
18
  if (model.description) {
14
19
  lines.push(model.description, '');
15
20
  }
@@ -139,3 +144,12 @@ function renderComponents(model) {
139
144
  function escapeTableCell(value) {
140
145
  return value.replaceAll('|', '\\|');
141
146
  }
147
+ function badgeLabel(model, badgePath) {
148
+ const fileName = badgePath.split('/').pop();
149
+ if (!fileName) {
150
+ return badgePath;
151
+ }
152
+ const id = fileName.replace(/\.svg$/, '');
153
+ const badge = model.badges.find((entry) => entry.id === id);
154
+ return badge ? `${badge.label}: ${badge.value}` : badgePath;
155
+ }
@@ -7,6 +7,10 @@ export interface DiagramArtifact {
7
7
  title: string;
8
8
  content: string;
9
9
  }
10
+ export interface BadgeArtifact {
11
+ path: string;
12
+ content: string;
13
+ }
10
14
  export interface RenderResult {
11
15
  readme: string;
12
16
  exportsMarkdown: string;
@@ -15,10 +19,12 @@ export interface RenderResult {
15
19
  paradoxJson: string;
16
20
  indexHtml: string;
17
21
  diagrams: DiagramArtifact[];
22
+ badges: BadgeArtifact[];
18
23
  }
19
24
  export interface RenderContext {
20
25
  model: DocumentationModel;
21
26
  outputDir: string;
22
27
  diagrams: DiagramArtifact[];
28
+ badges: BadgeArtifact[];
23
29
  result: RenderResult;
24
30
  }
@@ -13,6 +13,11 @@ export async function write(result, config, runtime) {
13
13
  await writeFile(join(outputRoot, 'exports.json'), result.exportsJson);
14
14
  await writeFile(join(outputRoot, 'paradox.json'), result.paradoxJson);
15
15
  await writeFile(join(outputRoot, 'index.html'), result.indexHtml);
16
+ for (const badge of result.badges) {
17
+ const badgePath = join(outputRoot, badge.path);
18
+ await mkdir(dirname(badgePath), { recursive: true });
19
+ await writeFile(badgePath, badge.content);
20
+ }
16
21
  for (const diagram of result.diagrams) {
17
22
  const diagramPath = join(outputRoot, diagram.path);
18
23
  await mkdir(dirname(diagramPath), { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/paradox",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Deterministic documentation generator for TypeScript packages.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {