zapdev 1.3.0 → 1.5.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 +26 -8
- package/dist/cli.js +151 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/mvagnon/zapdev/main/.github/assets/preview.gif" alt="zapdev CLI demonstration">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
[](https://mvagnon.dev)
|
|
8
|
+
[](https://buymeacoffee.com/mvagnon)
|
|
9
|
+
|
|
1
10
|
# zapdev
|
|
2
11
|
|
|
3
12
|
## Project Introduction
|
|
@@ -28,13 +37,14 @@ flowchart LR
|
|
|
28
37
|
|
|
29
38
|
## Environment Variables
|
|
30
39
|
|
|
31
|
-
| Variable
|
|
32
|
-
|
|
|
33
|
-
| `ZD_URL`
|
|
34
|
-
| `ZD_MODEL`
|
|
35
|
-
| `ZD_EFFORT`
|
|
40
|
+
| Variable | Required | Description |
|
|
41
|
+
| ----------------- | ------------ | --------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `ZD_URL` | For `commit` | Complete HTTP(S) Chat Completions endpoint, including its path |
|
|
43
|
+
| `ZD_MODEL` | For `commit` | Model identifier supported by the endpoint |
|
|
44
|
+
| `ZD_EFFORT` | For `commit` | Sent as `reasoning_effort`; use a value supported by your model, such as `low`, `medium`, or `high` |
|
|
45
|
+
| `ZD_HOOK_TIMEOUT` | No | Deadline per Git hook in seconds (default: `60`); positive numbers from `0.001` to `2147483.647` |
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
The LLM settings have no defaults, automatic provider detection, or backup models. CLI flags override these settings. Legacy `OLLAMA_*` variables are no longer read.
|
|
38
48
|
|
|
39
49
|
Requests use the OpenAI Chat Completions format over plain `fetch`, without a provider SDK. No authentication headers are sent; use an endpoint that does not require them. The endpoint and model must support `reasoning_effort`.
|
|
40
50
|
|
|
@@ -55,6 +65,12 @@ Install zapdev globally for daily use:
|
|
|
55
65
|
npm install -g zapdev
|
|
56
66
|
```
|
|
57
67
|
|
|
68
|
+
or
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
bun install -g zapdev
|
|
72
|
+
```
|
|
73
|
+
|
|
58
74
|
Configure your endpoint and model before running `commit` (replace these example values):
|
|
59
75
|
|
|
60
76
|
```bash
|
|
@@ -106,8 +122,8 @@ zapdev commit
|
|
|
106
122
|
| Flag | Description |
|
|
107
123
|
| ------------------- | ----------------------------------------------------------------- |
|
|
108
124
|
| `--url <url>` | Override the complete Chat Completions endpoint |
|
|
109
|
-
| `--model <model>` | Override the model
|
|
110
|
-
| `--effort <effort>` | Override the reasoning effort
|
|
125
|
+
| `--model <model>` | Override the model |
|
|
126
|
+
| `--effort <effort>` | Override the reasoning effort |
|
|
111
127
|
| `-t, --type <type>` | Force the Conventional Commit type (`feat`, `fix`, `chore`, etc.) |
|
|
112
128
|
| `-p, --push` | Push after committing without asking |
|
|
113
129
|
| `-s, --staged` | Commit only changes that are already staged |
|
|
@@ -124,6 +140,8 @@ Before contacting the LLM endpoint, zapdev runs `gitleaks git --staged --verbose
|
|
|
124
140
|
|
|
125
141
|
Failures are reported per repository while the others continue. Any failure produces a nonzero exit code.
|
|
126
142
|
|
|
143
|
+
In a terminal, Git and its hooks display live logs and support native prompts, even with `--yes` (which only skips zapdev prompts). Git controls hook stdin; interactive hooks should read from the terminal, for example `/dev/tty`. Each hook has a 60-second deadline, including time spent answering prompts; override it with `ZD_HOOK_TIMEOUT=120 zapdev commit`.
|
|
144
|
+
|
|
127
145
|
Pushing is optimistic, with no preliminary fetch. If the branch is behind upstream, `--rebase` runs `git pull --rebase`, while `--merge` runs `git pull --no-rebase --no-edit`; zapdev then retries once. Without either flag, interactive runs ask whether to rebase, merge, or quit. Runs using `--yes` or without a TTY must provide one of the flags.
|
|
128
146
|
|
|
129
147
|
Without a TTY, zapdev commits all prepared repositories automatically and only pushes when `--push` is set.
|
package/dist/cli.js
CHANGED
|
@@ -554,7 +554,7 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
|
|
|
554
554
|
// package.json
|
|
555
555
|
var package_default = {
|
|
556
556
|
name: "zapdev",
|
|
557
|
-
version: "1.
|
|
557
|
+
version: "1.5.0",
|
|
558
558
|
description: "Fast, performant and lightweight git chores and dev tools using your terminal and an OpenAI-compatible LLM endpoint.",
|
|
559
559
|
type: "module",
|
|
560
560
|
bin: {
|
|
@@ -1815,6 +1815,13 @@ function sanitizeCommitMessage(raw) {
|
|
|
1815
1815
|
}
|
|
1816
1816
|
|
|
1817
1817
|
// src/lib/config.ts
|
|
1818
|
+
function resolveHookTimeout(env = process.env) {
|
|
1819
|
+
const timeout = Number(env.ZD_HOOK_TIMEOUT ?? 60) * 1e3;
|
|
1820
|
+
if (!Number.isFinite(timeout) || timeout < 1 || timeout > 2147483647) {
|
|
1821
|
+
throw new Error("ZD_HOOK_TIMEOUT must be a number of seconds between 0.001 and 2147483.647.");
|
|
1822
|
+
}
|
|
1823
|
+
return timeout;
|
|
1824
|
+
}
|
|
1818
1825
|
function resolveConfig(env = process.env, overrides = {}) {
|
|
1819
1826
|
const config = {
|
|
1820
1827
|
url: (overrides.url ?? env.ZD_URL)?.trim() ?? "",
|
|
@@ -1837,8 +1844,11 @@ function resolveConfig(env = process.env, overrides = {}) {
|
|
|
1837
1844
|
}
|
|
1838
1845
|
|
|
1839
1846
|
// src/lib/git.ts
|
|
1847
|
+
import { execFileSync } from "node:child_process";
|
|
1840
1848
|
import { readdir } from "node:fs/promises";
|
|
1841
1849
|
import { join } from "node:path";
|
|
1850
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
1851
|
+
import { Readable } from "node:stream";
|
|
1842
1852
|
|
|
1843
1853
|
// node_modules/tinyexec/dist/main.mjs
|
|
1844
1854
|
import { spawn as e, spawnSync as t2 } from "node:child_process";
|
|
@@ -2166,14 +2176,105 @@ var R2 = (e2, t3, n4) => {
|
|
|
2166
2176
|
};
|
|
2167
2177
|
var z = R2;
|
|
2168
2178
|
|
|
2179
|
+
// src/lib/git-hooks.ts
|
|
2180
|
+
function createHookReporter(onHook, onTimeout, timeout) {
|
|
2181
|
+
const hooks = /* @__PURE__ */ new Map();
|
|
2182
|
+
const report = (line) => {
|
|
2183
|
+
let event;
|
|
2184
|
+
try {
|
|
2185
|
+
const value = JSON.parse(line);
|
|
2186
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
2187
|
+
event = value;
|
|
2188
|
+
} catch {
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
if (typeof event.sid !== "string" || typeof event.child_id !== "number") return;
|
|
2192
|
+
const key = `${event.sid}:${event.child_id}`;
|
|
2193
|
+
if (event.event === "child_start" && event.child_class === "hook" && typeof event.hook_name === "string") {
|
|
2194
|
+
const name = event.hook_name;
|
|
2195
|
+
hooks.set(key, { name, timer: setTimeout(() => onTimeout(name), timeout) });
|
|
2196
|
+
onHook?.({ name, phase: "start" });
|
|
2197
|
+
} else if (event.event === "child_exit" && typeof event.code === "number") {
|
|
2198
|
+
const hook = hooks.get(key);
|
|
2199
|
+
if (!hook) return;
|
|
2200
|
+
clearTimeout(hook.timer);
|
|
2201
|
+
hooks.delete(key);
|
|
2202
|
+
onHook?.({ name: hook.name, phase: "exit", exitCode: event.code });
|
|
2203
|
+
}
|
|
2204
|
+
};
|
|
2205
|
+
return {
|
|
2206
|
+
report,
|
|
2207
|
+
close() {
|
|
2208
|
+
for (const { timer } of hooks.values()) clearTimeout(timer);
|
|
2209
|
+
hooks.clear();
|
|
2210
|
+
}
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2169
2214
|
// src/lib/git.ts
|
|
2170
2215
|
var UPSTREAM_REF = "@{upstream}";
|
|
2171
|
-
async function git(args, cwd) {
|
|
2172
|
-
const
|
|
2173
|
-
|
|
2174
|
-
|
|
2216
|
+
async function git(args, cwd, onHook) {
|
|
2217
|
+
const timeout = resolveHookTimeout();
|
|
2218
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && ["commit", "push", "pull", "fetch"].includes(args[0] ?? ""));
|
|
2219
|
+
const terminalState = interactive && process.platform !== "win32" ? execFileSync("stty", ["-g"], { stdio: ["inherit", "pipe", "ignore"], encoding: "utf8" }).trim() : void 0;
|
|
2220
|
+
const child = R2("git", args, {
|
|
2221
|
+
nodeOptions: {
|
|
2222
|
+
cwd,
|
|
2223
|
+
detached: !interactive && process.platform !== "win32",
|
|
2224
|
+
env: { ...process.env, GIT_TRACE2_EVENT: "3" },
|
|
2225
|
+
stdio: interactive ? ["inherit", "inherit", "inherit", "pipe"] : ["pipe", "pipe", "pipe", "pipe"]
|
|
2226
|
+
}
|
|
2227
|
+
});
|
|
2228
|
+
let timeoutError;
|
|
2229
|
+
const { report, close } = createHookReporter(onHook, (name) => {
|
|
2230
|
+
if (timeoutError || child.pid === void 0) return;
|
|
2231
|
+
timeoutError = new Error(`${name} hook timed out after ${timeout / 1e3} seconds`);
|
|
2232
|
+
try {
|
|
2233
|
+
if (process.platform === "win32") {
|
|
2234
|
+
execFileSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
|
|
2235
|
+
} else if (interactive) {
|
|
2236
|
+
killProcessTree(child.pid);
|
|
2237
|
+
} else {
|
|
2238
|
+
process.kill(-child.pid, "SIGKILL");
|
|
2239
|
+
}
|
|
2240
|
+
} catch {
|
|
2241
|
+
child.kill("SIGKILL");
|
|
2242
|
+
}
|
|
2243
|
+
}, timeout);
|
|
2244
|
+
const trace = child.process?.stdio[3];
|
|
2245
|
+
const reader = trace instanceof Readable ? createInterface2({ input: trace }).on("line", report) : void 0;
|
|
2246
|
+
try {
|
|
2247
|
+
const result = await child;
|
|
2248
|
+
if (timeoutError) throw timeoutError;
|
|
2249
|
+
if (result.exitCode !== 0) {
|
|
2250
|
+
throw new Error(result.stderr.trim() || `git ${args.join(" ")} failed`);
|
|
2251
|
+
}
|
|
2252
|
+
return result.stdout;
|
|
2253
|
+
} finally {
|
|
2254
|
+
reader?.close();
|
|
2255
|
+
close();
|
|
2256
|
+
if (terminalState) execFileSync("stty", [terminalState], { stdio: ["inherit", "ignore", "ignore"] });
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
function killProcessTree(pid) {
|
|
2260
|
+
const children = /* @__PURE__ */ new Map();
|
|
2261
|
+
const output = execFileSync("ps", ["-A", "-o", "pid=,ppid="], { encoding: "utf8" });
|
|
2262
|
+
for (const line of output.trim().split("\n")) {
|
|
2263
|
+
const [child, parent] = line.trim().split(/\s+/).map(Number);
|
|
2264
|
+
if (child === void 0 || parent === void 0) continue;
|
|
2265
|
+
const siblings = children.get(parent) ?? [];
|
|
2266
|
+
siblings.push(child);
|
|
2267
|
+
children.set(parent, siblings);
|
|
2268
|
+
}
|
|
2269
|
+
const pids = [pid];
|
|
2270
|
+
for (const parent of pids) pids.push(...children.get(parent) ?? []);
|
|
2271
|
+
for (const child of pids.reverse()) {
|
|
2272
|
+
try {
|
|
2273
|
+
process.kill(child, "SIGKILL");
|
|
2274
|
+
} catch (error) {
|
|
2275
|
+
if (error.code !== "ESRCH") throw error;
|
|
2276
|
+
}
|
|
2175
2277
|
}
|
|
2176
|
-
return result.stdout;
|
|
2177
2278
|
}
|
|
2178
2279
|
async function tryGit(args, cwd) {
|
|
2179
2280
|
const result = await R2("git", args, { nodeOptions: { cwd } });
|
|
@@ -2185,8 +2286,8 @@ async function stageAll(repo) {
|
|
|
2185
2286
|
async function getStagedDiff(repo) {
|
|
2186
2287
|
return git(["diff", "--cached"], repo);
|
|
2187
2288
|
}
|
|
2188
|
-
async function commit(repo, message) {
|
|
2189
|
-
await git(["commit", "-m", message], repo);
|
|
2289
|
+
async function commit(repo, message, onHook) {
|
|
2290
|
+
await git(["commit", "-m", message], repo, onHook);
|
|
2190
2291
|
}
|
|
2191
2292
|
async function currentBranch(repo) {
|
|
2192
2293
|
return (await git(["symbolic-ref", "--short", "HEAD"], repo)).trim();
|
|
@@ -2203,20 +2304,20 @@ async function hasUpstream(repo) {
|
|
|
2203
2304
|
const result = await tryGit(["rev-parse", "--abbrev-ref", "--symbolic-full-name", UPSTREAM_REF], repo);
|
|
2204
2305
|
return result !== null;
|
|
2205
2306
|
}
|
|
2206
|
-
async function push(repo) {
|
|
2207
|
-
await git(["push"], repo);
|
|
2307
|
+
async function push(repo, onHook) {
|
|
2308
|
+
await git(["push"], repo, onHook);
|
|
2208
2309
|
}
|
|
2209
|
-
async function pushSetUpstream(repo, branch) {
|
|
2210
|
-
await git(["push", "-u", "origin", branch], repo);
|
|
2310
|
+
async function pushSetUpstream(repo, branch, onHook) {
|
|
2311
|
+
await git(["push", "-u", "origin", branch], repo, onHook);
|
|
2211
2312
|
}
|
|
2212
|
-
async function pullRebase(repo) {
|
|
2213
|
-
await git(["pull", "--rebase"], repo);
|
|
2313
|
+
async function pullRebase(repo, onHook) {
|
|
2314
|
+
await git(["pull", "--rebase"], repo, onHook);
|
|
2214
2315
|
}
|
|
2215
|
-
async function pullMerge(repo) {
|
|
2216
|
-
await git(["pull", "--no-rebase", "--no-edit"], repo);
|
|
2316
|
+
async function pullMerge(repo, onHook) {
|
|
2317
|
+
await git(["pull", "--no-rebase", "--no-edit"], repo, onHook);
|
|
2217
2318
|
}
|
|
2218
|
-
async function fetchRemote(repo) {
|
|
2219
|
-
await git(["fetch"], repo);
|
|
2319
|
+
async function fetchRemote(repo, onHook) {
|
|
2320
|
+
await git(["fetch"], repo, onHook);
|
|
2220
2321
|
}
|
|
2221
2322
|
async function behindCount(repo) {
|
|
2222
2323
|
const out = await tryGit(["rev-list", "--count", `HEAD..${UPSTREAM_REF}`], repo);
|
|
@@ -2403,6 +2504,7 @@ var commitCommand = defineCommand({
|
|
|
2403
2504
|
}
|
|
2404
2505
|
let config;
|
|
2405
2506
|
try {
|
|
2507
|
+
resolveHookTimeout();
|
|
2406
2508
|
config = resolveConfig(process.env, {
|
|
2407
2509
|
url: args.url,
|
|
2408
2510
|
model: args.model,
|
|
@@ -2468,8 +2570,9 @@ var commitCommand = defineCommand({
|
|
|
2468
2570
|
}
|
|
2469
2571
|
const committed = [];
|
|
2470
2572
|
for (const draft of selected) {
|
|
2573
|
+
log.info(`${draft.label}: committing`);
|
|
2471
2574
|
try {
|
|
2472
|
-
await commit(draft.repo, draft.message);
|
|
2575
|
+
await commit(draft.repo, draft.message, reportHooks(draft.label));
|
|
2473
2576
|
committed.push(draft);
|
|
2474
2577
|
log.success(`${draft.label}: committed ${draft.message}`);
|
|
2475
2578
|
} catch (error) {
|
|
@@ -2495,7 +2598,6 @@ var commitCommand = defineCommand({
|
|
|
2495
2598
|
try {
|
|
2496
2599
|
const pushed = await pushOptimistic(
|
|
2497
2600
|
repository,
|
|
2498
|
-
interactive,
|
|
2499
2601
|
interactive && !args.yes,
|
|
2500
2602
|
syncStrategy
|
|
2501
2603
|
);
|
|
@@ -2509,6 +2611,17 @@ var commitCommand = defineCommand({
|
|
|
2509
2611
|
if (interactive) outro(process.exitCode ? "Finished with errors." : "Done.");
|
|
2510
2612
|
}
|
|
2511
2613
|
});
|
|
2614
|
+
function reportHooks(label) {
|
|
2615
|
+
return (event) => {
|
|
2616
|
+
const hook = `${label}: ${event.name}`;
|
|
2617
|
+
if (event.phase === "start") {
|
|
2618
|
+
log.info(`${hook} running`);
|
|
2619
|
+
} else {
|
|
2620
|
+
if (event.exitCode === 0) log.success(`${hook} \u2713`);
|
|
2621
|
+
else log.error(`${hook} \u2717 (exit ${event.exitCode})`);
|
|
2622
|
+
}
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2512
2625
|
async function reviewMessages(drafts, canPrompt) {
|
|
2513
2626
|
while (true) {
|
|
2514
2627
|
for (const draft of drafts) log.message(`${draft.pendingLabel}: ${draft.message}`);
|
|
@@ -2541,30 +2654,28 @@ async function reviewMessages(drafts, canPrompt) {
|
|
|
2541
2654
|
action.draft.message = edited.trim();
|
|
2542
2655
|
}
|
|
2543
2656
|
}
|
|
2544
|
-
async function syncWithUpstream({ repo, label }, strategy
|
|
2545
|
-
const loader = interactive ? spinner() : void 0;
|
|
2657
|
+
async function syncWithUpstream({ repo, label }, strategy) {
|
|
2546
2658
|
const actionLabel = strategy === "rebase" ? "Rebase" : "Merge";
|
|
2547
|
-
|
|
2659
|
+
log.info(`${label}: pulling --${strategy === "rebase" ? "rebase" : "no-rebase"}`);
|
|
2548
2660
|
try {
|
|
2549
|
-
await (strategy === "rebase" ? pullRebase
|
|
2550
|
-
|
|
2661
|
+
await (strategy === "rebase" ? pullRebase : pullMerge)(repo, reportHooks(label));
|
|
2662
|
+
log.success(`${label}: ${strategy === "rebase" ? "rebased on upstream" : "merged upstream"}`);
|
|
2551
2663
|
return true;
|
|
2552
2664
|
} catch (error) {
|
|
2553
|
-
loader?.error(`${actionLabel} failed`);
|
|
2554
2665
|
log.error(`${label}: ${actionLabel} failed (resolve conflicts, then push): ${errorMessage(error)}`);
|
|
2555
2666
|
return false;
|
|
2556
2667
|
}
|
|
2557
2668
|
}
|
|
2558
|
-
async function pushOptimistic(repository,
|
|
2669
|
+
async function pushOptimistic(repository, canPrompt, strategy) {
|
|
2559
2670
|
const { repo, label } = repository;
|
|
2560
2671
|
const [upstream, branch] = await Promise.all([hasUpstream(repo), currentBranch(repo)]);
|
|
2561
|
-
const doPush = () => upstream ? push(repo) : pushSetUpstream(repo, branch);
|
|
2562
|
-
const first = await tryPush(repository,
|
|
2672
|
+
const doPush = (onHook) => upstream ? push(repo, onHook) : pushSetUpstream(repo, branch, onHook);
|
|
2673
|
+
const first = await tryPush(repository, doPush);
|
|
2563
2674
|
if (first.ok) return true;
|
|
2564
|
-
if (upstream && await isBehind(repository
|
|
2675
|
+
if (upstream && await isBehind(repository)) {
|
|
2565
2676
|
const syncStrategy = strategy ?? await chooseSyncStrategy(repository, canPrompt);
|
|
2566
|
-
if (!syncStrategy || !await syncWithUpstream(repository, syncStrategy
|
|
2567
|
-
const retry = await tryPush(repository,
|
|
2677
|
+
if (!syncStrategy || !await syncWithUpstream(repository, syncStrategy)) return false;
|
|
2678
|
+
const retry = await tryPush(repository, doPush);
|
|
2568
2679
|
if (retry.ok) return true;
|
|
2569
2680
|
log.error(`${label}: push failed: ${errorMessage(retry.error)}`);
|
|
2570
2681
|
return false;
|
|
@@ -2591,30 +2702,24 @@ async function chooseSyncStrategy({ label }, interactive) {
|
|
|
2591
2702
|
}
|
|
2592
2703
|
return action;
|
|
2593
2704
|
}
|
|
2594
|
-
async function tryPush({ label },
|
|
2595
|
-
|
|
2596
|
-
loader?.start(`${label}: pushing`);
|
|
2705
|
+
async function tryPush({ label }, doPush) {
|
|
2706
|
+
log.info(`${label}: pushing`);
|
|
2597
2707
|
try {
|
|
2598
|
-
await doPush();
|
|
2599
|
-
|
|
2708
|
+
await doPush(reportHooks(label));
|
|
2709
|
+
log.success(`${label}: pushed`);
|
|
2600
2710
|
return { ok: true };
|
|
2601
2711
|
} catch (error) {
|
|
2602
|
-
loader?.error("Push failed");
|
|
2603
2712
|
return { ok: false, error };
|
|
2604
2713
|
}
|
|
2605
2714
|
}
|
|
2606
|
-
async function isBehind({ repo, label }
|
|
2607
|
-
|
|
2608
|
-
loader?.start(`${label}: checking upstream`);
|
|
2715
|
+
async function isBehind({ repo, label }) {
|
|
2716
|
+
log.info(`${label}: checking upstream`);
|
|
2609
2717
|
try {
|
|
2610
|
-
await fetchRemote(repo);
|
|
2718
|
+
await fetchRemote(repo, reportHooks(label));
|
|
2611
2719
|
const behind = await behindCount(repo);
|
|
2612
|
-
|
|
2613
|
-
behind > 0 ? `Behind upstream by ${behind} commit${behind > 1 ? "s" : ""}` : "Up to date with upstream"
|
|
2614
|
-
);
|
|
2720
|
+
log.info(`${label}: ${behind > 0 ? `Behind upstream by ${behind} commit${behind > 1 ? "s" : ""}` : "Up to date with upstream"}`);
|
|
2615
2721
|
return behind > 0;
|
|
2616
2722
|
} catch (error) {
|
|
2617
|
-
loader?.error("Could not check upstream");
|
|
2618
2723
|
log.warn(`${label}: could not check upstream: ${errorMessage(error)}`);
|
|
2619
2724
|
return false;
|
|
2620
2725
|
}
|