Files
simple-scaffold/src/config.ts
Chen Asraf 3413151358 test: add tests
chore: use node:* modules for builtins
2023-06-12 23:26:25 +03:00

135 lines
4.1 KiB
TypeScript

import path from "node:path"
import {
ConfigLoadConfig,
FileResponse,
FileResponseHandler,
LogLevel,
ScaffoldCmdConfig,
ScaffoldConfig,
ScaffoldConfigFile,
} from "./types"
import { OptionsBase } from "massarg/types"
import { handlebarsParse } from "./parser"
import { log } from "./logger"
import { resolve, wrapNoopResolver } from "./utils"
import { getGitConfig } from "./git"
export function getOptionValueForFile<T>(
config: ScaffoldConfig,
filePath: string,
fn: FileResponse<T>,
defaultValue?: T,
): T {
if (typeof fn !== "function") {
return defaultValue ?? (fn as T)
}
return (fn as FileResponseHandler<T>)(
filePath,
path.dirname(handlebarsParse(config, filePath, { isPath: true }).toString()),
path.basename(handlebarsParse(config, filePath, { isPath: true }).toString()),
)
}
export function parseAppendData(value: string, options: ScaffoldCmdConfig & OptionsBase): unknown {
const data = options.data ?? {}
const [key, val] = value.split(/\:?=/)
// raw
if (value.includes(":=") && !val.includes(":=")) {
return { ...data, [key]: JSON.parse(val) }
}
return { ...data, [key]: isWrappedWithQuotes(val) ? val.substring(1, val.length - 1) : val }
}
function isWrappedWithQuotes(string: string): boolean {
return (string.startsWith('"') && string.endsWith('"')) || (string.startsWith("'") && string.endsWith("'"))
}
/** @internal */
export async function parseConfig(config: ScaffoldCmdConfig & OptionsBase): Promise<ScaffoldConfig> {
let c: ScaffoldConfig = config
if (config.github) {
log(config, LogLevel.Info, `Loading config from github ${config.github}`)
config.config = githubPartToUrl(config.github)
}
if (config.config) {
const { configFile, key, isRemote } = parseConfigSelection(config.config, config.key)
log(config, LogLevel.Info, `Loading config from ${configFile} with key ${key}`)
const configPromise = await getConfig({
config: configFile,
isRemote,
quiet: config.quiet,
verbose: config.verbose,
})
const configImport = await resolve(configPromise, config)
if (!configImport[key]) {
throw new Error(`Template "${key}" not found in ${configFile}`)
}
c = {
...config,
...configImport[key],
data: {
...configImport[key].data,
...config.data,
},
}
}
c.data = { ...c.data, ...config.appendData }
delete config.appendData
return c
}
export function parseConfigSelection(
config: string,
key?: string,
): { configFile: string; key: string; isRemote: boolean } {
const isUrl = config.includes("://")
const hasColonToken = (!isUrl && config.includes(":")) || (isUrl && count(config, ":") > 1)
const colonIndex = config.lastIndexOf(":")
const [configFile, templateKey = "default"] = hasColonToken
? [config.substring(0, colonIndex), config.substring(colonIndex + 1)]
: [config, undefined]
const _key = (key ?? templateKey) || "default"
return { configFile, key: _key, isRemote: isUrl }
}
export function githubPartToUrl(part: string): string {
const gitUrl = new URL(`https://github.com/${part}`)
if (!gitUrl.pathname.endsWith(".git")) {
gitUrl.pathname += ".git"
}
return gitUrl.toString()
}
/** @internal */
export async function getConfig(config: ConfigLoadConfig): Promise<ScaffoldConfigFile> {
const { config: configFile, isRemote, ...logConfig } = config as Required<typeof config>
if (!isRemote) {
log(logConfig, LogLevel.Info, `Loading config from file ${configFile}`)
const absolutePath = path.resolve(process.cwd(), configFile)
return wrapNoopResolver(import(absolutePath))
}
const url = new URL(configFile)
const isHttp = url.protocol === "http:" || url.protocol === "https:"
const isGit = url.protocol === "git:" || (isHttp && url.pathname.endsWith(".git"))
if (isGit) {
return getGitConfig(url, logConfig)
}
if (!isHttp) {
throw new Error(`Unsupported protocol ${url.protocol}`)
}
return wrapNoopResolver(import(path.resolve(process.cwd(), configFile)))
}
function count(string: string, substring: string): number {
return string.split(substring).length - 1
}