mirror of
https://github.com/donlon/cloudflare-error-page.git
synced 2026-01-06 15:41:45 +00:00
misc updates
This commit is contained in:
26
README.md
26
README.md
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## What does this project do?
|
## What does this project do?
|
||||||
|
|
||||||
This project creates customized error pages that mimics the well-known Cloudflare error page. You can also embed it into your website.
|
This project creates customized error pages that mimic the well-known Cloudflare error page. You can also embed it into your own website.
|
||||||
|
|
||||||
## Online Editor
|
## Online Editor
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ Here's an online editor to create customized error pages and example server apps
|
|||||||
|
|
||||||
### Python
|
### Python
|
||||||
|
|
||||||
Install `cloudflare-error-page` with pip.
|
Install `cloudflare-error-page` using pip.
|
||||||
|
|
||||||
``` Bash
|
``` Bash
|
||||||
# Install from PyPI
|
# Install from PyPI
|
||||||
@@ -26,7 +26,7 @@ pip install cloudflare-error-page
|
|||||||
pip install git+https://github.com/donlon/cloudflare-error-page.git
|
pip install git+https://github.com/donlon/cloudflare-error-page.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you can generate an error page using the `render` function. ([example.py](examples/example.py))
|
Then an error page can be generated using the `render` function provided by the package. ([example.py](examples/example.py))
|
||||||
|
|
||||||
``` Python
|
``` Python
|
||||||
import webbrowser
|
import webbrowser
|
||||||
@@ -48,7 +48,7 @@ error_page = render_cf_error_page({
|
|||||||
"status": 'ok',
|
"status": 'ok',
|
||||||
"location": 'example.com',
|
"location": 'example.com',
|
||||||
},
|
},
|
||||||
# can be 'browser', 'cloudflare', or 'host'
|
# Position of the error indicator, valid options are 'browser', 'cloudflare', and 'host'
|
||||||
'error_source': 'cloudflare',
|
'error_source': 'cloudflare',
|
||||||
|
|
||||||
# Texts shown in the bottom of the page
|
# Texts shown in the bottom of the page
|
||||||
@@ -56,9 +56,11 @@ error_page = render_cf_error_page({
|
|||||||
'what_can_i_do': '<p>Please try again in a few minutes.</p>',
|
'what_can_i_do': '<p>Please try again in a few minutes.</p>',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Write generated webpage to file
|
||||||
with open('error.html', 'w') as f:
|
with open('error.html', 'w') as f:
|
||||||
f.write(error_page)
|
f.write(error_page)
|
||||||
|
|
||||||
|
# Open the generated page in browser
|
||||||
webbrowser.open('error.html')
|
webbrowser.open('error.html')
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -83,14 +85,14 @@ import { render as render_cf_error_page } from 'cloudflare-error-page';
|
|||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
/* Some code that break prod. Recently pushed by a new employee. */
|
/* Some code that break prod. Pushed by a new employee recently. */
|
||||||
let [feature_values, _] = features
|
let [feature_values, _] = features
|
||||||
.append_with_names(self.config.feature_names)
|
.append_with_names(self.config.feature_names)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle the error intelligently by using a custom error handler */
|
|
||||||
app.use((err, req, res) => {
|
app.use((err, req, res) => {
|
||||||
|
/* Handle the error intelligently by using a custom handler */
|
||||||
res.status(500).send(render_cf_error_page({
|
res.status(500).send(render_cf_error_page({
|
||||||
"title": "Internal server error",
|
"title": "Internal server error",
|
||||||
"error_code": "500",
|
"error_code": "500",
|
||||||
@@ -182,15 +184,15 @@ params = {
|
|||||||
|
|
||||||
Ray ID and user IP field in the error page can be set by `ray_id` and `client_ip` properties in the `params` argument passed to the render function. The real Cloudflare Ray ID and the data center location of current request can be extracted from the `Cf-Ray` request header (e.g. `Cf-Ray: 230b030023ae2822-SJC`). Detailed description of this header can be found at [Cloudflare documentation](https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-ray).
|
Ray ID and user IP field in the error page can be set by `ray_id` and `client_ip` properties in the `params` argument passed to the render function. The real Cloudflare Ray ID and the data center location of current request can be extracted from the `Cf-Ray` request header (e.g. `Cf-Ray: 230b030023ae2822-SJC`). Detailed description of this header can be found at [Cloudflare documentation](https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-ray).
|
||||||
|
|
||||||
To lookup the city name of the data center corresponding to the three letter code in the header, you can use a location list from [here](https://github.com/Netrvin/cloudflare-colo-list/blob/main/DC-Colos.json)
|
To lookup the city name of the data center corresponding to the three letter code in the header, you can use a location list [here](https://github.com/Netrvin/cloudflare-colo-list/blob/main/DC-Colos.json)
|
||||||
|
|
||||||
The demo server runs in our website did handle these. Take a look at [this file](https://github.com/donlon/cloudflare-error-page/blob/94c3c4ddbe521dee0c9a880ef33fa7a9f0720cbe/editor/server/utils.py#L34) for reference.
|
The demo server runs in our website did handle these. Take a look at [this file](https://github.com/donlon/cloudflare-error-page/blob/e2226ff5bb7a877c9fe3ac09deadccdc58b0c1c7/editor/server/utils.py#L78) for reference.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
- [cloudflare-error-page-3th.pages.dev](https://cloudflare-error-page-3th.pages.dev/)
|
- [cloudflare-error-page-3th.pages.dev](https://cloudflare-error-page-3th.pages.dev/)
|
||||||
|
|
||||||
Error page of every HTTP status code (reload to show random page).
|
Error page of every HTTP status code (reload to show random pages).
|
||||||
|
|
||||||
- [oftx/cloudflare-error-page](https://github.com/oftx/cloudflare-error-page)
|
- [oftx/cloudflare-error-page](https://github.com/oftx/cloudflare-error-page)
|
||||||
|
|
||||||
@@ -205,7 +207,7 @@ The demo server runs in our website did handle these. Take a look at [this file]
|
|||||||
"error_code": "500",
|
"error_code": "500",
|
||||||
"time": "2025-11-18 12:34:56 UTC", // Current UTC time will be shown if empty
|
"time": "2025-11-18 12:34:56 UTC", // Current UTC time will be shown if empty
|
||||||
|
|
||||||
// Configuration of "Visit ... for more information" line
|
// Configuration of "Visit ... for more information"
|
||||||
"more_information": {
|
"more_information": {
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"text": "cloudflare.com",
|
"text": "cloudflare.com",
|
||||||
@@ -213,7 +215,7 @@ The demo server runs in our website did handle these. Take a look at [this file]
|
|||||||
"for": "more information",
|
"for": "more information",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Configuration of the Browser/Cloudflare/Host status
|
// Configuration of the Browser/Cloudflare/Host status block
|
||||||
"browser_status": {
|
"browser_status": {
|
||||||
"status": "ok", // "ok" or "error"
|
"status": "ok", // "ok" or "error"
|
||||||
"location": "You",
|
"location": "You",
|
||||||
@@ -235,7 +237,7 @@ The demo server runs in our website did handle these. Take a look at [this file]
|
|||||||
"status_text": "Working",
|
"status_text": "Working",
|
||||||
"status_text_color": "#9bca3e",
|
"status_text_color": "#9bca3e",
|
||||||
},
|
},
|
||||||
// Position of the error indicator, can be "browser", "cloudflare", or "host"
|
// Position of the error indicator, valid options are 'browser', 'cloudflare', and 'host'
|
||||||
"error_source": "host",
|
"error_source": "host",
|
||||||
|
|
||||||
"what_happened": "<p>There is an internal server error on Cloudflare's network.</p>",
|
"what_happened": "<p>There is an internal server error on Cloudflare's network.</p>",
|
||||||
|
|||||||
@@ -169,7 +169,6 @@
|
|||||||
grid-area: code;
|
grid-area: code;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 700px !important;
|
|
||||||
max-height: 1200px !important;
|
max-height: 1200px !important;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 0.8em !important;
|
font-size: 0.8em !important;
|
||||||
@@ -235,7 +234,7 @@
|
|||||||
<label for="presetSelect">Preset</label>
|
<label for="presetSelect">Preset</label>
|
||||||
<select id="presetSelect" class="form-select form-select-sm">
|
<select id="presetSelect" class="form-select form-select-sm">
|
||||||
<option value="default">Internal server error (Default)</option>
|
<option value="default">Internal server error (Default)</option>
|
||||||
<option value="empty">Empty</option>
|
<option value="empty">(Empty)</option>
|
||||||
<option value="catastrophic">Catastrophic failure</option>
|
<option value="catastrophic">Catastrophic failure</option>
|
||||||
<option value="working">Server working</option>
|
<option value="working">Server working</option>
|
||||||
<option value="teapot">Teapot</option>
|
<option value="teapot">Teapot</option>
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ class EjsCodeGen implements CodeGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getErrorCode(error_code?: string | number) {
|
function getErrorCode(error_code?: string | number): string {
|
||||||
const errorCode = error_code || '';
|
const errorCode = error_code ?? '';
|
||||||
return /\d{3}/.test(errorCode + '') ? errorCode : 500;
|
return /\d{3}/.test(errorCode + '') ? errorCode : '500';
|
||||||
}
|
}
|
||||||
|
|
||||||
class JSCodeGen extends EjsCodeGen {
|
class JSCodeGen extends EjsCodeGen {
|
||||||
@@ -72,7 +72,7 @@ class PythonCodeGen extends EjsCodeGen {
|
|||||||
params.client_ip = clientIpKey;
|
params.client_ip = clientIpKey;
|
||||||
const paramsArg = JSON.stringify(
|
const paramsArg = JSON.stringify(
|
||||||
params,
|
params,
|
||||||
(key, value) => {
|
(_, value) => {
|
||||||
if (typeof value === 'boolean') {
|
if (typeof value === 'boolean') {
|
||||||
return randomKey + value.toString();
|
return randomKey + value.toString();
|
||||||
} else if (value === null) {
|
} else if (value === null) {
|
||||||
@@ -93,7 +93,6 @@ class PythonCodeGen extends EjsCodeGen {
|
|||||||
protected prepareTemplateArgs(params: ErrorPageParams): Record<string, any> {
|
protected prepareTemplateArgs(params: ErrorPageParams): Record<string, any> {
|
||||||
return {
|
return {
|
||||||
errorCode: getErrorCode(params.error_code),
|
errorCode: getErrorCode(params.error_code),
|
||||||
// TODO: format to JS-style object (key w/o parens)
|
|
||||||
indentedParams: this.formatPythonArgs(params).replaceAll('\n', '\n '),
|
indentedParams: this.formatPythonArgs(params).replaceAll('\n', '\n '),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
/*
|
/*
|
||||||
* JavaScript Example: Render and serve the Cloudflare error page using Express server
|
* JavaScript Example: Render and serve the Cloudflare error page using Express server
|
||||||
*
|
*
|
||||||
* Prerequisits:
|
* Prerequisites:
|
||||||
* npm install express cloudflare-error-page
|
* npm install express cloudflare-error-page
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { render as render_cf_error_page } from 'cloudflare-error-page';
|
import { render } from 'cloudflare-error-page';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = 3000;
|
const port = 3000;
|
||||||
@@ -15,7 +15,8 @@ const port = 3000;
|
|||||||
<%# TODO: use the library as middleware? _%>
|
<%# TODO: use the library as middleware? _%>
|
||||||
// Define a route for GET requests to the root URL
|
// Define a route for GET requests to the root URL
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.status(<%= errorCode %>).send(render_cf_error_page(<%- indentedParams %>));
|
// Render the error page and send to client
|
||||||
|
res.status(<%= errorCode %>).send(render(<%- indentedParams %>));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start the server and listen on the specified port
|
// Start the server and listen on the specified port
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Python Example: Render and serve the Cloudflare error page using Flask server
|
# Python Example: Render and serve the Cloudflare error page using Flask server
|
||||||
#
|
#
|
||||||
# Prerequisits:
|
# Prerequisites:
|
||||||
# pip install flask cloudflare-error-page
|
# pip install flask cloudflare-error-page
|
||||||
#
|
#
|
||||||
|
|
||||||
@@ -10,13 +10,15 @@ from flask import Flask, request
|
|||||||
from cloudflare_error_page import render as render_cf_error_page
|
from cloudflare_error_page import render as render_cf_error_page
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
port = 5000
|
||||||
|
|
||||||
<%# TODO: use the library as middleware? _%>
|
<%# TODO: use the library as middleware? _%>
|
||||||
# Define a route for GET requests to the root URL
|
# Define a route for GET requests to the root URL
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
# Render the error page
|
# Render the error page and send to client
|
||||||
return render_cf_error_page(<%- indentedParams %>), <%= errorCode %>
|
return render_cf_error_page(<%- indentedParams %>), <%= errorCode %>
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(debug=True, port=5000)
|
# Start the server and listen on the specified port
|
||||||
|
app.run(debug=True, port=port)
|
||||||
|
|||||||
@@ -53,9 +53,7 @@ let initialConfig = {
|
|||||||
// Demo presets (content brief — replace or expand as needed)
|
// Demo presets (content brief — replace or expand as needed)
|
||||||
const PRESETS = {
|
const PRESETS = {
|
||||||
default: structuredClone(initialConfig),
|
default: structuredClone(initialConfig),
|
||||||
empty: {
|
empty: {},
|
||||||
error_code: '500',
|
|
||||||
},
|
|
||||||
catastrophic: {
|
catastrophic: {
|
||||||
title: 'Catastrophic infrastructure failure',
|
title: 'Catastrophic infrastructure failure',
|
||||||
error_code: '500',
|
error_code: '500',
|
||||||
@@ -104,7 +102,7 @@ const PRESETS = {
|
|||||||
what_can_i_do: 'Visit the site before it crashes someday.',
|
what_can_i_do: 'Visit the site before it crashes someday.',
|
||||||
},
|
},
|
||||||
teapot: {
|
teapot: {
|
||||||
title: "I'm a teapot",
|
title: "I'm a Teapot",
|
||||||
error_code: '418',
|
error_code: '418',
|
||||||
more_information: {
|
more_information: {
|
||||||
text: 'rfc2324',
|
text: 'rfc2324',
|
||||||
@@ -112,20 +110,16 @@ const PRESETS = {
|
|||||||
},
|
},
|
||||||
browser_status: {
|
browser_status: {
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
location: 'You',
|
|
||||||
status_text: 'Working',
|
|
||||||
},
|
},
|
||||||
cloudflare_status: {
|
cloudflare_status: {
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
status_text: 'Working',
|
|
||||||
},
|
},
|
||||||
host_status: {
|
host_status: {
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
location: 'Teapot',
|
location: 'Teapot',
|
||||||
status_text: 'Working',
|
|
||||||
},
|
},
|
||||||
error_source: 'host',
|
error_source: 'host',
|
||||||
what_happened: "The server can not brew coffee for it is a teapot.",
|
what_happened: 'The server can not brew coffee for it is a teapot.',
|
||||||
what_can_i_do: 'Please try a different coffee machine.',
|
what_can_i_do: 'Please try a different coffee machine.',
|
||||||
},
|
},
|
||||||
consensual: {
|
consensual: {
|
||||||
@@ -306,7 +300,7 @@ function render() {
|
|||||||
doc.write(pageHtml);
|
doc.write(pageHtml);
|
||||||
doc.close();
|
doc.close();
|
||||||
|
|
||||||
updateStatusBlockStyles();
|
updateStatusBlock();
|
||||||
|
|
||||||
// store last rendered HTML for "open in new tab"
|
// store last rendered HTML for "open in new tab"
|
||||||
lastRenderedHtml = pageHtml;
|
lastRenderedHtml = pageHtml;
|
||||||
@@ -321,7 +315,7 @@ function openInNewTab() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createShareableLink() {
|
function createShareableLink() {
|
||||||
$('shareLink').value = '';
|
$('shareLink').value = 'Creating...';
|
||||||
fetch('../s/create', {
|
fetch('../s/create', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -333,16 +327,19 @@ function createShareableLink() {
|
|||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
alert('failed to create link');
|
throw new Error('failed to create link');
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.status != 'ok') {
|
if (result.status != 'ok') {
|
||||||
alert('failed to create link');
|
throw new Error('failed to create link');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
$('shareLink').value = result.url;
|
$('shareLink').value = result.url;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
alert(e);
|
||||||
|
$('shareLink').value = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function resizePreviewFrame() {
|
function resizePreviewFrame() {
|
||||||
@@ -352,7 +349,7 @@ function resizePreviewFrame() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Update status block colors based on selected status and error_source */
|
/* Update status block colors based on selected status and error_source */
|
||||||
function updateStatusBlockStyles() {
|
function updateStatusBlock() {
|
||||||
const browserOk = $('browser_status').value === 'ok';
|
const browserOk = $('browser_status').value === 'ok';
|
||||||
const cfOk = $('cloudflare_status').value === 'ok';
|
const cfOk = $('cloudflare_status').value === 'ok';
|
||||||
const hostOk = $('host_status').value === 'ok';
|
const hostOk = $('host_status').value === 'ok';
|
||||||
@@ -360,6 +357,10 @@ function updateStatusBlockStyles() {
|
|||||||
setBlockClass('block_browser', browserOk ? 'status-ok' : 'status-error');
|
setBlockClass('block_browser', browserOk ? 'status-ok' : 'status-error');
|
||||||
setBlockClass('block_cloudflare', cfOk ? 'status-ok' : 'status-error');
|
setBlockClass('block_cloudflare', cfOk ? 'status-ok' : 'status-error');
|
||||||
setBlockClass('block_host', hostOk ? 'status-ok' : 'status-error');
|
setBlockClass('block_host', hostOk ? 'status-ok' : 'status-error');
|
||||||
|
|
||||||
|
$('browser_status_text').placeholder = browserOk ? 'Working' : 'Error';
|
||||||
|
$('cloudflare_status_text').placeholder = cfOk ? 'Working' : 'Error';
|
||||||
|
$('host_status_text').placeholder = hostOk ? 'Working' : 'Error';
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBlockClass(id, cls) {
|
function setBlockClass(id, cls) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ fs.writeFileSync('error.html', errorPage);
|
|||||||
|
|
||||||
## API Reference
|
## API Reference
|
||||||
|
|
||||||
### `render(params: ErrorPageParams, allowHtml?: boolean, moreArgs?: { [name: string]: any; }): string`
|
### `render(params: ErrorPageParams, allowHtml?: boolean, moreArgs?: Record<string, any>): string`
|
||||||
|
|
||||||
Generates an HTML error page based on the provided parameters.
|
Generates an HTML error page based on the provided parameters.
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist/**/*",
|
"dist/**/*",
|
||||||
"templates/**/*",
|
|
||||||
"README.md",
|
"README.md",
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import { createFilter } from "@rollup/pluginutils";
|
|||||||
|
|
||||||
function createRawImportPlugin(include: string) {
|
function createRawImportPlugin(include: string) {
|
||||||
const rawFilter = createFilter(include);
|
const rawFilter = createFilter(include);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "raw-import",
|
name: "raw-import",
|
||||||
|
|
||||||
transform(code: string, id: string): any {
|
transform(code: string, id: string): any {
|
||||||
if (rawFilter(id)) {
|
if (rawFilter(id)) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -75,9 +75,7 @@ function genHexString(digits: number): string {
|
|||||||
export function render(
|
export function render(
|
||||||
params: ErrorPageParams,
|
params: ErrorPageParams,
|
||||||
allowHtml: boolean = true,
|
allowHtml: boolean = true,
|
||||||
moreArgs: {
|
moreArgs: Record<string, any> = {}
|
||||||
[name: string]: any;
|
|
||||||
} = {}
|
|
||||||
): string {
|
): string {
|
||||||
params = { ...params };
|
params = { ...params };
|
||||||
|
|
||||||
|
|||||||
@@ -4,23 +4,41 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "cloudflare-error-page"
|
name = "cloudflare-error-page"
|
||||||
|
dynamic = [ "version" ]
|
||||||
description = "A customizable Cloudflare error page generator"
|
description = "A customizable Cloudflare error page generator"
|
||||||
authors = [{ name = "Anthony Donlon" }]
|
|
||||||
license = { text = "MIT" }
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
license = "MIT"
|
||||||
|
authors = [{ name = "Anthony Donlon" }]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Programming Language :: Python :: 3.14",
|
||||||
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jinja2>=3.0"
|
"jinja2>=3.0"
|
||||||
]
|
]
|
||||||
dynamic = [ "version" ]
|
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://github.com/donlon/cloudflare-error-page"
|
Homepage = "https://github.com/donlon/cloudflare-error-page"
|
||||||
|
|
||||||
[tool.hatch.build]
|
[tool.hatch.build]
|
||||||
include = [
|
include = [
|
||||||
"cloudflare_error_page/*.py",
|
"cloudflare_error_page/**/*",
|
||||||
"cloudflare_error_page/templates/*",
|
]
|
||||||
|
exclude = [".gitignore"]
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.sdist]
|
||||||
|
include = [
|
||||||
|
"cloudflare_error_page/**/*",
|
||||||
|
"resources/**/*",
|
||||||
|
"scripts/**/*",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.hatch.build.targets.wheel.hooks.custom]
|
[tool.hatch.build.targets.wheel.hooks.custom]
|
||||||
|
|||||||
Reference in New Issue
Block a user