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 multiple accounts and instances #851

Merged
merged 74 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
d9fa0bb
feat(cli): Multiple accounts in CLI
byawitz May 21, 2024
6eeef88
Merge branch 'refs/heads/feat-cli-whoami-command' into feat-multiple-…
byawitz May 22, 2024
fd78fad
feat(cli): Hooking migration before any command
byawitz May 22, 2024
9387d95
feat(cli): Interactive multiple account changing
byawitz May 22, 2024
d250003
feat(cli): Adding json option to list accounts
byawitz May 22, 2024
144e043
feat(cli): Interactive multiple user logout
byawitz May 22, 2024
9a61aef
fix(cli): Changing migrate to regular function
byawitz May 22, 2024
0cf5edd
fix(cli): Login question
byawitz May 22, 2024
7c07523
fix(cli): Extracting migrate
byawitz May 22, 2024
58df0ee
fix(cli): Renaming the migrate function name
byawitz May 22, 2024
8c77004
fix(cli): Excluding migrate from tests
byawitz May 22, 2024
c1e5c41
fix(cli): Adding migrate to tests
byawitz May 22, 2024
ea494b7
fix(cli): Adapting the `client` command and bug when getting key.
byawitz May 22, 2024
8c7b894
refactor(cli): Multiple account login refactoring
byawitz May 24, 2024
32452ff
refactor(cli): Review fixing
byawitz May 28, 2024
0b7a25a
Merge branch 'feat-cli-g2' into feat-multiple-accounts-and-instances
Meldiron May 29, 2024
ab9e297
Merge branch 'refs/heads/feat-cli-g2' into feat-multiple-accounts-and…
byawitz May 29, 2024
02be0f0
feat(cli): Updating push to include the project
byawitz May 29, 2024
9ac316d
feat(cli): Updating push to include the project
byawitz May 29, 2024
e089e9c
feat(cli): Pull all resources
byawitz May 29, 2024
3d36787
Merge branch 'refs/heads/feat-pull-project-settings' into feat-push-p…
byawitz May 29, 2024
cf2ce7d
Merge branch 'refs/heads/feat-non-destructive-db-actions' into feat-m…
byawitz May 29, 2024
705647d
chore(cli): Adding headless login
byawitz May 29, 2024
2962472
Merge branch 'refs/heads/feat-multiple-accounts-and-instances' into f…
byawitz May 29, 2024
87e88c3
chore(cli): Adapting pull request
byawitz May 29, 2024
063ac9e
refactor(cli): Texts and removing unnecessary options
byawitz May 30, 2024
0135a4f
refactor(cli): removing unneeded async
byawitz May 30, 2024
dcea1df
feat(cli): Skipping unavailable push resources
byawitz May 30, 2024
f78b368
Merge pull request #865 from appwrite/feat-push-pull-all
christyjacob4 May 30, 2024
2e8f560
feat(cli): Showing deployed function URL
byawitz May 30, 2024
b35b4bb
Merge pull request #866 from appwrite/feat-function-deployment-url
christyjacob4 May 30, 2024
7efedaf
refactor(cli): Adding pagination and reorder questions
byawitz May 31, 2024
930685c
feat(cli): headless project setup
byawitz May 31, 2024
5bba9cd
feat(cli): creating project if not exist
byawitz May 31, 2024
3621c3c
refactor(cli): reorder questions
byawitz May 31, 2024
64c364e
Merge branch 'refs/heads/feat-multiple-accounts-and-instances' into f…
byawitz May 31, 2024
d36a623
wip
byawitz Jun 3, 2024
406ef22
feat: Init project only ID
byawitz Jun 7, 2024
fb8e08a
feat: Init to local only & database creation
byawitz Jun 7, 2024
9717830
refactor: Improve command line to use `rawArgs`
byawitz Jun 7, 2024
2fd8f2b
feat: Added list search
byawitz Jun 7, 2024
0b817a7
feat: Added Register
byawitz Jun 7, 2024
acef728
feat: Tip for detailed error
byawitz Jun 7, 2024
990557a
feat: clearer function summary
byawitz Jun 7, 2024
33a5680
feat: Adding error when enable to open with default browser
byawitz Jun 7, 2024
471fc85
feat: Adding endpoint to whoami
byawitz Jun 7, 2024
f24abb6
feat: Adding self-hosted to project init
byawitz Jun 7, 2024
85167e9
feat: Removing unusable accounts
byawitz Jun 7, 2024
069f70c
feat: Ask when deleting, and explanations
byawitz Jun 7, 2024
6d8b6f2
refactor: Make sure `min` and `max` are numbers
byawitz Jun 7, 2024
f64000e
refactor: Asking unanswered questions
byawitz Jun 7, 2024
c393058
Merge pull request #871 from appwrite/feat-search-list
christyjacob4 Jun 10, 2024
06b2a4b
Merge pull request #870 from appwrite/feat-improve-report
christyjacob4 Jun 10, 2024
034d08e
Update templates/cli/lib/questions.js.twig
christyjacob4 Jun 11, 2024
49488a6
Merge pull request #875 from appwrite/feat-collection-push-improvements
christyjacob4 Jun 11, 2024
469594c
Merge branch 'refs/heads/feat-arg-project-init' into feat-general-imp…
byawitz Jun 11, 2024
8420eb0
fix: reviews
byawitz Jun 11, 2024
cc78c01
Merge remote-tracking branch 'origin/feat-general-improvements' into …
byawitz Jun 11, 2024
4ae99d5
fix: reviews
byawitz Jun 11, 2024
b5ca8bf
fix: reviews
byawitz Jun 11, 2024
aabddae
fix: texts
byawitz Jun 11, 2024
dea75b4
refactor: Removing login from init
byawitz Jun 11, 2024
5fe0bd2
refactor: Removing self-hosted from login
byawitz Jun 11, 2024
2db03b5
Merge pull request #874 from appwrite/feat-general-improvements
christyjacob4 Jun 11, 2024
9116fc0
feat: Fetching function templates from API
byawitz Jun 11, 2024
7ed643e
Update templates/cli/lib/commands/init.js.twig
christyjacob4 Jun 11, 2024
57984ba
Merge pull request #877 from appwrite/feat-function-init-improvments
christyjacob4 Jun 11, 2024
0cf2882
refactor: Removing `--open` option
byawitz Jun 11, 2024
fcb2ddc
Merge pull request #868 from appwrite/feat-arg-project-init
christyjacob4 Jun 11, 2024
3b04fe8
Merge branch 'refs/heads/feat-console-flow' into feat-multiple-accoun…
byawitz Jun 11, 2024
3e054b0
Merge remote-tracking branch 'origin/feat-multiple-accounts-and-insta…
byawitz Jun 11, 2024
309d764
refactor: reviews
byawitz Jun 11, 2024
4641f61
refactor: removing redundant exports and questions.
byawitz Jun 11, 2024
0860867
refactor: renaming login to sessions
byawitz Jun 11, 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
169 changes: 126 additions & 43 deletions templates/cli/lib/commands/generic.js.twig
Original file line number Diff line number Diff line change
@@ -1,12 +1,82 @@
const inquirer = require("inquirer");
const { Command } = require("commander");
const Client = require("../client");
const { sdkForConsole } = require("../sdks");
const { sdkForConsole, questionGetEndpoint } = require("../sdks");
const { globalConfig, localConfig } = require("../config");
const { actionRunner, success, parseBool, commandDescriptions, error, parse, drawTable } = require("../parser");
{% if sdk.test != "true" %}
const { questionsLogin, questionsListFactors, questionsMfaChallenge } = require("../questions");
const { accountUpdateMfaChallenge, accountCreateMfaChallenge, accountGet, accountCreateEmailPasswordSession, accountDeleteSession } = require("./account");
const ID = require("../id");

