1tube 0.1.41 → 0.1.43
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 +57 -7
- package/deno.lock +5 -5
- package/drizzle/20260610161429_init_logs/snapshot.json +1 -1
- package/drizzle/20260610161430_logs_fts/snapshot.json +1 -1
- package/package.json +2 -2
- package/scripts/npm-cli.js +165 -19
- package/src/backends/deno/shared-runtime.ts +5 -1
- package/src/backends/deno/worker-entry.ts +27 -8
- package/src/backends/deno/worker-host.ts +253 -30
- package/src/backends/vercel/wrapper.ts +2 -1
- package/src/backends/workerd/backend.ts +450 -339
- package/src/backends/workerd/capnp.ts +30 -11
- package/src/backends/workerd/hot-reloader.ts +28 -11
- package/src/backends/workerd/prebuilt.ts +66 -51
- package/src/backends/workerd/process.ts +26 -9
- package/src/backends/workerd/runtime-overrides.ts +199 -59
- package/src/backends/workerd/shared-runtime.ts +12 -5
- package/src/boot-progress.ts +99 -0
- package/src/bundler/core.ts +3 -1
- package/src/cli/build.ts +17 -3
- package/src/cli/vercel-build.ts +12 -9
- package/src/discovery.ts +20 -9
- package/src/env-scope.ts +6 -2
- package/src/gateway/auth.ts +6 -2
- package/src/gateway/cors.ts +108 -13
- package/src/gateway/logging.ts +92 -31
- package/src/gateway/rate-limit.ts +16 -12
- package/src/gateway/route-prefix.ts +83 -0
- package/src/health.ts +3 -1
- package/src/logs/api.ts +1 -5
- package/src/logs/db.ts +3 -1
- package/src/logs/query.ts +3 -1
- package/src/logs/schema.ts +1 -6
- package/src/logs/writer.ts +3 -1
- package/src/manifest.ts +2 -1
- package/src/registry.ts +20 -9
- package/src/server.ts +95 -17
package/README.md
CHANGED
|
@@ -41,7 +41,12 @@ in-process on filesystem changes (including newly created function folders).
|
|
|
41
41
|
## npm CLI
|
|
42
42
|
|
|
43
43
|
The published npm package exposes a `1tube` binary. It is a tiny Node shim that
|
|
44
|
-
launches this repo's Deno CLI, so machines using it need Deno on `PATH
|
|
44
|
+
launches this repo's Deno CLI, so machines using it need Deno on `PATH` (set
|
|
45
|
+
`DENO_BIN` to point at a specific `deno` if it isn't). **Prefer this launcher
|
|
46
|
+
over a hand-rolled `deno run … src/server.ts` line** — it applies 1tube's
|
|
47
|
+
recommended defaults (see [minimum dependency age](#minimum-dependency-age)
|
|
48
|
+
below) before Deno starts, which a flag added _inside_ the gateway can no longer
|
|
49
|
+
do.
|
|
45
50
|
|
|
46
51
|
```bash
|
|
47
52
|
# Show CLI help
|
|
@@ -57,6 +62,44 @@ npx 1tube package --functions supabase/functions --in dist --out fw.1tube --sign
|
|
|
57
62
|
This replaces local-source invocations such as
|
|
58
63
|
`deno run -A ../1tube/src/cli.ts ...` in downstream CI pipelines.
|
|
59
64
|
|
|
65
|
+
### Passing deno flags through the launcher
|
|
66
|
+
|
|
67
|
+
Arguments before a standalone `--` are 1tube CLI args; everything **after** `--`
|
|
68
|
+
is forwarded verbatim to `deno run` (inserted before the entrypoint). This lets
|
|
69
|
+
the launcher fully replace a project's bespoke `deno run … src/server.ts`
|
|
70
|
+
command — including project-level deno flags like `--config`, `--env-file`,
|
|
71
|
+
`--node-modules-dir` and `--no-lock`:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx 1tube serve --functions ./supabase/functions --dev --hmr \
|
|
75
|
+
-- --no-lock --env-file=.env --config ./supabase/functions/deno.json --node-modules-dir=false
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- If your passthrough includes its own `--config`, the launcher does **not** add
|
|
79
|
+
1tube's bundled `deno.json` — your import map wins (the gateway's own sources
|
|
80
|
+
use fully-qualified `npm:`/`jsr:` specifiers, so they resolve regardless).
|
|
81
|
+
- Prefer keeping the command line clean? Set the same flags in
|
|
82
|
+
`1TUBE_DENO_ARGS` (space-separated) instead of after `--`. CLI passthrough
|
|
83
|
+
takes precedence on any duplicate flag.
|
|
84
|
+
|
|
85
|
+
### Minimum dependency age
|
|
86
|
+
|
|
87
|
+
Deno 2.9 turns on a **24-hour minimum dependency age** by default: a function
|
|
88
|
+
that pins a just-published npm version fails to resolve locally with
|
|
89
|
+
_"… is newer than the specified minimum dependency date"_. Because Deno
|
|
90
|
+
snapshots npm config at process startup, this can only be changed by the
|
|
91
|
+
launcher, not from inside the running gateway.
|
|
92
|
+
|
|
93
|
+
The `1tube` launcher (and the .NET host) therefore export
|
|
94
|
+
`NPM_CONFIG_MIN_RELEASE_AGE=0` for the Deno child **unless you've already set
|
|
95
|
+
it**. That env var is the lowest explicit tier in Deno's precedence chain
|
|
96
|
+
(`--minimum-dependency-age` > `deno.json` `minimumDependencyAge` > `.npmrc`
|
|
97
|
+
`min-release-age` > this env var > the 24h built-in default), so it cancels
|
|
98
|
+
_only_ the new built-in default — any age you configure through `.npmrc`,
|
|
99
|
+
`deno.json`, or the CLI flag still wins. To re-enable a guard, set
|
|
100
|
+
`NPM_CONFIG_MIN_RELEASE_AGE` (e.g. `24h`) yourself or use any higher-precedence
|
|
101
|
+
mechanism.
|
|
102
|
+
|
|
60
103
|
### Deno npm lifecycle scripts
|
|
61
104
|
|
|
62
105
|
Deno does not run npm `postinstall` / build scripts unless you allow them.
|
|
@@ -67,12 +110,12 @@ dependency install — **1tube’s runtime graph is just Hono + JSR std**.
|
|
|
67
110
|
|
|
68
111
|
## Endpoints
|
|
69
112
|
|
|
70
|
-
| Path | Description
|
|
71
|
-
| -------------------------- |
|
|
72
|
-
| `GET /` | Liveness probe (`{"status":"ok"}`) — intentionally minimal so unauthenticated callers don't see the function map.
|
|
73
|
-
| `POST /functions/v1/:name` | Invoke an edge function
|
|
74
|
-
| `GET /health` | Auth-gated health (`Authorization: Bearer $INTERNAL_KEY`); without auth returns the same minimal `{"status":"ok"}`.
|
|
75
|
-
| `GET /metrics` | Auth-gated Prometheus exposition (same scheme).
|
|
113
|
+
| Path | Description |
|
|
114
|
+
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
115
|
+
| `GET /` | Liveness probe (`{"status":"ok"}`) — intentionally minimal so unauthenticated callers don't see the function map. |
|
|
116
|
+
| `POST /functions/v1/:name` | Invoke an edge function |
|
|
117
|
+
| `GET /health` | Auth-gated health (`Authorization: Bearer $INTERNAL_KEY`); without auth returns the same minimal `{"status":"ok"}`. |
|
|
118
|
+
| `GET /metrics` | Auth-gated Prometheus exposition (same scheme). |
|
|
76
119
|
| `GET /1tube/warmup` | Boot progress (deferred boot): `{ready, total, loaded, loading, queued, failed}`. CORS-enabled so frontends can poll it to drive a warm-up overlay. |
|
|
77
120
|
|
|
78
121
|
## Configuration
|
|
@@ -94,6 +137,12 @@ All knobs default to safe-but-backwards-compatible values. The TS gateway
|
|
|
94
137
|
| `1TUBE_BODY_LIMIT_MB` | `30` | Hono `bodyLimit`; matches Supabase. Returns 413 before the handler runs. |
|
|
95
138
|
| `1TUBE_BODY_READ_MS` | `30000` | Slow-loris guard. Max idle gap (ms) between body chunks before the request is aborted with **408**. NOT a total body-read deadline — large but fast uploads pass through. Set `0` to disable. |
|
|
96
139
|
| `1TUBE_CORS_ORIGIN` | `*` (dev only) | Comma-separated allowlist or `*`. In prod, leaving unset disables CORS. |
|
|
140
|
+
| `1TUBE_CORS_ALLOW_HEADERS` | Supabase defaults | Comma-separated `Access-Control-Allow-Headers`. Replaces the built-in default list (`authorization, x-client-info, apikey, content-type`, …) when set. |
|
|
141
|
+
| `1TUBE_CORS_ALLOW_METHODS` | Supabase defaults | Comma-separated `Access-Control-Allow-Methods` (default `GET, POST, PUT, PATCH, DELETE, OPTIONS`). |
|
|
142
|
+
| `1TUBE_CORS_EXPOSE_HEADERS` | 1tube internals | Comma-separated `Access-Control-Expose-Headers`. **Merged with** (not replacing) the headers 1tube must always expose (e.g. `X-1tube-Warming`). |
|
|
143
|
+
| `1TUBE_CORS_MAX_AGE` | unset | Seconds for `Access-Control-Max-Age` (preflight cache). Omitted when unset. |
|
|
144
|
+
| `1TUBE_CORS_ALLOW_CREDENTIALS` | off | `1`/`true` emits `Access-Control-Allow-Credentials: true`. Per the Fetch spec this is incompatible with `Origin: *`, so when credentials are on, a literal `*` allowlist is rejected — set an explicit origin. |
|
|
145
|
+
| `--route-prefix` / `1TUBE_ROUTE_PREFIX` | `/functions/v1` | URL prefix functions are mounted under. Change it to e.g. `/api` to serve at `/api/<name>`. Normalized to a leading-slash, no-trailing-slash form; logging and rate-limiting follow it automatically. |
|
|
97
146
|
| `1TUBE_TRUSTED_PROXIES` | empty | Comma-separated list of remote IPs whose `X-Forwarded-For` is honored. Anything else uses the raw socket address — XFF spoofing no longer mints fresh rate-limit buckets. |
|
|
98
147
|
| `1TUBE_SHUTDOWN_GRACE_MS` | `10000` | SIGINT/SIGTERM drain budget. |
|
|
99
148
|
| `INTERNAL_KEY` | unset | Required to read detailed `/health` and `/metrics`. Header-only: `Authorization: Bearer $INTERNAL_KEY`. |
|
|
@@ -105,6 +154,7 @@ All knobs default to safe-but-backwards-compatible values. The TS gateway
|
|
|
105
154
|
| `1TUBE_SHUTDOWN_GRACE_MS` | `10000` | Total wall-clock budget (ms) for shutdown, split between (1) draining gateway in-flight requests and (2) tearing down the workerd subprocess. See [Graceful shutdown](#graceful-shutdown) below. |
|
|
106
155
|
| `1TUBE_DEFAULT_RPM` | `120` | Override the gateway-wide default rate limit (requests per minute, per IP, per function). Per-function `1tube.json#rpm` still wins. |
|
|
107
156
|
| `1TUBE_DISABLE_RATE_LIMIT` | unset | Set to `1` to bypass rate limiting entirely. **Load-test / dev only** — production deployments must keep the limiter on. The gateway prints a clear warning at boot when this is enabled. |
|
|
157
|
+
| `NPM_CONFIG_MIN_RELEASE_AGE` | `0` (set by launcher) | Deno 2.9's minimum dependency age. The `1tube` launcher and .NET host export `0` for the Deno child unless you set it, cancelling only Deno's new 24h default. See [Minimum dependency age](#minimum-dependency-age). Must be set _before_ Deno starts — a gateway flag can't change it. |
|
|
108
158
|
|
|
109
159
|
````
|
|
110
160
|
## Deferred boot & warm-up overlay
|
package/deno.lock
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"npm:es-module-lexer@2": "2.1.0",
|
|
21
21
|
"npm:esbuild@~0.25.5": "0.25.12",
|
|
22
22
|
"npm:esbuild@~0.28.1": "0.28.1",
|
|
23
|
-
"npm:hono@4": "4.12.
|
|
24
|
-
"npm:hono@^4.12.
|
|
23
|
+
"npm:hono@4": "4.12.27",
|
|
24
|
+
"npm:hono@^4.12.27": "4.12.27",
|
|
25
25
|
"npm:typescript@latest": "6.0.3",
|
|
26
26
|
"npm:zod@^3.23.0": "3.25.76"
|
|
27
27
|
},
|
|
@@ -515,8 +515,8 @@
|
|
|
515
515
|
"resolve-pkg-maps"
|
|
516
516
|
]
|
|
517
517
|
},
|
|
518
|
-
"hono@4.12.
|
|
519
|
-
"integrity": "sha512-
|
|
518
|
+
"hono@4.12.27": {
|
|
519
|
+
"integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q=="
|
|
520
520
|
},
|
|
521
521
|
"jiti@2.6.1": {
|
|
522
522
|
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
|
@@ -555,7 +555,7 @@
|
|
|
555
555
|
"dependencies": [
|
|
556
556
|
"npm:drizzle-kit@1.0.0-rc.3",
|
|
557
557
|
"npm:drizzle-orm@1.0.0-rc.3",
|
|
558
|
-
"npm:hono@^4.12.
|
|
558
|
+
"npm:hono@^4.12.27"
|
|
559
559
|
]
|
|
560
560
|
}
|
|
561
561
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "1tube",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Self-hosted Supabase Edge Functions gateway for Deno, plus an ESLint plugin for consumers",
|
|
6
6
|
"license": "MIT",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"drizzle-orm": "1.0.0-rc.3",
|
|
65
|
-
"hono": "^4.12.
|
|
65
|
+
"hono": "^4.12.27"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"drizzle-kit": "1.0.0-rc.3"
|
package/scripts/npm-cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import {
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
6
|
import process from "node:process";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
@@ -9,26 +10,112 @@ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
9
10
|
const denoConfig = resolve(packageRoot, "deno.json");
|
|
10
11
|
const cliEntrypoint = resolve(packageRoot, "src", "cli.ts");
|
|
11
12
|
const denoBin = process.env.DENO_BIN || "deno";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
|
|
14
|
+
// Arg model
|
|
15
|
+
// ---------
|
|
16
|
+
// Everything BEFORE a standalone `--` is a 1tube CLI argument (forwarded
|
|
17
|
+
// after the entrypoint). Everything AFTER `--` is a raw `deno run` flag,
|
|
18
|
+
// inserted BEFORE the entrypoint — so this launcher can fully replace a
|
|
19
|
+
// hand-rolled `deno run … src/server.ts` line:
|
|
20
|
+
//
|
|
21
|
+
// 1tube serve --functions ./supabase/functions --dev --hmr \
|
|
22
|
+
// -- --no-lock --env-file=.env --config ./supabase/functions/deno.json --node-modules-dir=false
|
|
23
|
+
//
|
|
24
|
+
// Projects that would rather not thread flags through the command line
|
|
25
|
+
// can set `1TUBE_DENO_ARGS` instead (same flags, space-separated).
|
|
26
|
+
const rawArgs = process.argv.slice(2);
|
|
27
|
+
const sepIndex = rawArgs.indexOf("--");
|
|
28
|
+
const tubeArgs = sepIndex === -1 ? rawArgs : rawArgs.slice(0, sepIndex);
|
|
29
|
+
const cliDenoArgs = sepIndex === -1 ? [] : rawArgs.slice(sepIndex + 1);
|
|
30
|
+
|
|
31
|
+
// CLI passthrough is appended last so it wins on any duplicate flag
|
|
32
|
+
// (Deno applies last-one-wins for repeated flags).
|
|
33
|
+
const denoPassthrough = [
|
|
34
|
+
...tokenizeArgs(
|
|
35
|
+
process.env.ONETUBE_DENO_ARGS ?? process.env["1TUBE_DENO_ARGS"] ?? "",
|
|
36
|
+
),
|
|
37
|
+
...cliDenoArgs,
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// Default to --help only when the user gave us nothing at all; if they
|
|
41
|
+
// supplied deno flags but no 1tube subcommand, let cli.ts fall through to
|
|
42
|
+
// its default `serve` path.
|
|
43
|
+
const forwardedTubeArgs = tubeArgs.length === 0 && denoPassthrough.length === 0
|
|
44
|
+
? ["--help"]
|
|
45
|
+
: tubeArgs;
|
|
46
|
+
|
|
47
|
+
// 1tube ships its own deno.json (import map for the gateway's jsr:/npm:
|
|
48
|
+
// dependencies). Only inject it when the project didn't supply its own
|
|
49
|
+
// --config — a Supabase project points --config at
|
|
50
|
+
// supabase/functions/deno.json so its function workers resolve the
|
|
51
|
+
// project's bare specifiers. The gateway's own sources use fully
|
|
52
|
+
// qualified npm:/jsr: specifiers, so they resolve without our config.
|
|
53
|
+
const userSuppliedConfig = denoPassthrough.some((a) =>
|
|
54
|
+
a === "--config" || a === "-c" ||
|
|
55
|
+
a.startsWith("--config=") || a.startsWith("-c=")
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const denoArgs = ["run", "--quiet", "-A"];
|
|
59
|
+
if (!userSuppliedConfig) denoArgs.push("--config", denoConfig);
|
|
60
|
+
denoArgs.push(...denoPassthrough, cliEntrypoint, ...forwardedTubeArgs);
|
|
61
|
+
|
|
62
|
+
// Deno 2.9 enables a 24h "minimum dependency age" by default, which makes
|
|
63
|
+
// a function that pins a freshly published npm version fail to resolve in
|
|
64
|
+
// local dev ("…newer than the specified minimum dependency date"). This
|
|
65
|
+
// setting cannot be changed from inside the process (Deno snapshots npm
|
|
66
|
+
// config at startup), so the launcher is the place to neutralize it.
|
|
67
|
+
//
|
|
68
|
+
// NPM_CONFIG_MIN_RELEASE_AGE is the LOWEST explicit tier in Deno's
|
|
69
|
+
// precedence chain (CLI flag > deno.json > .npmrc > this env var > the
|
|
70
|
+
// 24h built-in default). Setting it to 0 cancels ONLY the built-in
|
|
71
|
+
// default — any .npmrc `min-release-age`, deno.json `minimumDependencyAge`,
|
|
72
|
+
// or `--minimum-dependency-age` the project sets still wins. We never
|
|
73
|
+
// override a value the user already exported.
|
|
74
|
+
const childEnv = { ...process.env };
|
|
75
|
+
if (
|
|
76
|
+
childEnv.NPM_CONFIG_MIN_RELEASE_AGE === undefined &&
|
|
77
|
+
childEnv.npm_config_min_release_age === undefined
|
|
78
|
+
) {
|
|
79
|
+
childEnv.NPM_CONFIG_MIN_RELEASE_AGE = "0";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Resolve the real deno binary up front. Node ≥18.20 refuses to spawn a
|
|
83
|
+
// `.cmd`/`.bat` shim without a shell, but spawning a real `.exe` (or a
|
|
84
|
+
// bare binary on POSIX) with `shell:false` lets Node escape arguments
|
|
85
|
+
// properly — important now that we forward arbitrary deno flags, some of
|
|
86
|
+
// which carry paths with spaces (`--config "C:\My Project\deno.json"`).
|
|
87
|
+
// We only fall back to a shell when we must (a `.cmd`/`.bat` shim, or we
|
|
88
|
+
// couldn't locate the binary on a Windows PATH at all).
|
|
89
|
+
const resolvedDeno = resolveExecutable(denoBin);
|
|
90
|
+
const needsShell = resolvedDeno
|
|
91
|
+
? /\.(cmd|bat)$/i.test(resolvedDeno)
|
|
92
|
+
: process.platform === "win32";
|
|
93
|
+
const spawnBin = resolvedDeno ?? denoBin;
|
|
94
|
+
|
|
95
|
+
let child;
|
|
96
|
+
if (needsShell) {
|
|
97
|
+
// A `.cmd`/`.bat` shim (the npm-installed deno) — or an unresolved name
|
|
98
|
+
// on Windows — must go through a shell. We pass a single, manually
|
|
99
|
+
// quoted command string instead of an args array so that (a) paths
|
|
100
|
+
// with spaces survive and (b) we sidestep Node's DEP0190 "args are not
|
|
101
|
+
// escaped" deprecation, which fires only for the shell-plus-array form.
|
|
102
|
+
const command = [spawnBin, ...denoArgs].map(quoteForCmd).join(" ");
|
|
103
|
+
child = spawn(command, {
|
|
27
104
|
stdio: "inherit",
|
|
28
|
-
shell:
|
|
105
|
+
shell: true,
|
|
29
106
|
windowsHide: false,
|
|
30
|
-
|
|
31
|
-
);
|
|
107
|
+
env: childEnv,
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
// Real executable (deno.exe / a POSIX binary): no shell, so Node
|
|
111
|
+
// escapes each argument for us.
|
|
112
|
+
child = spawn(spawnBin, denoArgs, {
|
|
113
|
+
stdio: "inherit",
|
|
114
|
+
shell: false,
|
|
115
|
+
windowsHide: false,
|
|
116
|
+
env: childEnv,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
32
119
|
|
|
33
120
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
34
121
|
process.on(signal, () => {
|
|
@@ -54,3 +141,62 @@ child.on("exit", (code, signal) => {
|
|
|
54
141
|
}
|
|
55
142
|
process.exit(code ?? 1);
|
|
56
143
|
});
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Split a flag string into argv tokens, honoring single/double quotes so
|
|
147
|
+
* values with spaces (`--env-file="my env.env"`) survive. Good enough for
|
|
148
|
+
* a dev-time convenience var — not a full shell parser.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} s
|
|
151
|
+
* @returns {string[]}
|
|
152
|
+
*/
|
|
153
|
+
function tokenizeArgs(s) {
|
|
154
|
+
const out = [];
|
|
155
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
156
|
+
let m;
|
|
157
|
+
while ((m = re.exec(s)) !== null) {
|
|
158
|
+
out.push(m[1] ?? m[2] ?? m[3]);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Locate an executable on PATH (honoring PATHEXT on Windows). Returns an
|
|
165
|
+
* absolute path, or null when the name can't be resolved — in which case
|
|
166
|
+
* the caller hands the bare name to spawn and lets the OS/shell try.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} bin
|
|
169
|
+
* @returns {string | null}
|
|
170
|
+
*/
|
|
171
|
+
function resolveExecutable(bin) {
|
|
172
|
+
// An explicit path (absolute or containing a separator) is used as-is.
|
|
173
|
+
if (isAbsolute(bin) || bin.includes("/") || bin.includes("\\")) {
|
|
174
|
+
return existsSync(bin) ? bin : null;
|
|
175
|
+
}
|
|
176
|
+
const isWin = process.platform === "win32";
|
|
177
|
+
const exts = isWin
|
|
178
|
+
? (process.env.PATHEXT?.split(";").map((e) => e.trim()).filter(Boolean) ??
|
|
179
|
+
[".EXE", ".CMD", ".BAT"])
|
|
180
|
+
: [""];
|
|
181
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
182
|
+
for (const ext of exts) {
|
|
183
|
+
const candidate = join(dir, bin + ext);
|
|
184
|
+
if (existsSync(candidate)) return candidate;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Quote a single argument for cmd.exe. Leaves simple tokens untouched to
|
|
192
|
+
* avoid over-quoting; wraps anything with whitespace or shell
|
|
193
|
+
* metacharacters in double quotes, doubling any embedded quotes.
|
|
194
|
+
*
|
|
195
|
+
* @param {string} arg
|
|
196
|
+
* @returns {string}
|
|
197
|
+
*/
|
|
198
|
+
function quoteForCmd(arg) {
|
|
199
|
+
if (arg === "") return '""';
|
|
200
|
+
if (!/[\s"&|<>^()%!]/.test(arg)) return arg;
|
|
201
|
+
return '"' + arg.replace(/"/g, '""') + '"';
|
|
202
|
+
}
|
|
@@ -59,7 +59,11 @@ export interface DenoSharedRuntime {
|
|
|
59
59
|
* Invoke a function exported by a shared module. Throws if the
|
|
60
60
|
* module or export is unknown, or if the export itself throws.
|
|
61
61
|
*/
|
|
62
|
-
call(
|
|
62
|
+
call(
|
|
63
|
+
moduleId: string,
|
|
64
|
+
exportName: string,
|
|
65
|
+
args: readonly unknown[],
|
|
66
|
+
): Promise<unknown>;
|
|
63
67
|
/**
|
|
64
68
|
* Re-import a shared module after its source file changed. Returns
|
|
65
69
|
* the new record. If the export list changed, callers should
|
|
@@ -62,10 +62,16 @@ interface SharedCallResultMessage {
|
|
|
62
62
|
|
|
63
63
|
type HostMessage = InitMessage | DispatchMessage | SharedCallResultMessage;
|
|
64
64
|
|
|
65
|
-
type Handler = (
|
|
65
|
+
type Handler = (
|
|
66
|
+
req: Request,
|
|
67
|
+
auth?: AuthContext,
|
|
68
|
+
) => Response | Promise<Response>;
|
|
66
69
|
|
|
67
70
|
interface RegistryStub {
|
|
68
|
-
register(
|
|
71
|
+
register(
|
|
72
|
+
handler: Handler,
|
|
73
|
+
opts: { public: boolean; timeoutMs?: number },
|
|
74
|
+
): void;
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
interface CapturedHandler {
|
|
@@ -93,13 +99,23 @@ let initDone = false;
|
|
|
93
99
|
const invocationContext = new AsyncLocalStorage<{ invocationId?: string }>();
|
|
94
100
|
|
|
95
101
|
type ConsoleLevel = "debug" | "log" | "info" | "warn" | "error";
|
|
96
|
-
const CONSOLE_LEVELS: ConsoleLevel[] = [
|
|
102
|
+
const CONSOLE_LEVELS: ConsoleLevel[] = [
|
|
103
|
+
"debug",
|
|
104
|
+
"log",
|
|
105
|
+
"info",
|
|
106
|
+
"warn",
|
|
107
|
+
"error",
|
|
108
|
+
];
|
|
97
109
|
const MAX_CONSOLE_MESSAGE = 8 * 1024;
|
|
98
110
|
|
|
99
111
|
function formatConsoleArg(arg: unknown): string {
|
|
100
112
|
if (typeof arg === "string") return arg;
|
|
101
113
|
try {
|
|
102
|
-
return Deno.inspect(arg, {
|
|
114
|
+
return Deno.inspect(arg, {
|
|
115
|
+
depth: 4,
|
|
116
|
+
colors: false,
|
|
117
|
+
strAbbreviateSize: 4096,
|
|
118
|
+
});
|
|
103
119
|
} catch {
|
|
104
120
|
try {
|
|
105
121
|
return String(arg);
|
|
@@ -178,7 +194,9 @@ const stub: RegistryStub = {
|
|
|
178
194
|
// The function called serve() twice. Last write wins, matching the
|
|
179
195
|
// in-process registry's behaviour. Surface a warning so authors notice
|
|
180
196
|
// the bug.
|
|
181
|
-
console.warn(
|
|
197
|
+
console.warn(
|
|
198
|
+
`[1tube] "${functionName}" called serve() twice; using the last handler`,
|
|
199
|
+
);
|
|
182
200
|
}
|
|
183
201
|
captured = {
|
|
184
202
|
handler,
|
|
@@ -188,8 +206,8 @@ const stub: RegistryStub = {
|
|
|
188
206
|
},
|
|
189
207
|
};
|
|
190
208
|
|
|
191
|
-
(globalThis as { __edgeFunctionRegistry?: RegistryStub })
|
|
192
|
-
stub;
|
|
209
|
+
(globalThis as { __edgeFunctionRegistry?: RegistryStub })
|
|
210
|
+
.__edgeFunctionRegistry = stub;
|
|
193
211
|
|
|
194
212
|
self.addEventListener("unhandledrejection", (e) => {
|
|
195
213
|
e.preventDefault();
|
|
@@ -221,7 +239,8 @@ async function runDispatch(msg: DispatchMessage): Promise<void> {
|
|
|
221
239
|
postMessage({
|
|
222
240
|
type: "response_error",
|
|
223
241
|
id: msg.id,
|
|
224
|
-
message:
|
|
242
|
+
message:
|
|
243
|
+
`Function "${functionName}" did not call serve() before dispatch`,
|
|
225
244
|
});
|
|
226
245
|
return;
|
|
227
246
|
}
|