Skip to content
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

feat: env management commands #37

Merged
merged 30 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c382463
feat: refactored login flow and hooks
35C4n0r Nov 6, 2024
197314f
feat: more verbose errors
35C4n0r Nov 6, 2024
6f59601
fix: LoginFlow when apiKey is given.
35C4n0r Nov 7, 2024
b9ec416
chore: remove comments
35C4n0r Nov 7, 2024
7136f13
ref: refactor hooks
35C4n0r Nov 7, 2024
29c2ac3
feat: ApiKey hook
35C4n0r Nov 7, 2024
bb162b0
feat: api-key validation
35C4n0r Nov 8, 2024
d98af59
fix: fix bugs
35C4n0r Nov 20, 2024
9f66f7f
Merge branch 'refs/heads/main' into ref-login-components
35C4n0r Nov 20, 2024
00c9edf
chore: remove log statements
35C4n0r Nov 20, 2024
125ea84
chore: minor imporvs
35C4n0r Nov 24, 2024
c957917
feat: env management commands
35C4n0r Nov 24, 2024
8b7cab5
Merge branch 'refs/heads/main' into feat-env-command
35C4n0r Nov 24, 2024
573c523
chore: lint
35C4n0r Nov 24, 2024
4d99de8
chore: lint
35C4n0r Nov 24, 2024
76c0cbe
chore: lint
35C4n0r Nov 25, 2024
72c5837
chore: fix lint issues
35C4n0r Nov 25, 2024
b896855
chore: add tests
35C4n0r Nov 26, 2024
98103aa
fix: minor updates
35C4n0r Nov 29, 2024
ecab4bb
Merge branch 'refs/heads/main' into feat-env-command
35C4n0r Nov 29, 2024
0745d26
chore: update package-lock.json
35C4n0r Nov 29, 2024
02875e8
fix: take env name in input
35C4n0r Dec 1, 2024
286561a
fix: small fixes
35C4n0r Dec 1, 2024
1026d3e
fix: small fixes
35C4n0r Dec 2, 2024
9293499
chore: eslint fixes
35C4n0r Dec 2, 2024
f30fead
fix: browser login.tsx
35C4n0r Dec 3, 2024
93300c0
chore: minor changes
35C4n0r Dec 3, 2024
7f30e7a
fix: address review comments
35C4n0r Dec 13, 2024
dfe93ea
fix: address review comments
35C4n0r Dec 13, 2024
e43980a
Remove Redundant
gemanor Dec 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
node_modules
dist
coverage
.tsimp
.tsimp

