Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: Release

on:
pull_request:
branches:
- master
push:
branches:
- master
Expand All @@ -11,6 +14,7 @@ permissions:

jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -20,7 +24,9 @@ jobs:
- run: npm i -g pnpm
- run: pnpm run ci
- run: pnpm test

build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -32,6 +38,8 @@ jobs:
- run: pnpm build

release:
name: Release Please
if: github.event_name == 'push'
needs:
- build
- test
Expand All @@ -47,6 +55,7 @@ jobs:
target-branch: master

publish:
name: NPM Publish
needs: release
runs-on: ubuntu-latest
if: ${{ needs.release.outputs.release_created }}
Expand Down
16 changes: 8 additions & 8 deletions src/cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,17 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
return
}
log(config, LogLevel.info, `Simple Scaffold v${pkg.version}`)
const tmpPath = generateUniqueTmpPath()
config.tmpDir = generateUniqueTmpPath()
try {
log(config, LogLevel.debug, "Parsing config file...", config)
const parsed = await parseConfigFile(config, tmpPath)
const parsed = await parseConfigFile(config)
await Scaffold(parsed)
} catch (e) {
const message = "message" in (e as any) ? (e as any).message : e?.toString()
log(config, LogLevel.error, message)
} finally {
log(config, LogLevel.debug, "Cleaning up temporary files...", tmpPath)
await fs.rm(tmpPath, { recursive: true, force: true })
log(config, LogLevel.debug, "Cleaning up temporary files...", config.tmpDir)
await fs.rm(config.tmpDir, { recursive: true, force: true })
}
})
.option({
Expand Down Expand Up @@ -171,7 +171,6 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
aliases: ["ls"],
description: "List all available templates for a given config. See `list -h` for more information.",
run: async (_config) => {
const tmpPath = generateUniqueTmpPath()
const config = {
templates: [],
name: "",
Expand All @@ -180,19 +179,20 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
subdir: false,
overwrite: false,
dryRun: false,
tmpDir: generateUniqueTmpPath(),
..._config,
config: _config.config ?? (!_config.git ? process.cwd() : undefined),
}
try {
const file = await getConfigFile(config, tmpPath)
const file = await getConfigFile(config)
console.log(colorize.underline`Available templates:\n`)
console.log(Object.keys(file).join("\n"))
} catch (e) {
const message = "message" in (e as any) ? (e as any).message : e?.toString()
log(config, LogLevel.error, message)
} finally {
log(config, LogLevel.debug, "Cleaning up temporary files...", tmpPath)
await fs.rm(tmpPath, { recursive: true, force: true })
log(config, LogLevel.debug, "Cleaning up temporary files...", config.tmpDir)
await fs.rm(config.tmpDir, { recursive: true, force: true })
}
},
})
Expand Down
58 changes: 35 additions & 23 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { log } from "./logger"
import { resolve, wrapNoopResolver } from "./utils"
import { getGitConfig } from "./git"
import { createDirIfNotExists, getUniqueTmpPath, isDir, pathExists } from "./file"
import { exec, spawn } from "node:child_process"
import { exec } from "node:child_process"

/** @internal */
export function getOptionValueForFile<T>(
Expand All @@ -31,8 +31,8 @@ export function getOptionValueForFile<T>(
}
return (fn as FileResponseHandler<T>)(
filePath,
path.dirname(handlebarsParse(config, filePath, { isPath: true }).toString()),
path.basename(handlebarsParse(config, filePath, { isPath: true }).toString()),
path.dirname(handlebarsParse(config, filePath, { asPath: true }).toString()),
path.basename(handlebarsParse(config, filePath, { asPath: true }).toString()),
)
}

Expand All @@ -52,7 +52,7 @@ function isWrappedWithQuotes(string: string): boolean {
}

