Skip to content

Commit

Permalink
feat: add codegen utility (#18708)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZachJW34 authored Nov 1, 2021
1 parent 0501452 commit a5a232d
Show file tree
Hide file tree
Showing 20 changed files with 558 additions and 104 deletions.
8 changes: 4 additions & 4 deletions packages/app/cypress/e2e/integration/new-spec.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ describe('Button', () => {
const { Primary } = composedStories
mount(<Primary />)
})
it('should render Secondary', () => {
const { Secondary } = composedStories
mount(<Secondary />)
})
it('should render Large', () => {
const { Large } = composedStories
mount(<Large />)
})
it('should render Small', () => {
const { Small } = composedStories
mount(<Small />)
Expand All @@ -54,7 +54,7 @@ import Button from "./Button"
describe('<Button />', () => {
it('renders', () => {
see: https://reactjs.org/docs/test-utils.html
// see: https://reactjs.org/docs/test-utils.html
mount(<Button />)
})
})`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
exports['@packages/data-context initializeData should initialize 1'] = {
exports['@packages/data-context initializeData initializes 1'] = {
"shellConfig": {
"launchOptions": {},
"launchArgs": {},
Expand Down
4 changes: 4 additions & 0 deletions packages/data-context/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@
"cross-fetch": "^3.1.4",
"dataloader": "^2.0.0",
"dedent": "^0.7.0",
"ejs": "^3.1.6",
"electron": "14.1.0",
"endent": "2.0.1",
"execa": "1.0.0",
"front-matter": "^4.0.2",
"fs-extra": "8.1.0",
"getenv": "1.0.0",
"globby": "^11.0.1",
"graphql": "^15.5.1",
"isbinaryfile": "^4.0.8",
"lodash": "4.17.21",
"p-defer": "^3.0.0",
"wonka": "^4.0.15"
Expand All @@ -38,6 +41,7 @@
"@packages/ts": "0.0.0-development",
"@packages/types": "0.0.0-development",
"@types/dedent": "^0.7.0",
"@types/ejs": "^3.1.0",
"mocha": "7.0.1",
"rimraf": "3.0.2"
},
Expand Down
23 changes: 17 additions & 6 deletions packages/data-context/src/actions/ProjectActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import path from 'path'
import type { ProjectShape } from '../data/coreDataShape'

import type { DataContext } from '..'
import { SpecGenerator } from '../codegen'
import { codeGenerator, SpecOptions } from '../codegen'
import templates from '../codegen/templates'

export interface ProjectApiShape {
getConfig(projectRoot: string): Promise<FullConfig>
Expand Down Expand Up @@ -290,16 +291,26 @@ export class ProjectActions {
const codeGenPath = getCodeGenPath()
const searchFolder = getSearchFolder()

const { specContent, specAbsolute } = await new SpecGenerator(this.ctx, {
const newSpecCodeGenOptions = new SpecOptions(this.ctx, {
codeGenPath,
codeGenType,
specFileExtension,
}).generateSpec()
})

const codeGenOptions = await newSpecCodeGenOptions.getCodeGenOptions()
const codeGenResults = await codeGenerator(
{ templateDir: templates[codeGenType], target: path.parse(codeGenPath).dir },
codeGenOptions,
)

if (!codeGenResults.files[0] || codeGenResults.failed[0]) {
throw (codeGenResults.failed[0] || 'Unable to generate spec')
}

await this.ctx.fs.outputFile(specAbsolute, specContent)
const [newSpec] = codeGenResults.files

const spec = this.ctx.file.normalizeFileToSpec({
absolute: specAbsolute,
absolute: newSpec.file,
searchFolder,
specType: codeGenType === 'integration' ? 'integration' : 'component',
projectRoot: project.projectRoot,
Expand All @@ -308,7 +319,7 @@ export class ProjectActions {

project.generatedSpec = {
spec,
content: specContent,
content: newSpec.content,
}
}
}
156 changes: 156 additions & 0 deletions packages/data-context/src/codegen/code-generator.ts
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
}
}
4 changes: 3 additions & 1 deletion packages/data-context/src/codegen/index.ts
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'
Loading

0 comments on commit a5a232d

Please sign in to comment.