Website Structure

This commit is contained in:
supalerk-ar66 2026-01-13 10:46:40 +07:00
parent 62812f2090
commit 71f0676a62
22365 changed files with 4265753 additions and 791 deletions

9
Frontend-Learner/node_modules/youch/LICENSE.md generated vendored Normal file
View file

@ -0,0 +1,9 @@
# The MIT License
Copyright (c) 2023
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.

563
Frontend-Learner/node_modules/youch/README.md generated vendored Normal file
View file

@ -0,0 +1,563 @@
# Youch
> Pretty print JavaScript errors on the Web and the Terminal
<br />
[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] [![Downloads Stats][npm-downloads-image]][npm-url]
**Featured sponsors**
<table>
<tr>
<td>
<a href="https://route4me.com/?utm_source=adonisjs.com">
<img src="https://raw.githubusercontent.com/thetutlage/static/refs/heads/main/featured_sponsors/logos/route4me.jpg" />
</a>
</td>
<td>
<a href="https://ezycourse.com/?utm_source=adonisjs.com">
<img src="https://raw.githubusercontent.com/thetutlage/static/refs/heads/main/featured_sponsors/logos/ezycourse.jpg" />
</a>
</td>
</tr>
<tr>
<td>
<a href="https://meteor.software/g6h?utm_source=adonisjs.com">
<img src="https://raw.githubusercontent.com/thetutlage/static/refs/heads/main/featured_sponsors/logos/galaxy.jpg" />
</a>
</td>
<td>
<a href="https://www.lambdatest.com/?utm_source=adonisjs.com">
<img src="https://raw.githubusercontent.com/thetutlage/static/refs/heads/main/featured_sponsors/logos/lambdatest.jpg" />
</a>
</td>
</tr>
<tr>
<td>
<a href="https://relancer.com/?utm_source=adonisjs.com">
<img src="https://raw.githubusercontent.com/thetutlage/static/refs/heads/main/featured_sponsors/logos/relancer.jpg" />
</a>
</td>
<td>
</td>
</tr>
</table>
**Used by**
<table>
<tr>
<td>
<img src="./assets/nitro.jpg" />
</td>
<td>
<img src="./assets/nuxt.jpg" />
</td>
</tr>
<tr>
<td>
<img src="./assets/cloudflare.jpg" />
</td>
<td>
<img src="./assets/adonisjs.jpg" />
</td>
</tr>
</table>
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**
- [What is Youch?](#what-is-youch)
- [Usage](#usage)
- [Render error to HTML output](#render-error-to-html-output)
- [Render error to ANSI output](#render-error-to-ansi-output)
- [Anatomy of the error page](#anatomy-of-the-error-page)
- [Error info](#error-info)
- [Stack trace](#stack-trace)
- [Raw output](#raw-output)
- [Error cause](#error-cause)
- [Metadata (HTML only)](#metadata-html-only)
- [Using a custom source code loader](#using-a-custom-source-code-loader)
- [Injecting custom styles](#injecting-custom-styles)
- [Overriding syntax highlighter](#overriding-syntax-highlighter)
- [Configuring code editors](#configuring-code-editors)
- [How do you detect the user's code editor?](#how-do-you-detect-the-users-code-editor)
- [Contributing](#contributing)
- [Code of Conduct](#code-of-conduct)
- [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
![](./assets/banner.png)
## What is Youch?
Youch is an error-parsing library that pretty prints JavaScript errors on a web page or the terminal.
As you can see in the following screenshots, the error presented by Youch is a lot more readable and presentable in comparison to the unformatted stack trace.
<table>
<tbody>
<tr>
<td>
<strong>Unformatted stack trace</strong>
</td>
</tr>
<tr>
<td>
<img src="./assets/raw-stack-trace.png" />
</td>
</tr>
<tr>
<td>
<strong>Youch output</strong>
</td>
</tr>
<tr>
<td>
<img src="./assets/youch-output.jpg" />
</td>
</tr>
</tbody>
<table>
## Usage
Install the package from the npm packages registry as follows.
```sh
npm i youch@beta
# Yarn
yarn add youch@beta
# Pnpm
pnpm add youch@beta
```
### Render error to HTML output
You can render errors to HTML output using the `youch.toHTML` method. The HTML output is self-contained and does not require separate CSS or JavaScript files.
In the following example, we use the `hono` framework and pretty print all the errors in development using Youch. You can replace Hono with any other framework of your choice.
```ts
import { Hono } from 'hono'
import { Youch } from 'youch'
const app = new Hono()
const IN_DEV = process.env.NODE_ENV === 'development'
app.onError(async (error, c) => {
if (IN_DEV) {
const youch = new Youch()
const html = await youch.toHTML(error)
return c.html(html)
}
return c.text(error.message)
})
```
The `youch.toHTML` method accepts the error as the first argument and the following options as the second argument.
```ts
await youch.toHTML(error, {
title: 'An error occurred',
cspNonce: '',
offset: 0,
ide: 'vscode',
})
```
| Option | Description |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `title` | Define the title for the error page. It defaults to **"An error has occurred"** |
| `cspNonce` | If your application is using CSP protection, then you must provide the [CSP-nonce](https://content-security-policy.com/nonce/) for rendering inline `style` and `script` tags. |
| `offset` | The offset can be used to skip displaying certain frames from the parsed error stack. |
| `ide` | The `ide` option defines the code editor for opening the files when the filename anchor tag is clicked. [Learn more about configuring code editors](#configuring-code-editors) |
### Render error to ANSI output
You can render an error to ANSI output (for terminal) using the `youch.toANSI` method.
```ts
try {
await performSomeAction()
} catch (error) {
const youch = new Youch()
const ansiOutput = await youch.toANSI(error)
console.error(ansiOutput)
}
```
The `youch.toANSI` method accepts the error as the first argument and the following options as the second argument.
```ts
await youch.toANSI(error, {
offset: 0,
})
```
| Option | Description |
| -------- | ------------------------------------------------------------------------------------- |
| `offset` | The offset can be used to skip displaying certain frames from the parsed error stack. |
## Anatomy of the error page
Let's deconstruct the error page and understand what each section of the output represents.
### Error info
The top-most section displays the Error info, which includes:
- The Error class constructor name.
- The Error title. It is set using the `options.title` property.
- And the Error message (highlighted in red).
<details>
<summary><strong>View HTML output</strong></summary>
![](./assets/error-info.png)
</details>
---
### Stack trace
The Stack trace section displays individual frames as accordion sections. Clicking on the section title reveals the frame source code. The source code is unavailable for native stack frames that are part of the Node.js, Deno, and Bun internals.
<details>
<summary><strong>View HTML output</strong></summary>
![](./assets/error-stack.png)
</details>
---
For the ANSI output, only the first frame from the application code is expanded to show the source code.
<details>
<summary><strong>View ANSI output</strong></summary>
![](./assets/terminal-error.png)
</details>
---
### Raw output
Clicking the `Raw` button displays the Error object in its raw form, with all the error properties (not just the stack trace).
The raw output may be helpful for errors that contain additional properties. HTTP client libraries like Axios, Got, Undici, and others usually contain the HTTP response details within the error object.
<details>
<summary><strong>View HTML output</strong></summary>
![](./assets/stack-raw-output.png)
</details>
---
In case of ANSI output, you can view the raw output using the `YOUCH_RAW` environment variable. For example: `YOUCH_RAW=true node your-script.js`.
<details>
<summary><strong>View ANSI output</strong></summary>
![](./assets/terminal-error-raw.png)
</details>
---
### Error cause
[Error cause](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) is a standard way to bubble errors while wrapping them within a generic error. Youch displays the error cause as an interactive property within its own section.
<details>
<summary><strong>View HTML output</strong></summary>
![](./assets/error-cause.png)
</details>
---
For the ANSI output, the nested properties are shown upto the two levels deep. However, you can adjust the depth using the `YOUCH_CAUSE` environment variable. For example: `YOUCH_CAUSE=4 node your-script.js`.
<details>
<summary><strong>View ANSI output</strong></summary>
![](./assets/terminal-error-cause.png)
</details>
---
### Metadata (HTML only)
Metadata refers to any additional data that you want to display on the error page. It could be the HTTP request headers, the logged-in user info, or the list of available application routes.
Metadata is structured as groups and sections. Each section contains an array of rows, and each row is composed of a `key-value` pair.
In the following example, we display the request headers under the `Request` group and the `Headers` section.
```ts
const youch = new Youch()
youch.group('Request', {
headers: [
{
key: 'cookie',
value: req.headers.cookie,
},
{
key: 'host',
value: req.headers.host,
},
],
})
```
Calling the `youch.group` method multiple times with the same group name will merge the new sections with existing sections.
## Using a custom source code loader
Youch reads the source code of files within the stack trace using the Node.js `fs` module. However, you can override this default and provide a custom source loader using the `youch.sourceLoader` method.
> Note: The `sourceLoader` is called for every frame within the stack traces. Therefore, you must perform the necessary checks before attempting to read the source code of a file.
>
> For example, you must not attempt to read the source code for fileNames pointing to native code.
```ts
import { Youch } from 'youch'
const youch = new Youch(options)
youch.sourceLoader(async (stackFrame) => {
if (stackFrame.type !== 'native') {
stackFrame.source = await getSourceForFile(stackFrame.fileName)
}
})
```
## Injecting custom styles
You may inject custom CSS styles using the `youch.templates.injectStyles` method. The styles will be injected after the styles from the inbuilt templates.
```ts
import { Youch } from 'youch'
const youch = new Youch(options)
youch.templates.injectStyles(`
:root {
// Override variables for light mode
--surface-bg: #fff;
--surface-fg: #000;
--muted-fg: #999;
}
html.dark {
// Override variables for dark mode
}
`)
```
## Overriding syntax highlighter
Youch uses [speed-highlight](https://github.com/speed-highlight/core), a lightweight code highlighting library for JavaScript. To override the syntax highlighter, you can register a custom component for the `errorStackSource` template.
In the following example, we use [Shiki](https://shiki.matsu.io/) to perform syntax highlighting using a custom component.
```ts
import { codeToHtml } from 'shiki'
import { ErrorStackSourceProps } from 'youch/types'
import { ErrorStackSource } from 'youch/templates/error_stack_source'
/**
* A custom component that uses shiki to render the source
* code snippet for a stack frame
*/
class CustomErrorStackSource<ErrorStackSourceProps> extends ErrorStackSource {
/**
* Return the styles you want to inject from this
* component
*/
async getStyles() {
return `
html.dark .shiki {
background-color: var(--shiki-dark-bg) !important;
}
html.dark .shiki span {
color: var(--shiki-dark) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
pre.shiki {
padding: 16px 0;
}
code .line {
position: relative;
padding: 0 16px 0 48px;
height: 24px;
line-height: 24px;
width: 100%;
display: inline-block;
}
code .line:before {
position: absolute;
content: attr(data-line);
opacity: 0.5;
text-align: right;
margin-right: 5px;
left: 0;
width: 32px;
}
code .highlighted {
background-color: #ff000632;
}
html.dark code .highlighted {
background-color: #ff173f2d !important;
}`
}
async toHTML(props: ErrorStackSourceProps) {
if (props.frame.source) {
const code = props.frame.source.map(({ chunk }) => chunk).join('\n')
return codeToHtml(code, {
lang: 'typescript',
themes: {
light: 'min-light',
dark: 'vitesse-dark',
},
transformers: [
{
line(node, line) {
const lineNumber = props.frame.source![line - 1].lineNumber
node.properties['data-line'] = lineNumber
if (lineNumber === props.frame.lineNumber) {
this.addClassToHast(node, 'highlighted')
}
},
},
],
})
}
return ''
}
}
const youch = new Youch()
/**
* Register the component
*/
youch.templates.use('errorStackSource', new CustomErrorStackSource(false))
const html = await youch.toHTML(error)
```
## Configuring code editors
When you click the filename anchor tag (displayed in the pretty error stack section), Youch will attempt to open the given file inside a pre-configured code editor (defaults to `vscode`).
You can specify which code editor to use via the `ide` option. Following is the list of support code editors.
- textmate
- macvim
- emacs
- sublime
- phpstorm
- atom
- vscode
If you prefer a different code editor, specify its URL via the `ide` option. Make sure the URL contains the `%f` placeholder for the filename and the `%l` placeholder for the line number.
```ts
await youch.toHTML(error, {
ide: 'mvim://open?url=file://%f&line=%l',
})
```
### How do you detect the user's code editor?
Youch relies on the `process.env.IDE` environment variable to detect the user's code editor and falls back to `vscode` if the environment variable is not defined.
However, you can use any detection logic and specify the detected code editor via the `ide` option. For example, In the case of AdonisJS, we configure the code editor within the `.env` file using the `ADONIS_IDE` environment variable.
## Contributing
One of the primary goals of Poppinss is to have a vibrant community of users and contributors who believe in the principles of the framework.
We encourage you to read the [contribution guide](https://github.com/poppinss/.github/blob/main/docs/CONTRIBUTING.md) before contributing to the framework.
## Code of Conduct
In order to ensure that the Poppinss community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/poppinss/.github/blob/main/docs/CODE_OF_CONDUCT.md).
## License
Youch is open-sourced software licensed under the [MIT license](LICENSE.md).
[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/poppinss/youch/checks.yml?style=for-the-badge
[gh-workflow-url]: https://github.com/poppinss/youch/actions/workflows/checks.yml 'Github action'
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
[typescript-url]: "typescript"
[npm-image]: https://img.shields.io/npm/v/youch.svg?style=for-the-badge&logo=npm
[npm-url]: https://npmjs.org/package/youch 'npm'
[license-image]: https://img.shields.io/npm/l/youch?color=blueviolet&style=for-the-badge
[license-url]: LICENSE.md 'license'
[npm-downloads-image]: https://img.shields.io/npm/dm/youch.svg?style=for-the-badge

View file

@ -0,0 +1,42 @@
// src/helpers.ts
import useColors from "@poppinss/colors";
var ANSI_REGEX = new RegExp(
[
`[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?(?:\\u0007|\\u001B\\u005C|\\u009C))`,
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
].join("|"),
"g"
);
function htmlEscape(value) {
return value.replace(/&/g, "&amp;").replace(/\\"/g, "&bsol;&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function wordWrap(value, options) {
const width = options.width;
const indent = options.indent;
const newLine = `${options.newLine}${indent}`;
if (!width) {
return options.escape ? options.escape(value) : htmlEscape(value);
}
let regexString = ".{1," + width + "}";
regexString += "([\\s\u200B]+|$)|[^\\s\u200B]+?([\\s\u200B]+|$)";
const re = new RegExp(regexString, "g");
const lines = value.match(re) || [];
const result = lines.map(function(line) {
if (line.slice(-1) === "\n") {
line = line.slice(0, line.length - 1);
}
return options.escape ? options.escape(line) : htmlEscape(line);
}).join(newLine);
return result;
}
function stripAnsi(value) {
return value.replace(ANSI_REGEX, "");
}
var colors = useColors.ansi();
export {
htmlEscape,
wordWrap,
stripAnsi,
colors
};

View file

@ -0,0 +1,83 @@
import {
colors,
stripAnsi
} from "./chunk-4L7RY2JA.js";
import {
BaseComponent,
publicDirURL
} from "./chunk-PE3GG3TN.js";
// src/templates/error_stack_source/main.ts
import { extname } from "path";
import { highlightText } from "@speed-highlight/core";
import { highlightText as cliHighlightText } from "@speed-highlight/core/terminal";
var GUTTER = "\u2503";
var POINTER = "\u276F";
var LANGS_MAP = {
".tsx": "ts",
".jsx": "js",
".js": "js",
".ts": "ts",
".css": "css",
".json": "json",
".html": "html",
".astro": "ts",
".vue": "ts"
};
var ErrorStackSource = class extends BaseComponent {
cssFile = new URL("./error_stack_source/style.css", publicDirURL);
/**
* The toHTML method is used to output the HTML for the
* web view
*/
async toHTML(props) {
const frame = props.frame;
if (frame.type === "native" || !frame.source || !frame.fileName) {
return "";
}
const language = LANGS_MAP[extname(frame.fileName)] ?? "plain";
const highlightMarginTop = `${frame.source.findIndex((chunk) => {
return chunk.lineNumber === frame.lineNumber;
}) * 24}px`;
const highlight = `<div class="line-highlight" style="margin-top: ${highlightMarginTop}"></div>`;
let code = await highlightText(
frame.source.map((chunk) => chunk.chunk).join("\n"),
language,
true
);
code = code.replace(
'<div class="shj-numbers">',
`<div class="shj-numbers" style="counter-set: line ${frame.source[0].lineNumber - 1}">`
);
return `<pre><code class="shj-lang-js">${highlight}${code}</code></pre>`;
}
/**
* The toANSI method is used to output the text for the console
*/
async toANSI(props) {
const frame = props.frame;
if (frame.type === "native" || !frame.source || !frame.fileName) {
return "";
}
const language = LANGS_MAP[extname(frame.fileName)] ?? "plain";
const largestLineNumber = Math.max(...frame.source.map(({ lineNumber }) => lineNumber));
const lineNumberCols = String(largestLineNumber).length;
const code = frame.source.map(({ chunk }) => chunk).join("\n");
const highlighted = await cliHighlightText(code, language);
return `
${highlighted.split("\n").map((line, index) => {
const lineNumber = frame.source[index].lineNumber;
const alignedLineNumber = String(lineNumber).padStart(lineNumberCols, " ");
if (lineNumber === props.frame.lineNumber) {
return ` ${colors.bgRed(`${POINTER} ${alignedLineNumber} ${GUTTER} ${stripAnsi(line)}`)}`;
}
return ` ${colors.dim(alignedLineNumber)} ${colors.dim(GUTTER)} ${line}`;
}).join("\n")}
`;
}
};
export {
ErrorStackSource
};

View file

@ -0,0 +1,39 @@
import {
BaseComponent,
publicDirURL
} from "./chunk-PE3GG3TN.js";
// src/templates/header/main.ts
var DARK_MODE_SVG = `<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="15" height="15" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M0 0h24v24H0z" stroke="none"/><path d="M12 3h.393a7.5 7.5 0 0 0 7.92 12.446A9 9 0 1 1 12 2.992z"/></svg>`;
var LIGHT_MODE_SVG = `<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="15" height="15" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M0 0h24v24H0z" stroke="none"/><circle cx="12" cy="12" r="4"/><path d="M3 12h1m8-9v1m8 8h1m-9 8v1M5.6 5.6l.7.7m12.1-.7-.7.7m0 11.4.7.7m-12.1-.7-.7.7"/></svg>`;
var Header = class extends BaseComponent {
cssFile = new URL("./header/style.css", publicDirURL);
scriptFile = new URL("./header/script.js", publicDirURL);
/**
* The toHTML method is used to output the HTML for the
* web view
*/
async toHTML() {
return `<header id="header">
<div id="header-actions">
<div id="toggle-theme-container">
<input type="checkbox" id="toggle-theme-checkbox" />
<label id="toggle-theme-label" for="toggle-theme-checkbox">
<span id="light-theme-indicator" title="Light mode">${LIGHT_MODE_SVG}</span>
<span id="dark-theme-indicator" title="Dark mode">${DARK_MODE_SVG}</span>
</label>
</div>
</div>
</header>`;
}
/**
* The toANSI method is used to output the text for the console
*/
async toANSI() {
return "";
}
};
export {
Header
};

View file

@ -0,0 +1,44 @@
import {
BaseComponent,
publicDirURL
} from "./chunk-PE3GG3TN.js";
// src/templates/layout/main.ts
var Layout = class extends BaseComponent {
cssFile = new URL("./layout/style.css", publicDirURL);
scriptFile = new URL("./layout/script.js", publicDirURL);
/**
* The toHTML method is used to output the HTML for the
* web view
*/
async toHTML(props) {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>${props.title}</title>
<!-- STYLES -->
<!-- GLOBAL SCRIPT -->
</head>
<body>
<div id="layout">
${await props.children()}
</div>
<!-- SCRIPTS -->
</body>
</html>`;
}
/**
* The toANSI method is used to output the text for the console
*/
async toANSI(props) {
return `
${await props.children()}
`;
}
};
export {
Layout
};

View file

@ -0,0 +1,223 @@
import {
colors,
htmlEscape
} from "./chunk-4L7RY2JA.js";
import {
BaseComponent,
publicDirURL
} from "./chunk-PE3GG3TN.js";
// src/templates/error_stack/main.ts
import { dump, themes } from "@poppinss/dumper/html";
import { dump as dumpCli } from "@poppinss/dumper/console";
var CHEVIRON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
<path d="M6 9l6 6l6 -6"></path>
</svg>`;
var EDITORS = {
textmate: "txmt://open?url=file://%f&line=%l",
macvim: "mvim://open?url=file://%f&line=%l",
emacs: "emacs://open?url=file://%f&line=%l",
sublime: "subl://open?url=file://%f&line=%l",
phpstorm: "phpstorm://open?file=%f&line=%l",
atom: "atom://core/open/file?filename=%f&line=%l",
vscode: "vscode://file/%f:%l"
};
var ErrorStack = class extends BaseComponent {
cssFile = new URL("./error_stack/style.css", publicDirURL);
scriptFile = new URL("./error_stack/script.js", publicDirURL);
/**
* Returns the file's relative name from the CWD
*/
#getRelativeFileName(filePath) {
return filePath.replace(`${process.cwd()}/`, "");
}
/**
* Returns the index of the frame that should be expanded by
* default
*/
#getFirstExpandedFrameIndex(frames) {
let expandAtIndex = frames.findIndex((frame) => frame.type === "app");
if (expandAtIndex === -1) {
expandAtIndex = frames.findIndex((frame) => frame.type === "module");
}
return expandAtIndex;
}
/**
* Returns the link to open the file within known code
* editors
*/
#getEditorLink(ide, frame) {
const editorURL = EDITORS[ide] || ide;
if (!editorURL || frame.type === "native") {
return {
text: this.#getRelativeFileName(frame.fileName)
};
}
return {
href: editorURL.replace("%f", frame.fileName).replace("%l", String(frame.lineNumber)),
text: this.#getRelativeFileName(frame.fileName)
};
}
/**
* Returns the HTML fragment for the frame location
*/
#renderFrameLocation(frame, ide) {
const { text, href } = this.#getEditorLink(ide, frame);
const fileName = `<a${href ? ` href="${href}"` : ""} class="stack-frame-filepath" title="${text}">
${htmlEscape(text)}
</a>`;
const functionName = frame.functionName ? `<span>in <code title="${frame.functionName}">
${htmlEscape(frame.functionName)}
</code></span>` : "";
const loc = `<span>at line <code>${frame.lineNumber}:${frame.columnNumber}</code></span>`;
if (frame.type !== "native" && frame.source) {
return `<button class="stack-frame-location">
${fileName} ${functionName} ${loc}
</button>`;
}
return `<div class="stack-frame-location">
${fileName} ${functionName} ${loc}
</div>`;
}
/**
* Returns HTML fragment for the stack frame
*/
async #renderStackFrame(frame, index, expandAtIndex, props) {
const label = frame.type === "app" ? '<span class="frame-label">In App</span>' : "";
const expandedClass = expandAtIndex === index ? " expanded" : "";
const toggleButton = frame.type !== "native" && frame.source ? `<button class="stack-frame-toggle-indicator">${CHEVIRON}</button>` : "";
return `<li class="stack-frame stack-frame-${frame.type}${expandedClass}">
<div class="stack-frame-contents">
${this.#renderFrameLocation(frame, props.ide)}
<div class="stack-frame-extras">
${label}
${toggleButton}
</div>
</div>
<div class="stack-frame-source">
${await props.sourceCodeRenderer(props.error, frame)}
</div>
</li>`;
}
/**
* Returns the ANSI output to print the stack frame on the
* terminal
*/
async #printStackFrame(frame, index, expandAtIndex, props) {
const fileName = this.#getRelativeFileName(frame.fileName);
const loc = `${fileName}:${frame.lineNumber}:${frame.columnNumber}`;
if (index === expandAtIndex) {
const functionName2 = frame.functionName ? `at ${frame.functionName} ` : "";
const codeSnippet = await props.sourceCodeRenderer(props.error, frame);
return ` \u2043 ${functionName2}${colors.yellow(`(${loc})`)}${codeSnippet}`;
}
if (frame.type === "native") {
const functionName2 = frame.functionName ? `at ${colors.italic(frame.functionName)} ` : "";
return colors.dim(` \u2043 ${functionName2}(${colors.italic(loc)})`);
}
const functionName = frame.functionName ? `at ${frame.functionName} ` : "";
return ` \u2043 ${functionName}${colors.yellow(`(${loc})`)}`;
}
/**
* The toHTML method is used to output the HTML for the
* web view
*/
async toHTML(props) {
const frames = await Promise.all(
props.error.frames.map((frame, index) => {
return this.#renderStackFrame(
frame,
index,
this.#getFirstExpandedFrameIndex(props.error.frames),
props
);
})
);
return `<section>
<div class="card">
<div class="card-heading">
<div>
<h3 class="card-title">
Stack Trace
</h3>
</div>
</div>
<div class="card-body">
<div id="stack-frames-wrapper">
<div id="stack-frames-header">
<div id="all-frames-toggle-wrapper">
<label id="all-frames-toggle">
<input type="checkbox" />
<span> View All Frames </span>
</label>
</div>
<div>
<div class="toggle-switch">
<button id="formatted-frames-toggle" class="active"> Pretty </button>
<button id="raw-frames-toggle"> Raw </button>
</div>
</div>
</div>
<div id="stack-frames-body">
<div id="stack-frames-formatted" class="visible">
<ul id="stack-frames">
${frames.join("\n")}
</ul>
</div>
<div id="stack-frames-raw">
${dump(props.error.raw, {
styles: themes.cssVariables,
expand: true,
cspNonce: props.cspNonce,
inspectObjectPrototype: false,
inspectStaticMembers: false,
inspectArrayPrototype: false
})}
</div>
</div>
<div>
</div>
</div>
</section>`;
}
/**
* The toANSI method is used to output the text for the console
*/
async toANSI(props) {
const displayRaw = process.env.YOUCH_RAW;
if (displayRaw) {
const depth = Number.isNaN(Number(displayRaw)) ? 2 : Number(displayRaw);
return `
${colors.red("[RAW]")}
${dumpCli(props.error.raw, {
depth,
inspectObjectPrototype: false,
inspectStaticMembers: false,
inspectArrayPrototype: false
})}`;
}
const frames = await Promise.all(
props.error.frames.map((frame, index) => {
return this.#printStackFrame(
frame,
index,
this.#getFirstExpandedFrameIndex(props.error.frames),
props
);
})
);
if (frames.length) {
return `
${frames.join("\n")}`;
}
return "";
}
};
export {
ErrorStack
};

View file

@ -0,0 +1,81 @@
import {
colors,
wordWrap
} from "./chunk-4L7RY2JA.js";
import {
BaseComponent,
publicDirURL
} from "./chunk-PE3GG3TN.js";
// src/templates/error_info/main.ts
var ERROR_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="24" height="24" fill="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 7v6m0 4.01.01-.011M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10Z"/></svg>`;
var HINT_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="24" height="24" fill="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m21 2-1 1M3 2l1 1m17 13-1-1M3 16l1-1m5 3h6m-5 3h4M12 3C8 3 5.952 4.95 6 8c.023 1.487.5 2.5 1.5 3.5S9 13 9 15h6c0-2 .5-2.5 1.5-3.5h0c1-1 1.477-2.013 1.5-3.5.048-3.05-2-5-6-5Z"/></svg>`;
var COPY_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 7m0 2.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667z" /><path d="M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1" /></svg>`;
function htmlAttributeEscape(value) {
return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
var ErrorInfo = class extends BaseComponent {
cssFile = new URL("./error_info/style.css", publicDirURL);
scriptFile = new URL("./error_info/script.js", publicDirURL);
/**
* The toHTML method is used to output the HTML for the
* web view
*/
async toHTML(props) {
return `<section>
<h4 id="error-name">${props.error.name}</h4>
<h1 id="error-title">${props.title}</h1>
</section>
<section>
<div class="card">
<div class="card-body">
<h2 id="error-message">
<span>${ERROR_ICON_SVG}</span>
<span>${props.error.message}</span>
<button
id="copy-error-btn"
data-error-text="${htmlAttributeEscape(`${props.error.name}: ${props.error.message}`)}"
onclick="copyErrorMessage(this)"
title="Copy error message"
aria-label="Copy error message to clipboard"
>
${COPY_ICON_SVG}
</button>
</h2>
${props.error.hint ? `<div id="error-hint">
<span>${HINT_ICON_SVG}</span>
<span>${props.error.hint}</span>
</div>` : ""}
</div>
</div>
</section>`;
}
/**
* The toANSI method is used to output the text for the console
*/
async toANSI(props) {
const errorMessage = colors.red(
`\u2139 ${wordWrap(`${props.error.name}: ${props.error.message}`, {
width: process.stdout.columns,
indent: " ",
newLine: "\n",
escape: (value) => value
})}`
);
const hint = props.error.hint ? `
${colors.blue("\u25C9")} ${colors.dim().italic(
wordWrap(props.error.hint.replace(/(<([^>]+)>)/gi, ""), {
width: process.stdout.columns,
indent: " ",
newLine: "\n",
escape: (value) => value
})
)}` : "";
return `${errorMessage}${hint}`;
}
};
export {
ErrorInfo
};

View file

@ -0,0 +1,87 @@
import {
BaseComponent,
publicDirURL
} from "./chunk-PE3GG3TN.js";
// src/templates/error_metadata/main.ts
import { dump, themes } from "@poppinss/dumper/html";
var ErrorMetadata = class extends BaseComponent {
cssFile = new URL("./error_metadata/style.css", publicDirURL);
#primitives = ["string", "boolean", "number", "undefined"];
/**
* Formats the error row value
*/
#formatRowValue(value, dumpValue, cspNonce) {
if (dumpValue === true) {
return dump(value, { styles: themes.cssVariables, cspNonce });
}
if (this.#primitives.includes(typeof value) || value === null) {
return value;
}
return dump(value, { styles: themes.cssVariables, cspNonce });
}
/**
* Returns HTML fragment with HTML table containing rows
* metadata section rows
*/
#renderRows(rows, cspNonce) {
return `<table class="card-table">
<tbody>
${rows.map((row) => {
return `<tr>
<td class="table-key">${row.key}</td>
<td class="table-value">
${this.#formatRowValue(row.value, row.dump, cspNonce)}
</td>
</tr>`;
}).join("\n")}
</tbody>
</table>`;
}
/**
* Renders each section with its rows inside a table
*/
#renderSection(section, rows, cspNonce) {
return `<div>
<h4 class="card-subtitle">${section}</h4>
${Array.isArray(rows) ? this.#renderRows(rows, cspNonce) : `<span>${this.#formatRowValue(rows.value, rows.dump, cspNonce)}</span>`}
</div>`;
}
/**
* Renders each group as a card
*/
#renderGroup(group, sections, cspNonce) {
return `<section class="metadata-group">
<div class="card">
<div class="card-heading">
<h3 class="card-title">${group}</h3>
</div>
<div class="card-body">
${Object.keys(sections).map((section) => this.#renderSection(section, sections[section], cspNonce)).join("\n")}
</div>
</div>
</section>`;
}
/**
* The toHTML method is used to output the HTML for the
* web view
*/
async toHTML(props) {
const groups = props.metadata.toJSON();
const groupsNames = Object.keys(groups);
if (!groupsNames.length) {
return "";
}
return groupsNames.map((group) => this.#renderGroup(group, groups[group], props.cspNonce)).join("\n");
}
/**
* The toANSI method is used to output the text for the console
*/
async toANSI() {
return "";
}
};
export {
ErrorMetadata
};

View file

@ -0,0 +1,65 @@
// src/component.ts
import { readFile } from "fs/promises";
var BaseComponent = class {
#cachedStyles;
#cachedScript;
/**
* A flag to know if we are in dev mode or not. In dev mode,
* the styles and scripts are refetched from the disk.
* Otherwise they are cached.
*/
#inDevMode;
/**
* Absolute path to the frontend JavaScript that should be
* injected within the HTML head. The JavaScript does not
* get transpiled, hence it should work cross browser by
* default.
*/
scriptFile;
/**
* Absolute path to the CSS file that should be injected
* within the HTML head.
*/
cssFile;
constructor(devMode) {
this.#inDevMode = devMode;
}
/**
* Returns the styles for the component. The null value
* is not returned if no styles are associated with
* the component
*/
async getStyles() {
if (!this.cssFile) {
return null;
}
if (this.#inDevMode) {
return await readFile(this.cssFile, "utf-8");
}
this.#cachedStyles = this.#cachedStyles ?? await readFile(this.cssFile, "utf-8");
return this.#cachedStyles;
}
/**
* Returns the frontend script for the component. The null
* value is not returned if no styles are associated
* with the component
*/
async getScript() {
if (!this.scriptFile) {
return null;
}
if (this.#inDevMode) {
return await readFile(this.scriptFile, "utf-8");
}
this.#cachedScript = this.#cachedScript ?? await readFile(this.scriptFile, "utf-8");
return this.#cachedScript;
}
};
// src/public_dir.ts
var publicDirURL = new URL("./public/", import.meta.url);
export {
BaseComponent,
publicDirURL
};

View file

@ -0,0 +1,70 @@
import {
colors
} from "./chunk-4L7RY2JA.js";
import {
BaseComponent,
publicDirURL
} from "./chunk-PE3GG3TN.js";
// src/templates/error_cause/main.ts
import { dump, themes } from "@poppinss/dumper/html";
import { dump as dumpCli } from "@poppinss/dumper/console";
var ErrorCause = class extends BaseComponent {
cssFile = new URL("./error_cause/style.css", publicDirURL);
/**
* The toHTML method is used to output the HTML for the
* web view
*/
async toHTML(props) {
if (!props.error.cause) {
return "";
}
return `<section>
<div class="card">
<div class="card-heading">
<div>
<h3 class="card-title">
Error Cause
</h3>
</div>
</div>
<div class="card-body">
<div id="error-cause">
${dump(props.error.cause, {
cspNonce: props.cspNonce,
styles: themes.cssVariables,
inspectObjectPrototype: false,
inspectStaticMembers: false,
inspectArrayPrototype: false
})}
</div>
</div>
</div>
</section>`;
}
/**
* The toANSI method is used to output the text for the console
*/
async toANSI(props) {
if (!props.error.cause) {
return "";
}
let depth = process.env.YOUCH_CAUSE ? Number(process.env.YOUCH_CAUSE) : 2;
if (Number.isNaN(depth)) {
depth = 2;
}
return `
${colors.red("[CAUSE]")}
${dumpCli(props.error.cause, {
depth,
inspectObjectPrototype: false,
inspectStaticMembers: false,
inspectArrayPrototype: false
})}`;
}
};
export {
ErrorCause
};

3
Frontend-Learner/node_modules/youch/build/index.d.ts generated vendored Normal file
View file

@ -0,0 +1,3 @@
export { Youch } from './src/youch.js';
export { Metadata } from './src/metadata.js';
export { BaseComponent } from './src/component.js';

359
Frontend-Learner/node_modules/youch/build/index.js generated vendored Normal file
View file

@ -0,0 +1,359 @@
import {
Layout
} from "./chunk-CM7DWJNZ.js";
import {
ErrorCause
} from "./chunk-X53OIOJH.js";
import {
ErrorInfo
} from "./chunk-OIJ3WD7L.js";
import {
ErrorMetadata
} from "./chunk-P36L72PL.js";
import {
ErrorStack
} from "./chunk-EJH674NB.js";
import {
ErrorStackSource
} from "./chunk-7QV3D5YX.js";
import "./chunk-4L7RY2JA.js";
import {
Header
} from "./chunk-AUGPHE32.js";
import {
BaseComponent
} from "./chunk-PE3GG3TN.js";
// src/youch.ts
import { parse } from "cookie-es";
import { ErrorParser } from "youch-core";
// src/metadata.ts
var Metadata = class {
#groups = {};
/**
* Converts value to an array (if not an array already)
*/
#toArray(value) {
return Array.isArray(value) ? value : [value];
}
/**
* Define a group, its sections and their rows. In case of
* existing groups/sections, the new data will be merged
* with the existing data
*/
group(name, sections) {
this.#groups[name] = this.#groups[name] ?? {};
Object.keys(sections).forEach((section) => {
if (!this.#groups[name][section]) {
this.#groups[name][section] = sections[section];
} else {
this.#groups[name][section] = this.#toArray(this.#groups[name][section]);
this.#groups[name][section].push(...this.#toArray(sections[section]));
}
});
return this;
}
/**
* Returns the existing metadata groups, sections and
* rows.
*/
toJSON() {
return this.#groups;
}
};
// src/templates.ts
import { createScript, createStyleSheet } from "@poppinss/dumper/html";
var Templates = class {
constructor(devMode) {
this.devMode = devMode;
this.#knownTemplates = {
layout: new Layout(devMode),
header: new Header(devMode),
errorInfo: new ErrorInfo(devMode),
errorStack: new ErrorStack(devMode),
errorStackSource: new ErrorStackSource(devMode),
errorCause: new ErrorCause(devMode),
errorMetadata: new ErrorMetadata(devMode)
};
}
#knownTemplates;
#styles = /* @__PURE__ */ new Map([["global", createStyleSheet()]]);
#scripts = /* @__PURE__ */ new Map([["global", createScript()]]);
/**
* Returns a collection of style and script tags to dump
* inside the document HEAD.
*/
#getStylesAndScripts(cspNonce) {
let customInjectedStyles = "";
let globalScript = "";
const styles = [];
const scripts = [];
const cspNonceAttr = cspNonce ? ` nonce="${cspNonce}"` : "";
this.#styles.forEach((bucket, name) => {
if (name === "injected") {
customInjectedStyles = `<style id="${name}-styles"${cspNonceAttr}>${bucket}</style>`;
} else {
styles.push(`<style id="${name}-styles"${cspNonceAttr}>${bucket}</style>`);
}
});
this.#scripts.forEach((bucket, name) => {
if (name === "global") {
globalScript = `<script id="${name}-script"${cspNonceAttr}>${bucket}</script>`;
}
scripts.push(`<script id="${name}-script"${cspNonceAttr}>${bucket}</script>`);
});
return {
styles: `${styles.join("\n")}
${customInjectedStyles}`,
scripts: scripts.join("\n"),
globalScript
};
}
/**
* Collects styles and scripts for components as we render
* them.
*/
async #collectStylesAndScripts(templateName) {
if (!this.#styles.has(templateName)) {
const styles = await this.#knownTemplates[templateName].getStyles();
if (styles) {
this.#styles.set(templateName, styles);
}
}
if (!this.#scripts.has(templateName)) {
const script = await this.#knownTemplates[templateName].getScript();
if (script) {
this.#scripts.set(templateName, script);
}
}
}
/**
* Returns the HTML for a given template
*/
async #tmplToHTML(templateName, props) {
const component = this.#knownTemplates[templateName];
if (!component) {
throw new Error(`Invalid template "${templateName}"`);
}
await this.#collectStylesAndScripts(templateName);
return component.toHTML(props);
}
/**
* Returns the ANSI output for a given template
*/
async #tmplToANSI(templateName, props) {
const component = this.#knownTemplates[templateName];
if (!component) {
throw new Error(`Invalid template "${templateName}"`);
}
return component.toANSI(props);
}
/**
* Define a custom component to be used in place of the default component.
* Overriding components allows you control the HTML layout, styles and
* the frontend scripts of an HTML fragment.
*/
use(templateName, component) {
this.#knownTemplates[templateName] = component;
return this;
}
/**
* Inject custom styles to the document. Injected styles are
* always placed after the global and the components style
* tags.
*/
injectStyles(cssFragment) {
let injectedStyles = this.#styles.get("injected") ?? "";
injectedStyles += `
${cssFragment}`;
this.#styles.set("injected", injectedStyles);
return this;
}
/**
* Returns the HTML output for the given parsed error
*/
async toHTML(props) {
const html = await this.#tmplToHTML("layout", {
title: props.title,
ide: props.ide,
cspNonce: props.cspNonce,
children: async () => {
const header = await this.#tmplToHTML("header", props);
const info = await this.#tmplToHTML("errorInfo", props);
const stackTrace = await this.#tmplToHTML("errorStack", {
ide: process.env.EDITOR ?? "vscode",
sourceCodeRenderer: (error, frame) => {
return this.#tmplToHTML("errorStackSource", {
error,
frame,
ide: props.ide,
cspNonce: props.cspNonce
});
},
...props
});
const cause = await this.#tmplToHTML("errorCause", props);
const metadata = await this.#tmplToHTML("errorMetadata", props);
return `${header}${info}${stackTrace}${cause}${metadata}`;
}
});
const { globalScript, scripts, styles } = this.#getStylesAndScripts(props.cspNonce);
return html.replace("<!-- STYLES -->", styles).replace("<!-- SCRIPTS -->", scripts).replace("<!-- GLOBAL SCRIPT -->", globalScript);
}
/**
* Returns the ANSI output to be printed on the terminal
*/
async toANSI(props) {
const ansiOutput = await this.#tmplToANSI("layout", {
title: props.title,
children: async () => {
const header = await this.#tmplToANSI("header", {});
const info = await this.#tmplToANSI("errorInfo", props);
const stackTrace = await this.#tmplToANSI("errorStack", {
ide: process.env.EDITOR ?? "vscode",
sourceCodeRenderer: (error, frame) => {
return this.#tmplToANSI("errorStackSource", {
error,
frame
});
},
...props
});
const cause = await this.#tmplToANSI("errorCause", props);
const metadata = await this.#tmplToANSI("errorMetadata", props);
return `${header}${info}${stackTrace}${cause}${metadata}`;
}
});
return ansiOutput;
}
};
// src/youch.ts
var Youch = class {
/**
* Properties to be shared with the Error parser
*/
#sourceLoader;
#parsers = [];
#transformers = [];
/**
* Manage templates used for converting error to the HTML
* output
*/
templates = new Templates(false);
/**
* Define metadata to be displayed alongside the error output
*/
metadata = new Metadata();
/**
* Creates an instance of the ErrorParser and applies the
* source loader, parsers and transformers on it
*/
#createErrorParser(options) {
const errorParser = new ErrorParser(options);
if (this.#sourceLoader) {
errorParser.defineSourceLoader(this.#sourceLoader);
}
this.#parsers.forEach((parser) => errorParser.useParser(parser));
this.#transformers.forEach((transformer) => errorParser.useTransformer(transformer));
return errorParser;
}
/**
* Defines the request properties as a metadata group
*/
#defineRequestMetadataGroup(request) {
if (!request || Object.keys(request).length === 0) {
return;
}
this.metadata.group("Request", {
...request.url ? {
url: {
key: "URL",
value: request.url
}
} : {},
...request.method ? {
method: {
key: "Method",
value: request.method
}
} : {},
...request.headers ? {
headers: Object.keys(request.headers).map((key) => {
const value = request.headers[key];
return {
key,
value: key === "cookie" ? { ...parse(value) } : value
};
})
} : {}
});
}
/**
* Define custom implementation for loading the source code
* of a stack frame.
*/
defineSourceLoader(loader) {
this.#sourceLoader = loader;
return this;
}
/**
* Define a custom parser. Parsers are executed before the
* error gets parsed and provides you with an option to
* modify the error
*/
useParser(parser) {
this.#parsers.push(parser);
return this;
}
/**
* Define a custom transformer. Transformers are executed
* after the error has been parsed and can mutate the
* properties of the parsed error.
*/
useTransformer(transformer) {
this.#transformers.push(transformer);
return this;
}
/**
* Parses error to JSON
*/
async toJSON(error, options) {
options = { ...options };
return this.#createErrorParser({ offset: options.offset }).parse(error);
}
/**
* Render error to HTML
*/
async toHTML(error, options) {
options = { ...options };
this.#defineRequestMetadataGroup(options.request);
const parsedError = await this.#createErrorParser({ offset: options.offset }).parse(error);
return this.templates.toHTML({
title: options.title ?? "An error has occurred",
ide: options.ide ?? process.env.IDE ?? "vscode",
cspNonce: options.cspNonce,
error: parsedError,
metadata: this.metadata
});
}
/**
* Render error to ANSI output
*/
async toANSI(error, options) {
options = { ...options };
const parsedError = await this.#createErrorParser({ offset: options.offset }).parse(error);
return this.templates.toANSI({
title: "",
error: parsedError,
metadata: this.metadata
});
}
};
export {
BaseComponent,
Metadata,
Youch
};

View file

@ -0,0 +1,5 @@
#error-cause {
border: 1px solid var(--border);
border-radius: var(--radius);
--pre-bg-color: transparent;
}

View file

@ -0,0 +1,13 @@
function copyErrorMessage(button) {
const errorText = button.dataset.errorText;
navigator.clipboard.writeText(errorText)
.then(() => {
button.classList.add('copied');
setTimeout(() => button.classList.remove('copied'), 2000);
})
.catch(() => {
button.classList.add('copied');
setTimeout(() => button.classList.remove('copied'), 2000);
});
}

View file

@ -0,0 +1,145 @@
:root {
--copy-button-active-bg: var(--slate-a4);
--copy-button-border: var(--slate-7);
--copy-button-success-bg: var(--green-5);
--copy-button-success-border: var(--green-7);
--copy-button-success-fg: var(--green-12);
}
html.dark {
--copy-button-active-bg: var(--slate-a4);
--copy-button-border: var(--slate-7);
--copy-button-success-bg: var(--green-3);
--copy-button-success-border: var(--green-4);
--copy-button-success-fg: var(--green-11);
}
#error-name {
color: var(--danger-fg);
font-size: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#error-title {
color: var(--title-fg);
font-size: 32px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#error-message {
color: var(--danger-fg);
font-size: 22px;
font-weight: 700;
display: flex;
align-items: center;
align-items: flex-start;
gap: 12px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: relative;
word-break: break-word;
}
#error-message svg {
margin-top: 1.5px;
}
#error-hint {
border-top: 1px solid var(--border);
padding-top: 15px;
margin-top: 15px;
font-size: 15px;
font-style: italic;
display: flex;
gap: 12px;
align-items: flex-start;
padding-left: 1px;
}
#error-hint svg {
margin-bottom: -2px;
}
#error-hint strong {
color: var(--title-fg);
}
#error-hint a {
color: var(--links-fg);
}
#copy-error-btn {
display: flex;
align-items: center;
justify-content: center;
margin-left: auto;
position: relative;
cursor: pointer;
background: transparent;
border: 1px solid var(--copy-button-border);
font-size: 13px;
font-family: inherit;
font-weight: 500;
padding: 4px 8px 6px 8px;
border-radius: var(--radius);
color: var(--surface-fg);
transition: all 0.2s ease;
}
#copy-error-btn svg {
width: 18px;
height: 18px;
}
#copy-error-btn:hover {
background: var(--copy-button-active-bg);
color: var(--title-fg);
}
#copy-error-btn:active {
transform: scale(0.95);
}
#copy-error-btn.copied::after {
content: 'Copied';
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
background: var(--copy-button-success-bg);
color: var(--copy-button-success-fg);
border: 1px solid var(--copy-button-success-border);
border-radius: 20px;
font-size: 13px;
font-family: inherit;
font-weight: 500;
padding: 2px 8px 4px 8px;
letter-spacing: 0.3px;
white-space: nowrap;
opacity: 0;
animation: copyFeedback 2s ease-in-out forwards;
}
@keyframes copyFeedback {
0% {
opacity: 0;
transform: translateX(-50%) translateY(5px);
}
10% {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
90% {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
100% {
opacity: 0;
transform: translateX(-50%) translateY(-5px);
}
}
@media (min-width: 1024px) {
#error-hint {
align-items: center;
}
}

View file

@ -0,0 +1,3 @@
.metadata-group .card-subtitle + span {
word-break: break-word;
}

View file

@ -0,0 +1,72 @@
function showFormattedFrames(button) {
document.querySelector('#all-frames-toggle input[type="checkbox"]').disabled = false
const parent = button.closest('section')
const formattedFrames = parent.querySelector('#stack-frames-formatted')
formattedFrames.classList.add('visible')
const rawFrames = parent.querySelector('#stack-frames-raw')
rawFrames.classList.remove('visible')
button.parentElement.querySelectorAll('button').forEach((btn) => btn.classList.remove('active'))
button.classList.add('active')
}
function showRawFrames(button) {
document.querySelector('#all-frames-toggle input[type="checkbox"]').disabled = true
const parent = button.closest('section')
const formattedFrames = parent.querySelector('#stack-frames-formatted')
formattedFrames.classList.remove('visible')
const rawFrames = parent.querySelector('#stack-frames-raw')
rawFrames.classList.add('visible')
button.parentElement.querySelectorAll('button').forEach((btn) => btn.classList.remove('active'))
button.classList.add('active')
}
function toggleFrameSource(parent) {
if (parent.classList.contains('expanded')) {
parent.classList.remove('expanded')
} else {
parent.classList.add('expanded')
}
}
function toggleAllFrames() {
const wrapper = document.querySelector('#stack-frames-wrapper')
const indicator = document.querySelector('#all-frames-toggle input[type="checkbox"]')
if (indicator.checked) {
wrapper.classList.add('display-all')
} else {
wrapper.classList.remove('display-all')
}
}
document.querySelector('#formatted-frames-toggle').addEventListener('click', function () {
showFormattedFrames(this)
})
document.querySelector('#raw-frames-toggle').addEventListener('click', function () {
showRawFrames(this)
})
document
.querySelector('#all-frames-toggle input[type="checkbox"]')
.addEventListener('change', function () {
toggleAllFrames()
})
document.querySelectorAll('button[class="stack-frame-location"]').forEach((sfl) => {
sfl.addEventListener('click', function (e) {
if (e.target.tagName === 'A') {
return
}
toggleFrameSource(e.target.closest('li'))
})
})
document.querySelectorAll('button[class="stack-frame-toggle-indicator"]').forEach((sfl) => {
sfl.addEventListener('click', function (e) {
toggleFrameSource(e.target.closest('li'))
})
})

View file

@ -0,0 +1,219 @@
:root {
--frame-bg: var(--slate-2);
--label-bg: var(--green-5);
--label-fg: var(--green-12);
--switch-bg: var(--slate-3);
--switch-active-bg: var(--slate-a4);
--switch-border: var(--slate-7);
}
html.dark {
--frame-bg: var(--slate-2);
--label-bg: var(--green-3);
--label-fg: var(--green-11);
--switch-bg: var(--slate-3);
--switch-active-bg: var(--slate-a4);
--switch-border: var(--slate-7);
}
#stack-frames-wrapper {
border: 1px solid var(--border);
border-radius: var(--radius);
}
#stack-frames-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: calc(2vw + 7px);
border-radius: var(--radius) var(--radius) 0 0;
}
#all-frames-toggle {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 500;
user-select: none;
}
#stack-frames {
list-style: none;
}
.stack-frame {
border-top: 1px solid var(--border);
font-size: 14px;
}
.stack-frame.stack-frame-native {
display: none;
font-style: italic;
}
.stack-frame.stack-frame-native a,
.stack-frame.stack-frame-native code {
color: var(--muted-fg);
}
#stack-frames-wrapper.display-all .stack-frame.stack-frame-native {
display: block;
}
.stack-frame-contents {
background: var(--frame-bg);
display: flex;
padding: 0 calc(1vw + 3px);
align-items: center;
justify-content: space-between;
}
.stack-frame:not(.stack-frame-native) .stack-frame-contents:hover {
background: var(--card-bg);
}
.stack-frame:last-child:not(.expanded) .stack-frame-contents {
border-radius: 0 0 var(--radius) var(--radius);
}
.stack-frame-filepath {
max-width: 100%;
word-wrap: break-word;
}
.stack-frame-location {
display: flex;
flex-direction: column;
overflow: hidden;
gap: 7px;
border: none;
background: none;
flex: 1;
color: inherit;
font: inherit;
padding: 9px 0;
align-items:start;
}
.stack-frame-location a {
text-decoration: none;
text-align: left;
}
.stack-frame-location span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stack-frame-location code {
font-family: var(--font-sans);
}
.stack-frame-extras {
display: flex;
gap: 6px;
align-items: center;
}
.stack-frame-toggle-indicator {
border: none;
border-radius: var(--radius);
height: 22px;
width: 22px;
display: flex;
justify-content: center;
align-items: center;
background: none;
color: inherit;
font: inherit;
}
.stack-frame-toggle-indicator:hover {
border: 1px solid var(--switch-border);
}
.stack-frame-toggle-indicator svg {
width: 16px;
display: block;
}
.stack-frame.expanded .stack-frame-toggle-indicator svg {
transform: rotate(180deg);
}
.frame-label {
padding: 0px 8px;
border-radius: 20px;
align-items: center;
height: 20px;
background: var(--label-bg);
font-size: 12px;
font-weight: 500;
color: var(--label-fg);
display: none;
}
.stack-frame-source {
display: none;
}
.stack-frame.expanded .stack-frame-source {
display: block;
}
#stack-frames-raw {
--pre-bg-color: transparent;
border-top: 1px solid var(--border);
}
#stack-frames-formatted,
#stack-frames-raw {
display: none;
}
#stack-frames-formatted.visible,
#stack-frames-raw.visible {
display: block;
}
.toggle-switch {
display: flex;
background: var(--switch-bg);
border: 1px solid var(--switch-border);
border-radius: var(--radius);
}
.toggle-switch button {
background: none;
border: none;
font-family: inherit;
color: inherit;
font-size: 13px;
font-weight: 500;
padding: 4px 8px;
border-right: 1px solid var(--switch-border);
}
.toggle-switch button:first-child {
border-radius: var(--radius) 0 0 var(--radius);
}
.toggle-switch button:last-child {
border-radius: 0 var(--radius) var(--radius) 0;
border: none;
}
.toggle-switch button.active {
background: var(--switch-active-bg);
color: var(--title-fg);
}
@media (min-width: 768px) {
.stack-frame-contents {
padding: 0 12px;
}
#stack-frames-header {
padding: 10px 16px;
}
.stack-frame-location {
flex-direction: row;
}
}
@media (min-width: 1024px) {
.frame-label {
display: inline-flex;
}
}

View file

@ -0,0 +1,123 @@
:root {
--error-bg: #ff000632;
--pre-selection-bg: var(--slate-a5);
--line-numbers-fg: var(--slate-a11);
}
html.dark {
--error-bg: #ff173f2d;
--pre-selection-bg: var(--slate-a5);
--line-numbers-fg: var(--slate-a11);
}
.line-highlight {
position: absolute;
left: 0;
right: 0;
background: var(--error-bg);
height: 24px;
pointer-events: none;
}
[class*='shj-lang-'] {
white-space: pre;
border-top: 1px solid var(--border);
background: var(--pre-bg-color);
color: var(--pre-fg-color);
line-height: 24px;
box-sizing: border-box;
max-width: min(100%, 100vw);
display: block;
font-size: 12px;
font-family: var(--font-mono);
position: relative;
padding: calc(2vw + 4px) calc(2vw + 6px) calc(2vw + 4px) calc(2vw + 6px);
}
[class*='shj-lang-'] * {
-webkit-font-smoothing: initial;
-moz-osx-font-smoothing: initial;
}
.shj-inline {
margin: 0;
padding: 2px 5px;
display: inline-block;
border-radius: 5px;
}
[class*='shj-lang-']::selection,
[class*='shj-lang-'] ::selection {
background: var(--pre-selection-bg);
}
[class*='shj-lang-'] > div {
display: flex;
overflow: auto;
}
[class*='shj-lang-'] > div :last-child {
flex: 1;
outline: none;
}
.shj-numbers {
padding-left: 5px;
counter-reset: line;
}
.shj-numbers div {
padding-right: 5px;
}
.shj-numbers div:before {
color: var(--line-numbers-fg);
display: block;
content: counter(line);
opacity: 0.5;
text-align: right;
margin-right: 5px;
counter-increment: line;
}
.shj-syn-cmnt {
font-style: italic;
}
.shj-syn-err,
.shj-syn-kwd {
color: var(--dt-symbol-fg-color);
}
.shj-syn-num {
color: var(--dt-number-fg-color);
}
.shj-syn-class {
color: var(--class-label-fg-color);
}
.shj-numbers,
.shj-syn-cmnt {
color: var(--dt-undefined-fg-color);
}
.shj-syn-insert,
.shj-syn-str {
color: var(--dt-string-fg-color);
}
.shj-syn-bool {
color: var(--dt-boolean-fg-color);
}
.shj-syn-type,
.shj-syn-oper {
color: var(--braces-fg-color);
}
.shj-syn-section,
.shj-syn-func {
color: var(--pre-fg-color);
}
.shj-syn-deleted,
.shj-syn-var {
color: var(--brackets-fg-color);
}
.shj-oneline {
padding: 12px 10px;
}
.shj-multiline.shj-mode-header {
padding: 20px;
}
@media (min-width: 768px) {
[class*='shj-lang-'] {
border-top: none;
padding: 10px 12px 12px 12px;
}
}

View file

@ -0,0 +1,14 @@
function toggleTheme(input) {
if (input.checked) {
document.documentElement.classList.add('dark')
localStorage.setItem('youch-theme', 'dark')
} else {
document.documentElement.classList.remove('dark')
localStorage.setItem('youch-theme', 'light')
}
}
document.querySelector('#toggle-theme-checkbox').checked = usesDarkMode()
document.querySelector('#toggle-theme-checkbox').addEventListener('change', function () {
toggleTheme(this)
})

View file

@ -0,0 +1,56 @@
#header-actions {
display: flex;
flex-direction: row-reverse;
}
#toggle-theme-container {
position: relative;
display: inline-block;
padding-right: 0px;
}
#toggle-theme-container input[type='checkbox'] {
opacity: 0;
position: absolute;
}
#toggle-theme-label {
position: relative;
cursor: pointer;
border-radius: 50px;
display: inline-flex;
}
#toggle-theme-checkbox:focus + #toggle-theme-label {
outline: 2px solid var(--slate-a6);
outline-offset: 4px;
}
#light-theme-indicator,
#dark-theme-indicator {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
#toggle-theme-label svg {
width: 30px;
height: 30px;
}
#toggle-theme-container #dark-theme-indicator {
display: none;
}
#toggle-theme-container
input[type='checkbox']:checked
+ #toggle-theme-label
#light-theme-indicator {
display: none;
}
#toggle-theme-container input[type='checkbox']:checked + #toggle-theme-label #dark-theme-indicator {
display: flex;
}

View file

@ -0,0 +1,12 @@
function usesDarkMode() {
let youchTheme = localStorage.getItem('youch-theme')
let hasDarkMode = false
if (youchTheme === null) {
hasDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
} else if (youchTheme === 'dark') {
hasDarkMode = true
}
return hasDarkMode
}
document.documentElement.classList.add(usesDarkMode() ? 'dark' : 'light')

View file

@ -0,0 +1,435 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', Arial, sans-serif;
--font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', 'Roboto Mono',
'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', 'Fira Mono', 'Droid Sans Mono',
'Courier New', monospace;
--radius: 6px;
}
:root {
/**
* The slate shade is used as the main color for the page
* background and the text.
*/
--slate-1: #fcfcfd;
--slate-2: #f9f9fb;
--slate-3: #f0f0f3;
--slate-4: #e8e8ec;
--slate-5: #e0e1e6;
--slate-6: #d9d9e0;
--slate-7: #cdced6;
--slate-8: #b9bbc6;
--slate-9: #8b8d98;
--slate-10: #80838d;
--slate-11: #60646c;
--slate-12: #1c2024;
--slate-a1: #00005503;
--slate-a2: #00005506;
--slate-a3: #0000330f;
--slate-a4: #00002d17;
--slate-a5: #0009321f;
--slate-a6: #00002f26;
--slate-a7: #00062e32;
--slate-a8: #00083046;
--slate-a9: #00051d74;
--slate-a10: #00071b7f;
--slate-a11: #0007149f;
--slate-a12: #000509e3;
/**
* The green shade is used for links and the accent color
*/
--green-1: #fbfefc;
--green-2: #f4fbf6;
--green-3: #e6f6eb;
--green-4: #d6f1df;
--green-5: #c4e8d1;
--green-6: #adddc0;
--green-7: #8eceaa;
--green-8: #5bb98b;
--green-9: #30a46c;
--green-10: #2b9a66;
--green-11: #218358;
--green-12: #193b2d;
/**
* The red shade is used as the danger color
*/
--red-1: #fffcfc;
--red-2: #fff7f7;
--red-3: #feebec;
--red-4: #ffdbdc;
--red-5: #ffcdce;
--red-6: #fdbdbe;
--red-7: #f4a9aa;
--red-8: #eb8e90;
--red-9: #e5484d;
--red-10: #dc3e42;
--red-11: #ce2c31;
--red-12: #641723;
--surface-bg: var(--slate-2);
--surface-fg: var(--slate-11);
--muted-fg: var(--slate-10);
--title-fg: var(--slate-12);
--subtitle-fg: var(--green-10);
--links-fg: var(--green-11);
--danger-fg: var(--red-11);
--border: var(--slate-4);
--checkbox-border: var(--slate-a7);
--checkbox-active-bg: var(--green-5);
--checkbox-active-fg: var(--green-11);
--checkbox-active-border: var(--green-6);
--card-bg: #ffffff;
--card-shadow: 0 0 0 1px color-mix(in oklab, var(--slate-a3), var(--slate-3) 25%),
0 8px 40px rgba(0, 0, 0, 0.05), 0 12px 32px -16px var(--slate-a3);
/**
* Colors for dumper. Also used by the errorStackSource
*/
--pre-bg-color: #fff;
--pre-fg-color: #212121;
--toggle-fg-color: #989999;
--braces-fg-color: #0431fa;
--brackets-fg-color: #0431fa;
--dt-number-fg-color: #1976d2;
--dt-bigint-fg-color: #1976d2;
--dt-boolean-fg-color: #1976d2;
--dt-string-fg-color: #22863a;
--dt-null-fg-color: #9c9c9d;
--dt-undefined-fg-color: #9c9c9d;
--prototype-label-fg-color: #9c9c9d;
--dt-symbol-fg-color: #d32f2f;
--dt-regex-fg-color: #1976d2;
--dt-date-fg-color: #7b3814;
--dt-buffer-fg-color: #7b3814;
--function-label-fg-color: #6f42c1;
--array-label-fg-color: #d32f2f;
--object-label-fg-color: #d32f2f;
--map-label-fg-color: #d32f2f;
--set-label-fg-color: #d32f2f;
--object-key-fg-color: #212121;
--object-key-prefix-fg-color: #9c9c9d;
--class-label-fg-color: #6f42c1;
--collpase-label-fg-color: #9c9c9d;
--getter-label-fg-color: #7b3814;
--circular-label-fg-color: #7b3814;
--weakset-label-fg-color: #7b3814;
--weakref-label-fg-color: #7b3814;
--weakmap-label-fg-color: #7b3814;
--observable-label-fg-color: #7b3814;
--promise-label-fg-color: #7b3814;
--generator-label-fg-color: #7b3814;
--blob-label-fg-color: #7b3814;
--unknown-label-fg-color: #7b3814;
color-scheme: only light;
}
html.dark {
/**
* The slate shade is used as the main color for the page
* background and the text.
*/
--slate-1: #111113;
--slate-2: #18191b;
--slate-3: #212225;
--slate-4: #272a2d;
--slate-5: #2e3135;
--slate-6: #363a3f;
--slate-7: #43484e;
--slate-8: #5a6169;
--slate-9: #696e77;
--slate-10: #777b84;
--slate-11: #b0b4ba;
--slate-12: #edeef0;
--slate-a1: #00000000;
--slate-a2: #d8f4f609;
--slate-a3: #ddeaf814;
--slate-a4: #d3edf81d;
--slate-a5: #d9edfe25;
--slate-a6: #d6ebfd30;
--slate-a7: #d9edff40;
--slate-a8: #d9edff5d;
--slate-a9: #dfebfd6d;
--slate-a10: #e5edfd7b;
--slate-a11: #f1f7feb5;
--slate-a12: #fcfdffef;
/**
* The green shade is used for links and the accent color
*/
--green-1: #0e1512;
--green-2: #121b17;
--green-3: #132d21;
--green-4: #113b29;
--green-5: #174933;
--green-6: #20573e;
--green-7: #28684a;
--green-8: #2f7c57;
--green-9: #30a46c;
--green-10: #33b074;
--green-11: #3dd68c;
--green-12: #b1f1cb;
/**
* The red shade is used as the danger color
*/
--red-1: #191111;
--red-2: #201314;
--red-3: #3b1219;
--red-4: #500f1c;
--red-5: #611623;
--red-6: #72232d;
--red-7: #8c333a;
--red-8: #b54548;
--red-9: #e5484d;
--red-10: #ec5d5e;
--red-11: #ff9592;
--red-12: #ffd1d9;
--surface-bg: var(--slate-2);
--surface-fg: var(--slate-11);
--muted-fg: var(--slate-10);
--title-fg: var(--slate-12);
--subtitle-fg: var(--green-10);
--links-fg: var(--green-11);
--danger-fg: var(--red-11);
--border: var(--slate-4);
--checkbox-border: var(--slate-a7);
--checkbox-active-bg: var(--green-5);
--checkbox-active-fg: var(--green-11);
--checkbox-active-border: var(--green-6);
--card-bg: var(--slate-a2);
--card-shadow: 0 0 0 1px color-mix(in oklab, var(--slate-a3), var(--slate-3) 25%),
0 8px 20px rgba(0, 0, 0, 0.05), 0 12px 32px -16px var(--slate-a1);
/**
* Colors for dumper. Also used by the errorStackSource
*/
--pre-bg-color: var(--slate-a1);
--pre-fg-color: #94e2d5;
--toggle-fg-color: #7c7c8c;
--braces-fg-color: #f38ba8;
--brackets-fg-color: #f38ba8;
--dt-number-fg-color: #fab387;
--dt-bigint-fg-color: #fab387;
--dt-boolean-fg-color: #cba6f7;
--dt-string-fg-color: #a6e3a1;
--dt-null-fg-color: #6c7086;
--dt-undefined-fg-color: #6c7086;
--prototype-label-fg-color: #6c7086;
--dt-symbol-fg-color: #f9e2af;
--dt-regex-fg-color: #cba6f7;
--dt-date-fg-color: #94e2d5;
--dt-buffer-fg-color: #94e2d5;
--function-label-fg-color: #cba6f7;
--array-label-fg-color: #f9e2af;
--object-label-fg-color: #f9e2af;
--map-label-fg-color: #f9e2af;
--set-label-fg-color: #f9e2af;
--object-key-fg-color: #89b4fa;
--object-key-prefix-fg-color: #6c7086;
--class-label-fg-color: #cba6f7;
--collpase-label-fg-color: #6c7086;
--getter-label-fg-color: #94e2d5;
--circular-label-fg-color: #94e2d5;
--weakset-label-fg-color: #94e2d5;
--weakref-label-fg-color: #94e2d5;
--weakmap-label-fg-color: #94e2d5;
--observable-label-fg-color: #94e2d5;
--promise-label-fg-color: #94e2d5;
--generator-label-fg-color: #94e2d5;
--blob-label-fg-color: #94e2d5;
--unknown-label-fg-color: #94e2d5;
color-scheme: dark;
}
html,
body {
background: var(--surface-bg);
font-family: var(--font-sans);
color: var(--surface-fg);
line-height: 1.2;
}
code {
color: var(--title-fg);
}
a {
color: var(--links-fg);
}
input[type='checkbox'] {
appearance: none;
-webkit-appearance: none;
display: flex;
align-content: center;
justify-content: center;
border: 2px solid var(--checkbox-border);
border-radius: 4px;
}
input[type='checkbox']:checked {
background: var(--checkbox-active-bg);
border-color: var(--checkbox-active-border);
}
input[type='checkbox']:checked + span {
color: var(--title-fg);
}
input[type='checkbox']::before {
content: '';
width: 15px;
height: 15px;
background: var(--checkbox-active-fg);
mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSI5IiBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTIgOSI+PHBhdGggZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTEuNzguMjJhLjc1Ljc1IDAgMCAxIDAgMS4wNjFsLTcuMjYgNy4yNmEuNzUuNzUgMCAwIDEtMS4wNjIgMEwuMjAyIDUuMjg1YS43NS43NSAwIDAgMSAxLjA2MS0xLjA2MWwyLjcyNSAyLjcyM0wxMC43MTguMjJhLjc1Ljc1IDAgMCAxIDEuMDYyIDAiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==);
mask-position: center;
mask-repeat: no-repeat;
mask-size: 75%;
visibility: hidden;
}
input[type='checkbox']:checked::before {
visibility: visible;
}
input[type='checkbox']:disabled,
input[type='checkbox']:disabled + span {
opacity: 0.5;
}
/**
* Overriding existing defaults of dumper
*/
.dumper-dump,
.dumper-dump pre,
.dumper-dump code,
.dumper-dump samp {
font-family: var(--font-mono);
}
.dumper-dump pre {
font-size: 13px;
padding: calc(0.5vw + 8px);
}
.dumper-dump .dumper-toggle span {
font-size: 12px;
}
/**
* Layout centers all the elements for displaying the
* error and its metadata
*/
#layout {
max-width: 1280px;
margin: auto;
display: flex;
flex-direction: column;
gap: 24px;
padding: calc(1vw + 15px);
}
.card {
background: var(--card-bg);
box-shadow: var(--card-shadow);
border-radius: var(--radius);
padding: calc(2vw + 7px);
display: flex;
flex-direction: column;
gap: 12px;
}
.card-heading {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-title {
font-size: 18px;
color: var(--title-fg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.card-subtitle {
color: var(--subtitle-fg);
font-size: 12px;
font-weight: 600;
margin-top: 20px;
margin-bottom: 10px;
text-transform: uppercase;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.card-table {
width: 100%;
max-width: 100%;
border-spacing: 0;
table-layout: fixed;
overflow: scroll;
border: 1px solid var(--border);
border-radius: var(--radius);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.card-table td {
border-bottom: 1px solid var(--border);
padding: calc(0.8vw + 4px) calc(0.8vw + 8px);
word-wrap: break-word;
}
.card-table tr:last-child td {
border-bottom: 0;
}
.card-table td.table-value .dumper-dump pre {
padding: 0;
-webkit-font-smoothing: initial;
-moz-osx-font-smoothing: initial;
}
@media (min-width: 768px) {
#layout {
padding: 40px;
}
.card {
padding: 22px 30px;
}
.card-table td {
padding: 6px 12px;
}
.card-table td.table-key {
width: 180px;
}
.dumper-dump pre {
padding: 10px 15px;
}
}
@media (min-width: 1024px) {
.card-table td.table-key {
width: 200px;
}
}

View file

@ -0,0 +1,42 @@
/**
* BaseComponent that must be used to create custom components.
* It has support for loading CSS files and frontend scripts.
*/
export declare abstract class BaseComponent<Props = undefined> {
#private;
$props: Props;
/**
* Absolute path to the frontend JavaScript that should be
* injected within the HTML head. The JavaScript does not
* get transpiled, hence it should work cross browser by
* default.
*/
scriptFile?: string | URL;
/**
* Absolute path to the CSS file that should be injected
* within the HTML head.
*/
cssFile?: string | URL;
constructor(devMode: boolean);
/**
* Returns the styles for the component. The null value
* is not returned if no styles are associated with
* the component
*/
getStyles(): Promise<string | null>;
/**
* Returns the frontend script for the component. The null
* value is not returned if no styles are associated
* with the component
*/
getScript(): Promise<string | null>;
/**
* The toHTML method is used to output the HTML for the
* web view
*/
abstract toHTML(props: Props): Promise<string>;
/**
* The toANSI method is used to output the text for the console
*/
abstract toANSI(props: Props): Promise<string>;
}

View file

@ -0,0 +1,24 @@
import { type Colors } from '@poppinss/colors/types';
/**
* HTML escape string values so that they can be nested
* inside the pre-tags.
*/
export declare function htmlEscape(value: string): string;
/**
* Wraps a string value to be on multiple lines after
* a certain characters limit has been hit.
*/
export declare function wordWrap(value: string, options: {
width: number;
indent: string;
newLine: string;
escape?: (value: string) => string;
}): string;
/**
* Strips ANSI sequences from the string
*/
export declare function stripAnsi(value: string): string;
/**
* ANSI coloring library
*/
export declare const colors: Colors;

View file

@ -0,0 +1,28 @@
import { type ErrorMetadataGroups, type ErrorMetadataRow } from './types.js';
/**
* Attach metadata to the parsed error as groups, sections
* and rows.
*
* - Groups are rendered as cards
* - Sections are headings within the cards
* - And rows are rendered within HTML tables.
*
* The primitive row values are rendered as it is and rich data-types
* like Objects, Arrays, Maps are printed using dumper.
*/
export declare class Metadata {
#private;
/**
* Define a group, its sections and their rows. In case of
* existing groups/sections, the new data will be merged
* with the existing data
*/
group(name: string, sections: {
[section: string]: ErrorMetadataRow | ErrorMetadataRow[];
}): this;
/**
* Returns the existing metadata groups, sections and
* rows.
*/
toJSON(): ErrorMetadataGroups;
}

View file

@ -0,0 +1 @@
export declare const publicDirURL: URL;

View file

@ -0,0 +1,61 @@
import type { ParsedError } from 'youch-core/types';
import { type Metadata } from './metadata.js';
import type { YouchTemplates } from './types.js';
/**
* A super lightweight templates collection that allows composing
* HTML using TypeScript based components with the ability to
* override the component for a pre-defined template.
*
* @example
* ```ts
* const templates = new Templates()
* const html = templates.render(
* {
* title: 'Internal server error',
* error: await new ErrorParser().parse(error)
* }
* )
*
* // Override a template
* class MyHeader extends BaseComponent {
* async render() {}
* }
*
* templates.use('header', new MyHeader(templates.devMode))
* ```
*/
export declare class Templates {
#private;
devMode: boolean;
constructor(devMode: boolean);
/**
* Define a custom component to be used in place of the default component.
* Overriding components allows you control the HTML layout, styles and
* the frontend scripts of an HTML fragment.
*/
use<K extends keyof YouchTemplates>(templateName: K, component: YouchTemplates[K]): this;
/**
* Inject custom styles to the document. Injected styles are
* always placed after the global and the components style
* tags.
*/
injectStyles(cssFragment: string): this;
/**
* Returns the HTML output for the given parsed error
*/
toHTML(props: {
title: string;
ide?: string;
cspNonce?: string;
error: ParsedError;
metadata: Metadata;
}): Promise<string>;
/**
* Returns the ANSI output to be printed on the terminal
*/
toANSI(props: {
title: string;
error: ParsedError;
metadata: Metadata;
}): Promise<string>;
}

View file

@ -0,0 +1,17 @@
import { BaseComponent } from '../../component.js';
import type { ErrorCauseProps } from '../../types.js';
/**
* Displays the Error cause as a formatted value
*/
export declare class ErrorCause extends BaseComponent<ErrorCauseProps> {
cssFile: URL;
/**
* The toHTML method is used to output the HTML for the
* web view
*/
toHTML(props: ErrorCause['$props']): Promise<string>;
/**
* The toANSI method is used to output the text for the console
*/
toANSI(props: ErrorCauseProps): Promise<string>;
}

View file

@ -0,0 +1,8 @@
import {
ErrorCause
} from "../../../chunk-X53OIOJH.js";
import "../../../chunk-4L7RY2JA.js";
import "../../../chunk-PE3GG3TN.js";
export {
ErrorCause
};

View file

@ -0,0 +1,19 @@
import { BaseComponent } from '../../component.js';
import type { ErrorInfoProps } from '../../types.js';
/**
* Displays the error info including the response status text,
* error name, error message and the hint.
*/
export declare class ErrorInfo extends BaseComponent<ErrorInfoProps> {
cssFile: URL;
scriptFile: URL;
/**
* The toHTML method is used to output the HTML for the
* web view
*/
toHTML(props: ErrorInfoProps): Promise<string>;
/**
* The toANSI method is used to output the text for the console
*/
toANSI(props: ErrorInfoProps): Promise<string>;
}

View file

@ -0,0 +1,8 @@
import {
ErrorInfo
} from "../../../chunk-OIJ3WD7L.js";
import "../../../chunk-4L7RY2JA.js";
import "../../../chunk-PE3GG3TN.js";
export {
ErrorInfo
};

View file

@ -0,0 +1,18 @@
import { BaseComponent } from '../../component.js';
import type { ErrorMetadataProps } from '../../types.js';
/**
* Displays the error metadata as cards
*/
export declare class ErrorMetadata extends BaseComponent<ErrorMetadataProps> {
#private;
cssFile: URL;
/**
* The toHTML method is used to output the HTML for the
* web view
*/
toHTML(props: ErrorMetadataProps): Promise<string>;
/**
* The toANSI method is used to output the text for the console
*/
toANSI(): Promise<string>;
}

View file

@ -0,0 +1,7 @@
import {
ErrorMetadata
} from "../../../chunk-P36L72PL.js";
import "../../../chunk-PE3GG3TN.js";
export {
ErrorMetadata
};

View file

@ -0,0 +1,20 @@
import { BaseComponent } from '../../component.js';
import type { ErrorStackProps } from '../../types.js';
/**
* Displays the formatted and raw error stack along with the
* source code for individual stack frames
*/
export declare class ErrorStack extends BaseComponent<ErrorStackProps> {
#private;
cssFile: URL;
scriptFile: URL;
/**
* The toHTML method is used to output the HTML for the
* web view
*/
toHTML(props: ErrorStackProps): Promise<string>;
/**
* The toANSI method is used to output the text for the console
*/
toANSI(props: ErrorStackProps): Promise<string>;
}

View file

@ -0,0 +1,8 @@
import {
ErrorStack
} from "../../../chunk-EJH674NB.js";
import "../../../chunk-4L7RY2JA.js";
import "../../../chunk-PE3GG3TN.js";
export {
ErrorStack
};

View file

@ -0,0 +1,18 @@
import { BaseComponent } from '../../component.js';
import type { ErrorStackSourceProps } from '../../types.js';
/**
* Pretty prints the stack frame source code with syntax
* highlighting.
*/
export declare class ErrorStackSource extends BaseComponent<ErrorStackSourceProps> {
cssFile: URL;
/**
* The toHTML method is used to output the HTML for the
* web view
*/
toHTML(props: ErrorStackSourceProps): Promise<string>;
/**
* The toANSI method is used to output the text for the console
*/
toANSI(props: ErrorStackSourceProps): Promise<string>;
}

View file

@ -0,0 +1,8 @@
import {
ErrorStackSource
} from "../../../chunk-7QV3D5YX.js";
import "../../../chunk-4L7RY2JA.js";
import "../../../chunk-PE3GG3TN.js";
export {
ErrorStackSource
};

View file

@ -0,0 +1,19 @@
import { BaseComponent } from '../../component.js';
import type { ComponentSharedProps } from '../../types.js';
/**
* Renders the header for the error page. It contains only the
* theme-switcher for now
*/
export declare class Header extends BaseComponent<ComponentSharedProps> {
cssFile: URL;
scriptFile: URL;
/**
* The toHTML method is used to output the HTML for the
* web view
*/
toHTML(): Promise<string>;
/**
* The toANSI method is used to output the text for the console
*/
toANSI(): Promise<string>;
}

View file

@ -0,0 +1,7 @@
import {
Header
} from "../../../chunk-AUGPHE32.js";
import "../../../chunk-PE3GG3TN.js";
export {
Header
};

View file

@ -0,0 +1,22 @@
import { BaseComponent } from '../../component.js';
import type { LayoutProps } from '../../types.js';
/**
* Layout component renders the HTML structure for the document
* along with the styles for the global elements.
*
* You can define a custom Layout if you want to modify the HTML
* structure or the CSS variables for the colors.
*/
export declare class Layout extends BaseComponent<LayoutProps> {
cssFile: URL;
scriptFile: URL;
/**
* The toHTML method is used to output the HTML for the
* web view
*/
toHTML(props: LayoutProps): Promise<string>;
/**
* The toANSI method is used to output the text for the console
*/
toANSI(props: LayoutProps): Promise<string>;
}

View file

@ -0,0 +1,7 @@
import {
Layout
} from "../../../chunk-CM7DWJNZ.js";
import "../../../chunk-PE3GG3TN.js";
export {
Layout
};

View file

@ -0,0 +1,178 @@
import type { ParsedError, StackFrame } from 'youch-core/types';
import type { Metadata } from './metadata.js';
import type { BaseComponent } from './component.js';
export * from 'youch-core/types';
/**
* Props accepted by the Layout component
*/
export type LayoutProps = ComponentSharedProps & {
title: string;
children: () => string | Promise<string>;
};
/**
* Props accepted by the Error stack source component
*/
export type ErrorStackSourceProps = ComponentSharedProps & {
error: ParsedError;
frame: StackFrame;
};
/**
* Props accepted by the Error stack component
*/
export type ErrorStackProps = ComponentSharedProps & {
error: ParsedError;
ide: string;
sourceCodeRenderer: (error: ParsedError, frame: StackFrame) => Promise<string>;
};
/**
* Props accepted by the Error info component
*/
export type ErrorInfoProps = ComponentSharedProps & {
title: string;
error: ParsedError;
};
/**
* Props accepted by the Error cause component
*/
export type ErrorCauseProps = ComponentSharedProps & {
error: ParsedError;
};
/**
* Props accepted by the Error metadata component
*/
export type ErrorMetadataProps = ComponentSharedProps & {
metadata: Metadata;
};
/**
* Error metadata row represents a key-value pair
*/
export type ErrorMetadataRow = {
key: string;
dump?: boolean;
value: any;
};
/**
* Representation of Error metadata groups. Each group is
* a collection of named sections
*/
export type ErrorMetadataGroups = {
[group: string]: {
[section: string]: ErrorMetadataRow | ErrorMetadataRow[];
};
};
/**
* Props shared with all the components
*/
export type ComponentSharedProps = {
ide?: string;
cspNonce?: string;
};
/**
* Collection of known templates. Only these templates can be
* rendered using the Templates collection
*/
export type YouchTemplates = {
header: BaseComponent<ComponentSharedProps>;
layout: BaseComponent<LayoutProps>;
errorInfo: BaseComponent<ErrorInfoProps>;
errorStack: BaseComponent<ErrorStackProps>;
errorStackSource: BaseComponent<ErrorStackSourceProps>;
errorCause: BaseComponent<ErrorCauseProps>;
errorMetadata: BaseComponent<ErrorMetadataProps>;
};
/**
* Set of options accepted by Youch when rendering error
* to HTML
*/
export type YouchHTMLOptions = {
/**
* Define the offset to skip certain stack frames from
* the top
*/
offset?: number;
/**
* Number of lines of code to display for the error stack frame.
* For example: If you set the frameSourceBuffer=7, then 3 lines
* above the error line and 3 lines after the error line will
* be displayed.
*/
frameSourceBuffer?: number;
/**
* Define the error title. It could be the HTTP status
* text
*/
title?: string;
/**
* Define the name of the IDE in which to open the files when
* the filename anchor tag is clicked.
*
* Following is the list of supported editors
*
* - textmate
* - macvim
* - emacs
* - sublime
* - phpstorm
* - atom
* - vscode
*
* You can also specify the URL for the editor via the `ide` property. Make
* sure to specify the placeholders for the filename and the line number
* as follows.
*
* someprotocol://file/%f:%l
*
* - %f is the filename placeholder
* - %l is the line number placeholder
*/
ide?: string;
/**
* CSP nonce to define on inline style and script tags
*/
cspNonce?: string;
/**
* Specify the HTTP request properties to be printed as
* metadata under the "Request" group
*/
request?: {
url?: string;
method?: string;
headers?: Record<string, string | string[] | undefined>;
};
};
/**
* Set of options accepted by Youch when rendering error
* to ANSI output
*/
export type YouchANSIOptions = {
/**
* Define the offset to skip certain stack frames from
* the top
*/
offset?: number;
/**
* Number of lines of code to display for the error stack frame.
* For example: If you set the frameSourceBuffer=7, then 3 lines
* above the error line and 3 lines after the error line will
* be displayed.
*/
frameSourceBuffer?: number;
};
/**
* Set of options accepted by Youch when rendering error
* to JSON output
*/
export type YouchJSONOptions = {
/**
* Define the offset to skip certain stack frames from
* the top
*/
offset?: number;
/**
* Number of lines of code to display for the error stack frame.
* For example: If you set the frameSourceBuffer=7, then 3 lines
* above the error line and 3 lines after the error line will
* be displayed.
*/
frameSourceBuffer?: number;
};

View file

@ -0,0 +1,2 @@
// src/types.ts
export * from "youch-core/types";

View file

@ -0,0 +1,48 @@
import type { Parser, SourceLoader, Transformer } from 'youch-core/types';
import { Metadata } from './metadata.js';
import { Templates } from './templates.js';
import { type YouchANSIOptions, type YouchHTMLOptions, type YouchJSONOptions } from './types.js';
/**
* Youch exposes the API to render errors to HTML output
*/
export declare class Youch {
#private;
/**
* Manage templates used for converting error to the HTML
* output
*/
templates: Templates;
/**
* Define metadata to be displayed alongside the error output
*/
metadata: Metadata;
/**
* Define custom implementation for loading the source code
* of a stack frame.
*/
defineSourceLoader(loader: SourceLoader): this;
/**
* Define a custom parser. Parsers are executed before the
* error gets parsed and provides you with an option to
* modify the error
*/
useParser(parser: Parser): this;
/**
* Define a custom transformer. Transformers are executed
* after the error has been parsed and can mutate the
* properties of the parsed error.
*/
useTransformer(transformer: Transformer): this;
/**
* Parses error to JSON
*/
toJSON(error: unknown, options?: YouchJSONOptions): Promise<import("youch-core/types").ParsedError>;
/**
* Render error to HTML
*/
toHTML(error: unknown, options?: YouchHTMLOptions): Promise<string>;
/**
* Render error to ANSI output
*/
toANSI(error: unknown, options?: YouchANSIOptions): Promise<string>;
}

View file

@ -0,0 +1,28 @@
MIT License
Cookie-es copyright (c) Pooya Parsa <pooya@pi0.io>
Cookie parsing based on https://github.com/jshttp/cookie
Copyright (c) 2012-2014 Roman Shtylman <shtylman@gmail.com>
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
Set-Cookie parsing based on https://github.com/nfriedly/set-cookie-parser
Copyright (c) 2015 Nathan Friedly <nathan@nfriedly.com> (http://nfriedly.com/)
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

@ -0,0 +1,52 @@
# 🍪 cookie-es
<!-- automd:badges bundlejs packagephobia codecov -->
[![npm version](https://img.shields.io/npm/v/cookie-es)](https://npmjs.com/package/cookie-es)
[![npm downloads](https://img.shields.io/npm/dm/cookie-es)](https://npm.chart.dev/cookie-es)
[![bundle size](https://img.shields.io/bundlejs/size/cookie-es)](https://bundlejs.com/?q=cookie-es)
[![install size](https://badgen.net/packagephobia/install/cookie-es)](https://packagephobia.com/result?p=cookie-es)
[![codecov](https://img.shields.io/codecov/c/gh/unjs/cookie-es)](https://codecov.io/gh/unjs/cookie-es)
<!-- /automd -->
ESM ready [`Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie) and [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) parser and serializer based on [cookie](https://github.com/jshttp/cookie) and [set-cookie-parser](https://github.com/nfriedly/set-cookie-parser) with built-in types.
## Usage
Install:
```sh
# ✨ Auto-detect (npm, yarn, pnpm, bun, deno)
npx nypm install cookie-es
```
Import:
**ESM** (Node.js, Bun, Deno)
```js
import {
parse,
serialize,
parseSetCookie,
splitSetCookieString,
} from "cookie-es";
```
**CDN** (Deno, Bun and Browsers)
```js
import {
parse,
serialize,
parseSetCookie,
splitSetCookieString,
} from "https://esm.sh/cookie-es";
```
## License
[MIT](./LICENSE)
Based on [jshttp/cookie](https://github.com/jshttp/cookie) (Roman Shtylman and hristopher Wilson) and [nfriedly/set-cookie-parser](https://github.com/nfriedly/set-cookie-parser) (Nathan Friedly).

View file

@ -0,0 +1,222 @@
/**
* Basic HTTP cookie parser and serializer for HTTP servers.
*/
/**
* Additional serialization options
*/
interface CookieSerializeOptions {
/**
* Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.3|Domain Set-Cookie attribute}. By default, no
* domain is set, and most clients will consider the cookie to apply to only
* the current domain.
*/
domain?: string | undefined;
/**
* Specifies a function that will be used to encode a cookie's value. Since
* value of a cookie has a limited character set (and must be a simple
* string), this function can be used to encode a value into a string suited
* for a cookie's value.
*
* The default function is the global `encodeURIComponent`, which will
* encode a JavaScript string into UTF-8 byte sequences and then URL-encode
* any that fall outside of the cookie range.
*/
encode?(value: string): string;
/**
* Specifies the `Date` object to be the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.1|`Expires` `Set-Cookie` attribute}. By default,
* no expiration is set, and most clients will consider this a "non-persistent cookie" and will delete
* it on a condition like exiting a web browser application.
*
* *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification}
* states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
* possible not all clients by obey this, so if both are set, they should
* point to the same date and time.
*/
expires?: Date | undefined;
/**
* Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.6|`HttpOnly` `Set-Cookie` attribute}.
* When truthy, the `HttpOnly` attribute is set, otherwise it is not. By
* default, the `HttpOnly` attribute is not set.
*
* *Note* be careful when setting this to true, as compliant clients will
* not allow client-side JavaScript to see the cookie in `document.cookie`.
*/
httpOnly?: boolean | undefined;
/**
* Specifies the number (in seconds) to be the value for the `Max-Age`
* `Set-Cookie` attribute. The given number will be converted to an integer
* by rounding down. By default, no maximum age is set.
*
* *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification}
* states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
* possible not all clients by obey this, so if both are set, they should
* point to the same date and time.
*/
maxAge?: number | undefined;
/**
* Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4|`Path` `Set-Cookie` attribute}.
* By default, the path is considered the "default path".
*/
path?: string | undefined;
/**
* Specifies the `string` to be the value for the [`Priority` `Set-Cookie` attribute][rfc-west-cookie-priority-00-4.1].
*
* - `'low'` will set the `Priority` attribute to `Low`.
* - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set.
* - `'high'` will set the `Priority` attribute to `High`.
*
* More information about the different priority levels can be found in
* [the specification][rfc-west-cookie-priority-00-4.1].
*
* **note** This is an attribute that has not yet been fully standardized, and may change in the future.
* This also means many clients may ignore this attribute until they understand it.
*/
priority?: "low" | "medium" | "high" | undefined;
/**
* Specifies the boolean or string to be the value for the {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|`SameSite` `Set-Cookie` attribute}.
*
* - `true` will set the `SameSite` attribute to `Strict` for strict same
* site enforcement.
* - `false` will not set the `SameSite` attribute.
* - `'lax'` will set the `SameSite` attribute to Lax for lax same site
* enforcement.
* - `'strict'` will set the `SameSite` attribute to Strict for strict same
* site enforcement.
* - `'none'` will set the SameSite attribute to None for an explicit
* cross-site cookie.
*
* More information about the different enforcement levels can be found in {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|the specification}.
*
* *note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.
*/
sameSite?: true | false | "lax" | "strict" | "none" | undefined;
/**
* Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.5|`Secure` `Set-Cookie` attribute}. When truthy, the
* `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
*
* *Note* be careful when setting this to `true`, as compliant clients will
* not send the cookie back to the server in the future if the browser does
* not have an HTTPS connection.
*/
secure?: boolean | undefined;
/**
* Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#section-2.1)
* attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the
* `Partitioned` attribute is not set.
*
* **note** This is an attribute that has not yet been fully standardized, and may change in the future.
* This also means many clients may ignore this attribute until they understand it.
*
* More information can be found in the [proposal](https://github.com/privacycg/CHIPS).
*/
partitioned?: boolean;
}
/**
* Additional parsing options
*/
interface CookieParseOptions {
/**
* Specifies a function that will be used to decode a cookie's value. Since
* the value of a cookie has a limited character set (and must be a simple
* string), this function can be used to decode a previously-encoded cookie
* value into a JavaScript string or other object.
*
* The default function is the global `decodeURIComponent`, which will decode
* any URL-encoded sequences into their byte representations.
*
* *Note* if an error is thrown from this function, the original, non-decoded
* cookie value will be returned as the cookie's value.
*/
decode?(value: string): string;
/**
* Custom function to filter parsing specific keys.
*/
filter?(key: string): boolean;
}
/**
* Parse an HTTP Cookie header string and returning an object of all cookie
* name-value pairs.
*
* @param str the string representing a `Cookie` header value
* @param [options] object containing parsing options
*/
declare function parse(str: string, options?: CookieParseOptions): Record<string, string>;
/**
* Serialize a cookie name-value pair into a `Set-Cookie` header string.
*
* @param name the name for the cookie
* @param value value to set the cookie to
* @param [options] object containing serialization options
* @throws {TypeError} when `maxAge` options is invalid
*/
declare function serialize(name: string, value: string, options?: CookieSerializeOptions): string;
interface SetCookieParseOptions {
/**
* Custom decode function to use on cookie values.
*
* By default, `decodeURIComponent` is used.
*
* **Note:** If decoding fails, the original (undecoded) value will be used
*/
decode?: false | ((value: string) => string);
}
interface SetCookie {
/**
* Cookie name
*/
name: string;
/**
* Cookie value
*/
value: string;
/**
* Cookie path
*/
path?: string | undefined;
/**
* Absolute expiration date for the cookie
*/
expires?: Date | undefined;
/**
* Relative max age of the cookie in seconds from when the client receives it (integer or undefined)
*
* Note: when using with express's res.cookie() method, multiply maxAge by 1000 to convert to milliseconds
*/
maxAge?: number | undefined;
/**
* Domain for the cookie,
* May begin with "." to indicate the named domain or any subdomain of it
*/
domain?: string | undefined;
/**
* Indicates that this cookie should only be sent over HTTPs
*/
secure?: boolean | undefined;
/**
* Indicates that this cookie should not be accessible to client-side JavaScript
*/
httpOnly?: boolean | undefined;
/**
* Indicates a cookie ought not to be sent along with cross-site requests
*/
sameSite?: true | false | "lax" | "strict" | "none" | undefined;
[key: string]: unknown;
}
/**
* Parse a [Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) header string into an object.
*/
declare function parseSetCookie(setCookieValue: string, options?: SetCookieParseOptions): SetCookie;
/**
* Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
* that are within a single set-cookie field-value, such as in the Expires portion.
*
* See https://tools.ietf.org/html/rfc2616#section-4.2
*/
declare function splitSetCookieString(cookiesString: string | string[]): string[];
export { type CookieParseOptions, type CookieSerializeOptions, type SetCookie, type SetCookieParseOptions, parse, parseSetCookie, serialize, splitSetCookieString };

View file

@ -0,0 +1,262 @@
function parse(str, options) {
if (typeof str !== "string") {
throw new TypeError("argument str must be a string");
}
const obj = {};
const opt = options || {};
const dec = opt.decode || decode;
let index = 0;
while (index < str.length) {
const eqIdx = str.indexOf("=", index);
if (eqIdx === -1) {
break;
}
let endIdx = str.indexOf(";", index);
if (endIdx === -1) {
endIdx = str.length;
} else if (endIdx < eqIdx) {
index = str.lastIndexOf(";", eqIdx - 1) + 1;
continue;
}
const key = str.slice(index, eqIdx).trim();
if (opt?.filter && !opt?.filter(key)) {
index = endIdx + 1;
continue;
}
if (void 0 === obj[key]) {
let val = str.slice(eqIdx + 1, endIdx).trim();
if (val.codePointAt(0) === 34) {
val = val.slice(1, -1);
}
obj[key] = tryDecode(val, dec);
}
index = endIdx + 1;
}
return obj;
}
function decode(str) {
return str.includes("%") ? decodeURIComponent(str) : str;
}
function tryDecode(str, decode2) {
try {
return decode2(str);
} catch {
return str;
}
}
const fieldContentRegExp = /^[\u0009\u0020-\u007E\u0080-\u00FF]+$/;
function serialize(name, value, options) {
const opt = options || {};
const enc = opt.encode || encodeURIComponent;
if (typeof enc !== "function") {
throw new TypeError("option encode is invalid");
}
if (!fieldContentRegExp.test(name)) {
throw new TypeError("argument name is invalid");
}
const encodedValue = enc(value);
if (encodedValue && !fieldContentRegExp.test(encodedValue)) {
throw new TypeError("argument val is invalid");
}
let str = name + "=" + encodedValue;
if (void 0 !== opt.maxAge && opt.maxAge !== null) {
const maxAge = opt.maxAge - 0;
if (Number.isNaN(maxAge) || !Number.isFinite(maxAge)) {
throw new TypeError("option maxAge is invalid");
}
str += "; Max-Age=" + Math.floor(maxAge);
}
if (opt.domain) {
if (!fieldContentRegExp.test(opt.domain)) {
throw new TypeError("option domain is invalid");
}
str += "; Domain=" + opt.domain;
}
if (opt.path) {
if (!fieldContentRegExp.test(opt.path)) {
throw new TypeError("option path is invalid");
}
str += "; Path=" + opt.path;
}
if (opt.expires) {
if (!isDate(opt.expires) || Number.isNaN(opt.expires.valueOf())) {
throw new TypeError("option expires is invalid");
}
str += "; Expires=" + opt.expires.toUTCString();
}
if (opt.httpOnly) {
str += "; HttpOnly";
}
if (opt.secure) {
str += "; Secure";
}
if (opt.priority) {
const priority = typeof opt.priority === "string" ? opt.priority.toLowerCase() : opt.priority;
switch (priority) {
case "low": {
str += "; Priority=Low";
break;
}
case "medium": {
str += "; Priority=Medium";
break;
}
case "high": {
str += "; Priority=High";
break;
}
default: {
throw new TypeError("option priority is invalid");
}
}
}
if (opt.sameSite) {
const sameSite = typeof opt.sameSite === "string" ? opt.sameSite.toLowerCase() : opt.sameSite;
switch (sameSite) {
case true: {
str += "; SameSite=Strict";
break;
}
case "lax": {
str += "; SameSite=Lax";
break;
}
case "strict": {
str += "; SameSite=Strict";
break;
}
case "none": {
str += "; SameSite=None";
break;
}
default: {
throw new TypeError("option sameSite is invalid");
}
}
}
if (opt.partitioned) {
str += "; Partitioned";
}
return str;
}
function isDate(val) {
return Object.prototype.toString.call(val) === "[object Date]" || val instanceof Date;
}
function parseSetCookie(setCookieValue, options) {
const parts = (setCookieValue || "").split(";").filter((str) => typeof str === "string" && !!str.trim());
const nameValuePairStr = parts.shift() || "";
const parsed = _parseNameValuePair(nameValuePairStr);
const name = parsed.name;
let value = parsed.value;
try {
value = options?.decode === false ? value : (options?.decode || decodeURIComponent)(value);
} catch {
}
const cookie = {
name,
value
};
for (const part of parts) {
const sides = part.split("=");
const partKey = (sides.shift() || "").trimStart().toLowerCase();
const partValue = sides.join("=");
switch (partKey) {
case "expires": {
cookie.expires = new Date(partValue);
break;
}
case "max-age": {
cookie.maxAge = Number.parseInt(partValue, 10);
break;
}
case "secure": {
cookie.secure = true;
break;
}
case "httponly": {
cookie.httpOnly = true;
break;
}
case "samesite": {
cookie.sameSite = partValue;
break;
}
default: {
cookie[partKey] = partValue;
}
}
}
return cookie;
}
function _parseNameValuePair(nameValuePairStr) {
let name = "";
let value = "";
const nameValueArr = nameValuePairStr.split("=");
if (nameValueArr.length > 1) {
name = nameValueArr.shift();
value = nameValueArr.join("=");
} else {
value = nameValuePairStr;
}
return { name, value };
}
function splitSetCookieString(cookiesString) {
if (Array.isArray(cookiesString)) {
return cookiesString.flatMap((c) => splitSetCookieString(c));
}
if (typeof cookiesString !== "string") {
return [];
}
const cookiesStrings = [];
let pos = 0;
let start;
let ch;
let lastComma;
let nextStart;
let cookiesSeparatorFound;
const skipWhitespace = () => {
while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
pos += 1;
}
return pos < cookiesString.length;
};
const notSpecialChar = () => {
ch = cookiesString.charAt(pos);
return ch !== "=" && ch !== ";" && ch !== ",";
};
while (pos < cookiesString.length) {
start = pos;
cookiesSeparatorFound = false;
while (skipWhitespace()) {
ch = cookiesString.charAt(pos);
if (ch === ",") {
lastComma = pos;
pos += 1;
skipWhitespace();
nextStart = pos;
while (pos < cookiesString.length && notSpecialChar()) {
pos += 1;
}
if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
cookiesSeparatorFound = true;
pos = nextStart;
cookiesStrings.push(cookiesString.slice(start, lastComma));
start = pos;
} else {
pos = lastComma + 1;
}
} else {
pos += 1;
}
}
if (!cookiesSeparatorFound || pos >= cookiesString.length) {
cookiesStrings.push(cookiesString.slice(start));
}
}
return cookiesStrings;
}
export { parse, parseSetCookie, serialize, splitSetCookieString };

View file

@ -0,0 +1,37 @@
{
"name": "cookie-es",
"version": "2.0.0",
"repository": "unjs/cookie-es",
"license": "MIT",
"sideEffects": false,
"type": "module",
"exports": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"types": "./dist/index.d.mts",
"files": [
"dist"
],
"scripts": {
"build": "unbuild",
"dev": "vitest --coverage",
"lint": "eslint --cache . && prettier -c src test",
"lint:fix": "automd && eslint --cache . --fix && prettier -c src test -w",
"release": "pnpm test && pnpm build && changelogen --release --push && npm publish",
"test": "pnpm lint && vitest run --coverage"
},
"devDependencies": {
"@types/node": "^22.13.5",
"@vitest/coverage-v8": "^3.0.7",
"automd": "^0.4.0",
"changelogen": "^0.6.0",
"eslint": "^9.21.0",
"eslint-config-unjs": "^0.4.2",
"prettier": "^3.5.2",
"typescript": "^5.7.3",
"unbuild": "^3.5.0",
"vitest": "^3.0.7"
},
"packageManager": "pnpm@10.5.2"
}

132
Frontend-Learner/node_modules/youch/package.json generated vendored Normal file
View file

@ -0,0 +1,132 @@
{
"name": "youch",
"description": "Pretty print JavaScript errors on the Web and the Terminal",
"version": "4.1.0-beta.13",
"type": "module",
"files": [
"build",
"!build/bin",
"!build/examples",
"!build/tests"
],
"main": "build/index.js",
"exports": {
".": "./build/index.js",
"./types": "./build/src/types.js",
"./templates/*": "./build/src/templates/*/main.js"
},
"scripts": {
"pretest": "npm run lint",
"test": "c8 npm run quick:test",
"lint": "eslint .",
"format": "prettier --write .",
"typecheck": "tsc --noEmit",
"precompile": "npm run lint",
"copy:assets": "copyfiles --up=1 src/public/**/* build",
"compile": "tsup-node && tsc --emitDeclarationOnly --declaration",
"build": "npm run compile && npm run copy:assets",
"version": "npm run build",
"prepublishOnly": "npm run build",
"release": "release-it",
"quick:test": "node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts"
},
"devDependencies": {
"@adonisjs/eslint-config": "^3.0.0-next.0",
"@adonisjs/prettier-config": "^1.4.5",
"@adonisjs/tsconfig": "^2.0.0-next.0",
"@aws-sdk/client-s3": "^3.922.0",
"@aws-sdk/s3-request-presigner": "^3.922.0",
"@japa/assert": "^4.1.1",
"@japa/expect": "^3.0.6",
"@japa/expect-type": "^2.0.3",
"@japa/file-system": "^2.3.2",
"@japa/runner": "^4.4.0",
"@japa/snapshot": "^2.0.9",
"@poppinss/exception": "^1.2.2",
"@poppinss/ts-exec": "^1.4.1",
"@release-it/conventional-changelog": "^10.0.1",
"@types/jsdom": "^27.0.0",
"@types/node": "^24.10.0",
"@types/pg": "^8.15.6",
"axios": "^1.13.1",
"c8": "^10.1.3",
"copyfiles": "^2.4.1",
"eslint": "^9.39.0",
"flydrive": "^1.3.0",
"jsdom": "^27.1.0",
"pg": "^8.16.3",
"prettier": "^3.6.2",
"release-it": "^19.0.5",
"tsup": "^8.5.0",
"typescript": "^5.9.3"
},
"dependencies": {
"@poppinss/colors": "^4.1.5",
"@poppinss/dumper": "^0.6.5",
"@speed-highlight/core": "^1.2.9",
"cookie-es": "^2.0.0",
"youch-core": "^0.3.3"
},
"homepage": "https://github.com/poppinss/youch#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/poppinss/youch.git"
},
"bugs": {
"url": "https://github.com/poppinss/youch/issues"
},
"keywords": [],
"author": "Harminder Virk <virk@adonisjs.com>",
"license": "MIT",
"publishConfig": {
"access": "public",
"provenance": true
},
"tsup": {
"entry": [
"index.ts",
"src/types.ts",
"src/templates/**/*.ts"
],
"outDir": "./build",
"clean": true,
"format": "esm",
"dts": false,
"sourcemap": false,
"target": "esnext"
},
"release-it": {
"git": {
"requireCleanWorkingDir": true,
"requireUpstream": true,
"commitMessage": "chore(release): ${version}",
"tagAnnotation": "v${version}",
"push": true,
"tagName": "v${version}"
},
"github": {
"release": true
},
"npm": {
"publish": true,
"skipChecks": true
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": {
"name": "angular"
}
}
}
},
"c8": {
"reporter": [
"text",
"html"
],
"exclude": [
"tests/**"
]
},
"prettier": "@adonisjs/prettier-config"
}