@getxflow/cli 0.1.10 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin.js +3 -3
- package/dist/commands/deploy.js +37 -0
- package/dist/commands/env.js +1 -1
- package/dist/commands/functions.js +2 -89
- package/dist/help.js +23 -7
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +21 -16
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ else's code belongs next to it.
|
|
|
21
21
|
| `status` / `push` / `pull` | state, sending and fetching sources |
|
|
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
|
-
| `functions list`
|
|
24
|
+
| `functions list` | cloud functions of the project from `functions/<name>/index.ts`, shipped by `deploy` |
|
|
25
25
|
| `functions invoke` / `functions logs` | call a function, look at its crashes with the stack |
|
|
26
26
|
| `schedules list` / `set` / `rm` | running functions on a timer (timer triggers) |
|
|
27
27
|
| `env` / `env check` / `env set` | environment variables of the functions, values are never handed back |
|
package/dist/bin.js
CHANGED
|
@@ -69,9 +69,9 @@ async function run(args) {
|
|
|
69
69
|
}
|
|
70
70
|
throw new errors_1.CliError(`Unknown command: mcp ${second}`, 'Available: install');
|
|
71
71
|
case 'functions':
|
|
72
|
+
// The command is gone, but silence would leave the habit unexplained.
|
|
72
73
|
if (second === 'deploy') {
|
|
73
|
-
|
|
74
|
-
return;
|
|
74
|
+
throw new errors_1.CliError('Functions are deployed by the build now', 'Run xflow deploy: it ships the functions and then builds the application with their addresses');
|
|
75
75
|
}
|
|
76
76
|
if (second === 'invoke') {
|
|
77
77
|
await (0, functions_1.functionsInvoke)(rest);
|
|
@@ -85,7 +85,7 @@ async function run(args) {
|
|
|
85
85
|
await (0, functions_1.functionsList)();
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
|
-
throw new errors_1.CliError(`Unknown command: functions ${second}`, 'Available: list,
|
|
88
|
+
throw new errors_1.CliError(`Unknown command: functions ${second}`, 'Available: list, invoke and logs');
|
|
89
89
|
case 'logs':
|
|
90
90
|
await (0, logs_1.logs)(rest);
|
|
91
91
|
return;
|
package/dist/commands/deploy.js
CHANGED
|
@@ -4,11 +4,14 @@ exports.deploy = deploy;
|
|
|
4
4
|
exports.publish = publish;
|
|
5
5
|
exports.rollback = rollback;
|
|
6
6
|
exports.deployments = deployments;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
7
9
|
const api_1 = require("../api");
|
|
8
10
|
const args_1 = require("../args");
|
|
9
11
|
const config_1 = require("../config");
|
|
10
12
|
const errors_1 = require("../errors");
|
|
11
13
|
const session_1 = require("../session");
|
|
14
|
+
const template_1 = require("../template");
|
|
12
15
|
const ui_1 = require("../ui");
|
|
13
16
|
const sources_1 = require("./sources");
|
|
14
17
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -40,6 +43,24 @@ async function waitForBuild(client, projectId, started) {
|
|
|
40
43
|
}
|
|
41
44
|
throw new errors_1.CliError('The build is taking longer than expected', `To check the state: xflow deployments. Version ${started.deploy_id}`);
|
|
42
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Write the function addresses into the local .env.
|
|
48
|
+
*
|
|
49
|
+
* The build bakes its own copy of the map into the bundle, this one is for
|
|
50
|
+
* `npm run dev`: without it the local run calls a function that it has no
|
|
51
|
+
* address for.
|
|
52
|
+
*/
|
|
53
|
+
async function refreshFunctionsEnv(root, client, projectId) {
|
|
54
|
+
const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}`);
|
|
55
|
+
// No .env at all (fresh clone): recreate it fully, like link does.
|
|
56
|
+
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(root, '.env')) && card.project_token) {
|
|
57
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, '.env'), (0, template_1.envFile)(card.project_token, client.apiUrl, card.functions), 'utf-8');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
(0, template_1.writeEnvValue)(root, template_1.FUNCTIONS_ENV_KEY, (0, template_1.functionsEnvValue)(card.functions));
|
|
61
|
+
}
|
|
62
|
+
return card.functions.filter((fn) => fn.invoke_url).map((fn) => fn.name);
|
|
63
|
+
}
|
|
43
64
|
async function deploy(args) {
|
|
44
65
|
const { root, config } = (0, config_1.requireProject)();
|
|
45
66
|
const client = (0, session_1.connect)(config);
|
|
@@ -68,6 +89,11 @@ async function deploy(args) {
|
|
|
68
89
|
reportIssues(e);
|
|
69
90
|
throw e;
|
|
70
91
|
}
|
|
92
|
+
// Removing a function cannot be undone: a new one gets a new address. Say it
|
|
93
|
+
// out loud rather than leaving it to be discovered in functions list.
|
|
94
|
+
if (started.removed_functions?.length) {
|
|
95
|
+
(0, ui_1.warn)(`Removed from the cloud, gone from the sources: ${started.removed_functions.join(', ')}`);
|
|
96
|
+
}
|
|
71
97
|
const build = await waitForBuild(client, config.projectId, started);
|
|
72
98
|
if (build.phase === 'failed') {
|
|
73
99
|
if (build.log_tail) {
|
|
@@ -78,6 +104,17 @@ async function deploy(args) {
|
|
|
78
104
|
}
|
|
79
105
|
(0, ui_1.ok)(`Version ${build.deploy_id} built from revision ${build.revision} in ${build.elapsed_s} s`);
|
|
80
106
|
(0, ui_1.out)(build.project_url);
|
|
107
|
+
// The functions were shipped by the build itself, so their addresses are known
|
|
108
|
+
// only now. A failure here does not undo a finished build: warn and stop there.
|
|
109
|
+
try {
|
|
110
|
+
const functions = await refreshFunctionsEnv(root, client, config.projectId);
|
|
111
|
+
if (functions.length > 0) {
|
|
112
|
+
(0, ui_1.note)((0, ui_1.dim)(` Functions in .env: ${functions.join(', ')}`));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
(0, ui_1.warn)('Could not refresh the function addresses in .env, the build itself is fine');
|
|
117
|
+
}
|
|
81
118
|
(0, ui_1.note)((0, ui_1.dim)(' Show it to visitors: xflow publish'));
|
|
82
119
|
}
|
|
83
120
|
async function publish() {
|
package/dist/commands/env.js
CHANGED
|
@@ -126,7 +126,7 @@ async function envSet(args) {
|
|
|
126
126
|
// Values reach a function on its next deploy.
|
|
127
127
|
const users = referencedByFunctions(root).needed.get(name);
|
|
128
128
|
if (users && users.length > 0) {
|
|
129
|
-
(0, ui_1.note)((0, ui_1.dim)(` For the value to arrive,
|
|
129
|
+
(0, ui_1.note)((0, ui_1.dim)(` For the value to arrive, run xflow deploy: it ships ${users.join(', ')} again`));
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
async function envRemove(args) {
|
|
@@ -2,68 +2,19 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.functionsList = functionsList;
|
|
4
4
|
exports.functionsInvoke = functionsInvoke;
|
|
5
|
-
exports.functionsDeploy = functionsDeploy;
|
|
6
|
-
const node_fs_1 = require("node:fs");
|
|
7
|
-
const node_path_1 = require("node:path");
|
|
8
5
|
const api_1 = require("../api");
|
|
9
6
|
const args_1 = require("../args");
|
|
10
7
|
const config_1 = require("../config");
|
|
11
8
|
const errors_1 = require("../errors");
|
|
12
9
|
const session_1 = require("../session");
|
|
13
|
-
const template_1 = require("../template");
|
|
14
10
|
const ui_1 = require("../ui");
|
|
15
|
-
const ENTRY_NAMES = ['index.ts', 'index.js', 'index.mjs'];
|
|
16
11
|
const FUNCTIONS_DIR = 'functions';
|
|
17
|
-
function entryFor(root, name) {
|
|
18
|
-
for (const entry of ENTRY_NAMES) {
|
|
19
|
-
const candidate = (0, node_path_1.join)(root, FUNCTIONS_DIR, name, entry);
|
|
20
|
-
if ((0, node_fs_1.existsSync)(candidate))
|
|
21
|
-
return candidate;
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
function discover(root) {
|
|
26
|
-
const dir = (0, node_path_1.join)(root, FUNCTIONS_DIR);
|
|
27
|
-
if (!(0, node_fs_1.existsSync)(dir))
|
|
28
|
-
return [];
|
|
29
|
-
return (0, node_fs_1.readdirSync)(dir)
|
|
30
|
-
.filter((name) => (0, node_fs_1.statSync)((0, node_path_1.join)(dir, name)).isDirectory())
|
|
31
|
-
.filter((name) => entryFor(root, name) !== null)
|
|
32
|
-
.sort();
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Bundle a function into one file. esbuild comes from the project's node_modules;
|
|
36
|
-
* `pg` stays external so the server wrapper can inject the project schema.
|
|
37
|
-
*/
|
|
38
|
-
function bundle(root, entry) {
|
|
39
|
-
let esbuild;
|
|
40
|
-
try {
|
|
41
|
-
esbuild = require(require.resolve('esbuild', { paths: [root] }));
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
throw new errors_1.CliError('Could not find esbuild in the project', 'Install it: npm i -D esbuild. It usually comes with Vite already');
|
|
45
|
-
}
|
|
46
|
-
const result = esbuild.buildSync({
|
|
47
|
-
entryPoints: [entry],
|
|
48
|
-
bundle: true,
|
|
49
|
-
platform: 'node',
|
|
50
|
-
target: 'node20',
|
|
51
|
-
format: 'cjs',
|
|
52
|
-
external: ['pg'],
|
|
53
|
-
write: false,
|
|
54
|
-
logLevel: 'silent',
|
|
55
|
-
});
|
|
56
|
-
const text = result.outputFiles[0]?.text;
|
|
57
|
-
if (!text)
|
|
58
|
-
throw new errors_1.CliError(`Building ${entry} produced nothing`);
|
|
59
|
-
return text;
|
|
60
|
-
}
|
|
61
12
|
async function functionsList() {
|
|
62
13
|
const { config } = (0, config_1.requireProject)();
|
|
63
14
|
const client = (0, session_1.connect)(config);
|
|
64
15
|
const data = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/functions`);
|
|
65
16
|
if (data.functions.length === 0) {
|
|
66
|
-
(0, ui_1.note)(`No functions. Put the code in ${FUNCTIONS_DIR}/<name>/index.ts and run xflow
|
|
17
|
+
(0, ui_1.note)(`No functions. Put the code in ${FUNCTIONS_DIR}/<name>/index.ts and run xflow deploy`);
|
|
67
18
|
return;
|
|
68
19
|
}
|
|
69
20
|
(0, ui_1.table)(data.functions.map((fn) => [
|
|
@@ -73,18 +24,6 @@ async function functionsList() {
|
|
|
73
24
|
fn.error_message ?? '',
|
|
74
25
|
]));
|
|
75
26
|
}
|
|
76
|
-
/** Write function URLs into .env, where the build picks them up. */
|
|
77
|
-
async function refreshFunctionsEnv(root, client, projectId) {
|
|
78
|
-
const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}`);
|
|
79
|
-
// No .env at all (fresh clone): recreate it fully, like link does.
|
|
80
|
-
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(root, '.env')) && card.project_token) {
|
|
81
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, '.env'), (0, template_1.envFile)(card.project_token, client.apiUrl, card.functions), 'utf-8');
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
(0, template_1.writeEnvValue)(root, template_1.FUNCTIONS_ENV_KEY, (0, template_1.functionsEnvValue)(card.functions));
|
|
85
|
-
}
|
|
86
|
-
return card.functions.filter((fn) => fn.invoke_url).map((fn) => fn.name);
|
|
87
|
-
}
|
|
88
27
|
function prettyBody(text) {
|
|
89
28
|
try {
|
|
90
29
|
return JSON.stringify(JSON.parse(text), null, 2);
|
|
@@ -105,7 +44,7 @@ async function functionsInvoke(args) {
|
|
|
105
44
|
if (!fn || !fn.invoke_url) {
|
|
106
45
|
throw new errors_1.CliError(`Function ${name} is not deployed`, card.functions.length > 0
|
|
107
46
|
? `Deployed: ${card.functions.map((item) => item.name).join(', ')}`
|
|
108
|
-
:
|
|
47
|
+
: 'Functions ship with the build: xflow deploy');
|
|
109
48
|
}
|
|
110
49
|
const data = (0, args_1.flagString)(args, 'data');
|
|
111
50
|
const method = ((0, args_1.flagString)(args, 'method') ?? (data ? 'POST' : 'GET')).toUpperCase();
|
|
@@ -136,29 +75,3 @@ async function functionsInvoke(args) {
|
|
|
136
75
|
throw new errors_1.CliError(`The function answered ${response.status}`, `The stack and console output: xflow functions logs ${name}`);
|
|
137
76
|
}
|
|
138
77
|
}
|
|
139
|
-
async function functionsDeploy(args) {
|
|
140
|
-
const { root, config } = (0, config_1.requireProject)();
|
|
141
|
-
const client = (0, session_1.connect)(config);
|
|
142
|
-
const wanted = args.words[1];
|
|
143
|
-
const names = wanted ? [wanted] : discover(root);
|
|
144
|
-
if (names.length === 0) {
|
|
145
|
-
throw new errors_1.CliError(`The project has no functions`, `Create ${FUNCTIONS_DIR}/<name>/index.ts exporting handler and try again`);
|
|
146
|
-
}
|
|
147
|
-
for (const name of names) {
|
|
148
|
-
const entry = entryFor(root, name);
|
|
149
|
-
if (!entry) {
|
|
150
|
-
throw new errors_1.CliError(`Could not find ${FUNCTIONS_DIR}/${name}/index.ts`, `Available functions: ${discover(root).join(', ') || 'none at all'}`);
|
|
151
|
-
}
|
|
152
|
-
(0, ui_1.step)(`Building ${name}`);
|
|
153
|
-
const code = bundle(root, entry);
|
|
154
|
-
(0, ui_1.step)(`Deploying ${name}`);
|
|
155
|
-
const result = await (0, api_1.apiUpload)(client, `/api/v1/projects/${config.projectId}/functions?name=${encodeURIComponent(name)}`, Buffer.from(code, 'utf-8'), { 'Content-Type': 'application/javascript' });
|
|
156
|
-
(0, ui_1.ok)(`${(0, ui_1.bold)(result.name)} deployed`);
|
|
157
|
-
(0, ui_1.out)(result.url);
|
|
158
|
-
if (result.secrets.length > 0) {
|
|
159
|
-
(0, ui_1.note)((0, ui_1.dim)(` Organization secrets in the environment: ${result.secrets.join(', ')}`));
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const available = await refreshFunctionsEnv(root, client, config.projectId);
|
|
163
|
-
(0, ui_1.note)((0, ui_1.dim)(` Addresses in .env updated (${available.join(', ')}). In the frontend: xflow.functions.invoke('${names[0]}')`));
|
|
164
|
-
}
|
package/dist/help.js
CHANGED
|
@@ -26,14 +26,13 @@ ${(0, ui_1.bold)('Code')}
|
|
|
26
26
|
fetch the sources (the latest revision by default)
|
|
27
27
|
|
|
28
28
|
${(0, ui_1.bold)('Releasing')}
|
|
29
|
-
xflow deploy [--no-push] send the code, build on the platform
|
|
29
|
+
xflow deploy [--no-push] send the code, ship the functions, build on the platform
|
|
30
30
|
xflow publish show the dev version to visitors
|
|
31
31
|
xflow rollback <version number> return the project to an earlier version
|
|
32
32
|
xflow deployments version history
|
|
33
33
|
|
|
34
34
|
${(0, ui_1.bold)('Functions')}
|
|
35
|
-
xflow functions list what is deployed
|
|
36
|
-
xflow functions deploy [name] build and ship a function
|
|
35
|
+
xflow functions list what is deployed (they ship with xflow deploy)
|
|
37
36
|
xflow functions invoke <name> [--data '{"a":1}']
|
|
38
37
|
call a function and print the answer
|
|
39
38
|
xflow functions logs [name] function crashes: stack and console output
|
|
@@ -125,8 +124,9 @@ mentions by name. A name assembled from an expression (${(0, ui_1.bold)("process
|
|
|
125
124
|
never reaches the environment, so read variables literally.
|
|
126
125
|
|
|
127
126
|
The value reaches the function on deploy, not at the moment it is stored: after
|
|
128
|
-
${(0, ui_1.bold)('env set')}
|
|
129
|
-
|
|
127
|
+
${(0, ui_1.bold)('env set')} run ${(0, ui_1.bold)('xflow deploy')}. The build ships a function whose code did not
|
|
128
|
+
change but whose variables did, so nothing is left holding an old value. The same after a
|
|
129
|
+
delete: a function already deployed keeps the old value until its next deploy.`,
|
|
130
130
|
schedules: `${(0, ui_1.bold)('xflow schedules')}: running functions on a timer
|
|
131
131
|
|
|
132
132
|
A schedule is a Yandex timer trigger: it calls the function itself, with no
|
|
@@ -224,8 +224,9 @@ into a non-empty one: the platform cannot merge changes, that is git's job.
|
|
|
224
224
|
--force overwrite the folder completely`,
|
|
225
225
|
deploy: `${(0, ui_1.bold)('xflow deploy')}: build and release
|
|
226
226
|
|
|
227
|
-
|
|
228
|
-
output directory come from xflow.json (npm run build and dist
|
|
227
|
+
Three steps: sending the sources, shipping the cloud functions, building the application.
|
|
228
|
+
The build command and the output directory come from xflow.json (npm run build and dist
|
|
229
|
+
by default).
|
|
229
230
|
|
|
230
231
|
--no-push do not send sources, build from the latest server revision
|
|
231
232
|
--force allow overwriting the server revision while sending
|
|
@@ -235,6 +236,21 @@ worked on my machine" no longer depends on your machine. Before the build the pr
|
|
|
235
236
|
is checked against the template: mismatches are printed as a list and the build does
|
|
236
237
|
not start at all.
|
|
237
238
|
|
|
239
|
+
Everything in ${(0, ui_1.bold)('functions/<name>/index.ts')} is bundled and shipped by the same run,
|
|
240
|
+
before the application is built. That order is not a convention but the only one that
|
|
241
|
+
works: the addresses of the functions are baked into the bundle, so they have to exist
|
|
242
|
+
first. A function whose code and variables did not change is left alone, and a function
|
|
243
|
+
that fails to ship fails the whole build.
|
|
244
|
+
|
|
245
|
+
A function gone from the sources is deleted from the cloud along with its schedules, and
|
|
246
|
+
the CLI names it before the build starts. That one is final: a function created again
|
|
247
|
+
later gets a different address. If the sources hold no functions at all while the cloud
|
|
248
|
+
holds several, nothing is deleted: that looks like a directory which never made it (a
|
|
249
|
+
${(0, ui_1.bold)('functions/')} line in .xflowignore) rather than a decision.
|
|
250
|
+
|
|
251
|
+
The database is not part of this: migrations change data in ways nothing can undo, so
|
|
252
|
+
they stay their own command (${(0, ui_1.bold)('xflow db migrate')}).
|
|
253
|
+
|
|
238
254
|
The built version is visible on the project page, and the CLI prints the link. Visitors
|
|
239
255
|
see it after ${(0, ui_1.bold)('xflow publish')}. The address of the build itself is not printed:
|
|
240
256
|
it carries the version number, and after the next publish such a link quietly serves an
|
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.2.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
|
@@ -40,8 +40,8 @@ restored within an hour of the data going back under the limit.
|
|
|
40
40
|
2. `npm run typecheck` for a two-second type check (older projects may not have the
|
|
41
41
|
script, then `npx tsc --noEmit`).
|
|
42
42
|
3. `npm run build` if the change is substantial, before deploying.
|
|
43
|
-
4. `xflow deploy` sends the sources
|
|
44
|
-
phase and the six-digit number of the version it built.
|
|
43
|
+
4. `xflow deploy` sends the sources, ships the cloud functions and builds the application
|
|
44
|
+
on the platform, printing each phase and the six-digit number of the version it built.
|
|
45
45
|
5. Give the user the project link the CLI printed and let them look. Do not open a
|
|
46
46
|
browser for them.
|
|
47
47
|
6. `xflow publish` makes that same version visible to visitors.
|
|
@@ -105,11 +105,16 @@ Interface rules, checked outside `src/components/ui` and `src/components/blocks`
|
|
|
105
105
|
|
|
106
106
|
## Cloud functions
|
|
107
107
|
|
|
108
|
-
Server-side code lives in `functions/<name>/index.ts` and exports `handler`.
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
Server-side code lives in `functions/<name>/index.ts` and exports `handler`. There is no
|
|
109
|
+
separate deploy command: `xflow deploy` ships the functions and then builds the application,
|
|
110
|
+
in that order. List what is live with `xflow functions list`. The handler returns
|
|
111
111
|
`{ statusCode, body }` where `body` is a JSON string.
|
|
112
112
|
|
|
113
|
+
The sources are the whole truth about which functions exist. Delete the directory and the
|
|
114
|
+
next deploy deletes the function from the cloud, schedules included, and that cannot be
|
|
115
|
+
undone: a function created again later gets a different address. So never remove a function
|
|
116
|
+
directory to "clean up" unless the user asked for the function to go.
|
|
117
|
+
|
|
113
118
|
Debugging a deployed function is two commands: `xflow functions invoke <name>` calls it
|
|
114
119
|
the way the app does and prints status, timing and body (`--data '{"a":1}'` sends a body),
|
|
115
120
|
and `xflow functions logs <name>` shows the failures, each with its stack and the console
|
|
@@ -118,9 +123,9 @@ never crashed, not that logging is broken.
|
|
|
118
123
|
|
|
119
124
|
From the app, call a function through `src/lib/xflow.ts`:
|
|
120
125
|
`await xflow.functions.invoke('send-mail', { body: { to } })`. It carries the project
|
|
121
|
-
token for you. Addresses are baked into the build
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
token for you. Addresses are baked into the build, which is why the functions go out first:
|
|
127
|
+
by the time the bundle is built they already exist, and a new function is never missing
|
|
128
|
+
from the application that calls it.
|
|
124
129
|
|
|
125
130
|
Treat a function as a public API: the token ships inside the frontend bundle, so anyone
|
|
126
131
|
who opens the app can call it.
|
|
@@ -131,8 +136,8 @@ functions read but the platform does not have. Values never come back out — th
|
|
|
131
136
|
they exist is inside the running function.
|
|
132
137
|
|
|
133
138
|
A function receives only the variables it mentions by name via `process.env.NAME`, so never
|
|
134
|
-
assemble a variable name from an expression. New values arrive on the next
|
|
135
|
-
|
|
139
|
+
assemble a variable name from an expression. New values arrive on the next `xflow deploy`,
|
|
140
|
+
not at the moment they are written.
|
|
136
141
|
|
|
137
142
|
To run a function on a timer: `xflow schedules set report "0 3 ? * * *"` (daily at 03:00).
|
|
138
143
|
Six fields, UTC, and exactly one of day-of-month / day-of-week must be `?` — that is
|
|
@@ -184,12 +189,12 @@ read-only queries, migrations, function logs and invocations, schedules, environ
|
|
|
184
189
|
variables, versions, publish and rollback. They answer with aggregates and say explicitly
|
|
185
190
|
when a result is truncated, which parsing terminal output does not.
|
|
186
191
|
|
|
187
|
-
Code never travels through those tools. Sending sources
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
never expect the starting call to return a finished version.
|
|
192
|
+
Code never travels through those tools. Sending sources stays in the CLI (`xflow push`):
|
|
193
|
+
pulling a repository through tool calls burns the user's tokens for nothing. Building is
|
|
194
|
+
available through the tools, because it runs from the revision already stored on the
|
|
195
|
+
server: `deployments action=build` starts it and answers immediately, `action=status`
|
|
196
|
+
reports the phase. It ships the functions too, from that same revision. A build takes
|
|
197
|
+
minutes, so never expect the starting call to return a finished version.
|
|
193
198
|
|
|
194
199
|
## Do not
|
|
195
200
|
|