@getxflow/cli 0.19.0 → 0.20.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/dist/help.js +4 -3
- package/dist/session.js +5 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +196 -238
package/dist/help.js
CHANGED
|
@@ -593,9 +593,10 @@ old copy.`,
|
|
|
593
593
|
init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project
|
|
594
594
|
|
|
595
595
|
Lays down the platform template: React on Vite, Tailwind, a set of components
|
|
596
|
-
(${(0, ui_1.bold)('src/components/ui')}) and ready-made blocks (${(0, ui_1.bold)('src/components/blocks')}):
|
|
597
|
-
|
|
598
|
-
|
|
596
|
+
(${(0, ui_1.bold)('src/components/ui')}) and ready-made blocks (${(0, ui_1.bold)('src/components/blocks')}): app
|
|
597
|
+
shell, page header, section, toolbar, list, empty state, stats, detail card, form rows,
|
|
598
|
+
card grid, timeline, table, charts. Projects created in the web get the same template,
|
|
599
|
+
which is what makes the applications look alike.
|
|
599
600
|
|
|
600
601
|
Building the interface on top of these components and the tokens in ${(0, ui_1.bold)('src/index.css')}
|
|
601
602
|
is not a formality: your own palette on top of them looks foreign inside the platform.
|
package/dist/session.js
CHANGED
|
@@ -31,7 +31,11 @@ function connect(config, options) {
|
|
|
31
31
|
...(0, credentials_1.storedKeyInputs)(apiUrl),
|
|
32
32
|
});
|
|
33
33
|
if (!picked) {
|
|
34
|
-
throw new errors_1.CliError(`No access key for ${apiUrl}`,
|
|
34
|
+
throw new errors_1.CliError(`No access key for ${apiUrl}`,
|
|
35
|
+
// Who does it, not where the key comes from: the key is issued by the sign-in
|
|
36
|
+
// itself and by nothing else, so an agent reading this has to hand the step over
|
|
37
|
+
// instead of hunting for a value to paste.
|
|
38
|
+
'Sign in: xflow login (needs a person with a browser). In CI pass the key in XFLOW_TOKEN');
|
|
35
39
|
}
|
|
36
40
|
if ('missing' in picked) {
|
|
37
41
|
throw new errors_1.CliError(`This folder is bound to the organization ${picked.organizationId}, and there is no stored key for it`, 'Sign in to that organization: xflow login. The stored ones: xflow org');
|
package/dist/version.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_API_URL = exports.CLI_VERSION = void 0;
|
|
4
4
|
/** Keep in sync with cli/package.json. */
|
|
5
|
-
exports.CLI_VERSION = '0.
|
|
5
|
+
exports.CLI_VERSION = '0.20.0';
|
|
6
6
|
/** Overridden by XFLOW_API_URL or the `api` field in xflow.json. */
|
|
7
7
|
exports.DEFAULT_API_URL = 'https://app.getxflow.com';
|
package/package.json
CHANGED
package/skills/xflow/SKILL.md
CHANGED
|
@@ -5,15 +5,13 @@ description: Build, deploy and publish apps on the XFlow platform with the xflow
|
|
|
5
5
|
|
|
6
6
|
# XFlow
|
|
7
7
|
|
|
8
|
-
Hosting for web apps. The code lives in an ordinary repository on the developer
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
the `xflow.json` file in its root.
|
|
8
|
+
Hosting for web apps. The code lives in an ordinary repository on the developer machine,
|
|
9
|
+
`xflow deploy` sends the sources, and the platform builds them in a clean sandbox and serves
|
|
10
|
+
the result as static files. A project is recognized by `xflow.json` in its root.
|
|
12
11
|
|
|
13
|
-
No `xflow.json` means the folder is not bound to a project yet
|
|
14
|
-
|
|
15
|
-
folder
|
|
16
|
-
a new project from the platform template.
|
|
12
|
+
No `xflow.json` means the folder is not bound to a project yet: `xflow projects list` names the
|
|
13
|
+
projects of the organization with their ids, `xflow link <id>` binds the folder to one (an empty
|
|
14
|
+
folder also gets the sources), and `xflow init` starts a new project from the template.
|
|
17
15
|
|
|
18
16
|
## First rule
|
|
19
17
|
|
|
@@ -24,21 +22,17 @@ contains the fix.
|
|
|
24
22
|
|
|
25
23
|
## Keeping these instructions current
|
|
26
24
|
|
|
27
|
-
These instructions ship with xflow CLI 0.
|
|
28
|
-
|
|
29
|
-
is visible in the text itself.
|
|
25
|
+
These instructions ship with xflow CLI 0.20.0, so the copy you are reading can be older than
|
|
26
|
+
the CLI answering your commands.
|
|
30
27
|
|
|
31
28
|
When the CLI says this file does not match it, run `xflow skills --refresh` and then **read
|
|
32
|
-
again the file it names
|
|
33
|
-
|
|
34
|
-
repair is yours to make, no need to ask anyone.
|
|
29
|
+
again the file it names**: refreshing only writes to disk, and until you re-read it you are
|
|
30
|
+
still working from the old rules. Do it yourself, no need to ask.
|
|
35
31
|
|
|
36
|
-
Updating the CLI
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
32
|
+
Updating the CLI is not yours. `xflow update` replaces a program shared by every project on
|
|
33
|
+
the machine, and most installations only print the command for a person: offer the update in
|
|
34
|
+
your summary. The exception is a platform refusing the CLI as too old: nothing runs until it
|
|
35
|
+
is updated, so say what you are doing and run it.
|
|
42
36
|
|
|
43
37
|
## Hard rules
|
|
44
38
|
|
|
@@ -56,8 +50,9 @@ the error points away from the cause. The sections below carry the details.
|
|
|
56
50
|
schedules, and a function created again later gets a different address.
|
|
57
51
|
4. An already applied migration is never re-run, so editing its file changes
|
|
58
52
|
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
|
-
|
|
53
|
+
5. When inserts start failing with `db_write_locked`, the database is over its plan size
|
|
54
|
+
and Postgres itself refuses. Reads and deletes keep working, so the fix is a migration
|
|
55
|
+
that deletes data, never a rewrite of the SQL. Write comes back within the hour.
|
|
61
56
|
6. In file storage, call `confirm` only after the PUT has finished: the platform reads
|
|
62
57
|
the real size from storage, so an early call answers that the file is not there.
|
|
63
58
|
7. Heavy media does not travel with the sources. Photos, video and PDFs go up with
|
|
@@ -65,41 +60,31 @@ the error points away from the cause. The sections below carry the details.
|
|
|
65
60
|
|
|
66
61
|
## Plan limits
|
|
67
62
|
|
|
68
|
-
The organization runs on a plan with finite limits: projects, databases,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
cap
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
code changes nothing: tell the user what ran out and stop. Only the owner or an admin
|
|
94
|
-
lifts it, in the web interface, by freeing the resource or moving to a bigger plan.
|
|
95
|
-
|
|
96
|
-
A schedule refused for running too often is one of these too, not a bad cron string: the
|
|
97
|
-
minimum interval comes from the plan, so widen the interval or say what the plan allows.
|
|
98
|
-
The number is in `xflow whoami`, never guess it.
|
|
99
|
-
|
|
100
|
-
On `db_write_locked` (hard rule 5) it is Postgres itself refusing, not the platform. Reads
|
|
101
|
-
and deletes keep working, which is what makes a deleting migration the way out. Write comes
|
|
102
|
-
back within an hour of the data going under the limit.
|
|
63
|
+
The organization runs on a plan with finite limits: projects, databases, functions, active
|
|
64
|
+
connections, seats, database and file storage, function minutes, builds per month and at
|
|
65
|
+
once, schedules and how often they may run. `xflow whoami` prints every one of them next to
|
|
66
|
+
what is used, and the rights of your own key with them: destroying data in a migration,
|
|
67
|
+
deleting files from storage and linking a connected account each need a right that is off by
|
|
68
|
+
default. Reading it before a long task turns a refusal halfway through the work into a
|
|
69
|
+
sentence said before it starts.
|
|
70
|
+
|
|
71
|
+
Three of them behave unlike the rest:
|
|
72
|
+
|
|
73
|
+
- A database outlives its project (deleting a project only detaches it), so a refused
|
|
74
|
+
database usually means an orphan: delete the unused one in the "Data" section of the web
|
|
75
|
+
interface.
|
|
76
|
+
- Connections are counted while active: switching an unused one off frees the seat without
|
|
77
|
+
deleting it.
|
|
78
|
+
- The cap on how long one function call may run is written into the function when it is
|
|
79
|
+
deployed, so a plan change reaches the cloud only with the next `xflow deploy` of each
|
|
80
|
+
project, in both directions.
|
|
81
|
+
|
|
82
|
+
A limit refusal is not a bad request. The CLI prints a line starting with `Plan limit:`, the
|
|
83
|
+
API answers `code: "forbidden"` with a `limit` object (`code`, `used`, `limit`), MCP tools
|
|
84
|
+
carry the same field. Retrying, renaming or rewriting the code changes nothing: tell the user
|
|
85
|
+
what ran out and stop, only the owner or an admin lifts it. A schedule refused for running too
|
|
86
|
+
often is one of these too, not a bad cron string: the minimum interval comes from the plan,
|
|
87
|
+
never guess it.
|
|
103
88
|
|
|
104
89
|
## Workflow
|
|
105
90
|
|
|
@@ -116,16 +101,13 @@ back within an hour of the data going under the limit.
|
|
|
116
101
|
browser for them.
|
|
117
102
|
6. `xflow publish` makes that same version visible to visitors.
|
|
118
103
|
|
|
119
|
-
The split is deliberate:
|
|
120
|
-
|
|
121
|
-
the very first version of a project: it publishes automatically, since there is no
|
|
122
|
-
live version to protect yet.
|
|
104
|
+
The split is deliberate: until `publish` runs, visitors keep seeing the previous pages. The
|
|
105
|
+
one exception is the very first version of a project, which publishes automatically.
|
|
123
106
|
|
|
124
|
-
`xflow status` says how the local copy differs from the server revision.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
deploy again. `--force` destroys that work, a last resort rather than a way around the error.
|
|
107
|
+
`xflow status` says how the local copy differs from the server revision. A deploy rejected
|
|
108
|
+
before the build starts means the server holds a version this folder has never seen: somebody
|
|
109
|
+
deployed first, or the folder was linked elsewhere. Fetch it next to yours (`xflow pull --into
|
|
110
|
+
./server-copy`), merge in git, deploy again. `--force` destroys that work.
|
|
129
111
|
|
|
130
112
|
**Only pages are versioned.** Cloud functions and the database are one per project: they
|
|
131
113
|
are not versioned, and dev and live share them. So `xflow deploy` changes the running
|
|
@@ -133,35 +115,29 @@ application the moment it finishes, before any `publish`, and `publish` moves th
|
|
|
133
115
|
only. Tell the user this when a deploy touches `functions/`: there is no staging step for
|
|
134
116
|
server code.
|
|
135
117
|
|
|
136
|
-
Rolling back: `xflow deployments` lists the history, `xflow rollback <id>` points the project
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
`--force` and a human typing the project name, so you cannot do it on your own.
|
|
118
|
+
Rolling back: `xflow deployments` lists the history, `xflow rollback <id>` points the project at
|
|
119
|
+
the pages of an earlier build. Only the pages come back and they talk to today's functions. The
|
|
120
|
+
code of that build is a separate fetch (`xflow pull --revision N --into ../old-version`, kept
|
|
121
|
+
outside the project folder); building it over the current one needs a human.
|
|
141
122
|
|
|
142
|
-
Pages are kept for the **three latest successful builds** plus whatever dev and live show
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
active project it is a matter of hours, so it is not a backup: keep anything you must not
|
|
148
|
-
lose in your own git repository.
|
|
123
|
+
Pages are kept for the **three latest successful builds** plus whatever dev and live show;
|
|
124
|
+
older versions stay in the history with `pages_kept` false, readable but not openable. The
|
|
125
|
+
history holds the **last 30 builds**, the sources the **last 50 revisions** (a revision a live
|
|
126
|
+
version was built from is never deleted). Three builds is the whole rollback depth, hours on an
|
|
127
|
+
active project: keep anything you must not lose in your own git repository.
|
|
149
128
|
|
|
150
129
|
**The only link you give a person is the project page**, `https://app.getxflow.com/projects/<id>`,
|
|
151
|
-
which the CLI prints for you. Refer to builds by their number ("version 481203 is built,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
holding that link then stares at a frozen app and concludes the changes never shipped. The
|
|
156
|
-
project page always shows the current state, and every version is reachable from it.
|
|
130
|
+
which the CLI prints for you. Refer to builds by their number ("version 481203 is built, 092399
|
|
131
|
+
is what visitors see"), never by address: a build address has the version baked into it and
|
|
132
|
+
after the next publish keeps answering with the old copy, so whoever holds it stares at a
|
|
133
|
+
frozen app and concludes nothing shipped. The project page always shows the current state.
|
|
157
134
|
|
|
158
135
|
## Build gate
|
|
159
136
|
|
|
160
|
-
Before the sandbox starts, the platform checks the sources against the template. Every
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
that already satisfies these rules instead of learning them from rejections.
|
|
137
|
+
Before the sandbox starts, the platform checks the sources against the template. Every rule
|
|
138
|
+
below blocks the build, and violations come back together with files and line numbers, up to
|
|
139
|
+
fifty at a time. A rejected attempt costs nothing, the sandbox never starts. Write code that
|
|
140
|
+
satisfies these rules instead of learning them from rejections.
|
|
165
141
|
|
|
166
142
|
Build setup:
|
|
167
143
|
|
|
@@ -198,9 +174,9 @@ Interface rules, checked outside `src/components/ui` and `src/components/blocks`
|
|
|
198
174
|
`react-hot-toast`, `react-dnd`) must have it mounted in `App.tsx`. Missing providers
|
|
199
175
|
build fine and give visitors a white screen.
|
|
200
176
|
|
|
201
|
-
Template integrity.
|
|
202
|
-
|
|
203
|
-
|
|
177
|
+
Template integrity. Part of the platform template is not yours to change. The reference is a
|
|
178
|
+
snapshot of the project itself, taken when the platform first looked at it, so these rules
|
|
179
|
+
never argue with work that was already there:
|
|
204
180
|
|
|
205
181
|
- Platform files must stay byte for byte as they arrived: `src/lib/theme-sync.ts`,
|
|
206
182
|
`src/lib/platform-auth.ts`, `src/contexts/platform-auth-context.tsx`,
|
|
@@ -225,14 +201,13 @@ A rejection names the file and the revision to take the original from:
|
|
|
225
201
|
## Cloud functions
|
|
226
202
|
|
|
227
203
|
Server-side code lives in `functions/<name>/index.ts` and exports `handler` (`index.js` and
|
|
228
|
-
`index.mjs` are read too). The directory name is the name of the function, and the build gate
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
build stops before the sandbox rather than halfway through it.
|
|
204
|
+
`index.mjs` are read too). The directory name is the name of the function, and the build gate is
|
|
205
|
+
strict about it: lowercase latin letters, digits and hyphens, starting with a letter or a digit,
|
|
206
|
+
up to 41 characters. `send-mail` passes, `sendMail` and `send_mail` do not.
|
|
232
207
|
|
|
233
208
|
There is no separate deploy command: `xflow deploy` ships the functions and then builds the
|
|
234
|
-
application, in that order.
|
|
235
|
-
|
|
209
|
+
application, in that order. `xflow functions list` shows what is live. The handler returns
|
|
210
|
+
`{ statusCode, body }` where `body` is a JSON string.
|
|
236
211
|
|
|
237
212
|
Keep one shape inside that string across the whole project: `{ success: true, data }`
|
|
238
213
|
when it worked, `{ success: false, error: { message, code } }` when it did not. Nothing
|
|
@@ -240,44 +215,32 @@ enforces this, but a project where every function answers its own way costs an a
|
|
|
240
215
|
on every call. Branch the frontend on `error.code`, never on `error.message`: wording
|
|
241
216
|
gets rewritten on any edit, a code does not.
|
|
242
217
|
|
|
243
|
-
How a non-2xx answer reaches the page depends on the generation of `src/lib/xflow.ts
|
|
244
|
-
|
|
245
|
-
`
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
`--data-file body.json` sends the same from a file, `--json` prints the body alone), and
|
|
265
|
-
`xflow functions logs <name>` shows the failures, each with its stack and the console
|
|
266
|
-
output of that call. Only failed calls are
|
|
267
|
-
logged, so an empty output means the function never crashed, not that logging is broken.
|
|
268
|
-
|
|
269
|
-
The body has to be JSON and the CLI checks it before calling. In PowerShell the double
|
|
270
|
-
quotes never reach the CLI: `--data '{"a":1}'` arrives as `{a:1}`, so escape them
|
|
271
|
-
(`--data '{\"a\":1}'`) or pass a file. This is a shell habit, not a bug in the function, and
|
|
272
|
-
it bites `--payload` of a schedule the same way. A command that worked writes nothing to
|
|
273
|
-
stderr, so a red line in PowerShell means it really failed; `--json` prints the payload and
|
|
274
|
-
nothing else.
|
|
218
|
+
How a non-2xx answer reaches the page depends on the generation of `src/lib/xflow.ts`, so read
|
|
219
|
+
that file before writing the catch: a copy declaring `XFlowError` throws it with `code` and
|
|
220
|
+
`status`, an older copy throws a plain `Error` whose message is all there is. In an older
|
|
221
|
+
project, a function whose error text must be seen answers `200` with `{ success: false, error }`.
|
|
222
|
+
|
|
223
|
+
The sources are the whole truth about which functions exist: a deleted directory takes the
|
|
224
|
+
function and its schedules out of the cloud, undoably, and a function created again later gets
|
|
225
|
+
a different address. Such a deploy refuses until somebody agrees, and `--allow-removals` is the
|
|
226
|
+
only way past it. Reaching for that flag to get a build running means you deleted something the
|
|
227
|
+
user never asked you to delete: put the directories back. If the removal is intended, name the
|
|
228
|
+
functions about to go and let the user answer.
|
|
229
|
+
|
|
230
|
+
Debugging a deployed function is two commands: `xflow functions invoke <name>` calls it the way
|
|
231
|
+
the app does and prints status, timing and body (`--data '{"a":1}'`, `--data-file body.json`,
|
|
232
|
+
`--json` for the body alone), and `xflow functions logs <name>` shows the failures with stacks
|
|
233
|
+
and console output. Only failed calls are logged, so empty output means it never crashed.
|
|
234
|
+
|
|
235
|
+
The body has to be JSON and the CLI checks it before calling. In PowerShell the double quotes
|
|
236
|
+
never reach the CLI (`--data '{"a":1}'` arrives as `{a:1}`): escape them (`--data '{\"a\":1}'`)
|
|
237
|
+
or pass a file. It bites `--payload` of a schedule the same way. A command that worked writes
|
|
238
|
+
nothing to stderr, so a red line there means it really failed.
|
|
275
239
|
|
|
276
240
|
From the app, call a function through `src/lib/xflow.ts`:
|
|
277
|
-
`await xflow.functions.invoke('send-mail', { body: { to } })
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
from the application that calls it.
|
|
241
|
+
`await xflow.functions.invoke('send-mail', { body: { to } })`, which carries the credentials for
|
|
242
|
+
you. Addresses are baked into the build, which is why functions go out first: by the time the
|
|
243
|
+
bundle is built they already exist.
|
|
281
244
|
|
|
282
245
|
### Who is calling
|
|
283
246
|
|
|
@@ -318,13 +281,12 @@ and `X-Server-Key` with `process.env.XFLOW_SERVER_KEY`. The token is the ticket
|
|
|
318
281
|
project and the key is the identity; the wrapper checks the ticket first, so the key alone
|
|
319
282
|
answers 401.
|
|
320
283
|
|
|
321
|
-
An outside service (a
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
consumer.
|
|
284
|
+
An outside service (a payment webhook, a bot, a CRM) has no person behind it and needs a key of
|
|
285
|
+
that one function. Keys are not issued by default and the CLI cannot create one: a human issues
|
|
286
|
+
it in the project settings, «Облачные функции» → the function → «Настройки». Ask the user to do
|
|
287
|
+
that and to paste the address back — never invent another way in. Send it in the
|
|
288
|
+
`X-Function-Key` header. A function holds at most two keys, the second one to replace the first
|
|
289
|
+
without downtime, not to serve a second consumer.
|
|
328
290
|
|
|
329
291
|
`xflow functions list` shows who can reach each function: `in-app only` (no keys, answers
|
|
330
292
|
only inside the application) or `external (N keys)` (a human issued external access). Key
|
|
@@ -333,33 +295,29 @@ values are never shown there.
|
|
|
333
295
|
## Secrets and connected accounts
|
|
334
296
|
|
|
335
297
|
Keys and passwords live on the platform, not in the repository: `xflow env set SMTP_PASSWORD=…`
|
|
336
|
-
writes one, `xflow env` lists the names, `xflow env check`
|
|
337
|
-
|
|
338
|
-
|
|
298
|
+
writes one, `xflow env` lists the names, `xflow env check` names the variables your functions
|
|
299
|
+
read but the platform does not have. Values never come back out: the only place they exist is
|
|
300
|
+
inside the running function.
|
|
339
301
|
|
|
340
302
|
A variable goes to the whole organization unless you say otherwise: `--scope project` keeps it
|
|
341
303
|
to this project alone, `--scope organization` is the default, and a third word is refused
|
|
342
304
|
rather than read as the default. Pick the narrow one for anything that belongs to one
|
|
343
305
|
application.
|
|
344
306
|
|
|
345
|
-
These commands see only what this project can see: variables
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
and refuses. If a call to that service starts failing with an authorisation error, run
|
|
360
|
-
`xflow status`: it says whether the token is merely expiring (any build renews it) or the
|
|
361
|
-
account was disconnected on the provider's side, which only a human can fix by reconnecting
|
|
362
|
-
it in the platform settings.
|
|
307
|
+
These commands see only what this project can see: organization-wide variables and the ones
|
|
308
|
+
bound to this project. One bound to another project is invisible here, so `env rm` reports it
|
|
309
|
+
missing even though names are unique within the organization.
|
|
310
|
+
|
|
311
|
+
Hence hard rules 1 and 2: a function receives only the variables it mentions by name, and the
|
|
312
|
+
platform reads those names out of the text of the code, so an opaque read would ship a function
|
|
313
|
+
whose secret never arrives. A value written today rides the next `xflow deploy`.
|
|
314
|
+
|
|
315
|
+
Some variables come from a connected account instead of from you. Credentials of an account
|
|
316
|
+
linked to the project show up in `xflow env` marked read-only, under a prefix chosen at link
|
|
317
|
+
time (`YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_CLIENT_LOGIN`): read them like any other variable,
|
|
318
|
+
`xflow env set` on those names is refused. If calls to that service start failing on
|
|
319
|
+
authorisation, `xflow status` says whether the token is merely expiring (any build renews it)
|
|
320
|
+
or the account was disconnected on the provider's side, which only a human can reconnect.
|
|
363
321
|
|
|
364
322
|
`xflow connections` lists those accounts: the ones already linked to this project, with the
|
|
365
323
|
alias and the state of the access, and the ones the organization has but this project does
|
|
@@ -373,11 +331,10 @@ while a function still reads one of the variables, naming those functions, so re
|
|
|
373
331
|
list before reaching for `--force`. Under `--project` there is no folder to read: it
|
|
374
332
|
prints the variables that go and removes the link.
|
|
375
333
|
|
|
376
|
-
Linking needs the `connections:link` right on the key, and only accounts granted to the
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
grant it to itself. Connecting a new account and switching one off stay with a person too.
|
|
334
|
+
Linking needs the `connections:link` right on the key, and only accounts granted to the owner
|
|
335
|
+
of the key personally can be linked at all; the same right covers calling an unlinked account.
|
|
336
|
+
A key cannot grant it to itself, so ask the person to turn it back on where the key permissions
|
|
337
|
+
live. Connecting a new account and switching one off stay with a person too.
|
|
381
338
|
|
|
382
339
|
### Asking an API what it returns
|
|
383
340
|
|
|
@@ -406,10 +363,9 @@ linked or not: an invented name is left in the text as you typed it and the requ
|
|
|
406
363
|
anyway, into somebody's live account. `xflow env` is not that list. Only the hosts of that
|
|
407
364
|
connector are reachable and a refusal lists them.
|
|
408
365
|
|
|
409
|
-
**Ask for the shape, not the volume.** One day and `limit=1` is enough to learn what the
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
'.response.body | keys'` gives the field names without the body entering your context at all.
|
|
366
|
+
**Ask for the shape, not the volume.** One day and `limit=1` is enough to learn what the fields
|
|
367
|
+
are called, and the whole dataset belongs in a cloud function anyway. `--json | jq
|
|
368
|
+
'.response.body | keys'` gives the field names without the body entering your context.
|
|
413
369
|
|
|
414
370
|
The call goes out for real, against a live account: `POST` and `DELETE` change things there,
|
|
415
371
|
and nothing undoes that. Read the method of the endpoint before calling it, and ask the
|
|
@@ -457,23 +413,19 @@ You never write that variable yourself: `xflow env set DATABASE_URL=...` is refu
|
|
|
457
413
|
fills those in itself, and your value under one of them would shadow the real one.
|
|
458
414
|
|
|
459
415
|
The platform keeps no database history and no backups, so anything that destroys data
|
|
460
|
-
(`DROP TABLE`, `DROP COLUMN`, `TRUNCATE`, `DELETE FROM` without a condition) needs
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
now, and `xflow db query "select ..."` reads it inside a READ ONLY transaction (50 rows by
|
|
474
|
-
default, `--limit` raises that to 300). The table cuts long values to keep the columns
|
|
475
|
-
readable; `--json` prints them whole. Look before
|
|
476
|
-
you write a migration against a shared database.
|
|
416
|
+
(`DROP TABLE`, `DROP COLUMN`, `TRUNCATE`, `DELETE FROM` without a condition) needs both the
|
|
417
|
+
`--allow-destructive` flag and the right to destroy data on the key, which is off by default and
|
|
418
|
+
only its owner turns on. When the refusal is about the right, the flag changes nothing: say what
|
|
419
|
+
needs deleting and why, and let the person decide. With both in place the affected tables are
|
|
420
|
+
dumped first and kept for 7 days. Check with `--dry-run`.
|
|
421
|
+
|
|
422
|
+
One logical database can be shared by several projects, so your migration can break an app you
|
|
423
|
+
do not see: `xflow db status` lists applied migrations with no file in your repository (that is
|
|
424
|
+
somebody else's project), and `xflow db list` names who else is on yours. For the same reason
|
|
425
|
+
`migrations/` is not the schema. It says what you did; `xflow db schema [table]` says what is in
|
|
426
|
+
the database right now, and `xflow db query "select ..."` reads it inside a READ ONLY
|
|
427
|
+
transaction (50 rows by default, `--limit` up to 300; `--json` prints long values whole). Look
|
|
428
|
+
before you write a migration against a shared database.
|
|
477
429
|
|
|
478
430
|
## File storage
|
|
479
431
|
|
|
@@ -489,29 +441,28 @@ xflow storage ls --json # the same list, machine-readable
|
|
|
489
441
|
```
|
|
490
442
|
|
|
491
443
|
The caps that send them here (10 MB of sources, 15 MB unpacked) are about heavy media, not
|
|
492
|
-
about code: icons, fonts and small artwork
|
|
493
|
-
|
|
494
|
-
not re-uploaded on every deploy.
|
|
444
|
+
about code: icons, fonts and small artwork stay in the repository as usual. Everything heavy
|
|
445
|
+
goes to storage, metered against the organization plan and not re-uploaded on every deploy.
|
|
495
446
|
|
|
496
|
-
`push` prints the address of every file and skips what is already stored under the same path
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
447
|
+
`push` prints the address of every file and skips what is already stored under the same path and
|
|
448
|
+
size, so a repeat run after a broken connection is cheap; `--replace` overwrites a file whose
|
|
449
|
+
size differs. An address belongs to the record rather than to the bytes, so a replacement keeps
|
|
450
|
+
it and existing links keep working. Put those addresses into the code or into a table: nothing
|
|
451
|
+
fetches files back to a machine.
|
|
501
452
|
|
|
502
453
|
Deleting needs a right of its own, off by default (**Delete files**, where the key permissions
|
|
503
|
-
live
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
454
|
+
live), so `xflow storage rm` may answer that the key was not granted it. Ask the person to switch
|
|
455
|
+
it on and do not look for a way around: whatever the users of the application uploaded lives in
|
|
456
|
+
the same folders, and there is no undo. A folder first says how many files it holds and deletes
|
|
457
|
+
them only with `--yes`.
|
|
507
458
|
|
|
508
459
|
**Files that users of the application upload** go through a cloud function of your own. The
|
|
509
|
-
browser cannot reach storage directly: those endpoints take only the server key of the
|
|
510
|
-
|
|
511
|
-
|
|
460
|
+
browser cannot reach storage directly: those endpoints take only the server key of the project,
|
|
461
|
+
which the platform puts into the environment of your functions as `XFLOW_SERVER_KEY` and
|
|
462
|
+
nothing else holds — not the bundle, not `.env`, not `xflow env`.
|
|
512
463
|
|
|
513
|
-
So uploading is a function of your own
|
|
514
|
-
|
|
464
|
+
So uploading is a function of your own: it asks the platform for a one-time link, the browser
|
|
465
|
+
sends the bytes straight to storage, and a second call records the file:
|
|
515
466
|
|
|
516
467
|
```js
|
|
517
468
|
const link = await fetch(`${process.env.XFLOW_API_URL}/api/storage/project/upload-url`, {
|
|
@@ -527,14 +478,12 @@ const link = await fetch(`${process.env.XFLOW_API_URL}/api/storage/project/uploa
|
|
|
527
478
|
`confirm` takes the returned `s3Key` and answers with the file and its address; `delete`
|
|
528
479
|
takes that same `url`. Never pipe the bytes through the function itself.
|
|
529
480
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
call, and the application reads its files from there.
|
|
481
|
+
No endpoint lists files back to a function, so the address `confirm` returns is the only copy
|
|
482
|
+
this door gives you: write it into a table of your own in the same call.
|
|
533
483
|
|
|
534
|
-
That address never expires and is safe to store, but it is not
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
anyone can open.
|
|
484
|
+
That address never expires and is safe to store, but it is not public: it opens only for a
|
|
485
|
+
signed-in visitor with access to this project, the same rule that guards the application itself,
|
|
486
|
+
so it works on your pages and does nothing in an email.
|
|
538
487
|
|
|
539
488
|
Four things bite an upload that otherwise looks right:
|
|
540
489
|
|
|
@@ -559,28 +508,27 @@ A refusal comes back as `{ error, code }`. Branch on `code` (`invalid_name`, `fi
|
|
|
559
508
|
`quota_exceeded`, `not_uploaded`, `duplicate_name`, `forbidden_type`, `not_found`, …) and never on the text:
|
|
560
509
|
the wording is free to change, the code is not.
|
|
561
510
|
|
|
562
|
-
What the app may do with files is decided inside that function, because the page
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
key that reached a visitor lets them delete every file of the project.
|
|
511
|
+
What the app may do with files is decided inside that function, because the page can be edited
|
|
512
|
+
by whoever opened it. Never send the key to the frontend: the build gate stops on a key found in
|
|
513
|
+
the sources, and a key that reached a visitor deletes every file of the project.
|
|
566
514
|
|
|
567
515
|
## Organizations and keys
|
|
568
516
|
|
|
569
|
-
One key per organization, stored side by side rather than replacing each other:
|
|
570
|
-
|
|
571
|
-
|
|
517
|
+
One key per organization, stored side by side rather than replacing each other: `xflow org`
|
|
518
|
+
lists them, `xflow org switch <name|id>` changes the active one, `xflow help org` has the
|
|
519
|
+
precedence rules.
|
|
572
520
|
|
|
573
|
-
Inside a project folder there is nothing to switch: commands follow the organization the
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
521
|
+
Inside a project folder there is nothing to switch: commands follow the organization the folder
|
|
522
|
+
is bound to, whatever the active one is, which is what lets projects of two organizations work
|
|
523
|
+
side by side. The exception is `--project <id>`, taken by every command that can answer without
|
|
524
|
+
the working copy (`xflow help project` lists them): it ignores the folder whole, so a second
|
|
525
|
+
project needs no second checkout. `projects get` and `link` name the project by id as an
|
|
526
|
+
argument instead.
|
|
579
527
|
|
|
580
528
|
A "not found" on a project you know exists usually means the key belongs to another
|
|
581
|
-
organization
|
|
582
|
-
|
|
583
|
-
|
|
529
|
+
organization: ids are unique across the platform, so a wrong key can never reach somebody
|
|
530
|
+
else's project. Check `xflow org`; signing in to a missing organization is `xflow login`, and
|
|
531
|
+
that needs a person with a browser.
|
|
584
532
|
|
|
585
533
|
## Direct access without the terminal
|
|
586
534
|
|
|
@@ -605,18 +553,28 @@ through tool calls also burns the user's tokens for nothing.
|
|
|
605
553
|
|
|
606
554
|
## App design
|
|
607
555
|
|
|
608
|
-
The platform design system
|
|
609
|
-
|
|
556
|
+
The platform design system ships inside the project, and the app should look like a part
|
|
557
|
+
of the platform:
|
|
558
|
+
|
|
559
|
+
- `src/components/ui` — primitives: buttons, inputs, dialogs, tables, menus
|
|
560
|
+
- `src/components/blocks` — composed blocks: app shell, page header, section, toolbar with
|
|
561
|
+
filters, list, empty state, stats, detail card, form rows, card grid, timeline, table,
|
|
562
|
+
charts. Props are typed next to each block
|
|
563
|
+
- `src/index.css` — colour tokens
|
|
564
|
+
|
|
565
|
+
Take a block before writing your own layout, and colours from tokens (`bg-card`,
|
|
566
|
+
`text-muted-foreground`, `bg-success-soft`): a custom hex palette makes the app look
|
|
567
|
+
foreign inside the platform.
|
|
610
568
|
|
|
611
|
-
|
|
612
|
-
- `src/components/blocks` for composed blocks: `data-table` and `charts`
|
|
613
|
-
- `src/index.css` for color tokens
|
|
569
|
+
Screens that read as a product, not as a form:
|
|
614
570
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
the
|
|
571
|
+
- Every screen starts with `Page` and `PageHeader`; header, filters and content in one column.
|
|
572
|
+
- Draw all four states: loading, error, empty, data. An empty screen says what appears here and offers an action.
|
|
573
|
+
- Colour only when it communicates: status, action, error, success. Grey carries the structure.
|
|
574
|
+
- Density over air: people scan and compare. Numbers in a column, `tabular-nums`.
|
|
575
|
+
- Name the shape first: list, list with detail, dashboard, form, board.
|
|
576
|
+
- Seed 5-10 believable rows in the first migration: a screen with zero rows looks unfinished.
|
|
577
|
+
- Squint at the result: if the hierarchy blurs away, it is decoration, not structure.
|
|
620
578
|
|
|
621
579
|
The app runs inside the platform in an iframe and receives the theme and the current
|
|
622
580
|
user from it. The `usePlatformAuth()` hook gives the name, role, permissions and the
|