mirror of
https://github.com/chenasraf/simple-scaffold.git
synced 2026-05-17 17:28:09 +00:00
feat: update all ouput logging
This commit is contained in:
@@ -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"],
|
||||
@@ -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
|
||||
|
||||
@@ -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>"}`,
|
||||
)
|
||||
|
||||
|
||||
11
src/file.ts
11
src/file.ts
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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}`))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user