mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
076585b7f0 | ||
|
|
dc6a5a7ec6 | ||
|
|
d19a84d246 | ||
|
|
b4b05dc5fa | ||
|
|
3c3574ab79 | ||
|
|
729a01fe64 | ||
|
|
72a511017e | ||
|
|
a58609e09f | ||
|
|
29f102c367 | ||
|
|
06563efbb9 |
No files matched your search
@@ -16,6 +16,8 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
- [build-tools] Reduce `expo-device-hub` preview resolution from 1280 px to 960 px to match `serve-sim` and lower streaming bandwidth. ([#4326](https://github.com/expo/eas-cli/pull/4326) by [@krystofwoldrich-agent](https://github.com/krystofwoldrich-agent))
|
||||
- [build-tools] Install FFmpeg before launching `expo-device-hub` in Android device sessions. ([#4332](https://github.com/expo/eas-cli/pull/4332) by [@krystofwoldrich-agent](https://github.com/krystofwoldrich-agent))
|
||||
- [eas-cli] Improve `eas env:*` non-interactive required input help and error messages. ([#3820](https://github.com/expo/eas-cli/pull/3820) by [@krystofwoldrich](https://github.com/krystofwoldrich))
|
||||
- [eas-cli] Reject conflicting positional `ENVIRONMENT` and `--variable-environment` inputs in `eas env:update` and `eas env:delete`. ([#3820](https://github.com/expo/eas-cli/pull/3820) by [@krystofwoldrich](https://github.com/krystofwoldrich))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import { boolish } from 'getenv';
|
||||
|
||||
export function isNonInteractiveByDefault(): boolean {
|
||||
@@ -26,6 +27,65 @@ export function resolveNonInteractiveAndJsonFlags(flags: {
|
||||
return { json, nonInteractive };
|
||||
}
|
||||
|
||||
export function prependFlagDescription<T extends Record<string, { description?: string }>>(
|
||||
flags: T,
|
||||
extraDescription: string
|
||||
): T {
|
||||
return Object.fromEntries(
|
||||
Object.entries(flags).map(([name, flag]) => {
|
||||
const description = flag.description ?? '';
|
||||
return [
|
||||
name,
|
||||
{
|
||||
...flag,
|
||||
description: description ? `${extraDescription} ${description}` : extraDescription,
|
||||
},
|
||||
];
|
||||
})
|
||||
) as T;
|
||||
}
|
||||
|
||||
export function markRequiredInNonInteractiveMode<
|
||||
T extends Record<string, { description?: string }>,
|
||||
>(flags: T): T {
|
||||
return prependFlagDescription(flags, '(required in non-interactive mode)');
|
||||
}
|
||||
|
||||
export function validateNonInteractiveRequiredInputs({
|
||||
nonInteractive,
|
||||
requiredInputs,
|
||||
helpCommand,
|
||||
}: {
|
||||
nonInteractive: boolean;
|
||||
requiredInputs: { if?: boolean; name: string; value: unknown }[];
|
||||
helpCommand: string;
|
||||
}): void {
|
||||
if (!nonInteractive) {
|
||||
return;
|
||||
}
|
||||
|
||||
const missingInputs = requiredInputs
|
||||
.filter(({ if: condition = true, value }) => {
|
||||
if (!condition) {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.length === 0;
|
||||
}
|
||||
return !value;
|
||||
})
|
||||
.map(({ name }) => name);
|
||||
|
||||
if (missingInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Missing required inputs for non-interactive mode: ${missingInputs.join(', ')}.\n` +
|
||||
`Run ${chalk.bold(helpCommand)} for usage and examples.`
|
||||
);
|
||||
}
|
||||
|
||||
export const EasEnvironmentFlagParameters = {
|
||||
description: "Environment variable's environment, e.g. 'production', 'preview', 'development'",
|
||||
};
|
||||
|
||||
@@ -365,6 +365,19 @@ describe(EnvCreate, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('in non-interactive mode', () => {
|
||||
it('reports missing required inputs before loading command context', async () => {
|
||||
const command = new EnvCreate(['--non-interactive'], mockConfig);
|
||||
// @ts-expect-error
|
||||
const getContextAsyncSpy = jest.spyOn(command, 'getContextAsync');
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
/Missing required inputs for non-interactive mode: --name, --value, --visibility, --environment\.[\s\S]*eas env:create --help/
|
||||
);
|
||||
expect(getContextAsyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts development environment when using positional argument', async () => {
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
|
||||
@@ -106,7 +106,7 @@ describe(EnvDelete, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('cancels deletion when user does not confirm', async () => {
|
||||
it('reports missing variable name in non-interactive mode before loading command context', async () => {
|
||||
const mockVariables = [
|
||||
{ id: variableId, name: 'TEST_VARIABLE', scope: EnvironmentVariableScope.Project },
|
||||
];
|
||||
@@ -117,9 +117,12 @@ describe(EnvDelete, () => {
|
||||
const command = new EnvDelete(['--non-interactive'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await expect(command.runAsync()).rejects.toThrowErrorMatchingSnapshot();
|
||||
const getContextAsyncSpy = jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
/Missing required inputs for non-interactive mode: --variable-name\.[\s\S]*eas env:delete --help/
|
||||
);
|
||||
|
||||
expect(getContextAsyncSpy).not.toHaveBeenCalled();
|
||||
expect(EnvironmentVariableMutation.deleteAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -158,4 +161,46 @@ describe(EnvDelete, () => {
|
||||
environment: DefaultEnvironment.Development,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects positional environment and --variable-environment used together', async () => {
|
||||
const mockVariable = {
|
||||
id: 'var1',
|
||||
name: 'TEST_VAR_1',
|
||||
value: 'value1',
|
||||
environments: [DefaultEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.String,
|
||||
};
|
||||
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce([mockVariable]);
|
||||
jest.mocked(EnvironmentVariableMutation.deleteAsync).mockResolvedValueOnce({ id: 'var1' });
|
||||
|
||||
const command = new EnvDelete(
|
||||
[
|
||||
'development',
|
||||
'--variable-name',
|
||||
'TEST_VAR_1',
|
||||
'--variable-environment',
|
||||
'production',
|
||||
'--scope',
|
||||
'project',
|
||||
'--non-interactive',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
const getContextAsyncSpy = jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId,
|
||||
});
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"You can't use the --variable-environment flag when the environment is passed as a positional argument. Run `eas env:delete --help` for more information."
|
||||
);
|
||||
expect(getContextAsyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -66,6 +66,21 @@ describe(EnvList, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('reports missing environment in non-interactive mode', async () => {
|
||||
const command = new EnvList(['--non-interactive'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
const getContextAsyncSpy = jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
'The `--environment` flag must be set when running in `--non-interactive` mode.'
|
||||
);
|
||||
expect(getContextAsyncSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('lists project environment variables in specified environments', async () => {
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce(mockVariables);
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@ describe(EnvPull, () => {
|
||||
const command = new EnvPull(['--non-interactive'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
const getContextAsyncSpy = jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
@@ -290,6 +290,7 @@ describe(EnvPull, () => {
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
'The `--environment` flag must be set when running in `--non-interactive` mode.'
|
||||
);
|
||||
expect(getContextAsyncSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -89,6 +89,52 @@ SECRET_KEY=super-secret-key`;
|
||||
});
|
||||
});
|
||||
|
||||
it('reports missing environment in non-interactive mode', async () => {
|
||||
const command = new EnvPush(['--non-interactive', '--path', testEnvPath], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
const getContextAsyncSpy = jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
/The `--environment` flag must be set when running in `--non-interactive` mode/
|
||||
);
|
||||
expect(getContextAsyncSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('allows non-interactive mode without --force when nothing needs confirmation', async () => {
|
||||
const command = new EnvPush(
|
||||
['--environment', 'development', '--path', testEnvPath, '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(confirmAsync).not.toHaveBeenCalled();
|
||||
expect(
|
||||
EnvironmentVariableMutation.createBulkEnvironmentVariablesForAppAsync
|
||||
).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'VARIABLE_NAME',
|
||||
environments: [DefaultEnvironment.Development],
|
||||
}),
|
||||
]),
|
||||
testProjectId
|
||||
);
|
||||
});
|
||||
|
||||
describe('--force option', () => {
|
||||
it('automatically overrides existing variables without confirmation when --force is used', async () => {
|
||||
const existingVariable = {
|
||||
@@ -275,5 +321,79 @@ SECRET_KEY=super-secret-key`;
|
||||
'Using --force flag: automatically overriding sensitive variables.'
|
||||
);
|
||||
});
|
||||
|
||||
it('throws before confirming overwrite in non-interactive mode when --force is not used', async () => {
|
||||
const existingVariable = {
|
||||
id: 'var1',
|
||||
name: 'VARIABLE_NAME',
|
||||
value: 'old variable value',
|
||||
environments: [DefaultEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.String,
|
||||
};
|
||||
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValue([existingVariable]);
|
||||
|
||||
const command = new EnvPush(
|
||||
['--environment', 'development', '--path', testEnvPath, '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
'Cannot confirm overwriting existing variables in non-interactive mode. Use --force to overwrite them.'
|
||||
);
|
||||
expect(confirmAsync).not.toHaveBeenCalled();
|
||||
expect(
|
||||
EnvironmentVariableMutation.createBulkEnvironmentVariablesForAppAsync
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('throws before confirming sensitive overwrite in non-interactive mode when --force is not used', async () => {
|
||||
const existingSensitiveVariable = {
|
||||
id: 'var1',
|
||||
name: 'SECRET_KEY',
|
||||
value: 'super-secret-key',
|
||||
environments: [DefaultEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Sensitive,
|
||||
type: EnvironmentSecretType.String,
|
||||
};
|
||||
|
||||
jest
|
||||
.mocked(EnvironmentVariablesQuery.byAppIdAsync)
|
||||
.mockResolvedValue([existingSensitiveVariable]);
|
||||
|
||||
const command = new EnvPush(
|
||||
['--environment', 'development', '--path', testEnvPath, '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
'Cannot confirm overwriting sensitive variables in non-interactive mode. Use --force to overwrite them.'
|
||||
);
|
||||
expect(confirmAsync).not.toHaveBeenCalled();
|
||||
expect(
|
||||
EnvironmentVariableMutation.createBulkEnvironmentVariablesForAppAsync
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -253,6 +253,50 @@ describe(EnvUpdate, () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects positional environment and --variable-environment used together', async () => {
|
||||
const mockVariable = {
|
||||
id: 'var1',
|
||||
name: 'TEST_VAR_1',
|
||||
value: 'value1',
|
||||
environments: [DefaultEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.String,
|
||||
};
|
||||
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce([mockVariable]);
|
||||
jest.mocked(EnvironmentVariableMutation.updateAsync).mockResolvedValueOnce(mockVariable);
|
||||
|
||||
const command = new EnvUpdate(
|
||||
[
|
||||
'development',
|
||||
'--variable-name',
|
||||
'TEST_VAR_1',
|
||||
'--variable-environment',
|
||||
'production',
|
||||
'--value',
|
||||
'new-value',
|
||||
'--scope',
|
||||
'project',
|
||||
'--non-interactive',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
const getContextAsyncSpy = jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId,
|
||||
});
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"You can't use the --variable-environment flag when the environment is passed as a positional argument. Run `eas env:update --help` for more information."
|
||||
);
|
||||
expect(getContextAsyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('processes file type variable in non-interactive mode with --type file and --value', async () => {
|
||||
const testFilePath = '/path/to/test-file.json';
|
||||
const testFileBase64 = 'dGVzdCBmaWxlIGNvbnRlbnQ=';
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EnvDelete cancels deletion when user does not confirm 1`] = `"Environment variable needs 'name' to be specified when running in non-interactive mode. Run the command with [1m--variable-name VARIABLE_NAME[22m flag to fix the issue"`;
|
||||
+27
-5
@@ -11,6 +11,8 @@ import {
|
||||
EASMultiEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableVisibilityFlag,
|
||||
markRequiredInNonInteractiveMode,
|
||||
validateNonInteractiveRequiredInputs,
|
||||
} from '../../commandUtils/flags';
|
||||
import {
|
||||
EnvironmentSecretType,
|
||||
@@ -61,6 +63,11 @@ export default class EnvCreate extends EasCommand {
|
||||
'create an environment variable for the current project or account (deprecated, use eas env:set)';
|
||||
static override hidden = true;
|
||||
|
||||
static override examples = [
|
||||
'$ eas env:create --environment production --environment preview --name API_TOKEN --value "$API_TOKEN" --visibility sensitive',
|
||||
'$ eas env:create --environment production --scope account --name SHARED_TOKEN --value "$SHARED_TOKEN" --visibility secret',
|
||||
];
|
||||
|
||||
static override args = {
|
||||
environment: Args.string({
|
||||
description:
|
||||
@@ -71,22 +78,24 @@ export default class EnvCreate extends EasCommand {
|
||||
|
||||
static override flags = {
|
||||
name: Flags.string({
|
||||
description: 'Name of the variable',
|
||||
description: '(required in non-interactive mode) Name of the variable',
|
||||
}),
|
||||
value: Flags.string({
|
||||
description: 'Text value or the variable',
|
||||
description:
|
||||
'(required in non-interactive mode) Text value of the variable, or a file path when --type=file',
|
||||
}),
|
||||
force: Flags.boolean({
|
||||
description: 'Overwrite existing variable',
|
||||
description:
|
||||
'(required when overwriting in non-interactive mode) Overwrite existing variable',
|
||||
default: false,
|
||||
}),
|
||||
type: Flags.option({
|
||||
description: 'The type of variable',
|
||||
options: ['string', 'file'] as const,
|
||||
})(),
|
||||
...EASVariableVisibilityFlag,
|
||||
...markRequiredInNonInteractiveMode(EASVariableVisibilityFlag),
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
...EASMultiEnvironmentFlag,
|
||||
...markRequiredInNonInteractiveMode(EASMultiEnvironmentFlag),
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
@@ -103,6 +112,19 @@ export default class EnvCreate extends EasCommand {
|
||||
const { args, flags } = await this.parse(EnvCreate);
|
||||
|
||||
const validatedFlags = this.sanitizeFlags(flags);
|
||||
validateNonInteractiveRequiredInputs({
|
||||
nonInteractive: validatedFlags['non-interactive'],
|
||||
requiredInputs: [
|
||||
{ name: '--name', value: validatedFlags.name },
|
||||
{ name: '--value', value: validatedFlags.value },
|
||||
{ name: '--visibility', value: validatedFlags.visibility },
|
||||
{
|
||||
name: '--environment',
|
||||
value: args.environment ?? validatedFlags.environment,
|
||||
},
|
||||
],
|
||||
helpCommand: 'eas env:create --help',
|
||||
});
|
||||
|
||||
const {
|
||||
projectId,
|
||||
|
||||
+16
-10
@@ -1,6 +1,5 @@
|
||||
import { Args, Flags } from '@oclif/core';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import {
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
EASEnvironmentVariableScopeFlagValue,
|
||||
EASNonInteractiveFlag,
|
||||
EasEnvironmentFlagParameters,
|
||||
validateNonInteractiveRequiredInputs,
|
||||
} from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableScope } from '../../graphql/generated';
|
||||
import { EnvironmentVariableMutation } from '../../graphql/mutations/EnvironmentVariableMutation';
|
||||
@@ -33,9 +33,14 @@ interface RawDeleteFlags {
|
||||
export default class EnvDelete extends EasCommand {
|
||||
static override description = 'delete an environment variable for the current project or account';
|
||||
|
||||
static override examples = [
|
||||
'$ eas env:delete --variable-environment production --variable-name API_TOKEN',
|
||||
'$ eas env:delete --scope account --variable-name SHARED_TOKEN',
|
||||
];
|
||||
|
||||
static override flags = {
|
||||
'variable-name': Flags.string({
|
||||
description: 'Name of the variable to delete',
|
||||
description: '(required in non-interactive mode) Name of the variable to delete',
|
||||
}),
|
||||
'variable-environment': Flags.string({
|
||||
...EasEnvironmentFlagParameters,
|
||||
@@ -148,14 +153,15 @@ export default class EnvDelete extends EasCommand {
|
||||
flags: RawDeleteFlags,
|
||||
{ environment }: { environment?: string }
|
||||
): DeleteFlags {
|
||||
if (flags['non-interactive']) {
|
||||
if (!flags['variable-name']) {
|
||||
throw new Error(
|
||||
`Environment variable needs 'name' to be specified when running in non-interactive mode. Run the command with ${chalk.bold(
|
||||
'--variable-name VARIABLE_NAME'
|
||||
)} flag to fix the issue`
|
||||
);
|
||||
}
|
||||
validateNonInteractiveRequiredInputs({
|
||||
nonInteractive: flags['non-interactive'],
|
||||
requiredInputs: [{ name: '--variable-name', value: flags['variable-name'] }],
|
||||
helpCommand: 'eas env:delete --help',
|
||||
});
|
||||
if (environment && flags['variable-environment']) {
|
||||
throw new Error(
|
||||
"You can't use the --variable-environment flag when the environment is passed as a positional argument. Run `eas env:delete --help` for more information."
|
||||
);
|
||||
}
|
||||
|
||||
const scope =
|
||||
|
||||
+2
@@ -29,6 +29,8 @@ export default class EnvExec extends EasCommand {
|
||||
static override description =
|
||||
'execute a command with environment variables from the selected environment';
|
||||
|
||||
static override examples = ["$ eas env:exec production 'bun run build'"];
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
|
||||
+20
-14
@@ -9,6 +9,7 @@ import {
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableFormatFlag,
|
||||
EasEnvironmentFlagParameters,
|
||||
validateNonInteractiveRequiredInputs,
|
||||
} from '../../commandUtils/flags';
|
||||
import {
|
||||
EnvironmentVariableFragment,
|
||||
@@ -39,6 +40,11 @@ interface GetFlags {
|
||||
export default class EnvGet extends EasCommand {
|
||||
static override description = 'view an environment variable for the current project or account';
|
||||
|
||||
static override examples = [
|
||||
'$ eas env:get --variable-environment production --variable-name API_URL',
|
||||
'$ eas env:get --variable-environment production --variable-name API_TOKEN --format long',
|
||||
];
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
@@ -54,11 +60,11 @@ export default class EnvGet extends EasCommand {
|
||||
|
||||
static override flags = {
|
||||
'variable-name': Flags.string({
|
||||
description: 'Name of the variable',
|
||||
description: '(required) Name of the variable',
|
||||
}),
|
||||
'variable-environment': Flags.string({
|
||||
...EasEnvironmentFlagParameters,
|
||||
description: 'Current environment of the variable',
|
||||
description: '(required in non-interactive mode) Current environment of the variable',
|
||||
}),
|
||||
...EASVariableFormatFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
@@ -134,20 +140,20 @@ export default class EnvGet extends EasCommand {
|
||||
}
|
||||
|
||||
private sanitizeInputs(flags: RawGetFlags, { environment }: { environment?: string }): GetFlags {
|
||||
if (flags['non-interactive']) {
|
||||
if (!flags['variable-name']) {
|
||||
throw new Error('Variable name is required. Run the command with --variable-name flag.');
|
||||
}
|
||||
if (!flags.scope) {
|
||||
throw new Error('Scope is required. Run the command with --scope flag.');
|
||||
}
|
||||
if (!(flags['variable-environment'] ?? environment)) {
|
||||
throw new Error('Environment is required.');
|
||||
}
|
||||
}
|
||||
validateNonInteractiveRequiredInputs({
|
||||
nonInteractive: flags['non-interactive'],
|
||||
requiredInputs: [
|
||||
{ name: '--variable-name', value: flags['variable-name'] },
|
||||
{
|
||||
name: '--variable-environment',
|
||||
value: environment ?? flags['variable-environment'],
|
||||
},
|
||||
],
|
||||
helpCommand: 'eas env:get --help',
|
||||
});
|
||||
if (environment && flags['variable-environment']) {
|
||||
throw new Error(
|
||||
"You can't use both --variable-environment flag when environment is passed as an argument. Run `eas env:get --help` for more information."
|
||||
"You can't use the --variable-environment flag when the environment is passed as a positional argument. Run `eas env:get --help` for more information."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+7
-3
@@ -7,7 +7,9 @@ import {
|
||||
EASEnvironmentVariableScopeFlag,
|
||||
EASEnvironmentVariableScopeFlagValue,
|
||||
EASMultiEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableFormatFlag,
|
||||
markRequiredInNonInteractiveMode,
|
||||
} from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableScope } from '../../graphql/generated';
|
||||
import {
|
||||
@@ -72,7 +74,7 @@ interface RawListFlags {
|
||||
environment: string[] | undefined;
|
||||
'include-sensitive': boolean;
|
||||
'include-file-content': boolean;
|
||||
'non-interactive'?: boolean;
|
||||
'non-interactive': boolean;
|
||||
}
|
||||
|
||||
interface ListFlags {
|
||||
@@ -87,6 +89,8 @@ interface ListFlags {
|
||||
export default class EnvList extends EasCommand {
|
||||
static override description = 'list environment variables for the current project or account';
|
||||
|
||||
static override examples = ['$ eas env:list --environment production'];
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
@@ -101,9 +105,10 @@ export default class EnvList extends EasCommand {
|
||||
description: 'Display files content in the output',
|
||||
default: false,
|
||||
}),
|
||||
...EASMultiEnvironmentFlag,
|
||||
...markRequiredInNonInteractiveMode(EASMultiEnvironmentFlag),
|
||||
...EASVariableFormatFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override args = {
|
||||
@@ -186,7 +191,6 @@ export default class EnvList extends EasCommand {
|
||||
|
||||
return {
|
||||
...flags,
|
||||
'non-interactive': flags['non-interactive'] ?? false,
|
||||
environment: environments,
|
||||
scope:
|
||||
flags.scope === 'account'
|
||||
|
||||
+11
-2
@@ -4,7 +4,11 @@ import * as fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASEnvironmentFlag, EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import {
|
||||
EASEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
markRequiredInNonInteractiveMode,
|
||||
} from '../../commandUtils/flags';
|
||||
import { EnvironmentSecretType, EnvironmentVariableVisibility } from '../../graphql/generated';
|
||||
import {
|
||||
EnvironmentVariableWithFileContent,
|
||||
@@ -18,6 +22,11 @@ export default class EnvPull extends EasCommand {
|
||||
static override description =
|
||||
'pull environment variables for the selected environment to .env file';
|
||||
|
||||
static override examples = [
|
||||
'$ eas env:pull --environment development',
|
||||
'$ eas env:pull --environment production --path .env.production',
|
||||
];
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
@@ -34,7 +43,7 @@ export default class EnvPull extends EasCommand {
|
||||
|
||||
static override flags = {
|
||||
...EASNonInteractiveFlag,
|
||||
...EASEnvironmentFlag,
|
||||
...markRequiredInNonInteractiveMode(EASEnvironmentFlag),
|
||||
path: Flags.string({
|
||||
description: 'Path to the result `.env` file',
|
||||
default: '.env.local',
|
||||
|
||||
+33
-6
@@ -4,7 +4,11 @@ import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASMultiEnvironmentFlag } from '../../commandUtils/flags';
|
||||
import {
|
||||
EASMultiEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
markRequiredInNonInteractiveMode,
|
||||
} from '../../commandUtils/flags';
|
||||
import {
|
||||
CreateEnvironmentVariableInput,
|
||||
EnvironmentVariableFragment,
|
||||
@@ -21,13 +25,17 @@ export default class EnvPush extends EasCommand {
|
||||
static override description =
|
||||
'push environment variables from .env file to the selected environment';
|
||||
|
||||
static override examples = [
|
||||
'$ eas env:push --environment production --path .env.production --force',
|
||||
];
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
...EASMultiEnvironmentFlag,
|
||||
...markRequiredInNonInteractiveMode(EASMultiEnvironmentFlag),
|
||||
path: Flags.string({
|
||||
description: 'Path to the input `.env` file',
|
||||
default: '.env.local',
|
||||
@@ -36,6 +44,7 @@ export default class EnvPush extends EasCommand {
|
||||
description: 'Skip confirmation and automatically override existing variables',
|
||||
default: false,
|
||||
}),
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override args = {
|
||||
@@ -49,18 +58,23 @@ export default class EnvPush extends EasCommand {
|
||||
async runAsync(): Promise<void> {
|
||||
const { args, flags } = await this.parse(EnvPush);
|
||||
|
||||
let { environment: environments, path: envPath, force } = this.parseFlagsAndArgs(flags, args);
|
||||
let {
|
||||
environment: environments,
|
||||
path: envPath,
|
||||
force,
|
||||
'non-interactive': nonInteractive,
|
||||
} = this.parseFlagsAndArgs(flags, args);
|
||||
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvPush, {
|
||||
nonInteractive: false,
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
if (!environments) {
|
||||
environments = await promptVariableEnvironmentAsync({
|
||||
nonInteractive: false,
|
||||
nonInteractive,
|
||||
multiple: true,
|
||||
canEnterCustomEnvironment: true,
|
||||
graphqlClient,
|
||||
@@ -132,6 +146,12 @@ export default class EnvPush extends EasCommand {
|
||||
)} environment variables already exist in ${displayedEnvironment} environment. Do you want to override them all?`
|
||||
: `The ${variableNames[0]} environment variable already exists in ${displayedEnvironment} environment. Do you want to override it?`;
|
||||
|
||||
if (nonInteractive) {
|
||||
throw new Error(
|
||||
'Cannot confirm overwriting existing variables in non-interactive mode. Use --force to overwrite them.'
|
||||
);
|
||||
}
|
||||
|
||||
const confirm = await confirmAsync({
|
||||
message: confirmationMessage,
|
||||
});
|
||||
@@ -179,6 +199,12 @@ export default class EnvPush extends EasCommand {
|
||||
const existingSensitiveVariablesNames = existingSensitiveVariables.map(
|
||||
variable => `- ${variable.name}`
|
||||
);
|
||||
if (nonInteractive) {
|
||||
throw new Error(
|
||||
'Cannot confirm overwriting sensitive variables in non-interactive mode. Use --force to overwrite them.'
|
||||
);
|
||||
}
|
||||
|
||||
const confirm = await confirmAsync({
|
||||
message: `You are about to overwrite sensitive variables.\n${existingSensitiveVariablesNames.join(
|
||||
'\n'
|
||||
@@ -212,9 +238,10 @@ export default class EnvPush extends EasCommand {
|
||||
path: string;
|
||||
environment: string[] | undefined;
|
||||
force: boolean;
|
||||
'non-interactive': boolean;
|
||||
},
|
||||
{ environment }: { environment?: string }
|
||||
): { environment?: string[]; path: string; force: boolean } {
|
||||
): { environment?: string[]; path: string; force: boolean; 'non-interactive': boolean } {
|
||||
const environments = flags.environment ?? (environment ? [environment] : undefined);
|
||||
|
||||
return {
|
||||
|
||||
+25
-13
@@ -13,6 +13,7 @@ import {
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableVisibilityFlag,
|
||||
EasEnvironmentFlagParameters,
|
||||
validateNonInteractiveRequiredInputs,
|
||||
} from '../../commandUtils/flags';
|
||||
import {
|
||||
EnvironmentSecretType,
|
||||
@@ -63,13 +64,20 @@ interface UpdateFlags {
|
||||
}
|
||||
|
||||
export default class EnvUpdate extends EasCommand {
|
||||
static override description =
|
||||
'update an environment variable on the current project or account (deprecated, use eas env:set)';
|
||||
static override description = `update an environment variable on the current project or account (deprecated, use eas env:set)
|
||||
|
||||
In non-interactive mode, if --type is provided, --value is also required.`;
|
||||
|
||||
static override hidden = true;
|
||||
|
||||
static override examples = [
|
||||
'$ eas env:update --variable-environment production --variable-name API_URL --value https://api.example.com',
|
||||
'$ eas env:update --variable-environment development --variable-name GOOGLE_SERVICES_JSON --type file --value ./google-services.json --environment production',
|
||||
];
|
||||
|
||||
static override flags = {
|
||||
'variable-name': Flags.string({
|
||||
description: 'Current name of the variable',
|
||||
description: '(required in non-interactive mode) Current name of the variable',
|
||||
}),
|
||||
'variable-environment': Flags.string({
|
||||
...EasEnvironmentFlagParameters,
|
||||
@@ -79,7 +87,8 @@ export default class EnvUpdate extends EasCommand {
|
||||
description: 'New name of the variable',
|
||||
}),
|
||||
value: Flags.string({
|
||||
description: 'New value or the variable',
|
||||
description:
|
||||
'(required with --type in non-interactive mode) New value for the variable, or a file path when --type=file',
|
||||
}),
|
||||
type: Flags.option({
|
||||
description: 'The type of variable',
|
||||
@@ -217,15 +226,18 @@ export default class EnvUpdate extends EasCommand {
|
||||
flags: RawUpdateFlags,
|
||||
{ environment }: { environment?: string }
|
||||
): UpdateFlags {
|
||||
if (flags['non-interactive']) {
|
||||
if (!flags['variable-name']) {
|
||||
throw new Error(
|
||||
'Current name is required in non-interactive mode. Run the command with --variable-name flag.'
|
||||
);
|
||||
}
|
||||
if (flags['type'] && !flags['value']) {
|
||||
throw new Error('Value is required when type is set. Run the command with --value flag.');
|
||||
}
|
||||
validateNonInteractiveRequiredInputs({
|
||||
nonInteractive: flags['non-interactive'],
|
||||
requiredInputs: [
|
||||
{ name: '--variable-name', value: flags['variable-name'] },
|
||||
{ if: !!flags.type, name: '--value', value: flags.value },
|
||||
],
|
||||
helpCommand: 'eas env:update --help',
|
||||
});
|
||||
if (environment && flags['variable-environment']) {
|
||||
throw new Error(
|
||||
"You can't use the --variable-environment flag when the environment is passed as a positional argument. Run `eas env:update --help` for more information."
|
||||
);
|
||||
}
|
||||
|
||||
const scope =
|
||||
|
||||
Reference in new issue
Block a user