Skip to content

Commit

Permalink
feat: add --language flag to functions:create command (#3464)
Browse files Browse the repository at this point in the history
* refactor: rename JS templates directory

* refactor: rename TS templates directory

* feat: add language flag

* refactor: rename variable

* chore: update docs

* chore: fix tests

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
eduardoboucas and kodiakhq[bot] authored Oct 11, 2021
1 parent 4289e60 commit e406f33
Show file tree
Hide file tree
Showing 106 changed files with 157 additions and 15 deletions.
1 change: 1 addition & 0 deletions docs/commands/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ netlify functions:create

- `name` (*string*) - function name
- `url` (*string*) - pull template from URL
- `language` (*string*) - function language
- `debug` (*boolean*) - Print debugging information
- `httpProxy` (*string*) - Proxy server address to route requests through.
- `httpProxyCertificateFilename` (*string*) - Certificate file to use when connecting using a proxy server
Expand Down
37 changes: 26 additions & 11 deletions src/commands/functions/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ const showGoTemplates = process.env.NETLIFY_EXPERIMENTAL_BUILD_GO_SOURCE === 'tr
// each `value` property in this list, and that it matches the extension of the
// files used by that language.
const languages = [
{ name: 'JavaScript', value: 'js' },
{ name: 'TypeScript', value: 'ts' },
{ name: 'JavaScript', value: 'javascript' },
{ name: 'TypeScript', value: 'typescript' },
showGoTemplates && { name: 'Go', value: 'go' },
]

Expand Down Expand Up @@ -69,6 +69,7 @@ FunctionsCreateCommand.aliases = ['function:create']
FunctionsCreateCommand.flags = {
name: flagsLib.string({ char: 'n', description: 'function name' }),
url: flagsLib.string({ char: 'u', description: 'pull template from URL' }),
language: flagsLib.string({ char: 'l', description: 'function language' }),
...FunctionsCreateCommand.flags,
}
module.exports = FunctionsCreateCommand
Expand Down Expand Up @@ -155,7 +156,7 @@ const formatRegistryArrayForInquirer = function (lang) {
}

// pick template from our existing templates
const pickTemplate = async function () {
const pickTemplate = async function ({ language: languageFromFlag }) {
const specialCommands = [
new inquirer.Separator(),
{
Expand All @@ -170,16 +171,30 @@ const pickTemplate = async function () {
},
new inquirer.Separator(),
]
const { language } = await inquirer.prompt({
choices: languages.filter(Boolean),
message: 'Select the language of your function',
name: 'language',
type: 'list',
})

let language = languageFromFlag

if (language === undefined) {
const { language: languageFromPrompt } = await inquirer.prompt({
choices: languages.filter(Boolean),
message: 'Select the language of your function',
name: 'language',
type: 'list',
})

language = languageFromPrompt
}

inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)

const templatesForLanguage = formatRegistryArrayForInquirer(language)
let templatesForLanguage

try {
templatesForLanguage = formatRegistryArrayForInquirer(language)
} catch (_) {
throw error(`Invalid language: ${language}`)
}

const { chosenTemplate } = await inquirer.prompt({
name: 'chosenTemplate',
message: 'Pick a template',
Expand Down Expand Up @@ -373,7 +388,7 @@ const installDeps = async ({ functionPackageJson, functionPath, functionsDir })
// no --url flag specified, pick from a provided template
const scaffoldFromTemplate = async function (context, flags, args, functionsDir) {
// pull the rest of the metadata from the template
const chosenTemplate = await pickTemplate()
const chosenTemplate = await pickTemplate(flags)
if (chosenTemplate === 'url') {
const { chosenUrl } = await inquirer.prompt([
{
Expand Down
134 changes: 130 additions & 4 deletions tests/command.functions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ test('should create a new function directory when none is found', async (t) => {
},
{
question: 'Select the language of your function',
answer: answerWithValue('ts'),
answer: answerWithValue(CONFIRM),
},
{
question: 'Pick a template',
Expand Down Expand Up @@ -142,7 +142,7 @@ test('should install function template dependencies on a site-level `package.jso
},
{
question: 'Select the language of your function',
answer: answerWithValue('ts'),
answer: answerWithValue(CONFIRM),
},
{
question: 'Pick a template',
Expand Down Expand Up @@ -218,7 +218,7 @@ test('should install function template dependencies in the function sub-director
},
{
question: 'Select the language of your function',
answer: answerWithValue('ts'),
answer: answerWithValue(CONFIRM),
},
{
question: 'Pick a template',
Expand Down Expand Up @@ -286,7 +286,7 @@ test('should not create a new function directory when one is found', async (t) =
const createFunctionQuestions = [
{
question: 'Select the language of your function',
answer: answerWithValue('ts'),
answer: answerWithValue(CONFIRM),
},
{
question: 'Pick a template',
Expand Down Expand Up @@ -317,6 +317,132 @@ test('should not create a new function directory when one is found', async (t) =
})
})

test('should only show function templates for the language specified via the --language flag, if one is present', async (t) => {
const siteInfo = {
admin_url: 'https://app.netlify.com/sites/site-name/overview',
ssl_url: 'https://site-name.netlify.app/',
id: 'site_id',
name: 'site-name',
build_settings: { repo_url: 'https://github.com/owner/repo' },
}

const routes = [
{
path: 'accounts',
response: [{ slug: 'test-account' }],
},
{ path: 'sites/site_id/service-instances', response: [] },
{ path: 'sites/site_id', response: siteInfo },
{
path: 'sites',
response: [siteInfo],
},
{ path: 'sites/site_id', method: 'patch', response: {} },
]

await withSiteBuilder('site-with-no-functions-dir', async (builder) => {
await builder.buildAsync()

const createFunctionQuestions = [
{
question: 'Enter the path, relative to your site',
answer: answerWithValue('test/functions'),
},
{
question: 'Pick a template',
answer: answerWithValue(CONFIRM),
},
{
question: 'Name your function',
answer: answerWithValue(CONFIRM),
},
]

await withMockApi(routes, async ({ apiUrl }) => {
const childProcess = execa(cliPath, ['functions:create', '--language', 'javascript'], {
env: {
NETLIFY_API_URL: apiUrl,
NETLIFY_SITE_ID: 'site_id',
NETLIFY_AUTH_TOKEN: 'fake-token',
},
cwd: builder.directory,
})

handleQuestions(childProcess, createFunctionQuestions)

await childProcess

t.is(await fs.fileExistsAsync(`${builder.directory}/test/functions/hello-world/hello-world.js`), true)
})
})
})

test('throws an error when the --language flag contains an unsupported value', async (t) => {
const siteInfo = {
admin_url: 'https://app.netlify.com/sites/site-name/overview',
ssl_url: 'https://site-name.netlify.app/',
id: 'site_id',
name: 'site-name',
build_settings: { repo_url: 'https://github.com/owner/repo' },
}

const routes = [
{
path: 'accounts',
response: [{ slug: 'test-account' }],
},
{ path: 'sites/site_id/service-instances', response: [] },
{ path: 'sites/site_id', response: siteInfo },
{
path: 'sites',
response: [siteInfo],
},
{ path: 'sites/site_id', method: 'patch', response: {} },
]

await withSiteBuilder('site-with-no-functions-dir', async (builder) => {
await builder.buildAsync()

const createFunctionQuestions = [
{
question: 'Enter the path, relative to your site',
answer: answerWithValue('test/functions'),
},
{
question: 'Pick a template',
answer: answerWithValue(CONFIRM),
},
{
question: 'Name your function',
answer: answerWithValue(CONFIRM),
},
]

await withMockApi(routes, async ({ apiUrl }) => {
const childProcess = execa(cliPath, ['functions:create', '--language', 'coffeescript'], {
env: {
NETLIFY_API_URL: apiUrl,
NETLIFY_SITE_ID: 'site_id',
NETLIFY_AUTH_TOKEN: 'fake-token',
},
cwd: builder.directory,
})

handleQuestions(childProcess, createFunctionQuestions)

try {
await childProcess

t.fail()
} catch (error) {
t.true(error.message.includes('Invalid language: coffeescript'))
}

t.is(await fs.fileExistsAsync(`${builder.directory}/test/functions/hello-world/hello-world.js`), false)
})
})
})

const DEFAULT_PORT = 9999
const SERVE_TIMEOUT = 180000

Expand Down

1 comment on commit e406f33

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📊 Benchmark results

Package size: 352 MB

Please sign in to comment.