appwrite-cli 5.0.5 → 6.0.0-rc.1

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.
Files changed (53) hide show
  1. package/README.md +4 -4
  2. package/docs/examples/functions/create-build.md +1 -1
  3. package/docs/examples/functions/create-execution.md +1 -0
  4. package/docs/examples/functions/create.md +1 -0
  5. package/docs/examples/functions/delete-execution.md +3 -0
  6. package/docs/examples/functions/update-deployment-build.md +3 -0
  7. package/docs/examples/functions/update.md +1 -0
  8. package/docs/examples/projects/create-j-w-t.md +4 -0
  9. package/docs/examples/projects/update-mock-numbers.md +3 -0
  10. package/docs/examples/projects/update-session-alerts.md +3 -0
  11. package/docs/examples/users/create-j-w-t.md +4 -0
  12. package/docs/examples/vcs/get-repository-contents.md +4 -0
  13. package/index.js +34 -7
  14. package/install.ps1 +3 -3
  15. package/install.sh +2 -2
  16. package/lib/client.js +17 -3
  17. package/lib/commands/account.js +306 -152
  18. package/lib/commands/assistant.js +8 -5
  19. package/lib/commands/avatars.js +114 -58
  20. package/lib/commands/console.js +8 -5
  21. package/lib/commands/databases.js +353 -164
  22. package/lib/commands/functions.js +310 -100
  23. package/lib/commands/generic.js +206 -54
  24. package/lib/commands/graphql.js +14 -8
  25. package/lib/commands/health.js +140 -71
  26. package/lib/commands/init.js +250 -155
  27. package/lib/commands/locale.js +50 -26
  28. package/lib/commands/messaging.js +334 -156
  29. package/lib/commands/migrations.js +98 -50
  30. package/lib/commands/project.js +38 -20
  31. package/lib/commands/projects.js +449 -144
  32. package/lib/commands/proxy.js +32 -17
  33. package/lib/commands/pull.js +231 -0
  34. package/lib/commands/push.js +1518 -0
  35. package/lib/commands/run.js +282 -0
  36. package/lib/commands/storage.js +160 -76
  37. package/lib/commands/teams.js +102 -50
  38. package/lib/commands/users.js +324 -134
  39. package/lib/commands/vcs.js +102 -29
  40. package/lib/config.js +190 -18
  41. package/lib/emulation/docker.js +187 -0
  42. package/lib/emulation/utils.js +177 -0
  43. package/lib/id.js +30 -0
  44. package/lib/paginate.js +1 -2
  45. package/lib/parser.js +69 -12
  46. package/lib/questions.js +452 -80
  47. package/lib/sdks.js +1 -1
  48. package/lib/spinner.js +103 -0
  49. package/lib/utils.js +242 -4
  50. package/lib/validations.js +17 -0
  51. package/package.json +6 -2
  52. package/scoop/appwrite.json +3 -3
  53. package/lib/commands/deploy.js +0 -940
@@ -3,58 +3,188 @@ const path = require("path");
3
3
  const childProcess = require('child_process');
4
4
  const { Command } = require("commander");
5
5
  const inquirer = require("inquirer");
6
- const { teamsCreate, teamsList } = require("./teams");
7
- const { projectsCreate } = require("./projects");
6
+ const { fetch } = require("undici");
7
+ const { projectsCreate, projectsGet } = require("./projects");
8
+ const { storageCreateBucket } = require("./storage");
9
+ const { messagingCreateTopic } = require("./messaging");
8
10
  const { functionsCreate } = require("./functions");
9
- const { databasesGet, databasesListCollections, databasesList } = require("./databases");
10
- const { storageListBuckets } = require("./storage");
11
+ const { databasesCreateCollection } = require("./databases");
12
+ const ID = require("../id");
13
+ const { localConfig, globalConfig } = require("../config");
14
+ const {
15
+ questionsCreateFunction,
16
+ questionsCreateFunctionSelectTemplate,
17
+ questionsCreateBucket,
18
+ questionsCreateMessagingTopic,
19
+ questionsCreateCollection,
20
+ questionsInitProject,
21
+ questionsInitResources,
22
+ questionsCreateTeam
23
+ } = require("../questions");
24
+ const { success, log, error, actionRunner, commandDescriptions } = require("../parser");
25
+ const { accountGet } = require("./account");
11
26
  const { sdkForConsole } = require("../sdks");
12
- const { localConfig } = require("../config");
13
- const { paginate } = require("../paginate");
14
- const { questionsInitProject, questionsInitFunction, questionsInitCollection } = require("../questions");
15
- const { success, log, actionRunner, commandDescriptions } = require("../parser");
16
27
 