/** @internal */
export async function getConfigFile(config: ScaffoldCmdConfig, tmpPath: string): Promise<ScaffoldConfigMap> {
export async function getConfigFile(config: ScaffoldCmdConfig): Promise<ScaffoldConfigMap> {
if (config.git && !config.git.includes("://")) {
log(config, LogLevel.info, `Loading config from GitHub ${config.git}`)
config.git = githubPartToUrl(config.git)
Expand All @@ -65,7 +65,7 @@ export async function getConfigFile(config: ScaffoldCmdConfig, tmpPath: string):
log(config, LogLevel.info, `Loading config from file ${configFilename}`)

const configPromise = await (isGit
? getRemoteConfig({ git: configPath, config: configFilename, logLevel: config.logLevel, tmpPath })
? getRemoteConfig({ git: configPath, config: configFilename, logLevel: config.logLevel, tmpDir: config.tmpDir! })
: getLocalConfig({ config: configFilename, logLevel: config.logLevel }))

// resolve the config
Expand All @@ -80,8 +80,20 @@ export async function getConfigFile(config: ScaffoldCmdConfig, tmpPath: string):
}

/** @internal */
export async function parseConfigFile(config: ScaffoldCmdConfig, tmpPath: string): Promise<ScaffoldConfig> {
let output: ScaffoldConfig = { ...config, beforeWrite: undefined }
export async function parseConfigFile(config: ScaffoldCmdConfig): Promise<ScaffoldConfig> {
let output: ScaffoldConfig = {
name: config.name,
templates: [],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears this change may have broken the templates. Previously, if a user defined a template, it would pass through with the parsed config, but afterward the templates is being reset to an empty array, so its not matching on any files.

This comment was marked as duplicate.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a new issue to track this regression

#110

Let's continue communication there so it's easier to track

output: config.output,
logLevel: config.logLevel,
dryRun: config.dryRun,
data: config.data,
subdir: config.subdir,
overwrite: config.overwrite,
subdirHelper: config.subdirHelper,
beforeWrite: undefined,
tmpDir: config.tmpDir!,
}

if (config.quiet) {
config.logLevel = LogLevel.none
Expand All @@ -91,7 +103,7 @@ export async function parseConfigFile(config: ScaffoldCmdConfig, tmpPath: string

if (shouldLoadConfig) {
const key = config.key ?? "default"
const configImport = await getConfigFile(config, tmpPath)
const configImport = await getConfigFile(config)

if (!configImport[key]) {
throw new Error(`Template "${key}" not found in ${config.config}`)
Expand All @@ -100,11 +112,11 @@ export async function parseConfigFile(config: ScaffoldCmdConfig, tmpPath: string
const imported = configImport[key]
log(config, LogLevel.debug, "Imported result", imported)
output = {
...config,
...output,
...imported,
beforeWrite: undefined,
data: {
...(imported as any).data,
...imported.data,
...config.data,
},
}
Expand Down Expand Up @@ -158,7 +170,7 @@ export async function getLocalConfig(config: ConfigLoadConfig & Partial<LogConfi
export async function getRemoteConfig(
config: RemoteConfigLoadConfig & Partial<LogConfig>,
): Promise<ScaffoldConfigFile> {
const { config: configFile, git, tmpPath, ...logConfig } = config as Required<typeof config>
const { config: configFile, git, tmpDir, ...logConfig } = config as Required<typeof config>

log(logConfig, LogLevel.info, `Loading config from remote ${git}, file ${configFile}`)

Expand All @@ -170,7 +182,7 @@ export async function getRemoteConfig(
throw new Error(`Unsupported protocol ${url.protocol}`)
}

return getGitConfig(url, configFile, tmpPath, logConfig)
return getGitConfig(url, configFile, tmpDir, logConfig)
}

/** @internal */
Expand All @@ -194,13 +206,13 @@ function wrapBeforeWrite(
beforeWrite: string,
): ScaffoldConfig["beforeWrite"] {
return async (content, rawContent, outputFile) => {
const tmpPath = path.join(getUniqueTmpPath(), path.basename(outputFile))
await createDirIfNotExists(path.dirname(tmpPath), config)
const tmpDir = path.join(getUniqueTmpPath(), path.basename(outputFile))
await createDirIfNotExists(path.dirname(tmpDir), config)
const ext = path.extname(outputFile)
const rawTmpPath = tmpPath.replace(ext, ".raw" + ext)
const rawTmpPath = tmpDir.replace(ext, ".raw" + ext)
try {
log(config, LogLevel.debug, "Parsing beforeWrite command", beforeWrite)
let cmd = await prepareBeforeWriteCmd({ beforeWrite, tmpPath, content, rawTmpPath, rawContent })
let cmd = await prepareBeforeWriteCmd({ beforeWrite, tmpDir, content, rawTmpPath, rawContent })
const result = await new Promise<string | undefined>((resolve, reject) => {
log(config, LogLevel.debug, "Running parsed beforeWrite command:", cmd)
const proc = exec(cmd)
Expand All @@ -221,21 +233,21 @@ function wrapBeforeWrite(
log(config, LogLevel.warning, "Error running beforeWrite command, returning original content")
return undefined
} finally {
await fs.rm(tmpPath, { force: true })
await fs.rm(tmpDir, { force: true })
await fs.rm(rawTmpPath, { force: true })
}
}
}

async function prepareBeforeWriteCmd({
beforeWrite,
tmpPath,
tmpDir,
content,
rawTmpPath,
rawContent,
}: {
beforeWrite: string
tmpPath: string
tmpDir: string
content: Buffer
rawTmpPath: string
rawContent: Buffer
Expand All @@ -244,16 +256,16 @@ async function prepareBeforeWriteCmd({
const pathReg = /\{\{\s*path\s*\}\}/gi
const rawPathReg = /\{\{\s*rawpath\s*\}\}/gi
if (pathReg.test(beforeWrite)) {
await fs.writeFile(tmpPath, content)
cmd = beforeWrite.replaceAll(pathReg, tmpPath)
await fs.writeFile(tmpDir, content)
cmd = beforeWrite.replaceAll(pathReg, tmpDir)
}
if (rawPathReg.test(beforeWrite)) {
await fs.writeFile(rawTmpPath, rawContent)
cmd = beforeWrite.replaceAll(rawPathReg, rawTmpPath)
}
if (!cmd) {
await fs.writeFile(tmpPath, content)
cmd = [beforeWrite, tmpPath].join(" ")
await fs.writeFile(tmpDir, content)
cmd = [beforeWrite, tmpDir].join(" ")
}
return cmd
}
7 changes: 3 additions & 4 deletions src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,9 @@ export async function getTemplateFileInfo(
): Promise<OutputFileInfo> {
const inputPath = path.resolve(process.cwd(), templatePath)
const outputPathOpt = getOptionValueForFile(config, inputPath, config.output)
const outputDir = getOutputDir(config, outputPathOpt, basePath)
const outputPath = handlebarsParse(config, path.join(outputDir, path.basename(inputPath)), {
isPath: true,
}).toString()
const outputDir = getOutputDir(config, outputPathOpt, basePath.replace(config.tmpDir!, "./"))
const rawOutputPath = path.join(outputDir, path.basename(inputPath))
const outputPath = handlebarsParse(config, rawOutputPath, { asPath: true }).toString()
const exists = await pathExists(outputPath)
return { inputPath, outputPathOpt, outputDir, outputPath, exists }
}
Expand Down
6 changes: 3 additions & 3 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,17 @@ export function registerHelpers(config: ScaffoldConfig): void {
export function handlebarsParse(
config: ScaffoldConfig,
templateBuffer: Buffer | string,
{ isPath = false }: { isPath?: boolean } = {},
{ asPath = false }: { asPath?: boolean } = {},
): Buffer {
const { data } = config
try {
let str = templateBuffer.toString()
if (isPath) {
if (asPath) {
str = str.replace(/\\/g, "/")
}
const parser = Handlebars.compile(str, { noEscape: true })
let outputContents = parser(data)
if (isPath && path.sep !== "/") {
if (asPath && path.sep !== "/") {
outputContents = outputContents.replace(/\//g, "\\")
}
return Buffer.from(outputContents)
Expand Down
7 changes: 4 additions & 3 deletions src/scaffold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,15 @@ export async function Scaffold(config: ScaffoldConfig): Promise<void> {
* @category Main
* @return {Promise<void>} A promise that resolves when the scaffold is complete
*/
Scaffold.fromConfig = async function(
Scaffold.fromConfig = async function (
/** The path or URL to the config file */
pathOrUrl: string,
/** Information needed before loading the config */
config: MinimalConfig,
/** Any overrides to the loaded config */
overrides?: Resolver<ScaffoldCmdConfig, Partial<Omit<ScaffoldConfig, "name">>>,
): Promise<void> {
const tmpPath = path.resolve(os.tmpdir(), `scaffold-config-${Date.now()}`)
const _cmdConfig: ScaffoldCmdConfig = {
dryRun: false,
output: process.cwd(),
Expand All @@ -136,11 +137,11 @@ Scaffold.fromConfig = async function(
quiet: false,
config: pathOrUrl,
version: false,
tmpDir: tmpPath,
...config,
}
const tmpPath = path.resolve(os.tmpdir(), `scaffold-config-${Date.now()}`)
const _overrides = resolve(overrides, _cmdConfig)
const _config = await parseConfigFile(_cmdConfig, tmpPath)
const _config = await parseConfigFile(_cmdConfig)
return Scaffold({ ..._config, ..._overrides })
}

Expand Down
7 changes: 6 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ export interface ScaffoldConfig {
rawContent: Buffer,
outputPath: string,
): string | Buffer | undefined | Promise<string | Buffer | undefined>

/** @internal */
tmpDir?: string
}

/**
Expand Down Expand Up @@ -377,6 +380,8 @@ export type ScaffoldCmdConfig = {
version: boolean
/** Run a script before writing the files. This can be a command or a path to a file. The file contents will be passed to the given command. */
beforeWrite?: string
/** @internal */
tmpDir?: string
}

/**
Expand Down Expand Up @@ -418,7 +423,7 @@ export type LogConfig = Pick<ScaffoldConfig, "logLevel">
export type ConfigLoadConfig = LogConfig & Pick<ScaffoldCmdConfig, "config">

/** @internal */
export type RemoteConfigLoadConfig = LogConfig & Pick<ScaffoldCmdConfig, "config" | "git"> & { tmpPath: string }
export type RemoteConfigLoadConfig = LogConfig & Pick<ScaffoldCmdConfig, "config" | "git" | "tmpDir">

/** @internal */
export type MinimalConfig = Pick<ScaffoldCmdConfig, "name" | "key">
Expand Down
Loading