@kybernesis/create 0.15.7 → 0.15.8
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/doctor.js +17 -0
- package/dist/env-urls.d.ts +17 -0
- package/dist/env-urls.js +79 -0
- package/package.json +3 -2
package/dist/doctor.js
CHANGED
|
@@ -9,6 +9,7 @@ import { classifyModelReach } from "./model-reach.js";
|
|
|
9
9
|
import { countAbandonedRuns } from "./run-store.js";
|
|
10
10
|
import { describeStaleSidecar, newestPackageChange, staleSidecars } from "./stale-sidecar.js";
|
|
11
11
|
import { checkSelfVersion } from "./self-version.js";
|
|
12
|
+
import { envUrlProblems } from "./env-urls.js";
|
|
12
13
|
const MARK = {
|
|
13
14
|
pass: green("✓"),
|
|
14
15
|
warn: yellow("!"),
|
|
@@ -215,6 +216,22 @@ export async function doctor() {
|
|
|
215
216
|
};
|
|
216
217
|
if (!existsSync(envPath))
|
|
217
218
|
add("warn", ".env.local missing", "copy .env.example and fill it");
|
|
219
|
+
/**
|
|
220
|
+
* Config that is shaped wrong rather than missing.
|
|
221
|
+
*
|
|
222
|
+
* A URL-valued setting with no scheme does not fail at start; it fails inside
|
|
223
|
+
* whatever background drain uses it, once per turn, forever. The reference
|
|
224
|
+
* fleet logged 121 of these and lost every analytics event of a deployment
|
|
225
|
+
* while reporting healthy throughout. The value alone is enough to know, so
|
|
226
|
+
* this costs nothing and lands before a client's first turn.
|
|
227
|
+
*/
|
|
228
|
+
const urlProblems = existsSync(envPath) ? envUrlProblems(readFileSync(envPath, "utf8")) : [];
|
|
229
|
+
for (const problem of urlProblems) {
|
|
230
|
+
add("fail", `${problem.name} cannot build a request (${problem.reason})`, problem.fix);
|
|
231
|
+
}
|
|
232
|
+
if (existsSync(envPath) && urlProblems.length === 0) {
|
|
233
|
+
add("pass", "every URL-valued setting is an absolute http(s) URL");
|
|
234
|
+
}
|
|
218
235
|
const githubCheck = githubToolsDoctorCheck(existsSync(join(cwd, "agent/extensions/github.ts")), env);
|
|
219
236
|
if (githubCheck)
|
|
220
237
|
add(githubCheck.verdict, githubCheck.label, githubCheck.detail);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Does this name hold a whole URL? */
|
|
2
|
+
export declare function expectsUrl(name: string): boolean;
|
|
3
|
+
export type EnvUrlProblem = {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
/** Why it cannot be used to build a request. Never includes the value. */
|
|
6
|
+
readonly reason: "no scheme" | "not a url" | "not http(s)";
|
|
7
|
+
readonly fix: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Check one value. Returns undefined when it is fine.
|
|
11
|
+
*
|
|
12
|
+
* The value never appears in the result: these files sit beside real secrets
|
|
13
|
+
* and a diagnostic that echoes them turns a config warning into a leak.
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkEnvUrl(name: string, value: string): EnvUrlProblem | undefined;
|
|
16
|
+
/** Every URL-shaped value in this env text that cannot build a request. */
|
|
17
|
+
export declare function envUrlProblems(text: string): EnvUrlProblem[];
|
package/dist/env-urls.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { parseEnv } from "./util.js";
|
|
2
|
+
/**
|
|
3
|
+
* Environment values that are used to build a URL, and whether they can be.
|
|
4
|
+
*
|
|
5
|
+
* An SDK handed `POSTHOG_HOST=eu.i.posthog.com` does not complain at start. It
|
|
6
|
+
* fails once per turn, forever, inside a background drain: `Failed to parse URL
|
|
7
|
+
* from eu.i.posthog.com/batch/`. On the reference fleet that was 121 logged
|
|
8
|
+
* failures and every analytics event of the deployment silently discarded,
|
|
9
|
+
* while the agent itself looked perfectly healthy. Nothing surfaces it because
|
|
10
|
+
* nothing that matters to the turn depends on it.
|
|
11
|
+
*
|
|
12
|
+
* Hence checking the SHAPE of the value at rest, which costs nothing and finds
|
|
13
|
+
* it before a client's first turn.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Names whose value is unambiguously a whole URL.
|
|
17
|
+
*
|
|
18
|
+
* `_HOST` is deliberately NOT here: plenty of them legitimately hold a bare
|
|
19
|
+
* hostname (`SMTP_HOST`, `PGHOST`, `REDIS_HOST`), and flagging those would make
|
|
20
|
+
* doctor cry wolf on a correct config.
|
|
21
|
+
*/
|
|
22
|
+
const URL_SUFFIXES = ["_URL", "_ENDPOINT", "_ISSUER"];
|
|
23
|
+
/**
|
|
24
|
+
* `_HOST` names that specific SDKs use as a base URL, so they do need a scheme.
|
|
25
|
+
* An allowlist rather than a rule, because only the SDK knows which it is.
|
|
26
|
+
*/
|
|
27
|
+
const BASE_URL_HOSTS = new Set(["POSTHOG_HOST"]);
|
|
28
|
+
/** Does this name hold a whole URL? */
|
|
29
|
+
export function expectsUrl(name) {
|
|
30
|
+
if (BASE_URL_HOSTS.has(name))
|
|
31
|
+
return true;
|
|
32
|
+
return URL_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check one value. Returns undefined when it is fine.
|
|
36
|
+
*
|
|
37
|
+
* The value never appears in the result: these files sit beside real secrets
|
|
38
|
+
* and a diagnostic that echoes them turns a config warning into a leak.
|
|
39
|
+
*/
|
|
40
|
+
export function checkEnvUrl(name, value) {
|
|
41
|
+
const trimmed = value.trim().replace(/^["']|["']$/g, "");
|
|
42
|
+
if (trimmed === "")
|
|
43
|
+
return undefined;
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = new URL(trimmed);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// A bare host is by far the common case, and the remedy differs from junk.
|
|
50
|
+
const bare = /^[a-z0-9.-]+(:\d+)?(\/.*)?$/i.test(trimmed);
|
|
51
|
+
return {
|
|
52
|
+
name,
|
|
53
|
+
reason: bare ? "no scheme" : "not a url",
|
|
54
|
+
fix: bare
|
|
55
|
+
? `${name} has no scheme, so anything built from it fails to parse. Prefix it with https://`
|
|
56
|
+
: `${name} is not a URL. Set it to an absolute https:// URL.`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
60
|
+
return {
|
|
61
|
+
name,
|
|
62
|
+
reason: "not http(s)",
|
|
63
|
+
fix: `${name} is a ${parsed.protocol.replace(":", "")} URL. Set it to an absolute https:// URL.`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Every URL-shaped value in this env text that cannot build a request. */
|
|
69
|
+
export function envUrlProblems(text) {
|
|
70
|
+
const problems = [];
|
|
71
|
+
for (const [name, value] of Object.entries(parseEnv(text))) {
|
|
72
|
+
if (!expectsUrl(name))
|
|
73
|
+
continue;
|
|
74
|
+
const problem = checkEnvUrl(name, value);
|
|
75
|
+
if (problem)
|
|
76
|
+
problems.push(problem);
|
|
77
|
+
}
|
|
78
|
+
return problems;
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.8",
|
|
4
4
|
"description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent — plus doctor and upgrade.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -46,5 +46,6 @@
|
|
|
46
46
|
},
|
|
47
47
|
"publishConfig": {
|
|
48
48
|
"access": "public"
|
|
49
|
-
}
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {}
|
|
50
51
|
}
|