commit 8f843dc27db0dac4081a52504d9f0d6f225fea4f Author: creations Date: Thu Aug 8 08:20:58 2024 -0400 initial comit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c54702 --- /dev/null +++ b/.gitignore @@ -0,0 +1,176 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store +/.vscode \ No newline at end of file diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..94d3a0d --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Creation's // [creations@creations.works] + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c679c3 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# TS Fastify Example + +This is just an example/template to get you started with Fastify and TypeScript. It starts off using EJS, but you can use something else if you'd like. + +## Getting Started + +To start the project, you can run either `start.bat` or `start.sh`. + +## Cleanup + +You can clean up your project with the following commands: + +- **Windows:** `start.bat clean` +- **Unix/Linux:** `start.sh clean` + +Have fun - **Creations** // **creations@creations.works** + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/config/environment.ts b/config/environment.ts new file mode 100644 index 0000000..5643cb5 --- /dev/null +++ b/config/environment.ts @@ -0,0 +1,43 @@ +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +export interface IEnvironment { + development: boolean; + + fastify: { + host: string; + port: number; + }; + + paths: { + src: string; + www: { + root: string; + views: string; + public: string; + } + }; +} + +const __dirname : string = join(dirname(fileURLToPath(import.meta.url)), ".."); + +const environment : IEnvironment = { + development: process.argv.includes("--development"), + + fastify: { + host: "0.0.0.0", + port: 7788 + }, + + paths: { + src: __dirname, + www: { + root: join(__dirname, "src", "www"), + views: join(__dirname, "src", "www", "views"), + public: join(__dirname, "src", "www", "public") + } + } +}; + +export default environment; +export { environment }; diff --git a/package.json b/package.json new file mode 100644 index 0000000..1b335a4 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "ts_fastify_example", + "module": "src/index.ts", + "type": "module", + "scripts": { + "dev": "bun run --watch src/index.ts --development" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/ejs": "^3.1.5" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@fastify/static": "^7.0.4", + "@fastify/view": "^9.1.0", + "ejs": "^3.1.10", + "fastify": "^4.28.1" + } +} diff --git a/src/fastify/manager.ts b/src/fastify/manager.ts new file mode 100644 index 0000000..6790b8e --- /dev/null +++ b/src/fastify/manager.ts @@ -0,0 +1,135 @@ +import Fastify from "fastify"; +import ejs from "ejs"; + +import { IncomingMessage, Server, ServerResponse } from "http"; +import { readdir, stat } from "fs/promises"; +import { join } from "path"; + +import { environment } from "../../config/environment"; + +// types +import type { FastifyInstance, FastifyReply, FastifyRequest, RouteShorthandMethod } from "fastify"; +import type { Stats } from "fs"; +import type { AddressInfo } from "net"; + +// official plugins +import { fastifyView } from "@fastify/view"; +import { fastifyStatic } from "@fastify/static"; + +//custom plugins +import faviconPlugin from "./plugins/favicon"; + +class FastifyManager { + private readonly port : number = environment.fastify.port; + private readonly server : FastifyInstance; + + constructor() { + this.server = Fastify({ + logger: false, + trustProxy: !environment.development, + ignoreTrailingSlash: true + }); + } + private async registerPlugins() : Promise { + console.log(environment.paths.www.views); + + // official plugins + this.server.register(fastifyView, { + engine: { + ejs + }, + root: environment.paths.www.views, + includeViewExtension: true + }); + + this.server.register(fastifyStatic, { + root: environment.paths.www.public, + prefix: "/public/", + prefixAvoidTrailingSlash: true + }); + + // custom plugins + this.server.register(faviconPlugin, { path: join(environment.paths.www.public, "assets", "favicon.ico") }); + } + + // dynamic route loading + private async loadRoutes(): Promise { + // you can specify a route path, prefix, and whether to load routes recursively e.g. ["routes", "/", true] will load all routes in the /routes directory with the prefix / and recursively load all routes in subdirectories + + const routePaths: [string, string, boolean][] = [ + ["routes", '/', false] + ]; + + for (const [routePath, prefix, recursive] of routePaths) { + const modifiedRoutePath = join(environment.paths.www.root, routePath); + + let files: string[]; + try { + files = await this.readDirRecursive(modifiedRoutePath, recursive); + } catch (err) { + console.error("Failed to read route directory", modifiedRoutePath, "error:", err); + return; + } + + for (const file of files) { + try { + const route = await import(file); + if (route.default) { + this.server.register(route.default, { prefix }); + } + } catch (err) { + console.error("Failed to load route", file, "error:", err); + } + } + } + } + + private async readDirRecursive(dir: string, recursive: boolean): Promise { + let results: string[] = []; + const list : string[] = await readdir(dir); + + for (const file of list) { + const filePath : string = join(dir, file); + const statObj : Stats = await stat(filePath); + if (statObj && statObj.isDirectory()) { + if (recursive) { + results = results.concat(await this.readDirRecursive(filePath, recursive)); + } + } else { + results.push(filePath); + } + } + + return results; + } + + public async start() : Promise { + try { + await this.registerPlugins(); + await this.loadRoutes(); + + await this.server.listen({ + port: environment.fastify.port, + host: environment.fastify.host + }); + + const [_address, _port, scheme]: [string, number, string] = (() : [string, number, string] => { + const address: string | AddressInfo | null = this.server.server.address(); + const resolvedAddress: [string, number] = (address && typeof address === 'object') ? [( address.address.startsWith("::") || address.address === "0.0.0.0") ? "localhost" : address.address, address.port] : ["localhost", this.port]; + const resolvedScheme: string = resolvedAddress[0] === "localhost" ? "http" : "https"; + + return [...resolvedAddress, resolvedScheme]; + })(); + + + console.info("Started Listening on", `${scheme}://${_address}:${_port}`); + console.info("Registered routes: ", "\n", this.server.printRoutes()); + } catch (error) { + console.error("Failed to start Fastify server", error); + } + } +} + +const fastifyManager = new FastifyManager(); +export default fastifyManager; +export { fastifyManager }; \ No newline at end of file diff --git a/src/fastify/plugins/favicon.ts b/src/fastify/plugins/favicon.ts new file mode 100644 index 0000000..f2b6fc8 --- /dev/null +++ b/src/fastify/plugins/favicon.ts @@ -0,0 +1,35 @@ +import { readFile } from 'fs/promises'; + +import type { FastifyInstance, FastifyPluginAsync } from 'fastify'; +import type { FastifyRequest } from "fastify/types/request"; +import type { FastifyReply } from "fastify/types/reply"; + +interface FaviconOptions { + path: string; +} + +const faviconPlugin: FastifyPluginAsync = async (fastify: FastifyInstance, options: FaviconOptions): Promise => { + let faviconData: Buffer | null = null; + + try { + faviconData = await readFile(options.path); + } catch (err) { + console.error("Error reading favicon:", err); + } + + fastify.get('/favicon.ico', async (_request: FastifyRequest, reply: FastifyReply): Promise => { + if (faviconData) { + reply.header('Content-Type', 'image/x-icon') + .header('Cache-Control', 'public, max-age=86400') // 1 day + .send(faviconData); + } else { + reply.status(404).send({ + code: 404, + error: 'FILE_NOT_FOUND', + message: 'Favicon not found' + }); + } + }); +}; + +export default faviconPlugin as FastifyPluginAsync; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..da7028a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,15 @@ +import { environment } from "../config/environment"; +import { fastifyManager } from "./fastify/manager"; + +class Index { + public static async main() : Promise { + const main = new Index(); + await main.start(); + }; + + public async start() : Promise { + await fastifyManager.start(); + } +} + +const index : Promise = Index.main(); diff --git a/src/www/public/assets/favicon.ico b/src/www/public/assets/favicon.ico new file mode 100644 index 0000000..7ab20f7 Binary files /dev/null and b/src/www/public/assets/favicon.ico differ diff --git a/src/www/routes/index.ts b/src/www/routes/index.ts new file mode 100644 index 0000000..cfe569e --- /dev/null +++ b/src/www/routes/index.ts @@ -0,0 +1,17 @@ +import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; + +const route : (fastify: FastifyInstance) => Promise = async (fastify: FastifyInstance) : Promise => { + const handleRequest: (request : FastifyRequest, reply : FastifyReply) => Promise = async (request : FastifyRequest, reply : FastifyReply) : Promise => { + + return reply.view("index.ejs", { + h1: "Hello, World!", + p: "This is a placeholder page.", + discord: "https://discord.gg/DxFhhbr2pm", + }); + + } + + fastify.get("/", handleRequest); +} + +export default route as (fastify: FastifyInstance) => Promise; diff --git a/src/www/views/index.ejs b/src/www/views/index.ejs new file mode 100644 index 0000000..c8636cb --- /dev/null +++ b/src/www/views/index.ejs @@ -0,0 +1,14 @@ + + + + + + Document + + +

