@kensio/colophon 0.2.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.
Files changed (51) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +198 -0
  3. package/dist/background.d.ts +8 -0
  4. package/dist/background.d.ts.map +1 -0
  5. package/dist/background.js +22 -0
  6. package/dist/background.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +74 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +17 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +64 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/content/index.d.ts +39 -0
  16. package/dist/content/index.d.ts.map +1 -0
  17. package/dist/content/index.js +99 -0
  18. package/dist/content/index.js.map +1 -0
  19. package/dist/generate.d.ts +42 -0
  20. package/dist/generate.d.ts.map +1 -0
  21. package/dist/generate.js +56 -0
  22. package/dist/generate.js.map +1 -0
  23. package/dist/index.d.ts +11 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +8 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/render.d.ts +17 -0
  28. package/dist/render.d.ts.map +1 -0
  29. package/dist/render.js +52 -0
  30. package/dist/render.js.map +1 -0
  31. package/dist/templates/banner.d.ts +9 -0
  32. package/dist/templates/banner.d.ts.map +1 -0
  33. package/dist/templates/banner.js +125 -0
  34. package/dist/templates/banner.js.map +1 -0
  35. package/dist/templates/card.d.ts +7 -0
  36. package/dist/templates/card.d.ts.map +1 -0
  37. package/dist/templates/card.js +82 -0
  38. package/dist/templates/card.js.map +1 -0
  39. package/dist/templates/index.d.ts +9 -0
  40. package/dist/templates/index.d.ts.map +1 -0
  41. package/dist/templates/index.js +13 -0
  42. package/dist/templates/index.js.map +1 -0
  43. package/dist/text.d.ts +56 -0
  44. package/dist/text.d.ts.map +1 -0
  45. package/dist/text.js +78 -0
  46. package/dist/text.js.map +1 -0
  47. package/dist/types.d.ts +129 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +2 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +87 -0
