1tube 0.1.39 → 0.1.41
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/package.json +1 -1
- package/src/backends/vercel/capture-config.ts +122 -0
- package/src/cli/vercel-build.ts +189 -10
package/package.json
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time capture of each function's declared `serve()` options for the
|
|
3
|
+
* Vercel target.
|
|
4
|
+
*
|
|
5
|
+
* On Vercel each function runs standalone, so `_shared/handler.ts` takes the
|
|
6
|
+
* `getStandaloneServe()` path and the gateway registry is never installed —
|
|
7
|
+
* which means a `serve({ timeoutMs })` value would otherwise be invisible to the
|
|
8
|
+
* static build that bakes `maxDuration` into `.vc-config.json`.
|
|
9
|
+
*
|
|
10
|
+
* Rather than parse source or require a side-car `1tube.json`, we read the value
|
|
11
|
+
* the function ACTUALLY declares, through the same contract the runtime uses:
|
|
12
|
+
* `_shared/handler.ts` calls `globalThis.__edgeFunctionRegistry.register(handler,
|
|
13
|
+
* { public, timeoutMs })` at import time whenever that global exists. We install
|
|
14
|
+
* a capturing stub, import each entrypoint once, and record what it registered.
|
|
15
|
+
*
|
|
16
|
+
* Cost & safety:
|
|
17
|
+
* - Runs ONLY at build time — the deployed function pays nothing.
|
|
18
|
+
* - Imports each entrypoint a single time; Deno caches the shared module graph
|
|
19
|
+
* (`handler.ts`, providers, …) across functions, so the marginal cost per
|
|
20
|
+
* function is just its own top-level `serve(...)` call.
|
|
21
|
+
* - The registry path returns before binding any server, so importing does not
|
|
22
|
+
* start listeners or do per-request work.
|
|
23
|
+
* - Resilient: a function that throws at import (e.g. a stray top-level env
|
|
24
|
+
* read) is skipped with a warning; the caller falls back to its manifest /
|
|
25
|
+
* the global default.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { pathToFileURL } from "node:url";
|
|
29
|
+
import type { BundleInput } from "../../bundler/core.ts";
|
|
30
|
+
|
|
31
|
+
/** What a function declared on its `serve({ ... })` call. */
|
|
32
|
+
export interface DeclaredServeConfig {
|
|
33
|
+
public: boolean;
|
|
34
|
+
/** Per-function wall-clock budget (ms). Undefined = no explicit timeout. */
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface RegistryStub {
|
|
39
|
+
register(
|
|
40
|
+
handler: unknown,
|
|
41
|
+
opts?: { public?: boolean; timeoutMs?: number },
|
|
42
|
+
): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const REGISTRY_KEY = "__edgeFunctionRegistry";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Minimal stand-in for `Deno.serve`'s return value so importing an entrypoint
|
|
49
|
+
* that calls `Deno.serve` directly (instead of the registry `serve()` wrapper)
|
|
50
|
+
* does not bind a real port during the build.
|
|
51
|
+
*/
|
|
52
|
+
function fakeHttpServer(): unknown {
|
|
53
|
+
return {
|
|
54
|
+
finished: Promise.resolve(),
|
|
55
|
+
shutdown: () => Promise.resolve(),
|
|
56
|
+
ref() {},
|
|
57
|
+
unref() {},
|
|
58
|
+
addr: { transport: "tcp", hostname: "0.0.0.0", port: 0 },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Import every entrypoint with a stub registry installed and collect the
|
|
64
|
+
* `serve()` options each one registers. Returns a `name → config` map; functions
|
|
65
|
+
* that don't register (or fail to import) are simply absent.
|
|
66
|
+
*/
|
|
67
|
+
export async function captureDeclaredServeConfigs(
|
|
68
|
+
inputs: readonly BundleInput[],
|
|
69
|
+
onWarn: (message: string) => void = (m) => console.warn(m),
|
|
70
|
+
): Promise<Map<string, DeclaredServeConfig>> {
|
|
71
|
+
const out = new Map<string, DeclaredServeConfig>();
|
|
72
|
+
if (inputs.length === 0) return out;
|
|
73
|
+
|
|
74
|
+
const globalAny = globalThis as Record<string, unknown>;
|
|
75
|
+
const previousRegistry = globalAny[REGISTRY_KEY];
|
|
76
|
+
|
|
77
|
+
let current: DeclaredServeConfig | undefined;
|
|
78
|
+
const stub: RegistryStub = {
|
|
79
|
+
register(_handler, opts) {
|
|
80
|
+
current = { public: !!opts?.public, timeoutMs: opts?.timeoutMs };
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
globalAny[REGISTRY_KEY] = stub;
|
|
84
|
+
|
|
85
|
+
// The registry path never calls Deno.serve, but neutralise it anyway so a raw
|
|
86
|
+
// `Deno.serve(...)` entrypoint can't bind a port (or leak a listener) during
|
|
87
|
+
// the sweep. Snapshot/restore around the whole pass.
|
|
88
|
+
const denoNs = (globalThis as { Deno?: { serve?: unknown } }).Deno;
|
|
89
|
+
const hadServe = !!denoNs && "serve" in denoNs;
|
|
90
|
+
const previousServe = denoNs?.serve;
|
|
91
|
+
if (denoNs) denoNs.serve = fakeHttpServer as unknown as typeof denoNs.serve;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
for (const input of inputs) {
|
|
95
|
+
current = undefined;
|
|
96
|
+
try {
|
|
97
|
+
await import(pathToFileURL(input.entrypoint).href);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
onWarn(
|
|
100
|
+
`could not load ${input.name} to read its serve() timeout; ` +
|
|
101
|
+
`falling back to manifest/default (${
|
|
102
|
+
err instanceof Error ? err.message : String(err)
|
|
103
|
+
})`,
|
|
104
|
+
);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (current) out.set(input.name, current);
|
|
108
|
+
}
|
|
109
|
+
} finally {
|
|
110
|
+
if (previousRegistry === undefined) delete globalAny[REGISTRY_KEY];
|
|
111
|
+
else globalAny[REGISTRY_KEY] = previousRegistry;
|
|
112
|
+
if (denoNs) {
|
|
113
|
+
if (hadServe) {
|
|
114
|
+
denoNs.serve = previousServe as typeof denoNs.serve;
|
|
115
|
+
} else {
|
|
116
|
+
delete denoNs.serve;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return out;
|
|
122
|
+
}
|
package/src/cli/vercel-build.ts
CHANGED
|
@@ -20,10 +20,19 @@
|
|
|
20
20
|
* required: the Vercel bundle wrapper captures `Deno.serve` and bridges Node
|
|
21
21
|
* req/res to the Web fetch handler (see `../backends/vercel/wrapper.ts`).
|
|
22
22
|
*
|
|
23
|
+
* Vercel's filesystem routing matches a `.func` only on its EXACT path, so
|
|
24
|
+
* `/functions/v1/rooms/<id>` would 404. Supabase's edge-runtime instead routes
|
|
25
|
+
* on the first path segment and forwards the whole request to the function, so
|
|
26
|
+
* path-based routers handle their own sub-paths. To reproduce that, this command
|
|
27
|
+
* also merges a per-function "main"-phase rewrite into `config.json` mapping
|
|
28
|
+
* `/<prefix>/<name>/<rest>` back to the bare function path (see
|
|
29
|
+
* {@link mergeSubpathRoutes}).
|
|
30
|
+
*
|
|
23
31
|
* This command MERGES into an existing `.vercel/output` (e.g. one produced by
|
|
24
32
|
* `vercel build` for the static frontend). It only creates/replaces the
|
|
25
|
-
* specific `.func` directories
|
|
26
|
-
* frontend's
|
|
33
|
+
* specific `.func` directories and additively edits `config.json`'s `routes`
|
|
34
|
+
* (preserving the frontend's existing routes and static assets); it never wipes
|
|
35
|
+
* the output root.
|
|
27
36
|
*
|
|
28
37
|
* Each function is fully standalone — there is no gateway shared runtime on
|
|
29
38
|
* Vercel, so modules like `_shared/profile-cache.ts` are bundled inline rather
|
|
@@ -40,6 +49,7 @@ import {
|
|
|
40
49
|
disposeBundlerResources,
|
|
41
50
|
} from "../bundler/core.ts";
|
|
42
51
|
import { VERCEL_PROFILE } from "../backends/vercel/bundle-profile.ts";
|
|
52
|
+
import { captureDeclaredServeConfigs } from "../backends/vercel/capture-config.ts";
|
|
43
53
|
import { loadManifest } from "../manifest.ts";
|
|
44
54
|
import { resolveDenoConfigPath } from "./deno-config.ts";
|
|
45
55
|
|
|
@@ -47,8 +57,13 @@ import { resolveDenoConfigPath } from "./deno-config.ts";
|
|
|
47
57
|
const DEFAULT_RUNTIME = "nodejs24.x";
|
|
48
58
|
/** Default route prefix; matches sciobot's `/functions/v1/<name>` calls. */
|
|
49
59
|
const DEFAULT_PATH_PREFIX = "functions/v1";
|
|
50
|
-
/**
|
|
51
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Default function timeout (seconds) when a function declares no timeout via
|
|
62
|
+
* `serve({ timeoutMs })` (or a `1tube.json` manifest). Defaults to the cap so
|
|
63
|
+
* long-running AI pipelines aren't silently truncated unless a function opts
|
|
64
|
+
* into a shorter timeout.
|
|
65
|
+
*/
|
|
66
|
+
const DEFAULT_MAX_DURATION = 800;
|
|
52
67
|
/** Upper bound for maxDuration (seconds). Fluid/Pro allows long durations. */
|
|
53
68
|
const DEFAULT_MAX_DURATION_CAP = 800;
|
|
54
69
|
|
|
@@ -70,7 +85,10 @@ export interface VercelBuildOptions {
|
|
|
70
85
|
pathPrefix?: string;
|
|
71
86
|
/** Vercel runtime identifier. Defaults to "nodejs24.x". */
|
|
72
87
|
runtime?: string;
|
|
73
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Fallback maxDuration (seconds) when a function declares no `timeoutMs`
|
|
90
|
+
* (via `serve({ timeoutMs })` or `1tube.json`).
|
|
91
|
+
*/
|
|
74
92
|
defaultMaxDuration?: number;
|
|
75
93
|
/** Hard cap (seconds) applied to any derived maxDuration. */
|
|
76
94
|
maxDurationCap?: number;
|
|
@@ -92,7 +110,14 @@ export type VercelBuildProgress =
|
|
|
92
110
|
route: string;
|
|
93
111
|
bytes: number;
|
|
94
112
|
maxDuration: number;
|
|
95
|
-
}
|
|
113
|
+
}
|
|
114
|
+
| {
|
|
115
|
+
phase: "config-merge";
|
|
116
|
+
configPath: string;
|
|
117
|
+
routes: number;
|
|
118
|
+
created: boolean;
|
|
119
|
+
}
|
|
120
|
+
| { phase: "capture-warning"; message: string };
|
|
96
121
|
|
|
97
122
|
export interface VercelFunctionEntry {
|
|
98
123
|
/** Logical function name. */
|
|
@@ -122,6 +147,10 @@ export interface VercelBuildResult {
|
|
|
122
147
|
pathPrefix: string;
|
|
123
148
|
/** Emitted functions, sorted by name. */
|
|
124
149
|
functions: VercelFunctionEntry[];
|
|
150
|
+
/** Absolute path to the Build Output API `config.json` that was merged. */
|
|
151
|
+
configPath: string;
|
|
152
|
+
/** Number of per-function sub-path routes written into `config.json`. */
|
|
153
|
+
subpathRoutes: number;
|
|
125
154
|
durationMs: number;
|
|
126
155
|
}
|
|
127
156
|
|
|
@@ -166,6 +195,116 @@ function normalizePrefix(prefix: string): string {
|
|
|
166
195
|
);
|
|
167
196
|
}
|
|
168
197
|
|
|
198
|
+
/** Escape a string for safe literal use inside a regular expression. */
|
|
199
|
+
function escapeRegExp(input: string): string {
|
|
200
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Minimal shape of a Build Output API route entry we read or write. */
|
|
204
|
+
interface VercelRoute {
|
|
205
|
+
handle?: string;
|
|
206
|
+
src?: string;
|
|
207
|
+
dest?: string;
|
|
208
|
+
[key: string]: unknown;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Minimal shape of `.vercel/output/config.json` (Build Output API v3). */
|
|
212
|
+
interface VercelOutputConfig {
|
|
213
|
+
version?: number;
|
|
214
|
+
routes?: VercelRoute[];
|
|
215
|
+
[key: string]: unknown;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Add, per function, a "main"-phase rewrite mapping `/<prefix>/<name>/<rest>`
|
|
220
|
+
* to the bare function path `/<prefix>/<name>` so a single Vercel Node Function
|
|
221
|
+
* also serves all of its sub-paths.
|
|
222
|
+
*
|
|
223
|
+
* Why this is needed: Vercel's filesystem routing matches a `.func` only on its
|
|
224
|
+
* exact path, so `/<prefix>/rooms/<id>` would 404. Supabase's edge-runtime
|
|
225
|
+
* instead routes on the first path segment and forwards the whole request to the
|
|
226
|
+
* function (`worker.fetch(req)`) — the behaviour path-based routers such as
|
|
227
|
+
* sciobot's `rooms` handler rely on. A *regular* serverless function receives
|
|
228
|
+
* the UNMODIFIED original URL through a rewrite (only prerender functions get
|
|
229
|
+
* the rewritten path), so the function still sees the sub-path in
|
|
230
|
+
* `url.pathname`.
|
|
231
|
+
*
|
|
232
|
+
* The routes go in the main phase (before the first `handle` marker) so each
|
|
233
|
+
* rewrite is immediately followed by the filesystem lookup that resolves the
|
|
234
|
+
* `.func`. The exact path (`/<prefix>/<name>`) is intentionally NOT matched — it
|
|
235
|
+
* already resolves via filesystem routing — keeping the change purely additive.
|
|
236
|
+
*
|
|
237
|
+
* Existing routes are preserved; only this builder's own previously-written
|
|
238
|
+
* sub-path routes (for the functions being built now) are replaced, so repeated
|
|
239
|
+
* and `--only` builds stay idempotent.
|
|
240
|
+
*/
|
|
241
|
+
async function mergeSubpathRoutes(
|
|
242
|
+
outDir: string,
|
|
243
|
+
pathPrefix: string,
|
|
244
|
+
functionNames: readonly string[],
|
|
245
|
+
): Promise<{ created: boolean; routeCount: number }> {
|
|
246
|
+
const configPath = join(outDir, "config.json");
|
|
247
|
+
|
|
248
|
+
let config: VercelOutputConfig | null = null;
|
|
249
|
+
try {
|
|
250
|
+
config = JSON.parse(
|
|
251
|
+
await Deno.readTextFile(configPath),
|
|
252
|
+
) as VercelOutputConfig;
|
|
253
|
+
} catch (err) {
|
|
254
|
+
if (!(err instanceof Deno.errors.NotFound)) throw err;
|
|
255
|
+
}
|
|
256
|
+
const created = config === null;
|
|
257
|
+
if (config === null) config = { version: 3, routes: [] };
|
|
258
|
+
if (typeof config.version !== "number") config.version = 3;
|
|
259
|
+
|
|
260
|
+
const existing = Array.isArray(config.routes) ? config.routes : [];
|
|
261
|
+
|
|
262
|
+
const prefixRe = escapeRegExp(pathPrefix);
|
|
263
|
+
const destPrefix = `/${pathPrefix}/`;
|
|
264
|
+
const builtNames = new Set(functionNames);
|
|
265
|
+
// The exact route a function `name` maps to. Used for BOTH emit and cleanup
|
|
266
|
+
// so detection can never drift from generation (e.g. a prefix with regex
|
|
267
|
+
// metacharacters is escaped identically in both).
|
|
268
|
+
const routeFor = (name: string): Required<Pick<VercelRoute, "src" | "dest">> => ({
|
|
269
|
+
src: `^/${prefixRe}/${escapeRegExp(name)}/.*$`,
|
|
270
|
+
dest: `${destPrefix}${name}`,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Drop our own previously-written routes for the functions being built now: a
|
|
274
|
+
// route is ours iff its dest is the bare function path AND its src is exactly
|
|
275
|
+
// what we would emit for that function. Gating on `builtNames` leaves routes
|
|
276
|
+
// for other functions intact, so `--only` (incremental) builds are safe.
|
|
277
|
+
const isOwnRoute = (r: VercelRoute): boolean => {
|
|
278
|
+
if (r.handle !== undefined || typeof r.dest !== "string") return false;
|
|
279
|
+
if (!r.dest.startsWith(destPrefix)) return false;
|
|
280
|
+
const name = r.dest.slice(destPrefix.length);
|
|
281
|
+
return builtNames.has(name) && r.src === routeFor(name).src;
|
|
282
|
+
};
|
|
283
|
+
const cleaned = existing.filter((r) => !isOwnRoute(r));
|
|
284
|
+
|
|
285
|
+
const subpathRoutes: VercelRoute[] = [...builtNames]
|
|
286
|
+
.sort((a, b) => a.localeCompare(b))
|
|
287
|
+
.map((name) => routeFor(name));
|
|
288
|
+
|
|
289
|
+
// Place the routes in the main phase — before the first `handle` marker — so
|
|
290
|
+
// each rewrite is immediately resolved against the filesystem (the `.func`).
|
|
291
|
+
const markerIdx = cleaned.findIndex((r) => typeof r.handle === "string");
|
|
292
|
+
config.routes = markerIdx === -1
|
|
293
|
+
? [...cleaned, ...subpathRoutes]
|
|
294
|
+
: [
|
|
295
|
+
...cleaned.slice(0, markerIdx),
|
|
296
|
+
...subpathRoutes,
|
|
297
|
+
...cleaned.slice(markerIdx),
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
await ensureDir(outDir);
|
|
301
|
+
await Deno.writeTextFile(
|
|
302
|
+
configPath,
|
|
303
|
+
JSON.stringify(config, null, 2) + "\n",
|
|
304
|
+
);
|
|
305
|
+
return { created, routeCount: subpathRoutes.length };
|
|
306
|
+
}
|
|
307
|
+
|
|
169
308
|
async function buildVercelOnce(
|
|
170
309
|
opts: VercelBuildOptions,
|
|
171
310
|
): Promise<VercelBuildResult> {
|
|
@@ -219,6 +358,15 @@ async function buildVercelOnce(
|
|
|
219
358
|
durationMs: performance.now() - bundleStart,
|
|
220
359
|
});
|
|
221
360
|
|
|
361
|
+
// Read each function's declared `serve({ timeoutMs })` so it can drive
|
|
362
|
+
// `maxDuration` without a side-car manifest. Done after bundling so the
|
|
363
|
+
// entrypoints' module graph is already warm in Deno's cache; the deployed
|
|
364
|
+
// function is unaffected (see capture-config.ts).
|
|
365
|
+
const declaredConfigs = await captureDeclaredServeConfigs(
|
|
366
|
+
inputs,
|
|
367
|
+
(m) => opts.onProgress?.({ phase: "capture-warning", message: m }),
|
|
368
|
+
);
|
|
369
|
+
|
|
222
370
|
const functionsRoot = join(outDir, "functions", ...pathPrefix.split("/"));
|
|
223
371
|
const entries: VercelFunctionEntry[] = [];
|
|
224
372
|
|
|
@@ -243,8 +391,11 @@ async function buildVercelOnce(
|
|
|
243
391
|
|
|
244
392
|
const handler = fn.moduleFiles[0];
|
|
245
393
|
const manifest = await loadManifest(functionsDir, fn.name);
|
|
394
|
+
// A `serve({ timeoutMs })` declaration wins over the manifest, matching
|
|
395
|
+
// the runtime precedence in `_shared/handler.ts`.
|
|
396
|
+
const declaredTimeoutMs = declaredConfigs.get(fn.name)?.timeoutMs;
|
|
246
397
|
const maxDuration = clampMaxDuration(
|
|
247
|
-
manifest.timeoutMs,
|
|
398
|
+
declaredTimeoutMs ?? manifest.timeoutMs,
|
|
248
399
|
defaultMaxDuration,
|
|
249
400
|
maxDurationCap,
|
|
250
401
|
);
|
|
@@ -289,12 +440,31 @@ async function buildVercelOnce(
|
|
|
289
440
|
}
|
|
290
441
|
|
|
291
442
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
443
|
+
|
|
444
|
+
// Make every function also serve its sub-paths (e.g. /<prefix>/rooms/<id>),
|
|
445
|
+
// matching Supabase edge-runtime's first-segment routing. Done after the
|
|
446
|
+
// `.func` dirs exist so the rewrites resolve to real functions.
|
|
447
|
+
const configPath = join(outDir, "config.json");
|
|
448
|
+
const merge = await mergeSubpathRoutes(
|
|
449
|
+
outDir,
|
|
450
|
+
pathPrefix,
|
|
451
|
+
entries.map((e) => e.name),
|
|
452
|
+
);
|
|
453
|
+
opts.onProgress?.({
|
|
454
|
+
phase: "config-merge",
|
|
455
|
+
configPath,
|
|
456
|
+
routes: merge.routeCount,
|
|
457
|
+
created: merge.created,
|
|
458
|
+
});
|
|
459
|
+
|
|
292
460
|
return {
|
|
293
461
|
outDir,
|
|
294
462
|
functionsRoot,
|
|
295
463
|
runtime,
|
|
296
464
|
pathPrefix,
|
|
297
465
|
functions: entries,
|
|
466
|
+
configPath,
|
|
467
|
+
subpathRoutes: merge.routeCount,
|
|
298
468
|
durationMs: performance.now() - startedAt,
|
|
299
469
|
};
|
|
300
470
|
} finally {
|
|
@@ -325,7 +495,7 @@ export async function buildVercel(
|
|
|
325
495
|
* --minify minify bundles
|
|
326
496
|
* --path-prefix <p> route prefix under functions/ (default functions/v1)
|
|
327
497
|
* --runtime <id> Vercel runtime (default nodejs24.x)
|
|
328
|
-
* --max-duration <s> fallback maxDuration seconds (default
|
|
498
|
+
* --max-duration <s> fallback maxDuration seconds (default 800)
|
|
329
499
|
* --max-duration-cap <s> hard cap for maxDuration (default 800)
|
|
330
500
|
* --config <path> explicit deno.json path
|
|
331
501
|
* --no-config skip the import map
|
|
@@ -463,6 +633,14 @@ export async function runVercelBuild(args: string[]): Promise<number> {
|
|
|
463
633
|
fmt(event.bytes)
|
|
464
634
|
} (maxDuration ${event.maxDuration}s)`,
|
|
465
635
|
);
|
|
636
|
+
} else if (event.phase === "config-merge") {
|
|
637
|
+
console.log(
|
|
638
|
+
`[1tube vercel-build] ${
|
|
639
|
+
event.created ? "wrote" : "merged"
|
|
640
|
+
} ${event.routes} sub-path route(s) into ${event.configPath}`,
|
|
641
|
+
);
|
|
642
|
+
} else if (event.phase === "capture-warning") {
|
|
643
|
+
console.warn(`[1tube vercel-build] ${event.message}`);
|
|
466
644
|
}
|
|
467
645
|
},
|
|
468
646
|
});
|
|
@@ -500,8 +678,9 @@ Options:
|
|
|
500
678
|
(default: ${DEFAULT_ESBUILD_TARGET})
|
|
501
679
|
--path-prefix <p> Route prefix under functions/ (default: functions/v1)
|
|
502
680
|
--runtime <id> Vercel runtime (default: nodejs24.x)
|
|
503
|
-
--max-duration <s> Fallback maxDuration seconds when
|
|
504
|
-
timeoutMs
|
|
681
|
+
--max-duration <s> Fallback maxDuration seconds when a function
|
|
682
|
+
declares no timeoutMs via serve()/1tube.json
|
|
683
|
+
(default: 800)
|
|
505
684
|
--max-duration-cap <s> Hard cap for maxDuration seconds (default: 800)
|
|
506
685
|
--config <path> Explicit deno.json path for the import map.
|
|
507
686
|
Default: auto-detect cwd/deno.json[c] then
|