<%= h1 %>

+

<%= p %>

+ +

join this cool discord: click here

+ + \ No newline at end of file diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..9a46df8 --- /dev/null +++ b/start.bat @@ -0,0 +1,150 @@ +@echo off +setlocal enabledelayedexpansion + +for %%I in ("%~dp0.") do set "SCRIPT_DIR=%%~fI" +cd /d "!SCRIPT_DIR!" + +if not exist package.json ( + echo package.json not found in !SCRIPT_DIR! + exit /b 1 +) + +set "PKG_MANAGER=bun" +set "MODE=dev" + +set "next_is_value=0" +set "last_key=" + +for %%a in (%*) do ( + set "arg=%%a" + + if "!next_is_value!"=="1" ( + if "!last_key!"=="manager" ( + set "PKG_MANAGER=%%a" + ) + if "!last_key!"=="mode" ( + set "MODE=%%a" + ) + set "next_is_value=0" + ) + + if "!arg!"=="clean" ( + goto cleanup + ) + + if "!arg!"=="cleanup" ( + goto cleanup + ) + + if "!arg!"=="manager" ( + set "last_key=manager" + set "next_is_value=1" + ) + + if "!arg!"=="mode" ( + set "last_key=mode" + set "next_is_value=1" + ) +) + +set "ALLOWED_MANAGERS=bun npm yarn pnpm" +set "ALLOWED_MODES=dev prod development production" + +set "manager_allowed=0" +for %%a in (!ALLOWED_MANAGERS!) do ( + if "!PKG_MANAGER!"=="%%a" ( + set "manager_allowed=1" + ) +) + +set "mode_allowed=0" +for %%a in (!ALLOWED_MODES!) do ( + if "!MODE!"=="%%a" ( + set "mode_allowed=1" + ) +) + +if "!manager_allowed!"=="0" ( + echo !PKG_MANAGER! is not a valid package manager, please use one of the following: !ALLOWED_MANAGERS! + exit /b 1 +) + +if "!mode_allowed!"=="0" ( + echo !MODE! is not a valid mode, please use one of the following: !ALLOWED_MODES! + exit /b 1 +) + +echo Using package manager: !PKG_MANAGER! +echo Mode: !MODE! + +set "NPM_MARKER=node_modules/.npm_used" +set "YARN_MARKER=node_modules/.yarn_used" +set "PNPM_MARKER=node_modules/.pnpm_used" +set "BUN_MARKER=node_modules/.bun_used" + +if "!PKG_MANAGER!"=="npm" ( + set "INSTALL_CMD=npm install" + set "RUN_CMD=npm run !MODE!" + set "CURRENT_MARKER=!NPM_MARKER!" +) else if "!PKG_MANAGER!"=="yarn" ( + set "INSTALL_CMD=yarn" + set "RUN_CMD=yarn !MODE!" + set "CURRENT_MARKER=!YARN_MARKER!" +) else if "!PKG_MANAGER!"=="pnpm" ( + set "INSTALL_CMD=pnpm install" + set "RUN_CMD=pnpm !MODE!" + set "CURRENT_MARKER=!PNPM_MARKER!" +) else if "!PKG_MANAGER!"=="bun" ( + set "INSTALL_CMD=bun install" + set "RUN_CMD=bun !MODE!" + set "CURRENT_MARKER=!BUN_MARKER!" +) + +if exist "!NPM_MARKER!" if not "!CURRENT_MARKER!"=="!NPM_MARKER!" goto cleanup +if exist "!YARN_MARKER!" if not "!CURRENT_MARKER!"=="!YARN_MARKER!" goto cleanup +if exist "!PNPM_MARKER!" if not "!CURRENT_MARKER!"=="!PNPM_MARKER!" goto cleanup +if exist "!BUN_MARKER!" if not "!CURRENT_MARKER!"=="!BUN_MARKER!" goto cleanup + +goto run + +:cleanup +set "files_to_delete=package-lock.json yarn.lock pnpm-lock.yaml bun.lockb" +set "folders_to_delete=node_modules dist logs" +set "markers_to_delete=node_modules/.npm_used node_modules/.yarn_used node_modules/.pnpm_used node_modules/.bun_used" + +echo Cleaning up files and folders... + +for %%a in (!files_to_delete!) do ( + if exist "%%a" ( + echo Found %%a in !SCRIPT_DIR! - deleting... + del "%%a" + ) +) + +for %%a in (!folders_to_delete!) do ( + if exist "%%a" ( + echo Found %%a in !SCRIPT_DIR! - deleting... + rmdir /s /q "%%a" + ) +) + +for %%a in (!markers_to_delete!) do ( + if exist "%%a" del "%%a" +) + +if defined RUN_CMD ( + goto run +) + +exit /b 0 + +:run +if defined RUN_CMD ( + echo Running !RUN_CMD!... + !INSTALL_CMD! + !RUN_CMD! +) else ( + echo No run command set, exiting... +) + +exit /b 0 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}