-
-
Notifications
You must be signed in to change notification settings - Fork 636
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
1 parent
40a85f9
commit f0f522d
Showing
2 changed files
with
234 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/* | ||
* @adonisjs/core | ||
* | ||
* (c) AdonisJS | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
import { CommandOptions } from '../../types/ace.js' | ||
import { args, BaseCommand, flags } from '../../modules/ace/main.js' | ||
import stringHelpers from '../../src/helpers/string.js' | ||
|
||
const ALLOWED_TYPES = ['string', 'boolean', 'number', 'enum'] as const | ||
type AllowedTypes = (typeof ALLOWED_TYPES)[number] | ||
|
||
/** | ||
* The env:add command is used to add a new environment variable to the | ||
* `.env`, `.env.example` and `start/env.ts` files. | ||
*/ | ||
export default class EnvAdd extends BaseCommand { | ||
static commandName = 'env:add' | ||
static description = 'Add a new environment variable' | ||
static options: CommandOptions = { | ||
allowUnknownFlags: true, | ||
} | ||
|
||
@args.string({ | ||
description: 'Variable name. Will be converted to screaming snake case', | ||
required: false, | ||
}) | ||
declare name: string | ||
|
||
@args.string({ description: 'Variable value', required: false }) | ||
declare value: string | ||
|
||
@flags.string({ description: 'Type of the variable' }) | ||
declare type: AllowedTypes | ||
|
||
@flags.array({ | ||
description: 'Allowed values for the enum type in a comma-separated list', | ||
default: [''], | ||
required: false, | ||
}) | ||
declare enumValues: string[] | ||
|
||
/** | ||
* Validate the type flag passed by the user | ||
*/ | ||
#isTypeFlagValid() { | ||
return ALLOWED_TYPES.includes(this.type) | ||
} | ||
|
||
async run() { | ||
/** | ||
* Prompt for missing name | ||
*/ | ||
if (!this.name) { | ||
this.name = await this.prompt.ask('Enter the variable name', { | ||
validate: (value) => !!value, | ||
format: (value) => stringHelpers.snakeCase(value).toUpperCase(), | ||
}) | ||
} | ||
|
||
/** | ||
* Prompt for missing value | ||
*/ | ||
if (!this.value) { | ||
this.value = await this.prompt.ask('Enter the variable value') | ||
} | ||
|
||
/** | ||
* Prompt for missing type | ||
*/ | ||
if (!this.type) { | ||
this.type = await this.prompt.choice('Select the variable type', ALLOWED_TYPES) | ||
} | ||
|
||
/** | ||
* Prompt for missing enum values if the selected env type is `enum` | ||
*/ | ||
if (this.type === 'enum' && !this.enumValues) { | ||
this.enumValues = await this.prompt.ask('Enter the enum values separated by a comma', { | ||
result: (value) => value.split(',').map((one) => one.trim()), | ||
}) | ||
} | ||
|
||
/** | ||
* Validate inputs | ||
*/ | ||
if (!this.#isTypeFlagValid()) { | ||
this.logger.error(`Invalid type "${this.type}". Must be one of ${ALLOWED_TYPES.join(', ')}`) | ||
return | ||
} | ||
|
||
/** | ||
* Add the environment variable to the `.env` and `.env.example` files | ||
*/ | ||
const codemods = await this.createCodemods() | ||
const transformedName = stringHelpers.snakeCase(this.name).toUpperCase() | ||
|
||
await codemods.defineEnvVariables({ [transformedName]: this.value }) | ||
|
||
/** | ||
* Add the environment variable to the `start/env.ts` file | ||
*/ | ||
const validation = { | ||
string: 'Env.schema.string()', | ||
number: 'Env.schema.number()', | ||
boolean: 'Env.schema.boolean()', | ||
enum: `Env.schema.enum(['${this.enumValues.join("','")}'] as const)`, | ||
}[this.type] | ||
|
||
await codemods.defineEnvValidations({ variables: { [transformedName]: validation } }) | ||
|
||
this.logger.success('Environment variable added successfully') | ||
} | ||
} |
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,116 @@ | ||
/* | ||
* @adonisjs/core | ||
* | ||
* (c) AdonisJS | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
import { test } from '@japa/runner' | ||
import EnvAdd from '../../commands/env/add.js' | ||
import { AceFactory } from '../../factories/core/ace.js' | ||
|
||
test.group('Env Add command', () => { | ||
test('add new env variable to the different files', async ({ assert, fs }) => { | ||
await fs.createJson('tsconfig.json', {}) | ||
await fs.create('.env', '') | ||
await fs.create('.env.example', '') | ||
await fs.create( | ||
'./start/env.ts', | ||
`import { Env } from '@adonisjs/core/env' | ||
export default await Env.create(new URL('../', import.meta.url), {})` | ||
) | ||
|
||
const ace = await new AceFactory().make(fs.baseUrl) | ||
await ace.app.init() | ||
ace.ui.switchMode('raw') | ||
|
||
const command = await ace.create(EnvAdd, ['variable', 'value', '--type=string']) | ||
await command.exec() | ||
|
||
await assert.fileContains('.env', 'VARIABLE=value') | ||
await assert.fileContains('.env.example', 'VARIABLE=value') | ||
await assert.fileContains('./start/env.ts', 'VARIABLE: Env.schema.string()') | ||
}) | ||
|
||
test('convert variable to screaming snake case', async ({ assert, fs }) => { | ||
await fs.createJson('tsconfig.json', {}) | ||
await fs.create('.env', '') | ||
await fs.create('.env.example', '') | ||
await fs.create( | ||
'./start/env.ts', | ||
`import { Env } from '@adonisjs/core/env' | ||
export default await Env.create(new URL('../', import.meta.url), {})` | ||
) | ||
|
||
const ace = await new AceFactory().make(fs.baseUrl) | ||
await ace.app.init() | ||
ace.ui.switchMode('raw') | ||
|
||
const command = await ace.create(EnvAdd, ['stripe_ApiKey', 'value', '--type=string']) | ||
await command.exec() | ||
|
||
await assert.fileContains('.env', 'STRIPE_API_KEY=value') | ||
await assert.fileContains('.env.example', 'STRIPE_API_KEY=value') | ||
await assert.fileContains('./start/env.ts', 'STRIPE_API_KEY: Env.schema.string()') | ||
}) | ||
|
||
test('enum type with allowed values', async ({ assert, fs }) => { | ||
await fs.createJson('tsconfig.json', {}) | ||
await fs.create('.env', '') | ||
await fs.create('.env.example', '') | ||
await fs.create( | ||
'./start/env.ts', | ||
`import { Env } from '@adonisjs/core/env' | ||
export default await Env.create(new URL('../', import.meta.url), {})` | ||
) | ||
|
||
const ace = await new AceFactory().make(fs.baseUrl) | ||
await ace.app.init() | ||
ace.ui.switchMode('raw') | ||
|
||
const command = await ace.create(EnvAdd, [ | ||
'variable', | ||
'bar', | ||
'--type=enum', | ||
'--enum-values=foo', | ||
'--enum-values=bar', | ||
]) | ||
await command.exec() | ||
|
||
await assert.fileContains('.env', 'VARIABLE=bar') | ||
await assert.fileContains('.env.example', 'VARIABLE=bar') | ||
await assert.fileContains( | ||
'./start/env.ts', | ||
"VARIABLE: Env.schema.enum(['foo', 'bar'] as const)" | ||
) | ||
}) | ||
|
||
test('prompt when nothing is passed to the command', async ({ assert, fs }) => { | ||
await fs.createJson('tsconfig.json', {}) | ||
await fs.create('.env', '') | ||
await fs.create('.env.example', '') | ||
await fs.create( | ||
'./start/env.ts', | ||
`import { Env } from '@adonisjs/core/env' | ||
export default await Env.create(new URL('../', import.meta.url), {})` | ||
) | ||
|
||
const ace = await new AceFactory().make(fs.baseUrl) | ||
await ace.app.init() | ||
ace.ui.switchMode('raw') | ||
|
||
const command = await ace.create(EnvAdd, []) | ||
|
||
command.prompt.trap('Enter the variable name').replyWith('my_variable_name') | ||
command.prompt.trap('Enter the variable value').replyWith('my_value') | ||
command.prompt.trap('Select the variable type').replyWith('string') | ||
|
||
await command.exec() | ||
|
||
await assert.fileContains('.env', 'MY_VARIABLE_NAME=my_value') | ||
await assert.fileContains('.env.example', 'MY_VARIABLE_NAME=my_value') | ||
await assert.fileContains('./start/env.ts', 'MY_VARIABLE_NAME: Env.schema.string()') | ||
}) | ||
}) |