mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:41:09 +02:00
style: auto-format codebase with Prettier
Applied formatting to 1487+ files using pnpm format:write - TypeScript/JavaScript files - Svelte components - Astro pages - JSON configs - Markdown docs 13 files still need manual review (Astro JSX comments)
This commit is contained in:
parent
0241f5554c
commit
d36b321d9d
3952 changed files with 661498 additions and 739751 deletions
|
|
@ -1,24 +1,24 @@
|
|||
{
|
||||
"name": "@manacore/shared-config",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./env": "./src/env.ts",
|
||||
"./api": "./src/api.ts",
|
||||
"./features": "./src/features.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.10.1",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
"name": "@manacore/shared-config",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./env": "./src/env.ts",
|
||||
"./api": "./src/api.ts",
|
||||
"./features": "./src/features.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.10.1",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,202 +6,202 @@
|
|||
* API configuration
|
||||
*/
|
||||
export interface ApiConfig {
|
||||
/** Base URL for the API */
|
||||
baseUrl: string;
|
||||
/** API version prefix (e.g., 'v1') */
|
||||
version?: string;
|
||||
/** Default timeout in milliseconds */
|
||||
timeout?: number;
|
||||
/** Default headers */
|
||||
headers?: Record<string, string>;
|
||||
/** Base URL for the API */
|
||||
baseUrl: string;
|
||||
/** API version prefix (e.g., 'v1') */
|
||||
version?: string;
|
||||
/** Default timeout in milliseconds */
|
||||
timeout?: number;
|
||||
/** Default headers */
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create API endpoint URL builder
|
||||
*/
|
||||
export function createApiBuilder(config: ApiConfig) {
|
||||
const { baseUrl, version } = config;
|
||||
const { baseUrl, version } = config;
|
||||
|
||||
// Remove trailing slash from base URL
|
||||
const base = baseUrl.replace(/\/$/, '');
|
||||
// Remove trailing slash from base URL
|
||||
const base = baseUrl.replace(/\/$/, '');
|
||||
|
||||
// Build base path with optional version
|
||||
const basePath = version ? `${base}/${version}` : base;
|
||||
// Build base path with optional version
|
||||
const basePath = version ? `${base}/${version}` : base;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Build endpoint URL from path segments
|
||||
*/
|
||||
endpoint(...segments: (string | number)[]): string {
|
||||
const path = segments
|
||||
.map(String)
|
||||
.map(s => s.replace(/^\/+|\/+$/g, '')) // Remove leading/trailing slashes
|
||||
.filter(Boolean)
|
||||
.join('/');
|
||||
return {
|
||||
/**
|
||||
* Build endpoint URL from path segments
|
||||
*/
|
||||
endpoint(...segments: (string | number)[]): string {
|
||||
const path = segments
|
||||
.map(String)
|
||||
.map((s) => s.replace(/^\/+|\/+$/g, '')) // Remove leading/trailing slashes
|
||||
.filter(Boolean)
|
||||
.join('/');
|
||||
|
||||
return `${basePath}/${path}`;
|
||||
},
|
||||
return `${basePath}/${path}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Build endpoint URL with query parameters
|
||||
*/
|
||||
endpointWithQuery(
|
||||
path: string | string[],
|
||||
params?: Record<string, string | number | boolean | undefined>
|
||||
): string {
|
||||
const segments = Array.isArray(path) ? path : [path];
|
||||
const url = this.endpoint(...segments);
|
||||
/**
|
||||
* Build endpoint URL with query parameters
|
||||
*/
|
||||
endpointWithQuery(
|
||||
path: string | string[],
|
||||
params?: Record<string, string | number | boolean | undefined>
|
||||
): string {
|
||||
const segments = Array.isArray(path) ? path : [path];
|
||||
const url = this.endpoint(...segments);
|
||||
|
||||
if (!params) {
|
||||
return url;
|
||||
}
|
||||
if (!params) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined) {
|
||||
searchParams.append(key, String(value));
|
||||
}
|
||||
}
|
||||
const searchParams = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined) {
|
||||
searchParams.append(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
return queryString ? `${url}?${queryString}` : url;
|
||||
},
|
||||
const queryString = searchParams.toString();
|
||||
return queryString ? `${url}?${queryString}` : url;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the base URL
|
||||
*/
|
||||
getBaseUrl(): string {
|
||||
return basePath;
|
||||
},
|
||||
/**
|
||||
* Get the base URL
|
||||
*/
|
||||
getBaseUrl(): string {
|
||||
return basePath;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the config
|
||||
*/
|
||||
getConfig(): ApiConfig {
|
||||
return config;
|
||||
},
|
||||
};
|
||||
/**
|
||||
* Get the config
|
||||
*/
|
||||
getConfig(): ApiConfig {
|
||||
return config;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build URL with query parameters
|
||||
*/
|
||||
export function buildUrl(
|
||||
baseUrl: string,
|
||||
path: string,
|
||||
params?: Record<string, string | number | boolean | undefined>
|
||||
baseUrl: string,
|
||||
path: string,
|
||||
params?: Record<string, string | number | boolean | undefined>
|
||||
): string {
|
||||
// Ensure single slash between base and path
|
||||
const base = baseUrl.replace(/\/$/, '');
|
||||
const cleanPath = path.replace(/^\//, '');
|
||||
const url = `${base}/${cleanPath}`;
|
||||
// Ensure single slash between base and path
|
||||
const base = baseUrl.replace(/\/$/, '');
|
||||
const cleanPath = path.replace(/^\//, '');
|
||||
const url = `${base}/${cleanPath}`;
|
||||
|
||||
if (!params) {
|
||||
return url;
|
||||
}
|
||||
if (!params) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined) {
|
||||
searchParams.append(key, String(value));
|
||||
}
|
||||
}
|
||||
const searchParams = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined) {
|
||||
searchParams.append(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
return queryString ? `${url}?${queryString}` : url;
|
||||
const queryString = searchParams.toString();
|
||||
return queryString ? `${url}?${queryString}` : url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse URL and extract components
|
||||
*/
|
||||
export function parseUrl(url: string): {
|
||||
protocol: string;
|
||||
host: string;
|
||||
port: string;
|
||||
pathname: string;
|
||||
search: string;
|
||||
params: Record<string, string>;
|
||||
protocol: string;
|
||||
host: string;
|
||||
port: string;
|
||||
pathname: string;
|
||||
search: string;
|
||||
params: Record<string, string>;
|
||||
} {
|
||||
const urlObj = new URL(url);
|
||||
const urlObj = new URL(url);
|
||||
|
||||
const params: Record<string, string> = {};
|
||||
urlObj.searchParams.forEach((value, key) => {
|
||||
params[key] = value;
|
||||
});
|
||||
const params: Record<string, string> = {};
|
||||
urlObj.searchParams.forEach((value, key) => {
|
||||
params[key] = value;
|
||||
});
|
||||
|
||||
return {
|
||||
protocol: urlObj.protocol.replace(':', ''),
|
||||
host: urlObj.hostname,
|
||||
port: urlObj.port,
|
||||
pathname: urlObj.pathname,
|
||||
search: urlObj.search,
|
||||
params,
|
||||
};
|
||||
return {
|
||||
protocol: urlObj.protocol.replace(':', ''),
|
||||
host: urlObj.hostname,
|
||||
port: urlObj.port,
|
||||
pathname: urlObj.pathname,
|
||||
search: urlObj.search,
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Join URL path segments
|
||||
*/
|
||||
export function joinPath(...segments: string[]): string {
|
||||
return segments
|
||||
.map(s => s.replace(/^\/+|\/+$/g, ''))
|
||||
.filter(Boolean)
|
||||
.join('/');
|
||||
return segments
|
||||
.map((s) => s.replace(/^\/+|\/+$/g, ''))
|
||||
.filter(Boolean)
|
||||
.join('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Common HTTP methods
|
||||
*/
|
||||
export const HTTP_METHODS = {
|
||||
GET: 'GET',
|
||||
POST: 'POST',
|
||||
PUT: 'PUT',
|
||||
PATCH: 'PATCH',
|
||||
DELETE: 'DELETE',
|
||||
HEAD: 'HEAD',
|
||||
OPTIONS: 'OPTIONS',
|
||||
GET: 'GET',
|
||||
POST: 'POST',
|
||||
PUT: 'PUT',
|
||||
PATCH: 'PATCH',
|
||||
DELETE: 'DELETE',
|
||||
HEAD: 'HEAD',
|
||||
OPTIONS: 'OPTIONS',
|
||||
} as const;
|
||||
|
||||
export type HttpMethod = typeof HTTP_METHODS[keyof typeof HTTP_METHODS];
|
||||
export type HttpMethod = (typeof HTTP_METHODS)[keyof typeof HTTP_METHODS];
|
||||
|
||||
/**
|
||||
* Common HTTP status codes
|
||||
*/
|
||||
export const HTTP_STATUS = {
|
||||
OK: 200,
|
||||
CREATED: 201,
|
||||
NO_CONTENT: 204,
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
CONFLICT: 409,
|
||||
UNPROCESSABLE_ENTITY: 422,
|
||||
TOO_MANY_REQUESTS: 429,
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
BAD_GATEWAY: 502,
|
||||
SERVICE_UNAVAILABLE: 503,
|
||||
OK: 200,
|
||||
CREATED: 201,
|
||||
NO_CONTENT: 204,
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
CONFLICT: 409,
|
||||
UNPROCESSABLE_ENTITY: 422,
|
||||
TOO_MANY_REQUESTS: 429,
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
BAD_GATEWAY: 502,
|
||||
SERVICE_UNAVAILABLE: 503,
|
||||
} as const;
|
||||
|
||||
export type HttpStatus = typeof HTTP_STATUS[keyof typeof HTTP_STATUS];
|
||||
export type HttpStatus = (typeof HTTP_STATUS)[keyof typeof HTTP_STATUS];
|
||||
|
||||
/**
|
||||
* Check if status code is successful (2xx)
|
||||
*/
|
||||
export function isSuccessStatus(status: number): boolean {
|
||||
return status >= 200 && status < 300;
|
||||
return status >= 200 && status < 300;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if status code is client error (4xx)
|
||||
*/
|
||||
export function isClientError(status: number): boolean {
|
||||
return status >= 400 && status < 500;
|
||||
return status >= 400 && status < 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if status code is server error (5xx)
|
||||
*/
|
||||
export function isServerError(status: number): boolean {
|
||||
return status >= 500 && status < 600;
|
||||
return status >= 500 && status < 600;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,166 +8,163 @@ import { z } from 'zod';
|
|||
* Common environment variable schemas
|
||||
*/
|
||||
export const envSchemas = {
|
||||
/** URL schema */
|
||||
url: z.string().url(),
|
||||
/** URL schema */
|
||||
url: z.string().url(),
|
||||
|
||||
/** Non-empty string schema */
|
||||
nonEmpty: z.string().min(1),
|
||||
/** Non-empty string schema */
|
||||
nonEmpty: z.string().min(1),
|
||||
|
||||
/** Optional string schema */
|
||||
optional: z.string().optional(),
|
||||
/** Optional string schema */
|
||||
optional: z.string().optional(),
|
||||
|
||||
/** Port number schema */
|
||||
port: z.coerce.number().int().min(1).max(65535),
|
||||
/** Port number schema */
|
||||
port: z.coerce.number().int().min(1).max(65535),
|
||||
|
||||
/** Boolean schema (accepts various formats) */
|
||||
boolean: z.preprocess(
|
||||
(val) => {
|
||||
if (typeof val === 'boolean') return val;
|
||||
if (typeof val === 'string') {
|
||||
return ['true', '1', 'yes', 'on'].includes(val.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
},
|
||||
z.boolean()
|
||||
),
|
||||
/** Boolean schema (accepts various formats) */
|
||||
boolean: z.preprocess((val) => {
|
||||
if (typeof val === 'boolean') return val;
|
||||
if (typeof val === 'string') {
|
||||
return ['true', '1', 'yes', 'on'].includes(val.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
}, z.boolean()),
|
||||
|
||||
/** Number schema */
|
||||
number: z.coerce.number(),
|
||||
/** Number schema */
|
||||
number: z.coerce.number(),
|
||||
|
||||
/** Positive integer schema */
|
||||
positiveInt: z.coerce.number().int().positive(),
|
||||
/** Positive integer schema */
|
||||
positiveInt: z.coerce.number().int().positive(),
|
||||
|
||||
/** Email schema */
|
||||
email: z.string().email(),
|
||||
/** Email schema */
|
||||
email: z.string().email(),
|
||||
|
||||
/** Node environment schema */
|
||||
nodeEnv: z.enum(['development', 'production', 'test']).default('development'),
|
||||
/** Node environment schema */
|
||||
nodeEnv: z.enum(['development', 'production', 'test']).default('development'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Common Supabase environment schema
|
||||
*/
|
||||
export const supabaseEnvSchema = z.object({
|
||||
SUPABASE_URL: envSchemas.url,
|
||||
SUPABASE_ANON_KEY: envSchemas.nonEmpty,
|
||||
SUPABASE_SERVICE_ROLE_KEY: envSchemas.nonEmpty.optional(),
|
||||
SUPABASE_URL: envSchemas.url,
|
||||
SUPABASE_ANON_KEY: envSchemas.nonEmpty,
|
||||
SUPABASE_SERVICE_ROLE_KEY: envSchemas.nonEmpty.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Common app environment schema
|
||||
*/
|
||||
export const appEnvSchema = z.object({
|
||||
NODE_ENV: envSchemas.nodeEnv,
|
||||
PORT: envSchemas.port.default(3000),
|
||||
NODE_ENV: envSchemas.nodeEnv,
|
||||
PORT: envSchemas.port.default(3000),
|
||||
});
|
||||
|
||||
/**
|
||||
* Create an environment config from schema
|
||||
*/
|
||||
export function createEnvConfig<T extends z.ZodTypeAny>(
|
||||
schema: T,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
schema: T,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): z.infer<T> {
|
||||
const result = schema.safeParse(env);
|
||||
const result = schema.safeParse(env);
|
||||
|
||||
if (!result.success) {
|
||||
const errors = result.error.errors
|
||||
.map((err) => ` ${err.path.join('.')}: ${err.message}`)
|
||||
.join('\n');
|
||||
if (!result.success) {
|
||||
const errors = result.error.errors
|
||||
.map((err) => ` ${err.path.join('.')}: ${err.message}`)
|
||||
.join('\n');
|
||||
|
||||
throw new Error(`Environment validation failed:\n${errors}`);
|
||||
}
|
||||
throw new Error(`Environment validation failed:\n${errors}`);
|
||||
}
|
||||
|
||||
return result.data;
|
||||
return result.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate environment variables with custom schema
|
||||
*/
|
||||
export function validateEnv<T extends z.ZodRawShape>(
|
||||
schema: z.ZodObject<T>,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
schema: z.ZodObject<T>,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): z.infer<z.ZodObject<T>> {
|
||||
return createEnvConfig(schema, env);
|
||||
return createEnvConfig(schema, env);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required environment variable with type safety
|
||||
*/
|
||||
export function getRequiredEnv(key: string, env: NodeJS.ProcessEnv = process.env): string {
|
||||
const value = env[key];
|
||||
const value = env[key];
|
||||
|
||||
if (value === undefined || value === '') {
|
||||
throw new Error(`Required environment variable "${key}" is not set`);
|
||||
}
|
||||
if (value === undefined || value === '') {
|
||||
throw new Error(`Required environment variable "${key}" is not set`);
|
||||
}
|
||||
|
||||
return value;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get optional environment variable with default
|
||||
*/
|
||||
export function getEnv(
|
||||
key: string,
|
||||
defaultValue: string,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
key: string,
|
||||
defaultValue: string,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): string {
|
||||
return env[key] ?? defaultValue;
|
||||
return env[key] ?? defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get boolean environment variable
|
||||
*/
|
||||
export function getBoolEnv(
|
||||
key: string,
|
||||
defaultValue: boolean = false,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
key: string,
|
||||
defaultValue: boolean = false,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): boolean {
|
||||
const value = env[key];
|
||||
const value = env[key];
|
||||
|
||||
if (value === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
if (value === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase());
|
||||
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number environment variable
|
||||
*/
|
||||
export function getNumEnv(
|
||||
key: string,
|
||||
defaultValue: number,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
key: string,
|
||||
defaultValue: number,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): number {
|
||||
const value = env[key];
|
||||
const value = env[key];
|
||||
|
||||
if (value === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
if (value === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const parsed = Number(value);
|
||||
return isNaN(parsed) ? defaultValue : parsed;
|
||||
const parsed = Number(value);
|
||||
return isNaN(parsed) ? defaultValue : parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if running in development
|
||||
*/
|
||||
export function isDevelopment(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
return env.NODE_ENV === 'development';
|
||||
return env.NODE_ENV === 'development';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if running in production
|
||||
*/
|
||||
export function isProduction(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
return env.NODE_ENV === 'production';
|
||||
return env.NODE_ENV === 'production';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if running in test
|
||||
*/
|
||||
export function isTest(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
return env.NODE_ENV === 'test';
|
||||
return env.NODE_ENV === 'test';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,168 +6,168 @@
|
|||
* Feature flag configuration
|
||||
*/
|
||||
export interface FeatureFlag {
|
||||
/** Feature key */
|
||||
key: string;
|
||||
/** Default enabled state */
|
||||
defaultEnabled: boolean;
|
||||
/** Description */
|
||||
description?: string;
|
||||
/** Environment variable to override */
|
||||
envVar?: string;
|
||||
/** Feature key */
|
||||
key: string;
|
||||
/** Default enabled state */
|
||||
defaultEnabled: boolean;
|
||||
/** Description */
|
||||
description?: string;
|
||||
/** Environment variable to override */
|
||||
envVar?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a feature flag manager
|
||||
*/
|
||||
export function createFeatureFlags<T extends Record<string, FeatureFlag>>(
|
||||
flags: T,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
flags: T,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
) {
|
||||
type FlagKey = keyof T;
|
||||
type FlagKey = keyof T;
|
||||
|
||||
/**
|
||||
* Check if a feature is enabled
|
||||
*/
|
||||
function isEnabled(key: FlagKey): boolean {
|
||||
const flag = flags[key];
|
||||
/**
|
||||
* Check if a feature is enabled
|
||||
*/
|
||||
function isEnabled(key: FlagKey): boolean {
|
||||
const flag = flags[key];
|
||||
|
||||
if (!flag) {
|
||||
return false;
|
||||
}
|
||||
if (!flag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check environment variable override
|
||||
if (flag.envVar) {
|
||||
const envValue = env[flag.envVar];
|
||||
if (envValue !== undefined) {
|
||||
return ['true', '1', 'yes', 'on'].includes(envValue.toLowerCase());
|
||||
}
|
||||
}
|
||||
// Check environment variable override
|
||||
if (flag.envVar) {
|
||||
const envValue = env[flag.envVar];
|
||||
if (envValue !== undefined) {
|
||||
return ['true', '1', 'yes', 'on'].includes(envValue.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
// Check generic feature flag env var
|
||||
const genericEnvVar = `FEATURE_${String(key).toUpperCase()}`;
|
||||
const genericValue = env[genericEnvVar];
|
||||
if (genericValue !== undefined) {
|
||||
return ['true', '1', 'yes', 'on'].includes(genericValue.toLowerCase());
|
||||
}
|
||||
// Check generic feature flag env var
|
||||
const genericEnvVar = `FEATURE_${String(key).toUpperCase()}`;
|
||||
const genericValue = env[genericEnvVar];
|
||||
if (genericValue !== undefined) {
|
||||
return ['true', '1', 'yes', 'on'].includes(genericValue.toLowerCase());
|
||||
}
|
||||
|
||||
return flag.defaultEnabled;
|
||||
}
|
||||
return flag.defaultEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all enabled features
|
||||
*/
|
||||
function getEnabledFeatures(): FlagKey[] {
|
||||
return (Object.keys(flags) as FlagKey[]).filter(isEnabled);
|
||||
}
|
||||
/**
|
||||
* Get all enabled features
|
||||
*/
|
||||
function getEnabledFeatures(): FlagKey[] {
|
||||
return (Object.keys(flags) as FlagKey[]).filter(isEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all disabled features
|
||||
*/
|
||||
function getDisabledFeatures(): FlagKey[] {
|
||||
return (Object.keys(flags) as FlagKey[]).filter(key => !isEnabled(key));
|
||||
}
|
||||
/**
|
||||
* Get all disabled features
|
||||
*/
|
||||
function getDisabledFeatures(): FlagKey[] {
|
||||
return (Object.keys(flags) as FlagKey[]).filter((key) => !isEnabled(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get feature configuration
|
||||
*/
|
||||
function getFlag(key: FlagKey): FeatureFlag | undefined {
|
||||
return flags[key];
|
||||
}
|
||||
/**
|
||||
* Get feature configuration
|
||||
*/
|
||||
function getFlag(key: FlagKey): FeatureFlag | undefined {
|
||||
return flags[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all flags with their current state
|
||||
*/
|
||||
function getAllFlags(): Record<string, boolean> {
|
||||
const result: Record<string, boolean> = {};
|
||||
for (const key of Object.keys(flags) as FlagKey[]) {
|
||||
result[String(key)] = isEnabled(key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Get all flags with their current state
|
||||
*/
|
||||
function getAllFlags(): Record<string, boolean> {
|
||||
const result: Record<string, boolean> = {};
|
||||
for (const key of Object.keys(flags) as FlagKey[]) {
|
||||
result[String(key)] = isEnabled(key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
isEnabled,
|
||||
getEnabledFeatures,
|
||||
getDisabledFeatures,
|
||||
getFlag,
|
||||
getAllFlags,
|
||||
};
|
||||
return {
|
||||
isEnabled,
|
||||
getEnabledFeatures,
|
||||
getDisabledFeatures,
|
||||
getFlag,
|
||||
getAllFlags,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple feature check using environment variable
|
||||
*/
|
||||
export function isFeatureEnabled(
|
||||
featureName: string,
|
||||
defaultValue: boolean = false,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
featureName: string,
|
||||
defaultValue: boolean = false,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): boolean {
|
||||
const envVar = `FEATURE_${featureName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}`;
|
||||
const value = env[envVar];
|
||||
const envVar = `FEATURE_${featureName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}`;
|
||||
const value = env[envVar];
|
||||
|
||||
if (value === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
if (value === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase());
|
||||
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* App metadata configuration
|
||||
*/
|
||||
export interface AppMetadata {
|
||||
/** App name */
|
||||
name: string;
|
||||
/** App version */
|
||||
version: string;
|
||||
/** App description */
|
||||
description?: string;
|
||||
/** Build number */
|
||||
buildNumber?: string;
|
||||
/** Git commit hash */
|
||||
commitHash?: string;
|
||||
/** Build timestamp */
|
||||
buildTime?: string;
|
||||
/** Environment */
|
||||
environment?: string;
|
||||
/** App name */
|
||||
name: string;
|
||||
/** App version */
|
||||
version: string;
|
||||
/** App description */
|
||||
description?: string;
|
||||
/** Build number */
|
||||
buildNumber?: string;
|
||||
/** Git commit hash */
|
||||
commitHash?: string;
|
||||
/** Build timestamp */
|
||||
buildTime?: string;
|
||||
/** Environment */
|
||||
environment?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create app metadata from environment
|
||||
*/
|
||||
export function createAppMetadata(
|
||||
config: {
|
||||
name: string;
|
||||
version: string;
|
||||
description?: string;
|
||||
},
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
config: {
|
||||
name: string;
|
||||
version: string;
|
||||
description?: string;
|
||||
},
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): AppMetadata {
|
||||
return {
|
||||
name: config.name,
|
||||
version: config.version,
|
||||
description: config.description,
|
||||
buildNumber: env.BUILD_NUMBER || env.VITE_BUILD_NUMBER,
|
||||
commitHash: env.COMMIT_HASH || env.VITE_COMMIT_HASH || env.GIT_COMMIT,
|
||||
buildTime: env.BUILD_TIME || env.VITE_BUILD_TIME,
|
||||
environment: env.NODE_ENV || 'development',
|
||||
};
|
||||
return {
|
||||
name: config.name,
|
||||
version: config.version,
|
||||
description: config.description,
|
||||
buildNumber: env.BUILD_NUMBER || env.VITE_BUILD_NUMBER,
|
||||
commitHash: env.COMMIT_HASH || env.VITE_COMMIT_HASH || env.GIT_COMMIT,
|
||||
buildTime: env.BUILD_TIME || env.VITE_BUILD_TIME,
|
||||
environment: env.NODE_ENV || 'development',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format version string with build info
|
||||
*/
|
||||
export function formatVersion(metadata: AppMetadata): string {
|
||||
let version = metadata.version;
|
||||
let version = metadata.version;
|
||||
|
||||
if (metadata.buildNumber) {
|
||||
version += ` (${metadata.buildNumber})`;
|
||||
}
|
||||
if (metadata.buildNumber) {
|
||||
version += ` (${metadata.buildNumber})`;
|
||||
}
|
||||
|
||||
if (metadata.commitHash) {
|
||||
const shortHash = metadata.commitHash.substring(0, 7);
|
||||
version += ` [${shortHash}]`;
|
||||
}
|
||||
if (metadata.commitHash) {
|
||||
const shortHash = metadata.commitHash.substring(0, 7);
|
||||
version += ` [${shortHash}]`;
|
||||
}
|
||||
|
||||
return version;
|
||||
return version;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,42 +7,42 @@
|
|||
|
||||
// Environment utilities
|
||||
export {
|
||||
envSchemas,
|
||||
supabaseEnvSchema,
|
||||
appEnvSchema,
|
||||
createEnvConfig,
|
||||
validateEnv,
|
||||
getRequiredEnv,
|
||||
getEnv,
|
||||
getBoolEnv,
|
||||
getNumEnv,
|
||||
isDevelopment,
|
||||
isProduction,
|
||||
isTest,
|
||||
envSchemas,
|
||||
supabaseEnvSchema,
|
||||
appEnvSchema,
|
||||
createEnvConfig,
|
||||
validateEnv,
|
||||
getRequiredEnv,
|
||||
getEnv,
|
||||
getBoolEnv,
|
||||
getNumEnv,
|
||||
isDevelopment,
|
||||
isProduction,
|
||||
isTest,
|
||||
} from './env';
|
||||
|
||||
// API utilities
|
||||
export {
|
||||
type ApiConfig,
|
||||
createApiBuilder,
|
||||
buildUrl,
|
||||
parseUrl,
|
||||
joinPath,
|
||||
HTTP_METHODS,
|
||||
HTTP_STATUS,
|
||||
type HttpMethod,
|
||||
type HttpStatus,
|
||||
isSuccessStatus,
|
||||
isClientError,
|
||||
isServerError,
|
||||
type ApiConfig,
|
||||
createApiBuilder,
|
||||
buildUrl,
|
||||
parseUrl,
|
||||
joinPath,
|
||||
HTTP_METHODS,
|
||||
HTTP_STATUS,
|
||||
type HttpMethod,
|
||||
type HttpStatus,
|
||||
isSuccessStatus,
|
||||
isClientError,
|
||||
isServerError,
|
||||
} from './api';
|
||||
|
||||
// Feature flag utilities
|
||||
export {
|
||||
type FeatureFlag,
|
||||
createFeatureFlags,
|
||||
isFeatureEnabled,
|
||||
type AppMetadata,
|
||||
createAppMetadata,
|
||||
formatVersion,
|
||||
type FeatureFlag,
|
||||
createFeatureFlags,
|
||||
isFeatureEnabled,
|
||||
type AppMetadata,
|
||||
createAppMetadata,
|
||||
formatVersion,
|
||||
} from './features';
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue