Merge pull request #157 from TxtDot/dev

Add jsx support to engines plugins, fix text output
This commit is contained in:
Andrey 2024-05-14 13:27:35 +00:00 committed by GitHub
commit a9abfd0889
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 490 additions and 106 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 TxtDot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 Artemy
Copyright (c) 2024 TxtDot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,6 +1,6 @@
{
"name": "@txtdot/plugins",
"version": "1.1.1",
"version": "2.0.0",
"description": "Official txtdot plugins",
"main": "dist/lib.js",
"types": "dist/lib.d.ts",
@ -19,9 +19,12 @@
"license": "MIT",
"dependencies": {
"@mozilla/readability": "^0.5.0",
"@txtdot/sdk": "workspace:*"
"@txtdot/sdk": "workspace:*",
"html-to-text": "^9.0.5",
"linkedom": "^0.18.0"
},
"devDependencies": {
"@types/html-to-text": "^9.0.4",
"typescript": "^5.4.5"
}
}

View File

@ -0,0 +1,35 @@
import { JSX } from '@txtdot/sdk';
export function PageFooter({
page,
previous,
next,
}: {
page: number;
previous: string | null;
next: string | null;
}) {
return (
<>
{page !== 1 ? <a href={previous}>Previous </a> : <></>}| {page} |
<a href={next}> Next</a>
</>
);
}
export function ResultItem({
url,
title,
content,
}: {
url: string;
title: string;
content: string;
}) {
return (
<>
<a href={url}>{title}</a>
<p>{content}</p>
</>
);
}

View File

