0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-07 23:01:25 -05:00

refactor: align console log (#3684)

add a shared ConsoleLog class for unified console logging.
This commit is contained in:
Gao Sun 2023-04-11 01:48:19 +08:00 committed by GitHub
parent 88032402dd
commit 83367569fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 221 additions and 161 deletions

View file

@ -91,6 +91,9 @@
},
"eslintConfig": {
"extends": "@silverhand",
"rules": {
"no-console": "error"
},
"ignorePatterns": [
"src/package-json.ts"
]

View file

@ -1,6 +1,6 @@
import type { CommandModule } from 'yargs';
import { log } from '../../utils.js';
import { consoleLog } from '../../utils.js';
import { addConnectors, addOfficialConnectors, inquireInstancePath } from './utils.js';
@ -36,7 +36,7 @@ const add: CommandModule<
const instancePath = await inquireInstancePath(path);
if (cloud && !official) {
log.error('--cloud option can only be used with --official option');
consoleLog.fatal('--cloud option can only be used with --official option');
}
if (official) {
@ -44,12 +44,12 @@ const add: CommandModule<
await addOfficialConnectors(instancePath, cloud);
} else {
if (!packageNames?.length) {
log.error('No connector name provided');
consoleLog.fatal('No connector name provided');
}
await addConnectors(instancePath, packageNames);
}
log.info('Restart your Logto instance to get the changes reflected.');
consoleLog.info('Restart your Logto instance to get the changes reflected.');
},
};

View file

@ -3,7 +3,7 @@ import path from 'node:path';
import type { CommandModule } from 'yargs';
import { log } from '../../utils.js';
import { consoleLog } from '../../utils.js';
import { getConnectorDirectory, getLocalConnectorPackages, inquireInstancePath } from './utils.js';
@ -47,12 +47,12 @@ const link: CommandModule<{ path?: string }, { path?: string; cloud: boolean; mo
await fs.rm(targetPath, { recursive: true, force: true });
await fs.symlink(path.relative(connectorDirectory, packagePath), targetPath);
} catch (error) {
log.warn(error);
consoleLog.warn(error);
return;
}
log.succeed('Linked', packagePath);
consoleLog.succeed('Linked', packagePath);
})
);
},

View file

