chatgpt-to-markdown 1.0.0 → 1.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.
package/README.md CHANGED
@@ -65,13 +65,15 @@ const json = [
65
65
  ];
66
66
 
67
67
  const sourceDir = "./output";
68
-
68
+ const options = {
69
+ dateFormat: (d) => d.toLocaleString(),
70
+ };
69
71
  chatgptToMarkdown(json, sourceDir);
70
72
  ```
71
73
 
72
74
  ## Options
73
75
 
74
- Currently, there are no additional options. Feel free to open an issue or submit a PR if there's a feature you'd like to see added!
76
+ - `dateFormat`: A function that takes a `Date` object and returns a string. Defaults to a string format like "1 Jan 2023 11:30 PM".
75
77
 
76
78
  ## Contributing
77
79
 
@@ -95,6 +97,8 @@ git push --follow-tags
95
97
 
96
98
  ## Release notes
97
99
 
100
+ - 1.2.0: 28 Sep 2023. Added test cases
101
+ - 1.1.0: 26 Sep 2023. Add date format option
98
102
  - 1.0.0: 26 Sep 2023. Initial release
99
103
 
100
104
  ## License
package/index.js CHANGED
@@ -37,12 +37,37 @@ function indent(str) {
37
37
  .join("\n");
38
38
  }
39
39
 
40
+ /**
41
+ * Formats a date to a string like "1 Jan 2021".
42
+ * @param {Date} date - The date to format.
43
+ * @returns {string} - The formatted date.
44
+ * @example
45
+ * formatDate(new Date());
46
+ * //=> "1 Jan 2021"
47
+ */
48
+ function formatDate(date) {
49
+ const month = date.toLocaleString("en", { month: "short" });
50
+ const min = date.getMinutes().toString().padStart(2, "0");
51
+ let hours = date.getHours();
52
+ let ampm = hours >= 12 ? "PM" : "AM";
53
+ hours = hours % 12;
54
+ hours = hours ? hours : 12; // the hour '0' should be '12'
55
+ return `${date.getDate()} ${month} ${date.getFullYear()} ${hours}:${min} ${ampm}`;
56
+ }
57
+
40
58
  /**
41
59
  * Converts a JSON object to markdown and saves it to a file.
42
60
  * @param {Object[]} json - The JSON object to convert.
43
61
  * @param {string} sourceDir - The directory to save the markdown files in.
62
+ * @param {Object} [options] - The options object.
63
+ * @param {Function} [options.dateFormat] - The function to format dates with.
64
+ * @returns {Promise<void>} - A promise that resolves when the file is saved.
65
+ * @example
66
+ * const json = [ ... ];
67
+ * await convertToMarkdown(json, "./output");
68
+ * //=> Creates a markdown file for each conversation in the output directory
44
69
  */
45
- async function chatgptToMarkdown(json, sourceDir) {
70
+ async function chatgptToMarkdown(json, sourceDir, { dateFormat } = { dateFormat: formatDate }) {
46
71
  if (!Array.isArray(json)) {
47
72
  throw new TypeError("The first argument must be an array.");
48
73
  }
@@ -56,8 +81,8 @@ async function chatgptToMarkdown(json, sourceDir) {
56
81
  const filePath = path.join(sourceDir, fileName);
57
82
  const title = `# ${wrapHtmlTagsInBackticks(conversation.title)}\n`;
58
83
  const metadata = [
59
- `- Created: ${new Date(conversation.create_time * 1000).toLocaleString()}\n`,
60
- `- Updated: ${new Date(conversation.update_time * 1000).toLocaleString()}\n`,
84
+ `- Created: ${dateFormat(new Date(conversation.create_time * 1000))}\n`,
85
+ `- Updated: ${dateFormat(new Date(conversation.update_time * 1000))}\n`,
61
86
  ].join("");
62
87
  const messages = Object.values(conversation.mapping)
