@getxflow/cli 0.34.1 → 1.0.0
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 +1 -1
- package/dist/api.js +21 -6
- package/dist/args.js +1 -1
- package/dist/bin.js +15 -3
- package/dist/commands/connections.js +4 -4
- package/dist/commands/db.js +27 -64
- package/dist/commands/deploy.js +170 -216
- package/dist/commands/dev.js +15 -29
- package/dist/commands/env.js +6 -3
- package/dist/commands/functions.js +27 -119
- package/dist/commands/logs.js +19 -15
- package/dist/commands/pages.js +1 -0
- package/dist/commands/projects.js +134 -190
- package/dist/commands/sources.js +218 -120
- package/dist/commands/storage.js +2 -2
- package/dist/config.js +82 -26
- package/dist/flags.js +19 -12
- package/dist/help.js +219 -216
- package/dist/limits.js +6 -6
- package/dist/session.js +167 -52
- package/dist/template.js +29 -125
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/skills/xflow/SKILL.md +682 -739
package/dist/help.js
CHANGED
|
@@ -12,7 +12,10 @@ const version_1 = require("./version");
|
|
|
12
12
|
const ALIASES = {
|
|
13
13
|
invoke: 'functions',
|
|
14
14
|
publish: 'deploy',
|
|
15
|
-
|
|
15
|
+
rollback: 'deploy',
|
|
16
|
+
deployments: 'deploy',
|
|
17
|
+
apps: 'projects',
|
|
18
|
+
status: 'projects',
|
|
16
19
|
};
|
|
17
20
|
/** Help text. For agents this is the reference documentation. */
|
|
18
21
|
/** Whether `xflow help <topic>` has a page: a hint that sends nowhere is a second dead end. */
|
|
@@ -35,19 +38,22 @@ Every command with a line of its own: xflow help`);
|
|
|
35
38
|
}
|
|
36
39
|
(0, ui_1.out)(`${(0, ui_1.bold)('xflow')} ${(0, ui_1.dim)(version_1.CLI_VERSION)} XFlow application hosting
|
|
37
40
|
|
|
41
|
+
A project is a box: one database, cloud functions, secrets. Its apps live in apps/<folder>,
|
|
42
|
+
each with its own versions and address. Commands follow the folder you stand in.
|
|
43
|
+
|
|
38
44
|
${(0, ui_1.bold)('Getting started')}
|
|
39
45
|
xflow login sign in through the browser
|
|
40
|
-
xflow init [dir] new project from the platform template
|
|
41
|
-
xflow
|
|
42
|
-
xflow link <id> link this folder to a project, an empty one
|
|
43
|
-
xflow dev
|
|
46
|
+
xflow init [dir] new project with its first app from the platform template
|
|
47
|
+
xflow apps add <folder> another app of this project, in apps/<folder>
|
|
48
|
+
xflow link <id> link this folder to a project, an empty one gets the whole project
|
|
49
|
+
xflow dev in an app folder: run it on 127.0.0.1:3000, functions included
|
|
44
50
|
xflow skills pick the agents that get the platform instructions
|
|
45
51
|
xflow mcp install give the agent platform access without a terminal
|
|
46
52
|
|
|
47
53
|
${(0, ui_1.bold)('Sources')}
|
|
48
|
-
xflow status what is on the server and how the local
|
|
49
|
-
xflow pull [--into dir] [--revision N]
|
|
50
|
-
fetch the sources
|
|
54
|
+
xflow status what is on the server and how the local copies differ
|
|
55
|
+
xflow pull [functions|apps/<folder>] [--into dir] [--revision N]
|
|
56
|
+
fetch the sources of the root or of one app
|
|
51
57
|
|
|
52
58
|
${(0, ui_1.bold)('Files')}
|
|
53
59
|
xflow storage ls [folder] what the file storage of the project holds
|
|
@@ -58,13 +64,16 @@ ${(0, ui_1.bold)('Files')}
|
|
|
58
64
|
delete a folder with everything in it
|
|
59
65
|
|
|
60
66
|
${(0, ui_1.bold)('Releasing')}
|
|
61
|
-
xflow deploy -m "what changed"
|
|
62
|
-
|
|
63
|
-
xflow
|
|
64
|
-
|
|
67
|
+
xflow deploy functions -m "what changed"
|
|
68
|
+
send the project root, ship the functions and their callers
|
|
69
|
+
xflow deploy apps/<folder> -m "what changed"
|
|
70
|
+
send one app and build it (a bare deploy inside its folder)
|
|
71
|
+
xflow publish [build] point the app address at the newest build, or a named one
|
|
72
|
+
xflow rollback <build> the same with an older build
|
|
73
|
+
xflow deployments [functions] builds of the app, or deploys of the functions
|
|
65
74
|
|
|
66
75
|
${(0, ui_1.bold)('Functions')}
|
|
67
|
-
xflow functions list what is deployed,
|
|
76
|
+
xflow functions list what is deployed, its callers, open or not, last call
|
|
68
77
|
xflow functions invoke <name> [--data '{"a":1}']
|
|
69
78
|
call a function and print the answer
|
|
70
79
|
xflow functions logs [name] function crashes: stack and console output
|
|
@@ -83,9 +92,9 @@ ${(0, ui_1.bold)('Functions')}
|
|
|
83
92
|
|
|
84
93
|
${(0, ui_1.bold)('Database')}
|
|
85
94
|
xflow db status which migrations are applied and which are waiting
|
|
86
|
-
xflow db list databases of the
|
|
95
|
+
xflow db list databases of the projects and the servers of the organization
|
|
87
96
|
xflow db schema [table] tables of the project, or the columns of one
|
|
88
|
-
xflow db query
|
|
97
|
+
xflow db query 'select ...' read data, in a read-only transaction
|
|
89
98
|
xflow db migrate [--dry-run] [--allow-destructive]
|
|
90
99
|
apply the migrations from migrations/*.sql
|
|
91
100
|
xflow db move --to <server> --confirm <database>
|
|
@@ -101,9 +110,10 @@ ${(0, ui_1.bold)('Pages')}
|
|
|
101
110
|
xflow pages rollback <page> <N> put an earlier version back on the address
|
|
102
111
|
|
|
103
112
|
${(0, ui_1.bold)('Reference')}
|
|
104
|
-
xflow logs [--limit N] browser errors
|
|
113
|
+
xflow logs [--limit N] browser errors of the published apps
|
|
105
114
|
xflow projects list projects of the organization
|
|
106
|
-
xflow projects get [id] project card
|
|
115
|
+
xflow projects get [id] project card: apps, database, functions
|
|
116
|
+
xflow apps the apps of this project and their folders
|
|
107
117
|
xflow org organizations with a stored key, the active one marked
|
|
108
118
|
xflow org members who is in the organization, and with which role
|
|
109
119
|
xflow org switch <name|id> make another organization the active one
|
|
@@ -115,6 +125,7 @@ ${(0, ui_1.bold)('Reference')}
|
|
|
115
125
|
${(0, ui_1.bold)('Outside a project folder')}
|
|
116
126
|
--project <id> name the project by id, for every command that can
|
|
117
127
|
answer without the local files (xflow help project)
|
|
128
|
+
--app <id> name an app by id: deploy --revision, publish, deployments, logs
|
|
118
129
|
|
|
119
130
|
${(0, ui_1.bold)('Environment')}
|
|
120
131
|
XFLOW_TOKEN access key (for CI, instead of xflow login)
|
|
@@ -161,26 +172,30 @@ check ${(0, ui_1.bold)('xflow org')}.`,
|
|
|
161
172
|
Every command that only talks to the platform takes it, so a service project needs
|
|
162
173
|
no working copy of its own:
|
|
163
174
|
|
|
164
|
-
xflow db query --project 3f2a…
|
|
175
|
+
xflow db query --project 3f2a… 'select id, title from tasks where done = false'
|
|
165
176
|
xflow functions invoke kanban --project 3f2a… --data-file card.json
|
|
166
177
|
|
|
167
178
|
Which commands: ${(0, ui_1.bold)('db status')}, ${(0, ui_1.bold)('db query')}, ${(0, ui_1.bold)('db schema')}, ${(0, ui_1.bold)('db move')}, ${(0, ui_1.bold)('functions list')},
|
|
168
179
|
${(0, ui_1.bold)('functions invoke')}, ${(0, ui_1.bold)('functions logs')}, ${(0, ui_1.bold)('logs')}, ${(0, ui_1.bold)('env')}, ${(0, ui_1.bold)('env rm')},
|
|
169
180
|
${(0, ui_1.bold)('connections')}, ${(0, ui_1.bold)('connections link')}, ${(0, ui_1.bold)('connections unlink')}, ${(0, ui_1.bold)('schedules')},
|
|
170
181
|
${(0, ui_1.bold)('schedules set')}, ${(0, ui_1.bold)('schedules rm')}, ${(0, ui_1.bold)('storage ls')}, ${(0, ui_1.bold)('storage push')},
|
|
171
|
-
${(0, ui_1.bold)('storage rm')}, ${(0, ui_1.bold)('
|
|
182
|
+
${(0, ui_1.bold)('storage rm')}, ${(0, ui_1.bold)('apps')}, ${(0, ui_1.bold)('deployments')} (the functions deploys) and ${(0, ui_1.bold)('deploy')}. Three of
|
|
172
183
|
them answer with less under the flag: ${(0, ui_1.bold)('db status')} gives the applied history alone,
|
|
173
184
|
because comparing it against the local ${(0, ui_1.bold)('migrations/')} directory needs the folder,
|
|
174
185
|
${(0, ui_1.bold)('connections unlink')} removes the link without naming the functions that read
|
|
175
186
|
its variables, for the same reason, and ${(0, ui_1.bold)('deploy')} takes the flag only together with
|
|
176
|
-
${(0, ui_1.bold)('--revision')}: that
|
|
177
|
-
nothing to send and no folder to send it from.
|
|
178
|
-
|
|
187
|
+
${(0, ui_1.bold)('--revision')}: that deploy runs a revision of the functions the platform already holds,
|
|
188
|
+
so there is nothing to send and no folder to send it from.
|
|
189
|
+
|
|
190
|
+
${(0, ui_1.bold)('--app <id>')} does the same for one app: ${(0, ui_1.bold)('deploy --app <id> --revision N')}, ${(0, ui_1.bold)('deployments')},
|
|
191
|
+
${(0, ui_1.bold)('logs')}, ${(0, ui_1.bold)('publish')} and ${(0, ui_1.bold)('rollback')}. Its project comes from the platform, so the two
|
|
192
|
+
flags never go together.
|
|
179
193
|
|
|
180
194
|
The rest cannot answer without the working copy and have nothing to do with an id
|
|
181
|
-
instead: ${(0, ui_1.bold)('pull')}, ${(0, ui_1.bold)('status')}, ${(0, ui_1.bold)('dev')}, ${(0, ui_1.bold)('db migrate')},
|
|
195
|
+
instead: ${(0, ui_1.bold)('pull')}, ${(0, ui_1.bold)('status')}, ${(0, ui_1.bold)('dev')}, ${(0, ui_1.bold)('apps add')}, ${(0, ui_1.bold)('db migrate')},
|
|
182
196
|
${(0, ui_1.bold)('env check')}, ${(0, ui_1.bold)('env set')}. They answer about the files next to them, so
|
|
183
|
-
they keep asking for a folder. ${(0, ui_1.bold)('
|
|
197
|
+
they keep asking for a folder. ${(0, ui_1.bold)('publish')} and ${(0, ui_1.bold)('rollback')} take ${(0, ui_1.bold)('--app')} rather than
|
|
198
|
+
${(0, ui_1.bold)('--project')}: they move one app. ${(0, ui_1.bold)('projects get')} and ${(0, ui_1.bold)('link')} take no flag either:
|
|
184
199
|
they name the project by its id as an argument.
|
|
185
200
|
|
|
186
201
|
The folder is then ignored whole, and that includes its ${(0, ui_1.bold)('xflow.json')}: the address
|
|
@@ -198,13 +213,35 @@ With several organizations signed in, the one holding the project is looked for
|
|
|
198
213
|
every call: there is no folder to write the answer into, and a second place to
|
|
199
214
|
remember it would be a second thing to keep true. When most calls go to one project,
|
|
200
215
|
${(0, ui_1.bold)('xflow org switch')} to its organization skips that search.`,
|
|
201
|
-
projects: `${(0, ui_1.bold)('xflow projects')}:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
216
|
+
projects: `${(0, ui_1.bold)('xflow projects')} and ${(0, ui_1.bold)('xflow apps')}: projects and their apps
|
|
217
|
+
|
|
218
|
+
A project is a box: one database with its migrations, the cloud functions, secrets and
|
|
219
|
+
members, and no code of its own. An app is code the platform builds and serves at
|
|
220
|
+
https://<id>--<org>.xflowpage.ru, with its own versions and access level. One folder per
|
|
221
|
+
project, apps in subfolders:
|
|
222
|
+
|
|
223
|
+
xflow.json projectId, and who may call each function
|
|
224
|
+
package.json dependencies of the functions
|
|
225
|
+
functions/<name>/index.ts cloud functions, shared by every app
|
|
226
|
+
migrations/0001_init.sql the one database of the project
|
|
227
|
+
apps/<folder>/ an app: a Vite project with its own xflow.json (appId)
|
|
228
|
+
|
|
229
|
+
xflow projects list every project, its apps count and age
|
|
230
|
+
xflow projects get [id] one card: apps, database, functions and callers, schedules
|
|
231
|
+
xflow apps the apps of this project, their folders and addresses
|
|
232
|
+
--json as JSON, with the app ids
|
|
233
|
+
xflow apps add <folder> a new app, laid into apps/<folder> from the template
|
|
234
|
+
--name <name> its name (the folder by default)
|
|
235
|
+
--color <color> its tile color (the platform picks one by default)
|
|
236
|
+
xflow status in the root: the root and every app; in an app: that app
|
|
237
|
+
|
|
238
|
+
The root and every app are separate histories: apps/** never travels with the root, the
|
|
239
|
+
root never travels with an app. The app is its appId, so a folder can be renamed freely.
|
|
240
|
+
Who opens an app (members of the project, a list, anyone with the link) is set by a person
|
|
241
|
+
in the web interface, not here.
|
|
242
|
+
|
|
243
|
+
Both ${(0, ui_1.bold)('projects')} commands name the project positionally: the id is an argument, not a
|
|
244
|
+
--project flag. Without one, ${(0, ui_1.bold)('get')} answers about the project of the folder it stands in.
|
|
208
245
|
|
|
209
246
|
An id typed by hand is looked for across every signed-in organization, and the
|
|
210
247
|
folder around the command is ignored whole, the way the ${(0, ui_1.bold)('--project')} flag does it
|
|
@@ -218,14 +255,14 @@ projects these are.
|
|
|
218
255
|
${(0, ui_1.bold)('xflow link <id>')} binds the current folder to a project; ${(0, ui_1.bold)('xflow init')} makes a new one.`,
|
|
219
256
|
link: `${(0, ui_1.bold)('xflow link')} <id>: bind this folder to a project
|
|
220
257
|
|
|
221
|
-
Writes xflow.json and .xflow/state.json
|
|
222
|
-
|
|
223
|
-
|
|
258
|
+
Writes the root xflow.json and .xflow/state.json and lays down the instructions for the
|
|
259
|
+
agents. The id comes from ${(0, ui_1.bold)('xflow projects list')}; a project of another signed-in
|
|
260
|
+
organization is found too, and the folder is bound to that organization.
|
|
224
261
|
|
|
225
|
-
An empty folder (.git alone counts as empty)
|
|
226
|
-
|
|
227
|
-
folder with files keeps them as they are: ${(0, ui_1.bold)('xflow status')} says
|
|
228
|
-
|
|
262
|
+
An empty folder (.git alone counts as empty) gets the whole project: the latest revision
|
|
263
|
+
of the root and of every app, each app in apps/<folder> under the folder name the project
|
|
264
|
+
revision records. A folder with files keeps them as they are: ${(0, ui_1.bold)('xflow status')} says
|
|
265
|
+
how they differ, ${(0, ui_1.bold)('xflow pull --into ./server-copy')} fetches the whole project next to them.
|
|
229
266
|
|
|
230
267
|
In a folder that is already linked, the command points xflow.json at the other project and
|
|
231
268
|
keeps the rest of the file. A new project instead of an existing one: ${(0, ui_1.bold)('xflow init')}.`,
|
|
@@ -233,8 +270,8 @@ keeps the rest of the file. A new project instead of an existing one: ${(0, ui_1
|
|
|
233
270
|
|
|
234
271
|
A page is a static site at https://<name>.xflowpage.ru: a landing page, a presentation, an
|
|
235
272
|
invoice for people outside the organization. It opens by its address for anyone, without
|
|
236
|
-
signing in and
|
|
237
|
-
platform,
|
|
273
|
+
signing in, and has no build, no publish step and no database. An app is the opposite: code
|
|
274
|
+
the platform builds, with versions, access levels and a database through functions.
|
|
238
275
|
|
|
239
276
|
xflow pages the pages, their state and address
|
|
240
277
|
xflow pages create <title> <path> a new page with its first version: there are no
|
|
@@ -261,10 +298,11 @@ build it and upload what the build writes. Dot entries are skipped. At most 5 MB
|
|
|
261
298
|
unpacked and 1000 files.
|
|
262
299
|
|
|
263
300
|
The page is public, so a file holding a secret key is refused whole: every visitor would read
|
|
264
|
-
it. A page that needs a backend (a form, a lookup) calls
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
301
|
+
it. A page that needs a backend (a form, a lookup) calls ${(0, ui_1.bold)('/_fn/<name>')} on its own address,
|
|
302
|
+
and only a function whose callers in the root xflow.json of its project name the page answers:
|
|
303
|
+
add the page name there and run ${(0, ui_1.bold)('xflow deploy functions')}. The page has to exist first.
|
|
304
|
+
Such a function is open to the whole internet. Search engines are asked not to index pages,
|
|
305
|
+
so do not promise SEO.
|
|
268
306
|
|
|
269
307
|
A page takes the key of the whole organization, the one ${(0, ui_1.bold)('xflow login')} issues: a key
|
|
270
308
|
scoped to projects is refused. Switching a page off and deleting it are for a person, in the
|
|
@@ -295,51 +333,50 @@ will work.`,
|
|
|
295
333
|
db: `${(0, ui_1.bold)('xflow db')}: schema, data, migrations
|
|
296
334
|
|
|
297
335
|
xflow db status what is applied, what is waiting, the server
|
|
298
|
-
xflow db list databases of the
|
|
336
|
+
xflow db list databases of the projects, the servers of the organization
|
|
299
337
|
xflow db schema [table] tables of the project, or the columns of one
|
|
300
|
-
xflow db query
|
|
338
|
+
xflow db query 'select ...' read data (--limit N: 50 by default, 300 at most;
|
|
301
339
|
--json prints the values whole)
|
|
302
340
|
xflow db query <file>.sql the same, the query read from a file
|
|
303
|
-
xflow db migrate apply migrations/*.sql
|
|
304
|
-
xflow db move --to <server> --confirm <
|
|
341
|
+
xflow db migrate store the project root, then apply migrations/*.sql
|
|
342
|
+
xflow db move --to <server> --confirm <project name>
|
|
305
343
|
move the project database to another server
|
|
306
344
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
the choice for ${(0, ui_1.bold)('xflow init --server')} and ${(0, ui_1.bold)('db move')}. Without that flag a new
|
|
315
|
-
database goes to ${(0, ui_1.bold)('platform')}.
|
|
345
|
+
Every project has exactly one database of its own, shared by all of its apps and functions,
|
|
346
|
+
and every command here works from any folder of the project. ${(0, ui_1.bold)('list')} is about the
|
|
347
|
+
organization: one database per project, and where each lives: ${(0, ui_1.bold)('platform')} is the common
|
|
348
|
+
server, ${(0, ui_1.bold)('own "<name>"')} an own server of the organization. While an own server is not
|
|
349
|
+
running, its databases do not answer, and that is not fixable from the code. An organization
|
|
350
|
+
with own servers also gets them listed, with the room used on each: that is the choice for
|
|
351
|
+
${(0, ui_1.bold)('xflow init --server')} and ${(0, ui_1.bold)('db move')}.
|
|
316
352
|
|
|
317
353
|
${(0, ui_1.bold)('move')} takes the database of this project to another server of the organization:
|
|
318
354
|
${(0, ui_1.bold)('platform')} or an own server by name (or id, when two share a name). The platform copies
|
|
319
|
-
the tables, switches the functions of
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
settings where the key permissions live. The command follows the move to the end;
|
|
355
|
+
the tables, switches the functions of the project and removes the old copy; nothing needs a
|
|
356
|
+
deploy. While it copies, the apps read data but cannot save anything new, usually for a
|
|
357
|
+
couple of minutes, which is why ${(0, ui_1.bold)('--confirm')} takes the project name. It also needs the
|
|
358
|
+
right to move databases on the key: off by default, only its owner turns it on, in the
|
|
359
|
+
platform settings where the key permissions live. The command follows the move to the end;
|
|
325
360
|
stopping it does not stop the move, and ${(0, ui_1.bold)('xflow db status')} shows where it stands. A move
|
|
326
|
-
that fails is rolled back
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
double quotes
|
|
334
|
-
|
|
361
|
+
that fails is rolled back. Buying, paying for and deleting servers stay with the person.
|
|
362
|
+
|
|
363
|
+
${(0, ui_1.bold)('schema')} answers a different question than the ${(0, ui_1.bold)('migrations/')} directory: the files say
|
|
364
|
+
what you did, the schema says what is actually in there. ${(0, ui_1.bold)('query')} runs inside a READ ONLY transaction, so
|
|
365
|
+
writes are rejected by the database itself, not by us reading your SQL. Single quotes around
|
|
366
|
+
the query hold in PowerShell, bash and zsh, while cmd.exe wants double ones; a query that
|
|
367
|
+
needs quotes of its own goes in a .sql file outside the project, because Windows PowerShell
|
|
368
|
+
strips double quotes from arguments and a file in migrations/ would be taken for a
|
|
369
|
+
migration. Its table cuts
|
|
335
370
|
long values to keep the columns readable; ${(0, ui_1.bold)('--json')} prints them whole, together with
|
|
336
371
|
the applied limit and its ceiling. ${(0, ui_1.bold)('status')} named by id (${(0, ui_1.bold)('--project')}) answers with
|
|
337
372
|
the applied history alone: comparing against ${(0, ui_1.bold)('migrations/')} needs the folder.
|
|
338
373
|
|
|
339
|
-
${(0, ui_1.bold)('migrate')}: the files live in the
|
|
374
|
+
${(0, ui_1.bold)('migrate')}: the files live in the project root (${(0, ui_1.bold)('migrations/0001_init.sql')},
|
|
340
375
|
${(0, ui_1.bold)('migrations/0002_orders.sql')} and so on). The order comes from the file name, which
|
|
341
|
-
is why the leading number is required.
|
|
342
|
-
|
|
376
|
+
is why the leading number is required. The command first stores the project root as a
|
|
377
|
+
revision, so the migrations stay in its history and a clone gets them, then applies what is
|
|
378
|
+
new. Every migration runs in its own transaction, the history is kept in the database
|
|
379
|
+
itself, and anything already applied is not run again.
|
|
343
380
|
|
|
344
381
|
--dry-run report what would be applied without touching the database
|
|
345
382
|
--allow-destructive allow operations that destroy data
|
|
@@ -351,21 +388,17 @@ access key. That right is off by default, and only its owner turns it on, in the
|
|
|
351
388
|
platform settings, where the key permissions live: a flag is something an agent adds by itself, a
|
|
352
389
|
right is not. With both in place, the contents of the affected tables are dumped and
|
|
353
390
|
kept for 7 days: that is a "caught it right away" safety net, not a backup.
|
|
354
|
-
${(0, ui_1.bold)('DROP DATABASE')} is never allowed
|
|
355
|
-
organization.
|
|
391
|
+
${(0, ui_1.bold)('DROP DATABASE')} is never allowed.
|
|
356
392
|
|
|
357
393
|
Editing an already applied file achieves nothing: ${(0, ui_1.bold)('migrate')} refuses a name that is
|
|
358
394
|
already applied with different content, and ${(0, ui_1.bold)('xflow db status')} lists such a file
|
|
359
|
-
separately. Write a new migration instead
|
|
360
|
-
|
|
361
|
-
One logical database can be attached to several projects, so the history may hold
|
|
362
|
-
migrations that are not in your repository. That is normal, but it also means your
|
|
363
|
-
migration can break somebody else's application, and that a name already in that history
|
|
364
|
-
is taken for you too: ${(0, ui_1.bold)('0001_init')} of a neighbour leaves yours no room.`,
|
|
395
|
+
separately. Write a new migration instead.`,
|
|
365
396
|
logs: `${(0, ui_1.bold)('xflow logs')} and ${(0, ui_1.bold)('xflow functions logs')}: project errors
|
|
366
397
|
|
|
367
|
-
|
|
368
|
-
|
|
398
|
+
Two streams: crashes of the cloud functions of the project, and browser errors of each
|
|
399
|
+
app. ${(0, ui_1.bold)('xflow logs')} in an app folder (or with ${(0, ui_1.bold)('--app <id>')}) shows that app, in the
|
|
400
|
+
project root every app, each row named by its folder. The last 200 entries of each stream
|
|
401
|
+
are kept, older ones fall off.
|
|
369
402
|
|
|
370
403
|
--limit N how many to show (10 by default)
|
|
371
404
|
|
|
@@ -380,7 +413,7 @@ never gets as far as the wrapper), going over the execution time cap of the plan
|
|
|
380
413
|
Those show up in the answer to ${(0, ui_1.bold)('xflow functions invoke')}.
|
|
381
414
|
|
|
382
415
|
Browser errors are collected by ${(0, ui_1.bold)('src/utils/error-logger.ts')} of the template and
|
|
383
|
-
only from
|
|
416
|
+
only from the published addresses: a local ${(0, ui_1.bold)('xflow dev')} writes nothing here.`,
|
|
384
417
|
storage: `${(0, ui_1.bold)('xflow storage')}: files of the application
|
|
385
418
|
|
|
386
419
|
The sources archive is capped at 10 MB and is rebuilt and re-uploaded on every
|
|
@@ -447,9 +480,10 @@ variables literally. ${(0, ui_1.bold)('check')} names those reads and the build
|
|
|
447
480
|
that quietly fails to arrive is found by the function falling over weeks later.
|
|
448
481
|
|
|
449
482
|
The value reaches the function on deploy, not at the moment it is stored: after
|
|
450
|
-
${(0, ui_1.bold)('env set')} run ${(0, ui_1.bold)('xflow deploy')}.
|
|
483
|
+
${(0, ui_1.bold)('env set')} run ${(0, ui_1.bold)('xflow deploy functions')}. It ships a function whose code did not
|
|
451
484
|
change but whose variables did, so nothing is left holding an old value. The same after a
|
|
452
|
-
delete: a function already deployed keeps the old value until its next deploy
|
|
485
|
+
delete: a function already deployed keeps the old value until its next deploy. Apps get no
|
|
486
|
+
variables at all: whatever a secret protects is reached through a function.`,
|
|
453
487
|
connections: `${(0, ui_1.bold)('xflow connections')}: accounts connected to the organization
|
|
454
488
|
|
|
455
489
|
Somebody signs in to Yandex Metrika, Bitrix or a mail service once, in the platform,
|
|
@@ -485,8 +519,8 @@ linked to a service project without a folder has to be removable the same way.
|
|
|
485
519
|
Variables are named after the alias: an OAuth connection called ${(0, ui_1.bold)('YANDEX_METRIKA')}
|
|
486
520
|
gives ${(0, ui_1.bold)('YANDEX_METRIKA_TOKEN')}, a key-based one gives a variable per field. They
|
|
487
521
|
arrive at a function on its next deploy, like every other variable, so after a change
|
|
488
|
-
run ${(0, ui_1.bold)('xflow deploy')}. A token close to expiry is renewed by
|
|
489
|
-
a revoked one is not renewed by anything until a human reconnects the account.
|
|
522
|
+
run ${(0, ui_1.bold)('xflow deploy functions')}. A token close to expiry is renewed by that deploy on the
|
|
523
|
+
way; a revoked one is not renewed by anything until a human reconnects the account.
|
|
490
524
|
|
|
491
525
|
Linking needs a right on the key, ${(0, ui_1.bold)('connections:link')}. Keys are issued with it,
|
|
492
526
|
and a person can take it away in the platform settings, where the key permissions live; a key cannot give
|
|
@@ -556,41 +590,38 @@ how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not
|
|
|
556
590
|
${(0, ui_1.bold)('list')} prints that body next to the schedule, so what a timer sends can be read
|
|
557
591
|
back without setting it again.
|
|
558
592
|
|
|
559
|
-
A scheduled run arrives at the handler as a POST with
|
|
560
|
-
|
|
561
|
-
|
|
593
|
+
A scheduled run arrives at the handler as a POST with ${(0, ui_1.bold)("event.xflow.caller === 'schedule'")}:
|
|
594
|
+
the envelope is signed by the platform, so such an event cannot be forged from outside. A
|
|
595
|
+
function without callers that only a schedule wakes is a workflow, and a project without
|
|
596
|
+
apps is made of those. The function has to be deployed already, since the schedule points
|
|
597
|
+
at it and not the other way round.
|
|
562
598
|
|
|
563
599
|
A failed run shows up in ${(0, ui_1.bold)('xflow functions logs')}: nobody is watching a scheduled
|
|
564
600
|
function, so the wrapper reports a crash the same way it does on an ordinary call.`,
|
|
565
601
|
functions: `${(0, ui_1.bold)('xflow functions')}: what is deployed, and calling it
|
|
566
602
|
|
|
567
|
-
xflow functions list name, state,
|
|
568
|
-
xflow functions invoke <name> call one
|
|
603
|
+
xflow functions list name, state, callers, open, keys, last call and error
|
|
604
|
+
xflow functions invoke <name> call one as yourself, through the platform
|
|
569
605
|
xflow functions logs [name] crashes: stack and console output
|
|
570
606
|
|
|
571
|
-
|
|
572
|
-
${(0, ui_1.bold)('functions/<name>/index.ts')}
|
|
573
|
-
|
|
574
|
-
${(0, ui_1.bold)('list')} answers who may reach each function, and the code decides it:
|
|
607
|
+
Functions belong to the project and ship on their own step: ${(0, ui_1.bold)('xflow deploy functions')}
|
|
608
|
+
sends the project root and deploys everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. Every app
|
|
609
|
+
of the project runs the new code the moment it finishes; there is no publish step for them.
|
|
575
610
|
|
|
576
|
-
|
|
577
|
-
external (N keys) plus an outside service holding a key a human issued in the interface
|
|
578
|
-
public plus anyone at all: the code says ${(0, ui_1.bold)("exports.access = 'public'")}
|
|
611
|
+
Who may call a function is its callers, in the root xflow.json:
|
|
579
612
|
|
|
580
|
-
|
|
581
|
-
and without a key, so the page calls the printed address directly and bakes no key into
|
|
582
|
-
its files. Remove the line and deploy again to close it. The function that stays inside
|
|
583
|
-
the application declares nothing, and ${(0, ui_1.bold)("exports.minRole = 'developer'")} raises the
|
|
584
|
-
role the visitor needs. The address is printed for every deployed function; it is not a
|
|
585
|
-
secret, the application bundle carries it.
|
|
613
|
+
{ "projectId": "…", "functions": { "book-slot": { "callers": ["zapis", "clinic-landing"] } } }
|
|
586
614
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
615
|
+
A caller is an app folder of this project (apps/zapis is "zapis") or the name of a page of
|
|
616
|
+
the organization. A function nobody lists has no callers: a workflow, woken by a schedule,
|
|
617
|
+
by a service holding a key a person issued, or by ${(0, ui_1.bold)('invoke')}. ${(0, ui_1.bold)('open')} in the list means a
|
|
618
|
+
caller is public (a page, or an app anyone with the link opens), so anyone on the internet
|
|
619
|
+
can call it. The address in the note under the table is for webhooks and other functions,
|
|
620
|
+
with a key; apps and pages call ${(0, ui_1.bold)('/_fn/<name>')} on their own address.
|
|
590
621
|
|
|
591
|
-
${(0, ui_1.bold)('invoke')} calls it
|
|
592
|
-
|
|
593
|
-
|
|
622
|
+
${(0, ui_1.bold)('invoke')} calls it as the person who owns the key: the platform checks the role, gives
|
|
623
|
+
the function a one-minute ticket and calls it itself, so the function sees
|
|
624
|
+
${(0, ui_1.bold)("event.xflow.caller === 'platform'")} and the person in ${(0, ui_1.bold)('event.xflow.user')}.
|
|
594
625
|
|
|
595
626
|
--data '{"a":1}' request body (the method becomes POST by default)
|
|
596
627
|
--data-file body.json the same body from a file
|
|
@@ -602,16 +633,17 @@ strips double quotes from a native call, so ${(0, ui_1.bold)('--data')} needs th
|
|
|
602
633
|
(${(0, ui_1.bold)('--data \'{\\"a\\":1}\'')}) and a file needs nothing.
|
|
603
634
|
|
|
604
635
|
Prints the status, the response time and the body; under ${(0, ui_1.bold)('--json')} the body
|
|
605
|
-
alone. A non-zero exit code on 4xx and 5xx: in CI such a call has to fail the step.
|
|
606
|
-
|
|
636
|
+
alone. A non-zero exit code on 4xx and 5xx: in CI such a call has to fail the step. A
|
|
637
|
+
refusal of the guard names its reason in the body (${(0, ui_1.bold)('not_bound')}, ${(0, ui_1.bold)('no_access')},
|
|
638
|
+
${(0, ui_1.bold)('functions_stopped')}); for a crash the hint points at ${(0, ui_1.bold)('xflow functions logs <name>')}.`,
|
|
607
639
|
mcp: `${(0, ui_1.bold)('xflow mcp install')}: connect the agent to the platform directly
|
|
608
640
|
|
|
609
641
|
Writes the MCP server into the configuration of a client that has its own connect
|
|
610
642
|
command: today that is Claude Code, and for the rest the command prints what to enter
|
|
611
643
|
by hand. After that the agent runs the platform without a terminal: it inspects the
|
|
612
|
-
database, applies migrations,
|
|
613
|
-
The commands stay: code, builds and
|
|
614
|
-
pushing sources through MCP means filling the agent context and spending your money.
|
|
644
|
+
database, applies migrations, calls functions, sets schedules, publishes app builds, reads
|
|
645
|
+
logs. The commands stay: code, builds and functions deploys are still done by the CLI,
|
|
646
|
+
because pushing sources through MCP means filling the agent context and spending your money.
|
|
615
647
|
|
|
616
648
|
The command takes the access key from your own login and does not print it: usually
|
|
617
649
|
the agent runs this command itself, and everything printed lands in its context and in
|
|
@@ -651,53 +683,57 @@ Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up
|
|
|
651
683
|
description matches the task, and "add a customers table" will not trigger it. AGENTS.md
|
|
652
684
|
is always read by the agent, which is why the pointer is appended there, at the end of
|
|
653
685
|
the file and once. ${(0, ui_1.bold)('CLAUDE.md')} is left alone: Claude Code reads .claude/skills anyway.`,
|
|
654
|
-
dev: `${(0, ui_1.bold)('xflow dev')}: run
|
|
686
|
+
dev: `${(0, ui_1.bold)('xflow dev')}: run an app on this machine
|
|
655
687
|
|
|
656
|
-
Runs ${(0, ui_1.bold)('npm run dev')} in the
|
|
657
|
-
organization that holds the project.
|
|
658
|
-
|
|
659
|
-
as
|
|
660
|
-
|
|
688
|
+
Runs ${(0, ui_1.bold)('npm run dev')} in the app folder it stands in (127.0.0.1:3000) with the access key
|
|
689
|
+
of the organization that holds the project. Every ${(0, ui_1.bold)('/_fn/<name>')} call of the app goes to
|
|
690
|
+
the platform as this app, so the callers, the access and the person calling are the same
|
|
691
|
+
as on its address. Nothing about functions is kept on this machine: a function someone
|
|
692
|
+
else shipped answers here at once.
|
|
661
693
|
|
|
662
|
-
Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}: that one is handed no key
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
no longer left in a file for that dev server to pick up.
|
|
666
|
-
|
|
667
|
-
The platform address comes from the CLI too, not from .env, so the key goes to the
|
|
668
|
-
platform that issued it and nowhere else. It never reaches the browser: the page gets
|
|
669
|
-
only the ${(0, ui_1.bold)('VITE_')} variables. The project token and the function addresses are
|
|
670
|
-
fetched on every start as well: a function someone else shipped answers here without a
|
|
671
|
-
deploy of yours.
|
|
694
|
+
Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}: that one is handed no key and no app id, so
|
|
695
|
+
its calls to functions fail. The platform address comes from the CLI too, so the key goes
|
|
696
|
+
to the platform that issued it and nowhere else. It never reaches the browser.
|
|
672
697
|
|
|
673
698
|
The server listens on 127.0.0.1 and nowhere else, because it holds that key: on a
|
|
674
699
|
shared network, anything else would let a neighbour call the functions of the project
|
|
675
700
|
as you. A phone on the same Wi-Fi therefore cannot open it either.`,
|
|
676
|
-
pull: `${(0, ui_1.bold)('xflow pull')}: fetch the sources
|
|
701
|
+
pull: `${(0, ui_1.bold)('xflow pull')} [functions|apps/<folder>]: fetch the sources
|
|
677
702
|
|
|
678
|
-
|
|
679
|
-
|
|
703
|
+
Every app and the project root are separate histories, and pull fetches one of them: in an
|
|
704
|
+
app folder that app, in the project root the root (apps/ stays as it is), or the one named.
|
|
705
|
+
It refuses to write into a non-empty folder: the platform cannot merge changes, that is
|
|
706
|
+
git's job.
|
|
680
707
|
|
|
681
|
-
--into <dir> unpack alongside, to compare
|
|
682
|
-
|
|
708
|
+
--into <dir> unpack alongside, to compare; in the project root with nothing named
|
|
709
|
+
it fetches the whole project there
|
|
710
|
+
--revision <N> a specific revision of that history (xflow deployments lists them)
|
|
683
711
|
--force overwrite the folder completely`,
|
|
684
|
-
deploy: `${(0, ui_1.bold)('xflow deploy')}: build
|
|
712
|
+
deploy: `${(0, ui_1.bold)('xflow deploy')}: deploy the functions or build an app, then publish
|
|
713
|
+
|
|
714
|
+
xflow deploy functions -m "…" send the project root, check it, ship the functions
|
|
715
|
+
and their callers
|
|
716
|
+
xflow deploy apps/<folder> -m "…" send one app and build it; inside its folder a plain
|
|
717
|
+
xflow deploy does the same
|
|
718
|
+
xflow publish [build] point the app address at the newest build, or a named one
|
|
719
|
+
xflow rollback <build> the same, the number required
|
|
720
|
+
xflow deployments [functions] the builds of the app, or the deploys of the functions
|
|
685
721
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
by default).
|
|
722
|
+
In the project root a plain ${(0, ui_1.bold)('xflow deploy')} refuses and names the targets: functions and
|
|
723
|
+
apps never go in one run, because a change of functions reaches every app at once.
|
|
689
724
|
|
|
690
725
|
-m <text> the version comment, required: one short line in the language the
|
|
691
|
-
user speaks, saying what changed
|
|
692
|
-
history (--message is the long form)
|
|
726
|
+
user speaks, saying what changed (--message is the long form)
|
|
693
727
|
--no-push do not send sources, build from the latest server revision
|
|
694
|
-
--revision <N>
|
|
695
|
-
|
|
696
|
-
--project <id> with --revision only: the
|
|
728
|
+
--revision <N> run a revision the platform already holds: that is how the functions
|
|
729
|
+
come back (xflow deployments functions lists them)
|
|
730
|
+
--project <id> with --revision only: the functions of that project, no folder needed
|
|
731
|
+
--app <id> with --revision only: that app, no folder needed
|
|
697
732
|
--force allow overwriting the server revision while sending
|
|
698
733
|
--allow-removals agree in advance to remove the functions gone from the sources
|
|
699
734
|
|
|
700
|
-
|
|
735
|
+
What goes up: an app sends its own folder; the project root sends everything but apps/,
|
|
736
|
+
plus the apps/<folder>/xflow.json files its callers are read from. Never sent: node_modules,
|
|
701
737
|
.git, dist, build, .next, any .env, plus everything listed in .xflowignore and in the
|
|
702
738
|
ignore field of xflow.json.
|
|
703
739
|
|
|
@@ -711,20 +747,21 @@ CLI shows whose work you are about to destroy and asks for confirmation by typin
|
|
|
711
747
|
project name. In a non-interactive run (CI, an agent) there is no way to confirm: a
|
|
712
748
|
version conflict has to fail the build rather than silently destroy somebody else's work.
|
|
713
749
|
|
|
714
|
-
The platform builds, in a clean sandbox on one Node version for everybody
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
that
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
750
|
+
The platform builds, in a clean sandbox on one Node version for everybody. Before that the
|
|
751
|
+
sources are checked: what breaks silently (the platform files, the calls in the entry point,
|
|
752
|
+
keys in the code, alert instead of a dialog) is printed as a list and nothing starts. What is
|
|
753
|
+
merely unfinished (the template page title, no icon) goes through and prints a line
|
|
754
|
+
${(0, ui_1.bold)('Unfinished:')}. Fix it before telling the person the app is done.
|
|
755
|
+
|
|
756
|
+
${(0, ui_1.bold)('deploy functions')} bundles everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. A function whose
|
|
757
|
+
code and variables did not change is left alone, and one that fails to ship fails the whole
|
|
758
|
+
deploy. It then records the callers and prints which functions are open to the whole
|
|
759
|
+
internet now. A change that touches data and screens goes in this order: the migration,
|
|
760
|
+
the functions, then every app that shows it; an app deploy warns when the root holds
|
|
761
|
+
changes nobody deployed.
|
|
762
|
+
|
|
763
|
+
A function gone from the sources would be deleted from the cloud along with its keys and
|
|
764
|
+
schedules, and that is final. So the deploy does not start at all until you say yes. In a terminal the CLI names the functions
|
|
728
765
|
and asks; without one (CI, an agent) it stops and ${(0, ui_1.bold)('--allow-removals')} is the only way
|
|
729
766
|
to agree. If the sources hold no functions at all while the cloud holds several, nothing
|
|
730
767
|
is deleted and nothing is asked: that looks like a directory which never made it (a
|
|
@@ -733,60 +770,26 @@ ${(0, ui_1.bold)('functions/')} line in .xflowignore) rather than a decision.
|
|
|
733
770
|
The database is not part of this: migrations change data in ways nothing can undo, so
|
|
734
771
|
they stay their own command (${(0, ui_1.bold)('xflow db migrate')}).
|
|
735
772
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
Lays down the platform template: React on Vite, Tailwind, a set of components
|
|
751
|
-
(${(0, ui_1.bold)('src/components/ui')}) and ready-made blocks (${(0, ui_1.bold)('src/components/blocks')}): app
|
|
752
|
-
shell, page header, section, toolbar, list, empty state, stats, detail card, form rows,
|
|
753
|
-
card grid, timeline, table, charts. Projects created in the web get the same template,
|
|
754
|
-
which is what makes the applications look alike.
|
|
755
|
-
|
|
756
|
-
Building the interface on top of these components and the tokens in ${(0, ui_1.bold)('src/index.css')}
|
|
757
|
-
is not a formality: your own palette on top of them looks foreign inside the platform.
|
|
773
|
+
A build of an app is not shown to visitors until ${(0, ui_1.bold)('xflow publish')}: publishing points the
|
|
774
|
+
app address at a build and takes a second, and an older kept build is the rollback. Only
|
|
775
|
+
the app moves: the functions and the database are one per project and stay as they are.
|
|
776
|
+
The very first build of a new app publishes itself. The files of the three latest builds
|
|
777
|
+
are kept; ${(0, ui_1.bold)('xflow deployments')} marks the rest "no pages". Give a person the app address
|
|
778
|
+
the CLI prints, and name builds by their number.`,
|
|
779
|
+
init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project with its first app
|
|
780
|
+
|
|
781
|
+
Creates the project on the platform and lays out its folder: the root xflow.json, a
|
|
782
|
+
package.json for the functions, empty ${(0, ui_1.bold)('functions/')} and ${(0, ui_1.bold)('migrations/')}, and the first app in
|
|
783
|
+
apps/<folder> from the platform template: React on Vite, Tailwind, components in
|
|
784
|
+
${(0, ui_1.bold)('src/components/ui')} and blocks in ${(0, ui_1.bold)('src/components/blocks')}. Those are a starting set:
|
|
785
|
+
change them, restyle them or delete what you do not use.
|
|
758
786
|
|
|
759
787
|
--name <name> project name (the folder name by default)
|
|
760
|
-
--
|
|
761
|
-
--
|
|
762
|
-
--
|
|
763
|
-
--server <name> a new database for the project, right away on that server:
|
|
788
|
+
--app <folder> the folder of the first app (made from the name by default)
|
|
789
|
+
--no-app a project without apps: its functions are workflows
|
|
790
|
+
--server <name> the database of the project, right away on that server:
|
|
764
791
|
platform, or an own server of the organization (xflow db list)
|
|
765
792
|
|
|
766
|
-
${(0, ui_1.bold)('
|
|
767
|
-
|
|
768
|
-
application of the organization calls it; who exactly is allowed in is decided by
|
|
769
|
-
${(0, ui_1.bold)('minRole')} inside the function. It builds and releases with the same deploy, but
|
|
770
|
-
there are no pages to publish, so publish and rollback do not apply to it.
|
|
771
|
-
|
|
772
|
-
Without either flag the project gets its database with the first migration, on the
|
|
773
|
-
platform server. ${(0, ui_1.bold)('--server')} decides it up front: the server has to be running, and
|
|
774
|
-
the database counts against the plan at once.
|
|
775
|
-
|
|
776
|
-
The order: the project is created on the platform first, because without it there is
|
|
777
|
-
nowhere to get the token for .env. If writing the files fails, the project stays empty
|
|
778
|
-
and the CLI explains how to pick it up with the link command.`,
|
|
779
|
-
rollback: `${(0, ui_1.bold)('xflow rollback')} <version number>: serve the pages of an earlier build
|
|
780
|
-
|
|
781
|
-
Points the project at the pages of that build. ${(0, ui_1.bold)('Only the pages come back.')} Cloud
|
|
782
|
-
functions and the database are one per project: they are not versioned, they are shared
|
|
783
|
-
with the published application, and they stay exactly as they are now. A page from August
|
|
784
|
-
will be talking to today's functions.
|
|
785
|
-
|
|
786
|
-
The sources stay at their own revision too. Fetching the code of that build is a separate
|
|
787
|
-
command (${(0, ui_1.bold)('xflow pull --revision N --into ../old-version')}, keep the copy outside the
|
|
788
|
-
project folder). Building that code over the current one is a further, deliberate step: it
|
|
789
|
-
replaces the server revision, and whatever is newer on the platform goes away.
|
|
790
|
-
|
|
791
|
-
Visitors keep seeing the published pages until ${(0, ui_1.bold)('xflow publish')} is run.`,
|
|
793
|
+
More apps later: ${(0, ui_1.bold)('xflow apps add <folder>')}. If writing the files fails, the project stays
|
|
794
|
+
on the platform and the CLI says how to pick it up with the link command.`,
|
|
792
795
|
};
|