Skip to content

Commit 52bc7d0

Browse files
Merge pull request #170 from appwrite/dev
feat: Add multi-region support to init command
2 parents 20cc906 + 0dd781c commit 52bc7d0

File tree

16 files changed

+172
-32
lines changed

16 files changed

+172
-32
lines changed

CHANGELOG.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Change Log
2+
3+
## 8.1.0
4+
5+
* Add multi-region support to `init` command
6+
* Update `init` command to clear previous configuration in `appwrite.json`
7+
* Update localConfig to store multi-region endpoint
8+
* Fix throw error when creating unknown attribute instead of timing out
9+
* Fix equal comparison of large numbers and BigNumber instances using proper equality checks
10+
* Fix duplication of reasons when comparing localConfig with remoteConfig
11+
* Fix `firstOrNull()` to `firstOrNull` in types generation for dart
12+
* Refactor to use `isCloud()` method consistently
13+
14+
## 8.0.2
15+
16+
* Add Type generation fixes:
17+
* Properly handle enum attributes in dart, java and kotlin
18+
* Fix initialisation of null attributes in dart's fromMap method
19+
* Fix relationships and enums in swift
20+
21+
## 8.0.1
22+
23+
* Add `resourceId` and `resourceType` attributes to `createRedirectRule`
24+
* Add `providerReference` to vcs command for getting repository contents
25+
* Add warning comment to `bulk updateDocuments` method
26+
* Fix type generation for enums in Typescript and PHP language
27+
28+
## 8.0.0
29+
30+
* Add `types` command to generate language specific typings for collections. Currently supports - `php`, `swift`, `dart`, `js`, `ts`, `kotlin` and `java`
31+
* Update bulk operation docs to include experiment feature warnings
32+
* Remove assistant service and commands
33+
34+
## 7.0.0
35+
36+
* Add `sites` command
37+
* Add `tokens` command
38+
* Add `devKeys` support to `projects` command
39+
* Add `init site`, `pull site` and `push site` commands
40+
* Add bulk operation methods like `createDocuments`, `deleteDocuments` etc.
41+
* Add new upsert methods: `upsertDocument` and `upsertDocuments`
42+
* Update GET requests to not include content-type header
43+
44+
## 6.2.3
45+
46+
* Fix hot swapping error in `python-ml` function
47+
48+
## 6.2.2
49+
50+
* Fix GitHub builds by adding `qemu-system` package
51+
* Fix attribute creation timed out
52+
53+
## 6.2.1
54+
55+
* Add `listOrganizations` method to `organizations` service and fix init project command
56+
57+
## 6.2.0
58+
59+
* Add specifications support to CLI
60+
* Update package version
61+
* Fix: Missed specifications param when updating a function

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Once the installation is complete, you can verify the install using
2929

3030
```sh
3131
$ appwrite -v
32-
8.0.2
32+
8.1.0
3333
```
3434

3535
### Install using prebuilt binaries
@@ -60,7 +60,7 @@ $ scoop install https://raw.githubusercontent.com/appwrite/sdk-for-cli/master/sc
6060
Once the installation completes, you can verify your install using
6161
```
6262
$ appwrite -v
63-
8.0.2
63+
8.1.0
6464
```
6565

6666
## Getting Started

install.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
# You can use "View source" of this page to see the full script.
1414

1515
# REPO
16-
$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/8.0.2/appwrite-cli-win-x64.exe"
17-
$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/8.0.2/appwrite-cli-win-arm64.exe"
16+
$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/8.1.0/appwrite-cli-win-x64.exe"
17+
$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/8.1.0/appwrite-cli-win-arm64.exe"
1818

1919
$APPWRITE_BINARY_NAME = "appwrite.exe"
2020

install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ printSuccess() {
9797
downloadBinary() {
9898
echo "[2/4] Downloading executable for $OS ($ARCH) ..."
9999

100-
GITHUB_LATEST_VERSION="8.0.2"
100+
GITHUB_LATEST_VERSION="8.1.0"
101101
GITHUB_FILE="appwrite-cli-${OS}-${ARCH}"
102102
GITHUB_URL="https://github.com/$GITHUB_REPOSITORY_NAME/releases/download/$GITHUB_LATEST_VERSION/$GITHUB_FILE"
103103

lib/client.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ class Client {
1616
'x-sdk-name': 'Command Line',
1717
'x-sdk-platform': 'console',
1818
'x-sdk-language': 'cli',
19-
'x-sdk-version': '8.0.2',
20-
'user-agent' : `AppwriteCLI/8.0.2 (${os.type()} ${os.version()}; ${os.arch()})`,
19+
'x-sdk-version': '8.1.0',
20+
'user-agent' : `AppwriteCLI/8.1.0 (${os.type()} ${os.version()}; ${os.arch()})`,
2121
'X-Appwrite-Response-Format' : '1.7.0',
2222
};
2323
}

lib/commands/init.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const { cliConfig, success, log, hint, error, actionRunner, commandDescriptions
2828
const { accountGet } = require("./account");
2929
const { sitesListTemplates } = require("./sites");
3030
const { sdkForConsole } = require("../sdks");
31+
const { isCloud } = require('../utils');
3132

3233
const initResources = async () => {
3334
const actions = {
@@ -94,6 +95,9 @@ const initProject = async ({ organizationId, projectId, projectName } = {}) => {
9495
}
9596
}
9697

98+
localConfig.clear(); // Clear the config to avoid any conflicts
99+
const url = new URL("https://cloud.appwrite.io/v1");
100+
97101
if (answers.start === 'new') {
98102
response = await projectsCreate({
99103
projectId: answers.id,
@@ -103,8 +107,14 @@ const initProject = async ({ organizationId, projectId, projectName } = {}) => {
103107
})
104108

105109
localConfig.setProject(response['$id']);
110+
if (answers.region) {
111+
localConfig.setEndpoint(`https://${answers.region}.${url.host}${url.pathname}`);
112+
}
106113
} else {
107-
localConfig.setProject(answers.project);
114+
localConfig.setProject(answers.project["$id"]);
115+
if(isCloud()) {
116+
localConfig.setEndpoint(`https://${answers.project["region"]}.${url.host}${url.pathname}`);
117+
}
108118
}
109119

110120
success(`Project successfully ${answers.start === 'existing' ? 'linked' : 'created'}. Details are now stored in appwrite.json file.`);

lib/commands/push.js

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,8 @@ const createAttribute = (databaseId, collectionId, attribute) => {
568568
onDelete: attribute.onDelete,
569569
parseOutput: false
570570
})
571+
default:
572+
throw new Error(`Unsupported attribute type: ${attribute.type}`);
571573
}
572574
}
573575

@@ -685,6 +687,8 @@ const updateAttribute = (databaseId, collectionId, attribute) => {
685687
onDelete: attribute.onDelete,
686688
parseOutput: false
687689
})
690+
default:
691+
throw new Error(`Unsupported attribute type: ${attribute.type}`);
688692
}
689693
}
690694
const deleteAttribute = async (collection, attribute, isIndex = false) => {
@@ -708,6 +712,33 @@ const deleteAttribute = async (collection, attribute, isIndex = false) => {
708712
});
709713
}
710714

