refactor(cli): replace logging utility with new logger implementation, update logging calls across modules

This commit is contained in:
Melvin Chia
2026-01-12 18:36:41 +08:00
parent af7cc2354b
commit 9308b6a97f
75 changed files with 484 additions and 570 deletions

View File

@@ -9,7 +9,6 @@
},
"dependencies": {
"tar": "^7.5.2",
"axios": "^1.12.2",
"chalk": "^5.6.2",
"commander": "^14.0.2",
"concurrently": "^9.2.1",
@@ -19,13 +18,13 @@
"handlebars": "^4.7.8",
"lodash": "^4.17.21",
"openai": "^6.7.0",
"ora": "^9.0.0",
"pocketbase": "^0.26.3",
"prettier": "^3.7.4",
"prompts": "^2.4.2",
"semver": "^7.7.3",
"shared": "workspace:*",
"zod": "^4.3.5"
"zod": "^4.3.5",
"@lifeforge/log": "workspace:*"
},
"devDependencies": {
"@types/crypto-js": "^4.2.2",
@@ -34,4 +33,4 @@
"@types/semver": "^7.7.1",
"@types/tar": "^6.1.13"
}
}
}

View File

@@ -1,6 +1,8 @@
import chalk from 'chalk'
import { Command } from 'commander'
import logger from '@/utils/logger'
const LOGO = `
╭─────────────────────────────────────╮
│ │
@@ -131,8 +133,8 @@ export function configureHelp(program: Command): void {
// Custom error formatting
program.configureOutput({
outputError: (str, write) => {
write(chalk.red(`\n ❌ ${str.trim()}\n`))
outputError: str => {
logger.error(str.trim())
}
})
}

View File

@@ -1,8 +1,10 @@
import { LOG_LEVELS } from '@lifeforge/log'
import { Command, program } from 'commander'
import fs from 'fs'
import path from 'path'
import Logging, { LOG_LEVELS } from '../utils/logging'
import logger, { setLogLevel } from '@/utils/logger'
import { commands } from './commands'
import { configureHelp } from './help'
@@ -38,12 +40,24 @@ export function setupCLI(): void {
`Set log level (${LOG_LEVELS.join(', ')})`,
'info'
)
.hook('preAction', thisCommand => {
.hook('preAction', (thisCommand, actionCommand) => {
const level = thisCommand.opts().logLevel as (typeof LOG_LEVELS)[number]
if (LOG_LEVELS.includes(level)) {
Logging.setLevel(level)
setLogLevel(level)
}
// Build full command path (e.g., "modules list")
const commandPath: string[] = []
let cmd: Command | null = actionCommand
while (cmd && cmd !== program) {
commandPath.unshift(cmd.name())
cmd = cmd.parent
}
logger.debug(`Executing command "${commandPath.join(' ')}"`)
})
setupCommands(program)

View File

@@ -3,7 +3,7 @@ import weekOfYear from 'dayjs/plugin/weekOfYear'
import fs from 'fs'
import path from 'path'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
dayjs.extend(weekOfYear)
@@ -30,7 +30,7 @@ export default function createChangelogHandler(year?: string, week?: string) {
const currentWeek = Number(week) || dayjs().week()
if (!fs.existsSync(CHANGELOG_PATH)) {
Logging.error(`Changelog directory not found at path: ${CHANGELOG_PATH}`)
logger.error(`Changelog directory not found at path: ${CHANGELOG_PATH}`)
process.exit(1)
}
@@ -43,7 +43,7 @@ export default function createChangelogHandler(year?: string, week?: string) {
const filePath = `${yearPath}/${String(currentWeek).padStart(2, '0')}.mdx`
if (fs.existsSync(filePath)) {
Logging.error(
logger.error(
`Changelog file for year ${targetYear} week ${currentWeek} already exists at path: ${filePath}`
)
process.exit(1)
@@ -51,5 +51,5 @@ export default function createChangelogHandler(year?: string, week?: string) {
fs.writeFileSync(filePath, boilerPlate)
Logging.success(`Created changelog file at path: ${filePath}`)
logger.success(`Created changelog file at path: ${filePath}`)
}

View File

@@ -1,3 +1,4 @@
import chalk from 'chalk'
import fs from 'fs'
import os from 'os'
import path from 'path'
@@ -5,7 +6,7 @@ import path from 'path'
import { PB_BINARY_PATH, PB_DIR } from '@/constants/db'
import executeCommand from '@/utils/commands'
import { isDockerMode } from '@/utils/helpers'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
const PB_VERSION = '0.35.0'
@@ -16,13 +17,13 @@ const PB_VERSION = '0.35.0'
export async function downloadPocketBaseBinary(): Promise<void> {
// Skip in Docker mode - binary is provided by the container
if (isDockerMode()) {
Logging.debug('Docker mode detected, skipping binary download')
logger.debug('Docker mode detected, skipping binary download')
return
}
if (fs.existsSync(PB_BINARY_PATH)) {
Logging.debug('PocketBase binary already exists, skipping download')
logger.debug('PocketBase binary already exists, skipping download')
return
}
@@ -42,7 +43,7 @@ export async function downloadPocketBaseBinary(): Promise<void> {
osName = 'windows'
break
default:
Logging.actionableError(
logger.actionableError(
`Unsupported platform: ${platform}`,
'PocketBase supports darwin, linux, and windows'
)
@@ -62,7 +63,7 @@ export async function downloadPocketBaseBinary(): Promise<void> {
archName = 'amd64'
break
default:
Logging.actionableError(
logger.actionableError(
`Unsupported architecture: ${arch}`,
'PocketBase supports arm64 and amd64'
)
@@ -71,8 +72,8 @@ export async function downloadPocketBaseBinary(): Promise<void> {
const downloadUrl = `https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_${osName}_${archName}.zip`
Logging.info(
`Downloading PocketBase ${Logging.highlight(`v${PB_VERSION}`)} for ${osName}/${archName}...`
logger.info(
`Downloading PocketBase ${chalk.blue(`v${PB_VERSION}`)} for ${osName}/${archName}...`
)
try {
@@ -91,7 +92,7 @@ export async function downloadPocketBaseBinary(): Promise<void> {
fs.writeFileSync(zipPath, Buffer.from(arrayBuffer))
Logging.debug('Download complete, extracting...')
logger.debug('Download complete, extracting...')
// Extract using unzip command
executeCommand(`unzip -o "${zipPath}" -d "${PB_DIR}"`, {
@@ -101,7 +102,7 @@ export async function downloadPocketBaseBinary(): Promise<void> {
// Clean up zip file and unnecessary files
fs.unlinkSync(zipPath)
const changelogPath = path.join(PB_DIR, 'CHANGELogging.md')
const changelogPath = path.join(PB_DIR, 'CHANGElogger.md')
const licensePath = path.join(PB_DIR, 'LICENSE.md')
@@ -113,11 +114,9 @@ export async function downloadPocketBaseBinary(): Promise<void> {
fs.chmodSync(PB_BINARY_PATH, 0o755)
}
Logging.success(
`Downloaded PocketBase ${Logging.highlight(`v${PB_VERSION}`)}`
)
logger.success(`Downloaded PocketBase ${chalk.blue(`v${PB_VERSION}`)}`)
} catch (error) {
Logging.actionableError(
logger.actionableError(
`Failed to download PocketBase: ${error instanceof Error ? error.message : 'Unknown error'}`,
'Check your internet connection and try again'
)

View File

@@ -1,8 +1,9 @@
import chalk from 'chalk'
import fs from 'fs'
import { PB_BINARY_PATH, PB_DATA_DIR, PB_KWARGS } from '@/constants/db'
import executeCommand from '@/utils/commands'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
/**
* Validates that PocketBase data directory doesn't already exist
@@ -13,8 +14,8 @@ export function validatePocketBaseNotInitialized(
const pbInitialized = fs.existsSync(PB_DATA_DIR)
if (pbInitialized && exitOnFailure) {
Logging.actionableError(
`PocketBase is already initialized in ${Logging.highlight(PB_DATA_DIR)}, aborting.`,
logger.actionableError(
`PocketBase is already initialized in ${chalk.blue(PB_DATA_DIR)}, aborting.`,
'If you want to re-initialize, please remove the existing pb_data folder in the database directory.'
)
process.exit(1)
@@ -30,7 +31,7 @@ export function createPocketBaseSuperuser(
email: string,
password: string
): void {
Logging.debug(`Creating superuser with email ${Logging.highlight(email)}...`)
logger.debug(`Creating superuser with email ${chalk.blue(email)}...`)
try {
const result = executeCommand(
@@ -45,9 +46,9 @@ export function createPocketBaseSuperuser(
throw new Error(result.replace(/^Error:\s*/, ''))
}
Logging.success('Created superuser')
logger.success('Created superuser')
} catch (error) {
Logging.actionableError(
logger.actionableError(
`Failed to create superuser: ${error instanceof Error ? error.message : 'Unknown error'}`,
'Check your PocketBase configuration and try again'
)

View File

@@ -1,6 +1,8 @@
import { LOG_LEVELS } from '@lifeforge/log'
import { PB_BINARY_PATH, PB_KWARGS } from '@/constants/db'
import executeCommand from '@/utils/commands'
import Logging, { LEVEL_ORDER } from '@/utils/logging'
import logger from '@/utils/logger'
/**
* Runs `pocketbase migrate up` to apply all pending migrations in the migrations directory.
@@ -11,11 +13,14 @@ import Logging, { LEVEL_ORDER } from '@/utils/logging'
* @throws Error if the migration command fails
*/
export default function applyStagedMigration(): void {
Logging.debug('Applying pending migrations...')
logger.debug('Applying pending migrations...')
executeCommand(`${PB_BINARY_PATH} migrate up ${PB_KWARGS.join(' ')}`, {
stdio: Logging.level > LEVEL_ORDER.debug ? 'pipe' : 'inherit'
stdio:
LOG_LEVELS.indexOf(logger.level) > LOG_LEVELS.indexOf('debug')
? 'pipe'
: 'inherit'
})
Logging.debug('Migrations applied successfully')
logger.debug('Migrations applied successfully')
}

View File

@@ -1,6 +1,6 @@
import path from 'path'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import { getSchemaFiles } from '../../utils'
@@ -45,7 +45,7 @@ export default async function buildIdToNameMap(
}
}
Logging.debug(`Built ID-to-name map with ${idToNameMap.size} collections`)
logger.debug(`Built ID-to-name map with ${idToNameMap.size} collections`)
return idToNameMap
}

View File

@@ -3,6 +3,7 @@ import fs from 'fs'
import prettier from 'prettier'
import { PB_BINARY_PATH, PB_KWARGS } from '@/constants/db'
import logger from '@/utils/logger'
import { PRETTIER_OPTIONS } from '../../utils'

View File

@@ -1,4 +1,6 @@
import Logging from '@/utils/logging'
import chalk from 'chalk'
import logger from '@/utils/logger'
import applyMigrations from './applyMigrations'
import createSingleMigration from './createSingleMigration'
@@ -41,7 +43,7 @@ export default async function stageMigration(
}>,
idToNameMap: Map<string, string>
) {
Logging.debug(`Phase ${index + 1}: Creating ${phase} migrations...`)
logger.debug(`Phase ${index + 1}: Creating ${phase} migrations...`)
const phaseFn = generateContentMap[phase]
@@ -52,15 +54,13 @@ export default async function stageMigration(
phaseFn(schema, idToNameMap)
)
Logging.debug(
`Created ${phase} migration for ${Logging.highlight(moduleName)}`
)
logger.debug(`Created ${phase} migration for ${chalk.blue(moduleName)}`)
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error)
throw new Error(
`Failed to create ${phase} migration for ${Logging.highlight(moduleName)}: ${errorMessage}`
`Failed to create ${phase} migration for ${chalk.blue(moduleName)}: ${errorMessage}`
)
}
}

View File

@@ -1,4 +1,6 @@
import Logging from '@/utils/logging'
import chalk from 'chalk'
import logger from '@/utils/logger'
import { listSchemaPaths } from './listSchemaPaths'
import { matchCollectionToModule } from './matchCollectionToModule'
@@ -25,20 +27,23 @@ export default async function buildModuleCollectionsMap(
const allModules = listSchemaPaths()
Logging.debug(
`Found ${Logging.highlight(String(allModules.length))} modules with schema files`
logger.debug(
`Found ${chalk.blue(String(allModules.length))} modules with schema files`
)
let matchedCount = 0
let unmatchedCount = 0
for (const collection of collections) {
const matchingModuleDir = await matchCollectionToModule(allModules, collection)
const matchingModuleDir = await matchCollectionToModule(
allModules,
collection
)
if (!matchingModuleDir) {
unmatchedCount++
Logging.debug(
`Collection ${Logging.highlight(collection.name as string)} has no matching module`
logger.debug(
`Collection ${chalk.blue(collection.name as string)} has no matching module`
)
continue
}
@@ -52,8 +57,8 @@ export default async function buildModuleCollectionsMap(
moduleCollectionsMap[matchingModuleDir].push(collection)
}
Logging.debug(
`Matched ${Logging.highlight(String(matchedCount))} collections to modules, ${unmatchedCount} unmatched`
logger.debug(
`Matched ${chalk.blue(String(matchedCount))} collections to modules, ${unmatchedCount} unmatched`
)
return moduleCollectionsMap

View File

@@ -1,4 +1,4 @@
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '@/utils/normalizePackage'
/**
@@ -33,7 +33,7 @@ export default function filterCollectionsMap(
: moduleCollectionsMap
if (targetModule && Object.keys(filteredModuleCollectionsMap).length === 0) {
Logging.error(`Module "${shortName}" not found or has no collections`)
logger.error(`Module "${shortName}" not found or has no collections`)
process.exit(1)
}

View File

@@ -1,4 +1,4 @@
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
/**
* PocketBase field definition with common properties.
@@ -64,7 +64,7 @@ function convertFieldToZodSchema(field: PocketBaseField): string | null {
const converter = FIELD_TYPE_MAPPING[field.type]
if (!converter) {
Logging.warn(
logger.warn(
`Unknown field type '${field.type}' for field '${field.name}'. Skipping.`
)

View File

@@ -1,5 +1,7 @@
import chalk from 'chalk'
import { isDockerMode } from '@/utils/helpers'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import getPBInstance from '@/utils/pocketbase'
/**
@@ -19,11 +21,11 @@ import getPBInstance from '@/utils/pocketbase'
* // Returns: [{ name: 'calendar__events', type: 'base', ... }, ...]
*/
export default async function getCollectionsFromPB() {
Logging.debug('Connecting to PocketBase...')
logger.debug('Connecting to PocketBase...')
const { pb, killPB } = await getPBInstance(!isDockerMode())
Logging.debug('Fetching all collections...')
logger.debug('Fetching all collections...')
const allCollections = await pb.collections.getFullList()
@@ -33,8 +35,8 @@ export default async function getCollectionsFromPB() {
collection => !collection.system
)
Logging.debug(
`Found ${Logging.highlight(String(allCollections.length))} collections, ${Logging.highlight(String(nonSystemCollections.length))} non-system`
logger.debug(
`Found ${chalk.blue(String(allCollections.length))} collections, ${chalk.blue(String(nonSystemCollections.length))} non-system`
)
return nonSystemCollections

View File

@@ -2,7 +2,7 @@ import fs from 'fs'
import path from 'path'
import { ROOT_DIR } from '@/constants/constants'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
/**
* Discovers all module directories that contain schema files.
@@ -38,7 +38,7 @@ export function listSchemaPaths(): string[] {
.flat()
.map(entry => path.dirname(entry).replace(/\/server$/, ''))
} catch (error) {
Logging.error(`Failed to read modules directory: ${error}`)
logger.error(`Failed to read modules directory: ${error}`)
process.exit(1)
}

View File

@@ -1,4 +1,4 @@
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import buildIdToNameMap from '../functions/migration-generation/buildIdToNameMap'
import stageMigration from '../functions/migration-generation/stageMigrations'
@@ -26,13 +26,13 @@ export async function generateMigrationsHandler(
await stageMigration(phase, Number(index), importedSchemas, idToNameMap)
}
Logging.success('Migrations generated successfully')
logger.success('Migrations generated successfully')
} catch (error) {
Logging.actionableError(
logger.actionableError(
'Migration script failed',
'Check the schema definitions and PocketBase configuration'
)
Logging.debug(
logger.debug(
`Error details: ${error instanceof Error ? error.message : String(error)}`
)
process.exit(1)

View File

@@ -1,4 +1,6 @@
import Logging from '@/utils/logging'
import chalk from 'chalk'
import logger from '@/utils/logger'
import buildModuleCollectionsMap from '../functions/schema-generation/buildModuleCollectionsMap'
import filterCollectionsMap from '../functions/schema-generation/filterCollectionsMap'
@@ -13,9 +15,7 @@ export async function generateSchemaHandler(
targetModule?: string
): Promise<void> {
if (targetModule) {
Logging.debug(
`Generating schema for module: ${Logging.highlight(targetModule)}`
)
logger.debug(`Generating schema for module: ${chalk.blue(targetModule)}`)
}
const collections = await getCollectionsFromPB()
@@ -27,7 +27,7 @@ export async function generateSchemaHandler(
const entries = Object.entries(filteredMap)
if (entries.length === 0) {
Logging.warn('No modules found to generate schemas for')
logger.warn('No modules found to generate schemas for')
return
}
@@ -36,8 +36,8 @@ export async function generateSchemaHandler(
entries.map(async ([modulePath, moduleCollections]) => {
const collectionCount = moduleCollections.length
Logging.debug(
`Writing ${Logging.highlight(String(collectionCount))} collections for module ${Logging.dim(modulePath)}`
logger.debug(
`Writing ${chalk.blue(String(collectionCount))} collections for module ${chalk.dim(modulePath)}`
)
await writeFormattedFile(
@@ -54,7 +54,7 @@ export async function generateSchemaHandler(
})
)
Logging.success(
`Generated schemas for ${Logging.highlight(String(entries.length))} modules`
logger.success(
`Generated schemas for ${chalk.blue(String(entries.length))} modules`
)
}

View File

@@ -1,5 +1,5 @@
import { getEnvVars } from '@/utils/helpers'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import { checkRunningPBInstances } from '@/utils/pocketbase'
import { downloadPocketBaseBinary } from '../functions/database-initialization/download-pocketbase'
@@ -25,5 +25,5 @@ export async function initializeDatabaseHandler() {
generateMigrationsHandler()
Logging.success('Database initialized successfully')
logger.success('Database initialized successfully')
}

View File

@@ -3,7 +3,7 @@ import path from 'path'
import prettier from 'prettier'
import { ROOT_DIR } from '@/constants/constants'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import { PRETTIER_OPTIONS } from './constants'
@@ -27,7 +27,7 @@ export async function writeFormattedFile(
fs.writeFileSync(filePath, formattedContent)
} catch (error) {
Logging.error(`Failed to write file ${filePath}: ${error}`)
logger.error(`Failed to write file ${filePath}: ${error}`)
throw error
}
}
@@ -64,7 +64,7 @@ export function getSchemaFiles(targetModule?: string): string[] {
.map((schemaPath: string) => getModuleName(schemaPath))
.join(', ')
Logging.error(
logger.error(
`Module "${targetModule}" not found. Available modules: ${availableModules}`
)
process.exit(1)

View File

@@ -1,10 +1,11 @@
import chalk from 'chalk'
import { execSync } from 'child_process'
import fs from 'fs'
import path from 'path'
import { PB_BINARY_PATH, PB_KWARGS, PB_MIGRATIONS_DIR } from '@/constants/db'
import { isDockerMode } from '@/utils/helpers'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
/**
* Cleans up old migrations.
@@ -14,7 +15,7 @@ export async function cleanupOldMigrations(
targetModule?: string
): Promise<void> {
try {
Logging.debug('Cleaning up old migrations directory...')
logger.debug('Cleaning up old migrations directory...')
if (!targetModule) {
fs.rmSync(PB_MIGRATIONS_DIR, { recursive: true, force: true })
@@ -49,8 +50,8 @@ export async function cleanupOldMigrations(
file.endsWith(`_${targetModule}.js`)
).length
Logging.debug(
`Removed ${Logging.highlight(String(removedCount))} old migrations for module ${Logging.highlight(targetModule)}.`
logger.debug(
`Removed ${chalk.blue(String(removedCount))} old migrations for module ${chalk.blue(targetModule)}.`
)
}
} catch {

View File

@@ -1,9 +1,10 @@
import chalk from 'chalk'
import fs from 'fs'
import { PB_BINARY_PATH, PB_KWARGS } from '@/constants/db'
import executeCommand from '@/utils/commands'
import { checkPortInUse, delay, killExistingProcess } from '@/utils/helpers'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
/**
* Service command configurations
@@ -24,7 +25,7 @@ export const SERVICE_COMMANDS: Record<string, ServiceConfig> = {
}
if (checkPortInUse(8090)) {
Logging.actionableError(
logger.actionableError(
'No Pocketbase instance found running, but port 8090 is already in use.',
'Please free up the port. Are you using the port for another application? (e.g., port forwarding, etc.)'
)
@@ -32,9 +33,9 @@ export const SERVICE_COMMANDS: Record<string, ServiceConfig> = {
}
if (!fs.existsSync(PB_BINARY_PATH)) {
Logging.actionableError(
logger.actionableError(
`PocketBase binary does not exist: ${PB_BINARY_PATH}`,
`Please run "${Logging.highlight('bun forge db init')}" to initialize the database`
`Please run "${chalk.blue('bun forge db init')}" to initialize the database`
)
process.exit(1)
}
@@ -57,7 +58,7 @@ export const SERVICE_COMMANDS: Record<string, ServiceConfig> = {
const PORT = process.env.PORT || '3636'
if (checkPortInUse(Number(PORT))) {
Logging.actionableError(
logger.actionableError(
`Port ${PORT} is already in use.`,
'Please free up the port or set a different PORT environment variable.'
)

View File

@@ -3,7 +3,7 @@ import concurrently from 'concurrently'
import { PROJECTS } from '@/commands/project/constants/projects'
import executeCommand from '@/utils/commands'
import { getEnvVars } from '@/utils/helpers'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import { SERVICE_COMMANDS } from '../config/commands'
import getConcurrentServices from './getConcurrentServices'
@@ -61,11 +61,11 @@ export async function startAllServices(): Promise<void> {
prefixColors: ['cyan', 'green', 'magenta']
})
} catch (error) {
Logging.actionableError(
logger.actionableError(
'Failed to start all services',
'Ensure PocketBase is properly configured and all dependencies are installed'
)
Logging.debug(`Error details: ${error}`)
logger.debug(`Error details: ${error}`)
process.exit(1)
}
}

View File

@@ -1,4 +1,6 @@
import Logging from '@/utils/logging'
import chalk from 'chalk'
import logger from '@/utils/logger'
import SERVICES from '../constants/services'
import {
@@ -8,31 +10,31 @@ import {
export function devHandler(service: string, extraArgs: string[] = []): void {
if (!service) {
Logging.info('Starting all services...')
logger.info('Starting all services...')
startAllServices()
return
}
if (!SERVICES.includes(service as (typeof SERVICES)[number])) {
Logging.error(`Unknown service: ${service}`)
logger.error(`Unknown service: ${service}`)
process.exit(1)
}
Logging.info(`Starting ${Logging.highlight(service)} service...`)
logger.info(`Starting ${chalk.blue(service)} service...`)
if (extraArgs.length > 0) {
Logging.debug(`Extra arguments: ${extraArgs.join(' ')}`)
logger.debug(`Extra arguments: ${extraArgs.join(' ')}`)
}
try {
startSingleService(service, extraArgs)
} catch (error) {
Logging.actionableError(
`Failed to start ${Logging.highlight(service)} service`,
logger.actionableError(
`Failed to start ${chalk.blue(service)} service`,
'Check if all required dependencies are installed and environment variables are set'
)
Logging.debug(`Error details: ${error}`)
logger.debug(`Error details: ${error}`)
process.exit(1)
}
}

View File

@@ -1,6 +1,6 @@
import { ROOT_DIR } from '@/constants/constants'
import executeCommand from '@/utils/commands'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
const REQUIRED_CONTAINERS = [
'lifeforge-db',
@@ -8,8 +8,6 @@ const REQUIRED_CONTAINERS = [
'lifeforge-client'
]
/**
* Checks if Docker is available and LifeForge containers are initialized.
* @returns true if Docker is ready, false otherwise
@@ -19,7 +17,7 @@ export default function checkDockerReady(): boolean {
// Check if Docker is running
executeCommand('docker info', { stdio: 'pipe' })
} catch {
Logging.actionableError(
logger.actionableError(
'Docker is not running',
'Start Docker Desktop or the Docker daemon first'
)
@@ -36,13 +34,12 @@ export default function checkDockerReady(): boolean {
const runningContainers = result.split('\n').map(name => name.trim())
const missingContainers = REQUIRED_CONTAINERS
.filter(
const missingContainers = REQUIRED_CONTAINERS.filter(
container => !runningContainers.includes(container)
)
if (missingContainers.length > 0) {
Logging.actionableError(
logger.actionableError(
`LifeForge containers not running: ${missingContainers.join(', ')}`,
'Run "docker compose up -d" first to start the containers'
)
@@ -50,7 +47,7 @@ export default function checkDockerReady(): boolean {
return false
}
} catch {
Logging.actionableError(
logger.actionableError(
'Failed to check Docker containers',
'Ensure Docker is running and docker-compose.yaml exists'
)

View File

@@ -1,6 +1,6 @@
import { ROOT_DIR } from '@/constants/constants'
import executeCommand from '@/utils/commands'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import checkDockerReady from '../functions/checkDockerReady'
@@ -28,29 +28,29 @@ export async function migrateHandler(options: MigrateOptions): Promise<void> {
const execOptions = { cwd: ROOT_DIR, stdio: 'inherit' as const }
try {
Logging.info('Starting Docker migration sequence...')
logger.info('Starting Docker migration sequence...')
// Step 1: Stop server
Logging.info('Stopping server...')
logger.info('Stopping server...')
executeCommand('docker compose stop server', execOptions)
// Step 2: Run migrations (unless skipped)
if (!options.skipMigrations) {
Logging.info('Regenerating and applying migrations...')
logger.info('Regenerating and applying migrations...')
executeCommand('docker compose run --rm db-init', execOptions)
} else {
Logging.info('Skipping migrations (--skip-migrations)')
logger.info('Skipping migrations (--skip-migrations)')
}
// Step 3: Restart server
Logging.info('Restarting server...')
logger.info('Restarting server...')
executeCommand('docker compose up -d server', execOptions)
Logging.success('Docker migration complete!')
Logging.info('Refresh the browser to load updated modules')
logger.success('Docker migration complete!')
logger.info('Refresh the browser to load updated modules')
} catch (error) {
Logging.error('Docker migration failed')
Logging.debug(
logger.error('Docker migration failed')
logger.debug(
`Error details: ${error instanceof Error ? error.message : String(error)}`
)
process.exit(1)

View File

@@ -1,3 +1,4 @@
import fs from 'fs'
import path from 'path'
import logger from '@/utils/logger'

View File

@@ -1,8 +1,8 @@
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import getPBInstance from '@/utils/pocketbase'
async function ensureLocaleNotInUse(shortName: string) {
Logging.debug('Checking if locale is in use...')
logger.debug('Checking if locale is in use...')
const { pb, killPB } = await getPBInstance()
@@ -11,7 +11,7 @@ async function ensureLocaleNotInUse(shortName: string) {
killPB?.()
if (user.language === shortName) {
Logging.actionableError(
logger.actionableError(
`Cannot uninstall locale "${shortName}"`,
'This language is currently selected. Change your language first.'
)

View File

@@ -1,4 +1,4 @@
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '@/utils/normalizePackage'
import { listLocalesWithMeta } from './listLocales'
@@ -7,7 +7,7 @@ function getPackagesToCheck(langCode?: string) {
const localePackages = listLocalesWithMeta()
if (!localePackages.length) {
Logging.info('No locales installed')
logger.info('No locales installed')
process.exit(0)
}
@@ -19,7 +19,7 @@ function getPackagesToCheck(langCode?: string) {
: localePackages
if (!packagesToCheck?.length) {
Logging.actionableError(
logger.actionableError(
`Locale "${langCode}" is not installed`,
'Run "bun forge locales list" to see installed locales'
)

View File

@@ -1,4 +1,6 @@
import Logging from '@/utils/logging'
import chalk from 'chalk'
import logger from '@/utils/logger'
import { getRegistryUrl } from '@/utils/registry'
interface LocaleUpgrade {
@@ -47,14 +49,14 @@ async function getUpgrades(
}
if (!upgrades.length) {
Logging.info('All locales are up to date')
logger.info('All locales are up to date')
process.exit(0)
}
Logging.info('Available upgrades:')
logger.info('Available upgrades:')
upgrades.forEach(u =>
Logging.print(
` ${Logging.highlight(u.name)}: ${u.current}${Logging.green(u.latest)}`
logger.print(
` ${chalk.blue(u.name)}: ${u.current}${chalk.green(u.latest)}`
)
)

View File

@@ -2,6 +2,7 @@ import fs from 'fs'
import path from 'path'
import { LOCALES_DIR } from '@/constants/constants'
import logger from '@/utils/logger'
export function listLocales(): string[] {
return fs.readdirSync(LOCALES_DIR).filter(dir => {

View File

@@ -1,4 +1,6 @@
import Logging from '@/utils/logging'
import chalk from 'chalk'
import logger from '@/utils/logger'
import getPBInstance from '@/utils/pocketbase'
import { listLocales } from './listLocales'
@@ -7,7 +9,7 @@ async function setFirstLangInDB(shortName: string) {
const installedLocales = listLocales()
if (installedLocales.length === 1) {
Logging.debug('This is the first locale, setting as default...')
logger.debug('This is the first locale, setting as default...')
const { pb, killPB } = await getPBInstance()
@@ -15,7 +17,7 @@ async function setFirstLangInDB(shortName: string) {
await pb.collection('users').update(user.id, { language: shortName })
Logging.info(`Set ${Logging.highlight(shortName)} as default language`)
logger.info(`Set ${chalk.blue(shortName)} as default language`)
killPB?.()
}
}

View File

@@ -1,7 +1,7 @@
import fs from 'fs'
import path from 'path'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
interface LocalePackageJson {
name?: string
@@ -55,11 +55,11 @@ export function validateLocaleStructure(localePath: string) {
}
if (errors.length > 0) {
Logging.error('Locale validation failed:')
errors.forEach(err => Logging.error(` - ${err}`))
logger.error('Locale validation failed:')
errors.forEach(err => logger.error(` - ${err}`))
process.exit(1)
}
warnings.forEach(warn => Logging.warn(` - ${warn}`))
warnings.forEach(warn => logger.warn(` - ${warn}`))
}

View File

@@ -1,8 +1,9 @@
import chalk from 'chalk'
import fs from 'fs'
import { installPackage } from '@/utils/commands'
import initGitRepository from '@/utils/initGitRepository'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '@/utils/normalizePackage'
import setFirstLangInDB from '../functions/setFirstLangInDB'
@@ -14,15 +15,15 @@ export async function installLocaleHandler(langCode: string): Promise<void> {
)
if (!/^@lifeforge\/lang-[a-z]{2}(-[A-Z]{2})?$/i.test(fullName)) {
Logging.actionableError(
`Invalid locale name: ${Logging.highlight(langCode)}`,
logger.actionableError(
`Invalid locale name: ${chalk.blue(langCode)}`,
'Locale names should follow the format "xx" or "xx-XX", where "xx" is a two-letter language code and "XX" is a two-letter country code.'
)
process.exit(1)
}
if (fs.existsSync(targetDir)) {
Logging.actionableError(
logger.actionableError(
`Locale already exists at locales/${shortName}`,
`Remove it first with: bun forge locales uninstall ${shortName}`
)
@@ -30,7 +31,7 @@ export async function installLocaleHandler(langCode: string): Promise<void> {
process.exit(1)
}
Logging.info(`Installing ${Logging.highlight(fullName)}...`)
logger.info(`Installing ${chalk.blue(fullName)}...`)
try {
installPackage(fullName, targetDir)
@@ -38,10 +39,10 @@ export async function installLocaleHandler(langCode: string): Promise<void> {
await setFirstLangInDB(shortName)
Logging.success(`Installed ${Logging.highlight(fullName)}`)
logger.success(`Installed ${chalk.blue(fullName)}`)
} catch (error) {
Logging.actionableError(
`Failed to install ${Logging.highlight(fullName)}`,
logger.actionableError(
`Failed to install ${chalk.blue(fullName)}`,
'Make sure the locale exists in the registry'
)
throw error

View File

@@ -1,4 +1,6 @@
import Logging from '@/utils/logging'
import chalk from 'chalk'
import logger from '@/utils/logger'
import { listLocalesWithMeta } from '../functions/listLocales'
@@ -6,19 +8,19 @@ export function listLocalesHandler(): void {
const locales = listLocalesWithMeta()
if (locales.length === 0) {
Logging.info('No language packs installed')
Logging.info(
logger.info('No language packs installed')
logger.info(
'Use "bun forge locales install <lang>" to install a language pack'
)
return
}
Logging.info(`Installed language packs (${locales.length}):`)
logger.info(`Installed language packs (${locales.length}):`)
for (const locale of locales.sort((a, b) => a.name.localeCompare(b.name))) {
Logging.print(
` ${Logging.highlight(locale.name)} - ${locale.displayName} (v.${locale.version})`
logger.print(
` ${chalk.blue(locale.name)} - ${locale.displayName} (v.${locale.version})`
)
}
}

View File

@@ -1,10 +1,11 @@
import chalk from 'chalk'
import fs from 'fs'
import bumpPackageVersion, {
revertPackageVersion
} from '@/utils/bumpPackageVersion'
import executeCommand from '@/utils/commands'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '@/utils/normalizePackage'
import { validateMaintainerAccess } from '../../../utils/github-cli'
@@ -18,7 +19,7 @@ export async function publishLocaleHandler(
const { fullName, targetDir } = normalizePackage(langCode, 'locale')
if (!fs.existsSync(targetDir)) {
Logging.actionableError(
logger.actionableError(
`Locale "${langCode}" not found in locales/`,
'Run "bun forge locales list" to see available locales'
)
@@ -26,23 +27,23 @@ export async function publishLocaleHandler(
return
}
Logging.info('Validating locale structure...')
logger.info('Validating locale structure...')
validateLocaleStructure(targetDir)
const auth = await checkAuth()
if (options?.official) {
Logging.info('Validating maintainer access...')
logger.info('Validating maintainer access...')
validateMaintainerAccess(auth.username ?? '')
}
const { oldVersion, newVersion } = bumpPackageVersion(targetDir)
Logging.info(
`Bumped version: ${Logging.highlight(oldVersion)}${Logging.highlight(newVersion)}`
logger.info(
`Bumped version: ${chalk.blue(oldVersion)}${chalk.blue(newVersion)}`
)
Logging.info(`Publishing ${Logging.highlight(fullName)}...`)
logger.info(`Publishing ${chalk.blue(fullName)}...`)
try {
executeCommand(`npm publish --registry ${getRegistryUrl()}`, {
@@ -50,12 +51,12 @@ export async function publishLocaleHandler(
stdio: 'inherit'
})
Logging.success(`Published ${Logging.highlight(fullName)}`)
logger.success(`Published ${chalk.blue(fullName)}`)
} catch (error) {
revertPackageVersion(targetDir, oldVersion)
Logging.actionableError(
`Failed to publish ${Logging.highlight(fullName)}`,
logger.actionableError(
`Failed to publish ${chalk.blue(fullName)}`,
'Check if you are properly authenticated with the registry'
)

View File

@@ -1,9 +1,10 @@
import chalk from 'chalk'
import fs from 'fs'
import path from 'path'
import { ROOT_DIR } from '@/constants/constants'
import { bunInstall } from '@/utils/commands'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '@/utils/normalizePackage'
import { findPackageName, removeDependency } from '@/utils/packageJson'
@@ -18,7 +19,7 @@ export async function uninstallLocaleHandler(langCode: string): Promise<void> {
const found = findPackageName(fullName)
if (!found) {
Logging.actionableError(
logger.actionableError(
`Locale "${shortName}" is not installed`,
'Run "bun forge locales list" to see installed locales'
)
@@ -28,7 +29,7 @@ export async function uninstallLocaleHandler(langCode: string): Promise<void> {
await ensureLocaleNotInUse(shortName)
Logging.info(`Uninstalling ${Logging.highlight(fullName)}...`)
logger.info(`Uninstalling ${chalk.blue(fullName)}...`)
const symlinkPath = path.join(ROOT_DIR, 'node_modules', fullName)
@@ -39,5 +40,5 @@ export async function uninstallLocaleHandler(langCode: string): Promise<void> {
bunInstall()
Logging.success(`Uninstalled ${Logging.highlight(fullName)}`)
logger.success(`Uninstalled ${chalk.blue(fullName)}`)
}

View File

@@ -1,6 +1,8 @@
import chalk from 'chalk'
import { bunInstall, installPackage } from '@/utils/commands'
import { confirmAction } from '@/utils/helpers'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '@/utils/normalizePackage'
import { checkAuth } from '../../../utils/registry'
@@ -19,7 +21,7 @@ export async function upgradeLocaleHandler(langCode?: string): Promise<void> {
let upgradedCount = 0
for (const upgrade of upgrades) {
Logging.info(`Upgrading ${Logging.highlight(upgrade.name)}...`)
logger.info(`Upgrading ${chalk.blue(upgrade.name)}...`)
try {
installPackage(
@@ -27,18 +29,16 @@ export async function upgradeLocaleHandler(langCode?: string): Promise<void> {
normalizePackage(upgrade.name, 'locale').targetDir
)
Logging.success(`Upgraded ${Logging.highlight(upgrade.name)}`)
logger.success(`Upgraded ${chalk.blue(upgrade.name)}`)
upgradedCount++
} catch (error) {
Logging.error(
`Failed to upgrade ${Logging.highlight(upgrade.name)}: ${error}`
)
logger.error(`Failed to upgrade ${chalk.blue(upgrade.name)}: ${error}`)
}
}
if (upgradedCount > 0) {
bunInstall()
Logging.success(
logger.success(
`Upgraded ${upgradedCount} locale${upgradedCount > 1 ? 's' : ''}`
)
}

View File

@@ -1,7 +1,7 @@
import fs from 'fs'
import path from 'path'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import { readRootPackageJson } from '@/utils/packageJson'
import normalizePackage from '../../../utils/normalizePackage'
@@ -47,7 +47,7 @@ export default function listModules(
}
if (exitIfNoModule && Object.keys(modules).length === 0) {
Logging.info('No @lifeforge/* modules found. Exiting...')
logger.info('No @lifeforge/* modules found. Exiting...')
process.exit(0)
}

View File

@@ -1,4 +1,5 @@
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import chalk from 'chalk'
/**
* Extract username and module name from a package name format
@@ -18,8 +19,8 @@ export function parsePackageName(
if (!withoutScope.includes('--')) {
if (!isLibModule) {
Logging.actionableError(
`Invalid package name: ${Logging.highlight(packageName)}`,
logger.actionableError(
`Invalid package name: ${chalk.blue(packageName)}`,
'Package name must include "--" separator (e.g., username--module-name)'
)
process.exit(1)

View File

@@ -1,6 +1,6 @@
import prompts from 'prompts'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
export async function promptModuleCategory(): Promise<string> {
const response = await prompts(
@@ -19,7 +19,7 @@ export async function promptModuleCategory(): Promise<string> {
},
{
onCancel: () => {
Logging.error('Module creation cancelled by user.')
logger.error('Module creation cancelled by user.')
process.exit(0)
}
}

View File

@@ -3,7 +3,7 @@ import prompts from 'prompts'
import z from 'zod'
import { fetchAI } from '@/utils/ai'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
export async function promptModuleDescription(): Promise<{
en: string
@@ -26,7 +26,7 @@ export async function promptModuleDescription(): Promise<{
},
{
onCancel: () => {
Logging.error('Module creation cancelled by user.')
logger.error('Module creation cancelled by user.')
process.exit(0)
}
}
@@ -53,7 +53,7 @@ export async function promptModuleDescription(): Promise<{
})
if (!translationResponse) {
Logging.warn(
logger.warn(
"Failed to translate description. Please edit it manually in the module's localization files."
)
@@ -66,7 +66,7 @@ export async function promptModuleDescription(): Promise<{
}
for (const [key, value] of Object.entries(translationResponse)) {
Logging.debug(`Translated module description [${key}]: ${value}`)
logger.debug(`Translated module description [${key}]: ${value}`)
}
return {

View File

@@ -2,7 +2,7 @@ import prompts from 'prompts'
import z from 'zod'
import { fetchAI } from '@/utils/ai'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import listModules from '../listModules'
@@ -34,7 +34,7 @@ export async function promptForModuleName(moduleName?: string): Promise<{
},
{
onCancel: () => {
Logging.error('Module creation cancelled by user.')
logger.error('Module creation cancelled by user.')
process.exit(0)
}
}
@@ -64,7 +64,7 @@ export async function promptForModuleName(moduleName?: string): Promise<{
})
if (!translationResponse) {
Logging.warn(
logger.warn(
"Failed to translate module name. Please edit it manually in the module's localization files."
)
@@ -77,7 +77,7 @@ export async function promptForModuleName(moduleName?: string): Promise<{
}
for (const [key, value] of Object.entries(translationResponse)) {
Logging.debug(`Translated module name [${key}]: ${value}`)
logger.debug(`Translated module name [${key}]: ${value}`)
}
return {

View File

@@ -2,9 +2,10 @@ import fs from 'fs'
import prompts from 'prompts'
import { ROOT_DIR } from '@/constants/constants'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import { AVAILABLE_TEMPLATE_MODULE_TYPES } from '../../handlers/createModuleHandler'
import chalk from 'chalk'
export async function promptModuleType(): Promise<
keyof typeof AVAILABLE_TEMPLATE_MODULE_TYPES
@@ -16,14 +17,14 @@ export async function promptModuleType(): Promise<
message: 'Select the type of module to create:',
choices: Object.entries(AVAILABLE_TEMPLATE_MODULE_TYPES).map(
([value, title]) => ({
title: `${title} ${Logging.dim(`(${value})`)}`,
title: `${title} ${chalk.dim(`(${value})`)}`,
value
})
)
},
{
onCancel: () => {
Logging.error('Module creation cancelled by user.')
logger.error('Module creation cancelled by user.')
process.exit(0)
}
}
@@ -38,26 +39,26 @@ export function checkModuleTypeAvailability(
const templateDir = `${ROOT_DIR}/tools/src/templates/${moduleType}`
if (!fs.existsSync(templateDir)) {
Logging.error(
logger.error(
`Template for module type "${moduleType}" does not exist at path: ${templateDir}`
)
process.exit(1)
}
Logging.debug(
logger.debug(
`Template for module type "${moduleType}" found at path: ${templateDir}`
)
const files = fs.readdirSync(templateDir)
if (files.length === 0) {
Logging.error(
logger.error(
`Template directory for module type "${moduleType}" is empty at path: ${templateDir}`
)
process.exit(1)
}
Logging.debug(
logger.debug(
`Template for module type "${moduleType}" is available and ready to use.`
)
}

View File

@@ -1,26 +1,22 @@
import axios from 'axios'
import ora from 'ora'
import prompts from 'prompts'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
export default async function selectIcon(): Promise<string> {
const iconCollections = (
await axios.get<
Record<
string,
{
name: string
total: number
categories?: Record<string, string[]>
uncategorized?: string[]
}
>
>('https://api.iconify.design/collections')
).data
const iconCollections = (await fetch(
'https://api.iconify.design/collections'
).then(res => res.json())) as Record<
string,
{
name: string
total: number
categories?: Record<string, string[]>
uncategorized?: string[]
}
>
if (!iconCollections) {
Logging.error('Failed to fetch icon collections from Iconify.')
logger.error('Failed to fetch icon collections from Iconify.')
process.exit(1)
}
@@ -55,39 +51,32 @@ export default async function selectIcon(): Promise<string> {
)
if (!moduleIconCollection.iconCollection) {
Logging.error('Please select a valid icon set.')
logger.error('Please select a valid icon set.')
continue
}
if (cancelled) {
Logging.error('Icon selection cancelled by user.')
logger.error('Icon selection cancelled by user.')
process.exit(1)
}
const spinner2 = ora('Fetching icons from Iconify...').start()
let icons: {
[key: string]: string[]
}[] = []
try {
icons = (
await axios(
`https://api.iconify.design/collection?prefix=${moduleIconCollection.iconCollection}`
)
).data
icons = await fetch(
`https://api.iconify.design/collection?prefix=${moduleIconCollection.iconCollection}`
).then(res => res.json())
} catch (error) {
spinner2.fail('Failed to fetch icons from Iconify.')
Logging.error(
logger.error(
`Error fetching icons for collection ${moduleIconCollection.iconCollection}: ${error}`
)
continue
}
spinner2.stop()
if (!icons) {
Logging.error('Failed to fetch icons from Iconify.')
logger.error('Failed to fetch icons from Iconify.')
process.exit(1)
}
@@ -167,7 +156,7 @@ export default async function selectIcon(): Promise<string> {
)
if (cancelled2) {
Logging.error('Icon selection cancelled by user.')
logger.error('Icon selection cancelled by user.')
process.exit(1)
}

View File

@@ -3,7 +3,7 @@ import _ from 'lodash'
import path from 'path'
import { SERVER_ROUTES_DIR } from '@/constants/constants'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '../../../../utils/normalizePackage'
import listModules from '../listModules'
@@ -28,7 +28,7 @@ export default function generateRouteRegistry() {
})
.join('\n')
let registry = `// AUTO-GENERATED - DO NOT EDIT
const registry = `// AUTO-GENERATED - DO NOT EDIT
import { forgeRouter } from '@functions/routes'
const appRoutes = forgeRouter({
@@ -40,5 +40,5 @@ export default appRoutes
fs.writeFileSync(SERVER_ROUTES_DIR, registry)
Logging.success('Generated route registry')
logger.success('Generated route registry')
}

View File

@@ -3,7 +3,7 @@ import _ from 'lodash'
import path from 'path'
import { SERVER_SCHEMA_DIR } from '@/constants/constants'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '../../../../utils/normalizePackage'
import listModules from '../listModules'
@@ -41,5 +41,5 @@ export default SCHEMAS
fs.writeFileSync(SERVER_SCHEMA_DIR, registry)
Logging.success('Generated schema registry')
logger.success('Generated schema registry')
}

