-
Notifications
You must be signed in to change notification settings - Fork 29
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
Open
35C4n0r
wants to merge
27
commits into
permitio:main
Choose a base branch
from
35C4n0r:feat-env-command
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
c382463
feat: refactored login flow and hooks
35C4n0r 197314f
feat: more verbose errors
35C4n0r 6f59601
fix: LoginFlow when apiKey is given.
35C4n0r b9ec416
chore: remove comments
35C4n0r 7136f13
ref: refactor hooks
35C4n0r 29c2ac3
feat: ApiKey hook
35C4n0r bb162b0
feat: api-key validation
35C4n0r d98af59
fix: fix bugs
35C4n0r 9f66f7f
Merge branch 'refs/heads/main' into ref-login-components
35C4n0r 00c9edf
chore: remove log statements
35C4n0r 125ea84
chore: minor imporvs
35C4n0r c957917
feat: env management commands
35C4n0r 8b7cab5
Merge branch 'refs/heads/main' into feat-env-command
35C4n0r 573c523
chore: lint
35C4n0r 4d99de8
chore: lint
35C4n0r 76c0cbe
chore: lint
35C4n0r 72c5837
chore: fix lint issues
35C4n0r b896855
chore: add tests
35C4n0r 98103aa
fix: minor updates
35C4n0r ecab4bb
Merge branch 'refs/heads/main' into feat-env-command
35C4n0r 0745d26
chore: update package-lock.json
35C4n0r 02875e8
fix: take env name in input
35C4n0r 286561a
fix: small fixes
35C4n0r 1026d3e
fix: small fixes
35C4n0r 9293499
chore: eslint fixes
35C4n0r f30fead
fix: browser login.tsx
35C4n0r 93300c0
chore: minor changes
35C4n0r File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 |
---|---|---|
@@ -1,4 +1,7 @@ | ||
node_modules | ||
dist | ||
coverage | ||
.tsimp | ||
.tsimp | ||
|
||
.idea | ||
.vscode |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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,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({ | ||
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 () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This hook should be external and be shared with other project-level commands such as select |
||
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>} | ||
</> | ||
); | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The structure of the inputs needs to make true sense for consistent flow.
Here are the following variables that should be supported:
API Key to be used for the environment copying (should be at least a project level key)
Optional: set the environment ID to copy from. In case not set, the CLI lets you select one.
Optional: The environment name to copy to. In case not set, the CLI will ask you for one.
Optional: The new environment description. In case not set, the CLI will ask you for it.
Optional: copy the environment to an existing environment. In case this variable is set, the 'name' and 'description' variables will be ignored
'Optional: Set the environment conflict strategy. In case not set, will use "fail"
The logic should be: