-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor component script #208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
6c16ab7
Initial work on plop replacement
kmonahan 50b9083
Write basic file from template
kmonahan 2c096a9
Create all scaffold files
kmonahan 0b42fb1
Confirm component before creating.
kmonahan 6b83aa1
Remove plop and old version of inquirer
kmonahan 8254b6b
Move tiny-mustache to its own package
kmonahan 2cb8cbf
Add JSDoc comments to remaining functions
kmonahan 5712340
Merge branch '2.x-RC' into update-plop-and-inquirer
kmonahan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,246 @@ | ||
| /* eslint-disable no-console */ | ||
| import mustache from '@forumone/tiny-mustache'; | ||
| import { confirm, input, select } from '@inquirer/prompts'; | ||
| import { camelCase, capitalCase, kebabCase, pascalCase } from 'change-case'; | ||
| import { | ||
| access, | ||
| lstat, | ||
| mkdir, | ||
| readdir, | ||
| readFile, | ||
| writeFile, | ||
| } from 'node:fs/promises'; | ||
| import path from 'node:path'; | ||
|
|
||
| /** | ||
| * Creates the cascade layer name from the directory name. | ||
| * @param {string} directoryName - The directory name | ||
| * @return {string} | ||
| */ | ||
| function cascadeLayer(directoryName) { | ||
| const parts = directoryName.split('-'); | ||
| return parts[parts.length - 1]; | ||
| } | ||
|
|
||
| /** | ||
| * Checks whether the source directory is an accessible directory. | ||
| * @param {node:fs.PathLike} source - Source path | ||
| * @return {Promise<boolean>} - True if source is an accessible directory | ||
| */ | ||
| async function isDirectory(source) { | ||
| const stats = await lstat(source); | ||
| return stats.isDirectory(); | ||
| } | ||
|
|
||
| /** | ||
| * Get available component directories. | ||
| * @param {node:fs.PathLike} source - Source path | ||
| * @return {Promise<string[]>} - Array of component directory paths | ||
| */ | ||
| async function getDirectories(source) { | ||
| /** @type {string[]} */ | ||
| const directoryFiles = await readdir(source); | ||
| /** @type {string[]} */ | ||
| const directoryPaths = directoryFiles | ||
| .filter( | ||
| dirName => !['00-config', '05-pages', '06-utility'].includes(dirName), | ||
| ) | ||
| .map(name => path.join(source, name)); | ||
| /** @type {Awaited<boolean>[]} */ | ||
| const isDirectoryResults = await Promise.all(directoryPaths.map(isDirectory)); | ||
| return directoryPaths.filter((value, index) => isDirectoryResults[index]); | ||
| } | ||
|
|
||
| /** | ||
| * Get the machine name from user input. | ||
| * @return {Promise<string>} - Machine name of new component | ||
| */ | ||
| async function getMachineName() { | ||
| const question = { | ||
| message: 'What is the name of your component?', | ||
| transformer: pascalCase, | ||
| required: true, | ||
| }; | ||
| const componentName = await input(question); | ||
| return pascalCase(componentName).trim(); | ||
| } | ||
|
|
||
| /** | ||
| * Get the human-readable name from user input. | ||
| * @param {string} componentName - Machine name of new component | ||
| * @returns {Promise<string>} - Human-readable name of new component | ||
| */ | ||
| async function getComponentTitle(componentName) { | ||
| const defaultComponentTitle = capitalCase(componentName); | ||
| const question = { | ||
| message: 'What is the human-readable title of your component?', | ||
| default: defaultComponentTitle, | ||
| transformer: capitalCase, | ||
| required: true, | ||
| }; | ||
| const componentTitle = await input(question); | ||
| return componentTitle.trim(); | ||
| } | ||
|
|
||
| /** | ||
| * Select the component folder from available directories. | ||
| * @returns {Promise<string>} - Name of selected folder | ||
| */ | ||
| async function getComponentFolder() { | ||
| const patternSrc = path.join(process.cwd(), 'source'); | ||
| const patternDir = await getDirectories(patternSrc); | ||
| const question = { | ||
| message: 'Choose the component location:', | ||
| choices: patternDir.map(item => path.basename(item)), | ||
| }; | ||
| return select(question); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the name of the optional subdirectory. | ||
| * @returns {Promise<string>} - Subdirectory or empty string if no directory entered | ||
| */ | ||
| async function getComponentFolderSub() { | ||
| const question = { | ||
| message: 'Include subfolder or leave blank', | ||
| }; | ||
| const componentFolderSub = await input(question); | ||
| return componentFolderSub.trim(); | ||
| } | ||
|
|
||
| /** | ||
| * Gets whether to generate a Storybook story. | ||
| * @returns {Promise<boolean>} | ||
| */ | ||
| async function getUseStorybook() { | ||
| const question = { | ||
| message: 'Create a Storybook story?', | ||
| default: true, | ||
| }; | ||
| return confirm(question); | ||
| } | ||
|
|
||
| /** | ||
| * Confirms whether to create component. | ||
| * @param {object} mustacheData - Data to fill in mustache templates. | ||
| * @returns {Promise<boolean>} | ||
| */ | ||
| async function confirmComponent(mustacheData) { | ||
| const output = mustache( | ||
| `--- | ||
| Component Name: {{componentName}} | ||
| Component Title: {{componentTitle}} | ||
| Component Location: {{componentLocation}} | ||
| Include Story?: {{useStorybook}} | ||
| ---`, | ||
| mustacheData, | ||
| ); | ||
| console.log(output); | ||
| const question = { | ||
| message: 'Is this what you want?', | ||
| }; | ||
| return confirm(question); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a file from a template and given mustache data. | ||
| * | ||
| * @param {string} fileName - The name of the final file, with mustache placeholders if required. | ||
| * @param {string} templatePath - The path to the template file to be used as a base for the new file. | ||
| * @param {Object} mustacheData - An object containing key-value pairs for populating the Mustache templates. | ||
| * @return {Promise<void>} | ||
| */ | ||
| async function createFile(fileName, templatePath, mustacheData) { | ||
| const filePath = mustache(fileName, mustacheData); | ||
| const templateContents = await readFile(path.resolve(templatePath), { | ||
| encoding: 'utf-8', | ||
| }); | ||
| const newFileContents = mustache(templateContents, mustacheData); | ||
| const directoryPath = path.dirname(filePath); | ||
| try { | ||
| await access(directoryPath); | ||
| } catch { | ||
| await mkdir(directoryPath, { recursive: true }); | ||
| } | ||
| await writeFile(filePath, newFileContents, { encoding: 'utf-8', flag: 'w+' }); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Generates a new component based on user input. | ||
| * | ||
| * @return {Promise<void>} | ||
| */ | ||
| async function generator() { | ||
| const componentName = await getMachineName(); | ||
| const componentTitle = await getComponentTitle(componentName); | ||
| const componentFolder = await getComponentFolder(); | ||
| const componentFolderSub = await getComponentFolderSub(); | ||
| const componentLocation = path.join( | ||
| componentFolder, | ||
| pascalCase(componentFolderSub), | ||
| ); | ||
| const useStorybook = await getUseStorybook(); | ||
|
|
||
| const mustacheData = { | ||
| // Partials | ||
| propsName: '{{componentName}}Props', | ||
| componentAlias: '{{componentName}}Component', | ||
| argsName: '{{#camelCase}}{{componentName}}{{/camelCase}}Args', | ||
| // Variables | ||
| componentName, | ||
| componentTitle, | ||
| componentLocation, | ||
| componentFolder, | ||
| useStorybook, | ||
| // Lambdas | ||
| machineName: (text, render) => { | ||
| return pascalCase(render(text)); | ||
| }, | ||
| humanName: (text, render) => { | ||
| return camelCase(render(text)); | ||
| }, | ||
| cascadeLayer: (text, render) => { | ||
| return cascadeLayer(render(text)); | ||
| }, | ||
| kebabCase: (text, render) => { | ||
| return kebabCase(render(text)); | ||
| }, | ||
| camelCase: (text, render) => { | ||
| return camelCase(render(text)); | ||
| }, | ||
| titleCase: (text, render) => { | ||
| return capitalCase(render(text)); | ||
| }, | ||
| }; | ||
| const confirmation = await confirmComponent(mustacheData); | ||
| if (confirmation) { | ||
| await createFile( | ||
| './source/{{componentLocation}}/{{componentName}}/{{componentName}}.tsx', | ||
| './lib/templates/Component.hbs', | ||
| mustacheData, | ||
| ); | ||
| await createFile( | ||
| './source/{{ componentLocation }}/{{ componentName }}/{{#kebabCase}}{{ componentName }}{{/kebabCase}}.module.css', | ||
| './lib/templates/Stylesheet.hbs', | ||
| mustacheData, | ||
| ); | ||
| if (useStorybook) { | ||
| await createFile( | ||
| './source/{{ componentLocation }}/{{ componentName }}/{{#camelCase}}{{ componentName }}{{/camelCase}}Args.ts', | ||
| './lib/templates/Data.hbs', | ||
| mustacheData, | ||
| ); | ||
| await createFile( | ||
| './source/{{ componentLocation }}/{{ componentName }}/{{ componentName }}.stories.tsx', | ||
| './lib/templates/Story.hbs', | ||
| mustacheData, | ||
| ); | ||
| } | ||
| console.log('Component created.'); | ||
| } else { | ||
| console.error('Component canceled.'); | ||
| } | ||
| } | ||
|
|
||
| generator(); | ||
This file was deleted.
Oops, something went wrong.
This file contains hidden or 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
File renamed without changes.
This file contains hidden or 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 hidden or 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,3 @@ | ||
| @layer {{#cascadeLayer}}{{ componentFolder }}{{/cascadeLayer}} { | ||
| .wrapper {} | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.