-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
558 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import * as fs from 'fs-extra' | ||
import { isBinaryFile } from 'isbinaryfile' | ||
import * as path from 'path' | ||
import * as ejs from 'ejs' | ||
import fm from 'front-matter' | ||
|
||
export interface Action { | ||
templateDir: string | ||
target: string | ||
overwrite?: boolean | ||
} | ||
|
||
export interface CodeGenResult { | ||
status: 'add' | 'overwrite' | 'skipped' | ||
type: 'text' | 'binary' | ||
file: string | ||
content: string | ||
} | ||
|
||
export interface CodeGenResults { | ||
files: Array<CodeGenResult> | ||
failed: Array<Error> | ||
} | ||
|
||
/** | ||
* Utility for generating files from ejs templates or for general scaffolding purposes. | ||
* Given a templte directory, all files within will be moved to the target directory specified whilst | ||
* maintaining the folder heirarchy. It supports both text and binary files, with text files having the | ||
* additional ablity to be rendered with .ejs support meaning any arguments passed in can be interpolated | ||
* into the file. For custom file naming, front-matter can be used to specify the output fileName. | ||
*/ | ||
export async function codeGenerator ( | ||
action: Action, | ||
args: { [key: string]: any }, | ||
): Promise<CodeGenResults> { | ||
const templateFiles = await allFilesInDir(action.templateDir) | ||
const codeGenResults: CodeGenResults = { files: [], failed: [] } | ||
|
||
for (const file of templateFiles) { | ||
const isBinary = await isBinaryFile(file) | ||
const parsedFile = path.parse(file) | ||
|
||
const processBinaryFile = async () => { | ||
const rawFileContent = await fs.readFile(file) | ||
const computedPath = computePath( | ||
action.templateDir, | ||
action.target, | ||
file, | ||
args, | ||
) | ||
|
||
return { computedPath, content: rawFileContent, type: 'binary' } as const | ||
} | ||
|
||
const processTextFile = async () => { | ||
const fileContent = (await fs.readFile(file)).toString() | ||
const { body, renderedAttributes } = frontMatter(fileContent, args) | ||
const computedPath = computePath( | ||
action.templateDir, | ||
action.target, | ||
path.join( | ||
parsedFile.dir, | ||
renderedAttributes.fileName || parsedFile.base, | ||
), | ||
args, | ||
) | ||
const renderedTemplate = ejs.render(body, args) | ||
|
||
return { computedPath, content: renderedTemplate, type: 'text' } as const | ||
} | ||
|
||
try { | ||
const { content, computedPath, type } = isBinary | ||
? await processBinaryFile() | ||
: await processTextFile() | ||
|
||
const exists = await fileExists(computedPath) | ||
const status = !exists | ||
? 'add' | ||
: exists && action.overwrite | ||
? 'overwrite' | ||
: 'skipped' | ||
|
||
if (status === 'add' || status === 'overwrite') { | ||
await fs.outputFile(computedPath, content) | ||
} | ||
|
||
codeGenResults.files.push({ | ||
file: computedPath, | ||
type, | ||
status, | ||
content: content.toString(), | ||
}) | ||
} catch (e) { | ||
codeGenResults.failed.push(e as Error) | ||
} | ||
} | ||
|
||
return codeGenResults | ||
} | ||
|
||
function computePath ( | ||
srcFolder: string, | ||
target: string, | ||
filePath: string, | ||
substitutions: { [k: string]: any }, | ||
): string { | ||
const relativeFromSrcFolder = path.relative(srcFolder, filePath) | ||
let computedPath = path.join(target, relativeFromSrcFolder) | ||
|
||
Object.entries(substitutions).forEach(([propertyName, value]) => { | ||
computedPath = computedPath.split(`{{${propertyName}}}`).join(value) | ||
}) | ||
|
||
return computedPath | ||
} | ||
|
||
async function allFilesInDir (parent: string): Promise<string[]> { | ||
let res: string[] = [] | ||
|
||
for (const dir of await fs.readdir(parent)) { | ||
const child = path.join(parent, dir) | ||
const isDir = (await fs.stat(child)).isDirectory() | ||
|
||
if (!isDir) { | ||
res.push(child) | ||
} else { | ||
res = [...res, ...(await allFilesInDir(child))] | ||
} | ||
} | ||
|
||
return res | ||
} | ||
|
||
function frontMatter (content: string, args: { [key: string]: any }) { | ||
const { attributes, body } = fm(content, { allowUnsafe: true }) as { | ||
attributes: { [key: string]: string } | ||
body: string | ||
} | ||
const renderedAttributes = Object.entries(attributes).reduce( | ||
(acc, [key, val]) => ({ ...acc, [key]: ejs.render(val, args) }), | ||
{} as { [key: string]: string }, | ||
) | ||
|
||
return { body, renderedAttributes } | ||
} | ||
|
||
async function fileExists (absolute: string) { | ||
try { | ||
await fs.access(absolute, fs.constants.F_OK) | ||
|
||
return true | ||
} catch (e) { | ||
return false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
/* eslint-disable padding-line-between-statements */ | ||
// created by autobarrel, do not modify directly | ||
|
||
export * from './code-generator' | ||
export * from './sample-config-files' | ||
export * from './spec-generator' | ||
export * from './spec-options' | ||
export * from './templates' |
Oops, something went wrong.