appwrite-cli 0.18.2 → 0.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Appwrite Command Line SDK
2
2
 
3
3
  ![License](https://img.shields.io/github/license/appwrite/sdk-for-cli.svg?style=flat-square)
4
- ![Version](https://img.shields.io/badge/api%20version-0.15.0-blue.svg?style=flat-square)
4
+ ![Version](https://img.shields.io/badge/api%20version-0.15.1-blue.svg?style=flat-square)
5
5
  [![Build Status](https://img.shields.io/travis/com/appwrite/sdk-generator?style=flat-square)](https://travis-ci.com/appwrite/sdk-generator)
6
6
  [![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
7
7
  [![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord)
@@ -29,7 +29,7 @@ Once the installation is complete, you can verify the install using
29
29
 
30
30
  ```sh
31
31
  $ appwrite -v
32
- 0.18.2
32
+ 0.18.3
33
33
  ```
34
34
 
35
35
  ### Install using prebuilt binaries
@@ -58,7 +58,7 @@ $ iwr -useb https://appwrite.io/cli/install.ps1 | iex
58
58
  Once the installation completes, you can verify your install using
59
59
  ```
60
60
  $ appwrite -v
61
- 0.18.2
61
+ 0.18.3
62
62
  ```
63
63
 
64
64
  ## Getting Started
@@ -94,12 +94,6 @@ You can also fetch all the collections in your current project using
94
94
  appwrite init collection
95
95
  ```
96
96
 
97
- The CLI also comes with a convenient `--all` flag to perform both these steps at once.
98
-
99
- ```sh
100
- appwrite init --all
101
- ```
102
-
103
97
  * ### Creating and deploying cloud functions
104
98
 
105
99
  The CLI makes it extremely easy to create and deploy Appwrite's cloud functions. Initialise your new function using
@@ -145,12 +139,6 @@ Similarly, you can deploy all your collections to your Appwrite server using
145
139
  appwrite deploy collections
146
140
  ```
147
141
 
148
- The `deploy` command also comes with a convenient `--all` flag to deploy all your functions and collections at once.
149
-
150
- ```sh
151
- appwrite deploy --all
152
- ```
153
-
154
142
  > ### Note
155
143
  > By default, requests to domains with self signed SSL certificates (or no certificates) are disabled. If you trust the domain, you can bypass the certificate validation using
156
144
  ```sh
package/install.ps1 CHANGED
@@ -13,8 +13,8 @@
13
13
  # You can use "View source" of this page to see the full script.
14
14
 
15
15
  # REPO
16
- $GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/0.18.2/appwrite-cli-win-x64.exe"
17
- $GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/0.18.2/appwrite-cli-win-arm64.exe"
16
+ $GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/0.18.3/appwrite-cli-win-x64.exe"
17
+ $GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/0.18.3/appwrite-cli-win-arm64.exe"
18
18
 
19
19
  $APPWRITE_BINARY_NAME = "appwrite.exe"
20
20
 
package/install.sh CHANGED
@@ -97,7 +97,7 @@ printSuccess() {
97
97
  downloadBinary() {
98
98
  echo "[2/4] Downloading executable for $OS ($ARCH) ..."
99
99
 
100
- GITHUB_LATEST_VERSION="0.18.2"
100
+ GITHUB_LATEST_VERSION="0.18.3"
101
101
  GITHUB_FILE="appwrite-cli-${OS}-${ARCH}"
102
102
  GITHUB_URL="https://github.com/$GITHUB_REPOSITORY_NAME/releases/download/$GITHUB_LATEST_VERSION/$GITHUB_FILE"
103
103
 
package/lib/client.js CHANGED
@@ -12,8 +12,8 @@ class Client {
12
12
  this.endpoint = 'https://HOSTNAME/v1';
13
13
  this.headers = {
14
14
  'content-type': '',
15
- 'x-sdk-version': 'appwrite:cli:0.18.2',
16
- 'User-Agent' : `AppwriteCLI/0.18.2 (${os.type()} ${os.version()}; ${os.arch()})`,
15
+ 'x-sdk-version': 'appwrite:cli:0.18.3',
16
+ 'User-Agent' : `AppwriteCLI/0.18.3 (${os.type()} ${os.version()}; ${os.arch()})`,
17
17
  'X-Appwrite-Response-Format' : '0.15.0',
18
18
  };
19
19
  }
@@ -784,7 +784,7 @@ account
784
784
 
785
785
  account
786
786
  .command(`updatePhone`)
787
- .description(`Update currently logged in user account phone number. After changing phone number, the user confirmation status will get reset. A new confirmation SMS is not sent automatically however you can use the phone confirmation endpoint again to send the confirmation SMS.`)
787
+ .description(`Update the currently logged in user's phone number. After updating the phone number, the phone verification status will be reset. A confirmation SMS is not sent automatically, however you can use the [POST /account/verification/phone](/docs/client/account#accountCreatePhoneVerification) endpoint to send a confirmation SMS.`)
788
788
  .requiredOption(`--number <number>`, `Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.`)
789
789
  .requiredOption(`--password <password>`, `User password. Must be at least 8 chars.`)
790
790
  .action(actionRunner(accountUpdatePhone))
@@ -856,7 +856,7 @@ account
856
856
  account
857
857
  .command(`createOAuth2Session`)
858
858
  .description(`Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. If there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.. `)
859
- .requiredOption(`--provider <provider>`, `OAuth2 Provider. Currently, supported providers are: amazon, apple, auth0, bitbucket, bitly, box, dailymotion, discord, dropbox, facebook, github, gitlab, google, linkedin, microsoft, notion, okta, paypal, paypalSandbox, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, yahoo, yammer, yandex, zoom.`)
859
+ .requiredOption(`--provider <provider>`, `OAuth2 Provider. Currently, supported providers are: amazon, apple, auth0, autodesk, bitbucket, bitly, box, dailymotion, discord, dropbox, facebook, github, gitlab, google, linkedin, microsoft, notion, okta, paypal, paypalSandbox, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, yahoo, yammer, yandex, zoom.`)
860
860
  .option(`--success <success>`, `URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.`)
861
861
  .option(`--failure <failure>`, `URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.`)
862
862
  .option(`--scopes <scopes...>`, `A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long.`)
@@ -864,14 +864,14 @@ account
864
864
 
865
865
  account
866
866
  .command(`createPhoneSession`)
867
- .description(`Sends the user a SMS with a secret key for creating a session. Use the returned user ID and the secret to submit a request to the [PUT /account/sessions/phone](/docs/client/account#accountUpdatePhoneSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.`)
867
+ .description(`Sends the user an SMS with a secret key for creating a session. Use the returned user ID and secret and submit a request to the [PUT /account/sessions/phone](/docs/client/account#accountUpdatePhoneSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.`)
868
868
  .requiredOption(`--userId <userId>`, `Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`)
869
869
  .requiredOption(`--number <number>`, `Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.`)
870
870
  .action(actionRunner(accountCreatePhoneSession))
871
871
 
872
872
  account
873
873
  .command(`updatePhoneSession`)
874
- .description(`Use this endpoint to complete creating the session with the Magic URL. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST /account/sessions/magic-url](/docs/client/account#accountCreateMagicURLSession) endpoint. Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.`)
874
+ .description(`Use this endpoint to complete creating a session with SMS. Use the **userId** from the [createPhoneSession](/docs/client/account#accountCreatePhoneSession) endpoint and the **secret** received via SMS to successfully update and confirm the phone session.`)
875
875
  .requiredOption(`--userId <userId>`, `User ID.`)
876
876
  .requiredOption(`--secret <secret>`, `Valid verification token.`)
877
877
  .action(actionRunner(accountUpdatePhoneSession))
@@ -914,7 +914,7 @@ account
914
914
 
915
915
  account
916
916
  .command(`createPhoneVerification`)
917
- .description(`Use this endpoint to send a verification message to your user's phone number to confirm they are the valid owners of that address. The provided secret should allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](/docs/client/account#accountUpdatePhoneVerification). The verification link sent to the user's phone number is valid for 15 minutes.`)
917
+ .description(`Use this endpoint to send a verification SMS to the currently logged in user. This endpoint is meant for use after updating a user's phone number using the [accountUpdatePhone](/docs/client/account#accountUpdatePhone) endpoint. Learn more about how to [complete the verification process](/docs/client/account#accountUpdatePhoneVerification). The verification code sent to the user's phone number is valid for 15 minutes.`)
918
918
  .action(actionRunner(accountCreatePhoneVerification))
919
919
 
920
920
  account
@@ -6,34 +6,37 @@ const { questionsDeployFunctions, questionsGetEntrypoint, questionsDeployCollect
6
6
  const { actionRunner, success, log, error, commandDescriptions } = require("../parser");
7
7
  const { functionsGet, functionsCreate, functionsUpdate, functionsCreateDeployment, functionsUpdateDeployment } = require('./functions');
8
8
  const {
9
- databaseCreateBooleanAttribute,
10
- databaseGetCollection,
11
- databaseCreateCollection,
12
- databaseCreateStringAttribute,
13
- databaseCreateIntegerAttribute,
14
- databaseCreateFloatAttribute,
15
- databaseCreateEmailAttribute,
16
- databaseCreateIndex,
17
- databaseCreateUrlAttribute,
18
- databaseCreateIpAttribute,
19
- databaseCreateEnumAttribute,
20
- databaseDeleteAttribute,
21
- databaseListAttributes,
22
- databaseListIndexes,
23
- databaseDeleteIndex
24
- } = require("./database");
9
+ databasesGet,
10
+ databasesCreate,
11
+ databasesCreateBooleanAttribute,
12
+ databasesGetCollection,
13
+ databasesCreateCollection,
14
+ databasesCreateStringAttribute,
15
+ databasesCreateIntegerAttribute,
16
+ databasesCreateFloatAttribute,
17
+ databasesCreateEmailAttribute,
18
+ databasesCreateIndex,
19
+ databasesCreateUrlAttribute,
20
+ databasesCreateIpAttribute,
21
+ databasesCreateEnumAttribute,
22
+ databasesDeleteAttribute,
23
+ databasesListAttributes,
24
+ databasesListIndexes,
25
+ databasesDeleteIndex
26
+ } = require("./databases");
25
27
 
26
28
  const POOL_DEBOUNCE = 2000; // in milliseconds
27
29
  const POOL_MAX_DEBOUNCES = 30;
28
30
 
29
31
  const awaitPools = {
30
- wipeAttributes: async (collectionId, iteration = 1) => {
32
+ wipeAttributes: async (databaseId, collectionId, iteration = 1) => {
31
33
  if (iteration > POOL_MAX_DEBOUNCES) {
32
34
  return false;
33
35
  }
34
36
 
35
37
  // TODO: Pagination?
36
- const { attributes: remoteAttributes } = await databaseListAttributes({
38
+ const { attributes: remoteAttributes } = await databasesListAttributes({
39
+ databaseId,
37
40
  collectionId,
38
41
  limit: 100,
39
42
  parseOutput: false
@@ -44,15 +47,16 @@ const awaitPools = {
44
47
  }
45
48
 
46
49
  await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
47
- return await awaitPools.wipeAttributes(collectionId, iteration + 1);
50
+ return await awaitPools.wipeAttributes(databaseId, collectionId, iteration + 1);
48
51
  },
49
- wipeIndexes: async (collectionId, iteration = 1) => {
52
+ wipeIndexes: async (databaseId, collectionId, iteration = 1) => {
50
53
  if (iteration > POOL_MAX_DEBOUNCES) {
51
54
  return false;
52
55
  }
53
56
 
54
57
  // TODO: Pagination?
55
- const { indexes: remoteIndexes } = await databaseListIndexes({
58
+ const { indexes: remoteIndexes } = await databasesListIndexes({
59
+ databaseId,
56
60
  collectionId,
57
61
  limit: 100,
58
62
  parseOutput: false
@@ -63,15 +67,16 @@ const awaitPools = {
63
67
  }
64
68
 
65
69
  await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
66
- return await awaitPools.wipeIndexes(collectionId, iteration + 1);
70
+ return await awaitPools.wipeIndexes(databaseId, collectionId, iteration + 1);
67
71
  },
68
- expectAttributes: async (collectionId, attributeKeys, iteration = 1) => {
72
+ expectAttributes: async (databaseId, collectionId, attributeKeys, iteration = 1) => {
69
73
  if (iteration > POOL_MAX_DEBOUNCES) {
70
74
  return false;
71
75
  }
72
76
 
73
77
  // TODO: Pagination?
74
- const { attributes: remoteAttributes } = await databaseListAttributes({
78
+ const { attributes: remoteAttributes } = await databasesListAttributes({
79
+ databaseId,
75
80
  collectionId,
76
81
  limit: 100,
77
82
  parseOutput: false
@@ -94,15 +99,16 @@ const awaitPools = {
94
99
  }
95
100
 
96
101
  await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
97
- return await awaitPools.expectAttributes(collectionId, attributeKeys, iteration + 1);
102
+ return await awaitPools.expectAttributes(databaseId, collectionId, attributeKeys, iteration + 1);
98
103
  },
99
- expectIndexes: async (collectionId, indexKeys, iteration = 1) => {
104
+ expectIndexes: async (databaseId, collectionId, indexKeys, iteration = 1) => {
100
105
  if (iteration > POOL_MAX_DEBOUNCES) {
101
106
  return false;
102
107
  }
103
108
 
104
109
  // TODO: Pagination?
105
- const { indexes: remoteIndexes } = await databaseListIndexes({
110
+ const { indexes: remoteIndexes } = await databasesListIndexes({
111
+ databaseId,
106
112
  collectionId,
107
113
  limit: 100,
108
114
  parseOutput: false
@@ -125,32 +131,49 @@ const awaitPools = {
125
131
  }
126
132
 
127
133
  await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
128
- return await awaitPools.expectIndexes(collectionId, indexKeys, iteration + 1);
134
+ return await awaitPools.expectIndexes(databaseId, collectionId, indexKeys, iteration + 1);
129
135
  },
130
136
  }
131
137
 
132
138
  const deploy = new Command("deploy")
133
139
  .description(commandDescriptions['deploy'])
134
- .option("--all", "Flag to deploy collections and functions")
135
- .action(actionRunner(async ({ all }, command) => {
136
- if (all == undefined) {
137
- command.help()
138
- }
139
-
140
- try {
141
- await deployFunction();
142
- } catch (e) {
143
- error(e.message);
144
- }
145
- await deployCollection()
140
+ .action(actionRunner(async (_options, command) => {
141
+ command.help()
146
142
  }));
147
143
 
148
- const deployFunction = async () => {
144
+ const deployFunction = async ({ functionId, all } = {}) => {
149
145
  let response = {};
150
146
 
151
- let answers = await inquirer.prompt(questionsDeployFunctions)
152
- let functions = answers.functions.map((func) => JSONbig.parse(func))
147
+ const functionIds = [];
148
+
149
+ if(functionId) {
150
+ functionIds.push(functionId);
151
+ } else if(all) {
152
+ const functions = localConfig.getFunctions();
153
+ if (functions.length === 0) {
154
+ throw new Error("No functions found in the current directory.");
155
+ }
156
+ functionIds.push(...functions.map((func, idx) => {
157
+ return func.$id;
158
+ }));
159
+ }
153
160
 
161
+ if(functionIds.length <= 0) {
162
+ const answers = await inquirer.prompt(questionsDeployFunctions);
163
+ functionIds.push(...answers.functions);
164
+ }
165
+
166
+ let functions = functionIds.map((id) => {
167
+ const functions = localConfig.getFunctions();
168
+ const func = functions.find((f) => f.$id === id);
169
+
170
+ if(!func) {
171
+ throw new Error("Function '" + id + "' not found.")
172
+ }
173
+
174
+ return func;
175
+ });
176
+
154
177
  for (let func of functions) {
155
178
  log(`Deploying function ${func.name} ( ${func['$id']} )`)
156
179
 
@@ -230,12 +253,13 @@ const deployFunction = async () => {
230
253
  }
231
254
  }
232
255
 
233
- const createAttribute = async (collectionId, attribute) => {
256
+ const createAttribute = async (databaseId, collectionId, attribute) => {
234
257
  switch (attribute.type) {
235
258
  case 'string':
236
259
  switch (attribute.format) {
237
260
  case 'email':
238
- return await databaseCreateEmailAttribute({
261
+ return await databasesCreateEmailAttribute({
262
+ databaseId,
239
263
  collectionId,
240
264
  key: attribute.key,
241
265
  required: attribute.required,
@@ -244,7 +268,8 @@ const createAttribute = async (collectionId, attribute) => {
244
268
  parseOutput: false
245
269
  })
246
270
  case 'url':
247
- return await databaseCreateUrlAttribute({
271
+ return await databasesCreateUrlAttribute({
272
+ databaseId,
248
273
  collectionId,
249
274
  key: attribute.key,
250
275
  required: attribute.required,
@@ -253,7 +278,8 @@ const createAttribute = async (collectionId, attribute) => {
253
278
  parseOutput: false
254
279
  })
255
280
  case 'ip':
256
- return await databaseCreateIpAttribute({
281
+ return await databasesCreateIpAttribute({
282
+ databaseId,
257
283
  collectionId,
258
284
  key: attribute.key,
259
285
  required: attribute.required,
@@ -262,7 +288,8 @@ const createAttribute = async (collectionId, attribute) => {
262
288
  parseOutput: false
263
289
  })
264
290
  case 'enum':
265
- return await databaseCreateEnumAttribute({
291
+ return await databasesCreateEnumAttribute({
292
+ databaseId,
266
293
  collectionId,
267
294
  key: attribute.key,
268
295
  elements: attribute.elements,
@@ -272,7 +299,8 @@ const createAttribute = async (collectionId, attribute) => {
272
299
  parseOutput: false
273
300
  })
274
301
  default:
275
- return await databaseCreateStringAttribute({
302
+ return await databasesCreateStringAttribute({
303
+ databaseId,
276
304
  collectionId,
277
305
  key: attribute.key,
278
306
  size: attribute.size,
@@ -284,7 +312,8 @@ const createAttribute = async (collectionId, attribute) => {
284
312
 
285
313
  }
286
314
  case 'integer':
287
- return await databaseCreateIntegerAttribute({
315
+ return await databasesCreateIntegerAttribute({
316
+ databaseId,
288
317
  collectionId,
289
318
  key: attribute.key,
290
319
  required: attribute.required,
@@ -295,7 +324,8 @@ const createAttribute = async (collectionId, attribute) => {
295
324
  parseOutput: false
296
325
  })
297
326
  case 'double':
298
- return databaseCreateFloatAttribute({
327
+ return databasesCreateFloatAttribute({
328
+ databaseId,
299
329
  collectionId,
300
330
  key: attribute.key,
301
331
  required: attribute.required,
@@ -306,7 +336,9 @@ const createAttribute = async (collectionId, attribute) => {
306
336
  parseOutput: false
307
337
  })
308
338
  case 'boolean':
309
- return databaseCreateBooleanAttribute({
339
+ return databasesCreateBooleanAttribute({
340
+ databaseId,
341
+ databaseId,
310
342
  collectionId,
311
343
  key: attribute.key,
312
344
  required: attribute.required,
@@ -317,15 +349,55 @@ const createAttribute = async (collectionId, attribute) => {
317
349
  }
318
350
  }
319
351
 
320
- const deployCollection = async () => {
352
+ const deployCollection = async ({ all } = {}) => {
321
353
  let response = {};
322
- let answers = await inquirer.prompt(questionsDeployCollections[0])
323
- let collections = answers.collections.map((collection) => JSONbig.parse(collection));
354
+
355
+ let collectionIds = [];
356
+ const configCollections = localConfig.getCollections();
357
+
358
+ if(all) {
359
+ if (configCollections.length === 0) {
360
+ throw new Error("No collections found in the current directory. Run `appwrite init collection` to fetch all your collections.");
361
+ }
362
+ collectionIds.push(...configCollections.map((c) => c.$id));
363
+ }
364
+
365
+ if(collectionIds.length <= 0) {
366
+ let answers = await inquirer.prompt(questionsDeployCollections[0])
367
+ collectionIds.push(...answers.collections);
368
+ }
369
+
370
+ let collections = [];
371
+
372
+ for(const collectionId of collectionIds) {
373
+ const idCollections = configCollections.filter((c) => c.$id === collectionId);
374
+ collections.push(...idCollections);
375
+ }
324
376
 
325
377
  for (let collection of collections) {
326
378
  log(`Deploying collection ${collection.name} ( ${collection['$id']} )`)
379
+
380
+ let databaseId;
381
+
382
+ try {
383
+ const database = await databasesGet({
384
+ databaseId: collection.databaseId,
385
+ parseOutput: false,
386
+ });
387
+ databaseId = database.$id;
388
+ } catch(err) {
389
+ log(`Database ${collection.databaseId} not found. Creating it now...`);
390
+ const database = await databasesCreate({
391
+ databaseId: collection.databaseId,
392
+ name: collection.databaseId,
393
+ parseOutput: false,
394
+ });
395
+ databaseId = database.$id;
396
+ }
397
+
327
398
  try {
328
- response = await databaseGetCollection({
399
+ response = await databasesGetCollection({
400
+ databaseId,
329
401
  collectionId: collection['$id'],
330
402
  parseOutput: false,
331
403
  })
@@ -340,51 +412,55 @@ const deployCollection = async () => {
340
412
  log(`Updating attributes ... `);
341
413
 
342
414
  // TODO: Pagination?
343
- const { indexes: remoteIndexes } = await databaseListIndexes({
415
+ const { indexes: remoteIndexes } = await databasesListIndexes({
416
+ databaseId,
344
417
  collectionId: collection['$id'],
345
418
  limit: 100,
346
419
  parseOutput: false
347
420
  });
348
421
 
349
422
  await Promise.all(remoteIndexes.map(async index => {
350
- await databaseDeleteIndex({
423
+ await databasesDeleteIndex({
424
+ databaseId,
351
425
  collectionId: collection['$id'],
352
426
  key: index.key,
353
427
  parseOutput: false
354
428
  });
355
429
  }));
356
430
 
357
- const deleteIndexesPoolStatus = await awaitPools.wipeIndexes(collection['$id']);
431
+ const deleteIndexesPoolStatus = await awaitPools.wipeIndexes(databaseId, collection['$id']);
358
432
  if (!deleteIndexesPoolStatus) {
359
433
  throw new Error("Index deletion did not finish for too long.");
360
434
  }
361
435
 
362
436
  // TODO: Pagination?
363
- const { attributes: remoteAttributes } = await databaseListAttributes({
437
+ const { attributes: remoteAttributes } = await databasesListAttributes({
438
+ databaseId,
364
439
  collectionId: collection['$id'],
365
440
  limit: 100,
366
441
  parseOutput: false
367
442
  });
368
443
 
369
444
  await Promise.all(remoteAttributes.map(async attribute => {
370
- await databaseDeleteAttribute({
445
+ await databasesDeleteAttribute({
446
+ databaseId,
371
447
  collectionId: collection['$id'],
372
448
  key: attribute.key,
373
449
  parseOutput: false
374
450
  });
375
451
  }));
376
452
 
377
- const deleteAttributesPoolStatus = await awaitPools.wipeAttributes(collection['$id']);
453
+ const deleteAttributesPoolStatus = await awaitPools.wipeAttributes(databaseId, collection['$id']);
378
454
  if (!deleteAttributesPoolStatus) {
379
455
  throw new Error("Attribute deletion did not finish for too long.");
380
456
  }
381
457
 
382
458
  await Promise.all(collection.attributes.map(async attribute => {
383
- await createAttribute(collection['$id'], attribute);
459
+ await createAttribute(databaseId, collection['$id'], attribute);
384
460
  }));
385
461
 
386
462
  const attributeKeys = collection.attributes.map(attribute => attribute.key);
387
- const createPoolStatus = await awaitPools.expectAttributes(collection['$id'], attributeKeys);
463
+ const createPoolStatus = await awaitPools.expectAttributes(databaseId, collection['$id'], attributeKeys);
388
464
  if (!createPoolStatus) {
389
465
  throw new Error("Attribute creation did not finish for too long.");
390
466
  }
@@ -393,7 +469,8 @@ const deployCollection = async () => {
393
469
 
394
470
  log(`Creating indexes ...`)
395
471
  await Promise.all(collection.indexes.map(async index => {
396
- await databaseCreateIndex({
472
+ await databasesCreateIndex({
473
+ databaseId,
397
474
  collectionId: collection['$id'],
398
475
  key: index.key,
399
476
  type: index.type,
@@ -404,7 +481,7 @@ const deployCollection = async () => {
404
481
  }));
405
482
 
406
483
  const indexKeys = collection.indexes.map(attribute => attribute.key);
407
- const indexPoolStatus = await awaitPools.expectIndexes(collection['$id'], indexKeys);
484
+ const indexPoolStatus = await awaitPools.expectIndexes(databaseId, collection['$id'], indexKeys);
408
485
  if (!indexPoolStatus) {
409
486
  throw new Error("Index creation did not finish for too long.");
410
487
  }
@@ -413,7 +490,8 @@ const deployCollection = async () => {
413
490
  } catch (e) {
414
491
  if (e.code == 404) {
415
492
  log(`Collection ${collection.name} does not exist in the project. Creating ... `);
416
- response = await databaseCreateCollection({
493
+ response = await databasesCreateCollection({
494
+ databaseId,
417
495
  collectionId: collection['$id'],
418
496
  name: collection.name,
419
497
  permission: collection.permission,
@@ -424,11 +502,11 @@ const deployCollection = async () => {
424
502
 
425
503
  log(`Creating attributes ... `);
426
504
  await Promise.all(collection.attributes.map(async attribute => {
427
- await createAttribute(collection['$id'], attribute);
505
+ await createAttribute(databaseId, collection['$id'], attribute);
428
506
  }));
429
507
 
430
508
  const attributeKeys = collection.attributes.map(attribute => attribute.key);
431
- const attributePoolStatus = await awaitPools.expectAttributes(collection['$id'], attributeKeys);
509
+ const attributePoolStatus = await awaitPools.expectAttributes(databaseId, collection['$id'], attributeKeys);
432
510
  if (!attributePoolStatus) {
433
511
  throw new Error("Attribute creation did not finish for too long.");
434
512
  }
@@ -437,7 +515,8 @@ const deployCollection = async () => {
437
515
 
438
516
  log(`Creating indexes ...`);
439
517
  await Promise.all(collection.indexes.map(async index => {
440
- await databaseCreateIndex({
518
+ await databasesCreateIndex({
519
+ databaseId,
441
520
  collectionId: collection['$id'],
442
521
  key: index.key,
443
522
  type: index.type,
@@ -448,7 +527,7 @@ const deployCollection = async () => {
448
527
  }));
449
528
 
450
529
  const indexKeys = collection.indexes.map(attribute => attribute.key);
451
- const indexPoolStatus = await awaitPools.expectIndexes(collection['$id'], indexKeys);
530
+ const indexPoolStatus = await awaitPools.expectIndexes(databaseId, collection['$id'], indexKeys);
452
531
  if (!indexPoolStatus) {
453
532
  throw new Error("Index creation did not finish for too long.");
454
533
  }
@@ -466,11 +545,14 @@ const deployCollection = async () => {
466
545
  deploy
467
546
  .command("function")
468
547
  .description("Deploy functions in the current directory.")
548
+ .option(`--functionId <functionId>`, `Function ID`)
549
+ .option(`--all`, `Flag to deploy all functions`)
469
550
  .action(actionRunner(deployFunction));
470
551
 
471
552
  deploy
472
553
  .command("collection")
473
554
  .description("Deploy collections in the current project.")
555
+ .option(`--all`, `Flag to deploy all functions`)
474
556
  .action(actionRunner(deployCollection));
475
557
 
476
558
  module.exports = {
@@ -5,7 +5,7 @@ const { sdkForConsole } = require("../sdks");
5
5
  const { globalConfig, localConfig } = require("../config");
6
6
  const { actionRunner, success, parseBool, commandDescriptions, log, parse } = require("../parser");
7
7
  const { questionsLogin } = require("../questions");
8
- const { accountCreateSession, accountDeleteSession } = require("./account");
8
+ const { accountCreateEmailSession, accountDeleteSession } = require("./account");
9
9
 
10
10
  const login = new Command("login")
11
11
  .description(commandDescriptions['login'])
@@ -14,7 +14,7 @@ const login = new Command("login")
14
14
 
15
15
  let client = await sdkForConsole(false);
16
16
 
17
- await accountCreateSession({
17
+ await accountCreateEmailSession({
18
18
  email: answers.email,
19
19
  password: answers.password,
20
20
  parseOutput: false,
@@ -6,22 +6,16 @@ const inquirer = require("inquirer");
6
6
  const { teamsCreate } = require("./teams");
7
7
  const { projectsCreate } = require("./projects");
8
8
  const { functionsCreate } = require("./functions");
9
- const { databaseListCollections } = require("./database");
9
+ const { databasesListCollections, databasesList } = require("./databases");
10
10
  const { sdkForConsole } = require("../sdks");
11
11
  const { localConfig } = require("../config");
12
- const { questionsInitProject, questionsInitFunction } = require("../questions");
12
+ const { questionsInitProject, questionsInitFunction, questionsInitCollection } = require("../questions");
13
13
  const { success, log, actionRunner, commandDescriptions } = require("../parser");
14
14
 
15
15
  const init = new Command("init")
16
16
  .description(commandDescriptions['init'])
17
- .option("--all", "Flag to initialize projects and collection")
18
- .action(actionRunner(async ({ all }, command) => {
19
- if (all == undefined) {
20
- command.help()
21
- }
22
-
23
- await initProject();
24
- await initCollection()
17
+ .action(actionRunner(async (_options, command) => {
18
+ command.help();
25
19
  }));
26
20
 
27
21
  const initProject = async () => {
@@ -40,7 +34,7 @@ const initProject = async () => {
40
34
 
41
35
  let teamId = response['$id'];
42
36
  response = await projectsCreate({
43
- projectId: 'unique()',
37
+ projectId: answers.id,
44
38
  name: answers.project,
45
39
  teamId,
46
40
  parseOutput: false
@@ -54,6 +48,7 @@ const initProject = async () => {
54
48
  }
55
49
 
56
50
  const initFunction = async () => {
51
+ // TODO: Add CI/CD support (ID, name, runtime)
57
52
  let answers = await inquirer.prompt(questionsInitFunction)
58
53
 
59
54
  if (fs.existsSync(path.join(process.cwd(), 'functions', answers.name))) {
@@ -65,7 +60,7 @@ const initFunction = async () => {
65
60
  }
66
61
 
67
62
  let response = await functionsCreate({
68
- functionId: 'unique()',
63
+ functionId: answers.id,
69
64
  name: answers.name,
70
65
  runtime: answers.runtime.id,
71
66
  parseOutput: false
@@ -110,20 +105,41 @@ const initFunction = async () => {
110
105
  success();
111
106
  }
112
107
 
113
- const initCollection = async () => {
114
- // TODO: Pagination?
115
- let response = await databaseListCollections({
116
- limit: 100,
117
- parseOutput: false
118
- })
108
+ const initCollection = async ({ all, databaseId } = {}) => {
109
+ const databaseIds = [];
110
+
111
+ if(databaseId) {
112
+ databaseIds.push(databaseId);
113
+ } else if(all) {
114
+ let allDatabases = await databasesList({
115
+ parseOutput: false
116
+ })
119
117
 
120
- let collections = response.collections;
121
- log(`Found ${collections.length} collections`);
118
+ databaseIds.push(...allDatabases.databases.map((d) => d.$id));
119
+ }
120
+
121
+ if(databaseIds.length <= 0) {
122
+ let answers = await inquirer.prompt(questionsInitCollection)
123
+ if (!answers.databases) process.exit(1)
124
+ databaseIds.push(...answers.databases);
125
+ }
122
126
 
123
- collections.forEach(async collection => {
124
- log(`Fetching ${collection.name} ...`);
125
- localConfig.addCollection(collection);
126
- });
127
+ for(const databaseId of databaseIds) {
128
+ // TODO: Pagination?
129
+ let response = await databasesListCollections({
130
+ databaseId,
131
+ limit: 100,
132
+ parseOutput: false
133
+ })
134
+
135
+ let collections = response.collections;
136
+ log(`Found ${collections.length} collections`);
137
+
138
+ collections.forEach(async collection => {
139
+ log(`Fetching ${collection.name} ...`);
140
+ localConfig.addCollection(collection);
141
+ });
142
+ }
127
143
 
128
144
  success();
129
145
  }
@@ -141,6 +157,8 @@ init
141
157
  init
142
158
  .command("collection")
143
159
  .description("Initialise your Appwrite collections")
160
+ .option(`--databaseId <databaseId>`, `Database ID`)
161
+ .option(`--all`, `Flag to initialize all databases`)
144
162
  .action(actionRunner(initCollection))
145
163
 
146
164
  module.exports = {
package/lib/parser.js CHANGED
@@ -150,7 +150,7 @@ const logo = "\n _ _ _ ___ __ _____
150
150
  const commandDescriptions = {
151
151
  "account": `The account command allows you to authenticate and manage a user account.`,
152
152
  "avatars": `The avatars command aims to help you complete everyday tasks related to your app image, icons, and avatars.`,
153
- "database": `The database command allows you to create structured collections of documents, query and filter lists of documents.`,
153
+ "databases": `The databases command allows you to create structured collections of documents, query and filter lists of documents.`,
154
154
  "deploy": `The deploy command provides a convenient wrapper for deploying your functions and collections.`,
155
155
  "functions": `The functions command allows you view, create and manage your Cloud Functions.`,
156
156
  "health": `The health command allows you to both validate and monitor your Appwrite server's health.`,
package/lib/questions.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const { localConfig } = require('./config');
2
2
  const { projectsList } = require('./commands/projects');
3
3
  const { functionsListRuntimes } = require('./commands/functions');
4
+ const { databasesList } = require('./commands/databases');
4
5
  const JSONbig = require("json-bigint")({ storeAsString: false });
5
6
 
6
7
  const getIgnores = (runtime) => {
@@ -108,6 +109,15 @@ const questionsInitProject = [
108
109
  return answers.start == "new";
109
110
  },
110
111
  },
112
+ {
113
+ type: "input",
114
+ name: "id",
115
+ message: "What ID would you like to have for your project?",
116
+ default: "myAwesomeProject",
117
+ when(answers) {
118
+ return answers.start == "new";
119
+ },
120
+ },
111
121
  {
112
122
  type: "list",
113
123
  name: "project",
@@ -146,6 +156,12 @@ const questionsInitFunction = [
146
156
  message: "What would you like to name your function?",
147
157
  default: "My Awesome Function"
148
158
  },
159
+ {
160
+ type: "input",
161
+ name: "id",
162
+ message: "What ID would you like to have for your function?",
163
+ default: "myAwesomeFunction"
164
+ },
149
165
  {
150
166
  type: "list",
151
167
  name: "runtime",
@@ -166,6 +182,31 @@ const questionsInitFunction = [
166
182
  }
167
183
  ];
168
184
 
185
+ const questionsInitCollection = [
186
+ {
187
+ type: "checkbox",
188
+ name: "databases",
189
+ message: "From which database would you like to init collections?",
190
+ choices: async () => {
191
+ let response = await databasesList({
192
+ parseOutput: false
193
+ })
194
+ let databases = response["databases"]
195
+
196
+ if(databases.length <= 0) {
197
+ throw new Error("No databases found. Please create one in project console.")
198
+ }
199
+ let choices = databases.map((database, idx) => {
200
+ return {
201
+ name: `${database.name} (${database.$id})`,
202
+ value: database.$id
203
+ }
204
+ })
205
+ return choices;
206
+ }
207
+ }
208
+ ];
209
+
169
210
  const questionsLogin = [
170
211
  {
171
212
  type: "input",
@@ -204,8 +245,8 @@ const questionsDeployFunctions = [
204
245
  }
205
246
  let choices = functions.map((func, idx) => {
206
247
  return {
207
- name: `${func.name} (${func['$id']})`,
208
- value: JSONbig.stringify(func)
248
+ name: `${func.name} (${func.$id})`,
249
+ value: func.$id
209
250
  }
210
251
  })
211
252
  return choices;
@@ -226,7 +267,7 @@ const questionsDeployCollections = [
226
267
  let choices = collections.map((collection, idx) => {
227
268
  return {
228
269
  name: `${collection.name} (${collection['$id']})`,
229
- value: JSONbig.stringify(collection)
270
+ value: collection.$id
230
271
  }
231
272
  })
232
273
  return choices;
@@ -258,6 +299,7 @@ module.exports = {
258
299
  questionsInitProject,
259
300
  questionsLogin,
260
301
  questionsInitFunction,
302
+ questionsInitCollection,
261
303
  questionsDeployFunctions,
262
304
  questionsDeployCollections,
263
305
  questionsGetEntrypoint
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "appwrite-cli",
3
3
  "homepage": "https://appwrite.io/support",
4
4
  "description": "Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API",
5
- "version": "0.18.2",
5
+ "version": "0.18.3",
6
6
  "license": "BSD-3-Clause",
7
7
  "main": "index.js",
8
8
  "bin": {