@getxflow/cli 0.10.0 → 0.10.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/dist/bin.js +26 -1
- package/dist/commands/skills.js +119 -21
- package/dist/commands/update.js +6 -4
- package/dist/help.js +453 -433
- package/dist/state.js +36 -2
- package/dist/version.js +1 -1
- package/package.json +24 -24
- package/skills/xflow/SKILL.md +552 -540
package/skills/xflow/SKILL.md
CHANGED
|
@@ -1,540 +1,552 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: xflow
|
|
3
|
-
description: Build, deploy and publish apps on the XFlow platform with the xflow CLI. Use whenever the project root has xflow.json or VITE_XFLOW_* variables, and for any task that touches deployment, publishing, rollback, source sync, the project database or SQL migrations, cloud functions, schedules, environment variables and secrets, production errors and logs, or UI built on the platform design system.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# XFlow
|
|
7
|
-
|
|
8
|
-
Hosting for web apps. The code lives in an ordinary repository on the developer
|
|
9
|
-
machine, `xflow deploy` sends the sources, and the platform builds them in a clean
|
|
10
|
-
sandbox and serves the result as static files. A platform project is recognized by
|
|
11
|
-
the `xflow.json` file in its root.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
a
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
the
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
- No
|
|
152
|
-
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
it
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
and
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
`xflow
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
xflow
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
xflow
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
- `
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
`
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
`
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
1
|
+
---
|
|
2
|
+
name: xflow
|
|
3
|
+
description: Build, deploy and publish apps on the XFlow platform with the xflow CLI. Use whenever the project root has xflow.json or VITE_XFLOW_* variables, and for any task that touches deployment, publishing, rollback, source sync, the project database or SQL migrations, cloud functions, schedules, environment variables and secrets, file storage and uploads of photos, video or PDFs, connected accounts of external services, production errors and logs, or UI built on the platform design system.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# XFlow
|
|
7
|
+
|
|
8
|
+
Hosting for web apps. The code lives in an ordinary repository on the developer
|
|
9
|
+
machine, `xflow deploy` sends the sources, and the platform builds them in a clean
|
|
10
|
+
sandbox and serves the result as static files. A platform project is recognized by
|
|
11
|
+
the `xflow.json` file in its root.
|
|
12
|
+
|
|
13
|
+
No `xflow.json` means the folder is not bound to a project yet. `xflow projects list`
|
|
14
|
+
names the projects of the organization with their ids, `xflow link <id>` binds the
|
|
15
|
+
folder to one of them (an empty folder also gets the sources), and `xflow init` starts
|
|
16
|
+
a new project from the platform template.
|
|
17
|
+
|
|
18
|
+
## First rule
|
|
19
|
+
|
|
20
|
+
Check commands and flags against `xflow help` and `xflow help <command>`, not against
|
|
21
|
+
memory. If a command is not in the help output, it does not exist: guessing flags is
|
|
22
|
+
pointless. The CLI prints a hint with almost every error, read it in full, it usually
|
|
23
|
+
contains the fix.
|
|
24
|
+
|
|
25
|
+
## Keeping these instructions current
|
|
26
|
+
|
|
27
|
+
These instructions ship with xflow CLI 0.10.1. They travel inside the package, so the copy
|
|
28
|
+
you are reading can be older than the CLI answering your commands, and nothing about that
|
|
29
|
+
is visible in the text itself.
|
|
30
|
+
|
|
31
|
+
When the CLI says this file is out of date, run `xflow skills --refresh` and then **read
|
|
32
|
+
again the file it names**. Refreshing only writes new text to disk; until you re-read it you
|
|
33
|
+
are still working from the old rules, and the command will look like it did nothing. This
|
|
34
|
+
repair is yours to make, no need to ask anyone.
|
|
35
|
+
|
|
36
|
+
Updating the CLI itself is a different matter and not yours by default. `xflow update`
|
|
37
|
+
replaces a program shared by every project on the machine, and in most installations it
|
|
38
|
+
cannot even do that: it prints the command for a person instead. So when a newer version is
|
|
39
|
+
merely available, finish the task and offer the update in your summary. The one exception is
|
|
40
|
+
a platform refusing to work with the CLI as too old: nothing runs until it is updated, so say
|
|
41
|
+
what you are doing and run it.
|
|
42
|
+
|
|
43
|
+
## Hard rules
|
|
44
|
+
|
|
45
|
+
These mistakes cost the most because nothing fails at the moment they are made, or
|
|
46
|
+
the error points away from the cause. The sections below carry the details.
|
|
47
|
+
|
|
48
|
+
1. Read environment variables literally: `process.env.API_KEY`. Destructuring
|
|
49
|
+
(`const { API_KEY } = process.env`), a name built from an expression, `?.` and
|
|
50
|
+
spreading the environment all read as no mention of the variable at all. The
|
|
51
|
+
build rejects them, and `xflow env check` names them before that.
|
|
52
|
+
2. A value written with `xflow env set` reaches the functions on the next
|
|
53
|
+
`xflow deploy`, not at the moment it is written.
|
|
54
|
+
3. Never delete a `functions/<name>/` directory unless the user asked for that
|
|
55
|
+
function to go. The next deploy removes it from the cloud together with its
|
|
56
|
+
schedules, and a function created again later gets a different address.
|
|
57
|
+
4. An already applied migration is never re-run, so editing its file changes
|
|
58
|
+
nothing. A schema change is always a new file.
|
|
59
|
+
5. When inserts start failing with `db_write_locked`, the database is over its plan
|
|
60
|
+
size. The fix is a migration that deletes data, never a rewrite of the failing SQL.
|
|
61
|
+
6. In file storage, call `confirm` only after the PUT has finished: the platform reads
|
|
62
|
+
the real size from storage, so an early call answers that the file is not there.
|
|
63
|
+
7. Heavy media does not travel with the sources. Photos, video and PDFs go up with
|
|
64
|
+
`xflow storage push`, not into the repository: the archive is capped at 10 MB.
|
|
65
|
+
|
|
66
|
+
## Plan limits
|
|
67
|
+
|
|
68
|
+
The organization runs on a plan with finite limits: projects, cloud functions, developer
|
|
69
|
+
and staff seats, database and file storage, function minutes per month, plus schedules and
|
|
70
|
+
how often they may run. `xflow whoami` prints every one of them next to what is already
|
|
71
|
+
used, and reading it before a long task is cheaper than hitting a wall mid-way.
|
|
72
|
+
|
|
73
|
+
The same output names the rights of your own key, which is the other half of the answer:
|
|
74
|
+
destroying data in a migration, deleting files from storage and linking a connected
|
|
75
|
+
account each need a right that is off by default. Reading that line first turns a refusal
|
|
76
|
+
halfway through the work into a sentence said before it starts.
|
|
77
|
+
|
|
78
|
+
A limit refusal is not a bad request. The CLI prints a line starting with `Plan limit:`,
|
|
79
|
+
the API answers `code: "forbidden"` with a `limit` object (`code`, `used`, `limit`), and
|
|
80
|
+
MCP tools carry the same field. Retrying the command, renaming things or rewriting the
|
|
81
|
+
code changes nothing: tell the user what ran out and stop. Only the owner or an admin
|
|
82
|
+
lifts it, in the web interface, by freeing the resource or moving to a bigger plan.
|
|
83
|
+
|
|
84
|
+
A schedule refused for running too often is one of these too, not a bad cron string: the
|
|
85
|
+
minimum interval comes from the plan, so widen the interval or say what the plan allows.
|
|
86
|
+
The number is in `xflow whoami`, never guess it.
|
|
87
|
+
|
|
88
|
+
On `db_write_locked` (hard rule 5) it is Postgres itself refusing, not the platform. Reads
|
|
89
|
+
and deletes keep working, which is what makes a deleting migration the way out. Write comes
|
|
90
|
+
back within an hour of the data going under the limit.
|
|
91
|
+
|
|
92
|
+
## Workflow
|
|
93
|
+
|
|
94
|
+
1. Change the code.
|
|
95
|
+
2. `npm run typecheck` for a two-second type check (older projects may not have the
|
|
96
|
+
script, then `npx tsc --noEmit`).
|
|
97
|
+
3. `npm run build` if the change is substantial. The platform builds again in its own
|
|
98
|
+
sandbox, on one Node version for everyone, so this is only a fast way to see errors early.
|
|
99
|
+
4. `xflow deploy` sends the sources, ships the cloud functions and builds the application
|
|
100
|
+
on the platform, printing each phase and the six-digit number of the version it built.
|
|
101
|
+
5. Give the user the project link the CLI printed and let them look. Do not open a
|
|
102
|
+
browser for them.
|
|
103
|
+
6. `xflow publish` makes that same version visible to visitors.
|
|
104
|
+
|
|
105
|
+
The split is deliberate: shipping a build and showing it are two separate decisions.
|
|
106
|
+
Until `publish` runs, visitors keep seeing the previous pages. The one exception is
|
|
107
|
+
the very first version of a project: it publishes automatically, since there is no
|
|
108
|
+
live version to protect yet.
|
|
109
|
+
|
|
110
|
+
`xflow status` says how the local copy differs from the server revision. When a deploy is
|
|
111
|
+
rejected before the build starts, the server revision is newer: somebody deployed first.
|
|
112
|
+
Fetch their work next to yours (`xflow pull --into ./server-copy`), merge it in git, deploy
|
|
113
|
+
again. `--force` destroys their work, a last resort rather than a way around the error.
|
|
114
|
+
|
|
115
|
+
**Only pages are versioned.** Cloud functions and the database are one per project: they
|
|
116
|
+
are not versioned, and dev and live share them. So `xflow deploy` changes the running
|
|
117
|
+
application the moment it finishes, before any `publish`, and `publish` moves the pages
|
|
118
|
+
only. Tell the user this when a deploy touches `functions/`: there is no staging step for
|
|
119
|
+
server code.
|
|
120
|
+
|
|
121
|
+
Rolling back: `xflow deployments` lists the history, `xflow rollback <id>` points the project
|
|
122
|
+
at the pages of an earlier build. Only the pages come back, and they then talk to today's
|
|
123
|
+
functions. The code of that build is a separate fetch (`xflow pull --revision N --into
|
|
124
|
+
../old-version`, kept outside the project folder); building it over the current one needs
|
|
125
|
+
`--force` and a human typing the project name, so you cannot do it on your own.
|
|
126
|
+
|
|
127
|
+
The platform keeps the **last 30 successful builds** and the **last 100 revisions** (a
|
|
128
|
+
revision a live version was built from is never deleted). That is rollback depth, not a
|
|
129
|
+
backup: keep anything you must not lose in your own git repository.
|
|
130
|
+
|
|
131
|
+
**The only link you give a person is the project page**, `https://app.getxflow.com/projects/<id>`,
|
|
132
|
+
which the CLI prints for you. Refer to builds by their number ("version 481203 is built,
|
|
133
|
+
092399 is what visitors see"), never by address. The platform does not hand out build
|
|
134
|
+
addresses and neither should you: a build address has the version number baked into it,
|
|
135
|
+
and after the next publish it does not break, it keeps answering with the old copy. Anyone
|
|
136
|
+
holding that link then stares at a frozen app and concludes the changes never shipped. The
|
|
137
|
+
project page always shows the current state, and every version is reachable from it.
|
|
138
|
+
|
|
139
|
+
## Build gate
|
|
140
|
+
|
|
141
|
+
Before the sandbox starts, the platform checks the sources against the template. Every
|
|
142
|
+
rule below blocks the build, and violations come back together with files and line
|
|
143
|
+
numbers rather than one per attempt: up to fifty of them, and a count when there are
|
|
144
|
+
more. Nothing is charged for a rejected attempt, the sandbox never starts. Write code
|
|
145
|
+
that already satisfies these rules instead of learning them from rejections.
|
|
146
|
+
|
|
147
|
+
Build setup:
|
|
148
|
+
|
|
149
|
+
- `package.json` with the build script named in `xflow.json` (`npm run build` by default).
|
|
150
|
+
- Vite: a `vite.config.*` and `vite` in dependencies.
|
|
151
|
+
- No server frameworks: `next`, `nuxt`, `remix`, `@sveltejs/kit`, `astro`.
|
|
152
|
+
- `index.html` in the root and an entry point at `src/main.tsx` (`.ts`, `.jsx` and `.js`
|
|
153
|
+
count too).
|
|
154
|
+
- Application code under `src/`. Root `app/`, `pages/`, `next/` are rejected.
|
|
155
|
+
- Heavy media does not belong in the sources at all: the archive is capped at 10 MB and
|
|
156
|
+
the built application at 15 MB unpacked. Upload it with `xflow storage push` instead
|
|
157
|
+
and refer to the addresses it prints, see **File storage**.
|
|
158
|
+
|
|
159
|
+
Platform contract, checked across all of `src/`:
|
|
160
|
+
|
|
161
|
+
- Call cloud functions through `xflow.functions.invoke`, never through a hardcoded
|
|
162
|
+
`*.yandexcloud.net` URL: the address changes and the app breaks silently.
|
|
163
|
+
- No API keys or tokens in the source: they end up in the bundle. Put the call in a
|
|
164
|
+
cloud function and the key in project secrets.
|
|
165
|
+
- No server modules (`fs`, `express`, `http`, `child_process`): there is no server runtime.
|
|
166
|
+
|
|
167
|
+
Interface rules, checked outside `src/components/ui` and `src/components/blocks`:
|
|
168
|
+
|
|
169
|
+
- No `alert()`, `confirm()`, `prompt()`. Use the Dialog and Toast components.
|
|
170
|
+
- No `console.log`. Deployed apps have a public console, and forgotten debugging prints
|
|
171
|
+
user data into it. `console.error` and `console.warn` are fine, they reach the project
|
|
172
|
+
logs.
|
|
173
|
+
- No inline styles with literal values (`style={{ color: '#fff' }}`). Computed styles
|
|
174
|
+
(a drag transform, a progress width) are fine, Tailwind cannot express them.
|
|
175
|
+
- No hex colors or Tailwind palette classes (`text-gray-500`): use the theme tokens.
|
|
176
|
+
Charts are exempt, they need real colors.
|
|
177
|
+
- No importing a `@/components/ui/*` component that does not exist in the project.
|
|
178
|
+
- A library that needs a provider (`@tanstack/react-query`, `react-redux`, `sonner`,
|
|
179
|
+
`react-hot-toast`, `react-dnd`) must have it mounted in `App.tsx`. Missing providers
|
|
180
|
+
build fine and give visitors a white screen.
|
|
181
|
+
|
|
182
|
+
Template integrity. The app grows out of the platform template, and part of that template
|
|
183
|
+
is not yours to change. The reference is a snapshot of the project itself, taken when the
|
|
184
|
+
platform first looked at it, so these rules never argue with work that was already there:
|
|
185
|
+
|
|
186
|
+
- Platform files must stay byte for byte as they arrived: `src/lib/theme-sync.ts`,
|
|
187
|
+
`src/lib/platform-auth.ts`, `src/contexts/platform-auth-context.tsx`,
|
|
188
|
+
`src/hooks/use-platform-auth.ts`, `src/utils/error-logger.ts`, `src/lib/xflow.ts`.
|
|
189
|
+
They wire the app to the platform, and every way they break is a silent one. Build what
|
|
190
|
+
you need around them, never inside them.
|
|
191
|
+
- The entry point keeps calling `initThemeSync()`, `initPlatformAuth()` and
|
|
192
|
+
`initErrorLogger()`, keeps importing `index.css` and keeps mounting `ThemeProvider`.
|
|
193
|
+
How the file is written is up to you.
|
|
194
|
+
- `index.html` keeps the element with `id="root"` and the script that loads `src/main`.
|
|
195
|
+
- Theme token names in `src/index.css` stay declared, in `:root` and in `.dark` alike, and
|
|
196
|
+
the Tailwind config keeps mapping them. Change the values as much as the design needs:
|
|
197
|
+
it is the names that components paint with.
|
|
198
|
+
- The Tailwind `content` globs keep covering `src/**`. Narrow them and Tailwind strips
|
|
199
|
+
every class the app uses.
|
|
200
|
+
- Files under `src/components/ui` and `src/components/blocks` may be edited freely but
|
|
201
|
+
not deleted.
|
|
202
|
+
|
|
203
|
+
A rejection names the file and the revision to take the original from:
|
|
204
|
+
`xflow pull --revision N --into ./original`, then copy the file back.
|
|
205
|
+
|
|
206
|
+
## Cloud functions
|
|
207
|
+
|
|
208
|
+
Server-side code lives in `functions/<name>/index.ts` and exports `handler` (`index.js` and
|
|
209
|
+
`index.mjs` are read too). The directory name is the name of the function, and the build gate
|
|
210
|
+
is strict about it: lowercase latin letters, digits and hyphens, starting with a letter or a
|
|
211
|
+
digit, up to 41 characters. `send-mail` passes, `sendMail` and `send_mail` do not, and the
|
|
212
|
+
build stops before the sandbox rather than halfway through it.
|
|
213
|
+
|
|
214
|
+
There is no separate deploy command: `xflow deploy` ships the functions and then builds the
|
|
215
|
+
application, in that order. List what is live with `xflow functions list`. The handler
|
|
216
|
+
returns `{ statusCode, body }` where `body` is a JSON string.
|
|
217
|
+
|
|
218
|
+
Keep one shape inside that string across the whole project: `{ success: true, data }`
|
|
219
|
+
when it worked, `{ success: false, error: { message, code } }` when it did not. Nothing
|
|
220
|
+
enforces this, but a project where every function answers its own way costs an adapter
|
|
221
|
+
on every call. Branch the frontend on `error.code`, never on `error.message`: wording
|
|
222
|
+
gets rewritten on any edit, a code does not.
|
|
223
|
+
|
|
224
|
+
The sources are the whole truth about which functions exist. Delete the directory and the
|
|
225
|
+
next deploy would delete the function from the cloud, schedules included, and that cannot be
|
|
226
|
+
undone: a function created again later gets a different address. So never remove a function
|
|
227
|
+
directory to "clean up" unless the user asked for the function to go.
|
|
228
|
+
|
|
229
|
+
Such a deploy does not start on its own: the platform names the functions it would remove and
|
|
230
|
+
refuses until somebody agrees. Under an agent there is no terminal to ask in, so the refusal
|
|
231
|
+
reaches you, and `--allow-removals` is the only way past it. Adding that flag to get the
|
|
232
|
+
build running is exactly the wrong move: it means you deleted something the user did not ask
|
|
233
|
+
you to delete. Put the directories back instead, and if the removal really is intended, say
|
|
234
|
+
which functions are about to go and let the user answer.
|
|
235
|
+
|
|
236
|
+
Debugging a deployed function is two commands: `xflow functions invoke <name>` calls it
|
|
237
|
+
the way the app does and prints status, timing and body (`--data '{"a":1}'` sends a body),
|
|
238
|
+
and `xflow functions logs <name>` shows the failures, each with its stack and the console
|
|
239
|
+
output of that call. Only failed calls are logged, so an empty output means the function
|
|
240
|
+
never crashed, not that logging is broken.
|
|
241
|
+
|
|
242
|
+
From the app, call a function through `src/lib/xflow.ts`:
|
|
243
|
+
`await xflow.functions.invoke('send-mail', { body: { to } })`. It carries the credentials
|
|
244
|
+
for you. Addresses are baked into the build, which is why the functions go out first:
|
|
245
|
+
by the time the bundle is built they already exist, and a new function is never missing
|
|
246
|
+
from the application that calls it.
|
|
247
|
+
|
|
248
|
+
### Who is calling
|
|
249
|
+
|
|
250
|
+
A function answers only to a member of the organization who has access to that project.
|
|
251
|
+
The platform issues a short-lived pass when it opens the application, the wrapper checks it
|
|
252
|
+
with the platform on every call, and the handler receives the answer in `event.xflow`:
|
|
253
|
+
|
|
254
|
+
```js
|
|
255
|
+
exports.handler = async (event) => {
|
|
256
|
+
const { caller, user } = event.xflow
|
|
257
|
+
// caller: 'visitor' (a person), 'service' (another function of this project),
|
|
258
|
+
// 'external' (an outside service with a key), 'schedule' (a timer run)
|
|
259
|
+
// user: { id, role } for a visitor, null for everything else
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Never trust an identity that arrives in the body or in a header of the request: those are
|
|
264
|
+
written by the page, which lives on someone else's computer. `event.xflow` is the only
|
|
265
|
+
identity the platform stands behind, and `usePlatformAuth()` in the frontend is a hint for
|
|
266
|
+
the interface, not a check.
|
|
267
|
+
|
|
268
|
+
A function that changes data should say so instead of checking the role by hand:
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
exports.minRole = 'admin' // 'member' | 'developer' | 'admin' | 'owner'
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The wrapper refuses anything below that role before your code runs. Without the line every
|
|
275
|
+
member of the project can call the function, including the ones who may only look at apps.
|
|
276
|
+
|
|
277
|
+
Losing access closes the function within five minutes, so a removed member cannot keep calling it.
|
|
278
|
+
Opening the deployed address directly does not work either: there is no pass outside the
|
|
279
|
+
platform.
|
|
280
|
+
|
|
281
|
+
Calling a function from another function is a server call. Send two headers, both from the
|
|
282
|
+
environment the platform fills in: `X-Project-Token` with `process.env.XFLOW_PROJECT_TOKEN`
|
|
283
|
+
and `X-Server-Key` with `process.env.XFLOW_SERVER_KEY`. The token is the ticket into the
|
|
284
|
+
project and the key is the identity; the wrapper checks the ticket first, so the key alone
|
|
285
|
+
answers 401.
|
|
286
|
+
|
|
287
|
+
An outside service (a webhook from a payment provider, a bot, a CRM) has no person behind it
|
|
288
|
+
and needs a key of that one function. Keys are not issued by default and the CLI cannot
|
|
289
|
+
create one: a human issues it in the project settings: «Облачные функции» → the function →
|
|
290
|
+
«Настройки». Ask the user to do that and to paste the address back to you — never invent
|
|
291
|
+
another way in. Send it in the `X-Function-Key` header. A function holds at most two keys,
|
|
292
|
+
and the second one exists to replace the first without downtime, not to serve a second
|
|
293
|
+
consumer.
|
|
294
|
+
|
|
295
|
+
`xflow functions list` shows who can reach each function: `in-app only` (no keys, answers
|
|
296
|
+
only inside the application) or `external (N keys)` (a human issued external access). Key
|
|
297
|
+
values are never shown there.
|
|
298
|
+
|
|
299
|
+
## Secrets and connected accounts
|
|
300
|
+
|
|
301
|
+
Keys and passwords live on the platform, not in the repository: `xflow env set SMTP_PASSWORD=…`
|
|
302
|
+
writes one, `xflow env` lists the names, `xflow env check` tells you which variables your
|
|
303
|
+
functions read but the platform does not have. Values never come back out — the only place
|
|
304
|
+
they exist is inside the running function.
|
|
305
|
+
|
|
306
|
+
These commands see only what this project can see: variables shared across the organization
|
|
307
|
+
and the ones bound to this project. A variable bound to another project is invisible here, so
|
|
308
|
+
`env rm` reports it as missing even though names are unique within the organization.
|
|
309
|
+
|
|
310
|
+
Why hard rules 1 and 2 are hard rules: a function receives only the variables it mentions by
|
|
311
|
+
name, and the platform reads those names out of the text of the code, so an opaque read ships
|
|
312
|
+
a function whose secret silently never arrives. The build stops on one instead. And a value
|
|
313
|
+
written today reaches the running function on the next `xflow deploy`, not before.
|
|
314
|
+
|
|
315
|
+
Some variables come from a connected account instead of from you. When someone connects an
|
|
316
|
+
advertising cabinet or another external service in the platform settings and links it to the
|
|
317
|
+
project, its credentials show up in `xflow env` marked read-only, under a prefix chosen at
|
|
318
|
+
link time: `YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_CLIENT_LOGIN`. Read them like any other
|
|
319
|
+
variable. Do not try to `xflow env set` those names: the platform keeps the values in sync
|
|
320
|
+
and refuses. If a call to that service starts failing with an authorisation error, run
|
|
321
|
+
`xflow status`: it says whether the token is merely expiring (any build renews it) or the
|
|
322
|
+
account was disconnected on the provider's side, which only a human can fix by reconnecting
|
|
323
|
+
it in the platform settings.
|
|
324
|
+
|
|
325
|
+
`xflow connections` lists those accounts: the ones already linked to this project, with the
|
|
326
|
+
alias and the state of the access, and the ones the organization has but this project does
|
|
327
|
+
not use yet, marked `available, not linked`. Check it before telling anyone a service is
|
|
328
|
+
unavailable: the account you need is often connected already, one link away.
|
|
329
|
+
|
|
330
|
+
`xflow connections link "Яндекс Метрика" --as YANDEX_METRIKA` is that link and
|
|
331
|
+
`xflow connections unlink YANDEX_METRIKA` undoes it; `xflow help connections` has the
|
|
332
|
+
naming rules and the flags. Unlink refuses while a function still reads one of the
|
|
333
|
+
variables and names those functions, so read that list before reaching for `--force`.
|
|
334
|
+
|
|
335
|
+
Linking needs the `connections:link` right on the key, and only accounts granted to the
|
|
336
|
+
owner of the key personally can be linked at all. If the right was taken away, say so and
|
|
337
|
+
ask the person to turn it back on in the platform settings under Developers: a key cannot
|
|
338
|
+
grant it to itself. Connecting a new account and switching one off stay with a person too.
|
|
339
|
+
|
|
340
|
+
## Schedules
|
|
341
|
+
|
|
342
|
+
`xflow schedules set report "0 3 ? * * *"` runs a function daily at 03:00. Six fields, UTC,
|
|
343
|
+
and exactly one of day-of-month / day-of-week must be `?` — that is how Yandex wants it.
|
|
344
|
+
A scheduled run reaches the handler as a POST with no headers, and `--payload '{"mode":"full"}'`
|
|
345
|
+
is how it gets a body. How often a schedule may run is a plan limit, see **Plan limits**.
|
|
346
|
+
|
|
347
|
+
The pieces line up in one pass. From a new function to a verified schedule:
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
xflow env set SMTP_PASSWORD=... # secrets first: values ride the next deploy
|
|
351
|
+
# write functions/report/index.ts, reading process.env.SMTP_PASSWORD literally
|
|
352
|
+
xflow deploy # ships the function, then builds the app
|
|
353
|
+
xflow schedules set report "0 3 ? * * *" --payload '{"mode":"full"}' # needs a deployed function
|
|
354
|
+
xflow functions invoke report # run it once, the way the app would
|
|
355
|
+
xflow functions logs report # empty output means it never crashed
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Database
|
|
359
|
+
|
|
360
|
+
Schema changes are files: `migrations/0001_init.sql`, `migrations/0002_orders.sql`, applied
|
|
361
|
+
in filename order by `xflow db migrate`. `xflow db status` shows what is applied and what
|
|
362
|
+
waits. History lives in the database itself, so an already applied file is never re-run and
|
|
363
|
+
editing it changes nothing: write a new migration instead.
|
|
364
|
+
|
|
365
|
+
The browser never reaches the database directly. The app reads and writes through a cloud
|
|
366
|
+
function, and inside the handler the connection string is already there:
|
|
367
|
+
|
|
368
|
+
```js
|
|
369
|
+
const { Client } = require('pg')
|
|
370
|
+
const db = new Client({ connectionString: process.env.DATABASE_URL })
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The platform passes `DATABASE_URL` only to functions that mention it, and sets the project
|
|
374
|
+
schema on every connection, so plain table names (`select * from tasks`) hit your project.
|
|
375
|
+
You never write that variable yourself: `xflow env set DATABASE_URL=...` is refused, and so is
|
|
376
|
+
`xflow env rm DATABASE_URL`. The same goes for every name starting with `XFLOW`: the platform
|
|
377
|
+
fills those in itself, and your value under one of them would shadow the real one.
|
|
378
|
+
|
|
379
|
+
The platform keeps no database history and no backups, so anything that destroys data
|
|
380
|
+
(`DROP TABLE`, `DROP COLUMN`, `TRUNCATE`, `DELETE FROM` without a condition) needs two
|
|
381
|
+
things at once: the `--allow-destructive` flag, and the right to destroy data on the key.
|
|
382
|
+
That right is off by default and only its owner turns it on, under Developers in the
|
|
383
|
+
platform settings. When the refusal is about the right rather than the flag, adding the flag
|
|
384
|
+
changes nothing: say what needs deleting and why, and let the person decide. With both in
|
|
385
|
+
place the affected tables are dumped first and kept for 7 days. Check with `--dry-run` first.
|
|
386
|
+
|
|
387
|
+
One logical database can be shared by several projects, so your migration can break an app
|
|
388
|
+
you do not see, and `xflow db status` lists applied migrations that have no file in your
|
|
389
|
+
repository: that is somebody else's project. For the same reason `migrations/` is not the
|
|
390
|
+
schema. It says what you did; `xflow db schema [table]` says what is in the database right
|
|
391
|
+
now, and `xflow db query "select ..."` reads it inside a READ ONLY transaction. Look before
|
|
392
|
+
you write a migration against a shared database.
|
|
393
|
+
|
|
394
|
+
## File storage
|
|
395
|
+
|
|
396
|
+
Two kinds of files live here, and they arrive through different doors.
|
|
397
|
+
|
|
398
|
+
**Static assets of the application you are building** — product photos, video, PDFs, big
|
|
399
|
+
artwork — you upload yourself, from the terminal:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
xflow storage push ./media --to media # a folder, its structure kept
|
|
403
|
+
xflow storage ls # what is there, with addresses and sizes
|
|
404
|
+
xflow storage ls --json # the same list, machine-readable
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
The caps that send them here (10 MB of sources, 15 MB unpacked) are about heavy media, not
|
|
408
|
+
about code: icons, fonts and small artwork the build needs stay in the repository as usual.
|
|
409
|
+
Everything heavy goes to storage, where it is metered against the organization plan and is
|
|
410
|
+
not re-uploaded on every deploy.
|
|
411
|
+
|
|
412
|
+
`push` prints the address of every file and skips what is already stored under the same path
|
|
413
|
+
and size, so a repeat run after a broken connection is cheap; `--replace` overwrites a file
|
|
414
|
+
whose size differs. An address belongs to the record rather than to the bytes, so a
|
|
415
|
+
replacement keeps it and the links in your code and tables keep working. Put those addresses
|
|
416
|
+
into the code or into a table: there is no command that fetches files back to a machine.
|
|
417
|
+
|
|
418
|
+
Deleting needs a right of its own, off by default (**Delete files** in the Developers
|
|
419
|
+
section of the platform), so `xflow storage rm` may answer that the key was not granted it.
|
|
420
|
+
Ask the person to switch it on, and do not look for a way around: whatever the users of the
|
|
421
|
+
application uploaded lives in the same folders, and there is no undo. A folder first tells
|
|
422
|
+
you how many files it holds and deletes them only with `--yes`.
|
|
423
|
+
|
|
424
|
+
**Files that users of the application upload** go through a cloud function of your own. The
|
|
425
|
+
browser cannot reach storage directly: those endpoints take only the server key of the
|
|
426
|
+
project, and the platform puts it into the environment of your cloud functions as
|
|
427
|
+
`XFLOW_SERVER_KEY`. Nothing else holds it: not the bundle, not `.env`, not `xflow env`.
|
|
428
|
+
|
|
429
|
+
So uploading is a function of your own. It asks the platform for a one-time link, the browser
|
|
430
|
+
then sends the bytes straight to storage, and a second call records the file:
|
|
431
|
+
|
|
432
|
+
```js
|
|
433
|
+
const link = await fetch(`${process.env.XFLOW_API_URL}/api/storage/project/upload-url`, {
|
|
434
|
+
method: 'POST',
|
|
435
|
+
headers: {
|
|
436
|
+
'Content-Type': 'application/json',
|
|
437
|
+
'X-Server-Key': process.env.XFLOW_SERVER_KEY,
|
|
438
|
+
},
|
|
439
|
+
body: JSON.stringify({ fileName, fileSize, contentType, folderPath: 'invoices' }),
|
|
440
|
+
}).then((r) => r.json())
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
`confirm` takes the returned `s3Key` and answers with the file and its address; `delete`
|
|
444
|
+
takes that same `url`. Never pipe the bytes through the function itself.
|
|
445
|
+
|
|
446
|
+
There is no endpoint that lists files back to a function, so the address that `confirm`
|
|
447
|
+
returns is the only copy this door gives you: write it into a table of your own in the same
|
|
448
|
+
call, and the application reads its files from there.
|
|
449
|
+
|
|
450
|
+
That address never expires and is safe to store, but it is not a public link. It opens only
|
|
451
|
+
for a visitor who is signed in and has access to this project, the same rule that guards the
|
|
452
|
+
application itself, so it works on your pages and does nothing in an email or on a page
|
|
453
|
+
anyone can open.
|
|
454
|
+
|
|
455
|
+
Three things bite an upload that otherwise looks right:
|
|
456
|
+
|
|
457
|
+
- **A name already taken in that folder is refused before the link is issued.** Pass
|
|
458
|
+
`overwrite: true` to replace the file: the bytes change and the address stays, which is
|
|
459
|
+
what the pages of the application expect. Without it, give the name a suffix of your own.
|
|
460
|
+
- **The limits are 200 MB per file and the storage quota of the organization.** The quota is
|
|
461
|
+
checked again on `confirm`, by the real size, which means a refusal can land after the
|
|
462
|
+
bytes are already up; the platform then removes the object and your table stays clean.
|
|
463
|
+
- **Confirm only after the PUT has finished.** The platform looks the object up in storage
|
|
464
|
+
and takes its real size and content type from there, not from what you declared, so an
|
|
465
|
+
early `confirm` answers that the file is not there. Send on the PUT the `Content-Type` you
|
|
466
|
+
named when asking for the link: storage serves the file under the header it received.
|
|
467
|
+
|
|
468
|
+
A refusal comes back as `{ error, code }`. Branch on `code` (`invalid_name`, `file_too_large`,
|
|
469
|
+
`quota_exceeded`, `not_uploaded`, `duplicate_name`, `not_found`, …) and never on the text:
|
|
470
|
+
the wording is free to change, the code is not.
|
|
471
|
+
|
|
472
|
+
What the app may do with files is decided inside that function, because the page in the
|
|
473
|
+
browser can be edited by whoever opened it. Never write the key into the sources and never
|
|
474
|
+
send it to the frontend: the build gate stops on a key found in the application code, and a
|
|
475
|
+
key that reached a visitor lets them delete every file of the project.
|
|
476
|
+
|
|
477
|
+
## Organizations and keys
|
|
478
|
+
|
|
479
|
+
One key per organization, stored side by side rather than replacing each other:
|
|
480
|
+
`xflow org` lists them, `xflow org switch <name|id>` changes the active one, and
|
|
481
|
+
`xflow help org` has the precedence rules. Two things are worth knowing without asking.
|
|
482
|
+
|
|
483
|
+
Inside a project folder there is nothing to switch: commands follow the organization the
|
|
484
|
+
folder is bound to, whatever the active one is. That is what lets two projects of two
|
|
485
|
+
organizations work side by side.
|
|
486
|
+
|
|
487
|
+
A "not found" on a project you know exists usually means the key belongs to another
|
|
488
|
+
organization, not that the project is gone: ids are unique across the platform, so a
|
|
489
|
+
wrong key can never reach somebody else's project. Check `xflow org`. Signing in to a
|
|
490
|
+
missing organization is `xflow login`, and that needs a person with a browser.
|
|
491
|
+
|
|
492
|
+
## Direct access without the terminal
|
|
493
|
+
|
|
494
|
+
The platform also exposes an MCP server, connected with `xflow mcp install`. When its tools
|
|
495
|
+
are available, prefer them for control-plane work: project state, database schema and
|
|
496
|
+
read-only queries, migrations, function logs and invocations, schedules, environment
|
|
497
|
+
variables, versions, publish and rollback. They answer with aggregates and say explicitly
|
|
498
|
+
when a result is truncated, which parsing terminal output does not.
|
|
499
|
+
|
|
500
|
+
Anything that depends on the working copy stays in the CLI: sending sources, building and
|
|
501
|
+
shipping the functions (`xflow deploy`), creating a project (`xflow init`). The
|
|
502
|
+
tools cannot see the folder you are working in, so a build started from there would release
|
|
503
|
+
whatever revision the server happens to hold, not what you have on disk. Pulling a repository
|
|
504
|
+
through tool calls also burns the user's tokens for nothing.
|
|
505
|
+
|
|
506
|
+
## Do not
|
|
507
|
+
|
|
508
|
+
- Edit `xflow.json` by hand: the CLI writes it.
|
|
509
|
+
- Commit `.env`: it holds the project token.
|
|
510
|
+
- Deploy with `--force` without checking `xflow status` first.
|
|
511
|
+
- Invent platform commands: what is not in `xflow help` does not exist.
|
|
512
|
+
|
|
513
|
+
## App design
|
|
514
|
+
|
|
515
|
+
The platform design system already ships inside the project, and the app is supposed
|
|
516
|
+
to look like a part of the platform:
|
|
517
|
+
|
|
518
|
+
- `src/components/ui` for primitives: buttons, inputs, dialogs, tables, menus
|
|
519
|
+
- `src/components/blocks` for composed blocks: `data-table` and `charts`
|
|
520
|
+
- `src/index.css` for color tokens
|
|
521
|
+
|
|
522
|
+
Before writing your own component, check whether a block already covers it: props are
|
|
523
|
+
typed next to each block, larger ones keep a separate `types.ts`. Take colors from
|
|
524
|
+
tokens (`bg-card`, `text-muted-foreground`, `bg-success-soft` and the like). A custom
|
|
525
|
+
hex palette makes the app look foreign inside the platform, which is the whole reason
|
|
526
|
+
the design system sits in the project.
|
|
527
|
+
|
|
528
|
+
The app runs inside the platform in an iframe and receives the theme and the current
|
|
529
|
+
user from it. The `usePlatformAuth()` hook gives the name, role, permissions and the
|
|
530
|
+
list of organization members. Use it to draw the interface, never to guard data: the
|
|
531
|
+
value lives on the page and is edited from the console. Guard data in the function, by
|
|
532
|
+
`event.xflow`.
|
|
533
|
+
|
|
534
|
+
## Errors from a deployed app
|
|
535
|
+
|
|
536
|
+
`xflow logs` prints what broke in the browser on deployed addresses: unhandled errors,
|
|
537
|
+
rejected promises and 5xx responses. The same stream, functions included, is in the
|
|
538
|
+
platform UI: open the project, section «Облако» (Cloud), tab «Логи» (Logs). The last 200
|
|
539
|
+
records per project are kept.
|
|
540
|
+
|
|
541
|
+
Local `npm run dev` does not report anything: these logs exist for what you cannot open
|
|
542
|
+
in your own devtools.
|
|
543
|
+
|
|
544
|
+
Cloud functions do work under `npm run dev`, and nothing has to be configured for that:
|
|
545
|
+
the dev server swaps the access key of the logged-in developer for the same narrow pass and
|
|
546
|
+
forwards the call. If it answers that the key is missing, the fix is `xflow login`.
|
|
547
|
+
|
|
548
|
+
## Environment
|
|
549
|
+
|
|
550
|
+
`VITE_XFLOW_PROJECT_TOKEN` and `VITE_XFLOW_API_URL` are written by the CLI when the
|
|
551
|
+
project is created, there is no need to edit them by hand. For CI and agent runs:
|
|
552
|
+
`XFLOW_TOKEN` replaces `xflow login`, `XFLOW_API_URL` points at another platform host.
|