A tool for publishing static site markdown to the ATmosphere
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Clean up files, add new test flows, mock more md files

+178 -147
+14
__tests__/markdowns/file-three.md
··· 1 + --- 2 + date: 2026-02-15T15:01:43.100Z 3 + description: Getting my third test file 4 + slug: file-three 5 + tags: 6 + - ATProto 7 + - ATmosphere 8 + - decentralization 9 + title: Testing 3 - some good functions 10 + --- 11 + 12 + ## Test file information 13 + 14 + This is another test file.
+14
__tests__/markdowns/file-two.md
··· 1 + --- 2 + date: 2026-02-15T15:01:43.100Z 3 + description: Getting my second test file 4 + slug: file-two 5 + tags: 6 + - ATProto 7 + - ATmosphere 8 + - decentralization 9 + title: Testing 2 - some good functions 10 + --- 11 + 12 + ## Test file information 13 + 14 + This is another test file.
+143 -48
__tests__/processMarkdown.test.ts
··· 1 - import { expect, test } from 'vitest' 2 - import { getMarkdownFile, setMarkdownFile } from '../src/processMarkdown' 1 + import { expect, test, describe } from 'vitest' 2 + import { getMarkdownFile, setMarkdownFile, addDataToMarkdownFile } from '../src/processMarkdown' 3 3 4 - test('get markdown file and its data', async () => { 5 - const fullFilePath = './__tests__/markdowns/file.md'; 6 - const result = getMarkdownFile(fullFilePath); 7 - expect(result).toHaveProperty('data'); 8 - expect(result).toHaveProperty('content'); 9 - expect(result).toHaveProperty('fileName'); 10 - expect(result).toHaveProperty('pathToFile'); 11 - expect(result.fileName).toBe('file'); 12 - expect(result.pathToFile).toBe('./__tests__/markdowns'); 13 - expect(result.data).toEqual({ 14 - "date": new Date("2026-02-15T15:01:43.100Z"), 15 - "description": "Getting my test file", 16 - "slug": "file", 17 - "tags": [ 18 - "ATProto", 19 - "ATmosphere", 20 - "decentralization", 21 - ], 22 - "title": "Testing - some good functions", 23 - }); 24 - expect(result.content).toBe(` 4 + describe('Handle reading markdown files', () => { 5 + test('get markdown file and its data', async () => { 6 + const fullFilePath = './__tests__/markdowns/file.md'; 7 + const result = getMarkdownFile(fullFilePath); 8 + expect(result).toHaveProperty('data'); 9 + expect(result).toHaveProperty('content'); 10 + expect(result).toHaveProperty('fileName'); 11 + expect(result).toHaveProperty('pathToFile'); 12 + expect(result.fileName).toBe('file'); 13 + expect(result.pathToFile).toBe('./__tests__/markdowns'); 14 + expect(result.data).toEqual({ 15 + "date": new Date("2026-02-15T15:01:43.100Z"), 16 + "description": "Getting my test file", 17 + "slug": "file", 18 + "tags": [ 19 + "ATProto", 20 + "ATmosphere", 21 + "decentralization", 22 + ], 23 + "title": "Testing - some good functions", 24 + }); 25 + expect(result.content).toBe(` 25 26 ## Test file information 26 27 27 28 This is a test file. 28 29 `); 30 + }); 29 31 }); 30 32 31 - test('set markdown file and its data with no change', async () => { 32 - const fullFilePath = './__tests__/markdowns/file.md'; 33 - const { data, content, fileName, pathToFile } = getMarkdownFile(fullFilePath); 34 - const markdownFileSet = setMarkdownFile(pathToFile, 'title', 'content', 35 - { slug: fileName, ...data, content } ); 33 + describe('Handle changing markdown files', () => { 34 + test('set markdown file and its data with no change', async () => { 35 + const fullFilePath = './__tests__/markdowns/file-two.md'; 36 + const { data, content, fileName, pathToFile } = getMarkdownFile(fullFilePath); 37 + const markdownFileSet = setMarkdownFile(pathToFile, 'title', 'content', 38 + { slug: fileName, ...data, content } ); 36 39 37 - expect(markdownFileSet).toBe(true); 38 - const result = getMarkdownFile(fullFilePath); 40 + expect(markdownFileSet).toBe(true); 41 + const result = getMarkdownFile(fullFilePath); 39 42 40 - expect(result).toHaveProperty('data'); 41 - expect(result).toHaveProperty('content'); 42 - expect(result).toHaveProperty('fileName'); 43 - expect(result).toHaveProperty('pathToFile'); 44 - expect(result.fileName).toBe('file'); 45 - expect(result.pathToFile).toBe('./__tests__/markdowns'); 46 - expect(result.data).toEqual({ 47 - "date": new Date("2026-02-15T15:01:43.100Z"), 48 - "description": "Getting my test file", 49 - "tags": [ 50 - "ATProto", 51 - "ATmosphere", 52 - "decentralization", 53 - ], 54 - "title": "Testing - some good functions", 55 - "slug": "file" 43 + expect(result).toHaveProperty('data'); 44 + expect(result).toHaveProperty('content'); 45 + expect(result).toHaveProperty('fileName'); 46 + expect(result).toHaveProperty('pathToFile'); 47 + expect(result.fileName).toBe('file-two'); 48 + expect(result.pathToFile).toBe('./__tests__/markdowns'); 49 + expect(result.data).toEqual({ 50 + "date": new Date("2026-02-15T15:01:43.100Z"), 51 + "description": "Getting my second test file", 52 + "tags": [ 53 + "ATProto", 54 + "ATmosphere", 55 + "decentralization", 56 + ], 57 + "title": "Testing 2 - some good functions", 58 + "slug": "file-two" 59 + }); 60 + expect(result.content).toBe(` 61 + ## Test file information 62 + 63 + This is another test file. 64 + `); 56 65 }); 57 - expect(result.content).toBe(` 66 + 67 + test('set markdown file and its data with change', async () => { 68 + const fullFilePath = './__tests__/markdowns/file-three.md'; 69 + const { data, content, fileName, pathToFile } = getMarkdownFile(fullFilePath); 70 + 71 + const result = getMarkdownFile(fullFilePath); 72 + 73 + expect(result).toHaveProperty('data'); 74 + expect(result).toHaveProperty('content'); 75 + expect(result).toHaveProperty('fileName'); 76 + expect(result).toHaveProperty('pathToFile'); 77 + expect(result.fileName).toBe('file-three'); 78 + expect(result.pathToFile).toBe('./__tests__/markdowns'); 79 + expect(result.data).toEqual({ 80 + "date": new Date("2026-02-15T15:01:43.100Z"), 81 + "description": "Getting my third test file", 82 + "tags": [ 83 + "ATProto", 84 + "ATmosphere", 85 + "decentralization", 86 + ], 87 + "title": "Testing 3 - some good functions", 88 + "slug": "file-three" 89 + }); 90 + expect(result.content).toBe(` 58 91 ## Test file information 59 92 60 - This is a test file. 93 + This is another test file. 61 94 `); 95 + const markdownFileSet = addDataToMarkdownFile(fullFilePath, 'title', 'content', 96 + { newData: "foobar" } ); 97 + 98 + expect(markdownFileSet).toBe(true); 99 + 100 + const resultTwo = getMarkdownFile(fullFilePath); 101 + 102 + expect(resultTwo).toHaveProperty('data'); 103 + expect(resultTwo).toHaveProperty('content'); 104 + expect(resultTwo).toHaveProperty('fileName'); 105 + expect(resultTwo).toHaveProperty('pathToFile'); 106 + expect(resultTwo.fileName).toBe('file-three'); 107 + expect(resultTwo.pathToFile).toBe('./__tests__/markdowns'); 108 + expect(resultTwo.data).toEqual({ 109 + "date": new Date("2026-02-15T15:01:43.100Z"), 110 + "description": "Getting my third test file", 111 + "tags": [ 112 + "ATProto", 113 + "ATmosphere", 114 + "decentralization", 115 + ], 116 + "title": "Testing 3 - some good functions", 117 + "slug": "file-three", 118 + "newData": "foobar" 119 + }); 120 + expect(resultTwo.content).toBe(` 121 + ## Test file information 122 + 123 + This is another test file. 124 + `); 125 + const markdownFileSetTwo = setMarkdownFile(pathToFile, 'title', 'content', 126 + { slug: fileName, ...data, content }, true ); 127 + 128 + expect(markdownFileSetTwo).toBe(true); 129 + const resultThree = getMarkdownFile(fullFilePath); 130 + 131 + expect({ slug: fileName, ...data, content }).not.toHaveProperty('newData'); 132 + expect(resultThree).toHaveProperty('data'); 133 + expect(resultThree).toHaveProperty('content'); 134 + expect(resultThree).toHaveProperty('fileName'); 135 + expect(resultThree).toHaveProperty('pathToFile'); 136 + expect(resultThree.fileName).toBe('file-three'); 137 + expect(resultThree.pathToFile).toBe('./__tests__/markdowns'); 138 + expect(resultThree.data).toEqual({ 139 + "date": new Date("2026-02-15T15:01:43.100Z"), 140 + "description": "Getting my third test file", 141 + "tags": [ 142 + "ATProto", 143 + "ATmosphere", 144 + "decentralization", 145 + ], 146 + "title": "Testing 3 - some good functions", 147 + "slug": "file-three" 148 + }); 149 + expect(resultThree.content).toBe(` 150 + ## Test file information 151 + 152 + This is another test file. 153 + `); 154 + }); 155 + 156 + 62 157 });
+4 -4
package-lock.json
··· 14 14 "@atcute/tid": "^1.1.1", 15 15 "dotenv": "^17.2.3", 16 16 "gray-matter": "^4.0.3", 17 - "json-to-markdown-file": "^1.0.0" 17 + "json-to-markdown-file": "^1.1.0" 18 18 }, 19 19 "devDependencies": { 20 20 "@types/node": "^25.2.3", ··· 1297 1297 } 1298 1298 }, 1299 1299 "node_modules/json-to-markdown-file": { 1300 - "version": "1.0.0", 1301 - "resolved": "https://registry.npmjs.org/json-to-markdown-file/-/json-to-markdown-file-1.0.0.tgz", 1302 - "integrity": "sha512-OPIzFLg2kHCLWt4pd75UZxREoGr5VhO5Zwrn5waoWcmOjmtnCWcY35W89BAikkoIXogbmLJz/lNB7IdkbRrt2w==", 1300 + "version": "1.1.0", 1301 + "resolved": "https://registry.npmjs.org/json-to-markdown-file/-/json-to-markdown-file-1.1.0.tgz", 1302 + "integrity": "sha512-t98QPHvs5re1E+YE0QAiVXSZ6nfQls7PZekUQerPTG3pmwt0PROoc8usa8Likoq0re+E1+cQTb7jllLeVtNfvA==", 1303 1303 "license": "ISC", 1304 1304 "dependencies": { 1305 1305 "gray-matter": "^4.0.3",
+1 -1
package.json
··· 28 28 "@atcute/tid": "^1.1.1", 29 29 "dotenv": "^17.2.3", 30 30 "gray-matter": "^4.0.3", 31 - "json-to-markdown-file": "^1.0.0" 31 + "json-to-markdown-file": "^1.1.0" 32 32 } 33 33 }
-92
src/index.ts
··· 129 129 console.log(`Uploaded record with rkey: ${rKey}`, resultRecord); 130 130 return {rKey, resultRecord}; 131 131 }; 132 - 133 - 134 - // Main execution function - can be called when running this file directly 135 - export const main = async () => { 136 - const config = setUpEnv(); 137 - if (!config) return; 138 - const { rpc, manager } = await connectionManager(config); 139 - const { handle } = config; 140 - 141 - //reversedHandle to make a fun example lexicon 142 - const reversedHandle = handle.split('.').reverse().map(word => word).join('.'); 143 - //An activity here is like a workout. running, walking, lifting weights, etc. 144 - const collection = `${reversedHandle}.feed.activity` 145 - 146 - //A list of activities that may be gotten from your phone or wherever, but you get the whole list everytime 147 - let activities = [] 148 - 149 - // Helper function to upload activities 150 - const uploadActivities = async (activities: any[], handle: string, collection: string, rpc: Client) => { 151 - for (const activity of activities) { 152 - //Creates that unique key from the startTime of the activity so we don't have duplicates 153 - let rKey = TID.create(activity.startTime.getTime() * 1000, CLOCK_ID); 154 - 155 - await ok(rpc.post('com.atproto.repo.putRecord', { 156 - input: { 157 - repo: handle, 158 - collection, 159 - rkey: rKey, 160 - record: activity, 161 - } 162 - })); 163 - console.log(`Uploaded activity with rkey: ${rKey}`); 164 - } 165 - } 166 - 167 - //I just finished a run, it's saved to my phone, now uploading it to the PDS 168 - activities.push({ 169 - $type: collection, 170 - type: 'run', 171 - startTime: new Date() 172 - }) 173 - 174 - console.log('You just finished a run. Uploading it to the PDS.'); 175 - 176 - //Upload my activities 177 - await uploadActivities(activities, handle, collection, rpc); 178 - 179 - //Going to generate the key here to show you can find the record from it easily if you have a date 180 - const rkey = TID.create(activities[0].startTime.getTime() * 1000, CLOCK_ID); 181 - 182 - const activityFromPDS = await ok((rpc as any).get('com.atproto.repo.getRecord', { 183 - params: { 184 - repo: handle, 185 - collection, 186 - rkey, 187 - } 188 - })) as any; 189 - 190 - console.log(`The PDS shows you went on a ${activityFromPDS.value.type} at ${activityFromPDS.value.startTime.toLocaleString()}.`); 191 - 192 - //I decide I want to go on a walk later, so I add it to the list 193 - activities.push({ 194 - $type: collection, 195 - type: 'walk', 196 - startTime: new Date() 197 - }) 198 - 199 - console.log('You just finished a walk. Uploading it to the PDS.'); 200 - 201 - //upload the new activities so the newest one can sync 202 - await uploadActivities(activities, handle, collection, rpc); 203 - 204 - const listResult = await ok((rpc as any).get('com.atproto.repo.listRecords', { 205 - params: { 206 - repo: handle, 207 - collection, 208 - limit: 10, 209 - } 210 - })) as any; 211 - 212 - console.log("Since you did an upsert you should only have 2 records even tho you uploaded 3.") 213 - console.log(`You have ${listResult.records.length} activities saved in the PDS.`); 214 - for (const recordIndex in listResult.records){ 215 - const record = listResult.records[recordIndex] 216 - console.log(`The PDS shows you went on a ${record.value.type} at ${record.value.startTime.toLocaleString()}.`); 217 - } 218 - }; 219 - 220 - // Run main function if this file is executed directly 221 - //if (require.main === module) { 222 - // main().catch(console.error); 223 - //}
+2 -2
src/processMarkdown.ts
··· 23 23 return { data, content, fileName, pathToFile }; 24 24 } 25 25 26 - export const setMarkdownFile = (pathToFile: string, titleProp: string, contentProp: string, fileData: any) => { 27 - const fileWriteResult = processObjectToMarkdown(titleProp, contentProp, pathToFile, fileData); 26 + export const setMarkdownFile = (pathToFile: string, titleProp: string, contentProp: string, fileData: any, overwriteData: boolean = false) => { 27 + const fileWriteResult = processObjectToMarkdown(titleProp, contentProp, pathToFile, fileData, false, overwriteData); 28 28 return fileWriteResult; 29 29 } 30 30