@ -1,7 +1,7 @@
import chalk from 'chalk';
import type { CommandModule } from 'yargs';
import type { ConnectorPackage } from '../../utils.js';
import { type ConnectorPackage, consoleLog } from '../../utils.js';
import { getConnectorPackagesFrom, isOfficialConnector } from './utils.js';
@ -10,9 +10,9 @@ const logConnectorNames = (type: string, packages: ConnectorPackage[]) => {
return;
}
console.log();
console.log(chalk.blue(type));
console.log(packages.map(({ name }) => ' ' + name).join('\n'));
consoleLog.plain();
consoleLog.plain(chalk.blue(type));
consoleLog.plain(packages.map(({ name }) => ' ' + name).join('\n'));
};
const list: CommandModule<{ path?: string }, { path?: string }> = {

View file

@ -3,7 +3,7 @@ import fs from 'node:fs/promises';
import chalk from 'chalk';
import type { CommandModule } from 'yargs';
import { log } from '../../utils.js';
import { consoleLog } from '../../utils.js';
import { getConnectorPackagesFrom } from './utils.js';
@ -19,7 +19,7 @@ const remove: CommandModule<{ path?: string }, { path?: string; packages?: strin
}),
handler: async ({ path: inputPath, packages: packageNames }) => {
if (!packageNames?.length) {
log.error('No connector name provided');
consoleLog.fatal('No connector name provided');
}
const existingPackages = await getConnectorPackagesFrom(inputPath);
@ -28,7 +28,7 @@ const remove: CommandModule<{ path?: string }, { path?: string; packages?: strin
);
if (notFoundPackageNames.length > 0) {
log.error(
consoleLog.fatal(
`Cannot remove ${notFoundPackageNames
.map((name) => chalk.green(name))
.join(', ')}: not found in your Logto instance directory`
@ -45,8 +45,8 @@ const remove: CommandModule<{ path?: string }, { path?: string; packages?: strin
return okSymbol;
} catch (error: unknown) {
log.warn(`Error while removing ${chalk.green(packageInfo?.name)}`);
log.warn(error);
consoleLog.warn(`Error while removing ${chalk.green(packageInfo?.name)}`);
consoleLog.warn(error);
return error;
}
@ -54,7 +54,7 @@ const remove: CommandModule<{ path?: string }, { path?: string; packages?: strin
);
const errorCount = result.filter((value) => value !== okSymbol).length;
log.info(`Removed ${result.length - errorCount} connectors`);
consoleLog.info(`Removed ${result.length - errorCount} connectors`);
},
};

View file

@ -14,7 +14,7 @@ import tar from 'tar';
import { z } from 'zod';
import { connectorDirectory } from '../../constants.js';
import { getConnectorPackagesFromDirectory, isTty, log, oraPromise } from '../../utils.js';
import { consoleLog, getConnectorPackagesFromDirectory, isTty, oraPromise } from '../../utils.js';
import { defaultPath } from '../install/utils.js';
const coreDirectory = 'packages/core';
@ -82,7 +82,7 @@ export const inquireInstancePath = async (initialPath?: string) => {
const validated = await validatePath(instancePath);
if (validated !== true) {
log.error(validated);
consoleLog.fatal(validated);
}
return instancePath;
@ -139,7 +139,7 @@ export const addConnectors = async (instancePath: string, packageNames: string[]
await fs.mkdir(cwd, { recursive: true });
}
log.info('Fetch connector metadata');
consoleLog.info('Fetch connector metadata');
const limit = pLimit(10);
const results = await Promise.all(
@ -166,14 +166,14 @@ export const addConnectors = async (instancePath: string, packageNames: string[]
await tar.extract({ cwd: packageDirectory, file: tarPath, strip: 1 });
await fs.unlink(tarPath);
log.succeed(`Added ${chalk.green(name)} v${version}`);
consoleLog.succeed(`Added ${chalk.green(name)} v${version}`);
};
return limit(async () => {
try {
await pRetry(run, { retries: 2 });
} catch (error: unknown) {
console.warn(`[${packageName}]`, error);
consoleLog.error(`[${packageName}]`, error);
return packageName;
}
@ -184,14 +184,14 @@ export const addConnectors = async (instancePath: string, packageNames: string[]
const errorPackages = results.filter(Boolean);
const errorCount = errorPackages.length;
log.info(
consoleLog.info(
errorCount
? `Finished with ${errorCount} error${conditionalString(errorCount > 1 && 's')}.`
: 'Finished'
);
if (errorCount) {
log.warn('Failed to add ' + errorPackages.map((name) => chalk.green(name)).join(', '));
consoleLog.warn('Failed to add ' + errorPackages.map((name) => chalk.green(name)).join(', '));
}
};
@ -261,7 +261,7 @@ export const addOfficialConnectors = async (
prefixText: chalk.blue('[info]'),
});
log.info(`Found ${packages.length} official connectors`);
consoleLog.info(`Found ${packages.length} official connectors`);
await addConnectors(
instancePath,

View file

@ -9,7 +9,7 @@ import {
getCurrentDatabaseAlterationTimestamp,
updateDatabaseTimestamp,
} from '../../../queries/system.js';
import { log } from '../../../utils.js';
import { consoleLog } from '../../../utils.js';
import type { AlterationFile } from './type.js';
import { getAlterationFiles, getTimestampFromFilename } from './utils.js';
@ -74,17 +74,17 @@ const deployAlteration = async (
}
});
} catch (error: unknown) {
console.error(error);
consoleLog.error(error);
await pool.end();
log.error(
consoleLog.fatal(
`Error ocurred during running alteration ${chalk.blue(filename)}.\n\n` +
" This alteration didn't change anything since it was in a transaction.\n" +
' Try to fix the error and deploy again.'
);
}
log.info(`Run alteration ${filename} \`${action}()\` function succeeded`);
consoleLog.info(`Run alteration ${filename} \`${action}()\` function succeeded`);
};
const alteration: CommandModule<unknown, { action: string; target?: string }> = {
@ -106,7 +106,7 @@ const alteration: CommandModule<unknown, { action: string; target?: string }> =
const files = await getAlterationFiles();
for (const file of files) {
console.log(file.filename);
consoleLog.plain(file.filename);
}
} else if (action === 'deploy') {
const pool = await createPoolFromConfig();
@ -115,7 +115,7 @@ const alteration: CommandModule<unknown, { action: string; target?: string }> =
target
);
log.info(
consoleLog.info(
`Found ${alterations.length} alteration${conditionalString(
alterations.length > 1 && 's'
)} to deploy`
@ -135,7 +135,7 @@ const alteration: CommandModule<unknown, { action: string; target?: string }> =
target ?? ''
);
log.info(
consoleLog.info(
`Found ${alterations.length} alteration${conditionalString(
alterations.length > 1 && 's'
)} to revert`
@ -150,7 +150,7 @@ const alteration: CommandModule<unknown, { action: string; target?: string }> =
await pool.end();
} else {
log.error('Unsupported action');
consoleLog.fatal('Unsupported action');
}
},
};

View file

@ -3,7 +3,7 @@ import chalk from 'chalk';
import inquirer from 'inquirer';
import { SemVer, compare, eq, gt } from 'semver';
import { findLastIndex, isTty, log } from '../../../utils.js';
import { consoleLog, findLastIndex, isTty } from '../../../utils.js';
import type { AlterationFile } from './type.js';
@ -43,7 +43,7 @@ export const chooseAlterationsByVersion = async (
return [];
}
log.info(`Deploy target ${chalk.green(nextTag)}`);
consoleLog.info(`Deploy target ${chalk.green(nextTag)}`);
return alterations.slice(0, endIndex + 1);
}
@ -89,7 +89,7 @@ export const chooseAlterationsByVersion = async (
const targetVersion = await getTargetVersion();
log.info(`Deploy target ${chalk.green(targetVersion.version)}`);
consoleLog.info(`Deploy target ${chalk.green(targetVersion.version)}`);
return alterations.filter(({ filename }) => {
const version = getVersionFromFilename(filename);
@ -104,7 +104,7 @@ export const chooseRevertAlterationsByVersion = async (
) => {
const semVersion = new SemVer(version);
log.info(`Revert target ${chalk.green(semVersion.version)}`);
consoleLog.info(`Revert target ${chalk.green(semVersion.version)}`);
return alterations.filter(({ filename }) => {
if (getVersionStringFromFilename(filename) === nextTag) {

View file

@ -11,7 +11,7 @@ import type { CommandModule } from 'yargs';
import { createPoolFromConfig } from '../../database.js';
import { getRowsByKeys, updateValueByKey } from '../../queries/logto-config.js';
import { log } from '../../utils.js';
import { consoleLog } from '../../utils.js';
import { generateOidcCookieKey, generateOidcPrivateKey } from './utils.js';
@ -30,7 +30,7 @@ const validateKeys: ValidateKeysFunction = (keys) => {
);
if (invalidKey) {
log.error(
consoleLog.fatal(
`Invalid config key ${chalk.red(invalidKey)} found, expected one of ${validKeysDisplay}`
);
}
@ -47,7 +47,9 @@ const validateRotateKey: ValidateRotateKeyFunction = (key) => {
// Using `.includes()` will result a type error
// eslint-disable-next-line unicorn/prefer-includes
if (!validRotateKeys.some((element) => element === key)) {
log.error(`Invalid config key ${chalk.red(key)} found, expected one of ${validKeysDisplay}`);
consoleLog.fatal(
`Invalid config key ${chalk.red(key)} found, expected one of ${validKeysDisplay}`
);
}
};
@ -80,7 +82,7 @@ const getConfig: CommandModule<unknown, { key: string; keys: string[]; tenantId:
const { rows } = await getRowsByKeys(pool, tenantId, queryKeys);
await pool.end();
console.log(
consoleLog.plain(
queryKeys
.map((currentKey) => {
const value = rows.find(({ key }) => currentKey === key)?.value;
@ -125,7 +127,7 @@ const setConfig: CommandModule<unknown, { key: string; value: string; tenantId:
await updateValueByKey(pool, tenantId, key, guarded);
await pool.end();
log.info(`Update ${chalk.green(key)} succeeded`);
consoleLog.info(`Update ${chalk.green(key)} succeeded`);
},
};
@ -152,7 +154,7 @@ const rotateConfig: CommandModule<unknown, { key: string; tenantId: string }> =
const { rows } = await getRowsByKeys(pool, tenantId, [key]);
if (!rows[0]) {
log.warn('No key found, create a new one');
consoleLog.warn('No key found, create a new one');
}
const getValue = async () => {
@ -174,7 +176,7 @@ const rotateConfig: CommandModule<unknown, { key: string; tenantId: string }> =
await updateValueByKey(pool, tenantId, key, rotated);
await pool.end();
log.info(`Rotate ${chalk.green(key)} succeeded, now it has ${rotated.length} keys`);
consoleLog.info(`Rotate ${chalk.green(key)} succeeded, now it has ${rotated.length} keys`);
},
};
@ -203,14 +205,14 @@ const trimConfig: CommandModule<unknown, { key: string; length: number; tenantId
validateRotateKey(key);
if (length < 1) {
log.error('Invalid length provided');
consoleLog.fatal('Invalid length provided');
}
const pool = await createPoolFromConfig();
const { rows } = await getRowsByKeys(pool, tenantId, [key]);
if (!rows[0]) {
log.warn('No key found, create a new one');
consoleLog.warn('No key found, create a new one');
}
const getValue = async () => {
@ -218,7 +220,9 @@ const trimConfig: CommandModule<unknown, { key: string; length: number; tenantId
if (value.length - length < 1) {
await pool.end();
log.error(`You should keep at least one key in the array, current length=${value.length}`);
consoleLog.fatal(
`You should keep at least one key in the array, current length=${value.length}`
);
}
return value.slice(0, -length);
@ -227,7 +231,7 @@ const trimConfig: CommandModule<unknown, { key: string; length: number; tenantId
await updateValueByKey(pool, tenantId, key, trimmed);
await pool.end();
log.info(`Trim ${chalk.green(key)} succeeded, now it has ${trimmed.length} keys`);
consoleLog.info(`Trim ${chalk.green(key)} succeeded, now it has ${trimmed.length} keys`);
},
};

View file

@ -4,7 +4,7 @@ import { appendPath } from '@silverhand/essentials';
import type { CommonQueryMethods } from 'slonik';
import { sql } from 'slonik';
import { log } from '../../../utils.js';
import { consoleLog } from '../../../utils.js';
/**
* Append Redirect URIs for the default tenant callback in cloud Admin Console.
@ -38,5 +38,5 @@ export const appendAdminConsoleRedirectUris = async (pool: CommonQueryMethods) =
and tenant_id = ${adminTenantId}
`);
log.succeed('Appended initial Redirect URIs to Admin Console:', redirectUris.map(String));
consoleLog.succeed('Appended initial Redirect URIs to Admin Console:', redirectUris.map(String));
};

View file

@ -4,7 +4,7 @@ import type { CommandModule } from 'yargs';
import { createPoolAndDatabaseIfNeeded } from '../../../database.js';
import { doesConfigsTableExist } from '../../../queries/logto-config.js';
import { log, oraPromise } from '../../../utils.js';
import { consoleLog, oraPromise } from '../../../utils.js';
import { getLatestAlterationTimestamp } from '../alteration/index.js';
import { getAlterationDirectory } from '../alteration/utils.js';
@ -53,7 +53,7 @@ const seed: CommandModule<Record<string, unknown>, { swe?: boolean; cloud?: bool
const pool = await createPoolAndDatabaseIfNeeded();
if (swe && (await doesConfigsTableExist(pool))) {
log.info('Seeding skipped');
consoleLog.info('Seeding skipped');
await pool.end();
return;
@ -62,9 +62,8 @@ const seed: CommandModule<Record<string, unknown>, { swe?: boolean; cloud?: bool
try {
await seedByPool(pool, cloud);
} catch (error: unknown) {
console.error(error);
console.log();
log.warn(
consoleLog.error(error);
consoleLog.error(
'Error ocurred during seeding your database.\n\n' +
' Nothing has changed since the seeding process was in a transaction.\n' +
' Try to fix the error and seed again.'

View file

@ -8,7 +8,7 @@ import type { DatabaseTransactionConnection } from 'slonik';
import { z } from 'zod';
import { getRowsByKeys, updateValueByKey } from '../../../queries/logto-config.js';
import { log } from '../../../utils.js';
import { consoleLog } from '../../../utils.js';
import { generateOidcCookieKey, generateOidcPrivateKey } from '../utils.js';
const isBase64FormatPrivateKey = (key: string) => !key.includes('-');
@ -37,7 +37,7 @@ export const seedOidcConfigs = async (pool: DatabaseTransactionConnection, tenan
const included = existingKeys.has(key);
if (included) {
log.info(tenantPrefix, `Key ${chalk.green(key)} exists, skipping`);
consoleLog.info(tenantPrefix, `Key ${chalk.green(key)} exists, skipping`);
}
return !included;
@ -49,16 +49,16 @@ export const seedOidcConfigs = async (pool: DatabaseTransactionConnection, tenan
const { value, fromEnv } = await oidcConfigReaders[key]();
if (fromEnv) {
log.info(tenantPrefix, `Read config ${chalk.green(key)} from env`);
consoleLog.info(tenantPrefix, `Read config ${chalk.green(key)} from env`);
} else {
log.info(tenantPrefix, `Generated config ${chalk.green(key)}`);
consoleLog.info(tenantPrefix, `Generated config ${chalk.green(key)}`);
}
await updateValueByKey(pool, tenantId, key, value);
}
/* eslint-enable no-await-in-loop */
log.succeed(tenantPrefix, 'Seed OIDC config');
consoleLog.succeed(tenantPrefix, 'Seed OIDC config');
};
/**

View file

@ -25,7 +25,7 @@ import { raw } from 'slonik-sql-tag-raw';
import { insertInto } from '../../../database.js';
import { getDatabaseName } from '../../../queries/database.js';
import { updateDatabaseTimestamp } from '../../../queries/system.js';
import { getPathInModule, log } from '../../../utils.js';
import { consoleLog, getPathInModule } from '../../../utils.js';
import { appendAdminConsoleRedirectUris } from './cloud.js';
import { seedOidcConfigs } from './oidc-config.js';
@ -174,7 +174,7 @@ export const seedTables = async (
updateDatabaseTimestamp(connection, latestTimestamp),
]);
log.succeed('Seed data');
consoleLog.succeed('Seed data');
};
export const seedCloud = async (connection: DatabaseTransactionConnection) => {

View file

@ -6,7 +6,7 @@ import type { CommandModule } from 'yargs';
import { createPoolFromConfig } from '../../database.js';
import { getRowByKey, updateValueByKey } from '../../queries/system.js';
import { log } from '../../utils.js';
import { consoleLog } from '../../utils.js';
const validKeysDisplay = chalk.green(systemKeys.join(', '));
@ -19,7 +19,9 @@ const validateKey: ValidateKeysFunction = (key) => {
// Using `.includes()` will result a type error
// eslint-disable-next-line unicorn/prefer-includes
if (!systemKeys.some((element) => element === key)) {
log.error(`Invalid config key ${chalk.red(key)} found, expected one of ${validKeysDisplay}`);
consoleLog.fatal(
`Invalid config key ${chalk.red(key)} found, expected one of ${validKeysDisplay}`
);
}
};
@ -41,7 +43,7 @@ const getConfig: CommandModule<unknown, { key: string }> = {
const value = row?.value;
console.log(
consoleLog.plain(
chalk.magenta(key) +
'=' +
(value === undefined ? chalk.gray(value) : chalk.green(JSON.stringify(value)))
@ -73,7 +75,7 @@ const setConfig: CommandModule<unknown, { key: string; value: string }> = {
await updateValueByKey(pool, key, guarded);
await pool.end();
log.info(`Update ${chalk.green(key)} succeeded`);
consoleLog.info(`Update ${chalk.green(key)} succeeded`);
},
};

View file

@ -2,7 +2,7 @@ import chalk from 'chalk';
import type { CommandModule } from 'yargs';
import { getDatabaseUrlFromConfig } from '../../database.js';
import { log } from '../../utils.js';
import { consoleLog } from '../../utils.js';
import {
validateNodeVersion,
@ -39,7 +39,7 @@ const installLogto = async ({ path, skipSeed, downloadUrl, cloud }: InstallArgs)
// Seed database
if (skipSeed) {
log.info(
consoleLog.info(
`Skipped database seeding.\n\n' + ' You can use the ${chalk.green(
'db seed'
)} command to seed database when ready.\n`

View file

@ -15,9 +15,9 @@ import { packageJson } from '../../package-json.js';
import {
cliConfig,
ConfigKey,
consoleLog,
downloadFile,
isTty,
log,
oraPromise,
safeExecSync,
} from '../../utils.js';
@ -32,11 +32,13 @@ export const validateNodeVersion = () => {
const current = new semver.SemVer(execSync('node -v', { encoding: 'utf8', stdio: 'pipe' }));
if (required.every((version) => version.major !== current.major)) {
log.error(`Logto requires NodeJS ${requiredVersionString}, but ${current.version} found.`);
consoleLog.fatal(
`Logto requires NodeJS ${requiredVersionString}, but ${current.version} found.`
);
}
if (required.some((version) => version.major === current.major && version.compare(current) > 0)) {
log.warn(
consoleLog.warn(
`Logto is tested under NodeJS ${requiredVersionString}, but version ${current.version} found.`
);
}
@ -70,7 +72,7 @@ export const inquireInstancePath = async (initialPath?: string) => {
const validated = validatePath(instancePath);
if (validated !== true) {
log.error(validated);
consoleLog.fatal(validated);
}
return instancePath;
@ -96,7 +98,7 @@ export const validateDatabase = async () => {
});
if (hasPostgresUrl === false) {
log.error('Logto requires a Postgres instance to run.');
consoleLog.fatal('Logto requires a Postgres instance to run.');
}
};
@ -106,8 +108,8 @@ export const downloadRelease = async (url?: string) => {
url ??
`https://github.com/logto-io/logto/releases/download/v${packageJson.version}/logto.tar.gz`;
log.info(`Download Logto from ${from}`);
log.info(`Target ${tarFilePath}`);
consoleLog.info(`Download Logto from ${from}`);
consoleLog.info(`Target ${tarFilePath}`);
await downloadFile(from, tarFilePath);
return tarFilePath;
@ -119,7 +121,7 @@ export const decompress = async (toPath: string, tarPath: string) => {
await fs.mkdir(toPath);
await tar.extract({ file: tarPath, cwd: toPath, strip: 1 });
} catch (error: unknown) {
log.error(error);
consoleLog.fatal(error);
}
};
@ -139,14 +141,14 @@ export const seedDatabase = async (instancePath: string, cloud: boolean) => {
await seedByPool(pool);
await pool.end();
} catch (error: unknown) {
console.error(error);
consoleLog.error(error);
await oraPromise(fs.rm(instancePath, { force: true, recursive: true }), {
text: 'Clean up',
prefixText: chalk.blue('[info]'),
});
log.error(
consoleLog.fatal(
'Error occurred during seeding your Logto database. Nothing has changed since the seeding process was in a transaction.\n\n' +
` To skip the database seeding, append ${chalk.green(
'--skip-seed'
@ -158,12 +160,12 @@ export const seedDatabase = async (instancePath: string, cloud: boolean) => {
export const createEnv = async (instancePath: string, databaseUrl: string) => {
const dotEnvPath = path.resolve(instancePath, '.env');
await fs.writeFile(dotEnvPath, `DB_URL=${databaseUrl}`, 'utf8');
log.info(`Saved database URL to ${chalk.blue(dotEnvPath)}`);
consoleLog.info(`Saved database URL to ${chalk.blue(dotEnvPath)}`);
};
export const logFinale = (instancePath: string) => {
const startCommand = `cd ${instancePath} && npm start`;
log.info(
consoleLog.info(
`Use the command below to start Logto. Happy hacking!\n\n ${chalk.green(startCommand)}`
);
};

View file

@ -3,7 +3,7 @@ import { isBuiltInLanguageTag as isPhrasesBuiltInLanguageTag } from '@logto/phra
import { isBuiltInLanguageTag as isPhrasesUiBuiltInLanguageTag } from '@logto/phrases-ui';
import type { CommandModule } from 'yargs';
import { log } from '../../utils.js';
import { consoleLog } from '../../utils.js';
import { inquireInstancePath } from '../connector/utils.js';
import { createFullTranslation } from './utils.js';
@ -20,18 +20,22 @@ const create: CommandModule<{ path?: string }, { path?: string; 'language-tag':
}),
handler: async ({ path: inputPath, languageTag }) => {
if (!isLanguageTag(languageTag)) {
log.error('Invalid language tag. Run `logto translate list-tags` to see available list.');
consoleLog.fatal(
'Invalid language tag. Run `logto translate list-tags` to see available list.'
);
}
const instancePath = await inquireInstancePath(inputPath);
if (isPhrasesBuiltInLanguageTag(languageTag)) {
log.info(languageTag + ' is a built-in tag of phrases, updating untranslated phrases');
consoleLog.info(languageTag + ' is a built-in tag of phrases, updating untranslated phrases');
}
await createFullTranslation({ instancePath, packageName: 'phrases', languageTag });
if (isPhrasesUiBuiltInLanguageTag(languageTag)) {
log.info(languageTag + ' is a built-in tag of phrases-ui, updating untranslated phrases');
consoleLog.info(
languageTag + ' is a built-in tag of phrases-ui, updating untranslated phrases'
);
}
await createFullTranslation({
instancePath,

View file

@ -4,13 +4,15 @@ import { isBuiltInLanguageTag as isPhrasesUiBuiltInLanguageTag } from '@logto/ph
import chalk from 'chalk';
import type { CommandModule } from 'yargs';
import { consoleLog } from '../../utils.js';
const listTags: CommandModule<Record<string, unknown>> = {
command: ['list-tags', 'list'],
describe: 'List all available language tags',
handler: async () => {
for (const tag of Object.keys(languages)) {
console.log(
consoleLog.plain(
...[
tag,
isPhrasesBuiltInLanguageTag(tag) && chalk.blue('phrases'),

View file

@ -6,7 +6,7 @@ import { type Got, got, HTTPError } from 'got';
import { HttpsProxyAgent } from 'hpagent';
import { z } from 'zod';
import { getProxy, log } from '../../utils.js';
import { consoleLog, getProxy } from '../../utils.js';
export const createOpenaiApi = () => {
const proxy = getProxy();
@ -47,10 +47,10 @@ export const translate = async (api: Got, languageTag: LanguageTag, filePath: st
})
.json(),
(error) => {
log.warn(`Error while translating ${filePath}:`, String(error));
consoleLog.warn(`Error while translating ${filePath}:`, String(error));
if (error instanceof HTTPError) {
log.warn(error.response.body);
consoleLog.warn(error.response.body);
}
}
);
@ -62,7 +62,7 @@ export const translate = async (api: Got, languageTag: LanguageTag, filePath: st
const guarded = gptResponseGuard.safeParse(response);
if (!guarded.success) {
log.warn(`Error while guarding response for ${filePath}:`, response);
consoleLog.warn(`Error while guarding response for ${filePath}:`, response);
return;
}
@ -70,13 +70,13 @@ export const translate = async (api: Got, languageTag: LanguageTag, filePath: st
const [entity] = guarded.data.choices;
if (!entity) {
log.warn(`No choice found in response when translating ${filePath}`);
consoleLog.warn(`No choice found in response when translating ${filePath}`);
return;
}
if (entity.finish_reason !== 'stop') {
log.warn(`Unexpected finish reason ${entity.finish_reason} for ${filePath}`);
consoleLog.warn(`Unexpected finish reason ${entity.finish_reason} for ${filePath}`);
}
const { content } = entity.message;
@ -88,7 +88,7 @@ export const translate = async (api: Got, languageTag: LanguageTag, filePath: st
return content;
}
log.warn('No matching code snippet from response:', content);
consoleLog.warn('No matching code snippet from response:', content);
}
return matched;

View file

@ -6,7 +6,7 @@ import { type LanguageTag } from '@logto/language-kit';
import { conditionalString } from '@silverhand/essentials';
import PQueue from 'p-queue';
import { log } from '../../utils.js';
import { consoleLog } from '../../utils.js';
import { createOpenaiApi, translate } from './openai.js';
@ -33,7 +33,7 @@ export const readBaseLocaleFiles = async (directory: string): Promise<string[]>
const stat = await fs.stat(enDirectory);
if (!stat.isDirectory()) {
log.error(directory, 'has no `' + baseLanguage.toLowerCase() + '` directory');
consoleLog.fatal(directory, 'has no `' + baseLanguage.toLowerCase() + '` directory');
}
return readLocaleFiles(enDirectory);
@ -58,7 +58,7 @@ export const createFullTranslation = async ({
const files = await readBaseLocaleFiles(directory);
if (verbose) {
log.info(
consoleLog.info(
'Found ' +
String(files.length) +
' file' +
@ -85,7 +85,9 @@ export const createFullTranslation = async ({
}
if (verbose) {
log.info(`Target path ${targetPath} exists and has no untranslated mark, skipping`);
consoleLog.info(
`Target path ${targetPath} exists and has no untranslated mark, skipping`
);
}
return;
@ -101,16 +103,16 @@ export const createFullTranslation = async ({
}
void queue.add(async () => {
log.info(`Translating ${translationPath}`);
consoleLog.info(`Translating ${translationPath}`);
const result = await translate(openai, languageTag, translationPath);
if (!result) {
log.error(`Unable to translate ${translationPath}`);
consoleLog.fatal(`Unable to translate ${translationPath}`);
}
await fs.mkdir(path.parse(targetPath).dir, { recursive: true });
await fs.writeFile(targetPath, result);
log.succeed(`Translated ${targetPath}`);
consoleLog.succeed(`Translated ${targetPath}`);
});
}
/* eslint-enable no-await-in-loop */

View file

@ -1,5 +1,7 @@
import chalk from 'chalk';
import { consoleLog } from '../utils.js';
import { notImplemented } from './consts.js';
import { loadConnector } from './loader.js';
import type { ConnectorFactory, ConnectorPackage } from './types.js';
@ -30,10 +32,8 @@ export const loadConnectorFactories = async (
};
} catch (error: unknown) {
if (error instanceof Error) {
console.log(
`${chalk.red(
`[load-connector] skip ${chalk.bold(name)} due to error: ${error.message}`
)}`
consoleLog.error(
`[load-connector] skip ${chalk.bold(name)} due to error: ${error.message}`
);
return;

View file

@ -4,6 +4,8 @@ import type { AllConnector, CreateConnector } from '@logto/connector-kit';
import connectorKitMeta from '@logto/connector-kit/package.json' assert { type: 'json' };
import { satisfies } from 'semver';
import { consoleLog } from '../utils.js';
import { isKeyInObject } from './utils.js';
const connectorKit = '@logto/connector-kit';
@ -21,7 +23,7 @@ const checkConnectorKitVersion = (dependencies: unknown, ignoreVersionMismatch:
const message = `Connector requires ${connectorKit} to be ${value}, but the version here is ${currentVersion}.`;
if (ignoreVersionMismatch) {
console.warn(`[warn] ${message}\n\nThis is highly discouraged in production.`);
consoleLog.warn(`${message}\n\nThis is highly discouraged in production.`);
return;
}
@ -52,7 +54,7 @@ export const loadConnector = async (
// CJS pattern
if (isKeyInObject(loaded.default, 'default')) {
if (typeof loaded.default.default === 'function') {
console.log(`[warn] Load connector ${connectorPath} in CJS mode`);
consoleLog.warn(`Load connector ${connectorPath} in CJS mode`);
// eslint-disable-next-line no-restricted-syntax
return loaded.default.default as CreateConnector<AllConnector>;

View file

@ -6,7 +6,7 @@ import { DatabaseError } from 'pg-protocol';
import { createPool, parseDsn, sql, stringifyDsn } from 'slonik';
import { createInterceptors } from 'slonik-interceptor-preset';
import { ConfigKey, getCliConfigWithPrompt, log } from './utils.js';
import { ConfigKey, consoleLog, getCliConfigWithPrompt } from './utils.js';
export const defaultDatabaseUrl = 'postgresql://localhost:5432/logto';
@ -39,7 +39,7 @@ export const createPoolAndDatabaseIfNeeded = async () => {
// Database does not exist, try to create one
// https://www.postgresql.org/docs/14/errcodes-appendix.html
if (!(error instanceof DatabaseError && error.code === '3D000')) {
log.error(error);
consoleLog.fatal(error);
}
const databaseUrl = await getDatabaseUrlFromConfig();
@ -59,7 +59,7 @@ export const createPoolAndDatabaseIfNeeded = async () => {
`);
await maintenancePool.end();
log.succeed(`Created database ${databaseName}`);
consoleLog.succeed(`Created database ${databaseName}`);
return createPoolFromConfig();
}

View file

@ -8,7 +8,7 @@ import database from './commands/database/index.js';
import install from './commands/install/index.js';
import translate from './commands/translate/index.js';
import { packageJson } from './package-json.js';
import { cliConfig, ConfigKey } from './utils.js';
import { cliConfig, ConfigKey, consoleLog } from './utils.js';
void yargs(hideBin(process.argv))
.version(false)
@ -30,7 +30,7 @@ void yargs(hideBin(process.argv))
})
.middleware(({ version }) => {
if (version) {
console.log(packageJson.name + ' v' + packageJson.version);
consoleLog.plain(packageJson.name + ' v' + packageJson.version);
// eslint-disable-next-line unicorn/no-process-exit
process.exit(0);
}

View file

@ -4,6 +4,7 @@ import { readdir, readFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import path from 'node:path';
import { ConsoleLog } from '@logto/shared';
import type { Optional } from '@silverhand/essentials';
import { conditionalString } from '@silverhand/essentials';
import chalk from 'chalk';
@ -21,29 +22,17 @@ export const safeExecSync = (command: string) => {
} catch {}
};
type Log = Readonly<{
info: typeof console.log;
succeed: typeof console.log;
warn: typeof console.log;
error: (...args: Parameters<typeof console.log>) => never;
}>;
export const log: Log = Object.freeze({
info: (...args) => {
console.log(chalk.blue('[info]'), ...args);
},
succeed: (...args) => {
log.info(chalk.green('✔'), ...args);
},
warn: (...args) => {
console.warn(chalk.yellow('[warn]'), ...args);
},
error: (...args) => {
console.error(chalk.red('[error]'), ...args);
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
},
});
// The explicit type annotation is required to make `.fatal()`
// works correctly without `return`:
//
// ```ts
// const foo: number | undefined;
// consoleLog.fatal();
// typeof foo // Still `number | undefined` without explicit type annotation
// ```
//
// For now I have no idea why.
export const consoleLog: ConsoleLog = new ConsoleLog();
export const getProxy = () => {
const { HTTPS_PROXY, HTTP_PROXY, https_proxy, http_proxy } = process.env;
@ -110,7 +99,7 @@ export const oraPromise = async <T>(
spinner.fail();
if (exitOnError) {
log.error(error);
consoleLog.fatal(error);
}
throw error;

View file

@ -118,7 +118,10 @@
"node": "^18.12.0"
},
"eslintConfig": {
"extends": "@silverhand"
"extends": "@silverhand",
"rules": {
"no-console": "error"
}
},
"prettier": "@silverhand/eslint-config/.prettierrc"
}

View file

@ -8,13 +8,14 @@ import type Koa from 'koa';
import { EnvSet } from '#src/env-set/index.js';
import { TenantNotFoundError, tenantPool } from '#src/tenants/index.js';
import { consoleLog } from '#src/utils/console.js';
import { getTenantId } from '#src/utils/tenant.js';
const logListening = (type: 'core' | 'admin' = 'core') => {
const urlSet = type === 'core' ? EnvSet.values.urlSet : EnvSet.values.adminUrlSet;
for (const url of urlSet.deduplicated()) {
console.log(chalk.bold(chalk.green(`${toTitle(type)} app is running at ${url.toString()}`)));
consoleLog.info(chalk.bold(`${toTitle(type)} app is running at ${url.toString()}`));
}
};

View file

@ -3,6 +3,7 @@ import { type Optional, conditional, yes } from '@silverhand/essentials';
import { createClient, type RedisClientType } from 'redis';
import { EnvSet } from '#src/env-set/index.js';
import { consoleLog } from '#src/utils/console.js';
import { type CacheStore } from './types.js';
@ -39,16 +40,16 @@ export class RedisCache implements CacheStore {
async connect() {
if (this.client) {
await this.client.connect();
console.log('[CACHE] Connected to Redis');
consoleLog.info('[CACHE] Connected to Redis');
} else {
console.warn('[CACHE] No Redis client initialized, skipping');
consoleLog.warn('[CACHE] No Redis client initialized, skipping');
}
}
async disconnect() {
if (this.client) {
await this.client.disconnect();
console.log('[CACHE] Disconnected from Redis');
consoleLog.info('[CACHE] Disconnected from Redis');
}
}
}

View file

@ -2,6 +2,8 @@ import { getAvailableAlterations } from '@logto/cli/lib/commands/database/altera
import chalk from 'chalk';
import type { DatabasePool } from 'slonik';
import { consoleLog } from '#src/utils/console.js';
export const checkAlterationState = async (pool: DatabasePool) => {
const alterations = await getAvailableAlterations(pool);
@ -9,10 +11,8 @@ export const checkAlterationState = async (pool: DatabasePool) => {
return;
}
console.error(
`${chalk.red(
'[error]'
)} Found undeployed database alterations, you must deploy them first by ${chalk.green(
consoleLog.error(
`Found undeployed database alterations, you must deploy them first by ${chalk.green(
'npm run alteration deploy'
)} command.\n\n` +
` See ${chalk.blue(

View file

@ -1,5 +1,7 @@
import type Provider from 'oidc-provider';
import { consoleLog } from '#src/utils/console.js';
import { grantListener, grantRevocationListener } from './grant.js';
import { interactionEndedListener, interactionStartedListener } from './interaction.js';
@ -14,6 +16,6 @@ export const addOidcEventListeners = (provider: Provider) => {
provider.addListener('interaction.started', interactionStartedListener);
provider.addListener('interaction.ended', interactionEndedListener);
provider.addListener('server_error', (_, error) => {
console.error('OIDC Provider server_error:', error);
consoleLog.error('OIDC Provider server_error:', error);
});
};

View file

@ -5,13 +5,14 @@ import Koa from 'koa';
import { checkAlterationState } from './env-set/check-alteration-state.js';
import SystemContext from './tenants/SystemContext.js';
import { consoleLog } from './utils/console.js';
dotenv.config({ path: await findUp('.env', {}) });
const { appInsights } = await import('@logto/app-insights/node');
if (await appInsights.setup('logto')) {
console.debug('Initialized ApplicationInsights');
consoleLog.info('Initialized ApplicationInsights');
}
// Import after env has been configured
@ -40,8 +41,8 @@ try {
const { default: initApp } = await import('./app/init.js');
await initApp(app);
} catch (error: unknown) {
console.error('Error while initializing app:');
console.error(error);
consoleLog.error('Error while initializing app:');
consoleLog.error(error);
await Promise.all([trySafe(tenantPool.endAll()), trySafe(redisCache.disconnect())]);
}

View file

@ -13,6 +13,7 @@ import type Provider from 'oidc-provider';
import { LogEntry } from '#src/middleware/koa-audit-log.js';
import type Queries from '#src/tenants/Queries.js';
import { consoleLog } from '#src/utils/console.js';
const parseResponse = ({ statusCode, body }: Response) => ({
statusCode,
@ -78,7 +79,7 @@ export const createHookLibrary = (queries: Queries) => {
await Promise.all(
rows.map(async ({ config: { url, headers, retries }, id }) => {
console.log(`\tTriggering hook ${id} due to ${hookEvent} event`);
consoleLog.info(`\tTriggering hook ${id} due to ${hookEvent} event`);
const json: HookEventPayload = { hookId: id, ...payload };
const logEntry = new LogEntry(`TriggerHook.${hookEvent}`);
@ -105,7 +106,7 @@ export const createHookLibrary = (queries: Queries) => {
});
});
console.log(
consoleLog.info(
`\tHook ${id} ${logEntry.payload.result === LogResult.Success ? 'succeeded' : 'failed'}`
);

View file

@ -4,6 +4,7 @@ import chalk from 'chalk';
import { z, ZodError } from 'zod';
import type Queries from '#src/tenants/Queries.js';
import { consoleLog } from '#src/utils/console.js';
export const createLogtoConfigLibrary = ({ getRowsByKeys }: Queries['logtoConfigs']) => {
const getOidcConfigs = async (): Promise<LogtoOidcConfigType> => {
@ -15,17 +16,17 @@ export const createLogtoConfigLibrary = ({ getRowsByKeys }: Queries['logtoConfig
.parse(Object.fromEntries(rows.map(({ key, value }) => [key, value])));
} catch (error: unknown) {
if (error instanceof ZodError) {
console.error(
consoleLog.error(
error.issues
.map(({ message, path }) => `${message} at ${chalk.green(path.join('.'))}`)
.join('\n')
);
} else {
console.error(error);
consoleLog.error(error);
}
console.error(
`\n${chalk.red('[error]')} Failed to get OIDC configs from your Logto database.` +
consoleLog.error(
`\nFailed to get OIDC configs from your Logto database.` +
' Did you forget to seed your database?\n\n' +
` Use ${chalk.green('npm run cli db seed')} to seed your Logto database;\n` +
` Or use ${chalk.green('npm run cli db seed oidc')} to seed OIDC configs alone.\n`

View file

@ -11,6 +11,7 @@ import { z } from 'zod';
import { EnvSet } from '#src/env-set/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import assertThat from '#src/utils/assert-that.js';
import { consoleLog } from '#src/utils/console.js';
import { getAdminTenantTokenValidationSet } from './utils.js';
@ -57,7 +58,7 @@ export const verifyBearerTokenFromRequest = async (
const userId = request.headers['development-user-id']?.toString() ?? developmentUserId;
if ((!isProduction || isIntegrationTest) && userId) {
console.log(`Found dev user ID ${userId}, skip token validation.`);
consoleLog.warn(`Found dev user ID ${userId}, skip token validation.`);
return { sub: userId, clientId: undefined, scopes: [defaultManagementApi.scope.name] };
}

View file

@ -5,6 +5,7 @@ import { HttpError } from 'koa';
import { EnvSet } from '#src/env-set/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import { consoleLog } from '#src/utils/console.js';
export default function koaErrorHandler<StateT, ContextT, BodyT>(): Middleware<
StateT,
@ -16,7 +17,7 @@ export default function koaErrorHandler<StateT, ContextT, BodyT>(): Middleware<
await next();
} catch (error: unknown) {
if (!EnvSet.values.isProduction) {
console.error(error);
consoleLog.error(error);
}
// Report all exceptions to ApplicationInsights
@ -36,7 +37,7 @@ export default function koaErrorHandler<StateT, ContextT, BodyT>(): Middleware<
// Should log 500 errors in prod anyway
if (EnvSet.values.isProduction) {
console.error(error);
consoleLog.error(error);
}
ctx.status = 500;

View file

@ -9,6 +9,7 @@ import { EnvSet } from '#src/env-set/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import { ResponseBodyError, StatusCodeError } from '#src/errors/ServerError/index.js';
import assertThat from '#src/utils/assert-that.js';
import { consoleLog } from '#src/utils/console.js';
/** Configure what and how to guard. */
export type GuardConfig<QueryT, BodyT, ParametersT, ResponseT, FilesT> = {
@ -167,7 +168,7 @@ export default function koaGuard<
if (!result.success) {
if (!EnvSet.values.isProduction) {
console.error('Invalid response:', result.error);
consoleLog.error('Invalid response:', result.error);
}
throw new ResponseBodyError(result.error);

View file

@ -24,6 +24,7 @@ import { isOriginAllowed, validateCustomClientMetadata } from '#src/oidc/utils.j
import { routes } from '#src/routes/consts.js';
import type Libraries from '#src/tenants/Libraries.js';
import type Queries from '#src/tenants/Queries.js';
import { consoleLog } from '#src/utils/console.js';
import { getUserClaimData, getUserClaims } from './scope.js';
import { OIDCExtraParametersKey, InteractionMode } from './type.js';
@ -63,7 +64,7 @@ export default function initOidc(
const oidc = new Provider(issuer, {
adapter: postgresAdapter.bind(null, envSet, queries),
renderError: (_ctx, _out, error) => {
console.error(error);
consoleLog.error(error);
throw error;
},

View file

@ -4,6 +4,8 @@ import { convertToIdentifiers } from '@logto/shared';
import type { CommonQueryMethods } from 'slonik';
import { sql } from 'slonik';
import { consoleLog } from '#src/utils/console.js';
const { table, fields } = convertToIdentifiers(Systems);
export default class SystemContext {
@ -23,7 +25,7 @@ export default class SystemContext {
const result = storageProviderDataGuard.safeParse(record.value);
if (!result.success) {
console.error('Failed to parse storage provider config:', result.error);
consoleLog.error('Failed to parse storage provider config:', result.error);
return;
}

View file

@ -2,6 +2,7 @@ import { LRUCache } from 'lru-cache';
import { redisCache } from '#src/caches/index.js';
import { EnvSet } from '#src/env-set/index.js';
import { consoleLog } from '#src/utils/console.js';
import Tenant from './Tenant.js';
@ -21,7 +22,7 @@ export class TenantPool {
return tenant;
}
console.log('Init tenant:', tenantId);
consoleLog.info('Init tenant:', tenantId);
const newTenant = Tenant.create(tenantId, redisCache);
this.cache.set(tenantId, newTenant);

View file

@ -21,6 +21,7 @@ export const createMockProvider = (
interactionDetails?: jest.Mock,
Grant?: typeof GrantMock
): Provider => {
// eslint-disable-next-line no-console
const originalWarn = console.warn;
const warn = jest.spyOn(console, 'warn').mockImplementation((...args) => {
// Disable while creating. Too many warnings.

View file

@ -0,0 +1,3 @@
import { ConsoleLog } from '@logto/shared';
export const consoleLog: ConsoleLog = new ConsoleLog();

View file

@ -4,6 +4,8 @@ import { conditionalString } from '@silverhand/essentials';
import { EnvSet, getTenantEndpoint } from '#src/env-set/index.js';
import { consoleLog } from './console.js';
const normalizePathname = (pathname: string) =>
pathname + conditionalString(!pathname.endsWith('/') && '/');
@ -57,7 +59,7 @@ export const getTenantId = (url: URL) => {
}
if ((!isProduction || isIntegrationTest) && developmentTenantId) {
console.log(`Found dev tenant ID ${developmentTenantId}.`);
consoleLog.warn(`Found dev tenant ID ${developmentTenantId}.`);
return developmentTenantId;
}

View file

@ -0,0 +1,27 @@
import chalk from 'chalk';
export default class ConsoleLog {
plain = console.log;
info: typeof console.log = (...args) => {
console.log(chalk.bold(chalk.blue('info')), ...args);
};
succeed: typeof console.log = (...args) => {
this.info(chalk.green('✔'), ...args);
};
warn: typeof console.log = (...args) => {
console.warn(chalk.bold(chalk.yellow('warn')), ...args);
};
error: typeof console.log = (...args) => {
console.error(chalk.bold(chalk.red('error')), ...args);
};
fatal: (...args: Parameters<typeof console.log>) => never = (...args) => {
console.error(chalk.bold(chalk.red('fatal')), ...args);
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
};
}

View file

@ -1,2 +1,3 @@
export { default as UrlSet } from './UrlSet.js';
export { default as GlobalValues } from './GlobalValues.js';
export { default as ConsoleLog } from './ConsoleLog.js';