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
|
// @ts-check
|
||||||
/** @type {import('./dist').ScaffoldConfigFile} */
|
/** @type {import('./dist').ScaffoldConfigFile} */
|
||||||
module.exports = (conf) => {
|
module.exports = () => {
|
||||||
console.log("Config:", conf)
|
// console.log("Config:", conf)
|
||||||
return {
|
return {
|
||||||
default: {
|
default: {
|
||||||
templates: ["examples/test-input/Component"],
|
templates: ["examples/test-input/Component"],
|
||||||
@@ -30,7 +30,7 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
|
|||||||
console.log(pkg.version)
|
console.log(pkg.version)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log(config, LogLevel.info, `Simple Scaffold v${pkg.version}`)
|
log(config, LogLevel.debug, `Simple Scaffold v${pkg.version}`)
|
||||||
config.tmpDir = generateUniqueTmpPath()
|
config.tmpDir = generateUniqueTmpPath()
|
||||||
try {
|
try {
|
||||||
// Auto-detect config file in cwd if not explicitly provided
|
// 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 */
|
/** Loads and resolves a config file (local or remote). @internal */
|
||||||
export async function getConfigFile(config: ScaffoldCmdConfig): Promise<ScaffoldConfigMap> {
|
export async function getConfigFile(config: ScaffoldCmdConfig): Promise<ScaffoldConfigMap> {
|
||||||
if (config.git && !config.git.includes("://")) {
|
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)
|
config.git = githubPartToUrl(config.git)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export async function getConfigFile(config: ScaffoldCmdConfig): Promise<Scaffold
|
|||||||
const configFilename = config.config
|
const configFilename = config.config
|
||||||
const configPath = isGit ? config.git : configFilename
|
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
|
const configPromise = await (isGit
|
||||||
? getRemoteConfig({
|
? getRemoteConfig({
|
||||||
@@ -157,11 +157,11 @@ export async function getLocalConfig(
|
|||||||
if (!exists) {
|
if (!exists) {
|
||||||
throw new Error(`Could not find config file in directory ${absolutePath}`)
|
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)))
|
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))
|
return wrapNoopResolver(import(absolutePath))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ export async function getRemoteConfig(
|
|||||||
|
|
||||||
log(
|
log(
|
||||||
logConfig,
|
logConfig,
|
||||||
LogLevel.info,
|
LogLevel.debug,
|
||||||
`Loading config from remote ${git}, config file ${configFile || "<auto-detect>"}`,
|
`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> {
|
): Promise<void> {
|
||||||
if (!exists || overwrite) {
|
if (!exists || overwrite) {
|
||||||
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}`)
|
log(config, LogLevel.debug, `Processing file ${inputPath}`)
|
||||||
const templateBuffer = await readFile(inputPath)
|
const templateBuffer = await readFile(inputPath)
|
||||||
@@ -142,13 +142,12 @@ export async function copyFileTransformed(
|
|||||||
if (!config.dryRun) {
|
if (!config.dryRun) {
|
||||||
await writeFile(outputPath, finalOutputContents)
|
await writeFile(outputPath, finalOutputContents)
|
||||||
} else {
|
} else {
|
||||||
log(config, LogLevel.info, "Dry Run. Output should be:")
|
log(config, LogLevel.debug, "Dry run — output would be:")
|
||||||
log(config, LogLevel.info, finalOutputContents.toString())
|
log(config, LogLevel.debug, finalOutputContents.toString())
|
||||||
}
|
}
|
||||||
} else if (exists) {
|
} 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. */
|
/** 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)
|
await createDirIfNotExists(path.dirname(outputPath), config)
|
||||||
|
|
||||||
const shouldWrite = (!exists || overwrite) && !config.dryRun
|
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 })
|
await copyFileTransformed(config, { exists, overwrite, outputPath, inputPath })
|
||||||
return shouldWrite ? outputPath : null
|
return shouldWrite ? outputPath : null
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export async function getGitConfig(
|
|||||||
): Promise<AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>> {
|
): Promise<AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>> {
|
||||||
const repoUrl = `${url.protocol}//${url.host}${url.pathname}`
|
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) => {
|
return new Promise((res, reject) => {
|
||||||
log(logConfig, LogLevel.debug, `Cloning git repo to ${tmpPath}`)
|
log(logConfig, LogLevel.debug, `Cloning git repo to ${tmpPath}`)
|
||||||
@@ -43,7 +43,7 @@ export async function loadGitConfig({
|
|||||||
file: string
|
file: string
|
||||||
tmpPath: string
|
tmpPath: string
|
||||||
}): Promise<AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>> {
|
}): 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 filename = file || (await findConfigFile(tmpPath))
|
||||||
const absolutePath = path.resolve(tmpPath, filename)
|
const absolutePath = path.resolve(tmpPath, filename)
|
||||||
log(logConfig, LogLevel.debug, `Resolving config file: ${absolutePath}`)
|
log(logConfig, LogLevel.debug, `Resolving config file: ${absolutePath}`)
|
||||||
@@ -52,7 +52,7 @@ export async function loadGitConfig({
|
|||||||
logConfig,
|
logConfig,
|
||||||
)
|
)
|
||||||
|
|
||||||
log(logConfig, LogLevel.info, `Loaded config from git`)
|
log(logConfig, LogLevel.debug, `Loaded config from git`)
|
||||||
log(logConfig, LogLevel.debug, `Raw config:`, loadedConfig)
|
log(logConfig, LogLevel.debug, `Raw config:`, loadedConfig)
|
||||||
const fixedConfig: ScaffoldConfigMap = {}
|
const fixedConfig: ScaffoldConfigMap = {}
|
||||||
for (const [k, v] of Object.entries(loadedConfig)) {
|
for (const [k, v] of Object.entries(loadedConfig)) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import util from "util"
|
import util from "util"
|
||||||
|
import path from "node:path"
|
||||||
import { LogConfig, LogLevel, ScaffoldConfig } from "./types"
|
import { LogConfig, LogLevel, ScaffoldConfig } from "./types"
|
||||||
import { colorize, TermColor } from "./colors"
|
import { colorize, TermColor } from "./colors"
|
||||||
|
|
||||||
@@ -14,8 +15,8 @@ const LOG_PRIORITY: Record<LogLevel, number> = {
|
|||||||
/** Maps each log level to a terminal color. */
|
/** Maps each log level to a terminal color. */
|
||||||
const LOG_LEVEL_COLOR: Record<LogLevel, TermColor> = {
|
const LOG_LEVEL_COLOR: Record<LogLevel, TermColor> = {
|
||||||
[LogLevel.none]: "reset",
|
[LogLevel.none]: "reset",
|
||||||
[LogLevel.debug]: "blue",
|
[LogLevel.debug]: "dim",
|
||||||
[LogLevel.info]: "dim",
|
[LogLevel.info]: "reset",
|
||||||
[LogLevel.warning]: "yellow",
|
[LogLevel.warning]: "yellow",
|
||||||
[LogLevel.error]: "red",
|
[LogLevel.error]: "red",
|
||||||
}
|
}
|
||||||
@@ -64,7 +65,7 @@ export function logInputFile(
|
|||||||
log(config, LogLevel.debug, data)
|
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 {
|
export function logInitStep(config: ScaffoldConfig): void {
|
||||||
log(config, LogLevel.debug, "Full config:", {
|
log(config, LogLevel.debug, "Full config:", {
|
||||||
name: config.name,
|
name: config.name,
|
||||||
@@ -79,5 +80,56 @@ export function logInitStep(config: ScaffoldConfig): void {
|
|||||||
dryRun: config.dryRun,
|
dryRun: config.dryRun,
|
||||||
beforeWrite: config.beforeWrite,
|
beforeWrite: config.beforeWrite,
|
||||||
} as Record<keyof ScaffoldConfig, unknown>)
|
} 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)
|
return Buffer.from(outputContents)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(config, LogLevel.debug, 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)
|
return Buffer.from(templateBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { isDir, getTemplateGlobInfo, getFileList, handleTemplateFile, GlobInfo }
|
|||||||
import { removeGlob, makeRelativePath, getBasePath } from "./path-utils"
|
import { removeGlob, makeRelativePath, getBasePath } from "./path-utils"
|
||||||
import { LogLevel, MinimalConfig, Resolver, ScaffoldCmdConfig, ScaffoldConfig } from "./types"
|
import { LogLevel, MinimalConfig, Resolver, ScaffoldCmdConfig, ScaffoldConfig } from "./types"
|
||||||
import { registerHelpers } from "./parser"
|
import { registerHelpers } from "./parser"
|
||||||
import { log, logInitStep } from "./logger"
|
import { log, logInitStep, logFileTree, logSummary } from "./logger"
|
||||||
import { parseConfigFile } from "./config"
|
import { parseConfigFile } from "./config"
|
||||||
import { resolveInputs } from "./prompts"
|
import { resolveInputs } from "./prompts"
|
||||||
import { loadIgnorePatterns, filterIgnoredFiles } from "./ignore"
|
import { loadIgnorePatterns, filterIgnoredFiles } from "./ignore"
|
||||||
@@ -57,11 +57,15 @@ export async function Scaffold(config: ScaffoldConfig): Promise<void> {
|
|||||||
await assertConfigValid(config)
|
await assertConfigValid(config)
|
||||||
config = await resolveInputs(config)
|
config = await resolveInputs(config)
|
||||||
registerHelpers(config)
|
registerHelpers(config)
|
||||||
|
|
||||||
|
const startTime = performance.now()
|
||||||
const writtenFiles: string[] = []
|
const writtenFiles: string[] = []
|
||||||
try {
|
try {
|
||||||
config.data = { name: config.name, ...config.data }
|
config.data = { name: config.name, ...config.data }
|
||||||
logInitStep(config)
|
logInitStep(config)
|
||||||
|
|
||||||
|
log(config, LogLevel.info, `Scaffolding "${config.name}"...`)
|
||||||
|
|
||||||
const excludes = config.templates.filter((t) => t.startsWith("!"))
|
const excludes = config.templates.filter((t) => t.startsWith("!"))
|
||||||
const includes = 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
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const elapsed = performance.now() - startTime
|
||||||
|
|
||||||
|
logFileTree(config, writtenFiles)
|
||||||
|
logSummary(config, writtenFiles.length, elapsed, config.dryRun)
|
||||||
|
|
||||||
if (config.afterScaffold) {
|
if (config.afterScaffold) {
|
||||||
await runAfterScaffoldHook(config, writtenFiles)
|
await runAfterScaffoldHook(config, writtenFiles)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ describe("logger", () => {
|
|||||||
expect(consoleSpy.log).toHaveBeenCalled()
|
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 = {
|
const config: ScaffoldConfig = {
|
||||||
name: "test",
|
name: "test",
|
||||||
output: "output",
|
output: "output",
|
||||||
@@ -124,8 +124,8 @@ describe("logger", () => {
|
|||||||
data: { name: "test" },
|
data: { name: "test" },
|
||||||
}
|
}
|
||||||
logInitStep(config)
|
logInitStep(config)
|
||||||
// Should only log the "Data:" line at info, not the "Full config:" at debug
|
// Full config is debug-only, nothing logged at info
|
||||||
expect(consoleSpy.log).toHaveBeenCalledTimes(1)
|
expect(consoleSpy.log).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user