mirror of
https://github.com/chenasraf/simple-scaffold.git
synced 2026-05-17 17:28:09 +00:00
test: add tests
chore: use node:* modules for builtins
This commit is contained in:
@@ -3,8 +3,8 @@ import massarg from "massarg"
|
||||
import chalk from "chalk"
|
||||
import { LogLevel, ScaffoldCmdConfig } from "./types"
|
||||
import { Scaffold } from "./scaffold"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import path from "node:path"
|
||||
import fs from "node:fs/promises"
|
||||
import { parseAppendData, parseConfig } from "./config"
|
||||
|
||||
export async function parseCliArgs(args = process.argv.slice(2)) {
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
import path from "path"
|
||||
import path from "node:path"
|
||||
import {
|
||||
AsyncResolver,
|
||||
ConfigLoadConfig,
|
||||
FileResponse,
|
||||
FileResponseHandler,
|
||||
LogConfig,
|
||||
LogLevel,
|
||||
Resolver,
|
||||
ScaffoldCmdConfig,
|
||||
ScaffoldConfig,
|
||||
ScaffoldConfigFile,
|
||||
ScaffoldConfigMap,
|
||||
} from "./types"
|
||||
import { OptionsBase } from "massarg/types"
|
||||
import { spawn } from "node:child_process"
|
||||
import os from "node:os"
|
||||
import { handlebarsParse } from "./parser"
|
||||
import { log } from "./logger"
|
||||
import { resolve } from "./utils"
|
||||
import { resolve, wrapNoopResolver } from "./utils"
|
||||
import { getGitConfig } from "./git"
|
||||
|
||||
export function getOptionValueForFile<T>(
|
||||
config: ScaffoldConfig,
|
||||
@@ -109,14 +104,6 @@ export function githubPartToUrl(part: string): string {
|
||||
return gitUrl.toString()
|
||||
}
|
||||
|
||||
function wrapNoopResolver<T, R = T>(value: Resolver<T, R>): Resolver<T, R> {
|
||||
if (typeof value === "function") {
|
||||
return value
|
||||
}
|
||||
|
||||
return (_) => value
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export async function getConfig(config: ConfigLoadConfig): Promise<ScaffoldConfigFile> {
|
||||
const { config: configFile, isRemote, ...logConfig } = config as Required<typeof config>
|
||||
@@ -132,7 +119,6 @@ export async function getConfig(config: ConfigLoadConfig): Promise<ScaffoldConfi
|
||||
const isGit = url.protocol === "git:" || (isHttp && url.pathname.endsWith(".git"))
|
||||
|
||||
if (isGit) {
|
||||
console.log("Calling getGitConfig", getGitConfig)
|
||||
return getGitConfig(url, logConfig)
|
||||
}
|
||||
|
||||
@@ -143,46 +129,6 @@ export async function getConfig(config: ConfigLoadConfig): Promise<ScaffoldConfi
|
||||
return wrapNoopResolver(import(path.resolve(process.cwd(), configFile)))
|
||||
}
|
||||
|
||||
export async function getGitConfig(
|
||||
url: URL,
|
||||
logConfig: LogConfig,
|
||||
): Promise<AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>> {
|
||||
console.log("Calling real getGitConfig")
|
||||
const repoUrl = `${url.protocol}//${url.host}${url.pathname}`
|
||||
|
||||
log(logConfig, LogLevel.Info, `Cloning git repo ${repoUrl}`)
|
||||
|
||||
const tmpPath = path.resolve(os.tmpdir(), `scaffold-config-${Date.now()}`)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const clone = spawn("git", ["clone", "--depth", "1", repoUrl, tmpPath])
|
||||
|
||||
clone.on("error", reject)
|
||||
clone.on("close", async (code) => {
|
||||
if (code === 0) {
|
||||
log(logConfig, LogLevel.Info, `Loading config from git repo: ${repoUrl}`)
|
||||
const hashPath = url.hash?.replace("#", "") || "scaffold.config.js"
|
||||
const absolutePath = path.resolve(tmpPath, hashPath)
|
||||
const loadedConfig = (await import(absolutePath)).default as ScaffoldConfigMap
|
||||
log(logConfig, LogLevel.Info, `Loaded config from git`)
|
||||
log(logConfig, LogLevel.Debug, `Raw config:`, loadedConfig)
|
||||
const fixedConfig: ScaffoldConfigMap = Object.fromEntries(
|
||||
Object.entries(loadedConfig).map(([k, v]) => [
|
||||
k,
|
||||
// use absolute paths for template as config is necessarily in another directory
|
||||
{ ...v, templates: v.templates.map((t) => path.resolve(tmpPath, t)) },
|
||||
]),
|
||||
)
|
||||
|
||||
resolve(wrapNoopResolver(fixedConfig))
|
||||
return
|
||||
}
|
||||
|
||||
reject(new Error(`Git clone failed with code ${code}`))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function count(string: string, substring: string): number {
|
||||
return string.split(substring).length - 1
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import path from "path"
|
||||
import { F_OK } from "constants"
|
||||
import path from "node:path"
|
||||
import { F_OK } from "node:constants"
|
||||
import { LogLevel, ScaffoldConfig } from "./types"
|
||||
import { promises as fsPromises } from "fs"
|
||||
const { stat, access, mkdir } = fsPromises
|
||||
import fs from "node:fs/promises"
|
||||
import { glob, hasMagic } from "glob"
|
||||
import { log } from "./logger"
|
||||
import { getOptionValueForFile } from "./config"
|
||||
import { handlebarsParse } from "./parser"
|
||||
import { handleErr } from "./utils"
|
||||
|
||||
const { readFile, writeFile } = fsPromises
|
||||
const { stat, access, mkdir, readFile, writeFile } = fs
|
||||
|
||||
export async function createDirIfNotExists(dir: string, config: ScaffoldConfig): Promise<void> {
|
||||
const parentDir = path.dirname(dir)
|
||||
|
||||
45
src/git.ts
Normal file
45
src/git.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import path from "node:path"
|
||||
import os from "node:os"
|
||||
import { log } from "./logger"
|
||||
import { AsyncResolver, LogConfig, LogLevel, ScaffoldCmdConfig, ScaffoldConfigMap } from "./types"
|
||||
import { spawn } from "node:child_process"
|
||||
import { wrapNoopResolver } from "./utils"
|
||||
|
||||
export async function getGitConfig(
|
||||
url: URL,
|
||||
logConfig: LogConfig,
|
||||
): Promise<AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>> {
|
||||
const repoUrl = `${url.protocol}//${url.host}${url.pathname}`
|
||||
|
||||
log(logConfig, LogLevel.Info, `Cloning git repo ${repoUrl}`)
|
||||
|
||||
const tmpPath = path.resolve(os.tmpdir(), `scaffold-config-${Date.now()}`)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const clone = spawn("git", ["clone", "--depth", "1", repoUrl, tmpPath])
|
||||
|
||||
clone.on("error", reject)
|
||||
clone.on("close", async (code) => {
|
||||
if (code === 0) {
|
||||
log(logConfig, LogLevel.Info, `Loading config from git repo: ${repoUrl}`)
|
||||
const hashPath = url.hash?.replace("#", "") || "scaffold.config.js"
|
||||
const absolutePath = path.resolve(tmpPath, hashPath)
|
||||
const loadedConfig = (await import(absolutePath)).default as ScaffoldConfigMap
|
||||
log(logConfig, LogLevel.Info, `Loaded config from git`)
|
||||
log(logConfig, LogLevel.Debug, `Raw config:`, loadedConfig)
|
||||
const fixedConfig: ScaffoldConfigMap = Object.fromEntries(
|
||||
Object.entries(loadedConfig).map(([k, v]) => [
|
||||
k,
|
||||
// use absolute paths for template as config is necessarily in another directory
|
||||
{ ...v, templates: v.templates.map((t) => path.resolve(tmpPath, t)) },
|
||||
]),
|
||||
)
|
||||
|
||||
resolve(wrapNoopResolver(fixedConfig))
|
||||
return
|
||||
}
|
||||
|
||||
reject(new Error(`Git clone failed with code ${code}`))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import path from "path"
|
||||
import path from "node:path"
|
||||
import { DefaultHelpers, Helper, LogLevel, ScaffoldConfig } from "./types"
|
||||
import Handlebars from "handlebars"
|
||||
import dtAdd from "date-fns/add"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* See [readme](README.md)
|
||||
*/
|
||||
import path from "path"
|
||||
import path from "node:path"
|
||||
import { handleErr, resolve } from "./utils"
|
||||
import {
|
||||
isDir,
|
||||
@@ -109,7 +109,7 @@ 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 */
|
||||
|
||||
@@ -7,3 +7,11 @@ export function handleErr(err: NodeJS.ErrnoException | null): void {
|
||||
export function resolve<T, R = T>(resolver: Resolver<T, R>, arg: T): R {
|
||||
return typeof resolver === "function" ? (resolver as (value: T) => R)(arg) : (resolver as R)
|
||||
}
|
||||
|
||||
export function wrapNoopResolver<T, R = T>(value: Resolver<T, R>): Resolver<T, R> {
|
||||
if (typeof value === "function") {
|
||||
return value
|
||||
}
|
||||
|
||||
return (_) => value
|
||||
}
|
||||
|
||||
@@ -2,8 +2,20 @@ import { ScaffoldCmdConfig } from "../src/types"
|
||||
import { OptionsBase } from "massarg/types"
|
||||
import * as config from "../src/config"
|
||||
import { resolve } from "../src/utils"
|
||||
// @ts-ignore
|
||||
import * as configFile from "../scaffold.config"
|
||||
|
||||
const { getConfig, githubPartToUrl, parseAppendData, parseConfig, parseConfigSelection } = config
|
||||
jest.mock("../src/git", () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
...jest.requireActual("../src/git"),
|
||||
getGitConfig: () => {
|
||||
return Promise.resolve({ default: blankCliConf })
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const { githubPartToUrl, parseAppendData, parseConfig, parseConfigSelection } = config
|
||||
|
||||
const blankCliConf: ScaffoldCmdConfig & OptionsBase = {
|
||||
verbose: 0,
|
||||
@@ -19,14 +31,6 @@ const blankCliConf: ScaffoldCmdConfig & OptionsBase = {
|
||||
help: false,
|
||||
}
|
||||
|
||||
jest.mock("../src/config", () => {
|
||||
console.log("mocking config")
|
||||
return {
|
||||
...jest.requireActual("../src/config"),
|
||||
getGitConfig: () => Promise.resolve({ default: blankCliConf }),
|
||||
}
|
||||
})
|
||||
|
||||
describe("config", () => {
|
||||
describe("parseAppendData", () => {
|
||||
test('works for "key=value"', () => {
|
||||
@@ -56,6 +60,13 @@ describe("config", () => {
|
||||
})
|
||||
|
||||
describe("parseConfigSelection", () => {
|
||||
test("no key", () => {
|
||||
expect(parseConfigSelection("scaffold.config.js")).toEqual({
|
||||
configFile: "scaffold.config.js",
|
||||
key: "default",
|
||||
isRemote: false,
|
||||
})
|
||||
})
|
||||
test("separate key", () => {
|
||||
expect(parseConfigSelection("scaffold.config.js", "component")).toEqual({
|
||||
configFile: "scaffold.config.js",
|
||||
@@ -113,40 +124,29 @@ describe("config", () => {
|
||||
expect(result?.data?.num).toEqual("1234")
|
||||
})
|
||||
})
|
||||
// describe("remote config", () => {
|
||||
// test("works", async () => {
|
||||
// // mock
|
||||
// const result = await parseConfig({
|
||||
// ...blankCliConf,
|
||||
// config: "https://github.com/chenasraf/simple-scaffold",
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
})
|
||||
|
||||
// TODO find how to mock getGitConfig properly
|
||||
//
|
||||
// describe("getConfig", () => {
|
||||
// test("gets git config", async () => {
|
||||
// // const original = config.githubPartToUrl
|
||||
// // config.githubPartToUrl = jest.fn().mockReturnValue(Promise.resolve(blankCliConf))
|
||||
// // const spy = jest.spyOn(config, "parseConfig").mockReturnValue(Promise.resolve(blankCliConf))
|
||||
//
|
||||
// // jest.spyOn(config, "getGitConfig").mockImplementation(() => Promise.resolve({ default: blankCliConf }))
|
||||
// // jest.mock("../src/config", () => ({
|
||||
// // ...jest.requireActual("../src/config"),
|
||||
// // getGitConfig: () => Promise.resolve({ default: blankCliConf }),
|
||||
// // }))
|
||||
// const resultFn = await config.getConfig({
|
||||
// config: "https://github.com/chenasraf/simple-scaffold.git",
|
||||
// isRemote: true,
|
||||
// quiet: true,
|
||||
// verbose: 0,
|
||||
// })
|
||||
// const result = await resolve(resultFn, blankCliConf)
|
||||
// expect(result).toEqual({ default: blankCliConf })
|
||||
// // expect(spy.)
|
||||
// // config.githubPartToUrl = original
|
||||
// })
|
||||
// })
|
||||
describe("getConfig", () => {
|
||||
test("gets git config", async () => {
|
||||
const resultFn = await config.getConfig({
|
||||
config: "https://github.com/chenasraf/simple-scaffold.git",
|
||||
isRemote: true,
|
||||
quiet: true,
|
||||
verbose: 0,
|
||||
})
|
||||
const result = await resolve(resultFn, blankCliConf)
|
||||
expect(result).toEqual({ default: blankCliConf })
|
||||
})
|
||||
|
||||
test("gets local file config", async () => {
|
||||
const resultFn = await config.getConfig({
|
||||
config: "scaffold.config.js",
|
||||
isRemote: false,
|
||||
quiet: true,
|
||||
verbose: 0,
|
||||
})
|
||||
const result = await resolve(resultFn, {} as any)
|
||||
expect(result).toEqual(configFile)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ScaffoldCmdConfig, ScaffoldConfig } from "../src/types"
|
||||
import path from "path"
|
||||
import path from "node:path"
|
||||
import * as dateFns from "date-fns"
|
||||
import { OptionsBase } from "massarg/types"
|
||||
import { dateHelper, defaultHelpers, handlebarsParse, nowHelper } from "../src/parser"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const path = require("path")
|
||||
const path = require("node:path")
|
||||
|
||||
/** @type {import('typedoc').TypeDocOptions} */
|
||||
module.exports = {
|
||||
|
||||
Reference in New Issue
Block a user