0.3.0
Publish npm package / publish (push) Canceled after 0s

This commit is contained in:
2026-10-07 15:30:39 +02:00
parent 452d872954
commit ed5a666ad3
11 changed files with 148 additions and 19 deletions
+32 -5
View File
@@ -58,12 +58,24 @@ export type ReverseMapManifest = {
export type RenderOptions = {
reverseMap?: boolean | string
/** When false, throw TargetExistsError instead of clearing a non-empty target. Defaults to true. */
overwrite?: boolean
/** Renames rendered file and directory names, e.g. `{ _gitignore: ".gitignore" }`. */
rename?: Record<string, string>
}
export class TargetExistsError extends Error {
constructor(target: string) {
super(`Refusing to overwrite non-empty target directory: ${target}`)
this.name = "TargetExistsError"
}
}
type RenderState = {
sourceRoot: string
destRoot: string
manifest?: ReverseMapManifest
rename: Record<string, string>
}
function evalCondition(expr: string | undefined, context: Record<string, unknown>): boolean {
@@ -169,6 +181,7 @@ function getReverseMapPath(destRoot: string, reverseMap: true | string): string
function getOutputName(
entry: string,
context: Record<string, unknown>,
rename: Record<string, string>,
state?: RenderState,
file?: ReverseMapFile,
): string | null {
@@ -188,7 +201,7 @@ function getOutputName(
}
}
return outputName.replace(VAR_RE, (_match, path: string) => {
const rendered = outputName.replace(VAR_RE, (_match, path: string) => {
const result = String(resolveContext(context, path) ?? "")
addReverseMapToken(state, file, {
kind: "path",
@@ -200,6 +213,15 @@ function getOutputName(
})
return result
})
return Object.hasOwn(rename, rendered) ? rename[rendered]! : rendered
}
function isNonEmptyTarget(destRoot: string): boolean {
try {
return !statSync(destRoot).isDirectory() || readdirSync(destRoot).length > 0
} catch {
return false
}
}
function isUtf8Text(buffer: Buffer): boolean {
@@ -450,10 +472,14 @@ function renderDir(
sourceRoot,
destRoot,
manifest: options.reverseMap ? createReverseMapManifest() : undefined,
rename: options.rename ?? {},
}
const mapPath = options.reverseMap ? getReverseMapPath(destRoot, options.reverseMap) : undefined
assertSafeRenderTarget(srcDir, destDir)
validateOutputPaths(srcDir, destRoot, context)
validateOutputPaths(srcDir, destRoot, context, state.rename)
if (options.overwrite === false && isNonEmptyTarget(destRoot)) {
throw new TargetExistsError(destDir)
}
rmSync(destDir, { recursive: true, force: true })
renderDirInner(srcDir, destRoot, context, state)
if (state.manifest && mapPath) {
@@ -466,18 +492,19 @@ function validateOutputPaths(
srcDir: string,
destDir: string,
context: Record<string, unknown>,
rename: Record<string, string>,
) {
const entries = readdirSync(srcDir).sort()
for (const entry of entries) {
const srcPath = resolvePath(srcDir, entry)
const stat = statSync(srcPath)
const outputName = getOutputName(entry, context)
const outputName = getOutputName(entry, context, rename)
if (outputName === null) continue
const destPath = resolveOutputPath(destDir, outputName)
if (stat.isDirectory()) {
validateOutputPaths(srcPath, destPath, context)
validateOutputPaths(srcPath, destPath, context, rename)
}
}
}
@@ -533,7 +560,7 @@ function renderDirInner(
templatePath,
tokens: [],
}
const outputName = getOutputName(entry, context, state, tempFile)
const outputName = getOutputName(entry, context, state.rename, state, tempFile)
if (outputName === null) {
storeSkippedTemplate(srcPath, state)
continue