vowel 0.1.42 → 0.1.44
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 +10 -0
- package/bin.js +2 -2
- package/package.json +1 -1
- package/src/lib/components/Page.svelte +0 -2
- package/src/lib/utilities/getMetadata.js +46 -0
- package/src/lib/utilities/index.js +2 -1
- package/src/lib/utilities/mutateMarkdownAST.js +23 -46
- package/src/lib/utilities/mutateMarkdownFrontmatter.js +23 -53
- package/src/lib/utilities/processMarkdownFiles.js +22 -10
- package/src/lib/utilities/sendWebmention.js +11 -22
- package/src/routes/$vowel/published.json/+server.js +7 -9
- /package/src/routes/{+layout.server.js → [...path]/+layout.server.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
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.44](https://github.com/samlfair/vowel/compare/v0.1.43...v0.1.44) (2024-10-21)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* metadata retrieval bug ([566f7cf](https://github.com/samlfair/vowel/commit/566f7cf64be2749e30cddcb474f6385638c408a9))
|
|
11
|
+
* webmentions bug ([2215ff5](https://github.com/samlfair/vowel/commit/2215ff5f5668d140535f6df3d30754fd685f22ae))
|
|
12
|
+
|
|
13
|
+
## [0.1.43](https://github.com/samlfair/vowel/compare/v0.1.42...v0.1.43) (2024-10-18)
|
|
14
|
+
|
|
5
15
|
## [0.1.42](https://github.com/samlfair/vowel/compare/v0.1.41...v0.1.42) (2024-10-18)
|
|
6
16
|
|
|
7
17
|
|
package/bin.js
CHANGED
|
@@ -28,7 +28,7 @@ if (args._.includes('publish')) {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
if (!args.verbose) {
|
|
31
|
-
filterConsole(['jsconfig', 'vite', 'Vite', 'tsconfig', `--host`]);
|
|
31
|
+
// filterConsole(['jsconfig', 'vite', 'Vite', 'tsconfig', `--host`]);
|
|
32
32
|
console.log(`\n\n`);
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -54,7 +54,7 @@ child.stdout.on('data', (data) => {
|
|
|
54
54
|
console.log(`\n\n`);
|
|
55
55
|
processedFiles = 0;
|
|
56
56
|
foundFiles = 0;
|
|
57
|
-
} else if(
|
|
57
|
+
} else if(true) { // Toggle to true to reveal all console output
|
|
58
58
|
console.log(message)
|
|
59
59
|
} else if (message.match('http://localhost:')) {
|
|
60
60
|
const url = message.match(/http:\/\/localhost:\S+/);
|
package/package.json
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { XMLParser } from "fast-xml-parser"
|
|
2
|
+
import urlMetadata from 'url-metadata';
|
|
3
|
+
|
|
4
|
+
export default async function getMetadata({ cache, url }) {
|
|
5
|
+
if (!cache[url]) {
|
|
6
|
+
try {
|
|
7
|
+
const urlObject = new URL(url);
|
|
8
|
+
const allMetadata = await urlMetadata(urlObject.href, {
|
|
9
|
+
includeResponseBody: true,
|
|
10
|
+
ensureSecureImageRequest: true
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const parser = new XMLParser({
|
|
14
|
+
unpairedTags: ["!doctype", "meta", "link", "hr", "br", "img"],
|
|
15
|
+
ignoreAttributes: false,
|
|
16
|
+
stopNodes: ["*.pre", "*.script"],
|
|
17
|
+
processEntities: true,
|
|
18
|
+
htmlEntities: true
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
let parsedData = parser.parse(allMetadata.responseBody)
|
|
22
|
+
const webmentionEndpoint = parsedData?.html?.head?.link?.find(link => {
|
|
23
|
+
return link["@_rel"] === "webmention"
|
|
24
|
+
})?.["@_href"]
|
|
25
|
+
|
|
26
|
+
const metadata = {
|
|
27
|
+
image: allMetadata['og:image'],
|
|
28
|
+
ogURL: allMetadata['og:url'],
|
|
29
|
+
canonicalURL: allMetadata.canonical,
|
|
30
|
+
title: allMetadata.title,
|
|
31
|
+
ogTitle: allMetadata['og:title'],
|
|
32
|
+
author: allMetadata.author,
|
|
33
|
+
description: allMetadata.description,
|
|
34
|
+
webmentionEndpoint
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
cache[url] = metadata;
|
|
38
|
+
|
|
39
|
+
return { metadata }
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.log({ fetchingMetadataError: error })
|
|
42
|
+
return { metadata: undefined }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { metadata: cache[url] }
|
|
46
|
+
}
|
|
@@ -18,4 +18,5 @@ 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"
|
|
21
|
+
export { default as sendWebmention } from "./sendWebmention"
|
|
22
|
+
export { default as getMetadata } from "./getMetadata"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { sendWebmention } from './';
|
|
1
|
+
import { sendWebmention, getMetadata } from './';
|
|
3
2
|
|
|
4
3
|
function isURL(string) {
|
|
5
4
|
try {
|
|
@@ -10,8 +9,9 @@ function isURL(string) {
|
|
|
10
9
|
}
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
export default async function mutateMarkdownAST(ast, cache, webmentions, pageURL) {
|
|
14
12
|
|
|
13
|
+
|
|
14
|
+
export default async function mutateMarkdownAST(ast, cache, webmentions, pageURL) {
|
|
15
15
|
const promises = ast.map(async (node) => {
|
|
16
16
|
// TODO: Improve this URL regex
|
|
17
17
|
if (node.type === 'paragraph') {
|
|
@@ -25,55 +25,32 @@ export default async function mutateMarkdownAST(ast, cache, webmentions, pageURL
|
|
|
25
25
|
node.type = 'url';
|
|
26
26
|
const { url } = node.children[0];
|
|
27
27
|
node.url = url
|
|
28
|
-
if (!cache[node.url]) {
|
|
29
|
-
try {
|
|
30
|
-
const urlObject = new URL(url);
|
|
31
|
-
const response = await urlMetadata(urlObject.href, {
|
|
32
|
-
includeResponseBody: true,
|
|
33
|
-
ensureSecureImageRequest: true
|
|
34
|
-
});
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
console.log({ webmentions })
|
|
38
|
-
console.log(webmentions.find(webmention => webmention.target === url))
|
|
29
|
+
const { metadata } = await getMetadata({ cache, url })
|
|
39
30
|
|
|
31
|
+
node.metadata = metadata;
|
|
32
|
+
node.value = url;
|
|
33
|
+
delete node.children;
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
35
|
+
try {
|
|
36
|
+
console.log("Looking for webmentions")
|
|
37
|
+
if (webmentions && !webmentions.find(webmention => webmention.target === url)) {
|
|
38
|
+
console.log("Found new page")
|
|
39
|
+
webmentions.push({
|
|
40
|
+
target: url,
|
|
41
|
+
status: "new"
|
|
42
|
+
})
|
|
43
|
+
} else if (webmentions) {
|
|
44
|
+
console.log("Found new webmention")
|
|
45
|
+
const webmention = webmentions.find(webmention => webmention.target === url)
|
|
46
|
+
if (webmention.status === "new" || webmention.status === "failure" || webmention.status === "429") {
|
|
47
|
+
console.log("Sending webmention")
|
|
48
|
+
webmention.status = await sendWebmention({ endpoint: metadata.webmentionEndpoint, target: webmention.target, source: pageURL })
|
|
51
49
|
}
|
|
52
|
-
|
|
53
|
-
const metadata = {
|
|
54
|
-
image: response['og:image'],
|
|
55
|
-
ogURL: response['og:url'],
|
|
56
|
-
canonicalURL: response.canonical,
|
|
57
|
-
title: response.title,
|
|
58
|
-
ogTitle: response['og:title'],
|
|
59
|
-
author: response.author,
|
|
60
|
-
description: response.description
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
cache[url] = metadata;
|
|
66
|
-
node.metadata = metadata;
|
|
67
|
-
node.value = url;
|
|
68
|
-
|
|
69
|
-
delete node.children;
|
|
70
|
-
} catch (error) {
|
|
71
|
-
node.value = url;
|
|
72
50
|
}
|
|
73
|
-
}
|
|
74
|
-
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.log({ webmentionError: error })
|
|
75
53
|
}
|
|
76
|
-
|
|
77
54
|
} else if (node.children[0].type === 'image') {
|
|
78
55
|
node.type = 'figure';
|
|
79
56
|
if (node?.children[1]?.type === 'text') {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import urlMetadata from 'url-metadata';
|
|
2
|
-
import { regexPatterns, isObject, parseDate, sendWebmention } from '.';
|
|
2
|
+
import { regexPatterns, isObject, parseDate, sendWebmention, getMetadata } from '.';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Description
|
|
@@ -74,62 +74,32 @@ export default async function mutateMarkdownFrontmatter(frontmatter, cache, webm
|
|
|
74
74
|
break;
|
|
75
75
|
}
|
|
76
76
|
case 'url': {
|
|
77
|
+
const { metadata } = await getMetadata({
|
|
78
|
+
cache,
|
|
79
|
+
url: input
|
|
80
|
+
})
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
ensureSecureImageRequest: true
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const selectedMetadata = {
|
|
87
|
-
url: metadata.url,
|
|
88
|
-
canonical: metadata.canonical,
|
|
89
|
-
title: metadata.title,
|
|
90
|
-
image: metadata.image,
|
|
91
|
-
favicons: url.favicons,
|
|
92
|
-
og_url: url['og:url'],
|
|
93
|
-
og_title: url['og:title'],
|
|
94
|
-
og_description: url['og:description'],
|
|
95
|
-
og_site_name: url['og:side_name'],
|
|
96
|
-
og_image: url['og:image']
|
|
97
|
-
};
|
|
82
|
+
frontmatter[key] = {
|
|
83
|
+
type: 'url',
|
|
84
|
+
output: metadata || input,
|
|
85
|
+
input
|
|
86
|
+
};
|
|
98
87
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
88
|
+
try {
|
|
89
|
+
if (webmentions && !webmentions.find(webmention => webmention.target === input)) {
|
|
90
|
+
webmentions.push({
|
|
91
|
+
target: input,
|
|
92
|
+
status: "new"
|
|
93
|
+
})
|
|
94
|
+
} else if (webmentions) {
|
|
95
|
+
const webmention = webmentions.find(webmention => webmention.target === input)
|
|
96
|
+
if (webmention.status === "new") {
|
|
97
|
+
webmention.status = await sendWebmention({ endpoint: metadata.webmentionEndpoint, target: webmention.target, source: pageURL })
|
|
109
98
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
cache[input] = selectedMetadata;
|
|
113
|
-
|
|
114
|
-
frontmatter[key] = {
|
|
115
|
-
type: 'url',
|
|
116
|
-
output: selectedMetadata,
|
|
117
|
-
input
|
|
118
|
-
};
|
|
119
|
-
} catch (e) {
|
|
120
|
-
console.error(`Error on URL in frontmatter: ${frontmatter[key]}`);
|
|
121
|
-
frontmatter[key] = {
|
|
122
|
-
type: 'string',
|
|
123
|
-
output: input,
|
|
124
|
-
input
|
|
125
|
-
};
|
|
126
99
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
output: cache[input],
|
|
131
|
-
input
|
|
132
|
-
};
|
|
100
|
+
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.log({ webmentionsError: error })
|
|
133
103
|
}
|
|
134
104
|
break;
|
|
135
105
|
}
|
|
@@ -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, publishedData) {
|
|
93
|
+
async function readFolder(folderPath, parents, cache, hidden, publishedData, domain) {
|
|
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, publishedData) {
|
|
|
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, publishedData)
|
|
109
|
+
async (file) => await readFile(file, parents, cache, folderPath, hidden, publishedData, domain)
|
|
110
110
|
);
|
|
111
111
|
|
|
112
112
|
const folder = (await Promise.all(promises)).reduce((acc, obj) => ({ ...acc, ...obj }), {});
|
|
@@ -125,16 +125,28 @@ async function readFolder(folderPath, parents, cache, hidden, publishedData) {
|
|
|
125
125
|
return folder;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
function buildHREF(url, domain) {
|
|
129
|
+
try {
|
|
130
|
+
const urlObj = new URL(domain)
|
|
131
|
+
urlObj.pathname = url
|
|
132
|
+
return urlObj.href
|
|
133
|
+
} catch (e) {
|
|
134
|
+
return undefined
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function readFile(file, parents, cache, folderPath, hidden, publishedData, domain) {
|
|
129
140
|
const route = path.parse(file.name).name;
|
|
130
141
|
const url = buildURL(parents, route);
|
|
131
142
|
const filePath = path.join(folderPath, file.name);
|
|
132
|
-
|
|
143
|
+
|
|
133
144
|
const extension = path.extname(file.name);
|
|
134
|
-
|
|
145
|
+
|
|
135
146
|
const hide = (file.name.startsWith('$') && file.name.length > 1) || hidden;
|
|
136
|
-
|
|
147
|
+
|
|
137
148
|
if (file.isFile() && extension === '.md') {
|
|
149
|
+
const href = buildHREF(url, domain)
|
|
138
150
|
const startLoad = performance.now();
|
|
139
151
|
const shortPath = parents + '/' + file.name;
|
|
140
152
|
|
|
@@ -147,7 +159,7 @@ async function readFile(file, parents, cache, folderPath, hidden, publishedData)
|
|
|
147
159
|
filePublishedData = publishedData.at(-1)
|
|
148
160
|
}
|
|
149
161
|
|
|
150
|
-
const { ast, frontmatter, imputedProperties } = await readMarkdownFile(filePath, cache, filePublishedData,
|
|
162
|
+
const { ast, frontmatter, imputedProperties } = await readMarkdownFile(filePath, cache, filePublishedData, href);
|
|
151
163
|
|
|
152
164
|
const loadTime = (performance.now() - startLoad).toFixed(2);
|
|
153
165
|
// console.log(`📄 ${shortPath} (${loadTime}ms)`);
|
|
@@ -189,7 +201,7 @@ async function readFile(file, parents, cache, folderPath, hidden, publishedData)
|
|
|
189
201
|
const shortPath = parents + '/' + file.name;
|
|
190
202
|
|
|
191
203
|
const startLoad = performance.now();
|
|
192
|
-
const folder = await readFolder(path.join(folderPath, file.name), url, cache, hide, publishedData);
|
|
204
|
+
const folder = await readFolder(path.join(folderPath, file.name), url, cache, hide, publishedData, domain);
|
|
193
205
|
|
|
194
206
|
const loadTime = (performance.now() - startLoad).toFixed(2);
|
|
195
207
|
// console.log(`📁 ${shortPath} (${loadTime}ms)`);
|
|
@@ -221,11 +233,11 @@ async function readFile(file, parents, cache, folderPath, hidden, publishedData)
|
|
|
221
233
|
* @param {import('./loadCache').Cache} cache
|
|
222
234
|
* @returns {Promise<ProcessedFiles>}
|
|
223
235
|
*/
|
|
224
|
-
export default async function processMarkdownFiles(cache, publishedData) {
|
|
236
|
+
export default async function processMarkdownFiles(cache, publishedData, domain) {
|
|
225
237
|
/** @type {Array<string>} */
|
|
226
238
|
// @ts-ignore
|
|
227
239
|
const [homeDir] = $home;
|
|
228
|
-
const folder = await readFolder(homeDir, '', cache, null, publishedData);
|
|
240
|
+
const folder = await readFolder(homeDir, '', cache, null, publishedData, domain);
|
|
229
241
|
|
|
230
242
|
return { folder, finalCache: cache };
|
|
231
243
|
}
|
|
@@ -1,28 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export default async function sendWebmention({ body, source, target }) {
|
|
1
|
+
export default async function sendWebmention({ endpoint, source, target }) {
|
|
5
2
|
try {
|
|
6
|
-
|
|
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) {
|
|
3
|
+
if (endpoint) {
|
|
20
4
|
const params = new URLSearchParams()
|
|
21
5
|
params.append('source', source)
|
|
22
6
|
params.append('target', target)
|
|
23
7
|
|
|
24
|
-
|
|
25
|
-
const response = await fetch(webmentionEndpoint, {
|
|
8
|
+
const response = await fetch(endpoint, {
|
|
26
9
|
method: "POST",
|
|
27
10
|
body: params,
|
|
28
11
|
headers: {
|
|
@@ -30,16 +13,22 @@ export default async function sendWebmention({ body, source, target }) {
|
|
|
30
13
|
}
|
|
31
14
|
})
|
|
32
15
|
|
|
16
|
+
console.log({
|
|
17
|
+
source, target,
|
|
18
|
+
webmentionResponse: await response.json()
|
|
19
|
+
})
|
|
20
|
+
|
|
33
21
|
if(response.status === 429) {
|
|
34
22
|
return "429"
|
|
35
23
|
} else if (response.status >= 200 && response.status < 300) {
|
|
36
24
|
return "success"
|
|
37
25
|
} else {
|
|
26
|
+
console.log({ failure: await response.json() })
|
|
38
27
|
return "failure"
|
|
39
28
|
}
|
|
40
29
|
}
|
|
41
|
-
} catch (
|
|
42
|
-
console.log(
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.log({ error })
|
|
43
32
|
return "error"
|
|
44
33
|
}
|
|
45
34
|
}
|
|
@@ -25,22 +25,22 @@ export async function GET() {
|
|
|
25
25
|
// everything.request.url
|
|
26
26
|
|
|
27
27
|
let settings
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
const settingsPath = join($home[0], "/settings.md")
|
|
30
30
|
const settingsExists = await checkFileExists(settingsPath);
|
|
31
|
-
if(settingsExists) {
|
|
31
|
+
if (settingsExists) {
|
|
32
32
|
settings = await readMarkdownFile(settingsPath)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Get published articles from live site
|
|
36
36
|
const publishedData = await getPublishedData(settings.frontmatter.domain)
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if($publish[0]) {
|
|
38
|
+
// if($publish[0]) {
|
|
39
|
+
if (true) {
|
|
41
40
|
const initialCache = await loadCache($home[0]);
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
const { folder: website } = await processMarkdownFiles(initialCache, publishedData, settings.frontmatter.domain);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const headers = {
|
|
@@ -48,8 +48,6 @@ export async function GET() {
|
|
|
48
48
|
'Content-Type': 'text/plain'
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
console.log({ publishedData })
|
|
52
|
-
|
|
53
51
|
const response = new Response(JSON.stringify(publishedData), {
|
|
54
52
|
headers
|
|
55
53
|
});
|
|
File without changes
|