@bytechain.cn/colamd-themes 0.3.0 → 0.3.2

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 (39) hide show
  1. package/.openclaw/skills/colamd-themes/SKILL.md +15 -124
  2. package/.trae/rules/colamd-themes.md +15 -124
  3. package/CHANGELOG.md +142 -3
  4. package/README.md +12 -1
  5. package/README_CN.md +12 -1
  6. package/RELEASENOTE.md +212 -0
  7. package/dist/cli.js +278 -2
  8. package/dist/cli.js.map +1 -1
  9. package/dist/export-cli.d.ts +1 -0
  10. package/dist/export-cli.js +208 -176
  11. package/dist/export-cli.js.map +1 -1
  12. package/dist/extractors/url-extractor.d.ts +6 -0
  13. package/dist/extractors/url-extractor.js +51 -2
  14. package/dist/extractors/url-extractor.js.map +1 -1
  15. package/dist/generator.d.ts +4 -0
  16. package/dist/generator.js +74 -7
  17. package/dist/generator.js.map +1 -1
  18. package/dist/renderer.d.ts +1 -1
  19. package/dist/renderer.js +87 -14
  20. package/dist/renderer.js.map +1 -1
  21. package/dist/theme-store.d.ts +7 -6
  22. package/dist/theme-store.js +84 -34
  23. package/dist/theme-store.js.map +1 -1
  24. package/dist/utils/cli-utils.d.ts +55 -0
  25. package/dist/utils/cli-utils.js +80 -0
  26. package/dist/utils/cli-utils.js.map +1 -0
  27. package/package.json +1 -1
  28. package/skills/colamd-themes/SKILL.md +30 -0
  29. package/src/cli.ts +289 -2
  30. package/src/export-cli.ts +248 -193
  31. package/src/extractors/url-extractor.ts +56 -2
  32. package/src/generator.ts +88 -7
  33. package/src/renderer.ts +96 -14
  34. package/src/theme-store.ts +93 -35
  35. package/src/utils/cli-utils.ts +84 -0
  36. package/.claude/skills/colamd-themes-preview.skill.md +0 -333
  37. package/.claude/skills/colamd-themes.skill.md +0 -155
  38. package/.hermes/skills/tools/colamd-themes/SKILL.md +0 -159
  39. package/.opencode/skills/colamd-themes/SKILL.md +0 -162
@@ -2,58 +2,103 @@
2
2
  * Theme configuration persistence.
3
3
  *
4
4
  * Stores default theme and registered custom themes in ~/.colamd-themes/config.json
5
+ * Uses async file I/O to avoid blocking the event loop.
5
6
  */
6
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
- import { join } from "node:path";
7
+ import { mkdir, readFile, writeFile, access } from "node:fs/promises";
8
+ import { statSync } from "node:fs";
9
+ import { join, basename } from "node:path";
8
10
  import { homedir } from "node:os";
9
11
  const CONFIG_DIR = join(homedir(), ".colamd-themes");
10
12
  const CONFIG_FILE = join(CONFIG_DIR, "config.json");
13
+ /** Maximum allowed CSS file size (1MB) to prevent OOM attacks */
14
+ const MAX_CSS_SIZE = 1024 * 1024;
11
15
  /** Built-in themes provided by @bytechain.cn/colamd */
12
16
  export const BUILTIN_THEMES = ["light", "dark", "elegant", "newsprint"];
