@getxflow/cli 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@ else's code belongs next to it.
18
18
  |---|---|
19
19
  | `login` / `logout` / `whoami` | sign in through the browser, sign out, whose key this is and what it can do |
20
20
  | `init` / `link` | new project, link a folder to an existing one |
21
- | `status` / `push` / `pull` | state, sending and fetching sources |
21
+ | `status` / `pull` | state of the sources on the server and fetching them back |
22
22
  | `deploy` / `publish` / `rollback` / `deployments` | build, publish, roll back, version history |
23
23
  | `db status` / `db migrate` | migrations from `migrations/*.sql` with a gate on destructive ones |
24
24
  | `db schema` / `db query` | tables and columns, reading data in a read-only transaction |
package/dist/bin.js CHANGED
@@ -167,9 +167,6 @@ async function run(args) {
167
167
  case 'status':
168
168
  await (0, sources_1.status)();
169
169
  return;
170
- case 'push':
171
- await (0, sources_1.push)(rest);
172
- return;
173
170
  case 'pull':
174
171
  await (0, sources_1.pull)(rest);
175
172
  return;
@@ -100,6 +100,18 @@ async function startBuild(client, projectId, revision, allowRemovals) {
100
100
  });
101
101
  }
102
102
  }
103
+ /**
104
+ * Ask whether the plan allows a build before the sources go up.
105
+ *
106
+ * The gate sits on the build itself, so a refusal used to arrive after the archive
107
+ * had already become a revision, leaving the project looking like a forgotten
108
+ * deploy. Same gate, same wording, one round trip earlier. A platform older than
109
+ * this CLI answers the plain build state instead, and the deploy carries on as
110
+ * before: the check is an improvement, not a requirement.
111
+ */
112
+ async function assertBuildAllowed(client, projectId) {
113
+ await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/builds?check=plan`);
114
+ }
103
115
  async function deploy(args) {
104
116
  const { root, config } = (0, config_1.requireProject)();
105
117
  const client = await (0, session_1.connectProject)(root, config);
@@ -107,12 +119,13 @@ async function deploy(args) {
107
119
  if ((0, args_1.flagBool)(args, 'no-push')) {
108
120
  const server = await (0, sources_1.latestRevision)(client, config.projectId);
109
121
  if (!server) {
110
- throw new errors_1.CliError('The server holds no sources', 'Send them: xflow push');
122
+ throw new errors_1.CliError('The server holds no sources', 'Repeat without --no-push, so that the sources go up first');
111
123
  }
112
124
  revision = server.revision;
113
125
  (0, ui_1.warn)(`Sources not sent (--no-push), the build runs from revision ${revision}`);
114
126
  }
115
127
  else {
128
+ await assertBuildAllowed(client, config.projectId);
116
129
  revision = (await (0, sources_1.pushSources)(root, config, client, { force: (0, args_1.flagBool)(args, 'force') })).revision;
117
130
  }
118
131
  (0, ui_1.step)(`Building on the platform from revision ${revision}`);
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.latestRevision = latestRevision;
4
4
  exports.pushSources = pushSources;
5
- exports.push = push;
6
5
  exports.pull = pull;
7
6
  exports.status = status;
8
7
  const node_fs_1 = require("node:fs");
@@ -69,7 +68,7 @@ async function confirmForce(client, projectId, server, local) {
69
68
  if (!confirmed)
70
69
  throw new errors_1.CliError('Cancelled');
71
70
  }
72
- /** Used by push and by the first step of deploy. */
71
+ /** The first step of deploy: sources go up as a revision before the build. */
73
72
  async function pushSources(root, config, client, options) {
74
73
  const tree = prepareTree(root, config);
75
74
  const state = (0, config_1.readState)(root);
@@ -111,11 +110,6 @@ async function pushSources(root, config, client, options) {
111
110
  }
112
111
  return { revision: result.revision, status: result.status };
113
112
  }
114
- async function push(args) {
115
- const { root, config } = (0, config_1.requireProject)();
116
- const client = await (0, session_1.connectProject)(root, config);
117
- await pushSources(root, config, client, { force: (0, args_1.flagBool)(args, 'force') });
118
- }
119
113
  function hasContent(dir) {
120
114
  if (!(0, node_fs_1.existsSync)(dir))
121
115
  return false;
@@ -169,7 +163,7 @@ async function status() {
169
163
  (0, ui_1.out)(`Local: ${tree.files.length} files, ${(0, ui_1.formatBytes)(tree.archive.length)} archived`);
170
164
  if (!server) {
171
165
  (0, ui_1.out)('On the server: no sources yet');
172
- (0, ui_1.note)((0, ui_1.dim)(' Send them: xflow push'));
166
+ (0, ui_1.note)((0, ui_1.dim)(' Send and build them: xflow deploy'));
173
167
  }
174
168
  else {
175
169
  (0, ui_1.out)(`On the server: revision ${server.revision}, ${server.file_count} files, ${(0, ui_1.formatAge)(server.created_at)}`);
@@ -182,7 +176,7 @@ async function status() {
182
176
  }
183
177
  else {
184
178
  (0, ui_1.out)(`State: ${(0, ui_1.bold)('local changes')}`);
185
- (0, ui_1.note)((0, ui_1.dim)(' Send them: xflow push'));
179
+ (0, ui_1.note)((0, ui_1.dim)(' Send and build them: xflow deploy'));
186
180
  }
187
181
  }
188
182
  (0, ui_1.out)('');
package/dist/help.js CHANGED
@@ -19,9 +19,8 @@ ${(0, ui_1.bold)('Getting started')}
19
19
  xflow skills pick the agents that get the platform instructions
20
20
  xflow mcp install give the agent platform access without a terminal
21
21
 
22
- ${(0, ui_1.bold)('Code')}
22
+ ${(0, ui_1.bold)('Sources')}
23
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
25
24
  xflow pull [--into dir] [--revision N]
26
25
  fetch the sources (the latest revision by default)
27
26
 
@@ -81,7 +80,7 @@ Which key a command then uses, in order:
81
80
 
82
81
  1. XFLOW_TOKEN, when set (and only for the default platform address)
83
82
  2. the organization this folder is bound to (${(0, ui_1.bold)('.xflow/state.json')}, written by
84
- init, link and the first successful push or pull)
83
+ init, link and the first successful deploy or pull)
85
84
  3. the active organization
86
85
 
87
86
  xflow org the stored organizations, the active one marked
@@ -322,20 +321,6 @@ Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up
322
321
  description matches the task, and "add a customers table" will not trigger it. AGENTS.md
323
322
  is always read by the agent, which is why the pointer is appended there, at the end of
324
323
  the file and once. ${(0, ui_1.bold)('CLAUDE.md')} is left alone: Claude Code reads .claude/skills anyway.`,
325
- push: `${(0, ui_1.bold)('xflow push')}: send the sources
326
-
327
- The whole working copy goes up at once, as one revision. What is not sent:
328
- node_modules, .git, dist, build, .next, any .env, plus everything listed in
329
- .xflowignore and in the ignore field of xflow.json.
330
-
331
- If the server holds a revision newer than the one you worked from, the push is
332
- rejected. That means somebody pushed before you: fetch their changes alongside
333
- (${(0, ui_1.bold)('xflow pull --into ./server-copy')}), merge them in git on your side, and retry.
334
-
335
- --force overwrites the server revision. Before that the CLI shows whose work you are
336
- about to destroy and asks for confirmation by typing the project name. In a
337
- non-interactive run (CI, an agent) there is no way to confirm: a version conflict has
338
- to fail the build rather than silently destroy somebody else's work.`,
339
324
  pull: `${(0, ui_1.bold)('xflow pull')}: fetch the sources
340
325
 
341
326
  By default it fetches the latest revision into the project folder and refuses to write
@@ -354,6 +339,18 @@ by default).
354
339
  --force allow overwriting the server revision while sending
355
340
  --allow-removals agree in advance to remove the functions gone from the sources
356
341
 
342
+ The whole working copy goes up at once, as one revision. What is not sent: node_modules,
343
+ .git, dist, build, .next, any .env, plus everything listed in .xflowignore and in the
344
+ ignore field of xflow.json.
345
+
346
+ If the server holds a revision newer than the one you worked from, the sources are
347
+ rejected and the build does not start. That means somebody deployed before you: fetch
348
+ their changes alongside (${(0, ui_1.bold)('xflow pull --into ./server-copy')}), merge them in git on
349
+ your side, and retry. ${(0, ui_1.bold)('--force')} overwrites the server revision, and before that the
350
+ CLI shows whose work you are about to destroy and asks for confirmation by typing the
351
+ project name. In a non-interactive run (CI, an agent) there is no way to confirm: a
352
+ version conflict has to fail the build rather than silently destroy somebody else's work.
353
+
357
354
  The platform builds, in a clean sandbox on one Node version for everybody, so "it
358
355
  worked on my machine" no longer depends on your machine. Before the build the project
359
356
  is checked against the template: mismatches are printed as a list and the build does
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.7.0';
5
+ exports.CLI_VERSION = '0.8.1';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getxflow/cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "CLI for the XFlow platform: source sync, deployment and publishing of applications",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {
@@ -361,12 +361,17 @@ const link = await fetch(`${process.env.XFLOW_API_URL}/api/storage/project/uploa
361
361
  ```
362
362
 
363
363
  `confirm` takes the same fields plus the returned `s3Key` and answers with the file and its
364
- public address; `delete` takes the file `url`. Never pipe the bytes through the function itself.
364
+ address; `delete` takes that same `url`. Never pipe the bytes through the function itself.
365
365
 
366
366
  There is no endpoint that lists the files back, so the address that `confirm` returns is the
367
367
  only copy you get: write it into a table of your own in the same call, and the application
368
368
  reads its files from there.
369
369
 
370
+ That address never expires and is safe to store, but it is not a public link. It opens only
371
+ for a visitor who is signed in and has access to this project, the same rule that guards the
372
+ application itself, so it works on your pages and does nothing in an email or on a page
373
+ anyone can open.
374
+
370
375
  Four things bite an upload that otherwise looks right, and none of them is obvious from the
371
376
  answers you get:
372
377
 
@@ -397,19 +402,19 @@ key that reached a visitor lets them delete every file of the project.
397
402
  ## Syncing code
398
403
 
399
404
  `xflow status` shows how the local copy differs from the server revision.
400
- `xflow push` sends sources, `xflow pull` fetches them.
405
+ `xflow deploy` sends sources, `xflow pull` fetches them.
401
406
 
402
- If a push is rejected, the server revision is newer, meaning someone pushed first.
403
- Fetch their work next to yours (`xflow pull --into ./server-copy`), merge it locally,
404
- then push again. `--force` destroys their work: a last resort, not a way around the
405
- error.
407
+ If a deploy is rejected before the build starts, the server revision is newer, meaning
408
+ someone deployed first. Fetch their work next to yours (`xflow pull --into ./server-copy`),
409
+ merge it locally, then deploy again. `--force` destroys their work: a last resort, not a
410
+ way around the error.
406
411
 
407
412
  ## Organizations and keys
408
413
 
409
414
  A key belongs to one organization, and `xflow login` stores it next to the ones already
410
415
  stored instead of replacing them. Which key a command uses, in order: `XFLOW_TOKEN` when
411
416
  set (default platform address only), then the organization the project folder is bound
412
- to (`.xflow/state.json`, written by init, link and the first successful push or pull),
417
+ to (`.xflow/state.json`, written by init, link and the first successful deploy or pull),
413
418
  then the active organization. `xflow org` lists the stored organizations with the active
414
419
  one marked, `xflow org switch <name|id>` makes another one active without a browser, and
415
420
  `xflow whoami` names the organization behind the current key.
@@ -430,8 +435,8 @@ read-only queries, migrations, function logs and invocations, schedules, environ
430
435
  variables, versions, publish and rollback. They answer with aggregates and say explicitly
431
436
  when a result is truncated, which parsing terminal output does not.
432
437
 
433
- Anything that depends on the working copy stays in the CLI: sending sources (`xflow push`),
434
- building and shipping the functions (`xflow deploy`), creating a project (`xflow init`). The
438
+ Anything that depends on the working copy stays in the CLI: sending sources, building and
439
+ shipping the functions (`xflow deploy`), creating a project (`xflow init`). The
435
440
  tools cannot see the folder you are working in, so a build started from there would release
436
441
  whatever revision the server happens to hold, not what you have on disk. Pulling a repository
437
442
  through tool calls also burns the user's tokens for nothing.
@@ -440,7 +445,7 @@ through tool calls also burns the user's tokens for nothing.
440
445
 
441
446
  - Edit `xflow.json` by hand: the CLI writes it.
442
447
  - Commit `.env`: it holds the project token.
443
- - Push with `--force` without checking `xflow status` first.
448
+ - Deploy with `--force` without checking `xflow status` first.
444
449
  - Invent platform commands: what is not in `xflow help` does not exist.
445
450
 
446
451
  ## App design