@getxflow/cli 0.1.8 → 0.1.10
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 +31 -30
- package/dist/api.js +14 -23
- package/dist/args.js +3 -11
- package/dist/bin.js +10 -9
- package/dist/commands/auth.js +48 -41
- package/dist/commands/db.js +21 -28
- package/dist/commands/deploy.js +21 -36
- package/dist/commands/env.js +22 -31
- package/dist/commands/functions.js +26 -48
- package/dist/commands/logs.js +7 -8
- package/dist/commands/mcp.js +20 -27
- package/dist/commands/projects.js +27 -39
- package/dist/commands/schedules.js +8 -8
- package/dist/commands/skills.js +24 -48
- package/dist/commands/sources.js +38 -47
- package/dist/config.js +13 -13
- package/dist/credentials.js +9 -10
- package/dist/errors.js +1 -1
- package/dist/help.js +215 -216
- package/dist/limits.js +68 -0
- package/dist/session.js +6 -10
- package/dist/template.js +13 -21
- package/dist/tree.js +5 -18
- package/dist/types.js +1 -8
- package/dist/ui.js +47 -25
- package/dist/version.js +3 -7
- package/dist/zip.js +22 -15
- package/package.json +22 -22
- package/skills/xflow/SKILL.md +19 -1
package/dist/help.js
CHANGED
|
@@ -3,265 +3,264 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.help = help;
|
|
4
4
|
const ui_1 = require("./ui");
|
|
5
5
|
const version_1 = require("./version");
|
|
6
|
-
/**
|
|
7
|
-
* Справка. Заменяет агенту машинную документацию: у CLI её роль играет `--help`,
|
|
8
|
-
* поэтому текст пишем так, чтобы по нему можно было работать, не открывая сайт.
|
|
9
|
-
*/
|
|
6
|
+
/** Help text. For agents this is the reference documentation. */
|
|
10
7
|
function help(topic) {
|
|
11
8
|
if (topic && TOPICS[topic]) {
|
|
12
9
|
(0, ui_1.out)(TOPICS[topic]);
|
|
13
10
|
return;
|
|
14
11
|
}
|
|
15
|
-
(0, ui_1.out)(`${(0, ui_1.bold)('xflow')} ${(0, ui_1.dim)(version_1.CLI_VERSION)}
|
|
16
|
-
|
|
17
|
-
${(0, ui_1.bold)('
|
|
18
|
-
xflow login
|
|
19
|
-
xflow init [
|
|
20
|
-
xflow templates
|
|
21
|
-
xflow link <id>
|
|
22
|
-
xflow skills [--global]
|
|
23
|
-
xflow mcp install
|
|
24
|
-
|
|
25
|
-
${(0, ui_1.bold)('
|
|
26
|
-
xflow status
|
|
27
|
-
xflow push [--force]
|
|
12
|
+
(0, ui_1.out)(`${(0, ui_1.bold)('xflow')} ${(0, ui_1.dim)(version_1.CLI_VERSION)} XFlow application hosting
|
|
13
|
+
|
|
14
|
+
${(0, ui_1.bold)('Getting started')}
|
|
15
|
+
xflow login sign in through the browser
|
|
16
|
+
xflow init [dir] new project from the platform template
|
|
17
|
+
xflow templates which templates are available
|
|
18
|
+
xflow link <id> link this folder to a project (id: xflow projects list)
|
|
19
|
+
xflow skills [--global] refresh the platform instructions for an AI agent
|
|
20
|
+
xflow mcp install give the agent platform access without a terminal
|
|
21
|
+
|
|
22
|
+
${(0, ui_1.bold)('Code')}
|
|
23
|
+
xflow status what is on the server and how the local copy differs
|
|
24
|
+
xflow push [--force] send the sources as a new revision
|
|
28
25
|
xflow pull [--into dir] [--revision N]
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
${(0, ui_1.bold)('
|
|
32
|
-
xflow deploy [--no-push]
|
|
33
|
-
xflow publish
|
|
34
|
-
xflow rollback
|
|
35
|
-
xflow deployments
|
|
36
|
-
|
|
37
|
-
${(0, ui_1.bold)('
|
|
38
|
-
xflow functions list
|
|
39
|
-
xflow functions deploy [
|
|
40
|
-
xflow functions invoke
|
|
41
|
-
|
|
42
|
-
xflow functions logs [
|
|
43
|
-
xflow schedules list
|
|
44
|
-
xflow schedules set
|
|
45
|
-
|
|
46
|
-
xflow schedules rm
|
|
47
|
-
xflow env [check]
|
|
48
|
-
xflow env set
|
|
49
|
-
xflow env rm
|
|
50
|
-
|
|
51
|
-
${(0, ui_1.bold)('
|
|
52
|
-
xflow db status
|
|
26
|
+
fetch the sources (the latest revision by default)
|
|
27
|
+
|
|
28
|
+
${(0, ui_1.bold)('Releasing')}
|
|
29
|
+
xflow deploy [--no-push] send the code, build on the platform, release to dev
|
|
30
|
+
xflow publish show the dev version to visitors
|
|
31
|
+
xflow rollback <version number> return the project to an earlier version
|
|
32
|
+
xflow deployments version history
|
|
33
|
+
|
|
34
|
+
${(0, ui_1.bold)('Functions')}
|
|
35
|
+
xflow functions list what is deployed
|
|
36
|
+
xflow functions deploy [name] build and ship a function
|
|
37
|
+
xflow functions invoke <name> [--data '{"a":1}']
|
|
38
|
+
call a function and print the answer
|
|
39
|
+
xflow functions logs [name] function crashes: stack and console output
|
|
40
|
+
xflow schedules list what runs on a timer
|
|
41
|
+
xflow schedules set <name> "0 3 ? * * *"
|
|
42
|
+
run a function on a schedule (UTC)
|
|
43
|
+
xflow schedules rm <name> take a schedule off
|
|
44
|
+
xflow env [check] function variables: what is stored, what is missing
|
|
45
|
+
xflow env set NAME=value store a variable
|
|
46
|
+
xflow env rm NAME delete a variable
|
|
47
|
+
|
|
48
|
+
${(0, ui_1.bold)('Database')}
|
|
49
|
+
xflow db status which migrations are applied and which are waiting
|
|
53
50
|
xflow db migrate [--dry-run] [--allow-destructive]
|
|
54
|
-
|
|
51
|
+
apply the migrations from migrations/*.sql
|
|
55
52
|
|
|
56
|
-
${(0, ui_1.bold)('
|
|
57
|
-
xflow logs [--limit N]
|
|
58
|
-
xflow projects list
|
|
59
|
-
xflow projects get [id]
|
|
60
|
-
xflow whoami
|
|
61
|
-
xflow logout
|
|
53
|
+
${(0, ui_1.bold)('Reference')}
|
|
54
|
+
xflow logs [--limit N] browser errors from the released application
|
|
55
|
+
xflow projects list projects of the organization
|
|
56
|
+
xflow projects get [id] project card
|
|
57
|
+
xflow whoami whose key this is and what it can do
|
|
58
|
+
xflow logout forget the key
|
|
62
59
|
|
|
63
|
-
${(0, ui_1.bold)('
|
|
64
|
-
XFLOW_TOKEN
|
|
65
|
-
XFLOW_API_URL
|
|
60
|
+
${(0, ui_1.bold)('Environment')}
|
|
61
|
+
XFLOW_TOKEN access key (for CI, instead of xflow login)
|
|
62
|
+
XFLOW_API_URL platform address when it is not app.getxflow.com
|
|
66
63
|
|
|
67
|
-
|
|
64
|
+
More about one command: xflow help <command>`);
|
|
68
65
|
}
|
|
69
66
|
const TOPICS = {
|
|
70
|
-
db: `${(0, ui_1.bold)('xflow db migrate')}
|
|
67
|
+
db: `${(0, ui_1.bold)('xflow db migrate')}: apply migrations
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
The files live in the repository: ${(0, ui_1.bold)('migrations/0001_init.sql')}, ${(0, ui_1.bold)('migrations/0002_orders.sql')}
|
|
70
|
+
and so on. The order comes from the file name, which is why the leading number is
|
|
71
|
+
required. Every migration runs in its own transaction, the history is kept in the
|
|
72
|
+
database itself, and anything already applied is not run again.
|
|
76
73
|
|
|
77
|
-
--dry-run
|
|
78
|
-
--allow-destructive
|
|
74
|
+
--dry-run report what would be applied without touching the database
|
|
75
|
+
--allow-destructive allow operations that destroy data
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
${(0, ui_1.bold)('DROP TABLE')}, ${(0, ui_1.bold)('DROP COLUMN')}, ${(0, ui_1.bold)('TRUNCATE')}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
${(0, ui_1.bold)('DROP DATABASE')}
|
|
77
|
+
About destructive ones. The platform keeps no database history and makes no backups,
|
|
78
|
+
so ${(0, ui_1.bold)('DROP TABLE')}, ${(0, ui_1.bold)('DROP COLUMN')}, ${(0, ui_1.bold)('TRUNCATE')} and ${(0, ui_1.bold)('DELETE FROM')} without a
|
|
79
|
+
condition are rejected unless the flag is given. With the flag, the contents of the
|
|
80
|
+
affected tables are dumped and kept for 7 days: that is a "caught it right away"
|
|
81
|
+
safety net, not a backup. ${(0, ui_1.bold)('DROP DATABASE')} is never allowed, because the database
|
|
82
|
+
is shared across the organization.
|
|
85
83
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
Editing an already applied file achieves nothing: the comparison is by name, not by
|
|
85
|
+
content. ${(0, ui_1.bold)('xflow db status')} lists such a file separately, so write a new migration
|
|
86
|
+
instead.
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
logs: `${(0, ui_1.bold)('xflow logs')}
|
|
88
|
+
One logical database can be attached to several projects, so the history may hold
|
|
89
|
+
migrations that are not in your repository. That is normal, but it also means your
|
|
90
|
+
migration can break somebody else's application.`,
|
|
91
|
+
logs: `${(0, ui_1.bold)('xflow logs')} and ${(0, ui_1.bold)('xflow functions logs')}: project errors
|
|
94
92
|
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
One stream per project: crashes of the released application in the browser and
|
|
94
|
+
crashes of the cloud functions. The last 200 entries are kept, older ones fall off.
|
|
97
95
|
|
|
98
|
-
--limit N
|
|
96
|
+
--limit N how many to show (10 by default)
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
A function reports its own crashes: Yandex exposes its logs only over gRPC, so the
|
|
99
|
+
wrapper catches the exception, carries the tail of the console with it (the last 40
|
|
100
|
+
${(0, ui_1.bold)('console.log')} lines of that call) and sends it to the platform. Successful calls
|
|
101
|
+
write nothing, otherwise every request would pay for it in latency.
|
|
104
102
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
${(0, ui_1.bold)('xflow functions invoke')}.
|
|
103
|
+
What never reaches this list: a crash while the module is starting (the function
|
|
104
|
+
never gets as far as the wrapper), going over 90 seconds, and running out of memory.
|
|
105
|
+
Those show up in the answer to ${(0, ui_1.bold)('xflow functions invoke')}.
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
env: `${(0, ui_1.bold)('xflow env')}
|
|
107
|
+
Browser errors are collected by ${(0, ui_1.bold)('src/utils/error-logger.ts')} of the template and
|
|
108
|
+
only from released addresses: a local ${(0, ui_1.bold)('npm run dev')} writes nothing here.`,
|
|
109
|
+
env: `${(0, ui_1.bold)('xflow env')}: environment variables of the functions
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
Keys, passwords and third-party addresses are kept by the platform, not by the
|
|
112
|
+
repository. A value goes up and never comes back: the only place to see it is inside
|
|
113
|
+
the function. So keep your own copy wherever you got it from.
|
|
116
114
|
|
|
117
|
-
xflow env
|
|
118
|
-
xflow env check
|
|
119
|
-
xflow env set SMTP_PASSWORD=…
|
|
120
|
-
xflow env rm SMTP_PASSWORD
|
|
115
|
+
xflow env what is stored (names, not values)
|
|
116
|
+
xflow env check what the functions of this project are missing
|
|
117
|
+
xflow env set SMTP_PASSWORD=… store
|
|
118
|
+
xflow env rm SMTP_PASSWORD delete
|
|
121
119
|
|
|
122
|
-
--scope project
|
|
120
|
+
--scope project visible to this project only, not the whole organization
|
|
123
121
|
|
|
124
|
-
${(0, ui_1.bold)('check')}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
${(0, ui_1.bold)('check')} reads the sources in ${(0, ui_1.bold)('functions/')} and looks for ${(0, ui_1.bold)('process.env.NAME')}
|
|
123
|
+
references. The same rule applies on deploy: a function receives only the variables it
|
|
124
|
+
mentions by name. A name assembled from an expression (${(0, ui_1.bold)("process.env['KEY_' + n]")})
|
|
125
|
+
never reaches the environment, so read variables literally.
|
|
128
126
|
|
|
129
|
-
|
|
130
|
-
${(0, ui_1.bold)('xflow functions deploy')}
|
|
131
|
-
|
|
132
|
-
schedules: `${(0, ui_1.bold)('xflow schedules')}
|
|
127
|
+
The value reaches the function on deploy, not at the moment it is stored: after
|
|
128
|
+
${(0, ui_1.bold)('env set')} you need ${(0, ui_1.bold)('xflow functions deploy')} for the functions involved. The same
|
|
129
|
+
after a delete: a function already deployed keeps the old value until its next deploy.`,
|
|
130
|
+
schedules: `${(0, ui_1.bold)('xflow schedules')}: running functions on a timer
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
A schedule is a Yandex timer trigger: it calls the function itself, with no
|
|
133
|
+
application and no browser involved.
|
|
136
134
|
|
|
137
|
-
xflow schedules set
|
|
138
|
-
xflow schedules set
|
|
139
|
-
xflow schedules rm
|
|
135
|
+
xflow schedules set report "0 3 ? * * *" every day at 03:00
|
|
136
|
+
xflow schedules set digest "0 */4 ? * * *" every 4 hours
|
|
137
|
+
xflow schedules rm report take off every schedule of a function
|
|
140
138
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
Six fields: ${(0, ui_1.bold)('minutes hours day-of-month month day-of-week year')}. Exactly one of
|
|
140
|
+
the day-of-month and day-of-week fields has to be ${(0, ui_1.bold)('?')}, which is not our quirk but
|
|
141
|
+
how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not understood.
|
|
144
142
|
|
|
145
|
-
--payload '{"
|
|
143
|
+
--payload '{"mode":"full"}' the body the function will receive
|
|
146
144
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
A scheduled run arrives at the handler as a POST with no headers, and the project
|
|
146
|
+
token is not checked on it: such an event cannot be forged from outside. The function
|
|
147
|
+
has to be deployed already, since the schedule points at it and not the other way round.
|
|
150
148
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
invoke: `${(0, ui_1.bold)('xflow functions invoke')}
|
|
149
|
+
A failed run shows up in ${(0, ui_1.bold)('xflow functions logs')}: nobody is watching a scheduled
|
|
150
|
+
function, so the wrapper reports a crash the same way it does on an ordinary call.`,
|
|
151
|
+
invoke: `${(0, ui_1.bold)('xflow functions invoke')} <name>: call a function
|
|
154
152
|
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
Calls it exactly the way the application does, with the X-Project-Token header. The
|
|
154
|
+
token is taken from the project card on the platform, no local .env is needed.
|
|
157
155
|
|
|
158
|
-
--data '{"a":1}'
|
|
159
|
-
--method GET
|
|
156
|
+
--data '{"a":1}' request body (the method becomes POST by default)
|
|
157
|
+
--method GET a different method
|
|
160
158
|
|
|
161
|
-
|
|
162
|
-
5xx:
|
|
163
|
-
${(0, ui_1.bold)('xflow functions logs
|
|
164
|
-
mcp: `${(0, ui_1.bold)('xflow mcp install')}
|
|
159
|
+
Prints the status, the response time and the body. A non-zero exit code on 4xx and
|
|
160
|
+
5xx: in CI such a call has to fail the step. The cause of a crash is shown by
|
|
161
|
+
${(0, ui_1.bold)('xflow functions logs <name>')}.`,
|
|
162
|
+
mcp: `${(0, ui_1.bold)('xflow mcp install')}: connect the agent to the platform directly
|
|
165
163
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
164
|
+
Writes the MCP server into the configuration of a client that has its own connect
|
|
165
|
+
command: today that is Claude Code, and for the rest the command prints what to enter
|
|
166
|
+
by hand. After that the agent runs the platform without a terminal: it inspects the
|
|
167
|
+
database, applies migrations, ships and calls functions, sets schedules, reads logs.
|
|
168
|
+
The commands stay: code, builds and releases are still done by the CLI, because
|
|
169
|
+
pushing sources through MCP means filling the agent context and spending your money.
|
|
172
170
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
The command takes the access key from your own login and does not print it: usually
|
|
172
|
+
the agent runs this command itself, and everything printed lands in its context and in
|
|
173
|
+
the chat history.
|
|
176
174
|
|
|
177
|
-
--show-token
|
|
175
|
+
--show-token print the key anyway (not while under an agent)
|
|
178
176
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
${(0, ui_1.bold)('project_id')},
|
|
182
|
-
skills: `${(0, ui_1.bold)('xflow skills')}
|
|
177
|
+
There is one key per person per organization and its scope is not narrowed: the agent
|
|
178
|
+
sees every project of the organization and names the one it needs. It takes the
|
|
179
|
+
project from the ${(0, ui_1.bold)('project_id')} argument, not from your folder, which it cannot see.`,
|
|
180
|
+
skills: `${(0, ui_1.bold)('xflow skills')}: platform instructions for an AI agent
|
|
183
181
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
An agent has no way to know about XFlow: the platform is not in its training. The
|
|
183
|
+
instructions explain how to release and publish, where the design system components
|
|
184
|
+
come from, and where to look for production errors. ${(0, ui_1.bold)('init')} and ${(0, ui_1.bold)('link')} lay them
|
|
185
|
+
down on their own, so this command is for refreshing them after a CLI update: the two
|
|
186
|
+
ship as one version.
|
|
188
187
|
|
|
189
|
-
|
|
188
|
+
The format is shared (agentskills.io) but the folders differ per tool, so we write all:
|
|
190
189
|
|
|
191
190
|
.claude/skills/xflow/SKILL.md Claude Code
|
|
192
191
|
.agents/skills/xflow/SKILL.md Codex, OpenClaw
|
|
193
|
-
.cursor/rules/xflow.mdc Cursor:
|
|
194
|
-
|
|
195
|
-
--global
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
push: `${(0, ui_1.bold)('xflow push')}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
.git, dist, build, .next,
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
(${(0, ui_1.bold)('xflow pull --into ./server-copy')}),
|
|
213
|
-
|
|
214
|
-
--force
|
|
215
|
-
|
|
216
|
-
(CI,
|
|
217
|
-
|
|
218
|
-
pull: `${(0, ui_1.bold)('xflow pull')}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
--into
|
|
224
|
-
--revision <N>
|
|
225
|
-
--force
|
|
226
|
-
deploy: `${(0, ui_1.bold)('xflow deploy')}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
--no-push
|
|
232
|
-
--force
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
${(0, ui_1.bold)('xflow publish')}.
|
|
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
|
-
${(0, ui_1.bold)('xflow publish')}.`,
|
|
192
|
+
.cursor/rules/xflow.mdc Cursor: it does not read the shared format
|
|
193
|
+
|
|
194
|
+
--global the same in the home folder, which makes the skill visible in every
|
|
195
|
+
project. There ~/.codex/skills is added as well, because Codex looks in
|
|
196
|
+
its shared catalog only inside a repository. The Cursor rule and
|
|
197
|
+
AGENTS.md stay per project, they have no global counterpart
|
|
198
|
+
|
|
199
|
+
Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up lazily, only when its
|
|
200
|
+
description matches the task, and "add a customers table" will not trigger it. AGENTS.md
|
|
201
|
+
is always read by the agent, which is why the pointer is appended there, at the end of
|
|
202
|
+
the file and once. ${(0, ui_1.bold)('CLAUDE.md')} is left alone: Claude Code reads .claude/skills anyway.`,
|
|
203
|
+
push: `${(0, ui_1.bold)('xflow push')}: send the sources
|
|
204
|
+
|
|
205
|
+
The whole working copy goes up at once, as one revision. What is not sent:
|
|
206
|
+
node_modules, .git, dist, build, .next, any .env, plus everything listed in
|
|
207
|
+
.xflowignore and in the ignore field of xflow.json.
|
|
208
|
+
|
|
209
|
+
If the server holds a revision newer than the one you worked from, the push is
|
|
210
|
+
rejected. That means somebody pushed before you: fetch their changes alongside
|
|
211
|
+
(${(0, ui_1.bold)('xflow pull --into ./server-copy')}), merge them in git on your side, and retry.
|
|
212
|
+
|
|
213
|
+
--force overwrites the server revision. Before that the CLI shows whose work you are
|
|
214
|
+
about to destroy and asks for confirmation by typing the project name. In a
|
|
215
|
+
non-interactive run (CI, an agent) there is no way to confirm: a version conflict has
|
|
216
|
+
to fail the build rather than silently destroy somebody else's work.`,
|
|
217
|
+
pull: `${(0, ui_1.bold)('xflow pull')}: fetch the sources
|
|
218
|
+
|
|
219
|
+
By default it fetches the latest revision into the project folder and refuses to write
|
|
220
|
+
into a non-empty one: the platform cannot merge changes, that is git's job.
|
|
221
|
+
|
|
222
|
+
--into <dir> unpack alongside, to compare
|
|
223
|
+
--revision <N> a specific revision (the list: xflow deployments)
|
|
224
|
+
--force overwrite the folder completely`,
|
|
225
|
+
deploy: `${(0, ui_1.bold)('xflow deploy')}: build and release
|
|
226
|
+
|
|
227
|
+
Two steps: sending the sources and building on the platform. The build command and the
|
|
228
|
+
output directory come from xflow.json (npm run build and dist by default).
|
|
229
|
+
|
|
230
|
+
--no-push do not send sources, build from the latest server revision
|
|
231
|
+
--force allow overwriting the server revision while sending
|
|
232
|
+
|
|
233
|
+
The platform builds, in a clean sandbox on one Node version for everybody, so "it
|
|
234
|
+
worked on my machine" no longer depends on your machine. Before the build the project
|
|
235
|
+
is checked against the template: mismatches are printed as a list and the build does
|
|
236
|
+
not start at all.
|
|
237
|
+
|
|
238
|
+
The built version is visible on the project page, and the CLI prints the link. Visitors
|
|
239
|
+
see it after ${(0, ui_1.bold)('xflow publish')}. The address of the build itself is not printed:
|
|
240
|
+
it carries the version number, and after the next publish such a link quietly serves an
|
|
241
|
+
old copy.`,
|
|
242
|
+
init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project
|
|
243
|
+
|
|
244
|
+
Lays down the platform template: React on Vite, Tailwind, a set of components
|
|
245
|
+
(${(0, ui_1.bold)('src/components/ui')}) and ready-made blocks (${(0, ui_1.bold)('src/components/blocks')}): table,
|
|
246
|
+
form, filters, kanban, charts. Projects created in the web get the same template, which
|
|
247
|
+
is what makes the applications look alike.
|
|
248
|
+
|
|
249
|
+
Building the interface on top of these components and the tokens in ${(0, ui_1.bold)('src/index.css')}
|
|
250
|
+
is not a formality: your own palette on top of them looks foreign inside the platform.
|
|
251
|
+
|
|
252
|
+
--name <name> project name (the folder name by default)
|
|
253
|
+
--template <id> a different template (the list: xflow templates)
|
|
254
|
+
--database <id> attach a logical database of the organization
|
|
255
|
+
|
|
256
|
+
The order: the project is created on the platform first, because without it there is
|
|
257
|
+
nowhere to get the token for .env. If writing the files fails, the project stays empty
|
|
258
|
+
and the CLI explains how to pick it up with the link command.`,
|
|
259
|
+
rollback: `${(0, ui_1.bold)('xflow rollback')} <version number>: return an earlier build
|
|
260
|
+
|
|
261
|
+
Switches the project to the chosen build. Only the build comes back: the sources stay
|
|
262
|
+
at their own revision, and returning them is a separate decision
|
|
263
|
+
(${(0, ui_1.bold)('xflow pull --revision N --into ./old-version')}).
|
|
264
|
+
|
|
265
|
+
Visitors keep seeing the published version until ${(0, ui_1.bold)('xflow publish')} is run.`,
|
|
267
266
|
};
|
package/dist/limits.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Plan limits in CLI output: whoami and command refusals. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.limitLine = limitLine;
|
|
5
|
+
exports.quotaRows = quotaRows;
|
|
6
|
+
const DENIAL_LABELS = {
|
|
7
|
+
limit_projects: 'projects',
|
|
8
|
+
limit_functions: 'cloud functions',
|
|
9
|
+
limit_builds: 'builds this month',
|
|
10
|
+
limit_seats: 'seats in the organization',
|
|
11
|
+
limit_schedule_interval: 'schedule frequency',
|
|
12
|
+
db_write_locked: 'database volume, writes are off',
|
|
13
|
+
plan_no_schedules: 'schedules',
|
|
14
|
+
subscription_blocked: 'the subscription is unpaid',
|
|
15
|
+
billing_unavailable: 'plan check',
|
|
16
|
+
};
|
|
17
|
+
/** Why repeating the same command will not help. */
|
|
18
|
+
function limitLine(detail) {
|
|
19
|
+
const label = DENIAL_LABELS[detail.code] ?? detail.code;
|
|
20
|
+
const numbers = detail.used !== undefined && detail.limit !== undefined
|
|
21
|
+
? `, ${detail.used} of ${detail.limit} used`
|
|
22
|
+
: '';
|
|
23
|
+
return `Plan limit: ${label}${numbers}. Repeating the same command will not help.`;
|
|
24
|
+
}
|
|
25
|
+
const QUOTA_ORDER = [
|
|
26
|
+
'projects',
|
|
27
|
+
'functions',
|
|
28
|
+
'developers',
|
|
29
|
+
'members',
|
|
30
|
+
'database_mb',
|
|
31
|
+
'files_mb',
|
|
32
|
+
'function_minutes',
|
|
33
|
+
'builds',
|
|
34
|
+
];
|
|
35
|
+
const QUOTA_LABELS = {
|
|
36
|
+
projects: 'Projects',
|
|
37
|
+
functions: 'Cloud functions',
|
|
38
|
+
developers: 'Developers',
|
|
39
|
+
members: 'Staff',
|
|
40
|
+
database_mb: 'Database',
|
|
41
|
+
files_mb: 'Files',
|
|
42
|
+
function_minutes: 'Function minutes',
|
|
43
|
+
builds: 'Builds this month',
|
|
44
|
+
};
|
|
45
|
+
/** Decimal units, 1000 MB per GB: matches how volumes are sold. */
|
|
46
|
+
function sizeLabel(mb) {
|
|
47
|
+
if (mb < 1000)
|
|
48
|
+
return `${Math.round(mb)} MB`;
|
|
49
|
+
const gb = mb / 1000;
|
|
50
|
+
return `${Number.isInteger(gb) ? String(gb) : gb.toFixed(1)} GB`;
|
|
51
|
+
}
|
|
52
|
+
function quotaValue(key, use) {
|
|
53
|
+
// Same unit on both sides: "0.2 of 15 GB", not "245 of 15360".
|
|
54
|
+
if (key.endsWith('_mb')) {
|
|
55
|
+
const used = use.limit >= 1000 ? (use.used / 1000).toFixed(1) : String(use.used);
|
|
56
|
+
return `${used} of ${sizeLabel(use.limit)}`;
|
|
57
|
+
}
|
|
58
|
+
return `${use.used} of ${use.limit}`;
|
|
59
|
+
}
|
|
60
|
+
/** Unknown keys stay visible: the server may add new ones. */
|
|
61
|
+
function quotaRows(quotas) {
|
|
62
|
+
const known = QUOTA_ORDER.filter((key) => quotas[key]);
|
|
63
|
+
const rest = Object.keys(quotas).filter((key) => !QUOTA_ORDER.includes(key));
|
|
64
|
+
return [...known, ...rest].map((key) => [
|
|
65
|
+
` ${QUOTA_LABELS[key] ?? key}`,
|
|
66
|
+
quotaValue(key, quotas[key]),
|
|
67
|
+
]);
|
|
68
|
+
}
|