const DEFAULT_ENDPOINT = 'https://cloud.appwrite.io/v1';

const loginCommand = async ({ selfHosted }) => {
const answers = await inquirer.prompt(questionsLogin);
const oldCurrent = globalConfig.getCurrentLogin();
const id = ID.unique();

globalConfig.setCurrentLogin(id);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
globalConfig.setCurrentLogin(id);
globalConfig.setCurrentSession(id);

globalConfig.addLogin(id, {});
globalConfig.setEmail(answers.email);
globalConfig.setEndpoint(DEFAULT_ENDPOINT);

if (selfHosted) {
const selfHostedAnswers = await inquirer.prompt(questionGetEndpoint);

globalConfig.setEndpoint(selfHostedAnswers.endpoint);
}

let client = await sdkForConsole(false);

let account;

try {
await accountCreateEmailPasswordSession({
email: answers.email,
password: answers.password,
parseOutput: false,
sdk: client
})

client.setCookie(globalConfig.getCookie());

account = await accountGet({
sdk: client,
parseOutput: false
});
} catch (error) {
if (error.response === 'user_more_factors_required') {
const { factor } = await inquirer.prompt(questionsListFactors);

const challenge = await accountCreateMfaChallenge({
factor,
parseOutput: false,
sdk: client
});

const { otp } = await inquirer.prompt(questionsMfaChallenge);

await accountUpdateMfaChallenge({
challengeId: challenge.$id,
otp,
parseOutput: false,
sdk: client
});

account = await accountGet({
sdk: client,
parseOutput: false
});
} else {
globalConfig.removeLogin(id);
globalConfig.setCurrentLogin(oldCurrent);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
globalConfig.setCurrentLogin(oldCurrent);
globalConfig.setCurrentSession(oldCurrent);

throw error;
}
}

success("Signed in as user with ID: " + account.$id);
};

