commit d55459976859d364871374c71322660e066587e4 Author: creations <creations@creations.works> Date: Sat May 24 09:42:42 2025 -0400 first commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..980ef21 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = tab +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.forgejo/workflows/biomejs.yml b/.forgejo/workflows/biomejs.yml new file mode 100644 index 0000000..15c990c --- /dev/null +++ b/.forgejo/workflows/biomejs.yml @@ -0,0 +1,24 @@ +name: Code quality checks + +on: + push: + pull_request: + +jobs: + biome: + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Bun + run: | + curl -fsSL https://bun.sh/install | bash + export BUN_INSTALL="$HOME/.bun" + echo "$BUN_INSTALL/bin" >> $GITHUB_PATH + + - name: Install Dependencies + run: bun install + + - name: Run Biome with verbose output + run: bunx biome ci . --verbose diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d93a942 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2025, creations.works + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..66455d1 --- /dev/null +++ b/biome.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": true, + "ignore": ["dist", "types"] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineEnding": "lf" + }, + "organizeImports": { + "enabled": true + }, + "css": { + "formatter": { + "indentStyle": "tab", + "lineEnding": "lf" + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedImports": "error", + "useJsxKeyInIterable": "off", + "noUnusedVariables": "error" + }, + "style": { + "useConst": "error", + "noVar": "error" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "indentStyle": "tab", + "lineEnding": "lf", + "jsxQuoteStyle": "double", + "semicolons": "always" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ec7703b --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "@atums/echo", + "version": "1.0.0", + "description": "", + "private": false, + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "lint": "bunx biome check", + "lint:fix": "bunx biome check --fix", + "cleanup": "rm -rf logs node_modules bun.lock" + }, + "license": "BSD-3-Clause", + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/bun": "^1.2.13" + }, + "files": ["dist", "README.md", "LICENSE"], + "repository": { + "type": "git", + "url": "https://git.creations.works/creations/logger.git" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..1ba12c1 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,61 @@ +import { + constants, + type Stats, + accessSync, + existsSync, + mkdirSync, + statSync, +} from "node:fs"; +import { resolve } from "node:path"; +import { defaultConfig, loadEnvConfig, loadLoggerConfig } from "@lib/config"; + +export class Echo { + private readonly directory: string; + private readonly config: Required<LoggerConfig>; + + constructor(configOrPath?: string | LoggerConfig) { + const fileConfig: LoggerConfig = + typeof configOrPath === "string" + ? loadLoggerConfig(configOrPath) + : loadLoggerConfig(); + + const overrideConfig: LoggerConfig = + typeof configOrPath === "object" ? configOrPath : {}; + + const envConfig: LoggerConfig = loadEnvConfig(); + + this.config = { + ...defaultConfig, + ...fileConfig, + ...envConfig, + ...overrideConfig, + }; + + this.directory = resolve(this.config.directory); + + if (!this.config.disableFile) { + Echo.validateDirectory(this.directory); + } + } + + private static validateDirectory(dir: string): void { + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + + const stat: Stats = statSync(dir); + if (!stat.isDirectory()) { + throw new Error(`[@atums/echo] ${dir} is not a directory`); + } + + accessSync(dir, constants.W_OK); + } + + public getDirectory(): string { + return this.directory; + } + + public getConfig(): Required<LoggerConfig> { + return this.config; + } +} diff --git a/src/lib/char.ts b/src/lib/char.ts new file mode 100644 index 0000000..f6062ba --- /dev/null +++ b/src/lib/char.ts @@ -0,0 +1,8 @@ +function timestampToReadable(timestamp?: number): string { + const date: Date = + timestamp && !Number.isNaN(timestamp) ? new Date(timestamp) : new Date(); + if (Number.isNaN(date.getTime())) return "Invalid Date"; + return date.toISOString().replace("T", " ").replace("Z", ""); +} + +export { timestampToReadable }; diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..ebe28c3 --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,59 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +const defaultConfig: Required<LoggerConfig> = { + directory: "logs", + level: "info", + disableFile: false, + + rotate: true, + maxSizeMB: 5, + maxFiles: 3, + + console: true, + consoleColor: true, + + dateFormat: "YYYY-MM-DD HH:mm:ss", + timezone: "UTC", + + silent: false, + + pattern: "{timestamp} [{level-name}] ({file-name}:{line}){message}", +}; + +function loadLoggerConfig(configPath = "logger.json"): LoggerConfig { + try { + const fullPath: string = resolve(process.cwd(), configPath); + const raw: string = readFileSync(fullPath, "utf-8"); + return JSON.parse(raw); + } catch { + return {}; + } +} + +function loadEnvConfig(): LoggerConfig { + const config: LoggerConfig = {}; + + if (process.env.LOG_LEVEL) config.level = process.env.LOG_LEVEL as LogLevel; + if (process.env.LOG_DIRECTORY) config.directory = process.env.LOG_DIRECTORY; + if (process.env.LOG_DISABLE_FILE) + config.disableFile = process.env.LOG_DISABLE_FILE === "true"; + if (process.env.LOG_ROTATE) config.rotate = process.env.LOG_ROTATE === "true"; + if (process.env.LOG_MAX_SIZE_MB) + config.maxSizeMB = Number.parseInt(process.env.LOG_MAX_SIZE_MB, 10); + if (process.env.LOG_MAX_FILES) + config.maxFiles = Number.parseInt(process.env.LOG_MAX_FILES, 10); + if (process.env.LOG_CONSOLE) + config.console = process.env.LOG_CONSOLE === "true"; + if (process.env.LOG_CONSOLE_COLOR) + config.consoleColor = process.env.LOG_CONSOLE_COLOR === "true"; + if (process.env.LOG_DATE_FORMAT) + config.dateFormat = process.env.LOG_DATE_FORMAT; + if (process.env.LOG_TIMEZONE) config.timezone = process.env.LOG_TIMEZONE; + if (process.env.LOG_SILENT) config.silent = process.env.LOG_SILENT === "true"; + if (process.env.LOG_PATTERN) config.pattern = process.env.LOG_PATTERN; + + return config; +} + +export { defaultConfig, loadLoggerConfig, loadEnvConfig }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0ae7837 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@/*": ["src/*"], + "@types/*": ["types/*"], + "@lib/*": ["src/lib/*"] + }, + "typeRoots": ["./types", "./node_modules/@types"], + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "allowJs": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": ["src", "types", "config"] +} diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000..dcd32e4 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,32 @@ +const LogLevelValue = { + trace: 10, + debug: 20, + info: 30, + warn: 40, + error: 50, + fatal: 60, + silent: 70, +} as const; + +type LogLevelValue = typeof LogLevelValue[keyof typeof LogLevelValue]; +type LogLevel = keyof typeof LogLevelValue; + +type LoggerConfig = { + directory?: string; + level?: LogLevel; + disableFile?: boolean; + + rotate?: boolean; + maxSizeMB?: number; + maxFiles?: number; + + console?: boolean; + consoleColor?: boolean; + + dateFormat?: string; + timezone?: string; + + silent?: boolean; + + pattern?: string; +};