alpha release

update v0.8.0
This commit is contained in:
2026-03-17 22:49:38 -04:00
commit 63a91931da
157 changed files with 10951 additions and 0 deletions

307
src/lib/env.ts Normal file
View File

@@ -0,0 +1,307 @@
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { z } from 'zod/v4';
import {
DEFAULT_POST_LIMITER_ACTIVE,
DEFAULT_PRIVATE_DIRECTORY,
DEFAULT_PUBLIC_DIRECTORY,
DEFAULT_QUERY_PARAMETER_APP,
DEFAULT_QUERY_PARAMETER_CSS,
DEFAULT_QUERY_PARAMETER_FOLLOWING,
DEFAULT_QUERY_PARAMETER_JS,
DEFAULT_QUERY_PARAMETER_LOGOUT,
DEFAULT_QUERY_PARAMETER_METADATA,
DEFAULT_QUERY_PARAMETER_TWT,
DEFAULT_QUERY_PARAMETER_TWTS,
DEFAULT_ROUTE,
DEFAULT_TWTXT_FILENAME,
DEFAULT_UPLOAD_ACTIVE,
DEFAULT_UPLOAD_ALLOWED_MIME_TYPES,
DEFAULT_UPLOAD_ENCODING,
DEFAULT_UPLOAD_HASH_ALGORITHM,
DEFAULT_UPLOAD_KEEP_EXTENSIONS,
DEFAULT_UPLOAD_ROUTE,
} from './constants.js';
/*
The following keys are expected to exist in `process.env`, either as listed, or without the
`TWTKPR_` prefix
We only have listed default values for our keys, anything for other plugins (like formidable or
express-rate-limit) fall back to their own defaults (and thus are optional).
*/
const envSchema = z.object({
NODE_ENV: z
.enum(['development', 'production', 'test'])
.default('development'),
// required vars - MUST be passed via ENV
TWTKPR_REFRESH_SECRET: z.string().default(''),
TWTKPR_ACCESS_SECRET: z.string().default(''),
// vars with default values
TWTKPR_DEFAULT_ROUTE: z.string().default(DEFAULT_ROUTE),
TWTKPR_PRIVATE_DIRECTORY: z.string().default(DEFAULT_PRIVATE_DIRECTORY),
TWTKPR_PUBLIC_DIRECTORY: z.string().default(DEFAULT_PUBLIC_DIRECTORY),
TWTKPR_QUERY_PARAMETER_APP: z.string().default(DEFAULT_QUERY_PARAMETER_APP),
TWTKPR_QUERY_PARAMETER_CSS: z.string().default(DEFAULT_QUERY_PARAMETER_CSS),
TWTKPR_QUERY_PARAMETER_FOLLOWING: z
.string()
.default(DEFAULT_QUERY_PARAMETER_FOLLOWING),
TWTKPR_QUERY_PARAMETER_JS: z.string().default(DEFAULT_QUERY_PARAMETER_JS),
TWTKPR_QUERY_PARAMETER_LOGOUT: z
.string()
.default(DEFAULT_QUERY_PARAMETER_LOGOUT),
TWTKPR_QUERY_PARAMETER_METADATA: z
.string()
.default(DEFAULT_QUERY_PARAMETER_METADATA),
TWTKPR_QUERY_PARAMETER_TWT: z.string().default(DEFAULT_QUERY_PARAMETER_TWT),
TWTKPR_QUERY_PARAMETER_TWTS: z.string().default(DEFAULT_QUERY_PARAMETER_TWTS),
TWTKPR_TWTXT_FILENAME: z.string().default(DEFAULT_TWTXT_FILENAME),
/**
* Post limiter plugin
*/
// var with default value
TWTKPR_POST_LIMITER_ACTIVE: z.boolean().default(DEFAULT_POST_LIMITER_ACTIVE),
// optional vars
TWTKPR_POST_LIMITER_WINDOW_MS: z.optional(z.number()),
TWTKPR_POST_LIMITER_LIMIT: z.optional(z.union([z.number(), z.function()])),
TWTKPR_POST_LIMITER_MESSAGE: z.optional(z.any()),
TWTKPR_POST_LIMITER_STATUS_CODE: z.optional(z.number()),
TWTKPR_POST_LIMITER_HANDLER: z.optional(z.function()),
TWTKPR_POST_LIMITER_LEGACY_HEADERS: z.optional(z.boolean()),
TWTKPR_POST_LIMITER_STANDARD_HEADERS: z.optional(
z.union([z.boolean(), z.string()])
),
TWTKPR_POST_LIMITER_IDENTIFIER: z.optional(
z.union([z.string(), z.function()])
),
TWTKPR_POST_LIMITER_STORE: z.optional(z.any()),
TWTKPR_POST_LIMITER_PASS_ON_STORE_ERROR: z.optional(z.boolean()),
TWTKPR_POST_LIMITER_KEY_GENERATOR: z.optional(z.function()),
TWTKPR_POST_LIMITER_IPV6_SUBNET: z.optional(
z.union([z.number(), z.function(), z.boolean()])
),
TWTKPR_POST_LIMITER_REQUEST_PROPERTY_NAME: z.optional(z.string()),
TWTKPR_POST_LIMITER_SKIP: z.optional(z.function()),
TWTKPR_POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS: z.optional(z.boolean()),
TWTKPR_POST_LIMITER_SKIP_FAILED_REQUESTS: z.optional(z.boolean()),
TWTKPR_POST_LIMITER_REQUEST_WAS_SUCCESSFUL: z.optional(z.function()),
TWTKPR_POST_LIMITER_VALIDATE: z.optional(z.union([z.boolean(), z.object()])),
/**
* Upload plugin
*/
// vars with default values
TWTKPR_UPLOAD_ACTIVE: z.boolean().default(DEFAULT_UPLOAD_ACTIVE),
TWTKPR_UPLOAD_ALLOWED_MIME_TYPES: z
.union([z.string(), z.array(z.string())])
.default(DEFAULT_UPLOAD_ALLOWED_MIME_TYPES),
TWTKPR_UPLOAD_ROUTE: z.string().default(DEFAULT_UPLOAD_ROUTE),
// optional vars
TWTKPR_UPLOAD_ALLOW_EMPTY_FILES: z.optional(z.boolean()),
TWTKPR_UPLOAD_CREATE_DIRS_FROM_UPLOADS: z.optional(z.boolean()),
TWTKPR_UPLOAD_DIRECTORY: z.optional(z.string()),
TWTKPR_UPLOAD_ENCODING: z.string().default(DEFAULT_UPLOAD_ENCODING),
TWTKPR_UPLOAD_FILE_WRITE_STREAM_HANDLER: z.optional(z.function()),
TWTKPR_UPLOAD_FILENAME: z.optional(z.function()),
TWTKPR_UPLOAD_FILTER: z.optional(z.function()),
TWTKPR_UPLOAD_HASH_ALGORITHM: z
.union([z.boolean(), z.string()])
.default(DEFAULT_UPLOAD_HASH_ALGORITHM),
TWTKPR_UPLOAD_KEEP_EXTENSIONS: z
.boolean()
.default(DEFAULT_UPLOAD_KEEP_EXTENSIONS),
TWTKPR_UPLOAD_MAX_FIELDS: z.optional(z.number()),
TWTKPR_UPLOAD_MAX_FIELDS_SIZE: z.optional(z.number()),
TWTKPR_UPLOAD_MAX_FILE_SIZE: z.optional(z.number()),
TWTKPR_UPLOAD_MAX_FILES: z.optional(z.number()),
TWTKPR_UPLOAD_MAX_TOTAL_FILE_SIZE: z.optional(z.number()),
TWTKPR_UPLOAD_MIN_FILE_SIZE: z.optional(z.number()),
});
const parseEnv = () => {
try {
/**
* there's probably an easier way to do this, spreading the keys from above and accepting either
* the app (bare) or library (`TWTKPR_`-prefixed) version of said key.
* But this should work for now.
*/
const parsedEnv = envSchema.parse({
NODE_ENV: process.env.TWTKPR_NODE_ENV || process.env.NODE_ENV,
TWTKPR_ACCESS_SECRET:
process.env.TWTKPR_ACCESS_SECRET || process.env.ACCESS_SECRET,
TWTKPR_DEFAULT_ROUTE:
process.env.TWTKPR_DEFAULT_ROUTE || process.env.DEFAULT_ROUTE,
TWTKPR_PRIVATE_DIRECTORY:
process.env.TWTKPR_PRIVATE_DIRECTORY || process.env.PRIVATE_DIRECTORY,
TWTKPR_PUBLIC_DIRECTORY:
process.env.TWTKPR_PUBLIC_DIRECTORY || process.env.PUBLIC_DIRECTORY,
TWTKPR_REFRESH_SECRET:
process.env.TWTKPR_REFRESH_SECRET || process.env.REFRESH_SECRET,
TWTKPR_TWTXT_FILENAME:
process.env.TWTKPR_TWTXT_FILENAME || process.env.TWTXT_FILENAME,
TWTKPR_POST_LIMITER_ACTIVE:
process.env.TWTKPR_POST_LIMITER_ACTIVE ||
process.env.POST_LIMITER_ACTIVE,
TWTKPR_POST_LIMITER_WINDOW_MS:
process.env.TWTKPR_POST_LIMITER_WINDOW_MS ||
process.env.POST_LIMITER_WINDOW_MS,
TWTKPR_POST_LIMITER_LIMIT:
process.env.TWTKPR_POST_LIMITER_LIMIT || process.env.POST_LIMITER_LIMIT,
TWTKPR_POST_LIMITER_MESSAGE:
process.env.TWTKPR_POST_LIMITER_MESSAGE ||
process.env.POST_LIMITER_MESSAGE,
TWTKPR_POST_LIMITER_STATUS_CODE:
process.env.TWTKPR_POST_LIMITER_STATUS_CODE ||
process.env.POST_LIMITER_STATUS_CODE,
TWTKPR_POST_LIMITER_HANDLER:
process.env.TWTKPR_POST_LIMITER_HANDLER ||
process.env.POST_LIMITER_HANDLER,
TWTKPR_POST_LIMITER_LEGACY_HEADERS:
process.env.TWTKPR_POST_LIMITER_LEGACY_HEADERS ||
process.env.POST_LIMITER_LEGACY_HEADERS,
TWTKPR_POST_LIMITER_STANDARD_HEADERS:
process.env.TWTKPR_POST_LIMITER_STANDARD_HEADERS ||
process.env.POST_LIMITER_STANDARD_HEADERS,
TWTKPR_POST_LIMITER_IDENTIFIER:
process.env.TWTKPR_POST_LIMITER_IDENTIFIER ||
process.env.POST_LIMITER_IDENTIFIER,
TWTKPR_POST_LIMITER_STORE:
process.env.TWTKPR_POST_LIMITER_STORE || process.env.POST_LIMITER_STORE,
TWTKPR_POST_LIMITER_PASS_ON_STORE_ERROR:
process.env.TWTKPR_POST_LIMITER_PASS_ON_STORE_ERROR ||
process.env.POST_LIMITER_PASS_ON_STORE_ERROR,
TWTKPR_POST_LIMITER_KEY_GENERATOR:
process.env.TWTKPR_POST_LIMITER_KEY_GENERATOR ||
process.env.POST_LIMITER_KEY_GENERATOR,
TWTKPR_POST_LIMITER_IPV6_SUBNET:
process.env.TWTKPR_POST_LIMITER_IPV6_SUBNET ||
process.env.POST_LIMITER_IPV6_SUBNET,
TWTKPR_POST_LIMITER_REQUEST_PROPERTY_NAME:
process.env.TWTKPR_POST_LIMITER_REQUEST_PROPERTY_NAME ||
process.env.POST_LIMITER_REQUEST_PROPERTY_NAME,
TWTKPR_POST_LIMITER_SKIP:
process.env.TWTKPR_POST_LIMITER_SKIP || process.env.POST_LIMITER_SKIP,
TWTKPR_POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS:
process.env.TWTKPR_POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS ||
process.env.POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS,
TWTKPR_POST_LIMITER_SKIP_FAILED_REQUESTS:
process.env.TWTKPR_POST_LIMITER_SKIP_FAILED_REQUESTS ||
process.env.POST_LIMITER_SKIP_FAILED_REQUESTS,
TWTKPR_POST_LIMITER_REQUEST_WAS_SUCCESSFUL:
process.env.TWTKPR_POST_LIMITER_REQUEST_WAS_SUCCESSFUL ||
process.env.POST_LIMITER_REQUEST_WAS_SUCCESSFUL,
TWTKPR_POST_LIMITER_VALIDATE:
process.env.TWTKPR_POST_LIMITER_VALIDATE ||
process.env.POST_LIMITER_VALIDATE,
TWTKPR_QUERY_PARAMETER_APP:
process.env.TWTKPR_QUERY_PARAMETER_APP ||
process.env.QUERY_PARAMETER_APP,
TWTKPR_QUERY_PARAMETER_CSS:
process.env.TWTKPR_QUERY_PARAMETER_CSS ||
process.env.QUERY_PARAMETER_CSS,
TWTKPR_QUERY_PARAMETER_FOLLOWING:
process.env.TWTKPR_QUERY_PARAMETER_FOLLOWING ||
process.env.QUERY_PARAMETER_FOLLOWING,
TWTKPR_QUERY_PARAMETER_JS:
process.env.TWTKPR_QUERY_PARAMETER_JS || process.env.QUERY_PARAMETER_JS,
TWTKPR_QUERY_PARAMETER_LOGOUT:
process.env.TWTKPR_QUERY_PARAMETER_LOGOUT ||
process.env.QUERY_PARAMETER_LOGOUT,
TWTKPR_QUERY_PARAMETER_METADATA:
process.env.TWTKPR_QUERY_PARAMETER_METADATA ||
process.env.QUERY_PARAMETER_METADATA,
TWTKPR_QUERY_PARAMETER_TWT:
process.env.TWTKPR_QUERY_PARAMETER_TWT ||
process.env.QUERY_PARAMETER_TWT,
TWTKPR_QUERY_PARAMETER_TWTS:
process.env.TWTKPR_QUERY_PARAMETER_TWTS ||
process.env.QUERY_PARAMETER_TWTS,
TWTKPR_UPLOAD_ACTIVE:
process.env.TWTKPR_UPLOAD_ACTIVE || process.env.UPLOAD_ACTIVE,
TWTKPR_UPLOAD_ALLOW_EMPTY_FILES:
process.env.TWTKPR_UPLOAD_ALLOW_EMPTY_FILES ||
process.env.UPLOAD_ALLOW_EMPTY_FILES,
TWTKPR_UPLOAD_ALLOWED_MIME_TYPES:
process.env.TWTKPR_UPLOAD_ALLOWED_MIME_TYPES ||
process.env.UPLOAD_ALLOWED_MIME_TYPES,
TWTKPR_UPLOAD_CREATE_DIRS_FROM_UPLOADS:
process.env.TWTKPR_UPLOAD_CREATE_DIRS_FROM_UPLOADS ||
process.env.UPLOAD_CREATE_DIRS_FROM_UPLOADS,
TWTKPR_UPLOAD_DIRECTORY:
process.env.TWTKPR_UPLOAD_DIRECTORY ||
process.env.UPLOAD_DIRECTORY ||
process.env.UPLOAD_DIR,
TWTKPR_UPLOAD_ENCODING:
process.env.TWTKPR_UPLOAD_ENCODING || process.env.UPLOAD_ENCODING,
TWTKPR_UPLOAD_FILE_WRITE_STREAM_HANDLER:
process.env.TWTKPR_UPLOAD_FILE_WRITE_STREAM_HANDLER ||
process.env.UPLOAD_FILE_WRITE_STREAM_HANDLER,
TWTKPR_UPLOAD_FILENAME:
process.env.TWTKPR_UPLOAD_FILENAME || process.env.UPLOAD_FILENAME,
TWTKPR_UPLOAD_FILTER:
process.env.TWTKPR_UPLOAD_FILTER || process.env.UPLOAD_FILTER,
TWTKPR_UPLOAD_HASH_ALGORITHM:
process.env.TWTKPR_UPLOAD_HASH_ALGORITHM ||
process.env.UPLOAD_HASH_ALGORITHM,
TWTKPR_UPLOAD_KEEP_EXTENSIONS:
process.env.TWTKPR_UPLOAD_KEEP_EXTENSIONS ||
process.env.UPLOAD_KEEP_EXTENSIONS,
TWTKPR_UPLOAD_MAX_FIELDS:
process.env.TWTKPR_UPLOAD_MAX_FIELDS || process.env.UPLOAD_MAX_FIELDS,
TWTKPR_UPLOAD_MAX_FIELDS_SIZE:
process.env.TWTKPR_UPLOAD_MAX_FIELDS_SIZE ||
process.env.UPLOAD_MAX_FIELDS_SIZE,
TWTKPR_UPLOAD_MAX_FILE_SIZE:
process.env.TWTKPR_UPLOAD_MAX_FILE_SIZE ||
process.env.UPLOAD_MAX_FILE_SIZE,
TWTKPR_UPLOAD_MAX_FILES:
process.env.TWTKPR_UPLOAD_MAX_FILES || process.env.UPLOAD_MAX_FILES,
TWTKPR_UPLOAD_MAX_TOTAL_FILE_SIZE:
process.env.TWTKPR_UPLOAD_MAX_TOTAL_FILE_SIZE ||
process.env.UPLOAD_MAX_TOTAL_FILE_SIZE,
TWTKPR_UPLOAD_MIN_FILE_SIZE:
process.env.TWTKPR_UPLOAD_MIN_FILE_SIZE ||
process.env.UPLOAD_MIN_FILE_SIZE,
TWTKPR_UPLOAD_ROUTE:
process.env.TWTKPR_UPLOAD_ROUTE || process.env.UPLOAD_ROUTE,
});
if (!parsedEnv.TWTKPR_ACCESS_SECRET)
throw new Error(
'Either ACCESS_SECRET or TWTKPR_ACCESS_SECRET must be provided'
);
if (!parsedEnv.TWTKPR_REFRESH_SECRET)
throw new Error(
'Either REFRESH_SECRET or TWTKPR_REFRESH_SECRET must be provided'
);
return parsedEnv;
} catch (error) {
if (error instanceof z.ZodError) {
console.error(
'Missing environment variables:',
error.issues.flatMap((issue) => `${issue.path} or TWTKPR_${issue.path}`)
);
} else {
console.error(error);
}
process.exit(1);
}
};
export const env = parseEnv();
export const __dirname = resolve(dirname(fileURLToPath(import.meta.url)), '..');