feat: update all ouput logging

This commit is contained in:
2026-03-23 17:02:33 +02:00
parent 972d199fbb
commit bb0248f91a
9 changed files with 86 additions and 26 deletions

View File

@@ -1,7 +1,7 @@
// @ts-check
/** @type {import('./dist').ScaffoldConfigFile} */
module.exports = (conf) => {
console.log("Config:", conf)
module.exports = () => {
// console.log("Config:", conf)
return {
default: {
templates: ["examples/test-input/Component"],

View File

@@ -30,7 +30,7 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
console.log(pkg.version)
return
}
log(config, LogLevel.info, `Simple Scaffold v${pkg.version}`)
log(config, LogLevel.debug, `Simple Scaffold v${pkg.version}`)
config.tmpDir = generateUniqueTmpPath()
try {
// Auto-detect config file in cwd if not explicitly provided

View File

@@ -38,7 +38,7 @@ function isWrappedWithQuotes(string: string): boolean {
/** Loads and resolves a config file (local or remote). @internal */
export async function getConfigFile(config: ScaffoldCmdConfig): Promise<ScaffoldConfigMap> {
if (config.git && !config.git.includes("://")) {
log(config, LogLevel.info, `Loading config from GitHub ${config.git}`)
log(config, LogLevel.debug, `Loading config from GitHub ${config.git}`)
config.git = githubPartToUrl(config.git)
}
@@ -46,7 +46,7 @@ export async function getConfigFile(config: ScaffoldCmdConfig): Promise<Scaffold
const configFilename = config.config
const configPath = isGit ? config.git : configFilename
log(config, LogLevel.info, `Loading config from file ${configFilename}`)
log(config, LogLevel.debug, `Loading config from file ${configFilename}`)
const configPromise = await (isGit
? getRemoteConfig({
@@ -157,11 +157,11 @@ export async function getLocalConfig(
if (!exists) {
throw new Error(`Could not find config file in directory ${absolutePath}`)
}
log(logConfig, LogLevel.info, `Loading config from: ${path.resolve(absolutePath, file)}`)
log(logConfig, LogLevel.debug, `Loading config from: ${path.resolve(absolutePath, file)}`)
return wrapNoopResolver(import(path.resolve(absolutePath, file)))
}
log(logConfig, LogLevel.info, `Loading config from: ${absolutePath}`)
log(logConfig, LogLevel.debug, `Loading config from: ${absolutePath}`)
return wrapNoopResolver(import(absolutePath))
}
@@ -173,7 +173,7 @@ export async function getRemoteConfig(
log(
logConfig,
LogLevel.info,
LogLevel.debug,
`Loading config from remote ${git}, config file ${configFile || "<auto-detect>"}`,
)

View File

@@ -130,7 +130,7 @@ export async function copyFileTransformed(
): Promise<void> {
if (!exists || overwrite) {
if (exists && overwrite) {
log(config, LogLevel.info, `File ${outputPath} exists, overwriting`)
log(config, LogLevel.debug, `Overwriting ${outputPath}`)
}
log(config, LogLevel.debug, `Processing file ${inputPath}`)
const templateBuffer = await readFile(inputPath)
@@ -142,13 +142,12 @@ export async function copyFileTransformed(
if (!config.dryRun) {
await writeFile(outputPath, finalOutputContents)
} else {
log(config, LogLevel.info, "Dry Run. Output should be:")
log(config, LogLevel.info, finalOutputContents.toString())
log(config, LogLevel.debug, "Dry run — output would be:")
log(config, LogLevel.debug, finalOutputContents.toString())
}
} else if (exists) {
log(config, LogLevel.info, `File ${outputPath} already exists, skipping`)
log(config, LogLevel.debug, `Skipped ${outputPath} (already exists)`)
}
log(config, LogLevel.info, "Done.")
}
/** Computes the output directory for a file, combining the output path, base path, and optional subdir. */
@@ -205,7 +204,7 @@ export async function handleTemplateFile(
await createDirIfNotExists(path.dirname(outputPath), config)
const shouldWrite = (!exists || overwrite) && !config.dryRun
log(config, LogLevel.info, `Writing to ${outputPath}`)
log(config, LogLevel.debug, `Writing to ${outputPath}`)
await copyFileTransformed(config, { exists, overwrite, outputPath, inputPath })
return shouldWrite ? outputPath : null
} catch (e: unknown) {

View File

@@ -13,7 +13,7 @@ export async function getGitConfig(
): Promise<AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>> {
const repoUrl = `${url.protocol}//${url.host}${url.pathname}`
log(logConfig, LogLevel.info, `Cloning git repo ${repoUrl}`)
log(logConfig, LogLevel.debug, `Cloning git repo ${repoUrl}`)
return new Promise((res, reject) => {
log(logConfig, LogLevel.debug, `Cloning git repo to ${tmpPath}`)
@@ -43,7 +43,7 @@ export async function loadGitConfig({
file: string
tmpPath: string
}): Promise<AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>> {
log(logConfig, LogLevel.info, `Loading config from git repo: ${repoUrl}`)
log(logConfig, LogLevel.debug, `Loading config from git repo: ${repoUrl}`)
const filename = file || (await findConfigFile(tmpPath))
const absolutePath = path.resolve(tmpPath, filename)
log(logConfig, LogLevel.debug, `Resolving config file: ${absolutePath}`)
@@ -52,7 +52,7 @@ export async function loadGitConfig({
logConfig,
)
log(logConfig, LogLevel.info, `Loaded config from git`)
log(logConfig, LogLevel.debug, `Loaded config from git`)
log(logConfig, LogLevel.debug, `Raw config:`, loadedConfig)
const fixedConfig: ScaffoldConfigMap = {}
for (const [k, v] of Object.entries(loadedConfig)) {

View File

@@ -1,4 +1,5 @@
import util from "util"
import path from "node:path"
import { LogConfig, LogLevel, ScaffoldConfig } from "./types"
import { colorize, TermColor } from "./colors"
@@ -14,8 +15,8 @@ const LOG_PRIORITY: Record<LogLevel, number> = {
/** Maps each log level to a terminal color. */
const LOG_LEVEL_COLOR: Record<LogLevel, TermColor> = {
[LogLevel.none]: "reset",
[LogLevel.debug]: "blue",
[LogLevel.info]: "dim",
[LogLevel.debug]: "dim",
[LogLevel.info]: "reset",
[LogLevel.warning]: "yellow",
[LogLevel.error]: "red",
}
@@ -64,7 +65,7 @@ export function logInputFile(
log(config, LogLevel.debug, data)
}
/** Logs the full scaffold configuration at debug level, with a data summary at info level. */
/** Logs the full scaffold configuration at debug level. */
export function logInitStep(config: ScaffoldConfig): void {
log(config, LogLevel.debug, "Full config:", {
name: config.name,
@@ -79,5 +80,56 @@ export function logInitStep(config: ScaffoldConfig): void {
dryRun: config.dryRun,
beforeWrite: config.beforeWrite,
} as Record<keyof ScaffoldConfig, unknown>)
log(config, LogLevel.info, "Data:", config.data)
}
/**
* Logs a tree of created files, grouped by directory.
*/
export function logFileTree(config: LogConfig, files: string[]): void {
if (files.length === 0) return
// Find common prefix to make paths relative
const commonDir = files.reduce((prefix, file) => {
while (!file.startsWith(prefix)) {
prefix = path.dirname(prefix)
}
return prefix
}, path.dirname(files[0]))
log(config, LogLevel.info, "")
log(config, LogLevel.info, colorize.bold(`📁 ${commonDir}`))
const relPaths = files.map((f) => path.relative(commonDir, f)).sort()
for (let i = 0; i < relPaths.length; i++) {
const isLast = i === relPaths.length - 1
const prefix = isLast ? "└── " : "├── "
log(config, LogLevel.info, colorize.dim(prefix) + relPaths[i])
}
}
/**
* Logs a final summary line with file count and elapsed time.
*/
export function logSummary(
config: LogConfig,
fileCount: number,
elapsedMs: number,
dryRun?: boolean,
): void {
const timeStr =
elapsedMs < 1000 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1000).toFixed(2)}s`
log(config, LogLevel.info, "")
if (dryRun) {
log(
config,
LogLevel.info,
colorize.yellow(`🏜️ Dry run complete — ${fileCount} file(s) would be created (${timeStr})`),
)
} else if (fileCount === 0) {
log(config, LogLevel.info, colorize.yellow(`⚠️ No files created (${timeStr})`))
} else {
log(config, LogLevel.info, colorize.green(`✅ Created ${fileCount} file(s) in ${timeStr}`))
}
}

View File

@@ -134,7 +134,7 @@ export function handlebarsParse(
return Buffer.from(outputContents)
} catch (e) {
log(config, LogLevel.debug, e)
log(config, LogLevel.info, "Couldn't parse file with handlebars, returning original content")
log(config, LogLevel.debug, "Couldn't parse file with handlebars, returning original content")
return Buffer.from(templateBuffer)
}
}

View File

@@ -13,7 +13,7 @@ import { isDir, getTemplateGlobInfo, getFileList, handleTemplateFile, GlobInfo }
import { removeGlob, makeRelativePath, getBasePath } from "./path-utils"
import { LogLevel, MinimalConfig, Resolver, ScaffoldCmdConfig, ScaffoldConfig } from "./types"
import { registerHelpers } from "./parser"
import { log, logInitStep } from "./logger"
import { log, logInitStep, logFileTree, logSummary } from "./logger"
import { parseConfigFile } from "./config"
import { resolveInputs } from "./prompts"
import { loadIgnorePatterns, filterIgnoredFiles } from "./ignore"
@@ -57,11 +57,15 @@ export async function Scaffold(config: ScaffoldConfig): Promise<void> {
await assertConfigValid(config)
config = await resolveInputs(config)
registerHelpers(config)
const startTime = performance.now()
const writtenFiles: string[] = []
try {
config.data = { name: config.name, ...config.data }
logInitStep(config)
log(config, LogLevel.info, `Scaffolding "${config.name}"...`)
const excludes = config.templates.filter((t) => t.startsWith("!"))
const includes = config.templates.filter((t) => !t.startsWith("!"))
@@ -76,6 +80,11 @@ export async function Scaffold(config: ScaffoldConfig): Promise<void> {
throw e
}
const elapsed = performance.now() - startTime
logFileTree(config, writtenFiles)
logSummary(config, writtenFiles.length, elapsed, config.dryRun)
if (config.afterScaffold) {
await runAfterScaffoldHook(config, writtenFiles)
}

View File

@@ -115,7 +115,7 @@ describe("logger", () => {
expect(consoleSpy.log).toHaveBeenCalled()
})
test("does not log config at info level (debug only)", () => {
test("does not log at info level (debug only)", () => {
const config: ScaffoldConfig = {
name: "test",
output: "output",
@@ -124,8 +124,8 @@ describe("logger", () => {
data: { name: "test" },
}
logInitStep(config)
// Should only log the "Data:" line at info, not the "Full config:" at debug
expect(consoleSpy.log).toHaveBeenCalledTimes(1)
// Full config is debug-only, nothing logged at info
expect(consoleSpy.log).not.toHaveBeenCalled()
})
})