const whoami = new Command("whoami")
.description(commandDescriptions['whoami'])
Expand Down Expand Up @@ -49,61 +119,74 @@ const whoami = new Command("whoami")

const login = new Command("login")
.description(commandDescriptions['login'])
.option(`-sa, --self-hosted`, `Flag for enabling custom endpoint for self hosted instances`)
byawitz marked this conversation as resolved.
Show resolved Hide resolved
.configureHelp({
helpWidth: process.stdout.columns || 80
})
.action(actionRunner(loginCommand));

login
.command('list')
.description("List available logged accounts.")
.action(actionRunner(async () => {
const answers = await inquirer.prompt(questionsLogin)
const logins = globalConfig.getLogins();
const current = globalConfig.getCurrentLogin();

let client = await sdkForConsole(false);
const data = [...logins.map((login => {
return {
'Current': login.id === current ? '*' : '',
'ID': login.id,
'Endpoint': login.endpoint,
'Email': login.email
};
}))];

await accountCreateEmailPasswordSession({
email: answers.email,
password: answers.password,
parseOutput: false,
sdk: client
})
drawTable(data);

client.setCookie(globalConfig.getCookie());
}));

let account;
login
.command('change')
.description("Change the current account")
.option(`-a, --accountId <accountId>`, `Login ID`)
.action(actionRunner(async ({ accountId }) => {
const loginIds = globalConfig.getLoginIds();

try {
account = await accountGet({
sdk: client,
parseOutput: false
});
} catch (error) {
if (error.response === 'user_more_factors_required') {
const { factor } = await inquirer.prompt(questionsListFactors);

const challenge = await accountCreateMfaChallenge({
factor,
parseOutput: false,
sdk: client
});

const { otp } = await inquirer.prompt(questionsMfaChallenge);

await accountUpdateMfaChallenge({
challengeId: challenge.$id,
otp,
parseOutput: false,
sdk: client
});

account = await accountGet({
sdk: client,
parseOutput: false
});
} else {
throw error;
}
if (!loginIds.includes(accountId)) {
throw Error('Login ID not found');
}

success("Signed in as user with ID: " + account.$id);
globalConfig.setCurrentLogin(accountId);
success(`Current account is ${accountId}`);
}));

login
.command('migrate')
byawitz marked this conversation as resolved.
Show resolved Hide resolved
.description("Migrate existing login to new scheme")
.action(actionRunner(async ({ accountId }) => {
const endpoint = globalConfig.getEndpoint();
const cookie = globalConfig.getCookie();

if (endpoint === '' || cookie === '') {
throw Error(`Couldn't find any existing account credentials`)
}

const id = ID.unique();
const data = {
endpoint,
cookie,
email: 'legacy'
};

globalConfig.addLogin(id, data);
globalConfig.setCurrentLogin(id);
globalConfig.delete('endpoint');
globalConfig.delete('cookie');

success(`Account was migrated and it's the current account`);
}));


const logout = new Command("logout")
.description(commandDescriptions['logout'])
.configureHelp({
Expand Down
124 changes: 110 additions & 14 deletions templates/cli/lib/config.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -341,14 +341,18 @@ class Local extends Config {
class Global extends Config {
static CONFIG_FILE_PATH = ".{{ spec.title|caseLower }}/prefs.json";

static PREFERENCE_CURRENT = "current";
static PREFERENCE_ENDPOINT = "endpoint";
static PREFERENCE_EMAIL = "email";
static PREFERENCE_SELF_SIGNED = "selfSigned";
static PREFERENCE_COOKIE = "cookie";
static PREFERENCE_PROJECT = "project";
static PREFERENCE_KEY = "key";
static PREFERENCE_LOCALE = "locale";
static PREFERENCE_MODE = "mode";

static IGNORE_ATTRIBUTES = [Global.PREFERENCE_CURRENT, Global.PREFERENCE_SELF_SIGNED, Global.PREFERENCE_ENDPOINT, Global.PREFERENCE_COOKIE, Global.PREFERENCE_PROJECT, Global.PREFERENCE_KEY, Global.PREFERENCE_LOCALE, Global.PREFERENCE_MODE];

static MODE_ADMIN = "admin";
static MODE_DEFAULT = "default";

Expand All @@ -359,59 +363,151 @@ class Global extends Config {
super(`${homeDir}/${path}`);
}

getCurrentLogin() {
if (!this.has(Global.PREFERENCE_CURRENT)) {
return "";
}
return this.get(Global.PREFERENCE_CURRENT);
}

setCurrentLogin(endpoint) {
this.set(Global.PREFERENCE_CURRENT, endpoint);
}

getLoginIds() {
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't this be getSessionIds() ?

return Object.keys(this.data).filter((key) => !Global.IGNORE_ATTRIBUTES.includes(key));
}

getLogins() {
const logins = Object.keys(this.data).filter((key) => !Global.IGNORE_ATTRIBUTES.includes(key))

return logins.map((login) => {

return {
id: login,
endpoint: this.data[login][Global.PREFERENCE_ENDPOINT],
email: this.data[login][Global.PREFERENCE_EMAIL]
}
})
}

addLogin(login, data) {
this.set(login, data);
}

removeLogin(login, data) {
this.delete(login);
}
Copy link
Member

Choose a reason for hiding this comment

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

Lets refactor all these method names to use Session / Session

And deleteSession / logout


getEmail() {
if (!this.hasFrom(Global.PREFERENCE_EMAIL)) {
return "";
}

return this.getFrom(Global.PREFERENCE_EMAIL);
}

setEmail(email) {
this.setTo(Global.PREFERENCE_EMAIL, email);
}

getEndpoint() {
if (!this.has(Global.PREFERENCE_ENDPOINT)) {
if (!this.hasFrom(Global.PREFERENCE_ENDPOINT)) {
return "";
}
return this.get(Global.PREFERENCE_ENDPOINT);

return this.getFrom(Global.PREFERENCE_ENDPOINT);
}

setEndpoint(endpoint) {
this.set(Global.PREFERENCE_ENDPOINT, endpoint);
this.setTo(Global.PREFERENCE_ENDPOINT, endpoint);
}

getSelfSigned() {
if (!this.has(Global.PREFERENCE_SELF_SIGNED)) {
if (!this.hasFrom(Global.PREFERENCE_SELF_SIGNED)) {
return false;
}
return this.get(Global.PREFERENCE_SELF_SIGNED);
return this.getFrom(Global.PREFERENCE_SELF_SIGNED);
}

setSelfSigned(selfSigned) {
this.set(Global.PREFERENCE_SELF_SIGNED, selfSigned);
this.setTo(Global.PREFERENCE_SELF_SIGNED, selfSigned);
}

getCookie() {
if (!this.has(Global.PREFERENCE_COOKIE)) {
if (!this.hasFrom(Global.PREFERENCE_COOKIE)) {
return "";
}
return this.get(Global.PREFERENCE_COOKIE);
return this.getFrom(Global.PREFERENCE_COOKIE);
}

setCookie(cookie) {
this.set(Global.PREFERENCE_COOKIE, cookie);
this.setTo(Global.PREFERENCE_COOKIE, cookie);
}

getProject() {
if (!this.has(Global.PREFERENCE_PROJECT)) {
if (!this.hasFrom(Global.PREFERENCE_PROJECT)) {
return "";
}
return this.get(Global.PREFERENCE_PROJECT);
return this.getFrom(Global.PREFERENCE_PROJECT);
}

setProject(project) {
this.set(Global.PREFERENCE_PROJECT, project);
this.setTo(Global.PREFERENCE_PROJECT, project);
}

getKey() {
if (!this.has(Global.PREFERENCE_KEY)) {
if (!this.hasFrom(Global.PREFERENCE_KEY)) {
return "";
}
return this.get(Global.PREFERENCE_KEY);
}

setKey(key) {
this.set(Global.PREFERENCE_KEY, key);
this.setTo(Global.PREFERENCE_KEY, key);
}

hasFrom(key) {
try {
const current = this.getCurrentLogin();

if (current) {
const config = this.get(current);

return config[key] !== undefined;
}
} catch {
return this.has(key);
}
}

getFrom(key) {
try {
const current = this.getCurrentLogin();

if (current) {
const config = this.get(current);

return config[key];
}
} catch {
return this.get(key);
}
}

setTo(key, value) {
try {
const current = this.getCurrentLogin();

if (current) {
const config = this.get(current);

config[key] = value;
this.write();
}
} catch {
this.set(key, value);
}
}
}

Expand Down
Loading