gh-action-bump-version/node_modules/actions-toolkit/bin/create-action.js

210 lines
6.2 KiB
JavaScript
Executable File

const fs = require('fs')
const path = require('path')
const { promisify } = require('util')
const minimist = require('minimist')
const { Signale } = require('signale')
const { prompt } = require('enquirer')
const colors = require('./colors.json')
const icons = require('./feather-icons.json')
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
const mkdir = promisify(fs.mkdir)
/**
* Reads a template file from disk.
*
* @param {string} filename The template filename to read.
* @returns {Promise<string>} The template file string contents.
*/
async function readTemplate (filename) {
const templateDir = path.join(__dirname, 'template')
return readFile(path.join(templateDir, filename), 'utf8')
}
/**
* A predicate function to ensure a string is not empty.
*
* @param {string} value The string value.
* @returns {boolean} Whether the string is empty or not.
*/
const isNotEmpty = value => value.length > 0
/**
* The options object returned from the CLI questionnaire prompt.
* @typedef {object} PromptAnswers
* @property {string} name The action name.
* @property {string} description The action description.
* @property {string} icon The feather icon name. See `bin/feather-icons.json` for options.
* @property {string} color The GitHub Action color. See `bin/colors.json` for options.
*/
/**
* Prompts the user with a questionnaire to get key metadata for the GitHub Action.
*
* @returns {Promise<PromptAnswers>} An object containing prompt answers.
*/
async function getActionMetadata () {
return prompt([
{
type: 'input',
name: 'name',
message: 'What is the name of your action?',
initial: 'Your action name',
validate: isNotEmpty
},
{
type: 'input',
name: 'description',
message: 'What is a short description of your action?',
initial: 'A description of your action',
validate: isNotEmpty
},
{
type: 'autocomplete',
name: 'icon',
message: 'Choose an icon for your action. Visit https://feathericons.com for a visual reference.',
choices: icons,
limit: 10
},
{
type: 'autocomplete',
name: 'color',
message: 'Choose a background color background color used in the visual workflow editor for your action.',
choices: colors
}
])
}
/**
* Creates a Dockerfile contents string, replacing variables in the Dockerfile template
* with values passed in by the user from the CLI prompt.
*
* @param {PromptAnswers} answers The CLI prompt answers.
* @returns {Promise<string>} The Dockerfile contents.
*/
async function createDockerfile (answers) {
const dockerfileTemplate = await readTemplate('Dockerfile')
return dockerfileTemplate
.replace(':NAME', answers.name)
.replace(':DESCRIPTION', answers.description)
.replace(':ICON', answers.icon)
.replace(':COLOR', answers.color)
}
/**
* Creates a action.yml contents string, replacing variables in the action.yml template
* with values passed in by the user from the CLI prompt.
*
* @param {PromptAnswers} answers The CLI prompt answers.
* @returns {Promise<string>} The action.yml contents.
*/
async function createActionYaml (answers) {
const template = await readTemplate('action.yml')
return template
.replace(':NAME', answers.name)
.replace(':DESCRIPTION', answers.description)
.replace(':ICON', answers.icon)
.replace(':COLOR', answers.color)
}
/**
* Creates an index.test.js contents string, replacing variables in the index.test.js template
* with values passed in by the user from the CLI prompt.
*
* @param {PromptAnswers} answers The CLI prompt answers.
* @returns {Promise<string>} The index.test.js contents.
*/
async function createIndexTest (answers) {
const indexTest = await readTemplate('index.test.js')
return indexTest
.replace(':NAME', answers.name)
}
/**
* Creates a `package.json` object with the latest version
* of `actions-toolkit` ready to be installed.
*
* @param {string} name The action package name.
* @returns {object} The `package.json` contents.
*/
function createPackageJson (name) {
const { version, devDependencies } = require('../package.json')
return {
name,
private: true,
main: 'index.js',
scripts: {
start: 'node ./index.js',
test: 'jest'
},
dependencies: {
'actions-toolkit': `^${version}`
},
devDependencies: {
jest: devDependencies.jest
}
}
}
/**
* Runs the create action CLI prompt and bootstraps a new directory for the user.
*
* @public
* @param {string[]} argv The command line arguments to parse.
* @param {import("signale").Signale} [logger] The Signale logger.
* @returns {Promise<void>} Nothing.
*/
module.exports = async function createAction (argv, signale = new Signale({
config: {
displayLabel: false
}
})) {
const args = minimist(argv)
const directoryName = args._[0]
if (!directoryName || args.help) {
signale.log('\nUsage: npx actions-toolkit <name>')
return process.exit(1)
}
signale.star('Welcome to actions-toolkit! Let\'s get started creating an action.\n')
const base = path.join(process.cwd(), directoryName)
try {
signale.info(`Creating folder ${base}...`)
await mkdir(base)
} catch (err) {
if (err.code === 'EEXIST') {
signale.fatal(`Folder ${base} already exists!`)
}
throw err
}
// Collect answers
const metadata = await getActionMetadata()
signale.log('\n------------------------------------\n')
// Create the templated files
const actionYaml = await createActionYaml(metadata)
const dockerfile = await createDockerfile(metadata)
const indexTest = await createIndexTest(metadata)
const packageJson = createPackageJson(directoryName)
const entrypoint = await readTemplate('index.js')
await Promise.all([
['package.json', JSON.stringify(packageJson, null, 2)],
['Dockerfile', dockerfile],
['action.yml', actionYaml],
['index.js', entrypoint],
['index.test.js', indexTest]
].map(async ([filename, contents]) => {
signale.info(`Creating ${filename}...`)
await writeFile(path.join(base, filename), contents)
}))
signale.log('\n------------------------------------\n')
signale.success(`Done! Enjoy building your GitHub Action!`)
signale.info(`Get started with:\n\ncd ${directoryName} && npm install`)
}