@xano/cli 1.0.6-beta.13 → 1.0.6-beta.15
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/base-command.d.ts +5 -0
- package/dist/base-command.js +61 -4
- package/dist/utils/document-parser.js +36 -0
- package/dist/utils/multidoc-push.js +1 -0
- package/oclif.manifest.json +3095 -3095
- package/package.json +1 -1
package/dist/base-command.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export interface CredentialsFile {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
export declare function buildUserAgent(version: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* True when `error` is the "foreign dispatcher rejected by built-in fetch"
|
|
20
|
+
* failure (Node 26+). The real code lives on `error.cause.code`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isInvalidDispatcherError(error: unknown): boolean;
|
|
18
23
|
export interface SandboxTenant {
|
|
19
24
|
created_at?: string;
|
|
20
25
|
description?: string;
|
package/dist/base-command.js
CHANGED
|
@@ -29,13 +29,46 @@ function resolveRequestTimeoutMs(env = process.env) {
|
|
|
29
29
|
return DEFAULT_REQUEST_TIMEOUT_MS;
|
|
30
30
|
return parsed;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Whether the built-in `fetch` on this runtime accepts a dispatcher built from
|
|
34
|
+
* our bundled userland `undici`. Node <= 24 accepts it; Node 26 rejects a
|
|
35
|
+
* foreign dispatcher with UND_ERR_INVALID_ARG, which `fetch` surfaces only as
|
|
36
|
+
* the generic "fetch failed" — breaking *every* command (DEV-7773).
|
|
37
|
+
*
|
|
38
|
+
* Starts optimistic (`true`); flipped to `false` the first time a request fails
|
|
39
|
+
* with UND_ERR_INVALID_ARG (see `verboseFetch`, which then retries without the
|
|
40
|
+
* dispatcher). Once flipped, every later request skips the dispatcher outright.
|
|
41
|
+
*/
|
|
42
|
+
let dispatcherSupported = true;
|
|
43
|
+
/**
|
|
44
|
+
* True when `error` is the "foreign dispatcher rejected by built-in fetch"
|
|
45
|
+
* failure (Node 26+). The real code lives on `error.cause.code`.
|
|
46
|
+
*/
|
|
47
|
+
export function isInvalidDispatcherError(error) {
|
|
48
|
+
if (!(error instanceof Error))
|
|
49
|
+
return false;
|
|
50
|
+
const { cause } = error;
|
|
51
|
+
const code = cause && typeof cause === 'object' && 'code' in cause
|
|
52
|
+
? cause.code
|
|
53
|
+
: error.code;
|
|
54
|
+
return code === 'UND_ERR_INVALID_ARG';
|
|
55
|
+
}
|
|
32
56
|
/**
|
|
33
57
|
* Lazily-built undici dispatcher whose header/body inactivity timeouts match our
|
|
34
58
|
* request timeout, so undici's internal 300s default never fires first. Built
|
|
35
59
|
* once and reused across requests. `timeout === 0` means "no bound".
|
|
60
|
+
*
|
|
61
|
+
* Returns `undefined` when this runtime has been found to reject a foreign
|
|
62
|
+
* dispatcher (Node 26+, DEV-7773). Dropping the dispatcher does NOT regress the
|
|
63
|
+
* large-push timeout: the per-request `AbortSignal.timeout(timeoutMs)` in
|
|
64
|
+
* `verboseFetch` already bounds the whole request (headers included) to the same
|
|
65
|
+
* 15-min ceiling the dispatcher enforced, so undici's hidden 300s
|
|
66
|
+
* `headersTimeout` is subsumed by it.
|
|
36
67
|
*/
|
|
37
68
|
let sharedDispatcher;
|
|
38
69
|
function getRequestDispatcher(timeoutMs) {
|
|
70
|
+
if (!dispatcherSupported)
|
|
71
|
+
return undefined;
|
|
39
72
|
if (timeoutMs === 0) {
|
|
40
73
|
sharedDispatcher ??= new Agent({ bodyTimeout: 0, headersTimeout: 0 });
|
|
41
74
|
return sharedDispatcher;
|
|
@@ -324,15 +357,20 @@ export default class BaseCommand extends Command {
|
|
|
324
357
|
...options.headers,
|
|
325
358
|
};
|
|
326
359
|
const timeoutMs = resolveRequestTimeoutMs();
|
|
327
|
-
|
|
360
|
+
// Build the fetch options fresh each attempt: a retry needs a new
|
|
361
|
+
// AbortSignal (the first attempt's may already be aborted/consumed) and,
|
|
362
|
+
// when the dispatcher is the culprit, no dispatcher at all.
|
|
363
|
+
const buildFetchOptions = (withDispatcher) => ({
|
|
328
364
|
...options,
|
|
329
365
|
// undici dispatcher: lifts the hidden 300s headers/body timeout to our bound
|
|
330
|
-
dispatcher: getRequestDispatcher(timeoutMs),
|
|
366
|
+
...(withDispatcher ? { dispatcher: getRequestDispatcher(timeoutMs) } : {}),
|
|
331
367
|
headers,
|
|
332
368
|
// belt-and-suspenders hard ceiling that also covers DNS/connect stalls;
|
|
333
369
|
// surfaces as a clean AbortError (see describeNetworkError). 0 = no bound.
|
|
370
|
+
// This alone bounds the whole request, so dropping the dispatcher (Node 26)
|
|
371
|
+
// does not lose the large-push timeout protection.
|
|
334
372
|
...(timeoutMs > 0 && !options.signal ? { signal: AbortSignal.timeout(timeoutMs) } : {}),
|
|
335
|
-
};
|
|
373
|
+
});
|
|
336
374
|
const contentType = headers['Content-Type'] || 'application/json';
|
|
337
375
|
if (verbose) {
|
|
338
376
|
this.log('');
|
|
@@ -349,7 +387,26 @@ export default class BaseCommand extends Command {
|
|
|
349
387
|
}
|
|
350
388
|
}
|
|
351
389
|
const startTime = Date.now();
|
|
352
|
-
|
|
390
|
+
let response;
|
|
391
|
+
try {
|
|
392
|
+
response = await fetch(url, buildFetchOptions(dispatcherSupported));
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
// Node 26+ rejects our userland-undici dispatcher with UND_ERR_INVALID_ARG,
|
|
396
|
+
// surfaced only as "fetch failed" (DEV-7773). Disable it for the rest of the
|
|
397
|
+
// process and retry once without it; the AbortSignal.timeout ceiling above
|
|
398
|
+
// still bounds the request, so the large-push protection is preserved.
|
|
399
|
+
if (dispatcherSupported && isInvalidDispatcherError(error)) {
|
|
400
|
+
dispatcherSupported = false;
|
|
401
|
+
if (verbose) {
|
|
402
|
+
this.log(' (this Node runtime rejected undici dispatcher; retrying without it — see DEV-7773)');
|
|
403
|
+
}
|
|
404
|
+
response = await fetch(url, buildFetchOptions(false));
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
353
410
|
const elapsed = Date.now() - startTime;
|
|
354
411
|
if (verbose) {
|
|
355
412
|
this.log(`← ${response.status} ${response.statusText} (${elapsed}ms)`);
|
|
@@ -203,6 +203,42 @@ export function resolveDocumentPath(doc, outputDir, deps) {
|
|
|
203
203
|
// Realtime v1: realtime_trigger → realtime/trigger/{name}.xs
|
|
204
204
|
return { baseName: sanitize(doc.name), typeDir: join(outputDir, 'realtime', 'trigger') };
|
|
205
205
|
}
|
|
206
|
+
if (doc.type === 'realtime_server_trigger') {
|
|
207
|
+
// Realtime v2. A realtime_server's connect/disconnect trigger. It references its
|
|
208
|
+
// owning server by name via `realtime_server = "..."`, so it nests under that
|
|
209
|
+
// server's folder as `<server>/trigger/`, mirroring how the server's own document
|
|
210
|
+
// and its channels already nest under realtime/server/<server>/ (DEV-7712). Keeping
|
|
211
|
+
// it there means every realtime document stays under realtime/ instead of landing at
|
|
212
|
+
// the output root. A server-less doc (a malformed/legacy export) falls back to a flat
|
|
213
|
+
// realtime/server_trigger/ under realtime/ rather than the repo root.
|
|
214
|
+
return {
|
|
215
|
+
baseName: sanitize(doc.name),
|
|
216
|
+
typeDir: doc.server
|
|
217
|
+
? join(outputDir, 'realtime', 'server', sanitize(doc.server), 'trigger')
|
|
218
|
+
: join(outputDir, 'realtime', 'server_trigger'),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (doc.type === 'channel_trigger') {
|
|
222
|
+
// Realtime v2. A channel's join/leave/deliver trigger. It references BOTH its owning
|
|
223
|
+
// server (`realtime_server = "..."`) and its channel (`channel = "..."`), so it nests
|
|
224
|
+
// beside that channel as `<server>/channel/<channel>/trigger/` — the same path the
|
|
225
|
+
// channel's own document and messages already use (DEV-7712). Falls back gracefully:
|
|
226
|
+
// channel but no resolvable server → the legacy flat channel/<path>/trigger/ layout;
|
|
227
|
+
// neither → realtime/channel_trigger/ under realtime/ (never the output root).
|
|
228
|
+
if (doc.server && doc.channel) {
|
|
229
|
+
return {
|
|
230
|
+
baseName: sanitize(doc.name),
|
|
231
|
+
typeDir: join(outputDir, 'realtime', 'server', sanitize(doc.server), 'channel', snakeCase(doc.channel), 'trigger'),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (doc.channel) {
|
|
235
|
+
return {
|
|
236
|
+
baseName: sanitize(doc.name),
|
|
237
|
+
typeDir: join(outputDir, 'channel', ...channelPathSegments(doc.channel, snakeCase), 'trigger'),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return { baseName: sanitize(doc.name), typeDir: join(outputDir, 'realtime', 'channel_trigger') };
|
|
241
|
+
}
|
|
206
242
|
if (doc.type === 'realtime_server') {
|
|
207
243
|
// Realtime v2. The realtime_server is the top-level container that owns
|
|
208
244
|
// channels (which own messages). Its own document is named after itself
|
|
@@ -904,6 +904,7 @@ export function describeNetworkError(error, url, elapsedMs) {
|
|
|
904
904
|
ETIMEDOUT: `Connection to ${host} timed out. Check your network or VPN, then retry.`,
|
|
905
905
|
UND_ERR_CONNECT_TIMEOUT: `Connection to ${host} timed out. Check your network or VPN, then retry.`,
|
|
906
906
|
UND_ERR_HEADERS_TIMEOUT: `${host} accepted the connection but did not respond in time. The push may be too large; try splitting it or retrying.`,
|
|
907
|
+
UND_ERR_INVALID_ARG: `Your Node runtime rejected the CLI's HTTP dispatcher. Update the CLI, or run it under Node 24 (see DEV-7773).`,
|
|
907
908
|
};
|
|
908
909
|
let base;
|
|
909
910
|
if (code && hints[code]) {
|