View File

@@ -3,6 +3,7 @@ import Handlebars from 'handlebars'
import _ from 'lodash'
import { ROOT_DIR } from '@/constants/constants'
import logger from '@/utils/logger'
import type { AVAILABLE_TEMPLATE_MODULE_TYPES } from '../../handlers/createModuleHandler'

View File

@@ -3,7 +3,7 @@ import path from 'path'
import z from 'zod'
import { validateMaintainerAccess } from '@/utils/github-cli'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import { checkAuth } from '@/utils/registry'
export default async function validateModuleAuthor(modulePath: string) {
@@ -17,7 +17,7 @@ export default async function validateModuleAuthor(modulePath: string) {
)
if (!packageJson.success) {
Logging.actionableError(
logger.actionableError(
'Invalid package.json',
'Please fix the package.json file'
)
@@ -32,7 +32,7 @@ export default async function validateModuleAuthor(modulePath: string) {
if (usernamePrefix === 'lifeforge') {
validateMaintainerAccess(auth.username || '')
} else {
Logging.actionableError(
logger.actionableError(
`Cannot publish as "${auth.username}" - package belongs to "${usernamePrefix}"`,
`You can only publish packages starting with @lifeforge/${auth.username}--`
)

View File

@@ -2,7 +2,7 @@ import fs from 'fs'
import path from 'path'
import z from 'zod'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
const MODULE_STRUCTURE: Array<{
type: 'folder' | 'file'
@@ -102,9 +102,9 @@ export default async function validateModuleStructure(
}
if (errors.length > 0) {
Logging.error('Module validation failed:')
logger.error('Module validation failed:')
errors.forEach(error => {
Logging.error(`${error}`)
logger.error(`${error}`)
})
process.exit(1)
}

View File

@@ -1,8 +1,9 @@
import chalk from 'chalk'
import fs from 'fs'
import path from 'path'
import executeCommand from '@/utils/commands'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '@/utils/normalizePackage'
import listModules from '../functions/listModules'
@@ -30,9 +31,7 @@ export async function buildModuleHandler(moduleName?: string): Promise<void> {
// Check if module folder exists
if (!fs.existsSync(targetDir)) {
Logging.error(
`Module ${Logging.highlight(shortName)} not found at ${targetDir}`
)
logger.error(`Module ${chalk.blue(shortName)} not found at ${targetDir}`)
continue
}
@@ -45,14 +44,14 @@ export async function buildModuleHandler(moduleName?: string): Promise<void> {
const hasServer = fs.existsSync(serverIndexPath)
if (!hasClient && !hasServer) {
Logging.debug(`Skipping ${shortName} (no client or server)`)
logger.debug(`Skipping ${shortName} (no client or server)`)
skippedCount++
continue
}
// Build client
if (hasClient) {
Logging.info(`Building ${Logging.highlight(shortName)} client...`)
logger.info(`Building ${chalk.blue(shortName)} client...`)
try {
executeCommand('bun run build:client', {
@@ -61,13 +60,13 @@ export async function buildModuleHandler(moduleName?: string): Promise<void> {
})
clientBuiltCount++
} catch (error) {
Logging.error(`Failed to build ${shortName} client: ${error}`)
logger.error(`Failed to build ${shortName} client: ${error}`)
}
}
// Build server
if (hasServer) {
Logging.info(`Building ${Logging.highlight(shortName)} server...`)
logger.info(`Building ${chalk.blue(shortName)} server...`)
try {
executeCommand('bun run build:server', {
@@ -76,18 +75,18 @@ export async function buildModuleHandler(moduleName?: string): Promise<void> {
})
serverBuiltCount++
} catch (error) {
Logging.error(`Failed to build ${shortName} server: ${error}`)
logger.error(`Failed to build ${shortName} server: ${error}`)
}
}
}
if (clientBuiltCount > 0 || serverBuiltCount > 0) {
Logging.success(
`Built ${Logging.highlight(String(clientBuiltCount))} client bundle${clientBuiltCount !== 1 ? 's' : ''}, ${Logging.highlight(String(serverBuiltCount))} server bundle${serverBuiltCount !== 1 ? 's' : ''}`
logger.success(
`Built ${chalk.blue(String(clientBuiltCount))} client bundle${clientBuiltCount !== 1 ? 's' : ''}, ${chalk.blue(String(serverBuiltCount))} server bundle${serverBuiltCount !== 1 ? 's' : ''}`
)
}
if (skippedCount > 0 && !moduleName) {
Logging.info(`Skipped ${skippedCount} modules without builds`)
logger.info(`Skipped ${skippedCount} modules without builds`)
}
}

View File

@@ -1,3 +1,4 @@
import chalk from 'chalk'
import fs from 'fs'
import os from 'os'
import path from 'path'
@@ -6,7 +7,7 @@ import { pipeline } from 'stream/promises'
import { extract } from 'tar'
import { createGunzip } from 'zlib'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '@/utils/normalizePackage'
import { checkPackageExists, getPackageTarballUrl } from '@/utils/registry'
@@ -134,54 +135,50 @@ function printDiff(moduleName: string, diff: FileDiff): boolean {
diff.added.length > 0 || diff.modified.length > 0 || diff.deleted.length > 0
if (!hasChanges) {
Logging.print(
` ${Logging.green('✓')} ${Logging.highlight(moduleName)} ${Logging.dim('is in sync')}`
logger.print(
` ${chalk.green('✓')} ${chalk.blue(moduleName)} ${chalk.dim('is in sync')}`
)
return false
}
Logging.print(` ${Logging.yellow('!')} ${Logging.highlight(moduleName)}`)
logger.print(` ${chalk.yellow('!')} ${chalk.blue(moduleName)}`)
if (diff.added.length > 0) {
Logging.print(Logging.green(` + ${diff.added.length} added locally`))
logger.print(chalk.green(` + ${diff.added.length} added locally`))
for (const file of diff.added.slice(0, 5)) {
Logging.print(Logging.dim(` ${file}`))
logger.print(chalk.dim(` ${file}`))
}
if (diff.added.length > 5) {
Logging.print(
Logging.dim(` ... and ${diff.added.length - 5} more`)
)
logger.print(chalk.dim(` ... and ${diff.added.length - 5} more`))
}
}
if (diff.modified.length > 0) {
Logging.print(Logging.yellow(` ~ ${diff.modified.length} modified`))
logger.print(chalk.yellow(` ~ ${diff.modified.length} modified`))
for (const file of diff.modified.slice(0, 5)) {
Logging.print(Logging.dim(` ${file}`))
logger.print(chalk.dim(` ${file}`))
}
if (diff.modified.length > 5) {
Logging.print(
Logging.dim(` ... and ${diff.modified.length - 5} more`)
logger.print(
chalk.dim(` ... and ${diff.modified.length - 5} more`)
)
}
}
if (diff.deleted.length > 0) {
Logging.print(Logging.red(` - ${diff.deleted.length} deleted locally`))
logger.print(chalk.red(` - ${diff.deleted.length} deleted locally`))
for (const file of diff.deleted.slice(0, 5)) {
Logging.print(Logging.dim(` ${file}`))
logger.print(chalk.dim(` ${file}`))
}
if (diff.deleted.length > 5) {
Logging.print(
Logging.dim(` ... and ${diff.deleted.length - 5} more`)
)
logger.print(chalk.dim(` ... and ${diff.deleted.length - 5} more`))
}
}
@@ -197,8 +194,8 @@ async function compareModule(packageName: string): Promise<boolean | null> {
const { fullName, shortName, targetDir } = normalizePackage(packageName)
if (!fs.existsSync(targetDir)) {
Logging.actionableError(
`Module ${Logging.highlight(shortName)} is not installed`,
logger.actionableError(
`Module ${chalk.blue(shortName)} is not installed`,
'Run "bun forge modules list" to see installed modules'
)
@@ -206,8 +203,8 @@ async function compareModule(packageName: string): Promise<boolean | null> {
}
if (!(await checkPackageExists(fullName))) {
Logging.print(
` ${Logging.dim('○')} ${Logging.highlight(shortName)} ${Logging.dim('(not published)')}`
logger.print(
` ${chalk.dim('○')} ${chalk.blue(shortName)} ${chalk.dim('(not published)')}`
)
return null
@@ -216,7 +213,7 @@ async function compareModule(packageName: string): Promise<boolean | null> {
const tarballUrl = await getPackageTarballUrl(fullName)
if (!tarballUrl) {
Logging.warn(`Could not get tarball URL for ${Logging.highlight(fullName)}`)
logger.warn(`Could not get tarball URL for ${chalk.blue(fullName)}`)
return null
}
@@ -224,7 +221,7 @@ async function compareModule(packageName: string): Promise<boolean | null> {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-compare-'))
try {
Logging.debug(`Downloading ${Logging.highlight(fullName)} from registry...`)
logger.debug(`Downloading ${chalk.blue(fullName)} from registry...`)
await downloadAndExtractTarball(tarballUrl, tempDir)
@@ -250,9 +247,9 @@ async function compareModule(packageName: string): Promise<boolean | null> {
*/
export async function compareModuleHandler(moduleName?: string): Promise<void> {
if (moduleName) {
Logging.print('')
logger.print('')
await compareModule(moduleName)
Logging.print('')
logger.print('')
return
}
@@ -260,13 +257,13 @@ export async function compareModuleHandler(moduleName?: string): Promise<void> {
const modules = listModules()
if (Object.keys(modules).length === 0) {
Logging.print('No modules installed')
logger.print('No modules installed')
return
}
Logging.print(
`\nComparing ${Logging.highlight(String(Object.keys(modules).length))} modules with registry...\n`
logger.print(
`\nComparing ${chalk.blue(String(Object.keys(modules).length))} modules with registry...\n`
)
let changedCount = 0
@@ -279,13 +276,13 @@ export async function compareModuleHandler(moduleName?: string): Promise<void> {
}
}
Logging.print('')
logger.print('')
if (changedCount === 0) {
Logging.success('All modules are in sync with the registry')
logger.success('All modules are in sync with the registry')
} else {
Logging.warn(
`${Logging.highlight(String(changedCount))} module${changedCount > 1 ? 's' : ''} have local changes`
logger.warn(
`${chalk.blue(String(changedCount))} module${changedCount > 1 ? 's' : ''} have local changes`
)
}
}

View File

@@ -1,4 +1,4 @@
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import { registerHandlebarsHelpers } from '../functions/templates'
@@ -12,7 +12,7 @@ export const AVAILABLE_TEMPLATE_MODULE_TYPES = {
} as const
export async function createModuleHandler(moduleName?: string): Promise<void> {
Logging.info('Work in progress...')
logger.info('Work in progress...')
// checkRunningPBInstances()
// const moduleNameWithTranslation = await promptForModuleName(moduleName)
// const moduleType = await promptModuleType()

View File

@@ -1,3 +1,4 @@
import chalk from 'chalk'
import fs from 'fs'
import path from 'path'
@@ -6,7 +7,7 @@ import { installPackage } from '@/utils/commands'
import { smartReloadServer } from '@/utils/docker'
import { isDockerMode } from '@/utils/helpers'
import initGitRepository from '@/utils/initGitRepository'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '@/utils/normalizePackage'
import { checkPackageExists } from '@/utils/registry'
@@ -37,15 +38,15 @@ export async function installModuleHandler(
const { fullName, shortName, targetDir } = normalizePackage(moduleName)
if (!/^@lifeforge\/[a-z0-9-_]+--[a-z0-9-_]+$/i.test(fullName)) {
Logging.actionableError(
`Invalid module name: ${Logging.highlight(moduleName)}`,
logger.actionableError(
`Invalid module name: ${chalk.blue(moduleName)}`,
'Module names can only contain letters, numbers, hyphens, and underscores.'
)
continue
}
if (fs.existsSync(targetDir)) {
Logging.actionableError(
logger.actionableError(
`Module already exists at apps/${shortName}`,
`Remove it first with: bun forge modules uninstall ${shortName}`
)
@@ -53,28 +54,28 @@ export async function installModuleHandler(
}
if (!(await checkPackageExists(fullName))) {
Logging.actionableError(
`Module ${Logging.highlight(fullName)} does not exist in registry`,
logger.actionableError(
`Module ${chalk.blue(fullName)} does not exist in registry`,
'Check the module name and try again'
)
continue
}
Logging.debug(`Installing ${Logging.highlight(fullName)}...`)
logger.debug(`Installing ${chalk.blue(fullName)}...`)
installPackage(fullName, targetDir)
initGitRepository(targetDir)
installed.push(moduleName)
Logging.success(`Installed ${Logging.highlight(fullName)}`)
logger.success(`Installed ${chalk.blue(fullName)}`)
}
if (installed.length === 0) {
return
}
Logging.debug('Regenerating registries...')
logger.debug('Regenerating registries...')
generateRouteRegistry()
generateSchemaRegistry()
@@ -89,7 +90,7 @@ export async function installModuleHandler(
const { targetDir } = normalizePackage(moduleName)
if (fs.existsSync(path.join(targetDir, 'server', 'schema.ts'))) {
Logging.debug(`Generating database migrations for ${moduleName}...`)
logger.debug(`Generating database migrations for ${moduleName}...`)
generateMigrationsHandler(moduleName)
}
}

View File

@@ -1,4 +1,6 @@
import Logging from '@/utils/logging'
import chalk from 'chalk'
import logger from '@/utils/logger'
import listModules from '../functions/listModules'
@@ -11,22 +13,20 @@ export async function listModulesHandler(): Promise<void> {
const totalCount = Object.keys(modules).length
if (totalCount === 0) {
Logging.print('No modules installed')
Logging.print(
Logging.dim(' Run "bun forge modules install <name>" to install one')
logger.print('No modules installed')
logger.print(
chalk.dim(' Run "bun forge modules install <name>" to install one')
)
return
}
Logging.print(
`${Logging.highlight(String(totalCount))} installed module${totalCount > 1 ? 's' : ''}:\n`
logger.print(
`${chalk.blue(String(totalCount))} installed module${totalCount > 1 ? 's' : ''}:\n`
)
Object.entries(modules).forEach(([name, info]) => {
Logging.print(
` ${Logging.highlight(name)} ${Logging.dim(`v${info.version}`)}`
)
Logging.print(` ${Logging.dim(info.displayName)}`)
logger.print(` ${chalk.blue(name)} ${chalk.dim(`v${info.version}`)}`)
logger.print(` ${chalk.dim(info.displayName)}`)
})
}

View File

@@ -1,9 +1,10 @@
import chalk from 'chalk'
import fs from 'fs'
import path from 'path'
import { ROOT_DIR } from '@/constants/constants'
import executeCommand from '@/utils/commands'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import bumpPackageVersion, {
revertPackageVersion
@@ -26,26 +27,26 @@ export async function publishModuleHandler(moduleName: string): Promise<void> {
const modulePath = path.join(ROOT_DIR, 'apps', moduleName)
if (!fs.existsSync(modulePath)) {
Logging.actionableError(
`Module ${Logging.highlight(moduleName)} not found in apps/`,
logger.actionableError(
`Module ${chalk.blue(moduleName)} not found in apps/`,
'Make sure the module exists in the apps directory'
)
process.exit(1)
}
Logging.debug('Validating module structure...')
logger.debug('Validating module structure...')
await validateModuleStructure(modulePath)
Logging.debug('Validating module author...')
logger.debug('Validating module author...')
await validateModuleAuthor(modulePath)
const { oldVersion, newVersion } = bumpPackageVersion(modulePath)
Logging.print(
` Version: ${Logging.dim(oldVersion)} ${Logging.dim('→')} ${Logging.green(newVersion)}`
logger.print(
` Version: ${chalk.dim(oldVersion)} ${chalk.dim('→')} ${chalk.green(newVersion)}`
)
Logging.debug(`Publishing ${Logging.highlight(moduleName)}...`)
logger.debug(`Publishing ${chalk.blue(moduleName)}...`)
try {
executeCommand(`npm publish --registry ${getRegistryUrl()}`, {
@@ -53,17 +54,17 @@ export async function publishModuleHandler(moduleName: string): Promise<void> {
stdio: 'pipe'
})
Logging.success(
`Published ${Logging.highlight(moduleName)} ${Logging.dim(`v${newVersion}`)}`
logger.success(
`Published ${chalk.blue(moduleName)} ${chalk.dim(`v${newVersion}`)}`
)
} catch (error) {
revertPackageVersion(modulePath, oldVersion)
Logging.actionableError(
`Publish failed for ${Logging.highlight(moduleName)}`,
logger.actionableError(
`Publish failed for ${chalk.blue(moduleName)}`,
'Check npm authentication and try again'
)
Logging.debug(`Error: ${error}`)
logger.debug(`Error: ${error}`)
process.exit(1)
}
}

View File

@@ -1,10 +1,11 @@
import chalk from 'chalk'
import fs from 'fs'
import path from 'path'
import { ROOT_DIR } from '@/constants/constants'
import { bunInstall } from '@/utils/commands'
import { smartReloadServer } from '@/utils/docker'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '@/utils/normalizePackage'
import { findPackageName, removeDependency } from '@/utils/packageJson'
@@ -33,14 +34,14 @@ export async function uninstallModuleHandler(
const { targetDir, fullName } = normalizePackage(moduleName)
if (!findPackageName(fullName)) {
Logging.actionableError(
`Module ${Logging.highlight(fullName)} is not installed`,
logger.actionableError(
`Module ${chalk.blue(fullName)} is not installed`,
'Run "bun forge modules list" to see installed modules'
)
continue
}
Logging.debug(`Uninstalling ${Logging.highlight(fullName)}...`)
logger.debug(`Uninstalling ${chalk.blue(fullName)}...`)
removeDependency(fullName)
@@ -51,7 +52,7 @@ export async function uninstallModuleHandler(
uninstalled.push(moduleName)
Logging.success(`Uninstalled ${Logging.highlight(fullName)}`)
logger.success(`Uninstalled ${chalk.blue(fullName)}`)
}
if (uninstalled.length === 0) {
@@ -60,7 +61,7 @@ export async function uninstallModuleHandler(
bunInstall()
Logging.debug('Regenerating registries...')
logger.debug('Regenerating registries...')
generateRouteRegistry()
generateSchemaRegistry()

View File

@@ -1,10 +1,11 @@
import chalk from 'chalk'
import fs from 'fs'
import path from 'path'
import semver from 'semver'
import { generateMigrationsHandler } from '@/commands/db/handlers/generateMigrationsHandler'
import { installPackage } from '@/utils/commands'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import normalizePackage from '@/utils/normalizePackage'
import { getPackageLatestVersion } from '@/utils/registry'
@@ -29,21 +30,21 @@ async function upgradeModule(
const latestVersion = await getPackageLatestVersion(fullName)
if (!latestVersion) {
Logging.warn(`Could not check registry for ${Logging.highlight(fullName)}`)
logger.warn(`Could not check registry for ${chalk.blue(fullName)}`)
return false
}
if (semver.eq(currentVersion, latestVersion)) {
Logging.print(
` ${Logging.dim('•')} ${Logging.highlight(packageName)} ${Logging.dim(`v${currentVersion} is up to date`)}`
logger.print(
` ${chalk.dim('•')} ${chalk.blue(packageName)} ${chalk.dim(`v${currentVersion} is up to date`)}`
)
return false
}
Logging.debug(
`Upgrading ${Logging.highlight(packageName)} from ${currentVersion} to ${latestVersion}...`
logger.debug(
`Upgrading ${chalk.blue(packageName)} from ${currentVersion} to ${latestVersion}...`
)
const backupPath = path.join(path.dirname(targetDir), `${packageName}.backup`)
@@ -60,17 +61,17 @@ async function upgradeModule(
fs.rmSync(backupPath, { recursive: true, force: true })
Logging.print(
` ${Logging.green('↑')} ${Logging.highlight(packageName)} ${Logging.dim(`${currentVersion}`)} ${Logging.green(latestVersion)}`
logger.print(
` ${chalk.green('↑')} ${chalk.blue(packageName)} ${chalk.dim(`${currentVersion}`)} ${chalk.green(latestVersion)}`
)
return true
} catch (error) {
Logging.error(`Failed to upgrade ${Logging.highlight(fullName)}: ${error}`)
logger.error(`Failed to upgrade ${chalk.blue(fullName)}: ${error}`)
if (fs.existsSync(backupPath)) {
fs.renameSync(backupPath, targetDir)
Logging.debug('Restored backup after failed upgrade')
logger.debug('Restored backup after failed upgrade')
}
return false
@@ -96,8 +97,8 @@ export async function upgradeModuleHandler(moduleName?: string): Promise<void> {
const mod = modules[fullName]
if (!mod) {
Logging.actionableError(
`Module ${Logging.highlight(moduleName)} is not installed`,
logger.actionableError(
`Module ${chalk.blue(moduleName)} is not installed`,
'Run "bun forge modules list" to see installed modules'
)
process.exit(1)
@@ -109,8 +110,8 @@ export async function upgradeModuleHandler(moduleName?: string): Promise<void> {
upgraded.push(fullName)
}
} else {
Logging.print(
`Checking ${Logging.highlight(String(Object.keys(modules).length))} modules for updates...\n`
logger.print(
`Checking ${chalk.blue(String(Object.keys(modules).length))} modules for updates...\n`
)
for (const [name, { version }] of Object.entries(modules)) {
@@ -123,8 +124,8 @@ export async function upgradeModuleHandler(moduleName?: string): Promise<void> {
}
if (upgraded.length > 0) {
Logging.print('')
Logging.debug('Regenerating registries...')
logger.print('')
logger.debug('Regenerating registries...')
generateRouteRegistry()
generateSchemaRegistry()
@@ -136,11 +137,11 @@ export async function upgradeModuleHandler(moduleName?: string): Promise<void> {
generateMigrationsHandler()
Logging.success(
`Upgraded ${Logging.highlight(String(upgraded.length))} module${upgraded.length > 1 ? 's' : ''}`
logger.success(
`Upgraded ${chalk.blue(String(upgraded.length))} module${upgraded.length > 1 ? 's' : ''}`
)
} else if (!moduleName) {
Logging.print('')
Logging.success('All modules are up to date')
logger.print('')
logger.success('All modules are up to date')
}
}

View File

@@ -1,4 +1,4 @@
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import { PROJECTS } from '../constants/projects'
@@ -26,7 +26,7 @@ export function validateProjectArguments(projects: string[] | undefined): void {
const validation = validateProjects(projects, validProjects)
if (!validation.isValid) {
Logging.actionableError(
logger.actionableError(
`Invalid project(s): ${validation.invalidProjects.join(', ')}`,
'Available projects: ' + validProjects.join(', ')
)

View File

@@ -1,6 +1,8 @@
import fs from 'fs'
import path from 'path'
import logger from '@/utils/logger'
export const ROOT_DIR = import.meta.dirname.split('/tools')[0]
const GENERATED_DIR = path.join(

View File

@@ -3,7 +3,7 @@ import fs from 'fs'
import path from 'path'
import { getEnvVar, isDockerMode } from '@/utils/helpers'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import { ROOT_DIR } from './constants'
@@ -36,7 +36,7 @@ if (!isDockerMode()) {
try {
fs.accessSync(PB_DIR)
} catch (error) {
Logging.error(`PB_DIR is not accessible: ${error}`)
logger.error(`PB_DIR is not accessible: ${error}`)
process.exit(1)
}
}

View File

@@ -5,7 +5,7 @@ import path from 'path'
import { runCLI, setupCLI } from './cli/setup'
import { ROOT_DIR } from './constants/constants'
import Logging from './utils/logging'
import logger from './utils/logger'
/**
* LifeForge Forge - Build and development tool for LifeForge projects
@@ -22,7 +22,7 @@ const envPath = path.resolve(ROOT_DIR, 'env/.env.local')
if (fs.existsSync(envPath)) {
dotenv.config({ path: envPath, quiet: true })
} else {
Logging.warn(
logger.warn(
`Environment file not found at ${envPath}. Continuing without loading environment variables from file.`
)
}
@@ -32,5 +32,5 @@ try {
setupCLI()
runCLI()
} catch (error) {
Logging.error(`Unexpected error occurred: ${error}`)
logger.error(`Unexpected error occurred: ${error}`)
}

View File

@@ -1,11 +1,11 @@
import chalk from 'chalk'
import CryptoJS from 'crypto-js'
import OpenAI from 'openai'
import type { ResponseInput } from 'openai/resources/responses/responses.mjs'
import ora from 'ora'
import z from 'zod'
import { getEnvVars } from './helpers'
import Logging from './logging'
import logger from './logger'
import getPBInstance from './pocketbase'
import { zodTextFormat } from './zodResponseFormat'
@@ -39,7 +39,7 @@ export async function getAPIKey(): Promise<string | null> {
CryptoJS.enc.Utf8
)
} catch {
Logging.error('Failed to decrypt OpenAI API key.')
logger.error('Failed to decrypt OpenAI API key.')
return null
} finally {
@@ -64,7 +64,7 @@ export async function fetchAI<T extends z.ZodTypeAny>({
const apiKey = await getAPIKey()
if (!apiKey) {
Logging.error('OpenAI API key not found.')
logger.error('OpenAI API key not found.')
return null
}
@@ -73,7 +73,7 @@ export async function fetchAI<T extends z.ZodTypeAny>({
apiKey
})
const spinner = ora('Fetching AI response...').start()
logger.debug(`Using OpenAI model: ${model}`)
const completion = await client.responses.parse({
model,
@@ -85,9 +85,13 @@ export async function fetchAI<T extends z.ZodTypeAny>({
const parsedResponse = completion.output_parsed
spinner.stop()
logger.debug(
`Received response with length ${chalk.green(completion.output.length)} from OpenAI API and parsed successfully.`
)
if (!parsedResponse) {
logger.error('Failed to parse response from OpenAI API.')
return null
}

View File

@@ -1,6 +1,8 @@
import fs from 'fs'
import semver from 'semver'
import logger from '@/utils/logger'
/**
* Bumps the patch version in a module's package.json file.
*

View File

@@ -1,10 +1,11 @@
import { LOG_LEVELS, type LogLevel } from '@lifeforge/log'
import { type IOType, spawnSync } from 'child_process'
import fs from 'fs'
import path from 'path'
import { ROOT_DIR } from '@/constants/constants'
import Logging, { LEVEL_ORDER } from './logging'
import logger from './logger'
import { addDependency } from './packageJson'
interface CommandExecutionOptions {
@@ -33,7 +34,7 @@ export default function executeCommand(
try {
cmd = typeof command === 'function' ? command() : command
} catch (error) {
Logging.actionableError(
logger.actionableError(
`Failed to generate command: ${error}`,
'Check the command generation logic for errors'
)
@@ -41,7 +42,7 @@ export default function executeCommand(
}
try {
Logging.debug(`Executing: ${cmd}`)
logger.debug(`Executing: ${cmd}`)
const [toBeExecuted, ...args] = cmd.split(' ')
@@ -61,7 +62,7 @@ export default function executeCommand(
}
if (!options.stdio || options.stdio === 'inherit') {
Logging.debug(`Completed: ${cmd}`)
logger.debug(`Completed: ${cmd}`)
}
return result.stdout?.toString().trim() || ''
@@ -70,11 +71,11 @@ export default function executeCommand(
throw error
}
Logging.actionableError(
logger.actionableError(
`Command execution failed: ${cmd}`,
'Check if the command exists and you have the necessary permissions'
)
Logging.debug(`Error details: ${error}`)
logger.debug(`Error details: ${error}`)
process.exit(1)
}
}
@@ -85,7 +86,11 @@ export default function executeCommand(
export function bunInstall() {
executeCommand('bun install', {
cwd: ROOT_DIR,
stdio: Logging.level > LEVEL_ORDER['debug'] ? 'pipe' : 'inherit'
stdio:
LOG_LEVELS.indexOf(logger.instance.level as LogLevel) >
LOG_LEVELS.indexOf('debug')
? 'pipe'
: 'inherit'
})
}
@@ -103,25 +108,29 @@ export function installPackage(fullName: string, targetDir: string) {
fs.rmSync(targetDir, { recursive: true, force: true })
}
Logging.debug(`Installing ${Logging.highlight(fullName)} from registry...`)
logger.debug(`Installing ${fullName} from registry...`)
executeCommand(`bun add ${fullName}@latest`, {
cwd: ROOT_DIR,
stdio: Logging.level > LEVEL_ORDER['info'] ? 'pipe' : 'inherit'
stdio:
LOG_LEVELS.indexOf(logger.instance.level as LogLevel) >
LOG_LEVELS.indexOf('info')
? 'pipe'
: 'inherit'
})
const installedPath = path.join(ROOT_DIR, 'node_modules', fullName)
if (!fs.existsSync(installedPath)) {
Logging.actionableError(
`Failed to install ${Logging.highlight(fullName)}`,
logger.actionableError(
`Failed to install ${fullName}`,
'Check if the package exists in the registry'
)
process.exit(1)
}
Logging.debug(`Copying ${Logging.highlight(fullName)} to ${targetDir}...`)
logger.debug(`Copying ${fullName} to ${targetDir}...`)
fs.cpSync(installedPath, targetDir, { recursive: true, dereference: true })

View File

@@ -1,7 +1,7 @@
import { execSync } from 'child_process'
import { isDockerMode } from './helpers'
import Logging from './logging'
import logger from './logger'
const SERVER_CONTAINER = 'lifeforge-server'
@@ -40,7 +40,7 @@ export function isContainerRunning(containerName: string): boolean {
*/
export function restartServerContainer(): void {
if (isDockerMode()) {
Logging.warn('Cannot restart Docker from inside a container')
logger.warn('Cannot restart Docker from inside a container')
return
}
@@ -50,17 +50,17 @@ export function restartServerContainer(): void {
}
if (!isContainerRunning(SERVER_CONTAINER)) {
Logging.debug('Server container not running, skipping restart')
logger.debug('Server container not running, skipping restart')
return
}
try {
Logging.info('Restarting Docker server container...')
logger.info('Restarting Docker server container...')
execSync(`docker restart ${SERVER_CONTAINER}`, { stdio: 'inherit' })
Logging.success('Server container restarted')
logger.success('Server container restarted')
} catch (error) {
Logging.error(`Failed to restart Docker server: ${error}`)
logger.error(`Failed to restart Docker server: ${error}`)
}
}
@@ -76,6 +76,6 @@ export function smartReloadServer(): void {
if (isDockerRunning() && isContainerRunning(SERVER_CONTAINER)) {
restartServerContainer()
} else {
Logging.info('Refresh the browser to load module changes')
logger.info('Refresh the browser to load module changes')
}
}

View File

@@ -1,4 +1,4 @@
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import executeCommand from './commands'
@@ -22,13 +22,13 @@ export function validateMaintainerAccess(username: string): void {
return
}
Logging.warn(
logger.warn(
'Failed to verify maintainer access. Ensure you are authenticated with "gh auth login".'
)
process.exit(1)
} catch (error) {
Logging.actionableError(
logger.actionableError(
`Failed to check maintainer access for ${username}.`,
`Error: ${error instanceof Error ? error.message : String(error)}`
)
@@ -74,7 +74,7 @@ export function getGithubUser(): { name: string; email: string } | null {
return null
} catch (error) {
Logging.debug(`Failed to fetch GitHub user info: ${error}`)
logger.debug(`Failed to fetch GitHub user info: ${error}`)
return null
}

View File

@@ -2,7 +2,7 @@ import { spawnSync } from 'child_process'
import prompts from 'prompts'
import executeCommand from './commands'
import Logging from './logging'
import logger from './logger'
/**
* Validates and retrieves multiple required environment variables.
@@ -29,7 +29,7 @@ export function getEnvVars<const T extends readonly string[]>(
}
if (missing.length > 0) {
Logging.actionableError(
logger.actionableError(
`Missing required environment variables: ${missing.join(', ')}`,
'Use the "forge db init" command to set up the environment variables, or set them manually in your env/.env.local file'
)
@@ -58,7 +58,7 @@ export function getEnvVar(varName: string, fallback?: string): string {
return fallback
}
Logging.actionableError(
logger.actionableError(
`Missing required environment variable: ${varName}`,
'Use the "forge db init" command to set up the environment variables, or set them manually in your env/.env.local file'
)
@@ -78,9 +78,7 @@ export function killExistingProcess(
if (typeof processKeywordOrPID === 'number') {
process.kill(processKeywordOrPID)
Logging.debug(
`Killed process with PID: ${Logging.highlight(String(processKeywordOrPID))}`
)
logger.debug(`Killed process with PID: ${String(processKeywordOrPID)}`)
return
}
@@ -93,8 +91,8 @@ export function killExistingProcess(
if (serverInstance) {
executeCommand(`pkill -f "${processKeywordOrPID}"`)
Logging.debug(
`Killed process matching keyword: ${Logging.highlight(processKeywordOrPID)} (PID: ${Logging.highlight(serverInstance)})`
logger.debug(
`Killed process matching keyword: ${processKeywordOrPID} (PID: ${serverInstance})`
)
return parseInt(serverInstance, 10)

View File

@@ -2,7 +2,7 @@ import fs from 'fs'
import path from 'path'
import executeCommand from '@/utils/commands'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
/**
* Initializes a git repository in the target directory and sets up the remote.
@@ -30,7 +30,7 @@ export default function initGitRepository(targetDir: string): void {
return
}
Logging.info(`Initializing git repository...`)
logger.info(`Initializing git repository...`)
try {
executeCommand('git init', { cwd: targetDir, stdio: 'pipe' })
@@ -47,8 +47,8 @@ export default function initGitRepository(targetDir: string): void {
{ cwd: targetDir, stdio: 'pipe' }
)
Logging.debug(`Git repository initialized with remote: ${repoUrl}`)
logger.debug(`Git repository initialized with remote: ${repoUrl}`)
} catch (error) {
Logging.debug(`Failed to initialize git repository: ${error}`)
logger.debug(`Failed to initialize git repository: ${error}`)
}
}

22
tools/src/utils/logger.ts Normal file
View File

@@ -0,0 +1,22 @@
import { type LogLevel } from '@lifeforge/log'
import { type CLILogger, createCLILogger } from '@lifeforge/log/cli'
let loggerInstance: CLILogger | null = null
function getLogger(): CLILogger {
if (!loggerInstance) {
loggerInstance = createCLILogger()
}
return loggerInstance
}
export function setLogLevel(level: LogLevel): void {
const logger = getLogger()
logger.setLevel(level)
}
const logger = getLogger()
export default logger

View File

@@ -1,153 +0,0 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-ignore- Lazy to fix the TS config stuff
import { LoggingService } from '@server/core/functions/logging/loggingService'
import chalk from 'chalk'
export const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'] as const
export const LEVEL_ORDER = {
debug: 1,
info: 2,
warn: 3,
error: 4,
fatal: 5
}
type LogLevel = keyof typeof LEVEL_ORDER
/**
* CLI Logging service that wraps the server's LoggingService
* Provides consistent logging across the entire CLI with file persistence
*/
export default class Logging {
private static readonly SERVICE_NAME = 'CLI'
public static level: number = LEVEL_ORDER['info']
static setLevel(level: LogLevel): void {
Logging.level = LEVEL_ORDER[level]
}
// ─────────────────────────────────────────────────────────────
// Formatting Utilities
// ─────────────────────────────────────────────────────────────
/** Format text as bold */
static bold(text: string): string {
return chalk.bold(text)
}
/** Format text as dim/muted */
static dim(text: string): string {
return chalk.dim(text)
}
/** Format text as highlighted (bold blue) - for important values */
static highlight(text: string): string {
return chalk.bold.blue(text)
}
/** Format text as green - for success/positive items */
static green(text: string): string {
return chalk.green(text)
}
/** Format text as yellow - for warnings */
static yellow(text: string): string {
return chalk.yellow(text)
}
/** Format text as red - for errors */
static red(text: string): string {
return chalk.red(text)
}
/** Format text as cyan - for info/neutral items */
static cyan(text: string): string {
return chalk.cyan(text)
}
// ─────────────────────────────────────────────────────────────
// Core Logging Methods
// ─────────────────────────────────────────────────────────────
/**
* Log an informational message
*/
static info(message: string): void {
if (Logging.level > LEVEL_ORDER['info']) {
return
}
LoggingService.info(message, this.SERVICE_NAME)
}
/**
* Log an error message with consistent formatting
*/
static error(message: string, details?: string): void {
if (Logging.level > LEVEL_ORDER['error']) {
return
}
const formattedMessage = details ? `${message}: ${details}` : message
LoggingService.error(formattedMessage, this.SERVICE_NAME)
}
/**
* Log a warning message
*/
static warn(message: string): void {
if (Logging.level > LEVEL_ORDER['warn']) {
return
}
LoggingService.warn(message, this.SERVICE_NAME)
}
/**
* Log a debug message
*/
static debug(message: string): void {
if (Logging.level > LEVEL_ORDER['debug']) {
return
}
LoggingService.debug(message, this.SERVICE_NAME)
}
/**
* Log a success message with green checkmark
*/
static success(message: string): void {
if (Logging.level > LEVEL_ORDER['info']) {
return
}
LoggingService.info(`${chalk.green('✔')} ${message}`, this.SERVICE_NAME)
}
/**
* Print raw output without log prefix (for lists, tables, etc.)
* Respects log level - only prints if info level is enabled
*/
static print(message: string): void {
if (Logging.level > LEVEL_ORDER['info']) {
return
}
console.log(message)
}
/**
* Log an actionable error with next steps
*/
static actionableError(message: string, suggestion: string): void {
if (Logging.level > LEVEL_ORDER['error']) {
return
}
this.error(chalk.red(message))
this.info(chalk.yellow(`Suggestion: ${suggestion}`))
}
}

View File

@@ -2,6 +2,7 @@ import fs from 'fs'
import path from 'path'
import { ROOT_DIR } from '@/constants/constants'
import logger from '@/utils/logger'
type PackageType = 'module' | 'locale'

View File

@@ -3,7 +3,7 @@ import path from 'path'
import { ROOT_DIR } from '@/constants/constants'
import Logging from './logging'
import logger from './logger'
interface PackageJson {
name?: string
@@ -29,14 +29,14 @@ export function readRootPackageJson(): PackageJson {
* @param packageJson The JSON object to write to the root package.json file.
*/
export function writeRootPackageJson(packageJson: PackageJson): void {
Logging.debug(`Writing root package.json`)
logger.debug(`Writing root package.json`)
fs.writeFileSync(
ROOT_PACKAGE_JSON_DIR,
JSON.stringify(packageJson, null, 2) + '\n'
)
Logging.debug(`Wrote root package.json`)
logger.debug(`Wrote root package.json`)
}
/**
@@ -49,7 +49,7 @@ export function addDependency(
packageName: string,
version = 'workspace:*'
): void {
Logging.debug(`Adding workspace dependency: ${packageName}`)
logger.debug(`Adding workspace dependency: ${packageName}`)
const packageJson = readRootPackageJson()
@@ -61,7 +61,7 @@ export function addDependency(
writeRootPackageJson(packageJson)
Logging.debug(`Added workspace dependency: ${packageName}`)
logger.debug(`Added workspace dependency: ${packageName}`)
}
/**
@@ -70,7 +70,7 @@ export function addDependency(
* @param packageName The name of the package to remove as a dependency.
*/
export function removeDependency(packageName: string): void {
Logging.debug(`Removing workspace dependency: ${packageName}`)
logger.debug(`Removing workspace dependency: ${packageName}`)
const packageJson = readRootPackageJson()
@@ -79,7 +79,7 @@ export function removeDependency(packageName: string): void {
writeRootPackageJson(packageJson)
}
Logging.debug(`Removed workspace dependency: ${packageName}`)
logger.debug(`Removed workspace dependency: ${packageName}`)
}
/**
@@ -89,7 +89,7 @@ export function removeDependency(packageName: string): void {
* @returns The package name if found, or null if not found
*/
export function findPackageName(name: string): string | null {
Logging.debug(`Finding package name: ${name}`)
logger.debug(`Finding package name: ${name}`)
const packageJson = readRootPackageJson()
@@ -97,13 +97,13 @@ export function findPackageName(name: string): string | null {
for (const dep of Object.keys(dependencies)) {
if (dep === name) {
Logging.debug(`Found package name: ${name}`)
logger.debug(`Found package name: ${name}`)
return dep
}
}
Logging.debug(`Package name not found: ${name}`)
logger.debug(`Package name not found: ${name}`)
return null
}

View File

@@ -3,7 +3,7 @@ import PocketBase from 'pocketbase'
import { PB_BINARY_PATH, PB_KWARGS } from '@/constants/db'
import { getEnvVars } from '@/utils/helpers'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import executeCommand from './commands'
import { killExistingProcess } from './helpers'
@@ -61,7 +61,7 @@ export function checkRunningPBInstances(exitOnError = true): boolean {
if (validPids.length > 0) {
if (exitOnError) {
Logging.actionableError(
logger.actionableError(
`PocketBase is already running (PID: ${validPids.join(', ')})`,
'Stop the existing instance with "pkill -f pocketbase" before proceeding'
)
@@ -84,7 +84,7 @@ export function checkRunningPBInstances(exitOnError = true): boolean {
* @throws Rejects if the server fails to start or encounters an error
*/
export async function startPBServer(): Promise<number> {
Logging.debug('Starting PocketBase server...')
logger.debug('Starting PocketBase server...')
return new Promise((resolve, reject) => {
const pbProcess = spawn(PB_BINARY_PATH, ['serve', ...PB_KWARGS], {
@@ -101,12 +101,12 @@ export async function startPBServer(): Promise<number> {
}
if (output.includes('Server started')) {
Logging.debug(`PocketBase server started (PID: ${pbProcess.pid})`)
logger.debug(`PocketBase server started (PID: ${pbProcess.pid})`)
resolve(pbProcess.pid!)
}
if (output.includes('bind: address already in use')) {
Logging.actionableError(
logger.actionableError(
'Port 8090 is already in use',
'Run "pkill -f pocketbase" to stop existing instances, or check for other apps using port 8090'
)
@@ -117,18 +117,18 @@ export async function startPBServer(): Promise<number> {
pbProcess.stderr?.on('data', data => {
const error = data.toString().trim()
Logging.debug(`PocketBase stderr: ${error}`)
logger.debug(`PocketBase stderr: ${error}`)
reject(new Error(error))
})
pbProcess.on('error', error => {
Logging.debug(`PocketBase spawn error: ${error.message}`)
logger.debug(`PocketBase spawn error: ${error.message}`)
reject(error)
})
pbProcess.on('exit', code => {
if (code !== 0) {
Logging.debug(`PocketBase exited with code ${code}`)
logger.debug(`PocketBase exited with code ${code}`)
reject(new Error(`PocketBase process exited with code ${code}`))
}
})
@@ -145,7 +145,7 @@ export async function startPocketbase(): Promise<(() => void) | null> {
const pbRunning = checkRunningPBInstances(false)
if (pbRunning) {
Logging.debug('PocketBase is already running')
logger.debug('PocketBase is already running')
return null
}
@@ -156,7 +156,7 @@ export async function startPocketbase(): Promise<(() => void) | null> {
killExistingProcess(pbPid)
}
} catch (error) {
Logging.actionableError(
logger.actionableError(
`Failed to start PocketBase server: ${error instanceof Error ? error.message : 'Unknown error'}`,
'Run "bun forge db init" to initialize the database or check if the PocketBase binary exists'
)
@@ -199,7 +199,7 @@ export default async function getPBInstance(createNewInstance = true): Promise<{
killPB
}
} catch (error) {
Logging.actionableError(
logger.actionableError(
`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
'Check PB_EMAIL and PB_PASSWORD in env/.env.local are correct'
)

View File

@@ -2,7 +2,7 @@ import fs from 'fs'
import path from 'path'
import { ROOT_DIR } from '@/constants/constants'
import Logging from '@/utils/logging'
import logger from '@/utils/logger'
import executeCommand from './commands'
@@ -79,7 +79,7 @@ export async function checkAuth(): Promise<{
throw new Error('Not authenticated')
} catch {
Logging.warn('Not authenticated. Please login first.')
logger.warn('Not authenticated. Please login first.')
process.exit(1)
}

View File

@@ -6,9 +6,6 @@
"paths": {
"@/*": [
"./src/*"
],
"@server/*": [
"../server/src/*"
]
}
},