Skip to content

Commit

Permalink
refactor: switch MimeTypes to MimeType and use IANA media types (#…
Browse files Browse the repository at this point in the history
…603)

BREAKING CHANGE: `MimeTypes` has been removed in favor of the `MimeType` string union type consists of all IANA media types
  • Loading branch information
kyranet authored Aug 18, 2024
1 parent f5fa1f7 commit 834b446
Show file tree
Hide file tree
Showing 14 changed files with 2,218 additions and 47 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/auto-updater.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Automatic Data Update

on:
workflow_dispatch:
schedule:
- cron: '0 2 * * *'

jobs:
DataUpdater:
name: Automatic Data Update
runs-on: ubuntu-latest
if: github.repository_owner == 'sapphiredev'
steps:
- name: Checkout Project
uses: actions/checkout@v4
- name: Install dependencies
uses: sapphiredev/.github/actions/install-yarn-dependencies@main
with:
node-version: 20
- name: Run plugin-api MIME type sync
run: yarn workspace @sapphire/plugin-api sync-mime-types
- name: Run prettier on the code
run: yarn format
- name: Commit any changes and create a pull request
env:
GITHUB_USER: github-actions[bot]
GITHUB_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git add .;
if git diff-index --quiet HEAD --; then
echo "No changes to commit, exiting with code 0"
exit 0;
else
git remote set-url origin "https://${GITHUB_TOKEN}:[email protected]/${GITHUB_REPOSITORY}.git";
git config --local user.email "${GITHUB_EMAIL}";
git config --local user.name "${GITHUB_USER}";
git checkout -b update-iana-mime-type/$(date +%F-%H-%M);
git commit -sam "feat: update mime types";
git push --set-upstream origin $(git rev-parse --abbrev-ref HEAD)
gh pr create -t "feat: update mime types" -b "*bleep bloop* I updated the IANA mime type list" -B main;
fi
1 change: 1 addition & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"build:types:esm": "rollup-type-bundler -d dist/esm -t .mts",
"build:types:cleanup": "tsx ../../scripts/clean-register-imports.mts",
"build:rename-cjs-register": "tsx ../../scripts/rename-cjs-register.mts",
"sync-mime-types": "tsx scripts/sync-mime-types.mts",
"typecheck": "tsc -b src",
"docs": "typedoc-json-parser",
"prepack": "yarn build",
Expand Down
20 changes: 20 additions & 0 deletions packages/api/scripts/sync-mime-types.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { writeFile } from 'node:fs/promises';

const output = new URL('../src/lib/utils/MimeType.ts', import.meta.url);

const response = await fetch('https://www.iana.org/assignments/media-types/media-types.xml');
const text = await response.text();

const fileTypeTemplateRegex = /<file type="template">(\w+\/.+?)<\/file>/g;
const outputLines = ['export type MimeType ='];

let result: RegExpExecArray | null;
while ((result = fileTypeTemplateRegex.exec(text))) {
outputLines.push(`\t| '${result[1]}'`);
}

outputLines.push('\t| `X-${string}/${string}`;');
outputLines.push('');

await writeFile(output, outputLines.join('\n'), 'utf8');
console.log(`Successfully written to ${output.href}: ${outputLines.length - 3} mime types total`);
2 changes: 1 addition & 1 deletion packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export * from './lib/structures/router/RouterBranch';
export * from './lib/structures/router/RouterNode';
export * from './lib/structures/router/RouterRoot';
export * from './lib/structures/RouteStore';
export * from './lib/utils/MimeTypes';
export type * from './lib/utils/MimeType';

export { loadListeners } from './listeners/_load';
export { loadMediaParsers } from './mediaParsers/_load';
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/lib/structures/MediaParser.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Piece } from '@sapphire/pieces';
import type { Awaitable } from '@sapphire/utilities';
import { createBrotliDecompress, createGunzip, createInflate, type Gunzip } from 'zlib';
import type { MimeType } from '../utils/MimeType';
import type { ApiRequest } from './api/ApiRequest';
import type { MimeTypeWithoutParameters } from './http/Server';
import type { Route } from './Route';

/**
Expand All @@ -26,7 +26,7 @@ export abstract class MediaParser<Options extends MediaParser.Options = MediaPar
* @param route The route to be checked.
*/
public accepts(route: Route): boolean {
return route.acceptedContentMimeTypes === null || route.acceptedContentMimeTypes.includes(this.name as MimeTypeWithoutParameters);
return route.acceptedContentMimeTypes === null || route.acceptedContentMimeTypes.includes(this.name as MimeType);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions packages/api/src/lib/structures/Route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Piece } from '@sapphire/pieces';
import { isNullish, type Awaitable } from '@sapphire/utilities';
import type { MimeType } from '../utils/MimeType';
import type { ApiRequest } from './api/ApiRequest';
import type { ApiResponse } from './api/ApiResponse';
import type { MethodName } from './http/HttpMethods';
import type { MimeTypeWithoutParameters } from './http/Server';
import { RouterRoot } from './router/RouterRoot';

/**
Expand Down Expand Up @@ -52,7 +52,7 @@ export abstract class Route<Options extends Route.Options = Route.Options> exten
/**
* The accepted content types.
*/
public readonly acceptedContentMimeTypes: readonly MimeTypeWithoutParameters[] | null;
public readonly acceptedContentMimeTypes: readonly MimeType[] | null;

/**
* The path this route represents.
Expand Down Expand Up @@ -124,7 +124,7 @@ export interface RouteOptions extends Piece.Options {
*
* @defaultValue this.context.server.options.acceptedContentMimeTypes ?? null
*/
acceptedContentMimeTypes?: readonly MimeTypeWithoutParameters[] | null;
acceptedContentMimeTypes?: readonly MimeType[] | null;

/**
* The methods this route accepts.
Expand Down
17 changes: 7 additions & 10 deletions packages/api/src/lib/structures/api/ApiResponse.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IncomingMessage, ServerResponse, STATUS_CODES } from 'node:http';
import { Readable } from 'node:stream';
import { ReadableStream } from 'node:stream/web';
import { MimeTypes } from '../../utils/MimeTypes';
import type { MimeType } from '../../utils/MimeType';
import { HttpCodes } from '../http/HttpCodes';
import type { CookieStore } from './CookieStore';

Expand Down Expand Up @@ -110,28 +110,25 @@ export class ApiResponse<Request extends IncomingMessage = IncomingMessage> exte
* @since 1.0.0
*/
public json(data: any): void {
this.setContentType(MimeTypes.ApplicationJson).end(JSON.stringify(data));
this.setContentType('application/json').end(JSON.stringify(data));
}

/**
* @since 1.0.0
*/
public text(data: string): void {
this.setContentType(MimeTypes.TextPlain).end(data);
this.setContentType('text/plain').end(data);
}

/**
* @since 6.1.0
*
* Sets the image content type and sends the image data in the response.
*
* @param type - The MIME type of the image (e.g., {@link MimeTypes.ImagePng}).
* @param type - The MIME type of the image (e.g., 'image/png').
* @param data - The image data as a `string`, {@link Buffer}, {@link Uint8Array}, or {@link ReadableStream}.
*/
public image(
type: MimeTypes.ImageGif | MimeTypes.ImageJpg | MimeTypes.ImagePng | MimeTypes.ImageWebp | MimeTypes.ImageXIcon,
data: string | Buffer | Uint8Array | Readable
): void {
public image(type: Extract<MimeType, `image/${string}`>, data: string | Buffer | Uint8Array | Readable): void {
if (data instanceof Readable) {
this.setContentType(type);
data.pipe(this);
Expand All @@ -144,13 +141,13 @@ export class ApiResponse<Request extends IncomingMessage = IncomingMessage> exte
* @since 5.1.0
*/
public html(code: number, data: string): void {
this.setContentType(MimeTypes.TextHtml).status(code).end(data);
this.setContentType('text/html').status(code).end(data);
}

/**
* @since 1.0.0
*/
public setContentType(contentType: MimeTypes): this {
public setContentType(contentType: MimeType): this {
this.setHeader('Content-Type', contentType);
return this;
}
Expand Down
10 changes: 6 additions & 4 deletions packages/api/src/lib/structures/http/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { container } from '@sapphire/pieces';
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
import { Server as HttpServer, createServer as httpCreateServer, type ServerOptions as HttpOptions } from 'node:http';
import type { ListenOptions } from 'node:net';
import type { MimeType } from '../../utils/MimeType';
import { MediaParserStore } from '../MediaParserStore';
import { MiddlewareStore } from '../MiddlewareStore';
import type { Route } from '../Route';
Expand Down Expand Up @@ -168,15 +169,16 @@ export type ContentTypeParameter = `; ${string}=${string}`;
/**
* RFC 1341 4: Defines the syntax for a Content-Type field without parameters, which follows the following structure:
* `type "/" subtype`.
* @since 7.0.0
*/
export type MimeTypeWithoutParameters = `${ContentTypeType}/${string}`;
export type GenericMimeType = `${ContentTypeType}/${string}`;

/**
* RFC 1341 4: Defines the syntax for a Content-Type field, which follows the following structure:
* `type "/" subtype *[";" parameter]`.
* @since 1.3.0
* @since 7.0.0
*/
export type MimeType = `${MimeTypeWithoutParameters}${'' | ContentTypeParameter}`;
export type GenericParametrizedMimeType = `${GenericMimeType}${'' | ContentTypeParameter}`;

/**
* The API options.
Expand Down Expand Up @@ -209,7 +211,7 @@ export interface ServerOptions {
* @since 1.3.0
* @default null
*/
acceptedContentMimeTypes?: MimeTypeWithoutParameters[] | null;
acceptedContentMimeTypes?: MimeType[] | null;

/**
* The HTTP server options.
Expand Down
Loading

0 comments on commit 834b446

Please sign in to comment.