@ -1,7 +1,7 @@
import { Readability as OReadability } from '@mozilla/readability';
import { EngineParseError } from '@txtdot/sdk/dist/types/errors';
import { Engine } from '@txtdot/sdk';
import { Engine, EngineParseError } from '@txtdot/sdk';
import { parseHTML } from 'linkedom';
const Readability = new Engine(
'Readability',
@ -10,7 +10,7 @@ const Readability = new Engine(
);
Readability.route('*path', async (input, ro) => {
const reader = new OReadability(input.parseDom().window.document);
const reader = new OReadability(input.document);
const parsed = reader.parse();
if (!parsed) {
@ -19,7 +19,6 @@ Readability.route('*path', async (input, ro) => {
return {
content: parsed.content,
textContent: parsed.textContent,
title: parsed.title,
lang: parsed.lang,
};

View File

@ -1,5 +1,7 @@
import { Engine } from '@txtdot/sdk';
import { HandlerInput, Route } from '@txtdot/sdk/dist/types/handler';
import { Engine, JSX } from '@txtdot/sdk';
import { HandlerInput, Route } from '@txtdot/sdk';
import { parseHTML } from 'linkedom';
import { PageFooter, ResultItem } from '../components/searchers';
const SearX = new Engine('SearX', "Engine for searching with 'SearXNG'", [
'searx.*',
@ -9,17 +11,23 @@ async function search(
input: HandlerInput,
ro: Route<{ search: string; pageno?: string }>
) {
const document = input.parseDom().window.document;
const document = input.document;
const search = ro.q.search;
const page = parseInt(ro.q.pageno || '1');
const page_footer = `${
page !== 1
? `<a href="${ro.reverse({ search, pageno: page - 1 })}">Previous </a>|`
: ''
}<a href="${ro.reverse({ search, pageno: page + 1 })}"> Next</a>`;
let previous: string | null;
let next: string | null;
if (ro.q.pageno) {
previous = ro.reverse({ search, pageno: page - 1 }) || null;
next = ro.reverse({ search, pageno: page + 1 }) || null;
} else {
previous = null;
next = `/search?q=${search}&pageno=${page + 1}`;
}
const articles = Array.from(document.querySelectorAll('.result'));
const articles_parsed = articles.map((a) => {
const parsed = {
url:
@ -33,22 +41,18 @@ async function search(
.textContent || '',
};
return {
html: `<a href="${parsed.url}">${parsed.title}</a><p>${parsed.content}</p><hr>`,
text: `${parsed.title} (${parsed.url})\n${parsed.content}\n---\n\n`,
};
return <ResultItem {...parsed} />;
});
const content = `${articles_parsed
.map((a) => a.html)
.join('')}${page_footer}`;
const textContent = articles_parsed.map((a) => a.text).join('');
const content = (
<>
{articles_parsed}
<PageFooter page={page} previous={previous} next={next} />
</>
);
return {
content,
textContent,
title: `${search} - Searx - Page ${page}`,
lang: document.documentElement.lang,
content: content,
title: `"${(document.getElementById('q') as HTMLInputElement).value}" - Searx - Page ${page}`,
};
}

View File

@ -1,10 +1,11 @@
import { HandlerInput, Route } from '@txtdot/sdk/dist/types/handler';
import { HandlerInput, Route } from '@txtdot/sdk';
import { JSX } from '@txtdot/sdk';
async function questions(
input: HandlerInput,
ro: Route<{ id: string; slug: string }>
) {
const document = input.parseDom().window.document;
const document = input.document;
const questionEl = document.getElementById('question');
const question = postParser(questionEl);
@ -15,12 +16,15 @@ async function questions(
const answers = allAnswers.map((a) => postParser(a));
return {
content: `${question}<hr>${answers.length} answers <hr>${answers.join(
'<hr>'
)}`,
textContent: `${ro.q.id}/${ro.q.slug}\nText output not supported`, // TODO
content: (
<>
{question}
<hr />
{answers.length} answers <hr />
{answers.join(<hr />)}
</>
),
title,
lang: document.documentElement.lang,
};
}
@ -37,12 +41,27 @@ function postParser(el: Element | null): string {
(el.querySelector('.user-details a') as HTMLAnchorElement)?.href || '';
const userTitle = el.querySelector('.user-action-time')?.textContent || '';
return `<h4>${userTitle}${
userUrl ? ` by <a href="${userUrl}">${userName}</a>` : ''
}</h4>`;
return (
<h4>
{userTitle}
{userUrl ? (
<>
by <a href={userUrl}>{userName}</a>
</>
) : (
<></>
)}
</h4>
);
});
return `<h3>${voteCount} votes</h3>${body}${footer.join('')}`;
return (
<>
<h3>{voteCount} votes</h3>
{body}
{footer}
</>
);
}
export default questions;

View File

@ -1,10 +1,11 @@
import { HandlerInput, Route } from '@txtdot/sdk/dist/types/handler';
import { HandlerInput, Route } from '@txtdot/sdk';
import { JSX } from '@txtdot/sdk';
async function users(
input: HandlerInput,
ro: Route<{ id: string; slug: string }>
) {
const document = input.parseDom().window.document;
const document = input.document;
const userInfo =
document.querySelector('.md\\:ai-start > div:nth-child(2)')?.textContent ||
@ -21,15 +22,27 @@ async function users(
const type =
el.querySelector('.iconAnswer, .iconQuestion')?.textContent || '';
return `<strong>${type} (${votes}) </strong><a href="${url}">${title}</a>`;
return (
<>
<strong>
{type} ({votes}){' '}
</strong>
<a href={url}>{title}</a>
</>
);
})
.join('<br/>');
.join(<br />);
return {
content: `${userInfo}<hr><h3>Top Posts</h3>${topPosts}`,
textContent: `${ro.q.id}/${ro.q.slug}\n`, // TODO
content: (
<>
{userInfo}
<hr />
<h3>Top Posts</h3>
{topPosts}
</>
),
title: document.querySelector('title')?.textContent || '',
lang: document.documentElement.lang,
};
}

View File

@ -7,3 +7,12 @@ export const engineList = [
engines.SearX,
engines.Readability,
];
import { compile } from 'html-to-text';
export const html2text = compile({
longWordSplit: {
forceWrapOnLimit: true,
},
selectors: [{ selector: 'img', format: 'skip' }],
});

View File

@ -3,9 +3,9 @@
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */,
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
"incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */,
// "composite": true /* Enable constraints that allow a TypeScript project to be used with project references. */,
"tsBuildInfoFile": "./.tsbuildinfo" /* Specify the path to .tsbuildinfo incremental compilation file. */,
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
@ -13,13 +13,13 @@
/* Language and Environment */
"target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
"jsx": "react" /* Specify what JSX code is generated. */,
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
"reactNamespace": "JSX" /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */,
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 Artemy
Copyright (c) 2024 TxtDot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,6 +1,6 @@
{
"name": "@txtdot/sdk",
"version": "1.1.1",
"version": "2.0.0",
"description": "SDK for creating plugins for TxtDot",
"main": "dist/lib.js",
"types": "dist/lib.d.ts",
@ -18,7 +18,7 @@
],
"license": "MIT",
"dependencies": {
"linkedom": "^0.16.11",
"linkedom": "^0.18.0",
"route-parser": "^0.0.5"
},
"devDependencies": {

View File

@ -2,9 +2,9 @@ import Route from 'route-parser';
import {
HandlerInput,
IHandlerOutput,
EngineFunction,
RouteValues,
EngineOutput,
} from './types/handler';
import { NoHandlerFoundError } from './types/errors';
@ -33,7 +33,7 @@ export class Engine {
this.routes.push({ route: new Route<TParams>(path), handler });
}
async handle(input: HandlerInput): Promise<IHandlerOutput> {
async handle(input: HandlerInput): Promise<EngineOutput> {
const url = new URL(input.getUrl());
const path = url.pathname + url.search + url.hash;
for (const route of this.routes) {

35
packages/sdk/src/jsx.ts Normal file
View File

@ -0,0 +1,35 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-namespace */
export namespace JSX {
export type Element = string;
export interface IntrinsicElements {
[elemName: string]: any;
}
}
export function createElement(
name: any,
props: { [id: string]: any },
...inner: (string | string[])[]
) {
const content = inner.flat().join('');
if (typeof name === 'string') {
props = props || {};
const propsstr = Object.keys(props)
.map((key) => {
const value = props[key];
if (key === 'className') return `class=${value}`;
else return `${key}=${value}`;
})
.join(' ');
return inner.length === 0
? `<${name} ${propsstr}/>`
: `<${name} ${propsstr}>${content}</${name}>`;
} else if (typeof name === 'function') {
return name(props, content);
} else {
return content;
}
}

View File

@ -1,3 +1,38 @@
import { Engine } from './engine';
export { Engine };
import {
EngineParseError,
NoHandlerFoundError,
TxtDotError,
} from './types/errors';
import {
EngineFunction,
EngineMatch,
Engines,
RouteValues,
EnginesMatch,
HandlerInput,
HandlerOutput,
Route,
handlerSchema,
} from './types/handler';
import * as JSX from './jsx';
export {
Engine,
EngineParseError,
NoHandlerFoundError,
TxtDotError,
EngineFunction,
EngineMatch,
Engines,
RouteValues,
EnginesMatch,
HandlerInput,
HandlerOutput,
Route,
handlerSchema,
JSX,
};

View File

@ -4,7 +4,7 @@ import { Engine } from '../engine';
export class HandlerInput {
private data: string;
private url: string;
private dom?: Window;
private window?: Window;
constructor(data: string, url: string) {
this.data = data;
@ -15,19 +15,26 @@ export class HandlerInput {
return this.url;
}
parseDom(): Window {
if (this.dom) {
return this.dom;
get document(): Document {
if (this.window) {
return this.window.document;
}
this.dom = parseHTML(this.data);
return this.dom;
this.window = parseHTML(this.data);
return this.window.document;
}
}
export interface IHandlerOutput {
export interface HandlerOutput {
content: string;
textContent: string;
title: string;
lang: string;
}
export interface EngineOutput {
content: string;
textContent?: string;
title?: string;
lang?: string;
}
@ -66,7 +73,7 @@ export interface RouteValues {
export type EngineFunction<TParams extends RouteValues> = (
input: HandlerInput,
ro: Route<TParams>
) => Promise<IHandlerOutput>;
) => Promise<EngineOutput>;
export type EnginesMatch<TParams extends RouteValues> = EngineMatch<TParams>[];

View File

@ -3,9 +3,9 @@
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */,
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
"incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */,
// "composite": true /* Enable constraints that allow a TypeScript project to be used with project references. */,
"tsBuildInfoFile": "./.tsbuildinfo" /* Specify the path to .tsbuildinfo incremental compilation file. */,
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */

View File

@ -1,6 +1,6 @@
{
"name": "@txtdot/server",
"version": "1.7.0",
"version": "1.8.0",
"private": true,
"description": "txtdot is an HTTP proxy that parses only text, links and pictures from pages reducing internet bandwidth usage, removing ads and heavy scripts",
"main": "dist/app.js",
@ -18,6 +18,7 @@
"dev": "tsc-watch --onSuccess \"node ./dist/src/app.js\""
},
"dependencies": {
"@fastify/one-line-logger": "^1.3.0",
"@fastify/static": "^7.0.3",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^3.0.0",
@ -32,7 +33,7 @@
"iconv-lite": "^0.6.3",
"ip-range-check": "^0.2.0",
"json-schema-to-ts": "^3.0.1",
"linkedom": "^0.16.11",
"linkedom": "^0.18.0",
"micromatch": "^4.0.5",
"sharp": "^0.33.3"
},

View File

@ -23,7 +23,11 @@ import config from './config';
class App {
async init() {
const fastify = Fastify({
logger: true,
logger: {
transport: {
target: '@fastify/one-line-logger',
},
},
trustProxy: config.env.reverse_proxy,
connectionTimeout: config.env.timeout,
});

View File

@ -1,5 +1,5 @@
import { IAppConfig } from '../types/appConfig';
import { engineList } from '@txtdot/plugins';
import { IAppConfig } from '../types/pluginConfig';
import { engineList, html2text } from '@txtdot/plugins';
/**
* Configuration of plugins
@ -7,6 +7,7 @@ import { engineList } from '@txtdot/plugins';
*/
const plugin_config: IAppConfig = {
engines: [...engineList],
html2text,
};
export default plugin_config;

View File

@ -5,11 +5,12 @@ import { Readable } from 'stream';
import { NotHtmlMimetypeError } from './errors/main';
import { decodeStream, parseEncodingName } from './utils/http';
import replaceHref from './utils/replace-href';
import { parseHTML } from 'linkedom';
import { Engine } from '@txtdot/sdk';
import { HandlerInput, IHandlerOutput } from '@txtdot/sdk/dist/types/handler';
import { HandlerInput, HandlerOutput } from '@txtdot/sdk';
import config from './config';
import { parseHTML } from 'linkedom';
import { html2text } from './utils/html2text';
interface IEngineId {
[key: string]: number;
@ -32,7 +33,7 @@ export class Distributor {
requestUrl: URL, // proxy URL
engineName?: string,
redirectPath: string = 'get'
): Promise<IHandlerOutput> {
): Promise<HandlerOutput> {
const urlObj = new URL(remoteUrl);
const webder_url = config.env.third_party.webder_url;
@ -52,6 +53,7 @@ export class Distributor {
}
const engine = this.getFallbackEngine(urlObj.hostname, engineName);
const output = await engine.handle(
new HandlerInput(
await decodeStream(data, parseEncodingName(mime)),
@ -59,15 +61,35 @@ export class Distributor {
)
);
const dom = parseHTML(output.content);
// Get text content before link replacement, because in text format we need original links
const stdTextContent = dom.document.documentElement.textContent;
// post-process
// TODO: generate dom in handler and not parse here twice
const dom = parseHTML(output.content);
replaceHref(dom, requestUrl, new URL(remoteUrl), engineName, redirectPath);
replaceHref(
dom.document,
requestUrl,
new URL(remoteUrl),
engineName,
redirectPath
);
const purify = DOMPurify(dom.window);
output.content = purify.sanitize(dom.document.toString());
const purify = DOMPurify(dom);
const content = purify.sanitize(dom.document.toString());
const title = output.title || dom.document.title;
const lang = output.lang || dom.document.documentElement.lang;
const textContent =
html2text(stdTextContent, output, title) ||
'Text output cannot be generated.';
return output;
return {
content,
textContent,
title,
lang,
};
}
getFallbackEngine(host: string, specified?: string): Engine {

View File

@ -2,7 +2,7 @@ import { FastifyReply, FastifyRequest } from 'fastify';
import { NotHtmlMimetypeError } from './main';
import { getFastifyError } from './validation';
import { TxtDotError } from '@txtdot/sdk/dist/types/errors';
import { TxtDotError } from '@txtdot/sdk';
import { IGetSchema } from '../types/requests/browser';
import config from '../config';

View File

@ -1,5 +1,5 @@
import config from '../config';
import { TxtDotError } from '@txtdot/sdk/dist/types/errors';
import { TxtDotError } from '@txtdot/sdk';
export class LocalResourceError extends TxtDotError {
constructor() {

View File

@ -1,5 +0,0 @@
import { Engine } from '@txtdot/sdk';
export interface IAppConfig {
engines: Engine[];
}

View File

@ -0,0 +1,14 @@
import { Engine } from '@txtdot/sdk';
type Html2TextConverter = (html: string) => string;
export interface IAppConfig {
/**
* List of engines, ordered
*/
engines: Engine[];
/**
* HTML to text converter, if engine doesn't support text
*/
html2text?: Html2TextConverter;
}

View File

@ -2,7 +2,7 @@ import { FastifySchema, FastifyRequest } from 'fastify';
import { IApiError, errorResponseSchema } from '../../errors/api';
import { engineList } from '../../plugin_manager';
import { FromSchema } from 'json-schema-to-ts';
import { handlerSchema } from '@txtdot/sdk/dist/types/handler';
import { handlerSchema } from '@txtdot/sdk';
export interface IApiResponse<T> {
data?: T;

View File

@ -0,0 +1,18 @@
import { EngineOutput } from '@txtdot/sdk/dist/types/handler';
import config from '../config';
function setTitle(body: string | null, title: string) {
if (!body) return null;
return `${title.toUpperCase()}\n${'='.repeat(title.length)}\n\n${body}`;
}
export function html2text(
stdTextContent: string | null,
output: EngineOutput,
title: string
) {
if (output.textContent) return output.textContent;
else if (config.plugin.html2text)
return setTitle(config.plugin.html2text(output.content), title);
else return setTitle(stdTextContent, title);
}

View File

@ -2,13 +2,12 @@ import config from '../config';
import { generateParserUrl, generateProxyUrl } from './generate';
export default function replaceHref(
dom: Window,
doc: Document,
requestUrl: URL,
remoteUrl: URL,
engine?: string,
redirectPath: string = 'get'
) {
const doc: Document = dom.window.document;
const parserUrl = (href: string) =>
generateParserUrl(requestUrl, remoteUrl, href, engine, redirectPath);

View File

@ -3,9 +3,9 @@
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
"incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */,
// "composite": true /* Enable constraints that allow a TypeScript project to be used with project references. */,
"tsBuildInfoFile": "./.tsbuildinfo" /* Specify the path to .tsbuildinfo incremental compilation file. */,
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */

View File

@ -41,7 +41,16 @@ importers:
'@txtdot/sdk':
specifier: workspace:*
version: link:../sdk
html-to-text:
specifier: ^9.0.5
version: 9.0.5
linkedom:
specifier: ^0.18.0
version: 0.18.0
devDependencies:
'@types/html-to-text':
specifier: ^9.0.4
version: 9.0.4
typescript:
specifier: ^5.4.5
version: 5.4.5
@ -49,8 +58,8 @@ importers:
packages/sdk:
dependencies:
linkedom:
specifier: ^0.16.11
version: 0.16.11
specifier: ^0.18.0
version: 0.18.0
route-parser:
specifier: ^0.0.5
version: 0.0.5
@ -64,6 +73,9 @@ importers:
packages/server:
dependencies:
'@fastify/one-line-logger':
specifier: ^1.3.0
version: 1.3.0
'@fastify/static':
specifier: ^7.0.3
version: 7.0.4
@ -107,8 +119,8 @@ importers:
specifier: ^3.0.1
version: 3.1.0
linkedom:
specifier: ^0.16.11
version: 0.16.11
specifier: ^0.18.0
version: 0.18.0
micromatch:
specifier: ^4.0.5
version: 4.0.5
@ -190,6 +202,9 @@ packages:
'@fastify/merge-json-schemas@0.1.1':
resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==}
'@fastify/one-line-logger@1.3.0':
resolution: {integrity: sha512-91Rqm1UPCcF6eL/g0LkjNrIZBBK/7/lmUbKdIcqZAor5ZN8VV/CiHkEwK9SGl2IUN9CS1BFhSZBwTU6ovzH7Yw==}
'@fastify/send@2.1.0':
resolution: {integrity: sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==}
@ -534,6 +549,9 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
'@sigstore/bundle@1.1.0':
resolution: {integrity: sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@ -606,6 +624,9 @@ packages:
'@types/ejs@3.1.5':
resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==}
'@types/html-to-text@9.0.4':
resolution: {integrity: sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==}
'@types/jsdom@21.1.6':
resolution: {integrity: sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==}
@ -1031,6 +1052,9 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
columnify@1.6.0:
resolution: {integrity: sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==}
engines: {node: '>=8.0.0'}
@ -1132,6 +1156,9 @@ packages:
dateformat@3.0.3:
resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==}
dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
@ -1155,6 +1182,10 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
defaults@1.0.4:
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
@ -1355,6 +1386,9 @@ packages:
fast-content-type-parse@1.1.0:
resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==}
fast-copy@3.0.2:
resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==}
fast-decode-uri-component@1.0.1:
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
@ -1381,6 +1415,9 @@ packages:
resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
engines: {node: '>=6'}
fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
fast-uri@2.3.0:
resolution: {integrity: sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw==}
@ -1591,6 +1628,9 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
help-me@5.0.0:
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
@ -1613,6 +1653,13 @@ packages:
html-escaper@3.0.3:
resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
html-to-text@9.0.5:
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
engines: {node: '>=14'}
htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
htmlparser2@9.1.0:
resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
@ -1832,6 +1879,10 @@ packages:
resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@ -1904,6 +1955,9 @@ packages:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
leac@0.6.0:
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
lerna@8.1.2:
resolution: {integrity: sha512-RCyBAn3XsqqvHbz3TxLfD7ylqzCi1A2UJnFEZmhURgx589vM3qYWQa/uOMeEEf565q6cAdtmulITciX1wgkAtw==}
engines: {node: '>=18.0.0'}
@ -1931,8 +1985,8 @@ packages:
resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
linkedom@0.16.11:
resolution: {integrity: sha512-WgaTVbj7itjyXTsCvgerpneERXShcnNJF5VIV+/4SLtyRLN+HppPre/WDHRofAr2IpEuujSNgJbCBd5lMl6lRw==}
linkedom@0.18.0:
resolution: {integrity: sha512-Xtu+w437ENHrgggnSHssua47vYXX/a/MzNdo+AR3UuWoOAxGZNwDSvjRPnPbVRFoygT990HS+7BDVBE1MK6FLQ==}
load-json-file@4.0.0:
resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
@ -2130,6 +2184,9 @@ packages:
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
multimatch@5.0.0:
resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==}
engines: {node: '>=10'}
@ -2396,6 +2453,9 @@ packages:
parse5@7.1.2:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
parseley@0.12.1:
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
path-exists@3.0.0:
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
engines: {node: '>=4'}
@ -2430,6 +2490,9 @@ packages:
pause-stream@0.0.11:
resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==}
peberminta@0.9.0:
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
@ -2456,6 +2519,10 @@ packages:
pino-abstract-transport@1.2.0:
resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==}
pino-pretty@10.3.1:
resolution: {integrity: sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==}
hasBin: true
pino-std-serializers@6.2.2:
resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==}
@ -2529,6 +2596,9 @@ packages:
engines: {node: '>= 0.10'}
hasBin: true
pump@3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@ -2700,6 +2770,9 @@ packages:
secure-json-parse@2.7.0:
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
selderee@0.11.0:
resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
semver@5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
hasBin: true
@ -3233,6 +3306,10 @@ snapshots:
dependencies:
fast-deep-equal: 3.1.3
'@fastify/one-line-logger@1.3.0':
dependencies:
pino-pretty: 10.3.1
'@fastify/send@2.1.0':
dependencies:
'@lukeed/ms': 2.0.2
@ -3659,6 +3736,11 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
'@selderee/plugin-htmlparser2@0.11.0':
dependencies:
domhandler: 5.0.3
selderee: 0.11.0
'@sigstore/bundle@1.1.0':
dependencies:
'@sigstore/protobuf-specs': 0.2.1
@ -3738,6 +3820,8 @@ snapshots:
'@types/ejs@3.1.5': {}
'@types/html-to-text@9.0.4': {}
'@types/jsdom@21.1.6':
dependencies:
'@types/node': 20.12.11
@ -4198,6 +4282,8 @@ snapshots:
color-convert: 2.0.1
color-string: 1.9.1
colorette@2.0.20: {}
columnify@1.6.0:
dependencies:
strip-ansi: 6.0.1
@ -4326,6 +4412,8 @@ snapshots:
dateformat@3.0.3: {}
dateformat@4.6.3: {}
debug@4.3.4:
dependencies:
ms: 2.1.2
@ -4341,6 +4429,8 @@ snapshots:
deep-is@0.1.4: {}
deepmerge@4.3.1: {}
defaults@1.0.4:
dependencies:
clone: 1.0.4
@ -4552,6 +4642,8 @@ snapshots:
fast-content-type-parse@1.1.0: {}
fast-copy@3.0.2: {}
fast-decode-uri-component@1.0.1: {}
fast-deep-equal@3.1.3: {}
@ -4584,6 +4676,8 @@ snapshots:
fast-redact@3.5.0: {}
fast-safe-stringify@2.1.1: {}
fast-uri@2.3.0: {}
fastify-plugin@4.5.1: {}
@ -4830,6 +4924,8 @@ snapshots:
dependencies:
function-bind: 1.1.2
help-me@5.0.0: {}
hosted-git-info@2.8.9: {}
hosted-git-info@3.0.8:
@ -4850,6 +4946,21 @@ snapshots:
html-escaper@3.0.3: {}
html-to-text@9.0.5:
dependencies:
'@selderee/plugin-htmlparser2': 0.11.0
deepmerge: 4.3.1
dom-serializer: 2.0.0
htmlparser2: 8.0.2
selderee: 0.11.0
htmlparser2@8.0.2:
dependencies:
domelementtype: 2.3.0
domhandler: 5.0.3
domutils: 3.1.0
entities: 4.5.0
htmlparser2@9.1.0:
dependencies:
domelementtype: 2.3.0
@ -4900,7 +5011,7 @@ snapshots:
humanize-ms@1.2.1:
dependencies:
ms: 2.1.2
ms: 2.1.3
iconv-lite@0.4.24:
dependencies:
@ -5069,13 +5180,15 @@ snapshots:
jest-diff@29.7.0:
dependencies:
chalk: 4.1.2
chalk: 4.1.0
diff-sequences: 29.6.3
jest-get-type: 29.6.3
pretty-format: 29.7.0
jest-get-type@29.6.3: {}
joycon@3.1.1: {}
js-tokens@4.0.0: {}
js-yaml@3.14.1:
@ -5140,6 +5253,8 @@ snapshots:
kind-of@6.0.3: {}
leac@0.6.0: {}
lerna@8.1.2(encoding@0.1.13):
dependencies:
'@lerna/create': 8.1.2(encoding@0.1.13)(typescript@5.4.5)
@ -5259,7 +5374,7 @@ snapshots:
lines-and-columns@2.0.4: {}
linkedom@0.16.11:
linkedom@0.18.0:
dependencies:
css-select: 5.1.0
cssom: 0.5.0
@ -5486,13 +5601,15 @@ snapshots:
ms@2.1.2: {}
ms@2.1.3: {}
multimatch@5.0.0:
dependencies:
'@types/minimatch': 3.0.5
array-differ: 3.0.0
array-union: 2.1.0
arrify: 2.0.1
minimatch: 3.1.2
minimatch: 3.0.5
mute-stream@0.0.8: {}
@ -5669,7 +5786,7 @@ snapshots:
'@yarnpkg/parsers': 3.0.0-rc.46
'@zkochan/js-yaml': 0.0.6
axios: 1.6.8
chalk: 4.1.2
chalk: 4.1.0
cli-cursor: 3.1.0
cli-spinners: 2.6.1
cliui: 8.0.1
@ -5742,7 +5859,7 @@ snapshots:
ora@5.3.0:
dependencies:
bl: 4.1.0
chalk: 4.1.2
chalk: 4.1.0
cli-cursor: 3.1.0
cli-spinners: 2.6.1
is-interactive: 1.0.0
@ -5869,6 +5986,11 @@ snapshots:
dependencies:
entities: 4.5.0
parseley@0.12.1:
dependencies:
leac: 0.6.0
peberminta: 0.9.0
path-exists@3.0.0: {}
path-exists@4.0.0: {}
@ -5894,6 +6016,8 @@ snapshots:
dependencies:
through: 2.3.8
peberminta@0.9.0: {}
picocolors@1.0.0: {}
picomatch@2.3.1: {}
@ -5911,6 +6035,23 @@ snapshots:
readable-stream: 4.5.2
split2: 4.2.0
pino-pretty@10.3.1:
dependencies:
colorette: 2.0.20
dateformat: 4.6.3
fast-copy: 3.0.2
fast-safe-stringify: 2.1.1
help-me: 5.0.0
joycon: 3.1.1
minimist: 1.2.8
on-exit-leak-free: 2.1.2
pino-abstract-transport: 1.2.0
pump: 3.0.0
readable-stream: 4.5.2
secure-json-parse: 2.7.0
sonic-boom: 3.8.1
strip-json-comments: 3.1.1
pino-std-serializers@6.2.2: {}
pino@9.0.0:
@ -5975,6 +6116,11 @@ snapshots:
dependencies:
event-stream: 3.3.4
pump@3.0.0:
dependencies:
end-of-stream: 1.4.4
once: 1.4.0
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
@ -6147,6 +6293,10 @@ snapshots:
secure-json-parse@2.7.0: {}
selderee@0.11.0:
dependencies:
parseley: 0.12.1
semver@5.7.2: {}
semver@7.6.2: {}