.idea
.vscode
36 changes: 22 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
],
"dependencies": {
"clipboardy": "^4.0.0",
"delay": "^6.0.0",
"fuse.js": "^7.0.0",
"ink": "^5.0.1",
"ink": "^5.1.0",
"ink-ascii": "^0.0.4",
"ink-big-text": "^2.0.0",
"ink-gradient": "^3.0.0",
Expand Down
270 changes: 270 additions & 0 deletions source/commands/env/copy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import React, { useEffect, useState } from 'react';
import { Text } from 'ink';
import { option } from 'pastel';
import { TextInput } from '@inkjs/ui';
import { TokenType, tokenType } from '../../lib/auth.js';
import zod from 'zod';
import { type infer as zInfer } from 'zod';
import { useApiKeyApi } from '../../hooks/useApiKeyApi.js';
import { useEnvironmentApi } from '../../hooks/useEnvironmentApi.js';
import EnvironmentSelection, {
ActiveState,
} from '../../components/EnvironmentSelection.js';
import SelectInput from 'ink-select-input';

export const options = zod.object({
35C4n0r marked this conversation as resolved.
Show resolved Hide resolved
key: zod.string().describe(
option({
description:
'API Key to be used for the environment copying (should be least an project level key)',
}),
),
existing: zod
.boolean()
.optional()
.describe(
option({
description:
'Provide this API Key if you want to copy to an existing account',
}),
),
envName: zod
.string()
.optional()
.describe(
option({
description: 'Name for the new environment to copy to',
}),
),
envDescription: zod
.string()
.optional()
.describe(
option({
description: 'Description for the new environment to copy to',
}),
),
conflictStrategy: zod
.string()
.optional()
.describe(
option({
description: 'Conflict Strategy to use.',
}),
),
});

type Props = {
readonly options: zInfer<typeof options>;
};

interface EnvCopyBody {
existingEnvId?: string | null;
newEnvKey?: string | null;
newEnvName?: string | null;
newEnvDescription?: string | null;
conflictStrategy?: string | null;
}

export default function Copy({
options: { key: apiKey, existing, envName, envDescription, conflictStrategy },
}: Props) {
const [error, setError] = React.useState<string | null>(null);
const [authToken, setAuthToken] = React.useState<string | null>(null);
const [state, setState] = useState<
| 'loading'
| 'selecting-id'
| 'selecting-name'
| 'selecting-description'
| 'selecting-strategy'
| 'copying'
| 'done'
>('loading');
const [projectFrom, setProjectFrom] = useState<string | null>(null);
const [envToId, setEnvToId] = useState<string | null>(null);
const [envToName, setEnvToName] = useState<string | undefined>(envName);
const [envFrom, setEnvFrom] = useState<string | null>(null);
const [envToDescription, setEnvToDescription] = useState<string | undefined>(
envDescription,
);
const [envToConflictStrategy, setEnvToConflictStrategy] = useState<
string | undefined
>(conflictStrategy);

const { getApiKeyScope } = useApiKeyApi();
const { copyEnvironment } = useEnvironmentApi();

useEffect(() => {
if (error || state === 'done') {
process.exit(1);
}
}, [error, state]);

useEffect(() => {
const handleEnvCopy = async (envCopyBody: EnvCopyBody) => {
let body = {};
if (envCopyBody.existingEnvId) {
body = {
target_env: { existing: envCopyBody.existingEnvId },
};
} else if (envCopyBody.newEnvKey && envCopyBody.newEnvName) {
body = {
target_env: {
new: {
key: envCopyBody.newEnvKey,
35C4n0r marked this conversation as resolved.
Show resolved Hide resolved
name: envCopyBody.newEnvName,
description: envCopyBody.newEnvDescription ?? '',
},
},
};
}
if (envToConflictStrategy) {
body = {
...body,
conflict_strategy: envCopyBody.conflictStrategy ?? 'fail',
};
}
const { error } = await copyEnvironment(
projectFrom ?? '',
envFrom ?? '',
apiKey,
null,
body,
);
if (error) {
setError(`Error while copying Environment: ${error}`);
return;
}
setState('done');
};

if (
((envToName && envToDescription && envToConflictStrategy) ||
envName ||
envToId) &&
envFrom
) {
setState('copying');
handleEnvCopy({
newEnvKey: envToName,
newEnvName: envToName,
newEnvDescription: envToDescription,
existingEnvId: envToId,
conflictStrategy: envToConflictStrategy,
});
}
}, [
envToId,
existing,
envToName,
envFrom,
envToDescription,
envToConflictStrategy,
envName,
copyEnvironment,
projectFrom,
apiKey,
]);

useEffect(() => {
// Step 1, we use the API Key provided by the user &
// checks if the api_key scope >= project_level &
// sets the apiKey and sets the projectFrom

const validateApiKeyScope = async () => {
35C4n0r marked this conversation as resolved.
Show resolved Hide resolved
const { response: scope, error } = await getApiKeyScope(apiKey);
if (error) setError(error);
if (scope.environment_id) {
setError('Please provide a Project level token or above');
return;
} else {
setProjectFrom(scope.project_id);
setAuthToken(apiKey);
}
};

if (apiKey && tokenType(apiKey) === TokenType.APIToken) {
validateApiKeyScope();
} else {
setError('Invalid API Key. Please provide a valid API Key.');
return;
}
}, [apiKey, getApiKeyScope]);

const handleEnvFromSelection = (
_organisation_id: ActiveState,
_project_id: ActiveState,
environment_id: ActiveState,
) => {
setEnvFrom(environment_id.value);
if (existing) {
setState('selecting-id');
} else if (!envName) {
setState('selecting-name');
}
};

return (
<>
{state === 'loading' && authToken && (
<>
<Text>Select an existing Environment to copy from.</Text>
<EnvironmentSelection
accessToken={authToken}
cookie={''}
onComplete={handleEnvFromSelection}
onError={setError}
/>
</>
)}
{authToken && state === 'selecting-id' && envFrom && (
<>
<Text>Input the existing EnvironmentId to copy to.</Text>
<TextInput onSubmit={setEnvToId} placeholder={'Enter Id here...'} />
</>
)}
{authToken && state === 'selecting-name' && envFrom && (
<>
<Text>Input the new Environment name to copy to.</Text>
<TextInput
onSubmit={name => {
setEnvToName(name);
setState('selecting-description');
}}
placeholder={'Enter name here...'}
/>
</>
)}
{authToken && state === 'selecting-description' && (
<>
<Text>Input the new Environment Description.</Text>
<TextInput
onSubmit={description => {
setEnvToDescription(description);
setState('selecting-strategy');
}}
placeholder={'Enter description here...'}
/>
</>
)}
{authToken && state === 'selecting-strategy' && (
<>
<Text>Select the conflict strategy</Text>
<SelectInput
onSelect={strategy => {
setEnvToConflictStrategy(strategy.value);
setState('copying');
}}
items={[
{ label: 'fail', value: 'fail' },
{ label: 'overwrite', value: 'overwrite' },
]}
/>
</>
)}

{state === 'done' && <Text>Environment copied successfully</Text>}
{error && <Text>{error}</Text>}
</>
);
}
Loading
Loading