17
- const init = new Command("init")
18
- .description(commandDescriptions['init'])
19
- .configureHelp({
20
- helpWidth: process.stdout.columns || 80
21
- })
22
- .action(actionRunner(async (_options, command) => {
23
- command.help();
24
- }));
25
-
26
- const initProject = async () => {
27
- let response = {}
28
- const answers = await inquirer.prompt(questionsInitProject)
29
- if (!answers.project) process.exit(1)
30
-
31
- let sdk = await sdkForConsole();
32
- if (answers.start === "new") {
33
- response = await teamsCreate({
34
- teamId: 'unique()',
35
- name: answers.project,
36
- sdk,
37
- parseOutput: false
38
- })
28
+ const initResources = async () => {
29
+ const actions = {
30
+ function: initFunction,
31
+ collection: initCollection,
32
+ bucket: initBucket,
33
+ team: initTeam,
34
+ message: initTopic
35
+ }
36
+
37
+ const answers = await inquirer.prompt(questionsInitResources[0]);
38
+
39
+ const action = actions[answers.resource];
40
+ if (action !== undefined) {
41
+ await action({ returnOnZero: true });
42
+ }
43
+ };
44
+
45
+ const initProject = async ({ organizationId, projectId, projectName } = {}) => {
46
+ let response = {};
47
+
48
+ try {
49
+ if (globalConfig.getEndpoint() === '' || globalConfig.getCookie() === '') {
50
+ throw '';
51
+ }
52
+ const client = await sdkForConsole();
53
+
54
+ await accountGet({
55
+ parseOutput: false,
56
+ sdk: client
57
+ });
58
+ } catch (e) {
59
+ error('Error Session not found. Please run `appwrite login` to create a session');
60
+ process.exit(1);
61
+ }
62
+
63
+ let answers = {};
64
+
65
+ if (!organizationId && !projectId && !projectName) {
66
+ answers = await inquirer.prompt(questionsInitProject)
67
+ if (answers.override === false) {
68
+ process.exit(1)
69
+ }
70
+ } else {
71
+ answers.start = 'existing';
72
+ answers.project = {};
73
+ answers.organization = {};
74
+
75
+ answers.organization = organizationId ?? (await inquirer.prompt(questionsInitProject[2])).organization;
76
+ answers.project.name = projectName ?? (await inquirer.prompt(questionsInitProject[3])).project;
77
+ answers.project = projectId ?? (await inquirer.prompt(questionsInitProject[4])).id;
78
+
79
+ try {
80
+ await projectsGet({ projectId, parseOutput: false });
81
+ } catch (e) {
82
+ if (e.code === 404) {
83
+ answers.start = 'new';
84
+ answers.id = answers.project;
85
+ answers.project = answers.project.name;
86
+ } else {
87
+ throw e;
88
+ }
89
+ }
90
+ }
39
91
 
40
- let teamId = response['$id'];
92
+ if (answers.start === 'new') {
41
93
  response = await projectsCreate({
42
94
  projectId: answers.id,
43
95
  name: answers.project,
44
- teamId,
96
+ teamId: answers.organization,
45
97
  parseOutput: false
46
98
  })
47
99
 
48
- localConfig.setProject(response['$id'], response.name);
100
+ localConfig.setProject(response['$id']);
49
101
  } else {
50
- localConfig.setProject(answers.project.id, answers.project.name);
102
+ localConfig.setProject(answers.project);
103
+ }
104
+
105
+ success(`Project successfully ${answers.start === 'existing' ? 'linked' : 'created'}. Details are now stored in appwrite.json file.`);
106
+
107
+ log("Next you can use 'appwrite init' to create resources in your project, or use 'appwrite pull' and 'appwite push' to synchronize your project.")
108
+
109
+ if(answers.start === 'existing') {
110
+ log("Since you connected to an existing project, we highly recommend to run 'appwrite pull all' to synchronize all of your existing resources.");
51
111
  }
52
- success();
53
112
  }
54
113
 
114
+ const initBucket = async () => {
115
+ const answers = await inquirer.prompt(questionsCreateBucket)
116
+
117
+ localConfig.addBucket({
118
+ $id: answers.id === 'unique()' ? ID.unique() : answers.id,
119
+ name: answers.bucket,
120
+ fileSecurity: answers.fileSecurity.toLowerCase() === 'yes',
121
+ enabled: true,
122
+ });
123
+ success();
124
+ log("Next you can use 'appwrite push bucket' to deploy the changes.");
125
+ };
126
+
127
+ const initTeam = async () => {
128
+ const answers = await inquirer.prompt(questionsCreateTeam)
129
+
130
+ localConfig.addTeam({
131
+ $id: answers.id === 'unique()' ? ID.unique() : answers.id,
132
+ name: answers.bucket,
133
+ });
134
+
135
+ success();
136
+ log("Next you can use 'appwrite push team' to deploy the changes.");
137
+ };
138
+
139
+ const initCollection = async () => {
140
+ const answers = await inquirer.prompt(questionsCreateCollection)
141
+ const newDatabase = (answers.method ?? '').toLowerCase() !== 'existing';
142
+
143
+ if (!newDatabase) {
144
+ answers.databaseId = answers.database;
145
+ answers.databaseName = localConfig.getDatabase(answers.database).name;
146
+ }
147
+
148
+ const databaseId = answers.databaseId === 'unique()' ? ID.unique() : answers.databaseId;
149
+
150
+ if (newDatabase || !localConfig.getDatabase(answers.databaseId)) {
151
+ localConfig.addDatabase({
152
+ $id: databaseId,
153
+ name: answers.databaseName,
154
+ enabled: true
155
+ });
156
+ }
157
+
158
+ localConfig.addCollection({
159
+ $id: answers.id === 'unique()' ? ID.unique() : answers.id,
160
+ databaseId: databaseId,
161
+ name: answers.collection,
162
+ documentSecurity: answers.documentSecurity.toLowerCase() === 'yes',
163
+ attributes: [],
164
+ indexes: [],
165
+ enabled: true,
166
+ });
167
+
168
+ success();
169
+ log("Next you can use 'appwrite push collection' to deploy the changes.");
170
+ };
171
+
172
+ const initTopic = async () => {
173
+ const answers = await inquirer.prompt(questionsCreateMessagingTopic)
174
+
175
+ localConfig.addMessagingTopic({
176
+ $id: answers.id === 'unique()' ? ID.unique() : answers.id,
177
+ name: answers.topic,
178
+
179
+ });
180
+
181
+ success();
182
+ log("Next you can use 'appwrite push topic' to deploy the changes.");
183
+ };
184
+
55
185
  const initFunction = async () => {
56
186
  // TODO: Add CI/CD support (ID, name, runtime)
57
- const answers = await inquirer.prompt(questionsInitFunction)
187
+ const answers = await inquirer.prompt(questionsCreateFunction)
58
188
  const functionFolder = path.join(process.cwd(), 'functions');
59
189
 
60
190
  if (!fs.existsSync(functionFolder)) {
@@ -63,34 +193,46 @@ const initFunction = async () => {
63
193
  });
64
194
  }
65
195
 
66
- const functionDir = path.join(functionFolder, answers.name);
196
+ const functionId = answers.id === 'unique()' ? ID.unique() : answers.id;
197
+ const functionDir = path.join(functionFolder, functionId);
198
+ const templatesDir = path.join(functionFolder, `${functionId}-templates`);
199
+ const runtimeDir = path.join(templatesDir, answers.runtime.name);
67
200
 
68
201
  if (fs.existsSync(functionDir)) {
69
- throw new Error(`( ${answers.name} ) already exists in the current directory. Please choose another name.`);
202
+ throw new Error(`( ${functionId} ) already exists in the current directory. Please choose another name.`);
70
203
  }
71
204
 
72
205
  if (!answers.runtime.entrypoint) {
73
- log(`Entrypoint for this runtime not found. You will be asked to configure entrypoint when you first deploy the function.`);
206
+ log(`Entrypoint for this runtime not found. You will be asked to configure entrypoint when you first push the function.`);
74
207
  }
75
208
 
76
209
  if (!answers.runtime.commands) {
77
- log(`Installation command for this runtime not found. You will be asked to configure the install command when you first deploy the function.`);
210
+ log(`Installation command for this runtime not found. You will be asked to configure the install command when you first push the function.`);
78
211
  }
79
212
 
80
- let response = await functionsCreate({
81
- functionId: answers.id,
82
- name: answers.name,
83
- runtime: answers.runtime.id,
84
- entrypoint: answers.runtime.entrypoint || '',
85
- commands: answers.runtime.commands || '',
86
- parseOutput: false
87
- })
88
213
 
89
214
  fs.mkdirSync(functionDir, "777");
215
+ fs.mkdirSync(templatesDir, "777");
216
+ const repo = "https://github.com/appwrite/templates";
217
+ const api = `https://api.github.com/repos/appwrite/templates/contents/${answers.runtime.name}`
218
+ const templates = ['starter'];
219
+ let selected = undefined;
220
+
221
+ try {
222
+ const res = await fetch(api);
223
+ templates.push(...(await res.json()).map((template) => template.name));
90
224
 
91
- let gitInitCommands = "git clone -b v3 --single-branch --depth 1 --sparse https://github.com/appwrite/functions-starter ."; // depth prevents fetching older commits reducing the amount fetched
225
+ selected = await inquirer.prompt(questionsCreateFunctionSelectTemplate(templates))
226
+ } catch {
227
+ // Not a problem will go with directory pulling
228
+ log('Loading templates...');
229
+ }
230
+
231
+ const sparse = (selected ? `${answers.runtime.name}/${selected.template}` : answers.runtime.name).toLowerCase();
92
232
 
93
- let gitPullCommands = `git sparse-checkout add ${answers.runtime.id}`;
233
+ let gitInitCommands = `git clone --single-branch --depth 1 --sparse ${repo} .`; // depth prevents fetching older commits reducing the amount fetched
234
+
235
+ let gitPullCommands = `git sparse-checkout add ${sparse}`;
94
236
 
95
237
  /* Force use CMD as powershell does not support && */
96
238
  if (process.platform === 'win32') {
@@ -100,8 +242,8 @@ const initFunction = async () => {
100
242
 
101
243
  /* Execute the child process but do not print any std output */
102
244
  try {
103
- childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: functionDir });
104
- childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: functionDir });
245
+ childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: templatesDir });
246
+ childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: templatesDir });
105
247
  } catch (error) {
106
248
  /* Specialised errors with recommended actions to take */
107
249
  if (error.message.includes('error: unknown option')) {
@@ -113,7 +255,18 @@ const initFunction = async () => {
113
255
  }
114
256
  }
115
257
 
116
- fs.rmSync(path.join(functionDir, ".git"), { recursive: true });
258
+ fs.rmSync(path.join(templatesDir, ".git"), { recursive: true });
259
+ if (!selected) {
260
+ templates.push(...fs.readdirSync(runtimeDir, { withFileTypes: true })
261
+ .filter(item => item.isDirectory() && item.name !== 'starter')
262
+ .map(dirent => dirent.name));
263
+ selected = { template: 'starter' };
264
+
265
+ if (templates.length > 1) {
266
+ selected = await inquirer.prompt(questionsCreateFunctionSelectTemplate(templates))
267
+ }
268
+ }
269
+
117
270
  const copyRecursiveSync = (src, dest) => {
118
271
  let exists = fs.existsSync(src);
119
272
  let stats = exists && fs.statSync(src);
@@ -130,11 +283,11 @@ const initFunction = async () => {
130
283
  fs.copyFileSync(src, dest);
131
284
  }
132
285
  };
133
- copyRecursiveSync(path.join(functionDir, answers.runtime.id), functionDir);
286
+ copyRecursiveSync(path.join(runtimeDir, selected.template), functionDir);
134
287
 
135
- fs.rmSync(`${functionDir}/${answers.runtime.id}`, { recursive: true, force: true });
288
+ fs.rmSync(templatesDir, { recursive: true, force: true });
136
289
 
137
- const readmePath = path.join(process.cwd(), 'functions', answers.name, 'README.md');
290
+ const readmePath = path.join(process.cwd(), 'functions', functionId, 'README.md');
138
291
  const readmeFile = fs.readFileSync(readmePath).toString();
139
292
  const newReadmeFile = readmeFile.split('\n');
140
293
  newReadmeFile[0] = `# ${answers.name}`;
@@ -142,125 +295,67 @@ const initFunction = async () => {
142
295
  fs.writeFileSync(readmePath, newReadmeFile.join('\n'));
143
296
 
144
297
  let data = {
145
- $id: response['$id'],
146
- name: response.name,
147
- runtime: response.runtime,
148
- execute: response.execute,
149
- events: response.events,
150
- schedule: response.schedule,
151
- timeout: response.timeout,
152
- enabled: response.enabled,
153
- logging: response.logging,
154
- entrypoint: response.entrypoint,
155
- commands: response.commands,
298
+ $id: functionId,
299
+ name: answers.name,
300
+ runtime: answers.runtime.id,
301
+ execute: [],
302
+ events: [],
303
+ schedule: "",
304
+ timeout: 15,
305
+ enabled: true,
306
+ logging: true,
307
+ entrypoint: answers.runtime.entrypoint || '',
308
+ commands: answers.runtime.commands || '',
156
309
  ignore: answers.runtime.ignore || null,
157
- path: `functions/${answers.name}`,
310
+ path: `functions/${functionId}`,
158
311
  };
159
312
 
160
313
  localConfig.addFunction(data);
161
314
  success();
315
+ log("Next you can use 'appwrite run function' to develop a function locally. To deploy the function, use 'appwrite push function'");
162
316
  }
163
317
 
164
- const initCollection = async ({ all, databaseId } = {}) => {
165
- const databaseIds = [];
166
-
167
- if (databaseId) {
168
- databaseIds.push(databaseId);
169
- } else if (all) {
170
- let allDatabases = await databasesList({
171
- parseOutput: false
172
- })
173
-
174
- databaseIds.push(...allDatabases.databases.map((d) => d.$id));
175
- }
176
-
177
- if (databaseIds.length <= 0) {
178
- let answers = await inquirer.prompt(questionsInitCollection)
179
- if (!answers.databases) process.exit(1)
180
- databaseIds.push(...answers.databases);
181
- }
182
-
183
- for (const databaseId of databaseIds) {
184
- const database = await databasesGet({
185
- databaseId,
186
- parseOutput: false
187
- });
188
-
189
- localConfig.addDatabase(database);
190
-
191
- const { collections, total } = await paginate(databasesListCollections, {
192
- databaseId,
193
- parseOutput: false
194
- }, 100, 'collections');
195
-
196
- log(`Found ${total} collections`);
197
-
198
- collections.forEach(async collection => {
199
- log(`Fetching ${collection.name} ...`);
200
- localConfig.addCollection({
201
- ...collection,
202
- '$createdAt': undefined,
203
- '$updatedAt': undefined,
204
- });
205
- });
206
- }
207
-
208
- success();
209
- }
210
-
211
- const initBucket = async () => {
212
- const { buckets } = await paginate(storageListBuckets, { parseOutput: false }, 100, 'buckets');
213
-
214
- log(`Found ${buckets.length} buckets`);
215
-
216
- buckets.forEach(async bucket => {
217
- log(`Fetching ${bucket.name} ...`);
218
- localConfig.addBucket(bucket);
219
- });
220
-
221
- success();
222
- }
223
-
224
- const initTeam = async () => {
225
- const { teams } = await paginate(teamsList, { parseOutput: false }, 100, 'teams');
226
-
227
- log(`Found ${teams.length} teams`);
228
-
229
- teams.forEach(async team => {
230
- log(`Fetching ${team.name} ...`);
231
- const { total, $updatedAt, $createdAt, prefs, ...rest } = team;
232
- localConfig.addTeam(rest);
233
- });
234
-
235
- success();
236
- }
318
+ const init = new Command("init")
319
+ .description(commandDescriptions['init'])
320
+ .action(actionRunner(initResources));
237
321
 
238
322
  init
239
323
  .command("project")
240
- .description("Initialise your Appwrite project")
324
+ .description("Init a new Appwrite project")
325
+ .option("--organizationId <organizationId>", "Appwrite organization ID")
326
+ .option("--projectId <projectId>", "Appwrite project ID")
327
+ .option("--projectName <projectName>", "Appwrite project ID")
241
328
  .action(actionRunner(initProject));
242
329
 
243
330
  init
244
331
  .command("function")
245
- .description("Initialise your Appwrite cloud function")
246
- .action(actionRunner(initFunction))
247
-
248
- init
249
- .command("collection")
250
- .description("Initialise your Appwrite collections")
251
- .option(`--databaseId <databaseId>`, `Database ID`)
252
- .option(`--all`, `Flag to initialize all databases`)
253
- .action(actionRunner(initCollection))
332
+ .alias("functions")
333
+ .description("Init a new Appwrite function")
334
+ .action(actionRunner(initFunction));
254
335
 
255
336
  init
256
337
  .command("bucket")
257
- .description("Initialise your Appwrite buckets")
258
- .action(actionRunner(initBucket))
338
+ .alias("buckets")
339
+ .description("Init a new Appwrite bucket")
340
+ .action(actionRunner(initBucket));
259
341
 
260
342
  init
261
343
  .command("team")
262
- .description("Initialise your Appwrite teams")
263
- .action(actionRunner(initTeam))
344
+ .alias("teams")
345
+ .description("Init a new Appwrite team")
346
+ .action(actionRunner(initTeam));
347
+
348
+ init
349
+ .command("collection")
350
+ .alias("collections")
351
+ .description("Init a new Appwrite collection")
352
+ .action(actionRunner(initCollection));
353
+
354
+ init
355
+ .command("topic")
356
+ .alias("topics")
357
+ .description("Init a new Appwrite topic")
358
+ .action(actionRunner(initTopic));
264
359
 
265
360
  module.exports = {
266
361
  init,