vowel 0.1.35 → 0.1.36

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [0.1.36](https://github.com/samlfair/vowel/compare/v0.1.35...v0.1.36) (2024-10-17)
6
+
7
+
8
+ ### Features
9
+
10
+ * send webmentions for urls with publish command ([e07abeb](https://github.com/samlfair/vowel/commit/e07abeb5911ab8079b4caec715eb0a463c4809cd))
11
+
5
12
  ## [0.1.35](https://github.com/samlfair/vowel/compare/v0.1.34...v0.1.35) (2024-10-17)
6
13
 
7
14
  ## [0.1.34](https://github.com/samlfair/vowel/compare/v0.1.33...v0.1.34) (2024-10-16)
package/bin.js CHANGED
@@ -22,6 +22,10 @@ const homeDir = process.cwd();
22
22
 
23
23
  const spawnArgs = ['./server.js', '--directory', homeDir];
24
24
  if (args._.includes('build')) spawnArgs.push('--build');
25
+ if (args._.includes('publish')) {
26
+ spawnArgs.push('--build')
27
+ spawnArgs.push('--publish')
28
+ };
25
29
 
26
30
  if (!args.verbose) {
27
31
  filterConsole(['jsconfig', 'vite', 'Vite', 'tsconfig', `--host`]);
@@ -50,6 +54,8 @@ child.stdout.on('data', (data) => {
50
54
  console.log(`\n\n`);
51
55
  processedFiles = 0;
52
56
  foundFiles = 0;
57
+ } else if(false) { // Toggle to true to reveal all console output
58
+ console.log(message)
53
59
  } else if (message.match('http://localhost:')) {
54
60
  const url = message.match(/http:\/\/localhost:\S+/);
55
61
  console.log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vowel",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "homepage": "https://vowel.cc",
5
5
  "author": "Sam Littlefair (https://littlefair.ca)",
6
6
  "repository": {
@@ -33,6 +33,7 @@
33
33
  "any-date-parser": "^1.5.4",
34
34
  "change-case": "^5.4.1",
35
35
  "commit-and-tag-version": "^12.4.4",
36
+ "fast-xml-parser": "^4.5.0",
36
37
  "filter-console": "^1.0.0",
37
38
  "js-yaml": "^4.1.0",
38
39
  "loading-cli": "^1.1.2",
package/server.js CHANGED
@@ -6,7 +6,7 @@ import { dirname, join } from 'path';
6
6
  import mri from 'mri';
7
7
 
8
8
  const args = mri(process.argv);
9
- const { build: isBuild, directory } = args;
9
+ const { build: isBuild, directory, publish } = args;
10
10
 
11
11
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
12
12
 
@@ -18,7 +18,8 @@ const $home = isBuild ? process.cwd() : directory;
18
18
 
19
19
  const define = {
20
20
  $home: [directory],
21
- $build: [isBuild]
21
+ $build: [isBuild],
22
+ $publish: [publish]
22
23
  };
23
24
 
24
25
  const config = {
@@ -85,7 +85,8 @@
85
85
  {:else if node.type === 'image'}
86
86
  <Image {node} />
87
87
  {:else if node.type === 'url'}
88
- <LinkPreview url={node.value} metadata={node.metadata} {format} />
88
+ <!-- {@const dummy = console.log(node)} -->
89
+ <LinkPreview url={node.url} metadata={node.metadata} {format} />
89
90
  {:else if node.type === 'listItem'}
90
91
  {@const isChecklistItem = node.checked !== null}
91
92
  {#if isChecklistItem}
@@ -18,3 +18,4 @@ export { default as resolveHomeDirPath } from './resolveHomeDirPath';
18
18
  export { default as createPageClass } from './createPageClass';
19
19
  export { default as createFolderClass } from './createFolderClass';
20
20
  export { default as isActiveLink } from './isActiveLink';
21
+ export { default as sendWebmention } from "./sendWebmention"
@@ -1,25 +1,52 @@
1
1
  import urlMetadata from 'url-metadata';
2
+ import { sendWebmention } from './';
3
+
4
+ function isURL(string) {
5
+ try {
6
+ new URL(string)
7
+ return true
8
+ } catch (e) {
9
+ return false
10
+ }
11
+ }
12
+
13
+ export default async function mutateMarkdownAST(ast, cache, webmentions) {
2
14
 
3
- export default async function mutateMarkdownAST(ast, cache) {
4
15
  const promises = ast.map(async (node) => {
5
16
  // TODO: Improve this URL regex
6
17
  if (node.type === 'paragraph') {
18
+
7
19
  // URLs
8
20
  if (
9
21
  node.children?.length === 1 &&
10
- node.children[0]?.value?.match(/^https?:\/\/\S+$/) &&
11
- !node.children[0]?.children
22
+ node.children?.[0]?.type === "link" &&
23
+ node.children?.[0]?.children?.length === 1
12
24
  ) {
13
25
  node.type = 'url';
14
- const url = node.children[0].value;
15
- if (!cache[node.value]) {
26
+ const { url } = node.children[0];
27
+ node.url = url
28
+ if (!cache[node.url]) {
16
29
  try {
17
30
  const urlObject = new URL(url);
18
31
  const response = await urlMetadata(urlObject.href, {
19
- includeResponseBody: false,
32
+ includeResponseBody: true,
20
33
  ensureSecureImageRequest: true
21
34
  });
22
35
 
36
+ console.log("UR:")
37
+
38
+
39
+ if(webmentions && !webmentions.find(webmention => webmention.target === url)) {
40
+ console.log("Sending webmention)")
41
+ const webmentionStatus = await sendWebmention(response.responseBody)
42
+ console.log({webmentionStatus})
43
+
44
+ webmentions.push({
45
+ target: url,
46
+ status: webmentionStatus
47
+ })
48
+ }
49
+
23
50
  const metadata = {
24
51
  image: response['og:image'],
25
52
  ogURL: response['og:url'],
@@ -30,6 +57,8 @@ export default async function mutateMarkdownAST(ast, cache) {
30
57
  description: response.description
31
58
  };
32
59
 
60
+
61
+
33
62
  cache[url] = metadata;
34
63
  node.metadata = metadata;
35
64
  node.value = url;
@@ -37,11 +66,11 @@ export default async function mutateMarkdownAST(ast, cache) {
37
66
  delete node.children;
38
67
  } catch (error) {
39
68
  node.value = url;
40
- // console.log(`Error on URL in content: ${url}`);
41
69
  }
42
70
  } else {
43
71
  node.metadata = cache[node.value];
44
72
  }
73
+
45
74
  } else if (node.children[0].type === 'image') {
46
75
  node.type = 'figure';
47
76
  if (node?.children[1]?.type === 'text') {
@@ -1,5 +1,5 @@
1
1
  import urlMetadata from 'url-metadata';
2
- import { regexPatterns, isObject, parseDate } from '.';
2
+ import { regexPatterns, isObject, parseDate, sendWebmention } from '.';
3
3
 
4
4
  /**
5
5
  * Description
@@ -24,7 +24,7 @@ function imputeType(value) {
24
24
  return 'other';
25
25
  }
26
26
 
27
- export default async function mutateMarkdownFrontmatter(frontmatter, cache) {
27
+ export default async function mutateMarkdownFrontmatter(frontmatter, cache, webmentions) {
28
28
  const keys = Object.keys(frontmatter);
29
29
 
30
30
  const promises = keys.map(async (key) => {
@@ -74,6 +74,7 @@ export default async function mutateMarkdownFrontmatter(frontmatter, cache) {
74
74
  break;
75
75
  }
76
76
  case 'url': {
77
+
77
78
  if (!cache[input]) {
78
79
  try {
79
80
  const url = new URL(input);
@@ -95,6 +96,15 @@ export default async function mutateMarkdownFrontmatter(frontmatter, cache) {
95
96
  og_image: url['og:image']
96
97
  };
97
98
 
99
+ if(webmentions && !webmentions.find(webmention => webmention.target === url)) {
100
+ const webmentionStatus = await sendWebmention(response.responseBody)
101
+
102
+ webmentions.push({
103
+ target: input,
104
+ status: webmentionStatus
105
+ })
106
+ }
107
+
98
108
  cache[input] = selectedMetadata;
99
109
 
100
110
  frontmatter[key] = {
@@ -90,7 +90,7 @@ function DefaultDirectory(path) {
90
90
  * @param {import('./loadCache').Cache} cache
91
91
  * @returns {Promise<Directory>}
92
92
  */
93
- async function readFolder(folderPath, parents, cache, hidden) {
93
+ async function readFolder(folderPath, parents, cache, hidden, publishedData) {
94
94
  function filterFiles(file) {
95
95
  if (file.name.startsWith('.')) return false;
96
96
  if (file.name.startsWith('README')) return false;
@@ -106,7 +106,7 @@ async function readFolder(folderPath, parents, cache, hidden) {
106
106
  console.log(`filesfound:${files.length}`);
107
107
 
108
108
  const promises = files.map(
109
- async (file) => await readFile(file, parents, cache, folderPath, hidden)
109
+ async (file) => await readFile(file, parents, cache, folderPath, hidden, publishedData)
110
110
  );
111
111
 
112
112
  const folder = (await Promise.all(promises)).reduce((acc, obj) => ({ ...acc, ...obj }), {});
@@ -125,7 +125,7 @@ async function readFolder(folderPath, parents, cache, hidden) {
125
125
  return folder;
126
126
  }
127
127
 
128
- async function readFile(file, parents, cache, folderPath, hidden) {
128
+ async function readFile(file, parents, cache, folderPath, hidden, publishedData) {
129
129
  const route = path.parse(file.name).name;
130
130
  const url = buildURL(parents, route);
131
131
  const filePath = path.join(folderPath, file.name);
@@ -138,7 +138,17 @@ async function readFile(file, parents, cache, folderPath, hidden) {
138
138
  const startLoad = performance.now();
139
139
  const shortPath = parents + '/' + file.name;
140
140
 
141
- const { ast, frontmatter, imputedProperties } = await readMarkdownFile(filePath, cache);
141
+ let filePublishedData = publishedData?.find(item => item.path === url)
142
+ if(publishedData && !filePublishedData) {
143
+ publishedData.push({
144
+ path: url,
145
+ webmentions: []
146
+ })
147
+ filePublishedData = publishedData.at(-1)
148
+ }
149
+
150
+ const { ast, frontmatter, imputedProperties } = await readMarkdownFile(filePath, cache, filePublishedData);
151
+
142
152
  const loadTime = (performance.now() - startLoad).toFixed(2);
143
153
  // console.log(`📄 ${shortPath} (${loadTime}ms)`);
144
154
  console.log('fileread');
@@ -179,7 +189,7 @@ async function readFile(file, parents, cache, folderPath, hidden) {
179
189
  const shortPath = parents + '/' + file.name;
180
190
 
181
191
  const startLoad = performance.now();
182
- const folder = await readFolder(path.join(folderPath, file.name), url, cache, hide);
192
+ const folder = await readFolder(path.join(folderPath, file.name), url, cache, hide, publishedData);
183
193
 
184
194
  const loadTime = (performance.now() - startLoad).toFixed(2);
185
195
  // console.log(`📁 ${shortPath} (${loadTime}ms)`);
@@ -211,13 +221,11 @@ async function readFile(file, parents, cache, folderPath, hidden) {
211
221
  * @param {import('./loadCache').Cache} cache
212
222
  * @returns {Promise<ProcessedFiles>}
213
223
  */
214
- export default async function processMarkdownFiles(cache) {
224
+ export default async function processMarkdownFiles(cache, publishedData) {
215
225
  /** @type {Array<string>} */
216
226
  // @ts-ignore
217
227
  const [homeDir] = $home;
218
- const folder = await readFolder(homeDir, '', cache);
219
-
220
- console.log('loaded');
228
+ const folder = await readFolder(homeDir, '', cache, null, publishedData);
221
229
 
222
230
  return { folder, finalCache: cache };
223
231
  }
@@ -110,7 +110,7 @@ function imputeTitleFromAST(ast) {
110
110
  * @returns {Promise<ParsedMarkdown>}
111
111
  * @throws {Error} Will throw an error if the file cannot be read or parsed.
112
112
  */
113
- export default async function readMarkdownFile(filePath, cache) {
113
+ export default async function readMarkdownFile(filePath, cache, publishedData) {
114
114
  const fileName = path.basename(filePath.slice(0, -3));
115
115
 
116
116
  const fileContent = await fs.readFile(filePath, 'utf8');
@@ -167,11 +167,14 @@ export default async function readMarkdownFile(filePath, cache) {
167
167
  fileName
168
168
  };
169
169
 
170
- const promises = [mutateMarkdownAST(ast.children, cache)];
170
+
171
+ const webmentions = publishedData?.webmentions
172
+
173
+ const promises = [mutateMarkdownAST(ast.children, cache, webmentions)];
171
174
 
172
175
  // Don't mutate frontmatter on the settings pages
173
176
  if (!filePath.endsWith('settings.md'))
174
- promises.push(mutateMarkdownFrontmatter(frontmatter, cache));
177
+ promises.push(mutateMarkdownFrontmatter(frontmatter, cache, webmentions));
175
178
 
176
179
  await Promise.all(promises);
177
180
 
@@ -0,0 +1,45 @@
1
+ import { XMLParser } from "fast-xml-parser"
2
+
3
+
4
+ export default async function sendWebmention(body) {
5
+ try {
6
+ const parser = new XMLParser({
7
+ unpairedTags: ["!doctype", "meta", "link", "hr", "br", "img"],
8
+ ignoreAttributes: false,
9
+ stopNodes: ["*.pre", "*.script"],
10
+ processEntities: true,
11
+ htmlEntities: true
12
+ })
13
+
14
+ let parsedData = parser.parse(body)
15
+ const webmentionEndpoint = parsedData?.html?.head?.link?.find(link => {
16
+ return link["@_rel"] === "webmention"
17
+ })?.["@_href"]
18
+
19
+ if (webmentionEndpoint) {
20
+ const params = new URLSearchParams()
21
+ params.append('source', "https://www.littlefair.ca")
22
+ params.append('target', 'https://www.vowel.cc')
23
+
24
+
25
+ const response = await fetch(webmentionEndpoint, {
26
+ method: "POST",
27
+ body: params,
28
+ headers: {
29
+ 'Content-Type': 'application/x-www-form-urlencoded'
30
+ }
31
+ })
32
+
33
+ if(response.status === 429) {
34
+ return "429"
35
+ } else if (response.status >= 200 && response.status < 300) {
36
+ return "success"
37
+ } else {
38
+ return "failure"
39
+ }
40
+ }
41
+ } catch (e) {
42
+ console.log(e)
43
+ return "error"
44
+ }
45
+ }
@@ -1,27 +1,54 @@
1
+ import {
2
+ processMarkdownFiles,
3
+ loadCache,
4
+ checkFileExists,
5
+ readMarkdownFile
6
+ } from '$lib/utilities';
7
+ import { join } from 'path';
8
+
9
+
1
10
  export const prerender = true;
2
11
 
3
- export async function GET({}) {
4
- const body = [
5
- {
6
- path: "/notes",
7
- webmentions: [
8
- {
9
- target: "https://vowel.cc/about",
10
- success: true,
11
- error: undefined
12
+ async function getPublishedData(domain) {
13
+ try {
14
+ console.log({domain})
15
+ const publishedURL = new URL("/$vowel/published.json", domain)
16
+ const publishedDataResponse = await fetch(publishedURL.href)
17
+ const publushedDataJSON = await publishedDataResponse.json()
18
+ return publushedDataJSON
19
+ } catch (e) {
20
+ console.log(e)
21
+ return undefined
22
+ }
23
+ }
24
+
25
+ export async function GET() {
26
+ // everything.request.url
27
+
28
+ let settings
29
+
30
+ const settingsPath = join($home[0], "/settings.md")
31
+ const settingsExists = await checkFileExists(settingsPath);
32
+ if(settingsExists) {
33
+ settings = await readMarkdownFile(settingsPath)
34
+ console.log({settings})
35
+ }
12
36
 
13
- }
14
- ]
15
- }
16
- ]
37
+ // Get published articles from live site
38
+ const publishedData = await getPublishedData(settings.frontmatter.domain)
17
39
 
40
+ if($publish[0]) {
41
+ const initialCache = await loadCache($home[0]);
42
+
43
+ const { folder: website } = await processMarkdownFiles(initialCache, publishedData);
44
+ }
18
45
 
19
46
  const headers = {
20
- 'Cache-Control': 'max-age=0, s-maxage=3600',
47
+ 'Cache-Control': 'max-age=0, s-maxage=1',
21
48
  'Content-Type': 'text/plain'
22
49
  };
23
50
 
24
- const response = new Response(JSON.stringify(body), {
51
+ const response = new Response(JSON.stringify(publishedData), {
25
52
  headers
26
53
  });
27
54
  return response;
@@ -1,11 +1,9 @@
1
- import { loadCache, writeCache, processMarkdownFiles } from '$lib/utilities';
1
+ import { loadCache, writeCache, processMarkdownFiles, readMarkdownFile } from '$lib/utilities';
2
2
  import { access } from 'fs/promises';
3
3
  import { constants } from 'fs';
4
4
  import { normalize, join, basename } from 'path';
5
5
  import { dev } from '$app/environment';
6
6
  import mri from 'mri';
7
- import { getPage } from '../../lib/utilities';
8
- import { error } from '@sveltejs/kit';
9
7
 
10
8
  const args = mri(process.argv);
11
9
  const isBuild = args._.includes('build');
@@ -25,6 +23,8 @@ async function checkFileExists(filePath, homeDir) {
25
23
  }
26
24
  }
27
25
 
26
+
27
+
28
28
  /** @type {import('./$types').PageLoad} */
29
29
  export async function load() {
30
30
  // const startLoad = performance.now();
@@ -51,6 +51,9 @@ export async function load() {
51
51
  }
52
52
  };
53
53
 
54
+
55
+
56
+
54
57
  let data = {};
55
58
 
56
59
  if (contentCache) {
@@ -69,8 +72,6 @@ export async function load() {
69
72
  writeCache(hotURLCache, $home[0]);
70
73
  }
71
74
 
72
- // console.log(`Load time: ${(performance.now() - startLoad).toFixed(2)} ms`);
73
-
74
75
  const folderName = basename($home[0]);
75
76
 
76
77
  return { website, homeDir: $home[0], folderName, files, dev };