@@ -0,0 +1,99 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import matter from "gray-matter";
4
+ const defaultPropsKey = "meta_img_props";
5
+ const defaultTemplateField = "template";
6
+ const defaultExtensions = [".md", ".markdown"];
7
+ function coerceString(value) {
8
+ if (typeof value === "string") {
9
+ return value;
10
+ }
11
+ if (typeof value === "number" ||
12
+ typeof value === "boolean" ||
13
+ typeof value === "bigint") {
14
+ return String(value);
15
+ }
16
+ return undefined;
17
+ }
18
+ function isRecord(value) {
19
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20
+ }
21
+ /**
22
+ * Extract {@link MetaImageProps} from a parsed frontmatter object, or return
23
+ * `undefined` if this file should be skipped (no props object, or no usable
24
+ * template/title). Pure — no filesystem access.
25
+ */
26
+ export function extractProps(frontmatter, options = {}) {
27
+ const propsKey = options.propsKey ?? defaultPropsKey;
28
+ const templateField = options.templateField ?? defaultTemplateField;
29
+ const record = frontmatter[propsKey];
30
+ if (!isRecord(record)) {
31
+ return undefined;
32
+ }
33
+ const templateRaw = record[templateField];
34
+ const template = typeof templateRaw === "string" ? templateRaw : options.defaultTemplate;
35
+ if (template === undefined) {
36
+ return undefined;
37
+ }
38
+ const titleRaw = record["title"];
39
+ if (typeof titleRaw !== "string") {
40
+ return undefined;
41
+ }
42
+ const subtitle = coerceString(record["subtitle"]);
43
+ const version = coerceString(record["version"]);
44
+ const extras = {};
45
+ for (const [key, value] of Object.entries(record)) {
46
+ if (key !== templateField &&
47
+ key !== "template" &&
48
+ key !== "title" &&
49
+ key !== "subtitle" &&
50
+ key !== "version") {
51
+ extras[key] = value;
52
+ }
53
+ }
54
+ return {
55
+ ...extras,
56
+ template,
57
+ title: titleRaw,
58
+ ...(subtitle !== undefined && { subtitle }),
59
+ ...(version !== undefined && { version }),
60
+ };
61
+ }
62
+ async function collectContentFiles(dir, extensions) {
63
+ const entries = await readdir(dir, { withFileTypes: true });
64
+ const nested = await Promise.all(entries.map(async (entry) => {
65
+ const entryPath = path.join(dir, entry.name);
66
+ if (entry.isDirectory()) {
67
+ return collectContentFiles(entryPath, extensions);
68
+ }
69
+ if (entry.isFile() &&
70
+ extensions.some((extension) => entry.name.endsWith(extension))) {
71
+ return [entryPath];
72
+ }
73
+ return [];
74
+ }));
75
+ return nested.flat();
76
+ }
77
+ /**
78
+ * Walk a content tree, read each file's frontmatter with `gray-matter`, and
79
+ * return the files that declare image props. This is the host-project concern
80
+ * — finding content and reading frontmatter — kept separate from rendering.
81
+ */
82
+ export async function walkContent(options) {
83
+ const extensions = options.extensions ?? defaultExtensions;
84
+ const filePaths = await collectContentFiles(options.dir, extensions);
85
+ const files = await Promise.all(filePaths.map(async (filePath) => {
86
+ const raw = await readFile(filePath, "utf8");
87
+ const props = extractProps(matter(raw).data, options);
88
+ if (props === undefined) {
89
+ return;
90
+ }
91
+ return {
92
+ contentPath: path.relative(options.dir, filePath),
93
+ absolutePath: filePath,
94
+ props,
95
+ };
96
+ }));
97
+ return files.filter((file) => file !== undefined);
98
+ }
99
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,MAAM,MAAM,aAAa,CAAC;AAIjC,MAAM,eAAe,GAAG,gBAAgB,CAAC;AACzC,MAAM,oBAAoB,GAAG,UAAU,CAAC;AACxC,MAAM,iBAAiB,GAAsB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;AAElE,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,OAAO,KAAK,KAAK,SAAS;QAC1B,OAAO,KAAK,KAAK,QAAQ,EACzB,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AA6BD,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,WAAoC,EACpC,OAAO,GAGH,EAAE;IAEN,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,eAAe,CAAC;IACrD,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,oBAAoB,CAAC;IAEpE,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IAErC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;IAC1C,MAAM,QAAQ,GACZ,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC;IAE1E,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAEjC,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhD,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IACE,GAAG,KAAK,aAAa;YACrB,GAAG,KAAK,UAAU;YAClB,GAAG,KAAK,OAAO;YACf,GAAG,KAAK,UAAU;YAClB,GAAG,KAAK,SAAS,EACjB,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;IACH,CAAC;IAED,OAAO;QACL,GAAG,MAAM;QACT,QAAQ;QACR,KAAK,EAAE,QAAQ;QACf,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC3C,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,mBAAmB,CAChC,GAAW,EACX,UAA6B;IAE7B,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAE5D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAE7C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,OAAO,mBAAmB,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;QACpD,CAAC;QAED,IACE,KAAK,CAAC,MAAM,EAAE;YACd,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAC9D,CAAC;YACD,OAAO,CAAC,SAAS,CAAC,CAAC;QACrB,CAAC;QAED,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAoB;IAEpB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,iBAAiB,CAAC;IAC3D,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAErE,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QAC/B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEtD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QAED,OAAO;YACL,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC;YACjD,YAAY,EAAE,QAAQ;YACtB,KAAK;SACN,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAuB,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;AACzE,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { ContentFile, WalkOptions } from "./content/index.js";
2
+ import type { ColophonConfig, Dimensions } from "./types.js";
3
+ /**
4
+ * Outcome for one generated (or skipped) image.
5
+ */
6
+ export interface GeneratedImage {
7
+ readonly contentPath: string;
8
+ readonly dimensions: Dimensions;
9
+ readonly outputPath: string;
10
+ /** True when an existing file was left in place (no `overwrite`). */
11
+ readonly skipped: boolean;
12
+ }
13
+ /**
14
+ * Options for {@link generate}.
15
+ */
16
+ export interface GenerateOptions {
17
+ /** Root content directory to walk. */
18
+ readonly contentDir: string;
19
+ readonly config?: ColophonConfig;
20
+ /** Extra walk options (props key, template field, extensions, …). */
21
+ readonly walk?: Omit<WalkOptions, "dir">;
22
+ /** Override where each image is written. */
23
+ readonly outputPath?: (file: ContentFile, dimensions: Dimensions, index: number) => string;
24
+ /** Re-render even when the output file already exists. Default `false`. */
25
+ readonly overwrite?: boolean;
26
+ /** Called after each image is written or skipped. */
27
+ readonly onResult?: (result: GeneratedImage) => void;
28
+ }
29
+ /**
30
+ * Default output path: alongside the content file, named after the file (or
31
+ * its parent directory when the file is `index.*`). The first (square) size
32
+ * keeps the bare name; additional sizes get a `-WxH` suffix.
33
+ */
34
+ export declare function defaultOutputPath(file: ContentFile, dimensions: Dimensions, index: number): string;
35
+ /**
36
+ * Walk a content tree and render meta images for every file that declares
37
+ * props, writing PNGs to disk. Existing files are skipped unless `overwrite`
38
+ * is set (matching the original script's behaviour), and skipped sizes are
39
+ * never rendered.
40
+ */
41
+ export declare function generate(options: GenerateOptions): Promise<GeneratedImage[]>;
42
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,qEAAqE;IACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACzC,4CAA4C;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,CACpB,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,MAAM,KACV,MAAM,CAAC;IACZ,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CACtD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,MAAM,GACZ,MAAM,CAWR;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,EAAE,CAAC,CAmC3B"}
@@ -0,0 +1,56 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { resolveConfig } from "./config.js";
5
+ import { walkContent } from "./content/index.js";
6
+ import { buildSvg, renderSvgToPng } from "./render.js";
7
+ /**
8
+ * Default output path: alongside the content file, named after the file (or
9
+ * its parent directory when the file is `index.*`). The first (square) size
10
+ * keeps the bare name; additional sizes get a `-WxH` suffix.
11
+ */
12
+ export function defaultOutputPath(file, dimensions, index) {
13
+ const directory = path.dirname(file.absolutePath);
14
+ const extension = path.extname(file.contentPath);
15
+ const base = path.basename(file.contentPath, extension);
16
+ const stem = base === "index" ? path.basename(directory) : base;
17
+ const suffix = index === 0
18
+ ? ""
19
+ : `-${String(dimensions.width)}x${String(dimensions.height)}`;
20
+ return path.join(directory, `${stem}${suffix}.png`);
21
+ }
22
+ /**
23
+ * Walk a content tree and render meta images for every file that declares
24
+ * props, writing PNGs to disk. Existing files are skipped unless `overwrite`
25
+ * is set (matching the original script's behaviour), and skipped sizes are
26
+ * never rendered.
27
+ */
28
+ export async function generate(options) {
29
+ const resolved = resolveConfig(options.config);
30
+ const toOutputPath = options.outputPath ?? defaultOutputPath;
31
+ const files = await walkContent({ dir: options.contentDir, ...options.walk });
32
+ const jobs = files.flatMap((file) => resolved.dimensions.map((dimensions, index) => ({
33
+ file,
34
+ dimensions,
35
+ index,
36
+ })));
37
+ return Promise.all(jobs.map(async ({ file, dimensions, index }) => {
38
+ const outputPath = toOutputPath(file, dimensions, index);
39
+ const isSkipped = existsSync(outputPath) && options.overwrite !== true;
40
+ if (!isSkipped) {
41
+ const svg = buildSvg(file.props, resolved, dimensions);
42
+ const png = await renderSvgToPng(svg, dimensions);
43
+ await mkdir(path.dirname(outputPath), { recursive: true });
44
+ await writeFile(outputPath, png);
45
+ }
46
+ const result = {
47
+ contentPath: file.contentPath,
48
+ dimensions,
49
+ outputPath,
50
+ skipped: isSkipped,
51
+ };
52
+ options.onResult?.(result);
53
+ return result;
54
+ }));
55
+ }
56
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export type { Background, Badge, BrandColors, ColophonConfig, Dimensions, GradientStop, MetaImageProps, RenderedMetaImage, ResolvedConfig, Template, TemplateContext, } from "./types.js";
2
+ export { DEFAULT_COLORS, DEFAULT_DIMENSIONS, DEFAULT_FONT_FAMILY, defineConfig, resolveConfig, } from "./config.js";
3
+ export { backgroundSvg } from "./background.js";
4
+ export { buildSvg, renderMetaImages, renderSvgToPng } from "./render.js";
5
+ export { bannerTemplate, builtinTemplates, cardTemplate, } from "./templates/index.js";
6
+ export { defaultOutputPath, generate } from "./generate.js";
7
+ export type { GeneratedImage, GenerateOptions } from "./generate.js";
8
+ export { escapeXml, wrapText } from "./text.js";
9
+ export { extractProps, walkContent } from "./content/index.js";
10
+ export type { ContentFile, WalkOptions } from "./content/index.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,KAAK,EACL,WAAW,EACX,cAAc,EACd,UAAU,EACV,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,aAAa,GACd,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,YAAY,GACb,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/D,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { DEFAULT_COLORS, DEFAULT_DIMENSIONS, DEFAULT_FONT_FAMILY, defineConfig, resolveConfig, } from "./config.js";
2
+ export { backgroundSvg } from "./background.js";
3
+ export { buildSvg, renderMetaImages, renderSvgToPng } from "./render.js";
4
+ export { bannerTemplate, builtinTemplates, cardTemplate, } from "./templates/index.js";
5
+ export { defaultOutputPath, generate } from "./generate.js";
6
+ export { escapeXml, wrapText } from "./text.js";
7
+ export { extractProps, walkContent } from "./content/index.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,aAAa,GACd,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,YAAY,GACb,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG5D,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { ColophonConfig, Dimensions, MetaImageProps, RenderedMetaImage, ResolvedConfig } from "./types.js";
2
+ /**
3
+ * Build the complete SVG document for one image: the enclosing `<svg>` root,
4
+ * the config-driven background, and the template's foreground content.
5
+ */
6
+ export declare function buildSvg(props: MetaImageProps, config: ResolvedConfig, dimensions: Dimensions): string;
7
+ /**
8
+ * Rasterise an SVG string to a PNG buffer at the given dimensions.
9
+ */
10
+ export declare function renderSvgToPng(svg: string, dimensions: Dimensions): Promise<Buffer>;
11
+ /**
12
+ * Render an image for every configured dimension from a single set of props.
13
+ * This is the reusable core: it takes props and config and returns rendered
14
+ * bytes, with no filesystem or content-discovery concerns.
15
+ */
16
+ export declare function renderMetaImages(props: MetaImageProps, config?: ColophonConfig): Promise<RenderedMetaImage[]>;
17
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,cAAc,EAEf,MAAM,YAAY,CAAC;AAmBpB;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE,UAAU,GACrB,MAAM,CAWR;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,cAAc,EACrB,MAAM,CAAC,EAAE,cAAc,GACtB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAa9B"}
package/dist/render.js ADDED
@@ -0,0 +1,52 @@
1
+ import sharp from "sharp";
2
+ import { backgroundSvg } from "./background.js";
3
+ import { resolveConfig } from "./config.js";
4
+ const backgroundId = "colophon-bg";
5
+ function selectTemplate(config, name) {
6
+ const template = config.templates[name];
7
+ if (template === undefined) {
8
+ const available = Object.keys(config.templates)
9
+ .toSorted((a, b) => a.localeCompare(b))
10
+ .join(", ");
11
+ throw new Error(`Unknown template "${name}". Available templates: ${available}.`);
12
+ }
13
+ return template;
14
+ }
15
+ /**
16
+ * Build the complete SVG document for one image: the enclosing `<svg>` root,
17
+ * the config-driven background, and the template's foreground content.
18
+ */
19
+ export function buildSvg(props, config, dimensions) {
20
+ const template = selectTemplate(config, props.template);
21
+ const background = backgroundSvg(config.background, dimensions, backgroundId);
22
+ const body = template.render({ props, config, dimensions });
23
+ const { width, height } = dimensions;
24
+ return (`<svg width="${String(width)}" height="${String(height)}"` +
25
+ ` viewBox="0 0 ${String(width)} ${String(height)}"` +
26
+ ` xmlns="http://www.w3.org/2000/svg">${background}${body}</svg>`);
27
+ }
28
+ /**
29
+ * Rasterise an SVG string to a PNG buffer at the given dimensions.
30
+ */
31
+ export async function renderSvgToPng(svg, dimensions) {
32
+ return sharp(Buffer.from(svg))
33
+ .resize(dimensions.width, dimensions.height, { fit: "fill" })
34
+ .png()
35
+ .toBuffer();
36
+ }
37
+ /**
38
+ * Render an image for every configured dimension from a single set of props.
39
+ * This is the reusable core: it takes props and config and returns rendered
40
+ * bytes, with no filesystem or content-discovery concerns.
41
+ */
42
+ export async function renderMetaImages(props, config) {
43
+ const resolved = resolveConfig(config);
44
+ // Validate the template once up front so a bad name fails fast.
45
+ selectTemplate(resolved, props.template);
46
+ return Promise.all(resolved.dimensions.map(async (dimensions) => {
47
+ const svg = buildSvg(props, resolved, dimensions);
48
+ const png = await renderSvgToPng(svg, dimensions);
49
+ return { dimensions, svg, png };
50
+ }));
51
+ }
52
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAU5C,MAAM,YAAY,GAAG,aAAa,CAAC;AAEnC,SAAS,cAAc,CAAC,MAAsB,EAAE,IAAY;IAC1D,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAExC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;aAC5C,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;aACtC,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,IAAI,KAAK,CACb,qBAAqB,IAAI,2BAA2B,SAAS,GAAG,CACjE,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CACtB,KAAqB,EACrB,MAAsB,EACtB,UAAsB;IAEtB,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5D,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;IAErC,OAAO,CACL,eAAe,MAAM,CAAC,KAAK,CAAC,aAAa,MAAM,CAAC,MAAM,CAAC,GAAG;QAC1D,iBAAiB,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG;QACnD,uCAAuC,UAAU,GAAG,IAAI,QAAQ,CACjE,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAW,EACX,UAAsB;IAEtB,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;SAC3B,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;SAC5D,GAAG,EAAE;SACL,QAAQ,EAAE,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAqB,EACrB,MAAuB;IAEvB,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAEvC,gEAAgE;IAChE,cAAc,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAEzC,OAAO,OAAO,CAAC,GAAG,CAChB,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE;QAC3C,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QAClD,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAClC,CAAC,CAAC,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { Template } from "../types.js";
2
+ /**
3
+ * Generalised "banner" template: a left-aligned title with an optional
4
+ * version and wrapped subtitle, an optional corner badge, and an optional
5
+ * footer. This is the configurable descendant of the original `npm_package`
6
+ * layout.
7
+ */
8
+ export declare const bannerTemplate: Template;
9
+ //# sourceMappingURL=banner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banner.d.ts","sourceRoot":"","sources":["../../src/templates/banner.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAS,QAAQ,EAAmB,MAAM,aAAa,CAAC;AA0DpE;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,QAwH5B,CAAC"}
@@ -0,0 +1,125 @@
1
+ import { estimateCharsPerLine, layoutStack, textElement, wrapText, } from "../text.js";
2
+ const maxTitleLines = 3;
3
+ const maxSubtitleLines = 4;
4
+ function optionalString(value) {
5
+ if (typeof value === "string") {
6
+ return value;
7
+ }
8
+ if (typeof value === "number" ||
9
+ typeof value === "boolean" ||
10
+ typeof value === "bigint") {
11
+ return String(value);
12
+ }
13
+ return undefined;
14
+ }
15
+ function renderBadge(badge, fontFamily, brand, pad, height) {
16
+ const fontSize = Math.round(height * 0.6);
17
+ const padX = Math.round(height * 0.36);
18
+ const width = Math.round(badge.text.length * fontSize * 0.64 + padX * 2);
19
+ const radius = Math.round(height * 0.1);
20
+ const rect = `<rect x="${String(pad)}" y="${String(pad)}"` +
21
+ ` width="${String(width)}" height="${String(height)}"` +
22
+ ` rx="${String(radius)}" fill="${badge.background ?? "#ffffff"}"/>`;
23
+ const text = textElement(badge.text, {
24
+ x: pad + padX,
25
+ y: pad + Math.round(height * 0.72),
26
+ fontFamily,
27
+ fontSize,
28
+ fontWeight: 900,
29
+ fill: badge.color ?? brand,
30
+ });
31
+ return rect + text;
32
+ }
33
+ /**
34
+ * Generalised "banner" template: a left-aligned title with an optional
35
+ * version and wrapped subtitle, an optional corner badge, and an optional
36
+ * footer. This is the configurable descendant of the original `npm_package`
37
+ * layout.
38
+ */
39
+ export const bannerTemplate = {
40
+ name: "banner",
41
+ render({ props, config, dimensions }) {
42
+ const { width, height } = dimensions;
43
+ const { fontFamily } = config;
44
+ const fg = config.colors.foreground;
45
+ const pad = Math.round(width * 0.075);
46
+ const contentWidth = width - pad * 2;
47
+ const title = props.title;
48
+ const subtitle = optionalString(props.subtitle);
49
+ const version = optionalString(props.version);
50
+ const hasSubtitle = subtitle !== undefined && subtitle !== "";
51
+ const titleFs = Math.round(height * (hasSubtitle ? 0.078 : 0.092));
52
+ const versionFs = Math.round(height * 0.04);
53
+ const subFs = Math.round(height * 0.05);
54
+ const footerFs = Math.round(height * 0.034);
55
+ const badgeHeight = Math.round(height * 0.072);
56
+ const titleLines = wrapText(title, estimateCharsPerLine(contentWidth, titleFs, 0.6)).slice(0, maxTitleLines);
57
+ const subtitleLines = hasSubtitle
58
+ ? wrapText(subtitle, estimateCharsPerLine(contentWidth, subFs, 0.55)).slice(0, maxSubtitleLines)
59
+ : [];
60
+ // Extra breathing room between the title, version and subtitle groups so
61
+ // they don't read as one squashed block.
62
+ const versionGap = Math.round(height * 0.03);
63
+ const subtitleGap = Math.round(height * 0.045);
64
+ const lines = titleLines.map((text) => ({
65
+ text,
66
+ fontSize: titleFs,
67
+ fontWeight: 800,
68
+ opacity: 0.98,
69
+ }));
70
+ if (version !== undefined && version !== "") {
71
+ lines.push({
72
+ text: `v${version}`,
73
+ fontSize: versionFs,
74
+ fontWeight: 700,
75
+ opacity: 0.85,
76
+ gapBefore: versionGap,
77
+ });
78
+ }
79
+ for (const [index, text] of subtitleLines.entries()) {
80
+ lines.push({
81
+ text,
82
+ fontSize: subFs,
83
+ fontWeight: 500,
84
+ opacity: 0.86,
85
+ ...(index === 0 && { gapBefore: subtitleGap }),
86
+ });
87
+ }
88
+ const hasBadge = config.badge !== undefined;
89
+ const hasFooter = config.footer !== undefined && config.footer !== "";
90
+ const top = pad + (hasBadge ? badgeHeight + Math.round(height * 0.03) : 0);
91
+ const bottom = height - pad - (hasFooter ? footerFs + Math.round(height * 0.02) : 0);
92
+ const placed = layoutStack(lines.map((line) => ({
93
+ fontSize: line.fontSize,
94
+ lineHeight: 1.15,
95
+ ...(line.gapBefore !== undefined && { gapBefore: line.gapBefore }),
96
+ })), top, bottom);
97
+ const body = lines
98
+ .map((line, index) => textElement(line.text, {
99
+ x: pad,
100
+ y: placed[index]?.y ?? top,
101
+ fontFamily,
102
+ fontSize: line.fontSize,
103
+ fontWeight: line.fontWeight,
104
+ fill: fg,
105
+ fillOpacity: line.opacity,
106
+ }))
107
+ .join("");
108
+ const badge = config.badge === undefined
109
+ ? ""
110
+ : renderBadge(config.badge, fontFamily, config.colors.brand, pad, badgeHeight);
111
+ const footer = config.footer !== undefined && config.footer !== ""
112
+ ? textElement(config.footer, {
113
+ x: pad,
114
+ y: height - pad,
115
+ fontFamily,
116
+ fontSize: footerFs,
117
+ fontWeight: 500,
118
+ fill: fg,
119
+ fillOpacity: 0.78,
120
+ })
121
+ : "";
122
+ return badge + body + footer;
123
+ },
124
+ };
125
+ //# sourceMappingURL=banner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banner.js","sourceRoot":"","sources":["../../src/templates/banner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { Template } from "../types.js";
2
+ /**
3
+ * Minimal "card" template: a centred title with an optional subtitle. No
4
+ * badge or version — a clean, quieter alternative to `banner`.
5
+ */
6
+ export declare const cardTemplate: Template;
7
+ //# sourceMappingURL=card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../src/templates/card.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAmB,MAAM,aAAa,CAAC;AA4B7D;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,QAsF1B,CAAC"}
@@ -0,0 +1,82 @@
1
+ import { estimateCharsPerLine, layoutStack, textElement, wrapText, } from "../text.js";
2
+ const maxTitleLines = 3;
3
+ const maxSubtitleLines = 3;
4
+ function optionalString(value) {
5
+ if (typeof value === "string") {
6
+ return value;
7
+ }
8
+ if (typeof value === "number" ||
9
+ typeof value === "boolean" ||
10
+ typeof value === "bigint") {
11
+ return String(value);
12
+ }
13
+ return undefined;
14
+ }
15
+ /**
16
+ * Minimal "card" template: a centred title with an optional subtitle. No
17
+ * badge or version — a clean, quieter alternative to `banner`.
18
+ */
19
+ export const cardTemplate = {
20
+ name: "card",
21
+ render({ props, config, dimensions }) {
22
+ const { width, height } = dimensions;
23
+ const { fontFamily } = config;
24
+ const fg = config.colors.foreground;
25
+ const pad = Math.round(width * 0.09);
26
+ const contentWidth = width - pad * 2;
27
+ const centreX = Math.round(width / 2);
28
+ const title = props.title;
29
+ const subtitle = optionalString(props.subtitle);
30
+ const hasSubtitle = subtitle !== undefined && subtitle !== "";
31
+ const titleFs = Math.round(height * 0.1);
32
+ const subFs = Math.round(height * 0.045);
33
+ const footerFs = Math.round(height * 0.032);
34
+ const titleLines = wrapText(title, estimateCharsPerLine(contentWidth, titleFs, 0.58)).slice(0, maxTitleLines);
35
+ const subtitleLines = hasSubtitle
36
+ ? wrapText(subtitle, estimateCharsPerLine(contentWidth, subFs, 0.52)).slice(0, maxSubtitleLines)
37
+ : [];
38
+ const lines = [
39
+ ...titleLines.map((text) => ({
40
+ text,
41
+ fontSize: titleFs,
42
+ fontWeight: 800,
43
+ opacity: 1,
44
+ })),
45
+ ...subtitleLines.map((text) => ({
46
+ text,
47
+ fontSize: subFs,
48
+ fontWeight: 500,
49
+ opacity: 0.85,
50
+ })),
51
+ ];
52
+ const hasFooter = config.footer !== undefined && config.footer !== "";
53
+ const bottom = height - pad - (hasFooter ? footerFs + Math.round(height * 0.02) : 0);
54
+ const placed = layoutStack(lines.map((line) => ({ fontSize: line.fontSize, lineHeight: 1.2 })), pad, bottom);
55
+ const body = lines
56
+ .map((line, index) => textElement(line.text, {
57
+ x: centreX,
58
+ y: placed[index]?.y ?? pad,
59
+ fontFamily,
60
+ fontSize: line.fontSize,
61
+ fontWeight: line.fontWeight,
62
+ fill: fg,
63
+ fillOpacity: line.opacity,
64
+ anchor: "middle",
65
+ }))
66
+ .join("");
67
+ const footer = config.footer !== undefined && config.footer !== ""
68
+ ? textElement(config.footer, {
69
+ x: centreX,
70
+ y: height - pad,
71
+ fontFamily,
72
+ fontSize: footerFs,
73
+ fontWeight: 500,
74
+ fill: fg,
75
+ fillOpacity: 0.75,
76
+ anchor: "middle",
77
+ })
78
+ : "";
79
+ return body + footer;
80
+ },
81
+ };
82
+ //# sourceMappingURL=card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card.js","sourceRoot":"","sources":["../../src/templates/card.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Template } from "../types.js";
2
+ /**
3
+ * Templates registered out of the box. Projects can add to or override these
4
+ * via `config.templates`.
5
+ */
6
+ export declare const builtinTemplates: Readonly<Record<string, Template>>;
7
+ export { bannerTemplate } from "./banner.js";
8
+ export { cardTemplate } from "./card.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAG/D,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { bannerTemplate } from "./banner.js";
2
+ import { cardTemplate } from "./card.js";
3
+ /**
4
+ * Templates registered out of the box. Projects can add to or override these
5
+ * via `config.templates`.
6
+ */
7
+ export const builtinTemplates = {
8
+ [bannerTemplate.name]: bannerTemplate,
9
+ [cardTemplate.name]: cardTemplate,
10
+ };
11
+ export { bannerTemplate } from "./banner.js";
12
+ export { cardTemplate } from "./card.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAuC;IAClE,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,cAAc;IACrC,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,YAAY;CAClC,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}