@micropress/theme-sdk 0.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ css: () => css,
24
+ defineTheme: () => defineTheme,
25
+ escapeHtml: () => escapeHtml,
26
+ html: () => html,
27
+ isNewsArticle: () => isNewsArticle,
28
+ isNewsList: () => isNewsList,
29
+ isPageContent: () => isPageContent,
30
+ join: () => join,
31
+ raw: () => raw,
32
+ render: () => render,
33
+ renderer: () => renderer,
34
+ when: () => when,
35
+ withAssets: () => withAssets,
36
+ withScripts: () => withScripts,
37
+ withStyles: () => withStyles
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/html.ts
42
+ function escapeHtml(unsafe) {
43
+ return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
44
+ }
45
+ function html(strings, ...values) {
46
+ let result = "";
47
+ for (let i = 0; i < strings.length; i++) {
48
+ result += strings[i];
49
+ if (i < values.length) {
50
+ const value = values[i];
51
+ if (value === null || value === void 0) {
52
+ continue;
53
+ }
54
+ if (typeof value === "object" && "html" in value) {
55
+ result += value.html;
56
+ } else if (Array.isArray(value)) {
57
+ result += value.map((v) => {
58
+ if (v && typeof v === "object" && "html" in v) {
59
+ return v.html;
60
+ }
61
+ return escapeHtml(String(v));
62
+ }).join("");
63
+ } else {
64
+ result += escapeHtml(String(value));
65
+ }
66
+ }
67
+ }
68
+ return {
69
+ html: result,
70
+ toString() {
71
+ return this.html;
72
+ }
73
+ };
74
+ }
75
+ function raw(htmlString) {
76
+ return {
77
+ html: htmlString,
78
+ toString() {
79
+ return this.html;
80
+ }
81
+ };
82
+ }
83
+ function join(items, separator = "") {
84
+ return raw(items.map((i) => i.html).join(separator));
85
+ }
86
+ function when(condition, content) {
87
+ if (!condition) {
88
+ return raw("");
89
+ }
90
+ return typeof content === "string" ? raw(content) : content;
91
+ }
92
+ function css(strings, ...values) {
93
+ let result = "";
94
+ for (let i = 0; i < strings.length; i++) {
95
+ result += strings[i];
96
+ if (i < values.length) {
97
+ result += String(values[i]);
98
+ }
99
+ }
100
+ return result;
101
+ }
102
+
103
+ // src/define-theme.ts
104
+ function defineTheme(definition) {
105
+ return definition;
106
+ }
107
+ function render(fn) {
108
+ return { render: fn };
109
+ }
110
+ function withStyles(fn, styles) {
111
+ return {
112
+ render: fn,
113
+ styles: () => styles
114
+ };
115
+ }
116
+ function withScripts(fn, scripts) {
117
+ return {
118
+ render: fn,
119
+ scripts: () => scripts
120
+ };
121
+ }
122
+ function withAssets(fn, options) {
123
+ return {
124
+ render: fn,
125
+ styles: options.styles ? () => options.styles : void 0,
126
+ scripts: options.scripts ? () => options.scripts : void 0
127
+ };
128
+ }
129
+
130
+ // src/types.ts
131
+ function renderer(fnOrOptions) {
132
+ if (typeof fnOrOptions === "function") {
133
+ return { render: fnOrOptions };
134
+ }
135
+ return fnOrOptions;
136
+ }
137
+
138
+ // src/dto/content.ts
139
+ function isPageContent(content) {
140
+ return content.contentType === "page";
141
+ }
142
+ function isNewsArticle(content) {
143
+ return content.contentType === "news-article";
144
+ }
145
+ function isNewsList(content) {
146
+ return content.contentType === "news-list";
147
+ }
148
+ // Annotate the CommonJS export names for ESM import in node:
149
+ 0 && (module.exports = {
150
+ css,
151
+ defineTheme,
152
+ escapeHtml,
153
+ html,
154
+ isNewsArticle,
155
+ isNewsList,
156
+ isPageContent,
157
+ join,
158
+ raw,
159
+ render,
160
+ renderer,
161
+ when,
162
+ withAssets,
163
+ withScripts,
164
+ withStyles
165
+ });
166
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/html.ts","../src/define-theme.ts","../src/types.ts","../src/dto/content.ts"],"sourcesContent":["// MicroPress Theme SDK\n// Everything you need to build themes\n\n// HTML helpers\nexport { html, css, raw, join, when, escapeHtml } from './html';\nexport type { HtmlResult } from './html';\n\n// Theme definition\nexport { defineTheme, render, withStyles, withScripts, withAssets } from './define-theme';\nexport type { ThemeDefinition, ThemeConfig, ThemeRenderers } from './define-theme';\n\n// Types\nexport { renderer } from './types';\nexport type { RenderContext, RenderFn, Renderer, BlockRenderer } from './types';\n\n// DTOs - All block and document types\nexport * from './dto';\n","// HTML Tagged Template - Safe, easy HTML generation\n\n/**\n * Result of html`` tagged template.\n */\nexport interface HtmlResult {\n html: string;\n toString(): string;\n}\n\n/**\n * Escape HTML entities for safe insertion.\n */\nexport function escapeHtml(unsafe: string): string {\n return unsafe\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n}\n\n/**\n * Tagged template for HTML.\n * Automatically escapes interpolated values unless they're HtmlResult.\n *\n * @example\n * ```ts\n * const name = '<script>alert(\"xss\")</script>';\n * html`<div>${name}</div>` // Safe: <div>&lt;script&gt;...</div>\n *\n * const inner = html`<span>safe</span>`;\n * html`<div>${inner}</div>` // Nested HTML preserved\n * ```\n */\nexport function html(strings: TemplateStringsArray, ...values: unknown[]): HtmlResult {\n let result = '';\n\n for (let i = 0; i < strings.length; i++) {\n result += strings[i];\n\n if (i < values.length) {\n const value = values[i];\n\n if (value === null || value === undefined) {\n // Skip null/undefined\n continue;\n }\n\n if (typeof value === 'object' && 'html' in value) {\n // HtmlResult - already safe\n result += (value as HtmlResult).html;\n } else if (Array.isArray(value)) {\n // Array of values - join them\n result += value\n .map(v => {\n if (v && typeof v === 'object' && 'html' in v) {\n return (v as HtmlResult).html;\n }\n return escapeHtml(String(v));\n })\n .join('');\n } else {\n // Escape everything else\n result += escapeHtml(String(value));\n }\n }\n }\n\n return {\n html: result,\n toString() {\n return this.html;\n },\n };\n}\n\n/**\n * Mark raw HTML as safe (use carefully!).\n *\n * @example\n * ```ts\n * const trusted = raw('<b>trusted content</b>');\n * html`<div>${trusted}</div>`\n * ```\n */\nexport function raw(htmlString: string): HtmlResult {\n return {\n html: htmlString,\n toString() {\n return this.html;\n },\n };\n}\n\n/**\n * Join multiple HTML results.\n */\nexport function join(items: HtmlResult[], separator = ''): HtmlResult {\n return raw(items.map(i => i.html).join(separator));\n}\n\n/**\n * Conditional HTML - returns empty if condition is false.\n */\nexport function when(condition: boolean, content: HtmlResult | string): HtmlResult {\n if (!condition) {\n return raw('');\n }\n return typeof content === 'string' ? raw(content) : content;\n}\n\n/**\n * CSS tagged template - just returns the string.\n * Useful for syntax highlighting in editors.\n */\nexport function css(strings: TemplateStringsArray, ...values: unknown[]): string {\n let result = '';\n for (let i = 0; i < strings.length; i++) {\n result += strings[i];\n if (i < values.length) {\n result += String(values[i]);\n }\n }\n return result;\n}\n","// Theme Definition - The simplest way to create a theme\n\nimport type { Renderer, RenderFn } from './types';\nimport type {\n DocumentDTO,\n HeaderDTO,\n NavigationDTO,\n FooterDTO,\n PageContentDTO,\n NewsArticleDTO,\n NewsListDTO,\n NewsCardDTO,\n ContentBlock,\n} from './dto';\n\n/**\n * All the components a theme can customize.\n * Every component is optional - defaults are used for missing ones.\n */\nexport interface ThemeRenderers {\n // Document-level\n document?: Renderer<DocumentDTO>;\n header?: Renderer<HeaderDTO>;\n navigation?: Renderer<NavigationDTO>;\n footer?: Renderer<FooterDTO>;\n\n // Content-level\n article?: Renderer<PageContentDTO>;\n newsArticle?: Renderer<NewsArticleDTO>;\n newsList?: Renderer<NewsListDTO>;\n newsCard?: Renderer<NewsCardDTO>;\n\n // Block-level (key is block type)\n blocks?: Partial<Record<ContentBlock['type'], Renderer<ContentBlock>>>;\n}\n\n/**\n * Theme configuration.\n */\nexport interface ThemeConfig {\n /** Theme ID (must match manifest) */\n id: string;\n /** Theme display name */\n name: string;\n /** Theme version */\n version: string;\n /** CSS variables for customization */\n variables?: Record<string, string>;\n}\n\n/**\n * Complete theme definition.\n */\nexport interface ThemeDefinition {\n config: ThemeConfig;\n renderers: ThemeRenderers;\n /** Global styles for the theme */\n styles?: string;\n /** Global scripts for the theme */\n scripts?: string;\n}\n\n/**\n * Define a theme with custom renderers.\n *\n * @example\n * ```ts\n * import { defineTheme, html } from '@micropress/theme-sdk';\n *\n * export default defineTheme({\n * config: {\n * id: 'my-theme',\n * name: 'My Theme',\n * version: '1.0.0',\n * },\n * renderers: {\n * header: {\n * render: (data, ctx) => html`\n * <header class=\"my-header\">\n * <h1>${data.siteName}</h1>\n * </header>\n * `.html,\n * },\n * },\n * });\n * ```\n */\nexport function defineTheme(definition: ThemeDefinition): ThemeDefinition {\n return definition;\n}\n\n/**\n * Quick helper to define just a render function.\n *\n * @example\n * ```ts\n * const header = render<HeaderDTO>((data, ctx) => html`\n * <header>${data.siteName}</header>\n * `.html);\n * ```\n */\nexport function render<T>(fn: RenderFn<T>): Renderer<T> {\n return { render: fn };\n}\n\n/**\n * Quick helper to define a renderer with styles.\n *\n * @example\n * ```ts\n * const card = withStyles<CardDTO>(\n * (data, ctx) => html`<div class=\"card\">${ctx.renderAll(data.children)}</div>`.html,\n * css`.card { padding: 1rem; }`\n * );\n * ```\n */\nexport function withStyles<T>(fn: RenderFn<T>, styles: string): Renderer<T> {\n return {\n render: fn,\n styles: () => styles,\n };\n}\n\n/**\n * Quick helper to define a renderer with scripts.\n */\nexport function withScripts<T>(fn: RenderFn<T>, scripts: string): Renderer<T> {\n return {\n render: fn,\n scripts: () => scripts,\n };\n}\n\n/**\n * Define a renderer with both styles and scripts.\n */\nexport function withAssets<T>(\n fn: RenderFn<T>,\n options: { styles?: string; scripts?: string }\n): Renderer<T> {\n return {\n render: fn,\n styles: options.styles ? () => options.styles! : undefined,\n scripts: options.scripts ? () => options.scripts! : undefined,\n };\n}\n","// Theme SDK Types - Keep it simple\n\nimport type { ContentBlock, BuildingBlock } from './dto';\n\n/**\n * Context passed to every render function.\n * Contains everything you need to render content.\n */\nexport interface RenderContext {\n /** Render a child block */\n render: (block: ContentBlock | BuildingBlock) => string;\n /** Render multiple blocks */\n renderAll: (blocks: (ContentBlock | BuildingBlock)[]) => string;\n /** Site configuration */\n site: {\n name: string;\n baseUrl: string;\n currentPath?: string;\n };\n}\n\n/**\n * A render function takes data and context, returns HTML.\n */\nexport type RenderFn<T> = (data: T, ctx: RenderContext) => string;\n\n/**\n * A renderer object with optional styles and scripts.\n */\nexport interface Renderer<T = unknown> {\n render: RenderFn<T>;\n styles?: () => string;\n scripts?: () => string;\n}\n\n/**\n * Shorthand type for block renderers.\n */\nexport type BlockRenderer<T extends ContentBlock = ContentBlock> = Renderer<T>;\n\n/**\n * Helper to create a typed renderer.\n */\nexport function renderer<T>(fn: RenderFn<T>): Renderer<T>;\nexport function renderer<T>(options: Renderer<T>): Renderer<T>;\nexport function renderer<T>(fnOrOptions: RenderFn<T> | Renderer<T>): Renderer<T> {\n if (typeof fnOrOptions === 'function') {\n return { render: fnOrOptions };\n }\n return fnOrOptions;\n}\n","// Content DTOs\n// These define the content area of pages, news articles, and lists\n\nimport type { BuildingBlock } from './blocks';\n\n// ============================================================================\n// Content Types\n// ============================================================================\n\n/** The type of content being rendered */\nexport type ContentType = 'page' | 'news-article' | 'news-list';\n\n// ============================================================================\n// Base Content\n// ============================================================================\n\n/** Base content interface with common fields */\nexport interface BaseContentDTO {\n /** Page/article title */\n title: string;\n /** Content type discriminator */\n contentType: ContentType;\n}\n\n// ============================================================================\n// Page Content\n// ============================================================================\n\n/** A regular page's content */\nexport interface PageContentDTO extends BaseContentDTO {\n contentType: 'page';\n /** Content blocks to render */\n blocks: BuildingBlock[];\n}\n\n// ============================================================================\n// News Article\n// ============================================================================\n\n/** A back navigation link */\nexport interface BackLinkDTO {\n label: string;\n href: string;\n}\n\n/** A news article's content */\nexport interface NewsArticleDTO extends BaseContentDTO {\n contentType: 'news-article';\n /** Content blocks to render */\n blocks: BuildingBlock[];\n /** Publication date (ISO string for formatting) */\n publishedAt: string;\n /** Author name */\n author?: string;\n /** Link back to news list */\n backLink: BackLinkDTO;\n}\n\n// ============================================================================\n// News List\n// ============================================================================\n\n/** A news card in the news list */\nexport interface NewsCardDTO {\n /** Article title */\n title: string;\n /** Article excerpt/preview */\n excerpt?: string;\n /** Publication date (ISO string) */\n publishedAt: string;\n /** Link to full article */\n href: string;\n /** Whether this is a featured article */\n featured?: boolean;\n /** Cover image URL */\n image?: string;\n /** Category label (e.g., \"News\", \"Events\") */\n category?: string;\n /** Estimated reading time in minutes */\n readingTime?: number;\n}\n\n/** A news list page's content */\nexport interface NewsListDTO extends BaseContentDTO {\n contentType: 'news-list';\n /** The news cards to display */\n items: NewsCardDTO[];\n}\n\n// ============================================================================\n// Union Type\n// ============================================================================\n\n/** All content types that can be rendered in the main content area */\nexport type ContentDTO = PageContentDTO | NewsArticleDTO | NewsListDTO;\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/** Check if content is a regular page */\nexport function isPageContent(content: ContentDTO): content is PageContentDTO {\n return content.contentType === 'page';\n}\n\n/** Check if content is a news article */\nexport function isNewsArticle(content: ContentDTO): content is NewsArticleDTO {\n return content.contentType === 'news-article';\n}\n\n/** Check if content is a news list */\nexport function isNewsList(content: ContentDTO): content is NewsListDTO {\n return content.contentType === 'news-list';\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,125 @@
1
+ // src/html.ts
2
+ function escapeHtml(unsafe) {
3
+ return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
4
+ }
5
+ function html(strings, ...values) {
6
+ let result = "";
7
+ for (let i = 0; i < strings.length; i++) {
8
+ result += strings[i];
9
+ if (i < values.length) {
10
+ const value = values[i];
11
+ if (value === null || value === void 0) {
12
+ continue;
13
+ }
14
+ if (typeof value === "object" && "html" in value) {
15
+ result += value.html;
16
+ } else if (Array.isArray(value)) {
17
+ result += value.map((v) => {
18
+ if (v && typeof v === "object" && "html" in v) {
19
+ return v.html;
20
+ }
21
+ return escapeHtml(String(v));
22
+ }).join("");
23
+ } else {
24
+ result += escapeHtml(String(value));
25
+ }
26
+ }
27
+ }
28
+ return {
29
+ html: result,
30
+ toString() {
31
+ return this.html;
32
+ }
33
+ };
34
+ }
35
+ function raw(htmlString) {
36
+ return {
37
+ html: htmlString,
38
+ toString() {
39
+ return this.html;
40
+ }
41
+ };
42
+ }
43
+ function join(items, separator = "") {
44
+ return raw(items.map((i) => i.html).join(separator));
45
+ }
46
+ function when(condition, content) {
47
+ if (!condition) {
48
+ return raw("");
49
+ }
50
+ return typeof content === "string" ? raw(content) : content;
51
+ }
52
+ function css(strings, ...values) {
53
+ let result = "";
54
+ for (let i = 0; i < strings.length; i++) {
55
+ result += strings[i];
56
+ if (i < values.length) {
57
+ result += String(values[i]);
58
+ }
59
+ }
60
+ return result;
61
+ }
62
+
63
+ // src/define-theme.ts
64
+ function defineTheme(definition) {
65
+ return definition;
66
+ }
67
+ function render(fn) {
68
+ return { render: fn };
69
+ }
70
+ function withStyles(fn, styles) {
71
+ return {
72
+ render: fn,
73
+ styles: () => styles
74
+ };
75
+ }
76
+ function withScripts(fn, scripts) {
77
+ return {
78
+ render: fn,
79
+ scripts: () => scripts
80
+ };
81
+ }
82
+ function withAssets(fn, options) {
83
+ return {
84
+ render: fn,
85
+ styles: options.styles ? () => options.styles : void 0,
86
+ scripts: options.scripts ? () => options.scripts : void 0
87
+ };
88
+ }
89
+
90
+ // src/types.ts
91
+ function renderer(fnOrOptions) {
92
+ if (typeof fnOrOptions === "function") {
93
+ return { render: fnOrOptions };
94
+ }
95
+ return fnOrOptions;
96
+ }
97
+
98
+ // src/dto/content.ts
99
+ function isPageContent(content) {
100
+ return content.contentType === "page";
101
+ }
102
+ function isNewsArticle(content) {
103
+ return content.contentType === "news-article";
104
+ }
105
+ function isNewsList(content) {
106
+ return content.contentType === "news-list";
107
+ }
108
+ export {
109
+ css,
110
+ defineTheme,
111
+ escapeHtml,
112
+ html,
113
+ isNewsArticle,
114
+ isNewsList,
115
+ isPageContent,
116
+ join,
117
+ raw,
118
+ render,
119
+ renderer,
120
+ when,
121
+ withAssets,
122
+ withScripts,
123
+ withStyles
124
+ };
125
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/html.ts","../src/define-theme.ts","../src/types.ts","../src/dto/content.ts"],"sourcesContent":["// HTML Tagged Template - Safe, easy HTML generation\n\n/**\n * Result of html`` tagged template.\n */\nexport interface HtmlResult {\n html: string;\n toString(): string;\n}\n\n/**\n * Escape HTML entities for safe insertion.\n */\nexport function escapeHtml(unsafe: string): string {\n return unsafe\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n}\n\n/**\n * Tagged template for HTML.\n * Automatically escapes interpolated values unless they're HtmlResult.\n *\n * @example\n * ```ts\n * const name = '<script>alert(\"xss\")</script>';\n * html`<div>${name}</div>` // Safe: <div>&lt;script&gt;...</div>\n *\n * const inner = html`<span>safe</span>`;\n * html`<div>${inner}</div>` // Nested HTML preserved\n * ```\n */\nexport function html(strings: TemplateStringsArray, ...values: unknown[]): HtmlResult {\n let result = '';\n\n for (let i = 0; i < strings.length; i++) {\n result += strings[i];\n\n if (i < values.length) {\n const value = values[i];\n\n if (value === null || value === undefined) {\n // Skip null/undefined\n continue;\n }\n\n if (typeof value === 'object' && 'html' in value) {\n // HtmlResult - already safe\n result += (value as HtmlResult).html;\n } else if (Array.isArray(value)) {\n // Array of values - join them\n result += value\n .map(v => {\n if (v && typeof v === 'object' && 'html' in v) {\n return (v as HtmlResult).html;\n }\n return escapeHtml(String(v));\n })\n .join('');\n } else {\n // Escape everything else\n result += escapeHtml(String(value));\n }\n }\n }\n\n return {\n html: result,\n toString() {\n return this.html;\n },\n };\n}\n\n/**\n * Mark raw HTML as safe (use carefully!).\n *\n * @example\n * ```ts\n * const trusted = raw('<b>trusted content</b>');\n * html`<div>${trusted}</div>`\n * ```\n */\nexport function raw(htmlString: string): HtmlResult {\n return {\n html: htmlString,\n toString() {\n return this.html;\n },\n };\n}\n\n/**\n * Join multiple HTML results.\n */\nexport function join(items: HtmlResult[], separator = ''): HtmlResult {\n return raw(items.map(i => i.html).join(separator));\n}\n\n/**\n * Conditional HTML - returns empty if condition is false.\n */\nexport function when(condition: boolean, content: HtmlResult | string): HtmlResult {\n if (!condition) {\n return raw('');\n }\n return typeof content === 'string' ? raw(content) : content;\n}\n\n/**\n * CSS tagged template - just returns the string.\n * Useful for syntax highlighting in editors.\n */\nexport function css(strings: TemplateStringsArray, ...values: unknown[]): string {\n let result = '';\n for (let i = 0; i < strings.length; i++) {\n result += strings[i];\n if (i < values.length) {\n result += String(values[i]);\n }\n }\n return result;\n}\n","// Theme Definition - The simplest way to create a theme\n\nimport type { Renderer, RenderFn } from './types';\nimport type {\n DocumentDTO,\n HeaderDTO,\n NavigationDTO,\n FooterDTO,\n PageContentDTO,\n NewsArticleDTO,\n NewsListDTO,\n NewsCardDTO,\n ContentBlock,\n} from './dto';\n\n/**\n * All the components a theme can customize.\n * Every component is optional - defaults are used for missing ones.\n */\nexport interface ThemeRenderers {\n // Document-level\n document?: Renderer<DocumentDTO>;\n header?: Renderer<HeaderDTO>;\n navigation?: Renderer<NavigationDTO>;\n footer?: Renderer<FooterDTO>;\n\n // Content-level\n article?: Renderer<PageContentDTO>;\n newsArticle?: Renderer<NewsArticleDTO>;\n newsList?: Renderer<NewsListDTO>;\n newsCard?: Renderer<NewsCardDTO>;\n\n // Block-level (key is block type)\n blocks?: Partial<Record<ContentBlock['type'], Renderer<ContentBlock>>>;\n}\n\n/**\n * Theme configuration.\n */\nexport interface ThemeConfig {\n /** Theme ID (must match manifest) */\n id: string;\n /** Theme display name */\n name: string;\n /** Theme version */\n version: string;\n /** CSS variables for customization */\n variables?: Record<string, string>;\n}\n\n/**\n * Complete theme definition.\n */\nexport interface ThemeDefinition {\n config: ThemeConfig;\n renderers: ThemeRenderers;\n /** Global styles for the theme */\n styles?: string;\n /** Global scripts for the theme */\n scripts?: string;\n}\n\n/**\n * Define a theme with custom renderers.\n *\n * @example\n * ```ts\n * import { defineTheme, html } from '@micropress/theme-sdk';\n *\n * export default defineTheme({\n * config: {\n * id: 'my-theme',\n * name: 'My Theme',\n * version: '1.0.0',\n * },\n * renderers: {\n * header: {\n * render: (data, ctx) => html`\n * <header class=\"my-header\">\n * <h1>${data.siteName}</h1>\n * </header>\n * `.html,\n * },\n * },\n * });\n * ```\n */\nexport function defineTheme(definition: ThemeDefinition): ThemeDefinition {\n return definition;\n}\n\n/**\n * Quick helper to define just a render function.\n *\n * @example\n * ```ts\n * const header = render<HeaderDTO>((data, ctx) => html`\n * <header>${data.siteName}</header>\n * `.html);\n * ```\n */\nexport function render<T>(fn: RenderFn<T>): Renderer<T> {\n return { render: fn };\n}\n\n/**\n * Quick helper to define a renderer with styles.\n *\n * @example\n * ```ts\n * const card = withStyles<CardDTO>(\n * (data, ctx) => html`<div class=\"card\">${ctx.renderAll(data.children)}</div>`.html,\n * css`.card { padding: 1rem; }`\n * );\n * ```\n */\nexport function withStyles<T>(fn: RenderFn<T>, styles: string): Renderer<T> {\n return {\n render: fn,\n styles: () => styles,\n };\n}\n\n/**\n * Quick helper to define a renderer with scripts.\n */\nexport function withScripts<T>(fn: RenderFn<T>, scripts: string): Renderer<T> {\n return {\n render: fn,\n scripts: () => scripts,\n };\n}\n\n/**\n * Define a renderer with both styles and scripts.\n */\nexport function withAssets<T>(\n fn: RenderFn<T>,\n options: { styles?: string; scripts?: string }\n): Renderer<T> {\n return {\n render: fn,\n styles: options.styles ? () => options.styles! : undefined,\n scripts: options.scripts ? () => options.scripts! : undefined,\n };\n}\n","// Theme SDK Types - Keep it simple\n\nimport type { ContentBlock, BuildingBlock } from './dto';\n\n/**\n * Context passed to every render function.\n * Contains everything you need to render content.\n */\nexport interface RenderContext {\n /** Render a child block */\n render: (block: ContentBlock | BuildingBlock) => string;\n /** Render multiple blocks */\n renderAll: (blocks: (ContentBlock | BuildingBlock)[]) => string;\n /** Site configuration */\n site: {\n name: string;\n baseUrl: string;\n currentPath?: string;\n };\n}\n\n/**\n * A render function takes data and context, returns HTML.\n */\nexport type RenderFn<T> = (data: T, ctx: RenderContext) => string;\n\n/**\n * A renderer object with optional styles and scripts.\n */\nexport interface Renderer<T = unknown> {\n render: RenderFn<T>;\n styles?: () => string;\n scripts?: () => string;\n}\n\n/**\n * Shorthand type for block renderers.\n */\nexport type BlockRenderer<T extends ContentBlock = ContentBlock> = Renderer<T>;\n\n/**\n * Helper to create a typed renderer.\n */\nexport function renderer<T>(fn: RenderFn<T>): Renderer<T>;\nexport function renderer<T>(options: Renderer<T>): Renderer<T>;\nexport function renderer<T>(fnOrOptions: RenderFn<T> | Renderer<T>): Renderer<T> {\n if (typeof fnOrOptions === 'function') {\n return { render: fnOrOptions };\n }\n return fnOrOptions;\n}\n","// Content DTOs\n// These define the content area of pages, news articles, and lists\n\nimport type { BuildingBlock } from './blocks';\n\n// ============================================================================\n// Content Types\n// ============================================================================\n\n/** The type of content being rendered */\nexport type ContentType = 'page' | 'news-article' | 'news-list';\n\n// ============================================================================\n// Base Content\n// ============================================================================\n\n/** Base content interface with common fields */\nexport interface BaseContentDTO {\n /** Page/article title */\n title: string;\n /** Content type discriminator */\n contentType: ContentType;\n}\n\n// ============================================================================\n// Page Content\n// ============================================================================\n\n/** A regular page's content */\nexport interface PageContentDTO extends BaseContentDTO {\n contentType: 'page';\n /** Content blocks to render */\n blocks: BuildingBlock[];\n}\n\n// ============================================================================\n// News Article\n// ============================================================================\n\n/** A back navigation link */\nexport interface BackLinkDTO {\n label: string;\n href: string;\n}\n\n/** A news article's content */\nexport interface NewsArticleDTO extends BaseContentDTO {\n contentType: 'news-article';\n /** Content blocks to render */\n blocks: BuildingBlock[];\n /** Publication date (ISO string for formatting) */\n publishedAt: string;\n /** Author name */\n author?: string;\n /** Link back to news list */\n backLink: BackLinkDTO;\n}\n\n// ============================================================================\n// News List\n// ============================================================================\n\n/** A news card in the news list */\nexport interface NewsCardDTO {\n /** Article title */\n title: string;\n /** Article excerpt/preview */\n excerpt?: string;\n /** Publication date (ISO string) */\n publishedAt: string;\n /** Link to full article */\n href: string;\n /** Whether this is a featured article */\n featured?: boolean;\n /** Cover image URL */\n image?: string;\n /** Category label (e.g., \"News\", \"Events\") */\n category?: string;\n /** Estimated reading time in minutes */\n readingTime?: number;\n}\n\n/** A news list page's content */\nexport interface NewsListDTO extends BaseContentDTO {\n contentType: 'news-list';\n /** The news cards to display */\n items: NewsCardDTO[];\n}\n\n// ============================================================================\n// Union Type\n// ============================================================================\n\n/** All content types that can be rendered in the main content area */\nexport type ContentDTO = PageContentDTO | NewsArticleDTO | NewsListDTO;\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/** Check if content is a regular page */\nexport function isPageContent(content: ContentDTO): content is PageContentDTO {\n return content.contentType === 'page';\n}\n\n/** Check if content is a news article */\nexport function isNewsArticle(content: ContentDTO): content is NewsArticleDTO {\n return content.contentType === 'news-article';\n}\n\n/** Check if content is a news list */\nexport function isNewsList(content: ContentDTO): content is NewsListDTO {\n return content.contentType === 'news-list';\n}\n"],"mappings":";AAaO,SAAS,WAAW,QAAwB;AACjD,SAAO,OACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;AAeO,SAAS,KAAK,YAAkC,QAA+B;AACpF,MAAI,SAAS;AAEb,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,cAAU,QAAQ,CAAC;AAEnB,QAAI,IAAI,OAAO,QAAQ;AACrB,YAAM,QAAQ,OAAO,CAAC;AAEtB,UAAI,UAAU,QAAQ,UAAU,QAAW;AAEzC;AAAA,MACF;AAEA,UAAI,OAAO,UAAU,YAAY,UAAU,OAAO;AAEhD,kBAAW,MAAqB;AAAA,MAClC,WAAW,MAAM,QAAQ,KAAK,GAAG;AAE/B,kBAAU,MACP,IAAI,OAAK;AACR,cAAI,KAAK,OAAO,MAAM,YAAY,UAAU,GAAG;AAC7C,mBAAQ,EAAiB;AAAA,UAC3B;AACA,iBAAO,WAAW,OAAO,CAAC,CAAC;AAAA,QAC7B,CAAC,EACA,KAAK,EAAE;AAAA,MACZ,OAAO;AAEL,kBAAU,WAAW,OAAO,KAAK,CAAC;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,WAAW;AACT,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AACF;AAWO,SAAS,IAAI,YAAgC;AAClD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,WAAW;AACT,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AACF;AAKO,SAAS,KAAK,OAAqB,YAAY,IAAgB;AACpE,SAAO,IAAI,MAAM,IAAI,OAAK,EAAE,IAAI,EAAE,KAAK,SAAS,CAAC;AACnD;AAKO,SAAS,KAAK,WAAoB,SAA0C;AACjF,MAAI,CAAC,WAAW;AACd,WAAO,IAAI,EAAE;AAAA,EACf;AACA,SAAO,OAAO,YAAY,WAAW,IAAI,OAAO,IAAI;AACtD;AAMO,SAAS,IAAI,YAAkC,QAA2B;AAC/E,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,cAAU,QAAQ,CAAC;AACnB,QAAI,IAAI,OAAO,QAAQ;AACrB,gBAAU,OAAO,OAAO,CAAC,CAAC;AAAA,IAC5B;AAAA,EACF;AACA,SAAO;AACT;;;ACtCO,SAAS,YAAY,YAA8C;AACxE,SAAO;AACT;AAYO,SAAS,OAAU,IAA8B;AACtD,SAAO,EAAE,QAAQ,GAAG;AACtB;AAaO,SAAS,WAAc,IAAiB,QAA6B;AAC1E,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,MAAM;AAAA,EAChB;AACF;AAKO,SAAS,YAAe,IAAiB,SAA8B;AAC5E,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,SAAS,MAAM;AAAA,EACjB;AACF;AAKO,SAAS,WACd,IACA,SACa;AACb,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,QAAQ,SAAS,MAAM,QAAQ,SAAU;AAAA,IACjD,SAAS,QAAQ,UAAU,MAAM,QAAQ,UAAW;AAAA,EACtD;AACF;;;ACpGO,SAAS,SAAY,aAAqD;AAC/E,MAAI,OAAO,gBAAgB,YAAY;AACrC,WAAO,EAAE,QAAQ,YAAY;AAAA,EAC/B;AACA,SAAO;AACT;;;ACmDO,SAAS,cAAc,SAAgD;AAC5E,SAAO,QAAQ,gBAAgB;AACjC;AAGO,SAAS,cAAc,SAAgD;AAC5E,SAAO,QAAQ,gBAAgB;AACjC;AAGO,SAAS,WAAW,SAA6C;AACtE,SAAO,QAAQ,gBAAgB;AACjC;","names":[]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@micropress/theme-sdk",
3
+ "version": "0.0.1",
4
+ "description": "SDK for building MicroPress themes",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "micropress-theme": "dist/cli/index.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "test": "vitest run",
26
+ "lint": "eslint src cli --ext .ts",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "dependencies": {
30
+ "commander": "^14.0.2"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^24.10.9",
34
+ "eslint": "^9.39.2",
35
+ "tsup": "^8.5.1",
36
+ "typescript": "^5.9.3",
37
+ "vitest": "^4.0.17"
38
+ },
39
+ "keywords": [
40
+ "micropress",
41
+ "theme",
42
+ "sdk",
43
+ "cms",
44
+ "static-site"
45
+ ],
46
+ "author": "MicroPress Team",
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/JWThewes/micropress-theme-sdk.git"
51
+ },
52
+ "homepage": "https://github.com/JWThewes/micropress-theme-sdk#readme",
53
+ "bugs": {
54
+ "url": "https://github.com/JWThewes/micropress-theme-sdk/issues"
55
+ },
56
+ "engines": {
57
+ "node": ">=18.0.0"
58
+ }
59
+ }