@getxflow/cli 0.20.2 → 0.22.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/bin.js +5 -2
- package/dist/commands/db.js +23 -10
- package/dist/commands/deploy.js +12 -9
- package/dist/commands/dev.js +48 -0
- package/dist/commands/env.js +3 -2
- package/dist/commands/projects.js +2 -2
- package/dist/commands/skills.js +1 -1
- package/dist/flags.js +2 -0
- package/dist/help.js +23 -3
- package/dist/json-arg.js +2 -5
- package/dist/session.js +5 -6
- package/dist/spawn.js +7 -1
- package/dist/template.js +18 -4
- package/dist/text-file.js +23 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +10 -9
package/dist/bin.js
CHANGED
|
@@ -27,7 +27,9 @@ const skills_1 = require("./commands/skills");
|
|
|
27
27
|
const sources_1 = require("./commands/sources");
|
|
28
28
|
const storage_1 = require("./commands/storage");
|
|
29
29
|
const deploy_1 = require("./commands/deploy");
|
|
30
|
+
const dev_1 = require("./commands/dev");
|
|
30
31
|
const update_1 = require("./commands/update");
|
|
32
|
+
/** A command that wraps another process answers with its exit code. */
|
|
31
33
|
async function run(args) {
|
|
32
34
|
const [first, second] = args.words;
|
|
33
35
|
const rest = { ...args, words: args.words.slice(1) };
|
|
@@ -195,6 +197,8 @@ async function run(args) {
|
|
|
195
197
|
case 'status':
|
|
196
198
|
await (0, sources_1.status)();
|
|
197
199
|
return;
|
|
200
|
+
case 'dev':
|
|
201
|
+
return (0, dev_1.dev)();
|
|
198
202
|
case 'pull':
|
|
199
203
|
await (0, sources_1.pull)(rest);
|
|
200
204
|
return;
|
|
@@ -281,8 +285,7 @@ async function main() {
|
|
|
281
285
|
let nudgeCovered = args.words[0] === 'update';
|
|
282
286
|
try {
|
|
283
287
|
(0, flags_1.checkFlags)(args);
|
|
284
|
-
await run(args);
|
|
285
|
-
return 0;
|
|
288
|
+
return (await run(args)) ?? 0;
|
|
286
289
|
}
|
|
287
290
|
catch (e) {
|
|
288
291
|
if (e instanceof api_1.ApiError) {
|
package/dist/commands/db.js
CHANGED
|
@@ -13,6 +13,7 @@ const args_1 = require("../args");
|
|
|
13
13
|
const config_1 = require("../config");
|
|
14
14
|
const errors_1 = require("../errors");
|
|
15
15
|
const session_1 = require("../session");
|
|
16
|
+
const text_file_1 = require("../text-file");
|
|
16
17
|
const ui_1 = require("../ui");
|
|
17
18
|
/** Migration files live in the repo; the applied history lives in the database. */
|
|
18
19
|
const MIGRATIONS_DIR = 'migrations';
|
|
@@ -56,7 +57,7 @@ async function dbStatus(args) {
|
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
59
|
const { root, config } = (0, config_1.requireProject)();
|
|
59
|
-
const client = (0, session_1.
|
|
60
|
+
const client = await (0, session_1.connectProject)(config.projectId, root, config);
|
|
60
61
|
const local = readMigrations(root);
|
|
61
62
|
const history = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/db/migrate`);
|
|
62
63
|
const applied = new Map(history.applied.map((row) => [row.name, row]));
|
|
@@ -92,12 +93,18 @@ async function dbStatus(args) {
|
|
|
92
93
|
(0, ui_1.warn)('A changed file will not be applied again: write a new migration');
|
|
93
94
|
}
|
|
94
95
|
}
|
|
96
|
+
function attachedProjects(database) {
|
|
97
|
+
const names = database.projects.map((project) => project.name);
|
|
98
|
+
if (database.hidden_projects > 0)
|
|
99
|
+
names.push(`+${database.hidden_projects} without access`);
|
|
100
|
+
return names.length === 0 ? 'no projects' : names.join(', ');
|
|
101
|
+
}
|
|
95
102
|
/**
|
|
96
103
|
* Logical databases of the organization, and who is attached to them.
|
|
97
104
|
*
|
|
98
105
|
* The identifier is what `xflow init --database <id>` asks for, and the list of
|
|
99
|
-
* projects is not decoration: a database
|
|
100
|
-
* one application lands in the others too.
|
|
106
|
+
* projects is not decoration: a database can be shared, so a migration written
|
|
107
|
+
* for one application lands in the others too.
|
|
101
108
|
*/
|
|
102
109
|
async function dbList() {
|
|
103
110
|
const client = (0, session_1.connect)((0, config_1.localConfig)());
|
|
@@ -111,13 +118,15 @@ async function dbList() {
|
|
|
111
118
|
database.name,
|
|
112
119
|
database.id,
|
|
113
120
|
database.schema ?? '-',
|
|
114
|
-
database
|
|
115
|
-
? 'no projects'
|
|
116
|
-
: database.projects.map((project) => project.name).join(', '),
|
|
121
|
+
attachedProjects(database),
|
|
117
122
|
]));
|
|
118
123
|
if (data.truncated)
|
|
119
124
|
(0, ui_1.note)((0, ui_1.dim)(` Shown ${data.databases.length} of ${data.total}`));
|
|
120
|
-
|
|
125
|
+
for (const database of data.databases) {
|
|
126
|
+
const attached = database.projects.length + database.hidden_projects;
|
|
127
|
+
if (attached > 1)
|
|
128
|
+
(0, ui_1.note)((0, ui_1.dim)(` ${database.name} is shared by ${attached} projects: a migration affects all of them`));
|
|
129
|
+
}
|
|
121
130
|
}
|
|
122
131
|
/** Long values would tear the columns apart; the cut is marked, never silent. */
|
|
123
132
|
const CELL_LIMIT = 60;
|
|
@@ -179,9 +188,13 @@ async function dbSchema(args) {
|
|
|
179
188
|
* database itself rather than by us guessing at the text of the SQL.
|
|
180
189
|
*/
|
|
181
190
|
async function dbQuery(args) {
|
|
182
|
-
const
|
|
191
|
+
const typed = args.words.join(' ').trim();
|
|
192
|
+
const sql = /\.sql$/i.test(typed)
|
|
193
|
+
? (0, text_file_1.readTextFile)(typed, 'An argument ending in .sql is read as a file: check the path, or end the query with ;')
|
|
194
|
+
: typed;
|
|
183
195
|
if (!sql) {
|
|
184
|
-
throw new errors_1.CliError('A query is required', 'For example: xflow db query "select * from orders order by created_at desc limit 20"'
|
|
196
|
+
throw new errors_1.CliError(typed ? `${typed} is empty` : 'A query is required', 'For example: xflow db query "select * from orders order by created_at desc limit 20", ' +
|
|
197
|
+
'or a long one from a file outside the project: xflow db query ../report.sql');
|
|
185
198
|
}
|
|
186
199
|
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
187
200
|
const data = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/db/query`, {
|
|
@@ -224,7 +237,7 @@ async function dbQuery(args) {
|
|
|
224
237
|
}
|
|
225
238
|
async function dbMigrate(args) {
|
|
226
239
|
const { root, config } = (0, config_1.requireProject)();
|
|
227
|
-
const client = (0, session_1.
|
|
240
|
+
const client = await (0, session_1.connectProject)(config.projectId, root, config);
|
|
228
241
|
const migrations = requireMigrations(root);
|
|
229
242
|
const dryRun = (0, args_1.flagBool)(args, 'dry-run');
|
|
230
243
|
(0, ui_1.step)(dryRun ? `Checking the migrations (${migrations.length} in the directory)` : `Applying the migrations (${migrations.length} in the directory)`);
|
package/dist/commands/deploy.js
CHANGED
|
@@ -171,21 +171,24 @@ async function deploy(args) {
|
|
|
171
171
|
(0, ui_1.out)(build.project_url);
|
|
172
172
|
// The functions were shipped by the build itself, so their addresses are known
|
|
173
173
|
// only now. A failure here does not undo a finished build: warn and stop there.
|
|
174
|
+
let functions = null;
|
|
174
175
|
try {
|
|
175
|
-
|
|
176
|
-
if (functions.length > 0) {
|
|
177
|
-
(0, ui_1.note)((0, ui_1.dim)(` Functions in .env: ${functions.join(', ')}`));
|
|
178
|
-
}
|
|
176
|
+
functions = await refreshPlatformEnv(root, client, config.projectId);
|
|
179
177
|
}
|
|
180
178
|
catch {
|
|
181
179
|
(0, ui_1.warn)('Could not refresh the function addresses in .env, the build itself is fine');
|
|
182
180
|
}
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
if (functions === null) {
|
|
182
|
+
(0, ui_1.note)((0, ui_1.dim)(' The cloud functions of this build are already live: publishing does not hold them back'));
|
|
183
|
+
}
|
|
184
|
+
else if (functions.length > 0) {
|
|
185
|
+
(0, ui_1.note)((0, ui_1.dim)(` Cloud functions live now: ${functions.join(', ')}`));
|
|
186
|
+
(0, ui_1.note)((0, ui_1.dim)(' There is one copy per project, publishing does not hold them back'));
|
|
187
|
+
}
|
|
185
188
|
(0, ui_1.note)((0, ui_1.dim)(' Show the new pages to visitors: xflow publish'));
|
|
186
189
|
}
|
|
187
190
|
async function publish(args) {
|
|
188
|
-
const { projectId, client } = await (0, session_1.projectTarget)(args
|
|
191
|
+
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
189
192
|
(0, ui_1.step)('Publishing the current dev version');
|
|
190
193
|
const result = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/publish`, { method: 'POST', timeoutMs: 120_000 });
|
|
191
194
|
if (result.already_published) {
|
|
@@ -203,7 +206,7 @@ async function rollback(args) {
|
|
|
203
206
|
if (!deployId) {
|
|
204
207
|
throw new errors_1.CliError('A version number is required', 'To see the versions: xflow deployments. For example: xflow rollback 481203');
|
|
205
208
|
}
|
|
206
|
-
const { projectId, client } = await (0, session_1.projectTarget)(args
|
|
209
|
+
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
207
210
|
const result = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/rollback`, { method: 'POST', body: { deploy_id: deployId } });
|
|
208
211
|
(0, ui_1.ok)(`The project now serves the pages of build ${result.deploy_id}`);
|
|
209
212
|
(0, ui_1.out)(result.project_url);
|
|
@@ -216,7 +219,7 @@ async function rollback(args) {
|
|
|
216
219
|
(0, ui_1.note)((0, ui_1.dim)(' Visitors see these pages only after xflow publish'));
|
|
217
220
|
}
|
|
218
221
|
async function deployments(args) {
|
|
219
|
-
const { projectId, client } = await (0, session_1.projectTarget)(args
|
|
222
|
+
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
220
223
|
const data = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/deployments`);
|
|
221
224
|
if (data.deployments.length === 0) {
|
|
222
225
|
(0, ui_1.note)('No versions yet. To build and release: xflow deploy');
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dev = dev;
|
|
4
|
+
const node_os_1 = require("node:os");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
const config_1 = require("../config");
|
|
7
|
+
const errors_1 = require("../errors");
|
|
8
|
+
const session_1 = require("../session");
|
|
9
|
+
const spawn_1 = require("../spawn");
|
|
10
|
+
const template_1 = require("../template");
|
|
11
|
+
const ui_1 = require("../ui");
|
|
12
|
+
/**
|
|
13
|
+
* The local dev server, handed the key this CLI picks for the project. The template
|
|
14
|
+
* proxy trades it for a pass on every function call, so which organization holds the
|
|
15
|
+
* project is decided here, by the rule every other command follows.
|
|
16
|
+
*
|
|
17
|
+
* Answers with the exit code of the server.
|
|
18
|
+
*/
|
|
19
|
+
async function dev() {
|
|
20
|
+
const { root, config } = (0, config_1.requireProject)();
|
|
21
|
+
const client = await (0, session_1.connectProject)(config.projectId, root, config);
|
|
22
|
+
const env = {
|
|
23
|
+
...process.env,
|
|
24
|
+
XFLOW_TOKEN: client.token,
|
|
25
|
+
// The proxy sends the key to this address unchecked, and the environment beats
|
|
26
|
+
// .env: pinned here, the key goes only to the platform that issued it.
|
|
27
|
+
VITE_XFLOW_API_URL: (0, template_1.envApiUrl)(client.apiUrl),
|
|
28
|
+
};
|
|
29
|
+
// .env only moves on link and deploy: a function someone else shipped, or a new
|
|
30
|
+
// project token, would reach this run only after one of them.
|
|
31
|
+
try {
|
|
32
|
+
const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}`);
|
|
33
|
+
Object.assign(env, (0, template_1.platformEnv)(card.project_token, client.apiUrl, card.functions));
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
(0, ui_1.warn)(`The function addresses were not fetched (${e instanceof Error ? e.message : String(e)}): the ones in .env are used`);
|
|
37
|
+
}
|
|
38
|
+
// Ctrl+C reaches the server on its own; staying alive until it is gone is what
|
|
39
|
+
// hands its exit code back instead of leaving it to print over the prompt.
|
|
40
|
+
process.on('SIGINT', () => { });
|
|
41
|
+
const result = (0, spawn_1.run)('npm', ['run', 'dev'], { stdio: 'inherit', cwd: root, env });
|
|
42
|
+
if (result.error) {
|
|
43
|
+
throw new errors_1.CliError(`Could not start npm run dev: ${result.error.message}`, 'npm ships with Node.js: check that npm works in this terminal');
|
|
44
|
+
}
|
|
45
|
+
// Killed by a signal: the code a shell would report for it.
|
|
46
|
+
const signal = result.signal ? node_os_1.constants.signals[result.signal] : 0;
|
|
47
|
+
return result.status ?? 128 + signal;
|
|
48
|
+
}
|
package/dist/commands/env.js
CHANGED
|
@@ -104,7 +104,8 @@ async function envList(args) {
|
|
|
104
104
|
/** Check that every referenced variable is stored. */
|
|
105
105
|
async function envCheck() {
|
|
106
106
|
const { root, config } = (0, config_1.requireProject)();
|
|
107
|
-
const
|
|
107
|
+
const client = await (0, session_1.connectProject)(config.projectId, root, config);
|
|
108
|
+
const { names } = await fetchVariables(client, config.projectId);
|
|
108
109
|
const { needed, provided, opaque } = referencedByFunctions(root);
|
|
109
110
|
if (needed.size === 0 && provided.size === 0 && opaque.size === 0) {
|
|
110
111
|
(0, ui_1.note)('The functions of this project read no environment variables');
|
|
@@ -159,7 +160,7 @@ async function envCheck() {
|
|
|
159
160
|
}
|
|
160
161
|
async function envSet(args) {
|
|
161
162
|
const { root, config } = (0, config_1.requireProject)();
|
|
162
|
-
const client = (0, session_1.
|
|
163
|
+
const client = await (0, session_1.connectProject)(config.projectId, root, config);
|
|
163
164
|
const pair = args.words[1];
|
|
164
165
|
if (!pair || !pair.includes('=')) {
|
|
165
166
|
throw new errors_1.CliError('A NAME=value pair is required', 'For example: xflow env set SMTP_PASSWORD=secret');
|
|
@@ -98,7 +98,7 @@ async function init(args) {
|
|
|
98
98
|
(0, ui_1.out)(` ${(0, ui_1.bold)('Next:')}`);
|
|
99
99
|
(0, ui_1.out)(` cd ${(0, node_path_1.basename)(target)}`);
|
|
100
100
|
(0, ui_1.out)(' npm install');
|
|
101
|
-
(0, ui_1.out)('
|
|
101
|
+
(0, ui_1.out)(' xflow dev # develop');
|
|
102
102
|
(0, ui_1.out)(' xflow deploy # send the code, build and release');
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
@@ -160,7 +160,7 @@ async function link(args) {
|
|
|
160
160
|
if (client.organizationId)
|
|
161
161
|
(0, config_1.writeState)(dir, { organizationId: client.organizationId });
|
|
162
162
|
(0, template_1.writePlatformEnv)(dir, card.project_token, client.apiUrl, card.functions);
|
|
163
|
-
(0, ui_1.note)((0, ui_1.dim)(' .env now points at this project'));
|
|
163
|
+
(0, ui_1.note)((0, ui_1.dim)(' .env now points at this project, run it locally with xflow dev'));
|
|
164
164
|
const skillReady = (0, skills_1.installSkillQuietly)(dir);
|
|
165
165
|
(0, ui_1.ok)(`The folder is linked to the project "${card.name}"`);
|
|
166
166
|
(0, ui_1.note)((0, ui_1.dim)(` ${(0, node_path_1.join)(dir, config_1.CONFIG_FILE)}`));
|
package/dist/commands/skills.js
CHANGED
|
@@ -53,7 +53,7 @@ This project is hosted on the XFlow platform and ships through the \`xflow\` CLI
|
|
|
53
53
|
through a git push. Read \`${exports.SKILL_IN_PROJECT}\` before deploying, publishing,
|
|
54
54
|
rolling back, touching the database or migrations, cloud functions, schedules,
|
|
55
55
|
environment variables, file storage and uploads, connected accounts or production logs.
|
|
56
|
-
Command list: \`xflow help\`.
|
|
56
|
+
Run the app locally with \`xflow dev\`. Command list: \`xflow help\`.
|
|
57
57
|
`;
|
|
58
58
|
/** The skill ships inside the package, next to dist. */
|
|
59
59
|
function skillSource() {
|
package/dist/flags.js
CHANGED
|
@@ -82,6 +82,7 @@ exports.KNOWN = {
|
|
|
82
82
|
'storage rm': ['folder', 'yes', 'project'],
|
|
83
83
|
'storage remove': ['folder', 'yes', 'project'],
|
|
84
84
|
status: [],
|
|
85
|
+
dev: [],
|
|
85
86
|
pull: ['into', 'force', 'revision'],
|
|
86
87
|
deploy: ['no-push', 'force', 'allow-removals', 'message'],
|
|
87
88
|
publish: ['project'],
|
|
@@ -105,6 +106,7 @@ exports.WORKING_COPY = {
|
|
|
105
106
|
deploy: 'it sends the sources next to it',
|
|
106
107
|
pull: 'it writes the sources next to it',
|
|
107
108
|
status: 'it compares the folder with the revision on the platform',
|
|
109
|
+
dev: 'it runs the dev server of the code next to it',
|
|
108
110
|
'db migrate': 'it applies the files of migrations/',
|
|
109
111
|
'env check': 'it looks for the variables the code of the functions reads',
|
|
110
112
|
'env set': 'it names the functions that need a deploy for the value to arrive',
|
package/dist/help.js
CHANGED
|
@@ -40,6 +40,7 @@ ${(0, ui_1.bold)('Getting started')}
|
|
|
40
40
|
xflow init [dir] new project from the platform template
|
|
41
41
|
xflow templates which templates are available
|
|
42
42
|
xflow link <id> link this folder to a project, an empty one also gets the sources
|
|
43
|
+
xflow dev run the app on localhost:3000, cloud functions included
|
|
43
44
|
xflow skills pick the agents that get the platform instructions
|
|
44
45
|
xflow mcp install give the agent platform access without a terminal
|
|
45
46
|
|
|
@@ -163,8 +164,8 @@ and ${(0, ui_1.bold)('connections unlink')} removes the link without naming the
|
|
|
163
164
|
its variables, for the same reason.
|
|
164
165
|
|
|
165
166
|
The rest cannot answer without the working copy and have nothing to do with an id
|
|
166
|
-
instead: ${(0, ui_1.bold)('deploy')}, ${(0, ui_1.bold)('pull')}, ${(0, ui_1.bold)('status')}, ${(0, ui_1.bold)('
|
|
167
|
-
${(0, ui_1.bold)('env set')}. They answer about the files next to them, so
|
|
167
|
+
instead: ${(0, ui_1.bold)('deploy')}, ${(0, ui_1.bold)('pull')}, ${(0, ui_1.bold)('status')}, ${(0, ui_1.bold)('dev')}, ${(0, ui_1.bold)('db migrate')},
|
|
168
|
+
${(0, ui_1.bold)('env check')}, ${(0, ui_1.bold)('env set')}. They answer about the files next to them, so
|
|
168
169
|
they keep asking for a folder. ${(0, ui_1.bold)('projects get')} and ${(0, ui_1.bold)('link')} take no flag either:
|
|
169
170
|
they name the project by its id as an argument.
|
|
170
171
|
|
|
@@ -231,6 +232,7 @@ will work.`,
|
|
|
231
232
|
xflow db schema [table] tables of the project, or the columns of one
|
|
232
233
|
xflow db query "select ..." read data (--limit N: 50 by default, 300 at most;
|
|
233
234
|
--json prints the values whole)
|
|
235
|
+
xflow db query <file>.sql the same, the query read from a file
|
|
234
236
|
xflow db migrate apply migrations/*.sql
|
|
235
237
|
|
|
236
238
|
${(0, ui_1.bold)('list')} is about the organization, not about this project: it names the logical
|
|
@@ -240,7 +242,9 @@ attached to each one. That last column is the one to read before a migration.
|
|
|
240
242
|
${(0, ui_1.bold)('schema')} answers a different question than the ${(0, ui_1.bold)('migrations/')} directory: one logical
|
|
241
243
|
database is shared by several projects, so the files say what you did, and the schema
|
|
242
244
|
says what is actually in there. ${(0, ui_1.bold)('query')} runs inside a READ ONLY transaction, so
|
|
243
|
-
writes are rejected by the database itself, not by us reading your SQL.
|
|
245
|
+
writes are rejected by the database itself, not by us reading your SQL. A query with
|
|
246
|
+
double quotes goes in a .sql file outside the project: Windows PowerShell strips them from
|
|
247
|
+
arguments, and a file in migrations/ would be taken for a migration. Its table cuts
|
|
244
248
|
long values to keep the columns readable; ${(0, ui_1.bold)('--json')} prints them whole, together with
|
|
245
249
|
the applied limit and its ceiling. ${(0, ui_1.bold)('status')} named by id (${(0, ui_1.bold)('--project')}) answers with
|
|
246
250
|
the applied history alone: comparing against ${(0, ui_1.bold)('migrations/')} needs the folder.
|
|
@@ -532,6 +536,22 @@ Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up
|
|
|
532
536
|
description matches the task, and "add a customers table" will not trigger it. AGENTS.md
|
|
533
537
|
is always read by the agent, which is why the pointer is appended there, at the end of
|
|
534
538
|
the file and once. ${(0, ui_1.bold)('CLAUDE.md')} is left alone: Claude Code reads .claude/skills anyway.`,
|
|
539
|
+
dev: `${(0, ui_1.bold)('xflow dev')}: run the application on this machine
|
|
540
|
+
|
|
541
|
+
Runs ${(0, ui_1.bold)('npm run dev')} in the project folder (localhost:3000) with the access key of the
|
|
542
|
+
organization that holds the project. The dev server trades that key for a visitor pass,
|
|
543
|
+
so the cloud functions answer here the way they do on the platform. The key is picked
|
|
544
|
+
as for every other command (${(0, ui_1.bold)('xflow help org')}): with several organizations signed
|
|
545
|
+
in, the right one is found and the folder is bound to it.
|
|
546
|
+
|
|
547
|
+
Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}, which gets no key from here: its function
|
|
548
|
+
calls fail, or go out under the key of whichever organization is active.
|
|
549
|
+
|
|
550
|
+
The platform address comes from the CLI too, not from .env, so the key goes to the
|
|
551
|
+
platform that issued it and nowhere else. It never reaches the browser: the page gets
|
|
552
|
+
only the ${(0, ui_1.bold)('VITE_')} variables. The project token and the function addresses are
|
|
553
|
+
fetched on every start as well: a function someone else shipped answers here without a
|
|
554
|
+
deploy of yours.`,
|
|
535
555
|
pull: `${(0, ui_1.bold)('xflow pull')}: fetch the sources
|
|
536
556
|
|
|
537
557
|
By default it fetches the latest revision into the project folder and refuses to write
|
package/dist/json-arg.js
CHANGED
|
@@ -5,9 +5,9 @@ exports.jsonHint = jsonHint;
|
|
|
5
5
|
exports.bodyArg = bodyArg;
|
|
6
6
|
exports.assertJson = assertJson;
|
|
7
7
|
exports.jsonArg = jsonArg;
|
|
8
|
-
const node_fs_1 = require("node:fs");
|
|
9
8
|
const args_1 = require("./args");
|
|
10
9
|
const errors_1 = require("./errors");
|
|
10
|
+
const text_file_1 = require("./text-file");
|
|
11
11
|
/**
|
|
12
12
|
* A JSON body typed on the command line: --data for a call, --payload for a
|
|
13
13
|
* schedule. Checked here, before it leaves: PowerShell strips the double quotes
|
|
@@ -59,10 +59,7 @@ function bodyArg(args, flag) {
|
|
|
59
59
|
throw new errors_1.CliError(`--${flag} and --${fileFlag} cannot be used together`, 'Give the body once');
|
|
60
60
|
}
|
|
61
61
|
if (path !== undefined) {
|
|
62
|
-
|
|
63
|
-
throw new errors_1.CliError(`No file ${path}`, `--${fileFlag} takes the path to a file with the body`);
|
|
64
|
-
}
|
|
65
|
-
return { text: (0, node_fs_1.readFileSync)(path, 'utf-8').trim(), fromFile: path };
|
|
62
|
+
return { text: (0, text_file_1.readTextFile)(path, `--${fileFlag} takes the path to a file with the body`), fromFile: path };
|
|
66
63
|
}
|
|
67
64
|
if (inline === undefined)
|
|
68
65
|
return undefined;
|
package/dist/session.js
CHANGED
|
@@ -125,16 +125,15 @@ function isProjectId(value) {
|
|
|
125
125
|
* the default one or XFLOW_API_URL and nothing else: a config lying around must
|
|
126
126
|
* not be able to redirect a call made by id.
|
|
127
127
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* to lean on, so the search always happens.
|
|
128
|
+
* Either way the key comes from connectProject: an unbound folder gets the
|
|
129
|
+
* organization of its project looked up once and written down, an id gets it
|
|
130
|
+
* looked up on every call.
|
|
132
131
|
*
|
|
133
132
|
* `root` is that folder, and null when the id was named: a command that reads the
|
|
134
133
|
* sources for a check of its own then knows the check is not available rather than
|
|
135
134
|
* running it against whatever folder the caller happened to stand in.
|
|
136
135
|
*/
|
|
137
|
-
async function projectTarget(args
|
|
136
|
+
async function projectTarget(args) {
|
|
138
137
|
const named = (0, args_1.flagString)(args, 'project');
|
|
139
138
|
if (named !== undefined) {
|
|
140
139
|
if (!isProjectId(named)) {
|
|
@@ -150,6 +149,6 @@ async function projectTarget(args, options) {
|
|
|
150
149
|
return { projectId: named, client, root: null };
|
|
151
150
|
}
|
|
152
151
|
const { root, config } = (0, config_1.requireProject)();
|
|
153
|
-
const client =
|
|
152
|
+
const client = await connectProject(config.projectId, root, config);
|
|
154
153
|
return { projectId: config.projectId, client, root };
|
|
155
154
|
}
|
package/dist/spawn.js
CHANGED
|
@@ -13,7 +13,13 @@ function quote(value) {
|
|
|
13
13
|
return WINDOWS ? `"${value.replace(/"/g, '""')}"` : value;
|
|
14
14
|
}
|
|
15
15
|
function run(binary, args, options = {}) {
|
|
16
|
-
const common = {
|
|
16
|
+
const common = {
|
|
17
|
+
stdio: options.stdio,
|
|
18
|
+
timeout: options.timeout,
|
|
19
|
+
cwd: options.cwd,
|
|
20
|
+
env: options.env,
|
|
21
|
+
encoding: 'utf-8',
|
|
22
|
+
};
|
|
17
23
|
return WINDOWS
|
|
18
24
|
? (0, node_child_process_1.spawnSync)([binary, ...args.map(quote)].join(' '), { ...common, shell: true })
|
|
19
25
|
: (0, node_child_process_1.spawnSync)(binary, args, common);
|
package/dist/template.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.envApiUrl = envApiUrl;
|
|
4
|
+
exports.platformEnv = platformEnv;
|
|
3
5
|
exports.writePlatformEnv = writePlatformEnv;
|
|
4
6
|
exports.scaffoldFiles = scaffoldFiles;
|
|
5
7
|
const node_fs_1 = require("node:fs");
|
|
@@ -16,7 +18,7 @@ An application on XFlow.
|
|
|
16
18
|
|
|
17
19
|
\`\`\`bash
|
|
18
20
|
npm install # dependencies
|
|
19
|
-
|
|
21
|
+
xflow dev # develop on localhost:3000
|
|
20
22
|
xflow deploy # send the code and build it on the platform
|
|
21
23
|
xflow publish # show the dev version to visitors
|
|
22
24
|
\`\`\`
|
|
@@ -38,14 +40,26 @@ function functionsEnvValue(functions) {
|
|
|
38
40
|
}
|
|
39
41
|
return JSON.stringify(map);
|
|
40
42
|
}
|
|
43
|
+
/** The platform address the way .env holds it, and the way every template reads it. */
|
|
44
|
+
function envApiUrl(apiUrl) {
|
|
45
|
+
return apiUrl.replace(/^https?:\/\//, '');
|
|
46
|
+
}
|
|
47
|
+
/** The variables that point a local run at the project: written to .env, handed to xflow dev. */
|
|
48
|
+
function platformEnv(projectToken, apiUrl, functions = []) {
|
|
49
|
+
return {
|
|
50
|
+
VITE_XFLOW_PROJECT_TOKEN: projectToken ?? '',
|
|
51
|
+
VITE_XFLOW_API_URL: envApiUrl(apiUrl),
|
|
52
|
+
[FUNCTIONS_ENV_KEY]: functionsEnvValue(functions),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
41
55
|
/**
|
|
42
56
|
* Point .env at the current project: written on every link and deploy, so that `npm run dev`
|
|
43
57
|
* never keeps talking to the previous one. Variables of your own are left alone.
|
|
44
58
|
*/
|
|
45
59
|
function writePlatformEnv(root, projectToken, apiUrl, functions = []) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
for (const [key, value] of Object.entries(platformEnv(projectToken, apiUrl, functions))) {
|
|
61
|
+
writeEnvValue(root, key, value);
|
|
62
|
+
}
|
|
49
63
|
}
|
|
50
64
|
/** Update one variable in .env without touching the rest of the file. */
|
|
51
65
|
function writeEnvValue(root, key, value) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readTextFile = readTextFile;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_util_1 = require("node:util");
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
/**
|
|
8
|
+
* A file the agent wrote, whatever the shell saved it in. Windows PowerShell 5.1 writes
|
|
9
|
+
* UTF-16 with `>` and Out-File, and the code page with Set-Content: read as UTF-8, the
|
|
10
|
+
* first is garbage and the second turns non-latin text into U+FFFD without a word.
|
|
11
|
+
*/
|
|
12
|
+
function readTextFile(path, missingHint) {
|
|
13
|
+
if (!(0, node_fs_1.statSync)(path, { throwIfNoEntry: false })?.isFile())
|
|
14
|
+
throw new errors_1.CliError(`No file ${path}`, missingHint);
|
|
15
|
+
const bytes = (0, node_fs_1.readFileSync)(path);
|
|
16
|
+
const encoding = bytes[0] === 0xff && bytes[1] === 0xfe ? 'utf-16le' : 'utf-8';
|
|
17
|
+
try {
|
|
18
|
+
return new node_util_1.TextDecoder(encoding, { fatal: true }).decode(bytes).trim();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new errors_1.CliError(`${path} is not UTF-8`, 'Save it as UTF-8, in Windows PowerShell: Set-Content -Encoding utf8');
|
|
22
|
+
}
|
|
23
|
+
}
|
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.22.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
|
@@ -22,7 +22,7 @@ contains the fix.
|
|
|
22
22
|
|
|
23
23
|
## Keeping these instructions current
|
|
24
24
|
|
|
25
|
-
These instructions ship with xflow CLI 0.
|
|
25
|
+
These instructions ship with xflow CLI 0.22.0, so the copy you are reading can be older than
|
|
26
26
|
the CLI answering your commands.
|
|
27
27
|
|
|
28
28
|
When the CLI says this file does not match it, run `xflow skills --refresh` and then **read
|
|
@@ -270,7 +270,7 @@ the interface, not a check.
|
|
|
270
270
|
A function that changes data should say so instead of checking the role by hand:
|
|
271
271
|
|
|
272
272
|
```js
|
|
273
|
-
exports.minRole = '
|
|
273
|
+
exports.minRole = 'developer' // 'member' | 'developer' | 'owner'
|
|
274
274
|
```
|
|
275
275
|
|
|
276
276
|
The wrapper refuses anything below that role before your code runs. Without the line every
|
|
@@ -429,8 +429,9 @@ do not see: `xflow db status` lists applied migrations with no file in your repo
|
|
|
429
429
|
somebody else's project), and `xflow db list` names who else is on yours. For the same reason
|
|
430
430
|
`migrations/` is not the schema. It says what you did; `xflow db schema [table]` says what is in
|
|
431
431
|
the database right now, and `xflow db query "select ..."` reads it inside a READ ONLY
|
|
432
|
-
transaction (50 rows by default, `--limit` up to 300; `--json` prints long values whole).
|
|
433
|
-
|
|
432
|
+
transaction (50 rows by default, `--limit` up to 300; `--json` prints long values whole). A query
|
|
433
|
+
with double quotes goes in a `.sql` file outside the project, `xflow db query ../q.sql`: PowerShell
|
|
434
|
+
strips them from arguments. Look before you write a migration against a shared database.
|
|
434
435
|
|
|
435
436
|
## File storage
|
|
436
437
|
|
|
@@ -594,12 +595,12 @@ rejected promises and 5xx responses. The same stream, functions included, is in
|
|
|
594
595
|
platform UI: open the project, section «Облако» (Cloud), tab «Логи» (Logs). The last 200
|
|
595
596
|
records per project are kept.
|
|
596
597
|
|
|
597
|
-
|
|
598
|
-
|
|
598
|
+
A local run does not report anything: these logs exist for what you cannot open in your
|
|
599
|
+
own devtools.
|
|
599
600
|
|
|
600
|
-
|
|
601
|
-
the
|
|
602
|
-
|
|
601
|
+
Run the app locally with `xflow dev`, not a bare `npm run dev`: it hands the dev server
|
|
602
|
+
the key of the project's organization, which cloud functions need to answer, and their
|
|
603
|
+
current addresses, including functions someone else deployed.
|
|
603
604
|
|
|
604
605
|
## Environment
|
|
605
606
|
|