13
- function readConfig() {
14
- if (!existsSync(CONFIG_FILE))
15
- return { defaultTheme: "elegant", themes: {} };
17
+ async function readConfig() {
16
18
  try {
17
- return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
19
+ await access(CONFIG_FILE);
20
+ const data = await readFile(CONFIG_FILE, "utf-8");
21
+ return JSON.parse(data);
18
22
  }
19
23
  catch {
20
24
  return { defaultTheme: "elegant", themes: {} };
21
25
  }
22
26
  }
23
- function writeConfig(config) {
24
- if (!existsSync(CONFIG_DIR))
25
- mkdirSync(CONFIG_DIR, { recursive: true });
26
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
27
+ async function writeConfig(config) {
28
+ try {
29
+ await access(CONFIG_DIR);
30
+ }
31
+ catch {
32
+ await mkdir(CONFIG_DIR, { recursive: true });
33
+ }
34
+ await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
27
35
  }
28
36
  /** Get the full config. */
29
- export function getConfig() {
37
+ export async function getConfig() {
30
38
  return readConfig();
31
39
  }
32
40
  /** Set the default theme (built-in name or registered custom name). */
33
- export function setDefaultTheme(name) {
34
- const config = readConfig();
41
+ export async function setDefaultTheme(name) {
42
+ const config = await readConfig();
35
43
  config.defaultTheme = name;
36
- writeConfig(config);
44
+ await writeConfig(config);
37
45
  }
38
46
  /**
39
47
  * Register a custom theme.
40
48
  * @param name Unique name (will be prefixed with "custom:" when applied)
41
49
  * @param cssPath Absolute path to the CSS file
42
50
  */
43
- export function registerTheme(name, cssPath) {
44
- const config = readConfig();
51
+ export async function registerTheme(name, cssPath) {
52
+ const config = await readConfig();
45
53
  config.themes[name] = cssPath;
46
- writeConfig(config);
54
+ await writeConfig(config);
47
55
  }
48
56
  /** Remove a registered custom theme. */
49
- export function unregisterTheme(name) {
50
- const config = readConfig();
57
+ export async function unregisterTheme(name) {
58
+ const config = await readConfig();
51
59
  if (!(name in config.themes))
52
60
  return false;
53
61
  delete config.themes[name];
54
- writeConfig(config);
62
+ await writeConfig(config);
55
63
  return true;
56
64
  }
65
+ /**
66
+ * Sanitize theme name from file path.
67
+ * Removes special characters and limits length to prevent injection.
68
+ */
69
+ function sanitizeName(path) {
70
+ return basename(path, ".css")
71
+ .replace(/[^a-zA-Z0-9_-]/g, "-")
72
+ .replace(/-+/g, "-")
73
+ .slice(0, 50) || "custom";
74
+ }
75
+ /**
76
+ * Validate CSS file before reading.
77
+ * Checks extension, size, and basic content validity.
78
+ */
79
+ async function validateCssFile(filePath) {
80
+ // Check file extension
81
+ if (!filePath.toLowerCase().endsWith(".css")) {
82
+ throw new Error(`Theme file must have .css extension: ${filePath}`);
83
+ }
84
+ // Check file size (synchronous stat is acceptable for metadata)
85
+ let stats;
86
+ try {
87
+ stats = statSync(filePath);
88
+ }
89
+ catch {
90
+ throw new Error(`Cannot access theme file: ${filePath}`);
91
+ }
92
+ if (stats.size > MAX_CSS_SIZE) {
93
+ throw new Error(`Theme file too large (${(stats.size / 1024).toFixed(1)}KB). ` +
94
+ `Maximum size: ${(MAX_CSS_SIZE / 1024).toFixed(0)}MB`);
95
+ }
96
+ // Read and validate content
97
+ const content = await readFile(filePath, "utf-8");
98
+ if (content.length < 10) {
99
+ throw new Error(`Theme file appears to be empty or invalid: ${filePath}`);
100
+ }
101
+ }
57
102
  /**
58
103
  * Resolve a theme name to the arguments needed by the ColaMD renderer.
59
104
  *
@@ -61,30 +106,35 @@ export function unregisterTheme(name) {
61
106
  * @returns { themeName, customCSS? } — themeName is either a built-in name
62
107
  * or "custom:<name>"; customCSS is the CSS content for custom themes.
63
108
  */
64
- export function resolveTheme(name) {
65
- const config = readConfig();
109
+ export async function resolveTheme(name) {
110
+ const config = await readConfig();
66
111
  const resolved = name ?? config.defaultTheme;
67
112
  // Check if it's a registered custom theme
68
113
  if (config.themes[resolved]) {
69
114
  const cssPath = config.themes[resolved];
70
- if (!existsSync(cssPath)) {
71
- throw new Error(`Custom theme CSS file not found: ${cssPath}`);
72
- }
73
- return { themeName: `custom:${resolved}`, customCSS: readFileSync(cssPath, "utf-8") };
115
+ await validateCssFile(cssPath);
116
+ const customCSS = await readFile(cssPath, "utf-8");
117
+ return { themeName: `custom:${resolved}`, customCSS };
74
118
  }
75
119
  // Check if it's a built-in theme
76
120
  if (BUILTIN_THEMES.includes(resolved)) {
77
121
  return { themeName: resolved };
78
122
  }
79
123
  // Treat as a direct CSS file path
80
- if (existsSync(resolved)) {
81
- const nameFromPath = resolved.replace(/\.css$/i, "").split("/").pop() ?? "custom";
82
- return {
83
- themeName: `custom:${nameFromPath}`,
84
- customCSS: readFileSync(resolved, "utf-8"),
85
- };
124
+ try {
125
+ await access(resolved);
126
+ }
127
+ catch {
128
+ throw new Error(`Unknown theme: "${resolved}". Use a built-in theme (${BUILTIN_THEMES.join(", ")}), ` +
129
+ `a registered custom theme, or a path to a .css file.`);
86
130
  }
87
- throw new Error(`Unknown theme: "${resolved}". Use a built-in theme (${BUILTIN_THEMES.join(", ")}), ` +
88
- `a registered custom theme, or a path to a .css file.`);
131
+ // Validate and read direct CSS file
132
+ await validateCssFile(resolved);
133
+ const nameFromPath = sanitizeName(resolved);
134
+ const customCSS = await readFile(resolved, "utf-8");
135
+ return {
136
+ themeName: `custom:${nameFromPath}`,
137
+ customCSS,
138
+ };
89
139
  }
90
140
  //# sourceMappingURL=theme-store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"theme-store.js","sourceRoot":"","sources":["../src/theme-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"theme-store.js","sourceRoot":"","sources":["../src/theme-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Shared utility functions for CLI commands.
3
+ * Provides consistent file path resolution and error handling.
4
+ */
5
+ /**
6
+ * Resolve input file path with validation.
7
+ * Converts relative paths to absolute and checks existence.
8
+ *
9
+ * @param input File path provided by user
10
+ * @returns Resolved absolute path
11
+ * @throws Error if file does not exist
12
+ */
13
+ export declare function resolveInputPath(input: string): string;
14
+ /**
15
+ * Resolve output file path with sensible defaults.
16
+ * If output path is not specified, derives it from input path by replacing extension.
17
+ *
18
+ * @param inputPath Resolved absolute input path
19
+ * @param output Optional explicit output path
20
+ * @param ext Target extension (default: ".html")
21
+ * @returns Resolved absolute output path
22
+ */
23
+ export declare function resolveOutputPath(inputPath: string, output?: string, ext?: string): string;
24
+ /**
25
+ * Get base filename without extension for use as document title.
26
+ *
27
+ * @param filePath Absolute or relative file path
28
+ * @returns Filename without extension
29
+ */
30
+ export declare function getDocumentTitle(filePath: string): string;
31
+ /**
32
+ * Standard error codes for CLI operations.
33
+ * Enables consistent error handling across all commands.
34
+ */
35
+ export declare enum ErrorCode {
36
+ /** Input file not found */
37
+ FILE_NOT_FOUND = 1,
38
+ /** Theme resolution failed */
39
+ THEME_ERROR = 2,
40
+ /** Rendering/export failed */
41
+ RENDER_ERROR = 3,
42
+ /** Invalid arguments or configuration */
43
+ INVALID_INPUT = 4,
44
+ /** Unknown/unexpected error */
45
+ UNKNOWN = 99
46
+ }
47
+ /**
48
+ * Custom error class for CLI operations with structured error codes.
49
+ * Replaces direct process.exit() calls for better error propagation.
50
+ */
51
+ export declare class CLIError extends Error {
52
+ code: ErrorCode;
53
+ cause?: Error | undefined;
54
+ constructor(message: string, code?: ErrorCode, cause?: Error | undefined);
55
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Shared utility functions for CLI commands.
3
+ * Provides consistent file path resolution and error handling.
4
+ */
5
+ import { existsSync } from "node:fs";
6
+ import { resolve, isAbsolute, basename } from "node:path";
7
+ /**
8
+ * Resolve input file path with validation.
9
+ * Converts relative paths to absolute and checks existence.
10
+ *
11
+ * @param input File path provided by user
12
+ * @returns Resolved absolute path
13
+ * @throws Error if file does not exist
14
+ */
15
+ export function resolveInputPath(input) {
16
+ const path = isAbsolute(input) ? input : resolve(process.cwd(), input);
17
+ if (!existsSync(path)) {
18
+ throw new Error(`File not found: ${path}`);
19
+ }
20
+ return path;
21
+ }
22
+ /**
23
+ * Resolve output file path with sensible defaults.
24
+ * If output path is not specified, derives it from input path by replacing extension.
25
+ *
26
+ * @param inputPath Resolved absolute input path
27
+ * @param output Optional explicit output path
28
+ * @param ext Target extension (default: ".html")
29
+ * @returns Resolved absolute output path
30
+ */
31
+ export function resolveOutputPath(inputPath, output, ext = ".html") {
32
+ if (output) {
33
+ return isAbsolute(output) ? output : resolve(process.cwd(), output);
34
+ }
35
+ return inputPath.replace(/\.md$/i, ext);
36
+ }
37
+ /**
38
+ * Get base filename without extension for use as document title.
39
+ *
40
+ * @param filePath Absolute or relative file path
41
+ * @returns Filename without extension
42
+ */
43
+ export function getDocumentTitle(filePath) {
44
+ return basename(filePath, ".md");
45
+ }
46
+ /**
47
+ * Standard error codes for CLI operations.
48
+ * Enables consistent error handling across all commands.
49
+ */
50
+ export var ErrorCode;
51
+ (function (ErrorCode) {
52
+ /** Input file not found */
53
+ ErrorCode[ErrorCode["FILE_NOT_FOUND"] = 1] = "FILE_NOT_FOUND";
54
+ /** Theme resolution failed */
55
+ ErrorCode[ErrorCode["THEME_ERROR"] = 2] = "THEME_ERROR";
56
+ /** Rendering/export failed */
57
+ ErrorCode[ErrorCode["RENDER_ERROR"] = 3] = "RENDER_ERROR";
58
+ /** Invalid arguments or configuration */
59
+ ErrorCode[ErrorCode["INVALID_INPUT"] = 4] = "INVALID_INPUT";
60
+ /** Unknown/unexpected error */
61
+ ErrorCode[ErrorCode["UNKNOWN"] = 99] = "UNKNOWN";
62
+ })(ErrorCode || (ErrorCode = {}));
63
+ /**
64
+ * Custom error class for CLI operations with structured error codes.
65
+ * Replaces direct process.exit() calls for better error propagation.
66
+ */
67
+ export class CLIError extends Error {
68
+ code;
69
+ cause;
70
+ constructor(message, code = ErrorCode.UNKNOWN, cause) {
71
+ super(message);
72
+ this.code = code;
73
+ this.cause = cause;
74
+ this.name = "CLIError";
75
+ if (cause) {
76
+ this.stack = `${this.message}\nCaused by: ${cause.stack}`;
77
+ }
78
+ }
79
+ }
80
+ //# sourceMappingURL=cli-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-utils.js","sourceRoot":"","sources":["../../src/utils/cli-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;IACvE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,MAAe,EAAE,GAAG,GAAG,OAAO;IACjF,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,OAAO,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAN,IAAY,SAWX;AAXD,WAAY,SAAS;IACnB,2BAA2B;IAC3B,6DAAkB,CAAA;IAClB,8BAA8B;IAC9B,uDAAe,CAAA;IACf,8BAA8B;IAC9B,yDAAgB,CAAA;IAChB,yCAAyC;IACzC,2DAAiB,CAAA;IACjB,+BAA+B;IAC/B,gDAAY,CAAA;AACd,CAAC,EAXW,SAAS,KAAT,SAAS,QAWpB;AAED;;;GAGG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGxB;IACA;IAHT,YACE,OAAe,EACR,OAAkB,SAAS,CAAC,OAAO,EACnC,KAAa;QAEpB,KAAK,CAAC,OAAO,CAAC,CAAC;QAHR,SAAI,GAAJ,IAAI,CAA+B;QACnC,UAAK,GAAL,KAAK,CAAQ;QAGpB,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,KAAK,GAAG,GAAG,IAAI,CAAC,OAAO,gBAAgB,KAAK,CAAC,KAAK,EAAE,CAAC;QAC5D,CAAC;IACH,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytechain.cn/colamd-themes",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Theme development & export tool for ColaMD — extract formatting, generate themes, and export Markdown to HTML/PDF",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -139,6 +139,36 @@ Export uses Puppeteer (headless Chromium) with `@bytechain.cn/colamd/renderer`:
139
139
  - Generated CSS ≤ 300 lines
140
140
  - Font stacks must include CJK fallback and end with generic family
141
141
 
142
+ ## CSS Pitfall Quick Reference
143
+
144
+ > When adjusting themes, follow these rules strictly. For each step, reference the corresponding pattern in `academic-paper.css`.
145
+
146
+ ### 1. Screen & Print Must Stay in Sync
147
+
148
+ Every rule in SECTION 5 must have a mirror copy in SECTION 6 `@media print`. Reference the `SECTION 5 / SECTION 6` correspondence in `academic-paper.css`.
149
+
150
+ ### 2. Body Text First-Line Indent
151
+
152
+ **Pitfall:** `#write p` does not match. **Correct:** Reference `academic-paper.css` §5.3 directly — use `body.theme-custom p, body.theme-custom #write > p`, with `margin: 0` set beforehand.
153
+
154
+ ### 3. Indent Exclusion (Do Not Pollute Lists / Tables / Diagrams)
155
+
156
+ **Pitfall:** The `p` selector hits paragraphs inside all nested containers. **Correct:** Reference the exclusion block below `swiss-design.css` §5.2.1 — apply `text-indent: 0` uniformly to `li / li p / td p / th p / blockquote p / .mermaid-block p / figure p / pre p`.
157
+
158
+ ### 4. List Indentation (Hardest Pitfall)
159
+
160
+ **Pitfall:** `padding-left` does not move the bullet/number; missing the `#editor .ProseMirror` editor selector; missing `li::before` and `li > p` inline conversion.
161
+
162
+ **Correct:** Reference `academic-paper.css` §5.7 directly — copy its full selector structure (`#editor .ProseMirror > ul/ol` + `#write > ul/ol:first-of-type` + `li::before` + `#editor .ProseMirror li > p`). Use `margin-left: 2em` for indentation instead of `padding-left`. For Swiss style, keep `list-style-position: outside` and do NOT change to `none`.
163
+
164
+ ### 5. Mermaid CJK Text Overflow
165
+
166
+ **Pitfall:** Multi-line `<br/>` nodes have incorrect width/height calculations. **Correct:** ≤ 2 lines per node + `fontSize: 14px` + simplify text (use `·` instead of `/`, remove redundant modifiers).
167
+
168
+ ### 6. Mermaid classDef Does Not Inherit themeVariables
169
+
170
+ **Pitfall:** Assuming `themeVariables` sets text color so you don't need to repeat it. **Correct:** `classDef` must explicitly declare all three properties: `fill / color / stroke`.
171
+
142
172
  ## Architecture
143
173
 
144
174
  ```