@urmzd/github-insights 2.2.0 → 2.4.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/.githooks/.sr-hooks-hash +1 -0
- package/.githooks/commit-msg +0 -1
- package/.githooks/pre-commit +0 -1
- package/CHANGELOG.md +31 -0
- package/assets/insights/index.svg +1 -1
- package/assets/insights/metrics-constellation.svg +1 -1
- package/assets/insights/metrics-growth.svg +55 -0
- package/assets/insights/metrics-heatmap.svg +55 -0
- package/assets/insights/metrics-impact.svg +1 -1
- package/assets/insights/metrics-rhythm.svg +1 -1
- package/assets/insights/metrics-velocity.svg +1 -1
- package/examples/classic/README.md +16 -37
- package/examples/classic/index.svg +1 -1
- package/examples/classic/metrics-constellation.svg +1 -1
- package/examples/classic/metrics-growth.svg +55 -0
- package/examples/classic/metrics-heatmap.svg +55 -0
- package/examples/classic/metrics-impact.svg +1 -1
- package/examples/classic/metrics-rhythm.svg +1 -1
- package/examples/classic/metrics-velocity.svg +1 -1
- package/examples/ecosystem/README.md +51 -43
- package/examples/ecosystem/index.svg +1 -1
- package/examples/ecosystem/metrics-constellation.svg +1 -1
- package/examples/ecosystem/metrics-growth.svg +55 -0
- package/examples/ecosystem/metrics-heatmap.svg +55 -0
- package/examples/ecosystem/metrics-impact.svg +1 -1
- package/examples/ecosystem/metrics-rhythm.svg +1 -1
- package/examples/ecosystem/metrics-velocity.svg +1 -1
- package/examples/minimal/README.md +16 -37
- package/examples/minimal/index.svg +1 -1
- package/examples/minimal/metrics-constellation.svg +1 -1
- package/examples/minimal/metrics-growth.svg +55 -0
- package/examples/minimal/metrics-heatmap.svg +55 -0
- package/examples/minimal/metrics-impact.svg +1 -1
- package/examples/minimal/metrics-rhythm.svg +1 -1
- package/examples/minimal/metrics-velocity.svg +1 -1
- package/examples/modern/README.md +50 -74
- package/examples/modern/index.svg +1 -1
- package/examples/modern/metrics-constellation.svg +1 -1
- package/examples/modern/metrics-growth.svg +55 -0
- package/examples/modern/metrics-heatmap.svg +55 -0
- package/examples/modern/metrics-impact.svg +1 -1
- package/examples/modern/metrics-rhythm.svg +1 -1
- package/examples/modern/metrics-velocity.svg +1 -1
- package/package.json +1 -1
- package/src/components/contribution-heatmap.tsx +43 -0
- package/src/components/growth-arc.tsx +119 -0
- package/src/templates.test.ts +17 -42
- package/src/templates.ts +142 -57
- package/src/types.ts +6 -0
package/src/templates.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import { makeContributionData, makeUserProfile } from "./__fixtures__/repos.js";
|
|
3
3
|
import {
|
|
4
4
|
buildSocialBadges,
|
|
5
|
+
descriptiveAlt,
|
|
5
6
|
extractFirstName,
|
|
6
7
|
getTemplate,
|
|
7
8
|
shieldsBadgeLabel,
|
|
@@ -219,9 +220,11 @@ describe("classicTemplate", () => {
|
|
|
219
220
|
expect(output).toContain("A software developer in Austin, TX.");
|
|
220
221
|
});
|
|
221
222
|
|
|
222
|
-
it("includes SVG embeds", () => {
|
|
223
|
+
it("includes SVG embeds with descriptive alt text", () => {
|
|
223
224
|
const output = getTemplate("classic")(makeContext());
|
|
224
|
-
expect(output).toContain(
|
|
225
|
+
expect(output).toContain(
|
|
226
|
+
``,
|
|
227
|
+
);
|
|
225
228
|
});
|
|
226
229
|
|
|
227
230
|
it("includes social badges", () => {
|
|
@@ -246,7 +249,7 @@ describe("classicTemplate", () => {
|
|
|
246
249
|
expect(output).toContain("/ˈʊrm.zəd/");
|
|
247
250
|
});
|
|
248
251
|
|
|
249
|
-
it("
|
|
252
|
+
it("omits archived section", () => {
|
|
250
253
|
const output = getTemplate("classic")(
|
|
251
254
|
makeContext({
|
|
252
255
|
archivedProjects: [
|
|
@@ -259,12 +262,6 @@ describe("classicTemplate", () => {
|
|
|
259
262
|
],
|
|
260
263
|
}),
|
|
261
264
|
);
|
|
262
|
-
expect(output).toContain("## Archived");
|
|
263
|
-
expect(output).toContain("[old-project]");
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it("omits archived section when no archived projects", () => {
|
|
267
|
-
const output = getTemplate("classic")(makeContext());
|
|
268
265
|
expect(output).not.toContain("## Archived");
|
|
269
266
|
});
|
|
270
267
|
|
|
@@ -293,7 +290,7 @@ describe("modernTemplate", () => {
|
|
|
293
290
|
expect(output).toContain(
|
|
294
291
|
"### [resume-generator](https://github.com/urmzd/resume-generator)",
|
|
295
292
|
);
|
|
296
|
-
expect(output).toContain("
|
|
293
|
+
expect(output).toContain("Stars: 42");
|
|
297
294
|
});
|
|
298
295
|
|
|
299
296
|
it("includes maintained projects section with h3 headings", () => {
|
|
@@ -334,7 +331,7 @@ describe("modernTemplate", () => {
|
|
|
334
331
|
expect(output).toContain("assets/insights/metrics-constellation.svg");
|
|
335
332
|
});
|
|
336
333
|
|
|
337
|
-
it("
|
|
334
|
+
it("omits archived section", () => {
|
|
338
335
|
const output = getTemplate("modern")(
|
|
339
336
|
makeContext({
|
|
340
337
|
archivedProjects: [
|
|
@@ -347,8 +344,7 @@ describe("modernTemplate", () => {
|
|
|
347
344
|
],
|
|
348
345
|
}),
|
|
349
346
|
);
|
|
350
|
-
expect(output).toContain("## Archived");
|
|
351
|
-
expect(output).toContain("[legacy-lib]");
|
|
347
|
+
expect(output).not.toContain("## Archived");
|
|
352
348
|
});
|
|
353
349
|
|
|
354
350
|
it("includes social badges", () => {
|
|
@@ -375,9 +371,11 @@ describe("minimalTemplate", () => {
|
|
|
375
371
|
expect(output).toContain("A software developer in Austin, TX.");
|
|
376
372
|
});
|
|
377
373
|
|
|
378
|
-
it("includes SVG embeds", () => {
|
|
374
|
+
it("includes SVG embeds with descriptive alt text", () => {
|
|
379
375
|
const output = getTemplate("minimal")(makeContext());
|
|
380
|
-
expect(output).toContain(
|
|
376
|
+
expect(output).toContain(
|
|
377
|
+
``,
|
|
378
|
+
);
|
|
381
379
|
});
|
|
382
380
|
|
|
383
381
|
it("includes social badges", () => {
|
|
@@ -390,7 +388,7 @@ describe("minimalTemplate", () => {
|
|
|
390
388
|
expect(output).toContain("@urmzd/github-insights");
|
|
391
389
|
});
|
|
392
390
|
|
|
393
|
-
it("
|
|
391
|
+
it("omits archived section", () => {
|
|
394
392
|
const output = getTemplate("minimal")(
|
|
395
393
|
makeContext({
|
|
396
394
|
archivedProjects: [
|
|
@@ -403,8 +401,7 @@ describe("minimalTemplate", () => {
|
|
|
403
401
|
],
|
|
404
402
|
}),
|
|
405
403
|
);
|
|
406
|
-
expect(output).toContain("## Archived");
|
|
407
|
-
expect(output).toContain("[old-util]");
|
|
404
|
+
expect(output).not.toContain("## Archived");
|
|
408
405
|
});
|
|
409
406
|
|
|
410
407
|
it("ends with trailing newline", () => {
|
|
@@ -466,7 +463,7 @@ describe("ecosystemTemplate", () => {
|
|
|
466
463
|
expect(output).toContain("@urmzd/github-insights");
|
|
467
464
|
});
|
|
468
465
|
|
|
469
|
-
it("
|
|
466
|
+
it("omits archived section", () => {
|
|
470
467
|
const output = getTemplate("ecosystem")(
|
|
471
468
|
makeContext({
|
|
472
469
|
archivedProjects: [
|
|
@@ -478,31 +475,9 @@ describe("ecosystemTemplate", () => {
|
|
|
478
475
|
category: "Applications",
|
|
479
476
|
},
|
|
480
477
|
],
|
|
481
|
-
categorizedProjects: {
|
|
482
|
-
Applications: [
|
|
483
|
-
{
|
|
484
|
-
name: "resume-generator",
|
|
485
|
-
url: "https://github.com/urmzd/resume-generator",
|
|
486
|
-
description: "CLI tool for professional resumes",
|
|
487
|
-
stars: 42,
|
|
488
|
-
category: "Applications",
|
|
489
|
-
},
|
|
490
|
-
{
|
|
491
|
-
name: "old-app",
|
|
492
|
-
url: "https://github.com/urmzd/old-app",
|
|
493
|
-
description: "A retired application",
|
|
494
|
-
stars: 3,
|
|
495
|
-
category: "Applications",
|
|
496
|
-
},
|
|
497
|
-
],
|
|
498
|
-
},
|
|
499
478
|
}),
|
|
500
479
|
);
|
|
501
|
-
expect(output).toContain("### Archived");
|
|
502
|
-
expect(output).toContain("[old-app]");
|
|
503
|
-
// old-app should NOT appear in the Applications table
|
|
504
|
-
const appSection = output.split("### Applications")[1].split("###")[0];
|
|
505
|
-
expect(appSection).not.toContain("old-app");
|
|
480
|
+
expect(output).not.toContain("### Archived");
|
|
506
481
|
});
|
|
507
482
|
|
|
508
483
|
it("ends with trailing newline", () => {
|
package/src/templates.ts
CHANGED
|
@@ -10,7 +10,51 @@ import type {
|
|
|
10
10
|
|
|
11
11
|
function attribution(templateName: string): string {
|
|
12
12
|
const now = new Date().toISOString().split("T")[0];
|
|
13
|
-
return
|
|
13
|
+
return `<!-- section: footer -->\n<sub>Last generated on ${now} using [@urmzd/github-insights](https://github.com/urmzd/github-insights) · Template: \`${templateName}\`</sub>`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function frontmatter(ctx: TemplateContext): string {
|
|
17
|
+
const langs = ctx.languages.slice(0, 10).map((l) => l.name);
|
|
18
|
+
const lines = [
|
|
19
|
+
"<!-- ai-metadata",
|
|
20
|
+
`type: github-profile`,
|
|
21
|
+
`name: ${ctx.name}`,
|
|
22
|
+
`username: ${ctx.username}`,
|
|
23
|
+
...(ctx.title ? [`title: ${ctx.title}`] : []),
|
|
24
|
+
`languages: [${langs.join(", ")}]`,
|
|
25
|
+
`profile: https://github.com/${ctx.username}`,
|
|
26
|
+
"-->",
|
|
27
|
+
];
|
|
28
|
+
return lines.join("\n");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ALT_TEXT_MAP: Record<string, string> = {
|
|
32
|
+
"GitHub Metrics":
|
|
33
|
+
"Combined visualization of language velocity, contribution rhythm, project constellation, and open source impact for {name}",
|
|
34
|
+
"Language Velocity":
|
|
35
|
+
"Streamgraph of {name}'s programming language usage over the past year",
|
|
36
|
+
"Contribution Rhythm":
|
|
37
|
+
"Radar chart of {name}'s contribution patterns by day of week",
|
|
38
|
+
"Project Constellation":
|
|
39
|
+
"Map of {name}'s projects positioned by language ecosystem and complexity",
|
|
40
|
+
"Impact Trail":
|
|
41
|
+
"Bar chart of {name}'s open source contributions by repository star count",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function descriptiveAlt(label: string, name: string): string {
|
|
45
|
+
const template = ALT_TEXT_MAP[label];
|
|
46
|
+
if (template) return template.replace(/\{name\}/g, name);
|
|
47
|
+
return label;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function inlineMetadata(ctx: TemplateContext): string {
|
|
51
|
+
const parts: string[] = [];
|
|
52
|
+
if (ctx.title) parts.push(`**Role:** ${ctx.title}`);
|
|
53
|
+
const topLangs = ctx.languages.slice(0, 5).map((l) => l.name);
|
|
54
|
+
if (topLangs.length > 0)
|
|
55
|
+
parts.push(`**Top Languages:** ${topLangs.join(", ")}`);
|
|
56
|
+
if (parts.length === 0) return "";
|
|
57
|
+
return parts.join(" | ");
|
|
14
58
|
}
|
|
15
59
|
|
|
16
60
|
export function extractFirstName(fullName: string): string {
|
|
@@ -77,8 +121,9 @@ function renderProjectSection(title: string, projects: ProjectItem[]): string {
|
|
|
77
121
|
.map((p) => {
|
|
78
122
|
const desc = p.summary || p.description || "No description";
|
|
79
123
|
const meta: string[] = [];
|
|
80
|
-
if (p.stars > 0) meta.push(
|
|
81
|
-
if (p.languages?.length)
|
|
124
|
+
if (p.stars > 0) meta.push(`Stars: ${p.stars}`);
|
|
125
|
+
if (p.languages?.length)
|
|
126
|
+
meta.push(`Languages: ${p.languages.slice(0, 3).join(", ")}`);
|
|
82
127
|
const metaLine = meta.length > 0 ? `${meta.join(" \u00b7 ")}` : "";
|
|
83
128
|
return `### [${p.name}](${p.url})\n${desc}${metaLine ? `\n${metaLine}` : ""}`;
|
|
84
129
|
})
|
|
@@ -92,12 +137,16 @@ function renderProjectSection(title: string, projects: ProjectItem[]): string {
|
|
|
92
137
|
function renderProjectTable(title: string, projects: ProjectItem[]): string {
|
|
93
138
|
if (projects.length === 0) return "";
|
|
94
139
|
|
|
95
|
-
const header = `| Project | Description |\n
|
|
140
|
+
const header = `| Project | Description | Stars | Languages |\n|---------|-------------|-------|-----------|`;
|
|
96
141
|
const rows = projects
|
|
97
142
|
.map((p) => {
|
|
98
143
|
const desc = p.summary || p.description || "No description";
|
|
99
144
|
const safeDesc = desc.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
100
|
-
|
|
145
|
+
const stars = p.stars > 0 ? String(p.stars) : "-";
|
|
146
|
+
const langs = p.languages?.length
|
|
147
|
+
? p.languages.slice(0, 3).join(", ")
|
|
148
|
+
: "-";
|
|
149
|
+
return `| [${p.name}](${p.url}) | ${safeDesc} | ${stars} | ${langs} |`;
|
|
101
150
|
})
|
|
102
151
|
.join("\n");
|
|
103
152
|
|
|
@@ -109,6 +158,8 @@ function renderProjectTable(title: string, projects: ProjectItem[]): string {
|
|
|
109
158
|
function classicTemplate(ctx: TemplateContext): string {
|
|
110
159
|
const parts: string[] = [];
|
|
111
160
|
|
|
161
|
+
parts.push(frontmatter(ctx));
|
|
162
|
+
|
|
112
163
|
if (ctx.pronunciation) {
|
|
113
164
|
parts.push(`# ${ctx.name} <sub><i>(${ctx.pronunciation})</i></sub>`);
|
|
114
165
|
} else {
|
|
@@ -123,16 +174,18 @@ function classicTemplate(ctx: TemplateContext): string {
|
|
|
123
174
|
parts.push(ctx.preamble);
|
|
124
175
|
}
|
|
125
176
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
177
|
+
const meta = inlineMetadata(ctx);
|
|
178
|
+
if (meta) parts.push(meta);
|
|
129
179
|
|
|
130
|
-
|
|
131
|
-
parts.push(
|
|
180
|
+
if (ctx.socialBadges) {
|
|
181
|
+
parts.push(`<!-- section: social -->\n${ctx.socialBadges}`);
|
|
132
182
|
}
|
|
133
183
|
|
|
134
|
-
if (ctx.
|
|
135
|
-
|
|
184
|
+
if (ctx.svgs.length > 0) {
|
|
185
|
+
const svgLines = ctx.svgs
|
|
186
|
+
.map((svg) => ``)
|
|
187
|
+
.join("\n");
|
|
188
|
+
parts.push(`<!-- section: visualizations -->\n${svgLines}`);
|
|
136
189
|
}
|
|
137
190
|
|
|
138
191
|
if (ctx.bio) {
|
|
@@ -149,66 +202,82 @@ function classicTemplate(ctx: TemplateContext): string {
|
|
|
149
202
|
function modernTemplate(ctx: TemplateContext): string {
|
|
150
203
|
const parts: string[] = [];
|
|
151
204
|
|
|
205
|
+
parts.push(frontmatter(ctx));
|
|
206
|
+
|
|
152
207
|
parts.push(`# Hi, I'm ${ctx.firstName} 👋`);
|
|
153
208
|
|
|
154
209
|
if (ctx.preamble) {
|
|
155
210
|
parts.push(ctx.preamble);
|
|
156
211
|
}
|
|
157
212
|
|
|
213
|
+
const meta = inlineMetadata(ctx);
|
|
214
|
+
if (meta) parts.push(meta);
|
|
215
|
+
|
|
158
216
|
if (ctx.socialBadges) {
|
|
159
|
-
parts.push(ctx.socialBadges);
|
|
217
|
+
parts.push(`<!-- section: social -->\n${ctx.socialBadges}`);
|
|
160
218
|
}
|
|
161
219
|
|
|
220
|
+
// Projects
|
|
221
|
+
const projectSections: string[] = [];
|
|
162
222
|
const activeSection = renderProjectSection(
|
|
163
223
|
"Active Projects",
|
|
164
224
|
ctx.activeProjects,
|
|
165
225
|
);
|
|
166
|
-
if (activeSection)
|
|
226
|
+
if (activeSection) projectSections.push(activeSection);
|
|
167
227
|
|
|
168
228
|
const maintainedSection = renderProjectSection(
|
|
169
229
|
"Maintained Projects",
|
|
170
230
|
ctx.maintainedProjects,
|
|
171
231
|
);
|
|
172
|
-
if (maintainedSection)
|
|
232
|
+
if (maintainedSection) projectSections.push(maintainedSection);
|
|
173
233
|
|
|
174
234
|
const inactiveSection = renderProjectSection(
|
|
175
235
|
"Inactive Projects",
|
|
176
236
|
ctx.inactiveProjects,
|
|
177
237
|
);
|
|
178
|
-
if (inactiveSection)
|
|
238
|
+
if (inactiveSection) projectSections.push(inactiveSection);
|
|
179
239
|
|
|
180
|
-
|
|
181
|
-
"
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
240
|
+
if (projectSections.length > 0) {
|
|
241
|
+
parts.push(`<!-- section: projects -->\n${projectSections.join("\n\n")}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Visualizations
|
|
245
|
+
const vizParts: string[] = [];
|
|
185
246
|
|
|
186
247
|
// Constellation
|
|
187
248
|
if (ctx.sectionSvgs.constellation) {
|
|
188
|
-
|
|
189
|
-
`## Project Map\n\n`,
|
|
249
|
+
vizParts.push(
|
|
250
|
+
`## Project Map\n\n`,
|
|
190
251
|
);
|
|
191
252
|
}
|
|
192
253
|
|
|
193
254
|
// GitHub Stats section: rhythm + velocity
|
|
194
255
|
const statsImages: string[] = [];
|
|
195
256
|
if (ctx.sectionSvgs.velocity) {
|
|
196
|
-
statsImages.push(
|
|
257
|
+
statsImages.push(
|
|
258
|
+
``,
|
|
259
|
+
);
|
|
197
260
|
}
|
|
198
261
|
if (ctx.sectionSvgs.rhythm) {
|
|
199
|
-
statsImages.push(
|
|
262
|
+
statsImages.push(
|
|
263
|
+
``,
|
|
264
|
+
);
|
|
200
265
|
}
|
|
201
266
|
if (statsImages.length > 0) {
|
|
202
|
-
|
|
267
|
+
vizParts.push(`## GitHub Stats\n\n${statsImages.join("\n")}`);
|
|
203
268
|
}
|
|
204
269
|
|
|
205
270
|
// Impact
|
|
206
271
|
if (ctx.sectionSvgs.impact) {
|
|
207
|
-
|
|
208
|
-
`## Open Source Impact\n\n`,
|
|
272
|
+
vizParts.push(
|
|
273
|
+
`## Open Source Impact\n\n`,
|
|
209
274
|
);
|
|
210
275
|
}
|
|
211
276
|
|
|
277
|
+
if (vizParts.length > 0) {
|
|
278
|
+
parts.push(`<!-- section: visualizations -->\n${vizParts.join("\n\n")}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
212
281
|
parts.push(attribution(ctx.templateName));
|
|
213
282
|
|
|
214
283
|
return `${parts.join("\n\n")}\n`;
|
|
@@ -219,22 +288,26 @@ function modernTemplate(ctx: TemplateContext): string {
|
|
|
219
288
|
function minimalTemplate(ctx: TemplateContext): string {
|
|
220
289
|
const parts: string[] = [];
|
|
221
290
|
|
|
291
|
+
parts.push(frontmatter(ctx));
|
|
292
|
+
|
|
222
293
|
parts.push(`# ${ctx.firstName}`);
|
|
223
294
|
|
|
224
295
|
if (ctx.preamble) {
|
|
225
296
|
parts.push(ctx.preamble);
|
|
226
297
|
}
|
|
227
298
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
299
|
+
const meta = inlineMetadata(ctx);
|
|
300
|
+
if (meta) parts.push(meta);
|
|
231
301
|
|
|
232
|
-
|
|
233
|
-
parts.push(
|
|
302
|
+
if (ctx.socialBadges) {
|
|
303
|
+
parts.push(`<!-- section: social -->\n${ctx.socialBadges}`);
|
|
234
304
|
}
|
|
235
305
|
|
|
236
|
-
if (ctx.
|
|
237
|
-
|
|
306
|
+
if (ctx.svgs.length > 0) {
|
|
307
|
+
const svgLines = ctx.svgs
|
|
308
|
+
.map((svg) => ``)
|
|
309
|
+
.join("\n");
|
|
310
|
+
parts.push(`<!-- section: visualizations -->\n${svgLines}`);
|
|
238
311
|
}
|
|
239
312
|
|
|
240
313
|
parts.push(attribution(ctx.templateName));
|
|
@@ -254,70 +327,82 @@ const CATEGORY_ORDER = [
|
|
|
254
327
|
function ecosystemTemplate(ctx: TemplateContext): string {
|
|
255
328
|
const parts: string[] = [];
|
|
256
329
|
|
|
330
|
+
parts.push(frontmatter(ctx));
|
|
331
|
+
|
|
257
332
|
parts.push(`# Hi, I'm ${ctx.firstName} 👋`);
|
|
258
333
|
|
|
259
334
|
if (ctx.preamble) {
|
|
260
335
|
parts.push(ctx.preamble);
|
|
261
336
|
}
|
|
262
337
|
|
|
338
|
+
const meta = inlineMetadata(ctx);
|
|
339
|
+
if (meta) parts.push(meta);
|
|
340
|
+
|
|
263
341
|
if (ctx.socialBadges) {
|
|
264
|
-
parts.push(ctx.socialBadges);
|
|
342
|
+
parts.push(`<!-- section: social -->\n${ctx.socialBadges}`);
|
|
265
343
|
}
|
|
266
344
|
|
|
267
|
-
//
|
|
268
|
-
const
|
|
345
|
+
// Projects
|
|
346
|
+
const projectParts: string[] = [];
|
|
269
347
|
|
|
270
|
-
// Render project tables grouped by category
|
|
348
|
+
// Render project tables grouped by category
|
|
271
349
|
for (const category of CATEGORY_ORDER) {
|
|
272
|
-
const projects = ctx.categorizedProjects[category]
|
|
273
|
-
(p) => !archivedNames.has(p.name),
|
|
274
|
-
);
|
|
350
|
+
const projects = ctx.categorizedProjects[category];
|
|
275
351
|
if (projects && projects.length > 0) {
|
|
276
|
-
|
|
352
|
+
projectParts.push(renderProjectTable(category, projects));
|
|
277
353
|
}
|
|
278
354
|
}
|
|
279
355
|
|
|
280
|
-
// Render any uncategorized projects that don't match known categories
|
|
356
|
+
// Render any uncategorized projects that don't match known categories
|
|
281
357
|
for (const [category, projects] of Object.entries(ctx.categorizedProjects)) {
|
|
282
358
|
if (!CATEGORY_ORDER.includes(category)) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
parts.push(renderProjectTable(category, nonArchived));
|
|
359
|
+
if (projects.length > 0) {
|
|
360
|
+
projectParts.push(renderProjectTable(category, projects));
|
|
286
361
|
}
|
|
287
362
|
}
|
|
288
363
|
}
|
|
289
364
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
parts.push(renderProjectTable("Archived", ctx.archivedProjects));
|
|
365
|
+
if (projectParts.length > 0) {
|
|
366
|
+
parts.push(`<!-- section: projects -->\n${projectParts.join("\n\n")}`);
|
|
293
367
|
}
|
|
294
368
|
|
|
369
|
+
// Visualizations
|
|
370
|
+
const vizParts: string[] = [];
|
|
371
|
+
|
|
295
372
|
// Constellation
|
|
296
373
|
if (ctx.sectionSvgs.constellation) {
|
|
297
|
-
|
|
298
|
-
`## Project Map\n\n`,
|
|
374
|
+
vizParts.push(
|
|
375
|
+
`## Project Map\n\n`,
|
|
299
376
|
);
|
|
300
377
|
}
|
|
301
378
|
|
|
302
379
|
// GitHub Stats section: velocity + rhythm
|
|
303
380
|
const statsImages: string[] = [];
|
|
304
381
|
if (ctx.sectionSvgs.velocity) {
|
|
305
|
-
statsImages.push(
|
|
382
|
+
statsImages.push(
|
|
383
|
+
``,
|
|
384
|
+
);
|
|
306
385
|
}
|
|
307
386
|
if (ctx.sectionSvgs.rhythm) {
|
|
308
|
-
statsImages.push(
|
|
387
|
+
statsImages.push(
|
|
388
|
+
``,
|
|
389
|
+
);
|
|
309
390
|
}
|
|
310
391
|
if (statsImages.length > 0) {
|
|
311
|
-
|
|
392
|
+
vizParts.push(`## GitHub Stats\n\n${statsImages.join("\n")}`);
|
|
312
393
|
}
|
|
313
394
|
|
|
314
395
|
// Impact
|
|
315
396
|
if (ctx.sectionSvgs.impact) {
|
|
316
|
-
|
|
317
|
-
`## Open Source Impact\n\n`,
|
|
397
|
+
vizParts.push(
|
|
398
|
+
`## Open Source Impact\n\n`,
|
|
318
399
|
);
|
|
319
400
|
}
|
|
320
401
|
|
|
402
|
+
if (vizParts.length > 0) {
|
|
403
|
+
parts.push(`<!-- section: visualizations -->\n${vizParts.join("\n\n")}`);
|
|
404
|
+
}
|
|
405
|
+
|
|
321
406
|
parts.push(attribution(ctx.templateName));
|
|
322
407
|
|
|
323
408
|
return `${parts.join("\n\n")}\n`;
|
package/src/types.ts
CHANGED
|
@@ -166,6 +166,12 @@ export interface ContributionRhythm {
|
|
|
166
166
|
|
|
167
167
|
// ── Project constellation ─────────────────────────────────────────────────
|
|
168
168
|
|
|
169
|
+
export interface GrowthArcPoint {
|
|
170
|
+
label: string;
|
|
171
|
+
avgComplexity: number;
|
|
172
|
+
repoCount: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
169
175
|
export interface ConstellationNode {
|
|
170
176
|
name: string;
|
|
171
177
|
url: string;
|