63
88
  .map((node) => {
package/index.test.js ADDED
@@ -0,0 +1,152 @@
1
+ // index.test.js
2
+
3
+ import { promises as fs } from "fs";
4
+ import path from "path";
5
+ import os from "os";
6
+ import chatgptToMarkdown from "./index";
7
+
8
+ describe("chatgptToMarkdown", () => {
9
+ let tempDir;
10
+
11
+ beforeEach(async () => {
12
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "chatgptToMarkdown-"));
13
+ });
14
+
15
+ afterEach(async () => {
16
+ await fs.rm(tempDir, { recursive: true, force: true });
17
+ });
18
+
19
+ it("should write a markdown file for each conversation", async () => {
20
+ const json = [
21
+ {
22
+ title: "Test Conversation",
23
+ create_time: 1630454400,
24
+ update_time: 1630458000,
25
+ mapping: {
26
+ 0: {
27
+ message: {
28
+ author: { role: "user", name: "John" },
29
+ content: { content_type: "text", parts: ["Hello"] },
30
+ },
31
+ },
32
+ },
33
+ },
34
+ ];
35
+
36
+ await chatgptToMarkdown(json, tempDir);
37
+
38
+ const filePath = path.join(tempDir, "Test Conversation.md");
39
+ const fileContent = await fs.readFile(filePath, "utf8");
40
+
41
+ expect(fileContent).toBe(`# Test Conversation
42
+
43
+ - Created: 1 Sep 2021 5:30 AM
44
+ - Updated: 1 Sep 2021 6:30 AM
45
+
46
+ ## user (John)
47
+
48
+ Hello
49
+
50
+ `);
51
+ });
52
+
53
+ it("should handle titles with HTML tags", async () => {
54
+ const json = [
55
+ {
56
+ title: "<h1>Test Conversation</h1>",
57
+ create_time: 1630454400,
58
+ update_time: 1630458000,
59
+ mapping: {},
60
+ },
61
+ ];
62
+ await chatgptToMarkdown(json, tempDir);
63
+ const fileContent = await fs.readFile(path.join(tempDir, "h1 Test Conversation h1.md"), "utf8");
64
+ expect(fileContent).toContain("# `<h1>`Test Conversation`</h1>`\n");
65
+ });
66
+
67
+ it("should sanitize titles with invalid filename characters", async () => {
68
+ const json = [
69
+ {
70
+ title: ":/In\\<>*valid|?",
71
+ create_time: 1630454400,
72
+ update_time: 1630458000,
73
+ mapping: {},
74
+ },
75
+ ];
76
+ await chatgptToMarkdown(json, tempDir);
77
+ // Check that the file exists with the sanitized title
78
+ await expect(fs.access(path.join(tempDir, "In valid.md"))).resolves.not.toThrow();
79
+ });
80
+
81
+ it("should handle custom date format functions", async () => {
82
+ const json = [
83
+ {
84
+ title: "Test Conversation",
85
+ create_time: 1630454400,
86
+ update_time: 1630458000,
87
+ mapping: {},
88
+ },
89
+ ];
90
+ const customDateFormat = (date) => date.toISOString();
91
+ await chatgptToMarkdown(json, tempDir, { dateFormat: customDateFormat });
92
+ const fileContent = await fs.readFile(path.join(tempDir, "Test Conversation.md"), "utf8");
93
+ expect(fileContent).toContain("- Created: 2021-09-01T00:00:00.000Z\n");
94
+ });
95
+
96
+ it("should ignore messages with no content", async () => {
97
+ const json = [
98
+ {
99
+ title: "Test Conversation",
100
+ create_time: 1630454400,
101
+ update_time: 1630458000,
102
+ mapping: {
103
+ 0: {
104
+ message: {
105
+ /* no content property */
106
+ },
107
+ },
108
+ },
109
+ },
110
+ ];
111
+ await chatgptToMarkdown(json, tempDir);
112
+ const fileContent = await fs.readFile(path.join(tempDir, "Test Conversation.md"), "utf8");
113
+ expect(fileContent).not.toContain("## user (John)");
114
+ });
115
+
116
+ it("should ignore messages with empty content", async () => {
117
+ const json = [
118
+ {
119
+ title: "Test Conversation",
120
+ create_time: 1630454400,
121
+ update_time: 1630458000,
122
+ mapping: {
123
+ 0: { message: { content: { content_type: "text", parts: [] } } },
124
+ },
125
+ },
126
+ ];
127
+ await chatgptToMarkdown(json, tempDir);
128
+ const fileContent = await fs.readFile(path.join(tempDir, "Test Conversation.md"), "utf8");
129
+ expect(fileContent).not.toContain("## user (John)");
130
+ });
131
+
132
+ it("should indent messages with tool role that contain ``` fenced code blocks", async () => {
133
+ const json = [
134
+ {
135
+ title: "Test Conversation",
136
+ create_time: 1630454400,
137
+ update_time: 1630458000,
138
+ mapping: {
139
+ 0: {
140
+ message: {
141
+ author: { role: "tool" },
142
+ content: { content_type: "code", language: "javascript", text: 'console.log("Hello, world!");' },
143
+ },
144
+ },
145
+ },
146
+ },
147
+ ];
148
+ await chatgptToMarkdown(json, tempDir);
149
+ const fileContent = await fs.readFile(path.join(tempDir, "Test Conversation.md"), "utf8");
150
+ expect(fileContent).toContain('```javascript\nconsole.log("Hello, world!");\n```\n');
151
+ });
152
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatgpt-to-markdown",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Convert ChatGPT exported conversations.json to Markdown",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -8,8 +8,8 @@
8
8
  "chatgpt-to-markdown": "cli.js"
9
9
  },
10
10
  "scripts": {
11
- "prepublishOnly": "npx prettier --write *.js *.md package.json",
12
- "test": "echo \"Error: no test specified\" && exit 1"
11
+ "prepublishOnly": "npx prettier --write *.js *.md package.json && npm test",
12
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
13
13
  },
14
14
  "keywords": [
15
15
  "chatgpt",
@@ -22,6 +22,7 @@
22
22
  "printWidth": 120
23
23
  },
24
24
  "devDependencies": {
25
+ "jest": "^29.7.0",
25
26
  "prettier": "^3.0.3"
26
27
  }
27
28
  }