715+
const isEqual = (a, b) => {
716+
if (a === b) return true;
717+
718+
if (a && b && typeof a === 'object' && typeof b === 'object') {
719+
if (a.constructor && a.constructor.name === 'BigNumber' &&
720+
b.constructor && b.constructor.name === 'BigNumber') {
721+
return a.eq(b);
722+
}
723+
724+
if (typeof a.equals === 'function') {
725+
return a.equals(b);
726+
}
727+
728+
if (typeof a.eq === 'function') {
729+
return a.eq(b);
730+
}
731+
}
732+
733+
if (typeof a === 'number' && typeof b === 'number') {
734+
if (isNaN(a) && isNaN(b)) return true;
735+
if (!isFinite(a) && !isFinite(b)) return a === b;
736+
return Math.abs(a - b) < Number.EPSILON;
737+
}
738+
739+
return false;
740+
};
741+
711742
const compareAttribute = (remote, local, reason, key) => {
712743
if (isEmpty(remote) && isEmpty(local)) {
713744
return reason;
@@ -718,7 +749,7 @@ const compareAttribute = (remote, local, reason, key) => {
718749
const bol = reason === '' ? '' : '\n';
719750
reason += `${bol}${key} changed from ${chalk.red(remote)} to ${chalk.green(local)}`;
720751
}
721-
} else if (remote !== local) {
752+
} else if (!isEqual(remote, local)) {
722753
const bol = reason === '' ? '' : '\n';
723754
reason += `${bol}${key} changed from ${chalk.red(remote)} to ${chalk.green(local)}`;
724755
}
@@ -733,16 +764,16 @@ const compareAttribute = (remote, local, reason, key) => {
733764
* @param remote
734765
* @param local
735766
* @param collection
736-
* @param recraeting when true will check only non-changeable keys
767+
* @param recreating when true will check only non-changeable keys
737768
* @returns {undefined|{reason: string, action: *, attribute, key: string}}
738769
*/
739-
const checkAttributeChanges = (remote, local, collection, recraeting = true) => {
770+
const checkAttributeChanges = (remote, local, collection, recreating = true) => {
740771
if (local === undefined) {
741772
return undefined;
742773
}
743774

744775
const keyName = `${chalk.yellow(local.key)} in ${collection.name} (${collection['$id']})`;
745-
const action = chalk.cyan(recraeting ? 'recreating' : 'changing');
776+
const action = chalk.cyan(recreating ? 'recreating' : 'changing');
746777
let reason = '';
747778
let attribute = remote;
748779

@@ -752,17 +783,17 @@ const checkAttributeChanges = (remote, local, collection, recraeting = true) =>
752783
}
753784

754785
if (changeableKeys.includes(key)) {
755-
if (!recraeting) {
756-
reason += compareAttribute(remote[key], local[key], reason, key)
786+
if (!recreating) {
787+
reason = compareAttribute(remote[key], local[key], reason, key)
757788
}
758789
continue;
759790
}
760791

761-
if (!recraeting) {
792+
if (!recreating) {
762793
continue;
763794
}
764795

765-
reason += compareAttribute(remote[key], local[key], reason, key)
796+
reason = compareAttribute(remote[key], local[key], reason, key)
766797
}
767798

768799
return reason === '' ? undefined : { key: keyName, attribute, reason, action };

lib/config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ class Local extends Config {
150150
return _path.dirname(this.path)
151151
}
152152

153+
getEndpoint() {
154+
return this.get('endpoint') || '';
155+
}
156+
157+
setEndpoint(endpoint) {
158+
this.set('endpoint', endpoint);
159+
}
160+
153161
getSites() {
154162
if (!this.has("sites")) {
155163
return [];

lib/parser.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { description } = require('../package.json');
55
const { globalConfig } = require("./config.js");
66
const os = require('os');
77
const Client = require("./client");
8+
const { isCloud } = require("./utils");
89

910
const cliConfig = {
1011
verbose: false,
@@ -111,7 +112,6 @@ const parseError = (err) => {
111112
(async () => {
112113
let appwriteVersion = 'unknown';
113114
const endpoint = globalConfig.getEndpoint();
114-
const isCloud = endpoint.includes('cloud.appwrite.io') ? 'Yes' : 'No';
115115

116116
try {
117117
const client = new Client().setEndpoint(endpoint);
@@ -120,9 +120,9 @@ const parseError = (err) => {
120120
} catch {
121121
}
122122

123-
const version = '8.0.2';
123+
const version = '8.1.0';
124124
const stepsToReproduce = `Running \`appwrite ${cliConfig.reportData.data.args.join(' ')}\``;
125-
const yourEnvironment = `CLI version: ${version}\nOperation System: ${os.type()}\nAppwrite version: ${appwriteVersion}\nIs Cloud: ${isCloud}`;
125+
const yourEnvironment = `CLI version: ${version}\nOperation System: ${os.type()}\nAppwrite version: ${appwriteVersion}\nIs Cloud: ${isCloud()}`;
126126

127127
const stack = '```\n' + err.stack + '\n```';
128128

lib/questions.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const { validateRequired } = require("./validations");
1111
const { paginate } = require('./paginate');
1212
const { isPortTaken } = require('./utils');
1313
const { databasesList } = require('./commands/databases');
14-
const { checkDeployConditions } = require('./utils');
14+
const { checkDeployConditions, isCloud } = require('./utils');
1515
const JSONbig = require("json-bigint")({ storeAsString: false });
1616
const { sitesListFrameworks, sitesListSpecifications, sitesList } = require('./commands/sites');
1717

@@ -154,8 +154,7 @@ const questionsInitProject = [
154154
message: "Choose your organization",
155155
choices: async () => {
156156
let client = await sdkForConsole(true);
157-
const hostname = new URL(client.endpoint).hostname;
158-
const { teams } = hostname.endsWith('appwrite.io')
157+
const { teams } = isCloud()
159158
? await paginate(organizationsList, { parseOutput: false, sdk: client }, 100, 'teams')
160159
: await paginate(teamsList, { parseOutput: false, sdk: client }, 100, 'teams');
161160

@@ -203,17 +202,41 @@ const questionsInitProject = [
203202
let choices = projects.map((project) => {
204203
return {
205204
name: `${project.name} (${project['$id']})`,
206-
value: project['$id']
205+
value: {
206+
"$id": project['$id'],
207+
"region": project.region || ''
208+
}
207209
}
208210
})
209211

210-
if (choices.length == 0) {
212+
if (choices.length === 0) {
211213
throw new Error("No projects found. Please create a new project.")
212214
}
213215

214216
return choices;
215217
},
216218
when: (answer) => answer.start === 'existing'
219+
},
220+
{
221+
type: "list",
222+
name: "region",
223+
message: "Select your Appwrite Cloud region",
224+
choices: async () => {
225+
let client = await sdkForConsole(true);
226+
let response = await client.call("GET", "/console/regions");
227+
let regions = response.regions || [];
228+
if (!regions.length) {
229+
throw new Error("No regions found. Please check your network or Appwrite Cloud availability.");
230+
}
231+
return regions.filter(region => !region.disabled).map(region => ({
232+
name: `${region.name} (${region.$id})`,
233+
value: region.$id
234+
}));
235+
},
236+
when: (answer) => {
237+
if (answer.start === 'existing') return false;
238+
return isCloud();
239+
}
217240
}
218241
];
219242
const questionsInitProjectAutopull = [

0 commit comments

Comments
 (0)