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.
- package/README.md +4 -4
- package/docs/examples/functions/create-build.md +1 -1
- package/docs/examples/functions/create-execution.md +1 -0
- package/docs/examples/functions/create.md +1 -0
- package/docs/examples/functions/delete-execution.md +3 -0
- package/docs/examples/functions/update-deployment-build.md +3 -0
- package/docs/examples/functions/update.md +1 -0
- package/docs/examples/projects/create-j-w-t.md +4 -0
- package/docs/examples/projects/update-mock-numbers.md +3 -0
- package/docs/examples/projects/update-session-alerts.md +3 -0
- package/docs/examples/users/create-j-w-t.md +4 -0
- package/docs/examples/vcs/get-repository-contents.md +4 -0
- package/index.js +34 -7
- package/install.ps1 +3 -3
- package/install.sh +2 -2
- package/lib/client.js +17 -3
- package/lib/commands/account.js +306 -152
- package/lib/commands/assistant.js +8 -5
- package/lib/commands/avatars.js +114 -58
- package/lib/commands/console.js +8 -5
- package/lib/commands/databases.js +353 -164
- package/lib/commands/functions.js +310 -100
- package/lib/commands/generic.js +206 -54
- package/lib/commands/graphql.js +14 -8
- package/lib/commands/health.js +140 -71
- package/lib/commands/init.js +250 -155
- package/lib/commands/locale.js +50 -26
- package/lib/commands/messaging.js +334 -156
- package/lib/commands/migrations.js +98 -50
- package/lib/commands/project.js +38 -20
- package/lib/commands/projects.js +449 -144
- package/lib/commands/proxy.js +32 -17
- package/lib/commands/pull.js +231 -0
- package/lib/commands/push.js +1518 -0
- package/lib/commands/run.js +282 -0
- package/lib/commands/storage.js +160 -76
- package/lib/commands/teams.js +102 -50
- package/lib/commands/users.js +324 -134
- package/lib/commands/vcs.js +102 -29
- package/lib/config.js +190 -18
- package/lib/emulation/docker.js +187 -0
- package/lib/emulation/utils.js +177 -0
- package/lib/id.js +30 -0
- package/lib/paginate.js +1 -2
- package/lib/parser.js +69 -12
- package/lib/questions.js +452 -80
- package/lib/sdks.js +1 -1
- package/lib/spinner.js +103 -0
- package/lib/utils.js +242 -4
- package/lib/validations.js +17 -0
- package/package.json +6 -2
- package/scoop/appwrite.json +3 -3
- package/lib/commands/deploy.js +0 -940
|
@@ -0,0 +1,1518 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const inquirer = require("inquirer");
|
|
3
|
+
const JSONbig = require("json-bigint")({ storeAsString: false });
|
|
4
|
+
const { Command } = require("commander");
|
|
5
|
+
const { localConfig, globalConfig } = require("../config");
|
|
6
|
+
const { Spinner, SPINNER_ARC, SPINNER_DOTS } = require('../spinner');
|
|
7
|
+
const { paginate } = require('../paginate');
|
|
8
|
+
const { questionsPushBuckets, questionsPushTeams, questionsPushFunctions, questionsGetEntrypoint, questionsPushCollections, questionsConfirmPushCollections, questionsPushMessagingTopics, questionsPushResources } = require("../questions");
|
|
9
|
+
const { cliConfig, actionRunner, success, log, error, commandDescriptions, drawTable } = require("../parser");
|
|
10
|
+
const { proxyListRules } = require('./proxy');
|
|
11
|
+
const { functionsGet, functionsCreate, functionsUpdate, functionsCreateDeployment, functionsUpdateDeployment, functionsGetDeployment, functionsListVariables, functionsDeleteVariable, functionsCreateVariable } = require('./functions');
|
|
12
|
+
const {
|
|
13
|
+
databasesGet,
|
|
14
|
+
databasesCreate,
|
|
15
|
+
databasesUpdate,
|
|
16
|
+
databasesCreateBooleanAttribute,
|
|
17
|
+
databasesGetCollection,
|
|
18
|
+
databasesCreateCollection,
|
|
19
|
+
databasesCreateStringAttribute,
|
|
20
|
+
databasesCreateIntegerAttribute,
|
|
21
|
+
databasesCreateFloatAttribute,
|
|
22
|
+
databasesCreateEmailAttribute,
|
|
23
|
+
databasesCreateDatetimeAttribute,
|
|
24
|
+
databasesCreateIndex,
|
|
25
|
+
databasesCreateUrlAttribute,
|
|
26
|
+
databasesCreateIpAttribute,
|
|
27
|
+
databasesCreateEnumAttribute,
|
|
28
|
+
databasesUpdateBooleanAttribute,
|
|
29
|
+
databasesUpdateStringAttribute,
|
|
30
|
+
databasesUpdateIntegerAttribute,
|
|
31
|
+
databasesUpdateFloatAttribute,
|
|
32
|
+
databasesUpdateEmailAttribute,
|
|
33
|
+
databasesUpdateDatetimeAttribute,
|
|
34
|
+
databasesUpdateUrlAttribute,
|
|
35
|
+
databasesUpdateIpAttribute,
|
|
36
|
+
databasesUpdateEnumAttribute,
|
|
37
|
+
databasesUpdateRelationshipAttribute,
|
|
38
|
+
databasesCreateRelationshipAttribute,
|
|
39
|
+
databasesDeleteAttribute,
|
|
40
|
+
databasesListAttributes,
|
|
41
|
+
databasesListIndexes,
|
|
42
|
+
databasesUpdateCollection
|
|
43
|
+
} = require("./databases");
|
|
44
|
+
const {
|
|
45
|
+
storageGetBucket, storageUpdateBucket, storageCreateBucket
|
|
46
|
+
} = require("./storage");
|
|
47
|
+
const {
|
|
48
|
+
messagingGetTopic, messagingUpdateTopic, messagingCreateTopic
|
|
49
|
+
} = require("./messaging");
|
|
50
|
+
const {
|
|
51
|
+
teamsGet,
|
|
52
|
+
teamsUpdateName,
|
|
53
|
+
teamsCreate
|
|
54
|
+
} = require("./teams");
|
|
55
|
+
const {
|
|
56
|
+
projectsUpdate,
|
|
57
|
+
projectsUpdateServiceStatus,
|
|
58
|
+
projectsUpdateAuthStatus,
|
|
59
|
+
projectsUpdateAuthDuration,
|
|
60
|
+
projectsUpdateAuthLimit,
|
|
61
|
+
projectsUpdateAuthSessionsLimit,
|
|
62
|
+
projectsUpdateAuthPasswordDictionary,
|
|
63
|
+
projectsUpdateAuthPasswordHistory,
|
|
64
|
+
projectsUpdatePersonalDataCheck,
|
|
65
|
+
} = require("./projects");
|
|
66
|
+
const { checkDeployConditions } = require('../utils');
|
|
67
|
+
|
|
68
|
+
const STEP_SIZE = 100; // Resources
|
|
69
|
+
const POLL_DEBOUNCE = 2000; // Milliseconds
|
|
70
|
+
const POLL_MAX_DEBOUNCE = 30; // Times
|
|
71
|
+
|
|
72
|
+
let pollMaxDebounces = 30;
|
|
73
|
+
|
|
74
|
+
const changeableKeys = ['status', 'required', 'xdefault', 'elements', 'min', 'max', 'default', 'error'];
|
|
75
|
+
|
|
76
|
+
const awaitPools = {
|
|
77
|
+
wipeAttributes: async (databaseId, collectionId, iteration = 1) => {
|
|
78
|
+
if (iteration > pollMaxDebounces) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const { total } = await databasesListAttributes({
|
|
83
|
+
databaseId,
|
|
84
|
+
collectionId,
|
|
85
|
+
queries: [JSON.stringify({ method: 'limit', values: [1] })],
|
|
86
|
+
parseOutput: false
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (total === 0) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
|
|
94
|
+
if (steps > 1 && iteration === 1) {
|
|
95
|
+
pollMaxDebounces *= steps;
|
|
96
|
+
|
|
97
|
+
log('Found a large number of attributes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
|
|
101
|
+
|
|
102
|
+
return await awaitPools.wipeAttributes(
|
|
103
|
+
databaseId,
|
|
104
|
+
collectionId,
|
|
105
|
+
iteration + 1
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
wipeIndexes: async (databaseId, collectionId, iteration = 1) => {
|
|
109
|
+
if (iteration > pollMaxDebounces) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const { total } = await databasesListIndexes({
|
|
114
|
+
databaseId,
|
|
115
|
+
collectionId,
|
|
116
|
+
queries: [JSON.stringify({ method: 'limit', values: [1] })],
|
|
117
|
+
parseOutput: false
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (total === 0) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
|
|
125
|
+
if (steps > 1 && iteration === 1) {
|
|
126
|
+
pollMaxDebounces *= steps;
|
|
127
|
+
|
|
128
|
+
log('Found a large number of indexes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
|
|
132
|
+
|
|
133
|
+
return await awaitPools.wipeIndexes(
|
|
134
|
+
databaseId,
|
|
135
|
+
collectionId,
|
|
136
|
+
iteration + 1
|
|
137
|
+
);
|
|
138
|
+
},
|
|
139
|
+
wipeVariables: async (functionId, iteration = 1) => {
|
|
140
|
+
if (iteration > pollMaxDebounces) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const { total } = await functionsListVariables({
|
|
145
|
+
functionId,
|
|
146
|
+
queries: ['limit(1)'],
|
|
147
|
+
parseOutput: false
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (total === 0) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
|
|
155
|
+
if (steps > 1 && iteration === 1) {
|
|
156
|
+
pollMaxDebounces *= steps;
|
|
157
|
+
|
|
158
|
+
log('Found a large number of variables, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
|
|
162
|
+
|
|
163
|
+
return await awaitPools.wipeVariables(
|
|
164
|
+
functionId,
|
|
165
|
+
iteration + 1
|
|
166
|
+
);
|
|
167
|
+
},
|
|
168
|
+
deleteAttributes: async (databaseId, collectionId, attributeKeys, iteration = 1) => {
|
|
169
|
+
if (iteration > pollMaxDebounces) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
|
|
174
|
+
if (steps > 1 && iteration === 1) {
|
|
175
|
+
pollMaxDebounces *= steps;
|
|
176
|
+
|
|
177
|
+
log('Found a large number of attributes to be deleted. Increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const { attributes } = await paginate(databasesListAttributes, {
|
|
181
|
+
databaseId,
|
|
182
|
+
collectionId,
|
|
183
|
+
parseOutput: false
|
|
184
|
+
}, 100, 'attributes');
|
|
185
|
+
|
|
186
|
+
const ready = attributeKeys.filter(attribute => attributes.includes(attribute.key));
|
|
187
|
+
|
|
188
|
+
if (ready.length === 0) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
|
|
193
|
+
|
|
194
|
+
return await awaitPools.expectAttributes(
|
|
195
|
+
databaseId,
|
|
196
|
+
collectionId,
|
|
197
|
+
attributeKeys,
|
|
198
|
+
iteration + 1
|
|
199
|
+
);
|
|
200
|
+
},
|
|
201
|
+
expectAttributes: async (databaseId, collectionId, attributeKeys, iteration = 1) => {
|
|
202
|
+
if (iteration > pollMaxDebounces) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
|
|
207
|
+
if (steps > 1 && iteration === 1) {
|
|
208
|
+
pollMaxDebounces *= steps;
|
|
209
|
+
|
|
210
|
+
log('Creating a large number of attributes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const { attributes } = await paginate(databasesListAttributes, {
|
|
214
|
+
databaseId,
|
|
215
|
+
collectionId,
|
|
216
|
+
parseOutput: false
|
|
217
|
+
}, 100, 'attributes');
|
|
218
|
+
|
|
219
|
+
const ready = attributes
|
|
220
|
+
.filter(attribute => {
|
|
221
|
+
if (attributeKeys.includes(attribute.key)) {
|
|
222
|
+
if (['stuck', 'failed'].includes(attribute.status)) {
|
|
223
|
+
throw new Error(`Attribute '${attribute.key}' failed!`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return attribute.status === 'available';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return false;
|
|
230
|
+
})
|
|
231
|
+
.map(attribute => attribute.key);
|
|
232
|
+
|
|
233
|
+
if (ready.length === attributeKeys.length) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
|
|
238
|
+
|
|
239
|
+
return await awaitPools.expectAttributes(
|
|
240
|
+
databaseId,
|
|
241
|
+
collectionId,
|
|
242
|
+
attributeKeys,
|
|
243
|
+
iteration + 1
|
|
244
|
+
);
|
|
245
|
+
},
|
|
246
|
+
expectIndexes: async (databaseId, collectionId, indexKeys, iteration = 1) => {
|
|
247
|
+
if (iteration > pollMaxDebounces) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let steps = Math.max(1, Math.ceil(indexKeys.length / STEP_SIZE));
|
|
252
|
+
if (steps > 1 && iteration === 1) {
|
|
253
|
+
pollMaxDebounces *= steps;
|
|
254
|
+
|
|
255
|
+
log('Creating a large number of indexes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const { indexes } = await paginate(databasesListIndexes, {
|
|
259
|
+
databaseId,
|
|
260
|
+
collectionId,
|
|
261
|
+
parseOutput: false
|
|
262
|
+
}, 100, 'indexes');
|
|
263
|
+
|
|
264
|
+
const ready = indexes
|
|
265
|
+
.filter((index) => {
|
|
266
|
+
if (indexKeys.includes(index.key)) {
|
|
267
|
+
if (['stuck', 'failed'].includes(index.status)) {
|
|
268
|
+
throw new Error(`Index '${index.key}' failed!`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return index.status === 'available';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return false;
|
|
275
|
+
})
|
|
276
|
+
.map(index => index.key);
|
|
277
|
+
|
|
278
|
+
if (ready.length >= indexKeys.length) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
|
|
283
|
+
|
|
284
|
+
return await awaitPools.expectIndexes(
|
|
285
|
+
databaseId,
|
|
286
|
+
collectionId,
|
|
287
|
+
indexKeys,
|
|
288
|
+
iteration + 1
|
|
289
|
+
);
|
|
290
|
+
},
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const createAttribute = async (databaseId, collectionId, attribute) => {
|
|
294
|
+
switch (attribute.type) {
|
|
295
|
+
case 'string':
|
|
296
|
+
switch (attribute.format) {
|
|
297
|
+
case 'email':
|
|
298
|
+
return await databasesCreateEmailAttribute({
|
|
299
|
+
databaseId,
|
|
300
|
+
collectionId,
|
|
301
|
+
key: attribute.key,
|
|
302
|
+
required: attribute.required,
|
|
303
|
+
xdefault: attribute.default,
|
|
304
|
+
array: attribute.array,
|
|
305
|
+
parseOutput: false
|
|
306
|
+
})
|
|
307
|
+
case 'url':
|
|
308
|
+
return await databasesCreateUrlAttribute({
|
|
309
|
+
databaseId,
|
|
310
|
+
collectionId,
|
|
311
|
+
key: attribute.key,
|
|
312
|
+
required: attribute.required,
|
|
313
|
+
xdefault: attribute.default,
|
|
314
|
+
array: attribute.array,
|
|
315
|
+
parseOutput: false
|
|
316
|
+
})
|
|
317
|
+
case 'ip':
|
|
318
|
+
return await databasesCreateIpAttribute({
|
|
319
|
+
databaseId,
|
|
320
|
+
collectionId,
|
|
321
|
+
key: attribute.key,
|
|
322
|
+
required: attribute.required,
|
|
323
|
+
xdefault: attribute.default,
|
|
324
|
+
array: attribute.array,
|
|
325
|
+
parseOutput: false
|
|
326
|
+
})
|
|
327
|
+
case 'enum':
|
|
328
|
+
return await databasesCreateEnumAttribute({
|
|
329
|
+
databaseId,
|
|
330
|
+
collectionId,
|
|
331
|
+
key: attribute.key,
|
|
332
|
+
elements: attribute.elements,
|
|
333
|
+
required: attribute.required,
|
|
334
|
+
xdefault: attribute.default,
|
|
335
|
+
array: attribute.array,
|
|
336
|
+
parseOutput: false
|
|
337
|
+
})
|
|
338
|
+
default:
|
|
339
|
+
return await databasesCreateStringAttribute({
|
|
340
|
+
databaseId,
|
|
341
|
+
collectionId,
|
|
342
|
+
key: attribute.key,
|
|
343
|
+
size: attribute.size,
|
|
344
|
+
required: attribute.required,
|
|
345
|
+
xdefault: attribute.default,
|
|
346
|
+
array: attribute.array,
|
|
347
|
+
parseOutput: false
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
}
|
|
351
|
+
case 'integer':
|
|
352
|
+
return await databasesCreateIntegerAttribute({
|
|
353
|
+
databaseId,
|
|
354
|
+
collectionId,
|
|
355
|
+
key: attribute.key,
|
|
356
|
+
required: attribute.required,
|
|
357
|
+
min: parseInt(attribute.min.toString()),
|
|
358
|
+
max: parseInt(attribute.max.toString()),
|
|
359
|
+
xdefault: attribute.default,
|
|
360
|
+
array: attribute.array,
|
|
361
|
+
parseOutput: false
|
|
362
|
+
})
|
|
363
|
+
case 'double':
|
|
364
|
+
return databasesCreateFloatAttribute({
|
|
365
|
+
databaseId,
|
|
366
|
+
collectionId,
|
|
367
|
+
key: attribute.key,
|
|
368
|
+
required: attribute.required,
|
|
369
|
+
min: parseFloat(attribute.min.toString()),
|
|
370
|
+
max: parseFloat(attribute.max.toString()),
|
|
371
|
+
xdefault: attribute.default,
|
|
372
|
+
array: attribute.array,
|
|
373
|
+
parseOutput: false
|
|
374
|
+
})
|
|
375
|
+
case 'boolean':
|
|
376
|
+
return databasesCreateBooleanAttribute({
|
|
377
|
+
databaseId,
|
|
378
|
+
collectionId,
|
|
379
|
+
key: attribute.key,
|
|
380
|
+
required: attribute.required,
|
|
381
|
+
xdefault: attribute.default,
|
|
382
|
+
array: attribute.array,
|
|
383
|
+
parseOutput: false
|
|
384
|
+
})
|
|
385
|
+
case 'datetime':
|
|
386
|
+
return databasesCreateDatetimeAttribute({
|
|
387
|
+
databaseId,
|
|
388
|
+
collectionId,
|
|
389
|
+
key: attribute.key,
|
|
390
|
+
required: attribute.required,
|
|
391
|
+
xdefault: attribute.default,
|
|
392
|
+
array: attribute.array,
|
|
393
|
+
parseOutput: false
|
|
394
|
+
})
|
|
395
|
+
case 'relationship':
|
|
396
|
+
return databasesCreateRelationshipAttribute({
|
|
397
|
+
databaseId,
|
|
398
|
+
collectionId,
|
|
399
|
+
relatedCollectionId: attribute.relatedCollection,
|
|
400
|
+
type: attribute.relationType,
|
|
401
|
+
twoWay: attribute.twoWay,
|
|
402
|
+
key: attribute.key,
|
|
403
|
+
twoWayKey: attribute.twoWayKey,
|
|
404
|
+
onDelete: attribute.onDelete,
|
|
405
|
+
parseOutput: false
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const updateAttribute = async (databaseId, collectionId, attribute) => {
|
|
411
|
+
switch (attribute.type) {
|
|
412
|
+
case 'string':
|
|
413
|
+
switch (attribute.format) {
|
|
414
|
+
case 'email':
|
|
415
|
+
return await databasesUpdateEmailAttribute({
|
|
416
|
+
databaseId,
|
|
417
|
+
collectionId,
|
|
418
|
+
key: attribute.key,
|
|
419
|
+
required: attribute.required,
|
|
420
|
+
xdefault: attribute.default,
|
|
421
|
+
array: attribute.array,
|
|
422
|
+
parseOutput: false
|
|
423
|
+
})
|
|
424
|
+
case 'url':
|
|
425
|
+
return await databasesUpdateUrlAttribute({
|
|
426
|
+
databaseId,
|
|
427
|
+
collectionId,
|
|
428
|
+
key: attribute.key,
|
|
429
|
+
required: attribute.required,
|
|
430
|
+
xdefault: attribute.default,
|
|
431
|
+
array: attribute.array,
|
|
432
|
+
parseOutput: false
|
|
433
|
+
})
|
|
434
|
+
case 'ip':
|
|
435
|
+
return await databasesUpdateIpAttribute({
|
|
436
|
+
databaseId,
|
|
437
|
+
collectionId,
|
|
438
|
+
key: attribute.key,
|
|
439
|
+
required: attribute.required,
|
|
440
|
+
xdefault: attribute.default,
|
|
441
|
+
array: attribute.array,
|
|
442
|
+
parseOutput: false
|
|
443
|
+
})
|
|
444
|
+
case 'enum':
|
|
445
|
+
return await databasesUpdateEnumAttribute({
|
|
446
|
+
databaseId,
|
|
447
|
+
collectionId,
|
|
448
|
+
key: attribute.key,
|
|
449
|
+
elements: attribute.elements,
|
|
450
|
+
required: attribute.required,
|
|
451
|
+
xdefault: attribute.default,
|
|
452
|
+
array: attribute.array,
|
|
453
|
+
parseOutput: false
|
|
454
|
+
})
|
|
455
|
+
default:
|
|
456
|
+
return await databasesUpdateStringAttribute({
|
|
457
|
+
databaseId,
|
|
458
|
+
collectionId,
|
|
459
|
+
key: attribute.key,
|
|
460
|
+
size: attribute.size,
|
|
461
|
+
required: attribute.required,
|
|
462
|
+
xdefault: attribute.default,
|
|
463
|
+
array: attribute.array,
|
|
464
|
+
parseOutput: false
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
}
|
|
468
|
+
case 'integer':
|
|
469
|
+
return await databasesUpdateIntegerAttribute({
|
|
470
|
+
databaseId,
|
|
471
|
+
collectionId,
|
|
472
|
+
key: attribute.key,
|
|
473
|
+
required: attribute.required,
|
|
474
|
+
min: parseInt(attribute.min.toString()),
|
|
475
|
+
max: parseInt(attribute.max.toString()),
|
|
476
|
+
xdefault: attribute.default,
|
|
477
|
+
array: attribute.array,
|
|
478
|
+
parseOutput: false
|
|
479
|
+
})
|
|
480
|
+
case 'double':
|
|
481
|
+
return databasesUpdateFloatAttribute({
|
|
482
|
+
databaseId,
|
|
483
|
+
collectionId,
|
|
484
|
+
key: attribute.key,
|
|
485
|
+
required: attribute.required,
|
|
486
|
+
min: parseFloat(attribute.min.toString()),
|
|
487
|
+
max: parseFloat(attribute.max.toString()),
|
|
488
|
+
xdefault: attribute.default,
|
|
489
|
+
array: attribute.array,
|
|
490
|
+
parseOutput: false
|
|
491
|
+
})
|
|
492
|
+
case 'boolean':
|
|
493
|
+
return databasesUpdateBooleanAttribute({
|
|
494
|
+
databaseId,
|
|
495
|
+
collectionId,
|
|
496
|
+
key: attribute.key,
|
|
497
|
+
required: attribute.required,
|
|
498
|
+
xdefault: attribute.default,
|
|
499
|
+
array: attribute.array,
|
|
500
|
+
parseOutput: false
|
|
501
|
+
})
|
|
502
|
+
case 'datetime':
|
|
503
|
+
return databasesUpdateDatetimeAttribute({
|
|
504
|
+
databaseId,
|
|
505
|
+
collectionId,
|
|
506
|
+
key: attribute.key,
|
|
507
|
+
required: attribute.required,
|
|
508
|
+
xdefault: attribute.default,
|
|
509
|
+
array: attribute.array,
|
|
510
|
+
parseOutput: false
|
|
511
|
+
})
|
|
512
|
+
case 'relationship':
|
|
513
|
+
return databasesUpdateRelationshipAttribute({
|
|
514
|
+
databaseId,
|
|
515
|
+
collectionId,
|
|
516
|
+
relatedCollectionId: attribute.relatedCollection,
|
|
517
|
+
type: attribute.relationType,
|
|
518
|
+
twoWay: attribute.twoWay,
|
|
519
|
+
key: attribute.key,
|
|
520
|
+
twoWayKey: attribute.twoWayKey,
|
|
521
|
+
onDelete: attribute.onDelete,
|
|
522
|
+
parseOutput: false
|
|
523
|
+
})
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const deleteAttribute = async (collection, attribute) => {
|
|
527
|
+
log(`Deleting attribute ${attribute.key} of ${collection.name} ( ${collection['$id']} )`);
|
|
528
|
+
|
|
529
|
+
await databasesDeleteAttribute({
|
|
530
|
+
databaseId: collection['databaseId'],
|
|
531
|
+
collectionId: collection['$id'],
|
|
532
|
+
key: attribute.key,
|
|
533
|
+
parseOutput: false
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Check if attribute non-changeable fields has been changed
|
|
540
|
+
* If so return the differences as an object.
|
|
541
|
+
* @param remote
|
|
542
|
+
* @param local
|
|
543
|
+
* @param collection
|
|
544
|
+
* @param recraeting when true will check only non-changeable keys
|
|
545
|
+
* @returns {undefined|{reason: string, action: *, attribute, key: string}}
|
|
546
|
+
*/
|
|
547
|
+
const checkAttributeChanges = (remote, local, collection, recraeting = true) => {
|
|
548
|
+
if (local === undefined) {
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const keyName = `${chalk.yellow(local.key)} in ${collection.name} (${collection['$id']})`;
|
|
553
|
+
const action = chalk.cyan(recraeting ? 'recreating' : 'changing');
|
|
554
|
+
let reason = '';
|
|
555
|
+
let attribute = remote;
|
|
556
|
+
|
|
557
|
+
for (let key of Object.keys(remote)) {
|
|
558
|
+
if (changeableKeys.includes(key)) {
|
|
559
|
+
if (!recraeting) {
|
|
560
|
+
if (remote[key] !== local[key]) {
|
|
561
|
+
const bol = reason === '' ? '' : '\n';
|
|
562
|
+
reason += `${bol}${key} changed from ${chalk.red(remote[key])} to ${chalk.green(local[key])}`;
|
|
563
|
+
attribute = local;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (!recraeting) {
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (remote[key] !== local[key]) {
|
|
574
|
+
const bol = reason === '' ? '' : '\n';
|
|
575
|
+
reason += `${bol}${key} changed from ${chalk.red(remote[key])} to ${chalk.green(local[key])}`;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return reason === '' ? undefined : { key: keyName, attribute, reason, action };
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Check if attributes contain the given attribute
|
|
584
|
+
* @param attribute
|
|
585
|
+
* @param attributes
|
|
586
|
+
* @returns {*}
|
|
587
|
+
*/
|
|
588
|
+
const attributesContains = (attribute, attributes) => attributes.find((attr) => attr.key === attribute.key);
|
|
589
|
+
const generateChangesObject = (attribute, collection, isAdding) => {
|
|
590
|
+
return {
|
|
591
|
+
key: `${chalk.yellow(attribute.key)} in ${collection.name} (${collection['$id']})`,
|
|
592
|
+
attribute: attribute,
|
|
593
|
+
reason: isAdding ? 'Field doesn\'t exist on the remote server' : 'Field doesn\'t exist in appwrite.json file',
|
|
594
|
+
action: isAdding ? chalk.green('adding') : chalk.red('deleting')
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Filter deleted and recreated attributes,
|
|
601
|
+
* return list of attributes to create
|
|
602
|
+
* @param remoteAttributes
|
|
603
|
+
* @param localAttributes
|
|
604
|
+
* @param collection
|
|
605
|
+
* @returns {Promise<*|*[]>}
|
|
606
|
+
*/
|
|
607
|
+
const attributesToCreate = async (remoteAttributes, localAttributes, collection) => {
|
|
608
|
+
|
|
609
|
+
const deleting = remoteAttributes.filter((attribute) => !attributesContains(attribute, localAttributes)).map((attr) => generateChangesObject(attr, collection, false));
|
|
610
|
+
const adding = localAttributes.filter((attribute) => !attributesContains(attribute, remoteAttributes)).map((attr) => generateChangesObject(attr, collection, true));
|
|
611
|
+
const conflicts = remoteAttributes.map((attribute) => checkAttributeChanges(attribute, attributesContains(attribute, localAttributes), collection)).filter(attribute => attribute !== undefined);
|
|
612
|
+
const changes = remoteAttributes.map((attribute) => checkAttributeChanges(attribute, attributesContains(attribute, localAttributes), collection, false))
|
|
613
|
+
.filter(attribute => attribute !== undefined)
|
|
614
|
+
.filter(attribute => conflicts.filter(attr => attribute.key === attr.key).length !== 1);
|
|
615
|
+
|
|
616
|
+
let changedAttributes = [];
|
|
617
|
+
const changing = [...deleting, ...adding, ...conflicts, ...changes]
|
|
618
|
+
if (changing.length === 0) {
|
|
619
|
+
return changedAttributes;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
log(!cliConfig.force ? 'There are pending changes in your collection deployment' : 'List of applied changes');
|
|
623
|
+
|
|
624
|
+
drawTable(changing.map((change) => {
|
|
625
|
+
return { Key: change.key, Action: change.action, Reason: change.reason, };
|
|
626
|
+
}));
|
|
627
|
+
|
|
628
|
+
if (!cliConfig.force) {
|
|
629
|
+
if (deleting.length > 0) {
|
|
630
|
+
log(`Attribute deletion will cause ${chalk.red('loss of data')}`);
|
|
631
|
+
}
|
|
632
|
+
if (conflicts.length > 0) {
|
|
633
|
+
log(`Attribute recreation will cause ${chalk.red('loss of data')}`);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const answers = await inquirer.prompt(questionsPushCollections[1]);
|
|
637
|
+
|
|
638
|
+
if (answers.changes.toLowerCase() !== 'yes') {
|
|
639
|
+
return changedAttributes;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (conflicts.length > 0) {
|
|
644
|
+
changedAttributes = conflicts.map((change) => change.attribute);
|
|
645
|
+
await Promise.all(changedAttributes.map((changed) => deleteAttribute(collection, changed)));
|
|
646
|
+
remoteAttributes = remoteAttributes.filter((attribute) => !attributesContains(attribute, changedAttributes))
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (changes.length > 0) {
|
|
650
|
+
changedAttributes = changes.map((change) => change.attribute);
|
|
651
|
+
await Promise.all(changedAttributes.map((changed) => updateAttribute(collection['databaseId'],collection['$id'], changed)));
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const deletingAttributes = deleting.map((change) => change.attribute);
|
|
655
|
+
await Promise.all(deletingAttributes.map((attribute) => deleteAttribute(collection, attribute)));
|
|
656
|
+
const attributeKeys = [...remoteAttributes.map(attribute => attribute.key), ...deletingAttributes.map(attribute => attribute.key)]
|
|
657
|
+
|
|
658
|
+
if (attributeKeys.length) {
|
|
659
|
+
const deleteAttributesPoolStatus = await awaitPools.deleteAttributes(collection['databaseId'], collection['$id'], attributeKeys);
|
|
660
|
+
|
|
661
|
+
if (!deleteAttributesPoolStatus) {
|
|
662
|
+
throw new Error("Attribute deletion timed out.");
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return localAttributes.filter((attribute) => !attributesContains(attribute, remoteAttributes));
|
|
667
|
+
}
|
|
668
|
+
const createIndexes = async (indexes, collection) => {
|
|
669
|
+
log(`Creating indexes ...`)
|
|
670
|
+
|
|
671
|
+
for (let index of indexes) {
|
|
672
|
+
await databasesCreateIndex({
|
|
673
|
+
databaseId: collection['databaseId'],
|
|
674
|
+
collectionId: collection['$id'],
|
|
675
|
+
key: index.key,
|
|
676
|
+
type: index.type,
|
|
677
|
+
attributes: index.attributes,
|
|
678
|
+
orders: index.orders,
|
|
679
|
+
parseOutput: false
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const result = await awaitPools.expectIndexes(
|
|
684
|
+
collection['databaseId'],
|
|
685
|
+
collection['$id'],
|
|
686
|
+
indexes.map(index => index.key)
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
if (!result) {
|
|
690
|
+
throw new Error("Index creation timed out.");
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
success(`Created ${indexes.length} indexes`);
|
|
694
|
+
}
|
|
695
|
+
const createAttributes = async (attributes, collection) => {
|
|
696
|
+
for (let attribute of attributes) {
|
|
697
|
+
if (attribute.side !== 'child') {
|
|
698
|
+
await createAttribute(collection['databaseId'], collection['$id'], attribute);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const result = await awaitPools.expectAttributes(
|
|
703
|
+
collection['databaseId'],
|
|
704
|
+
collection['$id'],
|
|
705
|
+
collection.attributes.map(attribute => attribute.key)
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
if (!result) {
|
|
709
|
+
throw new Error(`Attribute creation timed out.`);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
success(`Created ${attributes.length} attributes`);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const pushResources = async () => {
|
|
716
|
+
const actions = {
|
|
717
|
+
project: pushProject,
|
|
718
|
+
functions: pushFunction,
|
|
719
|
+
collections: pushCollection,
|
|
720
|
+
buckets: pushBucket,
|
|
721
|
+
teams: pushTeam,
|
|
722
|
+
messages: pushMessagingTopic
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (cliConfig.all) {
|
|
726
|
+
for (let action of Object.values(actions)) {
|
|
727
|
+
await action({ returnOnZero: true });
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
const answers = await inquirer.prompt(questionsPushResources[0]);
|
|
731
|
+
|
|
732
|
+
const action = actions[answers.resource];
|
|
733
|
+
if (action !== undefined) {
|
|
734
|
+
await action({ returnOnZero: true });
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
const pushProject = async () => {
|
|
740
|
+
try {
|
|
741
|
+
const projectId = localConfig.getProject().projectId;
|
|
742
|
+
const projectName = localConfig.getProject().projectName;
|
|
743
|
+
const settings = localConfig.getProject().projectSettings ?? {};
|
|
744
|
+
|
|
745
|
+
log(`Updating project ${projectId}`);
|
|
746
|
+
|
|
747
|
+
if (projectName) {
|
|
748
|
+
await projectsUpdate({
|
|
749
|
+
projectId,
|
|
750
|
+
name: projectName,
|
|
751
|
+
parseOutput: false
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (settings.services) {
|
|
756
|
+
log('Updating service statuses');
|
|
757
|
+
for (let [service, status] of Object.entries(settings.services)) {
|
|
758
|
+
await projectsUpdateServiceStatus({
|
|
759
|
+
projectId,
|
|
760
|
+
service,
|
|
761
|
+
status,
|
|
762
|
+
parseOutput: false
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (settings.auth) {
|
|
768
|
+
if (settings.auth.security) {
|
|
769
|
+
log('Updating auth security settings');
|
|
770
|
+
await projectsUpdateAuthDuration({ projectId, duration: settings.auth.security.duration, parseOutput: false });
|
|
771
|
+
await projectsUpdateAuthLimit({ projectId, limit: settings.auth.security.limit, parseOutput: false });
|
|
772
|
+
await projectsUpdateAuthSessionsLimit({ projectId, limit: settings.auth.security.sessionsLimit, parseOutput: false });
|
|
773
|
+
await projectsUpdateAuthPasswordDictionary({ projectId, enabled: settings.auth.security.passwordDictionary, parseOutput: false });
|
|
774
|
+
await projectsUpdateAuthPasswordHistory({ projectId, limit: settings.auth.security.passwordHistory, parseOutput: false });
|
|
775
|
+
await projectsUpdatePersonalDataCheck({ projectId, enabled: settings.auth.security.personalDataCheck, parseOutput: false });
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (settings.auth.methods) {
|
|
779
|
+
log('Updating auth login methods');
|
|
780
|
+
|
|
781
|
+
for (let [method, status] of Object.entries(settings.auth.methods)) {
|
|
782
|
+
await projectsUpdateAuthStatus({
|
|
783
|
+
projectId,
|
|
784
|
+
method,
|
|
785
|
+
status,
|
|
786
|
+
parseOutput: false
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
success("Project configuration updated.");
|
|
793
|
+
} catch (e) {
|
|
794
|
+
throw e;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero: false }) => {
|
|
799
|
+
let response = {};
|
|
800
|
+
|
|
801
|
+
const functionIds = [];
|
|
802
|
+
|
|
803
|
+
if (functionId) {
|
|
804
|
+
functionIds.push(functionId);
|
|
805
|
+
} else if (cliConfig.all) {
|
|
806
|
+
checkDeployConditions(localConfig);
|
|
807
|
+
const functions = localConfig.getFunctions();
|
|
808
|
+
if (functions.length === 0) {
|
|
809
|
+
if (returnOnZero) {
|
|
810
|
+
log('No functions found, skipping');
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
throw new Error("No functions found in the current directory. Use 'appwrite pull functions' to synchronize existing one, or use 'appwrite init function' to create a new one.");
|
|
814
|
+
}
|
|
815
|
+
functionIds.push(...functions.map((func) => {
|
|
816
|
+
return func.$id;
|
|
817
|
+
}));
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (functionIds.length <= 0) {
|
|
821
|
+
const answers = await inquirer.prompt(questionsPushFunctions[0]);
|
|
822
|
+
functionIds.push(...answers.functions);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
let functions = functionIds.map((id) => {
|
|
826
|
+
const functions = localConfig.getFunctions();
|
|
827
|
+
const func = functions.find((f) => f.$id === id);
|
|
828
|
+
|
|
829
|
+
if (!func) {
|
|
830
|
+
throw new Error("Function '" + id + "' not found.")
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return func;
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
log('Validating functions');
|
|
837
|
+
// Validation is done BEFORE pushing so the deployment process can be run in async with progress update
|
|
838
|
+
for (let func of functions) {
|
|
839
|
+
|
|
840
|
+
if (!func.entrypoint) {
|
|
841
|
+
log(`Function ${func.name} does not have an endpoint`);
|
|
842
|
+
const answers = await inquirer.prompt(questionsGetEntrypoint)
|
|
843
|
+
func.entrypoint = answers.entrypoint;
|
|
844
|
+
localConfig.updateFunction(func['$id'], func);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (func.variables) {
|
|
848
|
+
func.pushVariables = cliConfig.force;
|
|
849
|
+
|
|
850
|
+
try {
|
|
851
|
+
const { total } = await functionsListVariables({
|
|
852
|
+
functionId: func['$id'],
|
|
853
|
+
queries: [JSON.stringify({ method: 'limit', values: [1] })],
|
|
854
|
+
parseOutput: false
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
if (total === 0) {
|
|
858
|
+
func.pushVariables = true;
|
|
859
|
+
} else if (total > 0 && !func.pushVariables) {
|
|
860
|
+
log(`The function ${func.name} has remote variables setup`);
|
|
861
|
+
const variableAnswers = await inquirer.prompt(questionsPushFunctions[1])
|
|
862
|
+
func.pushVariables = variableAnswers.override.toLowerCase() === "yes";
|
|
863
|
+
}
|
|
864
|
+
} catch (e) {
|
|
865
|
+
if (e.code != 404) {
|
|
866
|
+
throw e.message;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
log('All functions are validated');
|
|
874
|
+
log('Pushing functions\n');
|
|
875
|
+
|
|
876
|
+
Spinner.start(false);
|
|
877
|
+
let successfullyPushed = 0;
|
|
878
|
+
let successfullyDeployed = 0;
|
|
879
|
+
const failedDeployments = [];
|
|
880
|
+
|
|
881
|
+
await Promise.all(functions.map(async (func) => {
|
|
882
|
+
const ignore = func.ignore ? 'appwrite.json' : '.gitignore';
|
|
883
|
+
let functionExists = false;
|
|
884
|
+
let deploymentCreated = false;
|
|
885
|
+
|
|
886
|
+
const updaterRow = new Spinner({ status: '', resource: func.name, id: func['$id'], end: `Ignoring using: ${ignore}` });
|
|
887
|
+
|
|
888
|
+
updaterRow.update({ status: 'Getting' }).startSpinner(SPINNER_DOTS);
|
|
889
|
+
|
|
890
|
+
try {
|
|
891
|
+
response = await functionsGet({
|
|
892
|
+
functionId: func['$id'],
|
|
893
|
+
parseOutput: false,
|
|
894
|
+
});
|
|
895
|
+
functionExists = true;
|
|
896
|
+
if (response.runtime !== func.runtime) {
|
|
897
|
+
updaterRow.fail({ errorMessage: `Runtime mismatch! (local=${func.runtime},remote=${response.runtime}) Please delete remote function or update your appwrite.json` })
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
updaterRow.update({ status: 'Updating' }).replaceSpinner(SPINNER_ARC);
|
|
902
|
+
|
|
903
|
+
response = await functionsUpdate({
|
|
904
|
+
functionId: func['$id'],
|
|
905
|
+
name: func.name,
|
|
906
|
+
execute: func.execute,
|
|
907
|
+
events: func.events,
|
|
908
|
+
schedule: func.schedule,
|
|
909
|
+
timeout: func.timeout,
|
|
910
|
+
enabled: func.enabled,
|
|
911
|
+
logging: func.logging,
|
|
912
|
+
entrypoint: func.entrypoint,
|
|
913
|
+
commands: func.commands,
|
|
914
|
+
providerRepositoryId: func.providerRepositoryId ?? "",
|
|
915
|
+
installationId: func.installationId ?? '',
|
|
916
|
+
providerBranch: func.providerBranch ?? '',
|
|
917
|
+
providerRootDirectory: func.providerRootDirectory ?? '',
|
|
918
|
+
providerSilentMode: func.providerSilentMode ?? false,
|
|
919
|
+
vars: JSON.stringify(response.vars),
|
|
920
|
+
parseOutput: false
|
|
921
|
+
});
|
|
922
|
+
} catch (e) {
|
|
923
|
+
|
|
924
|
+
if (Number(e.code) === 404) {
|
|
925
|
+
functionExists = false;
|
|
926
|
+
} else {
|
|
927
|
+
updaterRow.fail({ errorMessage: e.message ?? 'General error occurs please try again' });
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if (!functionExists) {
|
|
933
|
+
updaterRow.update({ status: 'Creating' }).replaceSpinner(SPINNER_DOTS);
|
|
934
|
+
|
|
935
|
+
try {
|
|
936
|
+
response = await functionsCreate({
|
|
937
|
+
functionId: func.$id || 'unique()',
|
|
938
|
+
name: func.name,
|
|
939
|
+
runtime: func.runtime,
|
|
940
|
+
execute: func.execute,
|
|
941
|
+
events: func.events,
|
|
942
|
+
schedule: func.schedule,
|
|
943
|
+
timeout: func.timeout,
|
|
944
|
+
enabled: func.enabled,
|
|
945
|
+
logging: func.logging,
|
|
946
|
+
entrypoint: func.entrypoint,
|
|
947
|
+
commands: func.commands,
|
|
948
|
+
vars: JSON.stringify(func.vars),
|
|
949
|
+
parseOutput: false
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
localConfig.updateFunction(func['$id'], {
|
|
953
|
+
"$id": response['$id'],
|
|
954
|
+
});
|
|
955
|
+
func["$id"] = response['$id'];
|
|
956
|
+
updaterRow.update({ status: 'Created' });
|
|
957
|
+
} catch (e) {
|
|
958
|
+
updaterRow.fail({ errorMessage: e.message ?? 'General error occurs please try again' });
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (func.variables) {
|
|
964
|
+
if (!func.pushVariables) {
|
|
965
|
+
updaterRow.update({ end: 'Skipping variables' });
|
|
966
|
+
} else {
|
|
967
|
+
updaterRow.update({ end: 'Pushing variables' });
|
|
968
|
+
|
|
969
|
+
const { variables } = await paginate(functionsListVariables, {
|
|
970
|
+
functionId: func['$id'],
|
|
971
|
+
parseOutput: false
|
|
972
|
+
}, 100, 'variables');
|
|
973
|
+
|
|
974
|
+
await Promise.all(variables.map(async variable => {
|
|
975
|
+
await functionsDeleteVariable({
|
|
976
|
+
functionId: func['$id'],
|
|
977
|
+
variableId: variable['$id'],
|
|
978
|
+
parseOutput: false
|
|
979
|
+
});
|
|
980
|
+
}));
|
|
981
|
+
|
|
982
|
+
let result = await awaitPools.wipeVariables(func['$id']);
|
|
983
|
+
if (!result) {
|
|
984
|
+
updaterRow.fail({ errorMessage: 'Variable deletion timed out' })
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Push local variables
|
|
989
|
+
await Promise.all(Object.keys(func.variables).map(async localVariableKey => {
|
|
990
|
+
await functionsCreateVariable({
|
|
991
|
+
functionId: func['$id'],
|
|
992
|
+
key: localVariableKey,
|
|
993
|
+
value: func.variables[localVariableKey],
|
|
994
|
+
parseOutput: false
|
|
995
|
+
});
|
|
996
|
+
}));
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
try {
|
|
1001
|
+
updaterRow.update({ status: 'Pushing' }).replaceSpinner(SPINNER_ARC);
|
|
1002
|
+
response = await functionsCreateDeployment({
|
|
1003
|
+
functionId: func['$id'],
|
|
1004
|
+
entrypoint: func.entrypoint,
|
|
1005
|
+
commands: func.commands,
|
|
1006
|
+
code: func.path,
|
|
1007
|
+
activate: true,
|
|
1008
|
+
parseOutput: false
|
|
1009
|
+
})
|
|
1010
|
+
|
|
1011
|
+
updaterRow.update({ status: 'Pushed' });
|
|
1012
|
+
deploymentCreated = true;
|
|
1013
|
+
successfullyPushed++;
|
|
1014
|
+
} catch (e) {
|
|
1015
|
+
switch (e.code) {
|
|
1016
|
+
case 'ENOENT':
|
|
1017
|
+
updaterRow.fail({ errorMessage: 'Not found in the current directory. Skipping...' })
|
|
1018
|
+
break;
|
|
1019
|
+
default:
|
|
1020
|
+
updaterRow.fail({ errorMessage: e.message ?? 'An unknown error occurred. Please try again.' })
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (deploymentCreated && !async) {
|
|
1025
|
+
try {
|
|
1026
|
+
const deploymentId = response['$id'];
|
|
1027
|
+
updaterRow.update({ status: 'Deploying', end: 'Checking deployment status...' })
|
|
1028
|
+
let pollChecks = 0;
|
|
1029
|
+
|
|
1030
|
+
while (true) {
|
|
1031
|
+
if (pollChecks >= POLL_MAX_DEBOUNCE) {
|
|
1032
|
+
updaterRow.update({ end: 'Deployment is taking too long. Please check the console for more details.' })
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
response = await functionsGetDeployment({
|
|
1037
|
+
functionId: func['$id'],
|
|
1038
|
+
deploymentId: deploymentId,
|
|
1039
|
+
parseOutput: false
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
const status = response['status'];
|
|
1044
|
+
if (status === 'ready') {
|
|
1045
|
+
successfullyDeployed++;
|
|
1046
|
+
|
|
1047
|
+
let url = '';
|
|
1048
|
+
const res = await proxyListRules({
|
|
1049
|
+
parseOutput: false,
|
|
1050
|
+
queries: [
|
|
1051
|
+
JSON.stringify({ method: 'limit', values: [1] }),
|
|
1052
|
+
JSON.stringify({ method: 'equal', "attribute": "resourceType", "values": ["function"] }),
|
|
1053
|
+
JSON.stringify({ method: 'equal', "attribute": "resourceId", "values": [func['$id']] })
|
|
1054
|
+
],
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
if (Number(res.total) === 1) {
|
|
1058
|
+
url = res.rules[0].domain;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
updaterRow.update({ status: 'Deployed', end: url });
|
|
1062
|
+
|
|
1063
|
+
break;
|
|
1064
|
+
} else if (status === 'failed') {
|
|
1065
|
+
failedDeployments.push({ name: func['name'], $id: func['$id'], deployment: response['$id'] });
|
|
1066
|
+
updaterRow.fail({ errorMessage: `Failed to deploy` });
|
|
1067
|
+
|
|
1068
|
+
break;
|
|
1069
|
+
} else {
|
|
1070
|
+
updaterRow.update({ status: 'Deploying', end: `Current status: ${status}` })
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
pollChecks++;
|
|
1074
|
+
await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
|
|
1075
|
+
}
|
|
1076
|
+
} catch (e) {
|
|
1077
|
+
updaterRow.fail({ errorMessage: e.message ?? 'Unknown error occurred. Please try again' })
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
updaterRow.stopSpinner();
|
|
1082
|
+
}));
|
|
1083
|
+
|
|
1084
|
+
Spinner.stop();
|
|
1085
|
+
console.log('\n');
|
|
1086
|
+
|
|
1087
|
+
failedDeployments.forEach((failed) => {
|
|
1088
|
+
const { name, deployment, $id } = failed;
|
|
1089
|
+
const failUrl = `${globalConfig.getEndpoint().replace('/v1', '')}/console/project-${localConfig.getProject().projectId}/functions/function-${$id}/deployment-${deployment}`;
|
|
1090
|
+
|
|
1091
|
+
error(`Deployment of ${name} has failed. Check at ${failUrl} for more details\n`);
|
|
1092
|
+
})
|
|
1093
|
+
|
|
1094
|
+
let message = chalk.green(`Pushed and deployed ${successfullyPushed} functions`);
|
|
1095
|
+
|
|
1096
|
+
if (!async) {
|
|
1097
|
+
if (successfullyDeployed < successfullyPushed) {
|
|
1098
|
+
message = `${chalk.green(`Pushed and deployed ${successfullyPushed} functions.`)} ${chalk.red(`${successfullyPushed - successfullyDeployed} failed to deploy`)}`;
|
|
1099
|
+
} else {
|
|
1100
|
+
if (successfullyPushed === 0) {
|
|
1101
|
+
message = chalk.red(`Error pushing ${functions.length} functions`)
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
log(message);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
const pushCollection = async ({ returnOnZero } = { returnOnZero: false }) => {
|
|
1109
|
+
const collections = [];
|
|
1110
|
+
|
|
1111
|
+
if (cliConfig.all) {
|
|
1112
|
+
checkDeployConditions(localConfig);
|
|
1113
|
+
if (localConfig.getCollections().length === 0) {
|
|
1114
|
+
if (returnOnZero) {
|
|
1115
|
+
log('No collections found, skipping');
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
throw new Error("No collections found in the current directory. Use 'appwrite pull collections' to synchronize existing one, or use 'appwrite init collection' to create a new one.");
|
|
1120
|
+
}
|
|
1121
|
+
collections.push(...localConfig.getCollections());
|
|
1122
|
+
} else {
|
|
1123
|
+
const answers = await inquirer.prompt(questionsPushCollections[0])
|
|
1124
|
+
const configCollections = new Map();
|
|
1125
|
+
localConfig.getCollections().forEach((c) => {
|
|
1126
|
+
configCollections.set(`${c['databaseId']}|${c['$id']}`, c);
|
|
1127
|
+
});
|
|
1128
|
+
answers.collections.forEach((a) => {
|
|
1129
|
+
const collection = configCollections.get(a);
|
|
1130
|
+
collections.push(collection);
|
|
1131
|
+
})
|
|
1132
|
+
}
|
|
1133
|
+
const databases = Array.from(new Set(collections.map(collection => collection['databaseId'])));
|
|
1134
|
+
log('Checking for databases and collection changes');
|
|
1135
|
+
|
|
1136
|
+
// Parallel db actions
|
|
1137
|
+
await Promise.all(databases.map(async (databaseId) => {
|
|
1138
|
+
const localDatabase = localConfig.getDatabase(databaseId);
|
|
1139
|
+
|
|
1140
|
+
try {
|
|
1141
|
+
const database = await databasesGet({
|
|
1142
|
+
databaseId: databaseId,
|
|
1143
|
+
parseOutput: false,
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
if (database.name !== (localDatabase.name ?? databaseId)) {
|
|
1147
|
+
await databasesUpdate({
|
|
1148
|
+
databaseId: databaseId,
|
|
1149
|
+
name: localDatabase.name ?? databaseId,
|
|
1150
|
+
parseOutput: false
|
|
1151
|
+
})
|
|
1152
|
+
|
|
1153
|
+
success(`Updated ${localDatabase.name} ( ${databaseId} ) name`);
|
|
1154
|
+
}
|
|
1155
|
+
} catch (err) {
|
|
1156
|
+
log(`Database ${databaseId} not found. Creating it now...`);
|
|
1157
|
+
|
|
1158
|
+
await databasesCreate({
|
|
1159
|
+
databaseId: databaseId,
|
|
1160
|
+
name: localDatabase.name ?? databaseId,
|
|
1161
|
+
parseOutput: false,
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
}));
|
|
1165
|
+
|
|
1166
|
+
// Parallel collection actions
|
|
1167
|
+
await Promise.all(collections.map(async (collection) => {
|
|
1168
|
+
try {
|
|
1169
|
+
const remoteCollection = await databasesGetCollection({
|
|
1170
|
+
databaseId: collection['databaseId'],
|
|
1171
|
+
collectionId: collection['$id'],
|
|
1172
|
+
parseOutput: false,
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
if (remoteCollection.name !== collection.name) {
|
|
1176
|
+
await databasesUpdateCollection({
|
|
1177
|
+
databaseId: collection['databaseId'],
|
|
1178
|
+
collectionId: collection['$id'],
|
|
1179
|
+
name: collection.name,
|
|
1180
|
+
name: collection.name,
|
|
1181
|
+
parseOutput: false
|
|
1182
|
+
})
|
|
1183
|
+
|
|
1184
|
+
success(`Updated ${collection.name} ( ${collection['$id']} ) name`);
|
|
1185
|
+
}
|
|
1186
|
+
collection.remoteVersion = remoteCollection;
|
|
1187
|
+
|
|
1188
|
+
collection.isExisted = true;
|
|
1189
|
+
} catch
|
|
1190
|
+
(e) {
|
|
1191
|
+
if (Number(e.code) === 404) {
|
|
1192
|
+
log(`Collection ${collection.name} does not exist in the project. Creating ... `);
|
|
1193
|
+
await databasesCreateCollection({
|
|
1194
|
+
databaseId: collection['databaseId'],
|
|
1195
|
+
collectionId: collection['$id'],
|
|
1196
|
+
name: collection.name,
|
|
1197
|
+
documentSecurity: collection.documentSecurity,
|
|
1198
|
+
permissions: collection['$permissions'],
|
|
1199
|
+
parseOutput: false
|
|
1200
|
+
})
|
|
1201
|
+
} else {
|
|
1202
|
+
throw e;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}))
|
|
1206
|
+
|
|
1207
|
+
// Serialize attribute actions
|
|
1208
|
+
for (let collection of collections) {
|
|
1209
|
+
let attributes = collection.attributes;
|
|
1210
|
+
|
|
1211
|
+
if (collection.isExisted) {
|
|
1212
|
+
attributes = await attributesToCreate(collection.remoteVersion.attributes, collection.attributes, collection);
|
|
1213
|
+
|
|
1214
|
+
if (Array.isArray(attributes) && attributes.length <= 0) {
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
log(`Pushing collection ${collection.name} ( ${collection['databaseId']} - ${collection['$id']} ) attributes`)
|
|
1220
|
+
|
|
1221
|
+
try {
|
|
1222
|
+
await createAttributes(attributes, collection)
|
|
1223
|
+
} catch (e) {
|
|
1224
|
+
throw e;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
try {
|
|
1228
|
+
await createIndexes(collection.indexes, collection);
|
|
1229
|
+
} catch (e) {
|
|
1230
|
+
throw e;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
success(`Pushed ${collection.name} ( ${collection['$id']} )`);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const pushBucket = async ({ returnOnZero } = { returnOnZero: false }) => {
|
|
1238
|
+
let response = {};
|
|
1239
|
+
|
|
1240
|
+
let bucketIds = [];
|
|
1241
|
+
const configBuckets = localConfig.getBuckets();
|
|
1242
|
+
|
|
1243
|
+
if (cliConfig.all) {
|
|
1244
|
+
checkDeployConditions(localConfig);
|
|
1245
|
+
if (configBuckets.length === 0) {
|
|
1246
|
+
if (returnOnZero) {
|
|
1247
|
+
log('No buckets found, skipping');
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
throw new Error("No buckets found in the current directory. Use 'appwrite pull buckets' to synchronize existing one, or use 'appwrite init bucket' to create a new one.");
|
|
1251
|
+
}
|
|
1252
|
+
bucketIds.push(...configBuckets.map((b) => b.$id));
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
if (bucketIds.length === 0) {
|
|
1256
|
+
const answers = await inquirer.prompt(questionsPushBuckets[0])
|
|
1257
|
+
bucketIds.push(...answers.buckets);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
let buckets = [];
|
|
1261
|
+
|
|
1262
|
+
for (const bucketId of bucketIds) {
|
|
1263
|
+
const idBuckets = configBuckets.filter((b) => b.$id === bucketId);
|
|
1264
|
+
buckets.push(...idBuckets);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
for (let bucket of buckets) {
|
|
1268
|
+
log(`Pushing bucket ${bucket.name} ( ${bucket['$id']} )`)
|
|
1269
|
+
|
|
1270
|
+
try {
|
|
1271
|
+
response = await storageGetBucket({
|
|
1272
|
+
bucketId: bucket['$id'],
|
|
1273
|
+
parseOutput: false,
|
|
1274
|
+
})
|
|
1275
|
+
|
|
1276
|
+
log(`Updating bucket ...`)
|
|
1277
|
+
|
|
1278
|
+
await storageUpdateBucket({
|
|
1279
|
+
bucketId: bucket['$id'],
|
|
1280
|
+
name: bucket.name,
|
|
1281
|
+
permissions: bucket['$permissions'],
|
|
1282
|
+
fileSecurity: bucket.fileSecurity,
|
|
1283
|
+
enabled: bucket.enabled,
|
|
1284
|
+
maximumFileSize: bucket.maximumFileSize,
|
|
1285
|
+
allowedFileExtensions: bucket.allowedFileExtensions,
|
|
1286
|
+
encryption: bucket.encryption,
|
|
1287
|
+
antivirus: bucket.antivirus,
|
|
1288
|
+
compression: bucket.compression,
|
|
1289
|
+
parseOutput: false
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
success(`Pushed ${bucket.name} ( ${bucket['$id']} )`);
|
|
1293
|
+
} catch (e) {
|
|
1294
|
+
if (Number(e.code) === 404) {
|
|
1295
|
+
log(`Bucket ${bucket.name} does not exist in the project. Creating ... `);
|
|
1296
|
+
|
|
1297
|
+
response = await storageCreateBucket({
|
|
1298
|
+
bucketId: bucket['$id'],
|
|
1299
|
+
name: bucket.name,
|
|
1300
|
+
permissions: bucket['$permissions'],
|
|
1301
|
+
fileSecurity: bucket.fileSecurity,
|
|
1302
|
+
enabled: bucket.enabled,
|
|
1303
|
+
maximumFileSize: bucket.maximumFileSize,
|
|
1304
|
+
allowedFileExtensions: bucket.allowedFileExtensions,
|
|
1305
|
+
compression: bucket.compression,
|
|
1306
|
+
encryption: bucket.encryption,
|
|
1307
|
+
antivirus: bucket.antivirus,
|
|
1308
|
+
parseOutput: false
|
|
1309
|
+
})
|
|
1310
|
+
|
|
1311
|
+
success(`Pushed ${bucket.name} ( ${bucket['$id']} )`);
|
|
1312
|
+
} else {
|
|
1313
|
+
throw e;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
const pushTeam = async ({ returnOnZero } = { returnOnZero: false }) => {
|
|
1320
|
+
let response = {};
|
|
1321
|
+
|
|
1322
|
+
let teamIds = [];
|
|
1323
|
+
const configTeams = localConfig.getTeams();
|
|
1324
|
+
|
|
1325
|
+
if (cliConfig.all) {
|
|
1326
|
+
checkDeployConditions(localConfig);
|
|
1327
|
+
if (configTeams.length === 0) {
|
|
1328
|
+
if (returnOnZero) {
|
|
1329
|
+
log('No teams found, skipping');
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
throw new Error("No teams found in the current directory. Use 'appwrite pull teams' to synchronize existing one, or use 'appwrite init team' to create a new one.");
|
|
1333
|
+
}
|
|
1334
|
+
teamIds.push(...configTeams.map((t) => t.$id));
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
if (teamIds.length === 0) {
|
|
1338
|
+
const answers = await inquirer.prompt(questionsPushTeams[0])
|
|
1339
|
+
teamIds.push(...answers.teams);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
let teams = [];
|
|
1343
|
+
|
|
1344
|
+
for (const teamId of teamIds) {
|
|
1345
|
+
const idTeams = configTeams.filter((t) => t.$id === teamId);
|
|
1346
|
+
teams.push(...idTeams);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
for (let team of teams) {
|
|
1350
|
+
log(`Pushing team ${team.name} ( ${team['$id']} )`)
|
|
1351
|
+
|
|
1352
|
+
try {
|
|
1353
|
+
response = await teamsGet({
|
|
1354
|
+
teamId: team['$id'],
|
|
1355
|
+
parseOutput: false,
|
|
1356
|
+
})
|
|
1357
|
+
|
|
1358
|
+
log(`Updating team ...`)
|
|
1359
|
+
|
|
1360
|
+
await teamsUpdateName({
|
|
1361
|
+
teamId: team['$id'],
|
|
1362
|
+
name: team.name,
|
|
1363
|
+
parseOutput: false
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
success(`Pushed ${team.name} ( ${team['$id']} )`);
|
|
1367
|
+
} catch (e) {
|
|
1368
|
+
if (Number(e.code) === 404) {
|
|
1369
|
+
log(`Team ${team.name} does not exist in the project. Creating ... `);
|
|
1370
|
+
|
|
1371
|
+
response = await teamsCreate({
|
|
1372
|
+
teamId: team['$id'],
|
|
1373
|
+
name: team.name,
|
|
1374
|
+
parseOutput: false
|
|
1375
|
+
})
|
|
1376
|
+
|
|
1377
|
+
success(`Pushed ${team.name} ( ${team['$id']} )`);
|
|
1378
|
+
} else {
|
|
1379
|
+
throw e;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
const pushMessagingTopic = async ({ returnOnZero } = { returnOnZero: false }) => {
|
|
1386
|
+
let response = {};
|
|
1387
|
+
|
|
1388
|
+
let topicsIds = [];
|
|
1389
|
+
const configTopics = localConfig.getMessagingTopics();
|
|
1390
|
+
let overrideExisting = cliConfig.force;
|
|
1391
|
+
|
|
1392
|
+
if (cliConfig.all) {
|
|
1393
|
+
checkDeployConditions(localConfig);
|
|
1394
|
+
if (configTopics.length === 0) {
|
|
1395
|
+
if (returnOnZero) {
|
|
1396
|
+
log('No topics found, skipping');
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
throw new Error("No topics found in the current directory. Use 'appwrite pull topics' to synchronize existing one, or use 'appwrite init topic' to create a new one.");
|
|
1400
|
+
}
|
|
1401
|
+
topicsIds.push(...configTopics.map((b) => b.$id));
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
if (topicsIds.length === 0) {
|
|
1405
|
+
const answers = await inquirer.prompt(questionsPushMessagingTopics[0])
|
|
1406
|
+
topicsIds.push(...answers.topics);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
let topics = [];
|
|
1410
|
+
|
|
1411
|
+
for (const topicId of topicsIds) {
|
|
1412
|
+
const idTopic = configTopics.filter((b) => b.$id === topicId);
|
|
1413
|
+
topics.push(...idTopic);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
if (!cliConfig.force) {
|
|
1417
|
+
const answers = await inquirer.prompt(questionsPushMessagingTopics[1])
|
|
1418
|
+
if (answers.override.toLowerCase() === "yes") {
|
|
1419
|
+
overrideExisting = true;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
for (let topic of topics) {
|
|
1424
|
+
log(`Pushing topic ${topic.name} ( ${topic['$id']} )`)
|
|
1425
|
+
|
|
1426
|
+
try {
|
|
1427
|
+
response = await messagingGetTopic({
|
|
1428
|
+
topicId: topic['$id'],
|
|
1429
|
+
parseOutput: false
|
|
1430
|
+
})
|
|
1431
|
+
log(`Topic ${topic.name} ( ${topic['$id']} ) already exists.`);
|
|
1432
|
+
|
|
1433
|
+
if (!overrideExisting) {
|
|
1434
|
+
log(`Skipping ${topic.name} ( ${topic['$id']} )`);
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
log(`Updating Topic ...`)
|
|
1439
|
+
|
|
1440
|
+
await messagingUpdateTopic({
|
|
1441
|
+
topicId: topic['$id'],
|
|
1442
|
+
name: topic.name,
|
|
1443
|
+
subscribe: topic.subscribe,
|
|
1444
|
+
parseOutput: false
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
success(`Pushed ${topic.name} ( ${topic['$id']} )`);
|
|
1448
|
+
} catch (e) {
|
|
1449
|
+
if (Number(e.code) === 404) {
|
|
1450
|
+
log(`Topic ${topic.name} does not exist in the project. Creating ... `);
|
|
1451
|
+
|
|
1452
|
+
response = await messagingCreateTopic({
|
|
1453
|
+
topicId: topic['$id'],
|
|
1454
|
+
name: topic.name,
|
|
1455
|
+
subscribe: topic.subscribe,
|
|
1456
|
+
parseOutput: false
|
|
1457
|
+
})
|
|
1458
|
+
|
|
1459
|
+
success(`Created ${topic.name} ( ${topic['$id']} )`);
|
|
1460
|
+
} else {
|
|
1461
|
+
throw e;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
const push = new Command("push")
|
|
1468
|
+
.description(commandDescriptions['push'])
|
|
1469
|
+
.action(actionRunner(pushResources));
|
|
1470
|
+
|
|
1471
|
+
push
|
|
1472
|
+
.command("all")
|
|
1473
|
+
.description("Push all resource.")
|
|
1474
|
+
.action(actionRunner(() => {
|
|
1475
|
+
cliConfig.all = true;
|
|
1476
|
+
return pushResources();
|
|
1477
|
+
}));
|
|
1478
|
+
|
|
1479
|
+
push
|
|
1480
|
+
.command("project")
|
|
1481
|
+
.description("Push project name, services and auth settings")
|
|
1482
|
+
.action(actionRunner(pushProject));
|
|
1483
|
+
|
|
1484
|
+
push
|
|
1485
|
+
.command("function")
|
|
1486
|
+
.alias("functions")
|
|
1487
|
+
.description("Push functions in the current directory.")
|
|
1488
|
+
.option(`-f, --functionId <functionId>`, `Function ID`)
|
|
1489
|
+
.option(`-A, --async`, `Don't wait for functions deployments status`)
|
|
1490
|
+
.action(actionRunner(pushFunction));
|
|
1491
|
+
|
|
1492
|
+
push
|
|
1493
|
+
.command("collection")
|
|
1494
|
+
.alias("collections")
|
|
1495
|
+
.description("Push collections in the current project.")
|
|
1496
|
+
.action(actionRunner(pushCollection));
|
|
1497
|
+
|
|
1498
|
+
push
|
|
1499
|
+
.command("bucket")
|
|
1500
|
+
.alias("buckets")
|
|
1501
|
+
.description("Push buckets in the current project.")
|
|
1502
|
+
.action(actionRunner(pushBucket));
|
|
1503
|
+
|
|
1504
|
+
push
|
|
1505
|
+
.command("team")
|
|
1506
|
+
.alias("teams")
|
|
1507
|
+
.description("Push teams in the current project.")
|
|
1508
|
+
.action(actionRunner(pushTeam));
|
|
1509
|
+
|
|
1510
|
+
push
|
|
1511
|
+
.command("topic")
|
|
1512
|
+
.alias("topics")
|
|
1513
|
+
.description("Push messaging topics in the current project.")
|
|
1514
|
+
.action(actionRunner(pushMessagingTopic));
|
|
1515
|
+
|
|
1516
|
+
module.exports = {
|
|
1517
|
+
push
|
|
1518
|
+
}
|