create-nexu 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +603 -0
- package/package.json +41 -0
- package/templates/default/.changeset/config.json +11 -0
- package/templates/default/.eslintignore +13 -0
- package/templates/default/.eslintrc.js +66 -0
- package/templates/default/.github/actions/quality/action.yml +53 -0
- package/templates/default/.github/dependabot.yml +51 -0
- package/templates/default/.github/workflows/deploy-dev.yml +83 -0
- package/templates/default/.github/workflows/deploy-prod.yml +83 -0
- package/templates/default/.github/workflows/deploy-rec.yml +83 -0
- package/templates/default/.husky/commit-msg +1 -0
- package/templates/default/.husky/pre-commit +1 -0
- package/templates/default/.prettierignore +7 -0
- package/templates/default/.prettierrc +19 -0
- package/templates/default/.vscode/extensions.json +14 -0
- package/templates/default/.vscode/settings.json +36 -0
- package/templates/default/apps/.gitkeep +0 -0
- package/templates/default/commitlint.config.js +26 -0
- package/templates/default/docker/docker-compose.dev.yml +49 -0
- package/templates/default/docker/docker-compose.prod.yml +64 -0
- package/templates/default/docker/docker-compose.yml +5 -0
- package/templates/default/package.json +56 -0
- package/templates/default/packages/cache/package.json +26 -0
- package/templates/default/packages/cache/src/index.ts +137 -0
- package/templates/default/packages/cache/tsconfig.json +9 -0
- package/templates/default/packages/cache/tsup.config.ts +9 -0
- package/templates/default/packages/config/eslint/index.js +20 -0
- package/templates/default/packages/config/package.json +9 -0
- package/templates/default/packages/config/typescript/base.json +26 -0
- package/templates/default/packages/constants/package.json +26 -0
- package/templates/default/packages/constants/src/index.ts +121 -0
- package/templates/default/packages/constants/tsconfig.json +9 -0
- package/templates/default/packages/constants/tsup.config.ts +9 -0
- package/templates/default/packages/logger/package.json +27 -0
- package/templates/default/packages/logger/src/index.ts +197 -0
- package/templates/default/packages/logger/tsconfig.json +11 -0
- package/templates/default/packages/logger/tsup.config.ts +9 -0
- package/templates/default/packages/result/package.json +26 -0
- package/templates/default/packages/result/src/index.ts +142 -0
- package/templates/default/packages/result/tsconfig.json +9 -0
- package/templates/default/packages/result/tsup.config.ts +9 -0
- package/templates/default/packages/types/package.json +26 -0
- package/templates/default/packages/types/src/index.ts +78 -0
- package/templates/default/packages/types/tsconfig.json +9 -0
- package/templates/default/packages/types/tsup.config.ts +10 -0
- package/templates/default/packages/ui/package.json +38 -0
- package/templates/default/packages/ui/src/components/Button.tsx +65 -0
- package/templates/default/packages/ui/src/components/Card.tsx +90 -0
- package/templates/default/packages/ui/src/components/Input.tsx +51 -0
- package/templates/default/packages/ui/src/index.ts +15 -0
- package/templates/default/packages/ui/tsconfig.json +11 -0
- package/templates/default/packages/ui/tsup.config.ts +11 -0
- package/templates/default/packages/utils/package.json +30 -0
- package/templates/default/packages/utils/src/index.test.ts +130 -0
- package/templates/default/packages/utils/src/index.ts +154 -0
- package/templates/default/packages/utils/tsconfig.json +10 -0
- package/templates/default/packages/utils/tsup.config.ts +10 -0
- package/templates/default/pnpm-workspace.yaml +3 -0
- package/templates/default/scripts/deploy.sh +25 -0
- package/templates/default/scripts/generate-app.sh +166 -0
- package/templates/default/scripts/publish-cli.sh +54 -0
- package/templates/default/scripts/setup.sh +70 -0
- package/templates/default/services/.env.example +16 -0
- package/templates/default/services/docker-compose.yml +207 -0
- package/templates/default/services/grafana/provisioning/dashboards/dashboards.yml +11 -0
- package/templates/default/services/grafana/provisioning/datasources/datasources.yml +9 -0
- package/templates/default/services/postgres/init/.gitkeep +2 -0
- package/templates/default/services/prometheus/prometheus.yml +13 -0
- package/templates/default/tsconfig.json +27 -0
- package/templates/default/turbo.json +40 -0
- package/templates/default/vitest.config.ts +15 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"packageManager": "pnpm@9.0.0",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "turbo build",
|
|
7
|
+
"dev": "turbo dev",
|
|
8
|
+
"lint": "turbo lint",
|
|
9
|
+
"lint:fix": "turbo lint:fix",
|
|
10
|
+
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
11
|
+
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
12
|
+
"test": "turbo test",
|
|
13
|
+
"test:coverage": "turbo test:coverage",
|
|
14
|
+
"typecheck": "turbo typecheck",
|
|
15
|
+
"clean": "turbo clean && rm -rf node_modules",
|
|
16
|
+
"prepare": "husky install",
|
|
17
|
+
"docker:dev": "docker-compose -f docker/docker-compose.dev.yml up",
|
|
18
|
+
"docker:build": "docker-compose -f docker/docker-compose.prod.yml build",
|
|
19
|
+
"docker:prod": "docker-compose -f docker/docker-compose.prod.yml up -d",
|
|
20
|
+
"generate:app": "./scripts/generate-app.sh",
|
|
21
|
+
"generate:template": "./scripts/generate-template.sh",
|
|
22
|
+
"publish:cli": "./scripts/publish-cli.sh",
|
|
23
|
+
"changeset": "changeset",
|
|
24
|
+
"version-packages": "changeset version",
|
|
25
|
+
"release": "changeset publish"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@changesets/cli": "^2.27.0",
|
|
29
|
+
"@commitlint/cli": "^19.0.0",
|
|
30
|
+
"@commitlint/config-conventional": "^19.0.0",
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
32
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
33
|
+
"@vitest/coverage-v8": "^2.0.0",
|
|
34
|
+
"@vitest/ui": "^2.0.0",
|
|
35
|
+
"eslint": "^8.57.0",
|
|
36
|
+
"eslint-config-prettier": "^9.1.0",
|
|
37
|
+
"eslint-plugin-import": "^2.29.0",
|
|
38
|
+
"eslint-plugin-unused-imports": "^3.1.0",
|
|
39
|
+
"husky": "^9.0.0",
|
|
40
|
+
"lint-staged": "^15.0.0",
|
|
41
|
+
"prettier": "^3.2.0",
|
|
42
|
+
"prettier-plugin-tailwindcss": "^0.5.0",
|
|
43
|
+
"turbo": "^2.0.0",
|
|
44
|
+
"typescript": "^5.4.0",
|
|
45
|
+
"vitest": "^2.0.0"
|
|
46
|
+
},
|
|
47
|
+
"lint-staged": {
|
|
48
|
+
"*.{ts,tsx,js,jsx}": [
|
|
49
|
+
"eslint --fix",
|
|
50
|
+
"prettier --write"
|
|
51
|
+
],
|
|
52
|
+
"*.{json,md,yml,yaml}": [
|
|
53
|
+
"prettier --write"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@repo/cache",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"lint": "eslint src/",
|
|
19
|
+
"lint:fix": "eslint src/ --fix",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsup": "^8.0.0",
|
|
24
|
+
"typescript": "^5.4.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export interface CacheOptions {
|
|
2
|
+
ttl?: number; // Time to live in milliseconds
|
|
3
|
+
maxSize?: number; // Maximum number of items
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface CacheEntry<T> {
|
|
7
|
+
value: T;
|
|
8
|
+
expiresAt?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class Cache<T = unknown> {
|
|
12
|
+
private store = new Map<string, CacheEntry<T>>();
|
|
13
|
+
private ttl?: number;
|
|
14
|
+
private maxSize?: number;
|
|
15
|
+
|
|
16
|
+
constructor(options: CacheOptions = {}) {
|
|
17
|
+
this.ttl = options.ttl;
|
|
18
|
+
this.maxSize = options.maxSize;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private isExpired(entry: CacheEntry<T>): boolean {
|
|
22
|
+
if (!entry.expiresAt) return false;
|
|
23
|
+
return Date.now() > entry.expiresAt;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private evictIfNeeded(): void {
|
|
27
|
+
if (!this.maxSize || this.store.size < this.maxSize) return;
|
|
28
|
+
|
|
29
|
+
// Remove expired entries first
|
|
30
|
+
for (const [key, entry] of this.store) {
|
|
31
|
+
if (this.isExpired(entry)) {
|
|
32
|
+
this.store.delete(key);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// If still over limit, remove oldest entries
|
|
37
|
+
if (this.store.size >= this.maxSize) {
|
|
38
|
+
const keysToDelete = this.store.size - this.maxSize + 1;
|
|
39
|
+
const keys = Array.from(this.store.keys()).slice(0, keysToDelete);
|
|
40
|
+
keys.forEach(key => this.store.delete(key));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get(key: string): T | undefined {
|
|
45
|
+
const entry = this.store.get(key);
|
|
46
|
+
if (!entry) return undefined;
|
|
47
|
+
|
|
48
|
+
if (this.isExpired(entry)) {
|
|
49
|
+
this.store.delete(key);
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return entry.value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
set(key: string, value: T, ttl?: number): void {
|
|
57
|
+
this.evictIfNeeded();
|
|
58
|
+
|
|
59
|
+
const effectiveTtl = ttl ?? this.ttl;
|
|
60
|
+
const entry: CacheEntry<T> = {
|
|
61
|
+
value,
|
|
62
|
+
expiresAt: effectiveTtl ? Date.now() + effectiveTtl : undefined,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
this.store.set(key, entry);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
has(key: string): boolean {
|
|
69
|
+
return this.get(key) !== undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
delete(key: string): boolean {
|
|
73
|
+
return this.store.delete(key);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
clear(): void {
|
|
77
|
+
this.store.clear();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
size(): number {
|
|
81
|
+
// Clean expired entries first
|
|
82
|
+
for (const [key, entry] of this.store) {
|
|
83
|
+
if (this.isExpired(entry)) {
|
|
84
|
+
this.store.delete(key);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return this.store.size;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
keys(): string[] {
|
|
91
|
+
return Array.from(this.store.keys()).filter(key => this.has(key));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
values(): T[] {
|
|
95
|
+
return this.keys().map(key => this.get(key)!);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
entries(): [string, T][] {
|
|
99
|
+
return this.keys().map(key => [key, this.get(key)!]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async getOrSet(key: string, factory: () => T | Promise<T>, ttl?: number): Promise<T> {
|
|
103
|
+
const cached = this.get(key);
|
|
104
|
+
if (cached !== undefined) return cached;
|
|
105
|
+
|
|
106
|
+
const value = await factory();
|
|
107
|
+
this.set(key, value, ttl);
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Memoization helper
|
|
113
|
+
export function memoize<T extends (...args: unknown[]) => unknown>(
|
|
114
|
+
fn: T,
|
|
115
|
+
options: CacheOptions & { keyFn?: (...args: Parameters<T>) => string } = {}
|
|
116
|
+
): T {
|
|
117
|
+
const cache = new Cache<ReturnType<T>>(options);
|
|
118
|
+
const keyFn = options.keyFn ?? ((...args) => JSON.stringify(args));
|
|
119
|
+
|
|
120
|
+
return ((...args: Parameters<T>) => {
|
|
121
|
+
const key = keyFn(...args);
|
|
122
|
+
const cached = cache.get(key);
|
|
123
|
+
if (cached !== undefined) return cached;
|
|
124
|
+
|
|
125
|
+
const result = fn(...args) as ReturnType<T>;
|
|
126
|
+
cache.set(key, result);
|
|
127
|
+
return result;
|
|
128
|
+
}) as T;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Default cache instance
|
|
132
|
+
export const cache = new Cache();
|
|
133
|
+
|
|
134
|
+
// Factory function
|
|
135
|
+
export function createCache<T = unknown>(options: CacheOptions = {}): Cache<T> {
|
|
136
|
+
return new Cache<T>(options);
|
|
137
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
|
3
|
+
parser: '@typescript-eslint/parser',
|
|
4
|
+
plugins: ['@typescript-eslint', 'import'],
|
|
5
|
+
rules: {
|
|
6
|
+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
7
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
8
|
+
'import/order': [
|
|
9
|
+
'error',
|
|
10
|
+
{
|
|
11
|
+
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
|
12
|
+
'newlines-between': 'always',
|
|
13
|
+
alphabetize: { order: 'asc', caseInsensitive: true },
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
|
17
|
+
'prefer-const': 'error',
|
|
18
|
+
},
|
|
19
|
+
ignorePatterns: ['node_modules', 'dist', '.next', 'coverage'],
|
|
20
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationMap": true,
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noImplicitAny": true,
|
|
15
|
+
"strictNullChecks": true,
|
|
16
|
+
"noUnusedLocals": true,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"esModuleInterop": true,
|
|
20
|
+
"allowSyntheticDefaultImports": true,
|
|
21
|
+
"forceConsistentCasingInFileNames": true,
|
|
22
|
+
"skipLibCheck": true,
|
|
23
|
+
"isolatedModules": true
|
|
24
|
+
},
|
|
25
|
+
"exclude": ["node_modules", "dist"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@repo/constants",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"lint": "eslint src/",
|
|
19
|
+
"lint:fix": "eslint src/ --fix",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsup": "^8.0.0",
|
|
24
|
+
"typescript": "^5.4.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// HTTP Status Codes
|
|
2
|
+
export const HTTP_STATUS = {
|
|
3
|
+
// Success
|
|
4
|
+
OK: 200,
|
|
5
|
+
CREATED: 201,
|
|
6
|
+
ACCEPTED: 202,
|
|
7
|
+
NO_CONTENT: 204,
|
|
8
|
+
|
|
9
|
+
// Redirection
|
|
10
|
+
MOVED_PERMANENTLY: 301,
|
|
11
|
+
FOUND: 302,
|
|
12
|
+
NOT_MODIFIED: 304,
|
|
13
|
+
|
|
14
|
+
// Client Errors
|
|
15
|
+
BAD_REQUEST: 400,
|
|
16
|
+
UNAUTHORIZED: 401,
|
|
17
|
+
FORBIDDEN: 403,
|
|
18
|
+
NOT_FOUND: 404,
|
|
19
|
+
METHOD_NOT_ALLOWED: 405,
|
|
20
|
+
CONFLICT: 409,
|
|
21
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
22
|
+
TOO_MANY_REQUESTS: 429,
|
|
23
|
+
|
|
24
|
+
// Server Errors
|
|
25
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
26
|
+
NOT_IMPLEMENTED: 501,
|
|
27
|
+
BAD_GATEWAY: 502,
|
|
28
|
+
SERVICE_UNAVAILABLE: 503,
|
|
29
|
+
GATEWAY_TIMEOUT: 504,
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
export type HttpStatus = (typeof HTTP_STATUS)[keyof typeof HTTP_STATUS];
|
|
33
|
+
|
|
34
|
+
// Error Codes
|
|
35
|
+
export const ERROR_CODE = {
|
|
36
|
+
// Authentication
|
|
37
|
+
AUTH_INVALID_CREDENTIALS: 'AUTH_INVALID_CREDENTIALS',
|
|
38
|
+
AUTH_TOKEN_EXPIRED: 'AUTH_TOKEN_EXPIRED',
|
|
39
|
+
AUTH_TOKEN_INVALID: 'AUTH_TOKEN_INVALID',
|
|
40
|
+
AUTH_UNAUTHORIZED: 'AUTH_UNAUTHORIZED',
|
|
41
|
+
AUTH_FORBIDDEN: 'AUTH_FORBIDDEN',
|
|
42
|
+
|
|
43
|
+
// Validation
|
|
44
|
+
VALIDATION_FAILED: 'VALIDATION_FAILED',
|
|
45
|
+
VALIDATION_REQUIRED: 'VALIDATION_REQUIRED',
|
|
46
|
+
VALIDATION_INVALID_FORMAT: 'VALIDATION_INVALID_FORMAT',
|
|
47
|
+
|
|
48
|
+
// Resource
|
|
49
|
+
RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND',
|
|
50
|
+
RESOURCE_ALREADY_EXISTS: 'RESOURCE_ALREADY_EXISTS',
|
|
51
|
+
RESOURCE_CONFLICT: 'RESOURCE_CONFLICT',
|
|
52
|
+
|
|
53
|
+
// Rate Limiting
|
|
54
|
+
RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
|
|
55
|
+
|
|
56
|
+
// Server
|
|
57
|
+
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
58
|
+
SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
|
|
59
|
+
DATABASE_ERROR: 'DATABASE_ERROR',
|
|
60
|
+
EXTERNAL_SERVICE_ERROR: 'EXTERNAL_SERVICE_ERROR',
|
|
61
|
+
} as const;
|
|
62
|
+
|
|
63
|
+
export type ErrorCode = (typeof ERROR_CODE)[keyof typeof ERROR_CODE];
|
|
64
|
+
|
|
65
|
+
// User Roles
|
|
66
|
+
export const USER_ROLE = {
|
|
67
|
+
ADMIN: 'admin',
|
|
68
|
+
USER: 'user',
|
|
69
|
+
GUEST: 'guest',
|
|
70
|
+
} as const;
|
|
71
|
+
|
|
72
|
+
export type UserRole = (typeof USER_ROLE)[keyof typeof USER_ROLE];
|
|
73
|
+
|
|
74
|
+
// Pagination
|
|
75
|
+
export const PAGINATION = {
|
|
76
|
+
DEFAULT_PAGE: 1,
|
|
77
|
+
DEFAULT_LIMIT: 20,
|
|
78
|
+
MAX_LIMIT: 100,
|
|
79
|
+
} as const;
|
|
80
|
+
|
|
81
|
+
// Date/Time
|
|
82
|
+
export const TIME = {
|
|
83
|
+
SECOND: 1000,
|
|
84
|
+
MINUTE: 60 * 1000,
|
|
85
|
+
HOUR: 60 * 60 * 1000,
|
|
86
|
+
DAY: 24 * 60 * 60 * 1000,
|
|
87
|
+
WEEK: 7 * 24 * 60 * 60 * 1000,
|
|
88
|
+
} as const;
|
|
89
|
+
|
|
90
|
+
// Regex Patterns
|
|
91
|
+
export const REGEX = {
|
|
92
|
+
EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
93
|
+
UUID: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
94
|
+
SLUG: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
|
95
|
+
PHONE: /^\+?[1-9]\d{1,14}$/,
|
|
96
|
+
URL: /^https?:\/\/.+/,
|
|
97
|
+
} as const;
|
|
98
|
+
|
|
99
|
+
// Mime Types
|
|
100
|
+
export const MIME_TYPE = {
|
|
101
|
+
JSON: 'application/json',
|
|
102
|
+
HTML: 'text/html',
|
|
103
|
+
TEXT: 'text/plain',
|
|
104
|
+
XML: 'application/xml',
|
|
105
|
+
PDF: 'application/pdf',
|
|
106
|
+
PNG: 'image/png',
|
|
107
|
+
JPEG: 'image/jpeg',
|
|
108
|
+
GIF: 'image/gif',
|
|
109
|
+
SVG: 'image/svg+xml',
|
|
110
|
+
} as const;
|
|
111
|
+
|
|
112
|
+
export type MimeType = (typeof MIME_TYPE)[keyof typeof MIME_TYPE];
|
|
113
|
+
|
|
114
|
+
// Environment
|
|
115
|
+
export const ENV = {
|
|
116
|
+
DEVELOPMENT: 'development',
|
|
117
|
+
PRODUCTION: 'production',
|
|
118
|
+
TEST: 'test',
|
|
119
|
+
} as const;
|
|
120
|
+
|
|
121
|
+
export type Environment = (typeof ENV)[keyof typeof ENV];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@repo/logger",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"lint": "eslint src/",
|
|
19
|
+
"lint:fix": "eslint src/ --fix",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^20.0.0",
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"typescript": "^5.4.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
|
2
|
+
|
|
3
|
+
export interface LogContext {
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface LoggerOptions {
|
|
8
|
+
level?: LogLevel;
|
|
9
|
+
prefix?: string;
|
|
10
|
+
timestamp?: boolean;
|
|
11
|
+
colors?: boolean;
|
|
12
|
+
json?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface LogEntry {
|
|
16
|
+
timestamp: string;
|
|
17
|
+
level: LogLevel;
|
|
18
|
+
message: string;
|
|
19
|
+
prefix?: string;
|
|
20
|
+
context?: LogContext;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const LOG_LEVELS: Record<LogLevel, number> = {
|
|
24
|
+
debug: 0,
|
|
25
|
+
info: 1,
|
|
26
|
+
warn: 2,
|
|
27
|
+
error: 3,
|
|
28
|
+
silent: 4,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const LOG_COLORS: Record<Exclude<LogLevel, 'silent'>, string> = {
|
|
32
|
+
debug: '\x1b[36m', // cyan
|
|
33
|
+
info: '\x1b[32m', // green
|
|
34
|
+
warn: '\x1b[33m', // yellow
|
|
35
|
+
error: '\x1b[31m', // red
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const RESET = '\x1b[0m';
|
|
39
|
+
const DIM = '\x1b[2m';
|
|
40
|
+
const BOLD = '\x1b[1m';
|
|
41
|
+
|
|
42
|
+
export class Logger {
|
|
43
|
+
private level: LogLevel;
|
|
44
|
+
private prefix: string;
|
|
45
|
+
private showTimestamp: boolean;
|
|
46
|
+
private useColors: boolean;
|
|
47
|
+
private jsonOutput: boolean;
|
|
48
|
+
|
|
49
|
+
constructor(options: LoggerOptions = {}) {
|
|
50
|
+
this.level = options.level ?? 'info';
|
|
51
|
+
this.prefix = options.prefix ?? '';
|
|
52
|
+
this.showTimestamp = options.timestamp ?? true;
|
|
53
|
+
this.useColors = options.colors ?? true;
|
|
54
|
+
this.jsonOutput = options.json ?? false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private shouldLog(level: LogLevel): boolean {
|
|
58
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private formatJson(entry: LogEntry): string {
|
|
62
|
+
return JSON.stringify(entry);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private formatPretty(level: Exclude<LogLevel, 'silent'>, message: string): string {
|
|
66
|
+
const parts: string[] = [];
|
|
67
|
+
|
|
68
|
+
if (this.showTimestamp) {
|
|
69
|
+
const time = new Date().toISOString();
|
|
70
|
+
parts.push(this.useColors ? `${DIM}[${time}]${RESET}` : `[${time}]`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const levelTag = `[${level.toUpperCase()}]`;
|
|
74
|
+
if (this.useColors) {
|
|
75
|
+
parts.push(`${LOG_COLORS[level]}${BOLD}${levelTag}${RESET}`);
|
|
76
|
+
} else {
|
|
77
|
+
parts.push(levelTag);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (this.prefix) {
|
|
81
|
+
parts.push(this.useColors ? `${DIM}[${this.prefix}]${RESET}` : `[${this.prefix}]`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
parts.push(message);
|
|
85
|
+
|
|
86
|
+
return parts.join(' ');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private log(level: Exclude<LogLevel, 'silent'>, message: string, context?: LogContext): void {
|
|
90
|
+
if (!this.shouldLog(level)) return;
|
|
91
|
+
|
|
92
|
+
const consoleMethod =
|
|
93
|
+
level === 'debug' ? 'debug' : level === 'info' ? 'info' : level === 'warn' ? 'warn' : 'error';
|
|
94
|
+
|
|
95
|
+
if (this.jsonOutput) {
|
|
96
|
+
const entry: LogEntry = {
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
level,
|
|
99
|
+
message,
|
|
100
|
+
...(this.prefix && { prefix: this.prefix }),
|
|
101
|
+
...(context && { context }),
|
|
102
|
+
};
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console[consoleMethod](this.formatJson(entry));
|
|
105
|
+
} else {
|
|
106
|
+
const formatted = this.formatPretty(level, message);
|
|
107
|
+
if (context) {
|
|
108
|
+
// eslint-disable-next-line no-console
|
|
109
|
+
console[consoleMethod](formatted, context);
|
|
110
|
+
} else {
|
|
111
|
+
// eslint-disable-next-line no-console
|
|
112
|
+
console[consoleMethod](formatted);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
debug(message: string, context?: LogContext): void {
|
|
118
|
+
this.log('debug', message, context);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
info(message: string, context?: LogContext): void {
|
|
122
|
+
this.log('info', message, context);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
warn(message: string, context?: LogContext): void {
|
|
126
|
+
this.log('warn', message, context);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
error(message: string, context?: LogContext): void {
|
|
130
|
+
this.log('error', message, context);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
child(prefix: string): Logger {
|
|
134
|
+
return new Logger({
|
|
135
|
+
level: this.level,
|
|
136
|
+
prefix: this.prefix ? `${this.prefix}:${prefix}` : prefix,
|
|
137
|
+
timestamp: this.showTimestamp,
|
|
138
|
+
colors: this.useColors,
|
|
139
|
+
json: this.jsonOutput,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setLevel(level: LogLevel): void {
|
|
144
|
+
this.level = level;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getLevel(): LogLevel {
|
|
148
|
+
return this.level;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
isLevelEnabled(level: LogLevel): boolean {
|
|
152
|
+
return this.shouldLog(level);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
time(label: string): () => void {
|
|
156
|
+
const start = performance.now();
|
|
157
|
+
return () => {
|
|
158
|
+
const duration = performance.now() - start;
|
|
159
|
+
this.debug(`${label} completed`, { durationMs: Math.round(duration * 100) / 100 });
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async timeAsync<T>(label: string, fn: () => Promise<T>): Promise<T> {
|
|
164
|
+
const end = this.time(label);
|
|
165
|
+
try {
|
|
166
|
+
return await fn();
|
|
167
|
+
} finally {
|
|
168
|
+
end();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
group(label: string): void {
|
|
173
|
+
if (!this.shouldLog('debug')) return;
|
|
174
|
+
// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
175
|
+
console.group(this.formatPretty('debug', label));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
groupEnd(): void {
|
|
179
|
+
if (!this.shouldLog('debug')) return;
|
|
180
|
+
// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
181
|
+
console.groupEnd();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
table(data: unknown): void {
|
|
185
|
+
if (!this.shouldLog('debug')) return;
|
|
186
|
+
// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
187
|
+
console.table(data);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Default logger instance
|
|
192
|
+
export const logger = new Logger();
|
|
193
|
+
|
|
194
|
+
// Factory function
|
|
195
|
+
export function createLogger(options: LoggerOptions = {}): Logger {
|
|
196
|
+
return new Logger(options);
|
|
197
|
+
}
|