@jevitate/cli 0.9.0 → 0.10.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/dist/anchor-baselines.d.ts +26 -0
- package/dist/anchor-baselines.js +25 -0
- package/dist/anchor-baselines.js.map +1 -0
- package/dist/approval-provenance.d.ts +32 -2
- package/dist/approval-provenance.js +92 -5
- package/dist/approval-provenance.js.map +1 -1
- package/dist/bin.js +10194 -6042
- package/dist/build-info.generated.js +2 -2
- package/dist/catalog-analysis.js +14 -0
- package/dist/catalog-analysis.js.map +1 -1
- package/dist/catalog-api.js +12 -4
- package/dist/catalog-api.js.map +1 -1
- package/dist/catalog-bundle-api.d.ts +73 -0
- package/dist/catalog-bundle-api.js +206 -0
- package/dist/catalog-bundle-api.js.map +1 -0
- package/dist/catalog-bundle-findings.d.ts +54 -0
- package/dist/catalog-bundle-findings.js +252 -0
- package/dist/catalog-bundle-findings.js.map +1 -0
- package/dist/catalog-bundle.d.ts +192 -0
- package/dist/catalog-bundle.js +578 -0
- package/dist/catalog-bundle.js.map +1 -0
- package/dist/catalog-cli.d.ts +6 -0
- package/dist/catalog-cli.js +180 -5
- package/dist/catalog-cli.js.map +1 -1
- package/dist/catalog-refs.d.ts +42 -0
- package/dist/catalog-refs.js +188 -0
- package/dist/catalog-refs.js.map +1 -0
- package/dist/catalog-review.d.ts +3 -1
- package/dist/catalog-review.js +61 -0
- package/dist/catalog-review.js.map +1 -1
- package/dist/catalog.d.ts +27 -3
- package/dist/catalog.js +46 -2
- package/dist/catalog.js.map +1 -1
- package/dist/check-api.js +162 -9
- package/dist/check-api.js.map +1 -1
- package/dist/check-cli.js +71 -2
- package/dist/check-cli.js.map +1 -1
- package/dist/check-execute.d.ts +12 -0
- package/dist/check-execute.js +55 -3
- package/dist/check-execute.js.map +1 -1
- package/dist/check-types.d.ts +98 -0
- package/dist/check-types.js.map +1 -1
- package/dist/cli-output.d.ts +6 -0
- package/dist/cli-output.js +23 -0
- package/dist/cli-output.js.map +1 -1
- package/dist/cli-shared.d.ts +10 -0
- package/dist/cli-shared.js +0 -4
- package/dist/cli-shared.js.map +1 -1
- package/dist/exit-codes.js +15 -0
- package/dist/exit-codes.js.map +1 -1
- package/dist/forge-verify.d.ts +224 -0
- package/dist/forge-verify.js +649 -0
- package/dist/forge-verify.js.map +1 -0
- package/dist/init-codeowners.d.ts +19 -0
- package/dist/init-codeowners.js +66 -0
- package/dist/init-codeowners.js.map +1 -1
- package/dist/job-draft-api.d.ts +52 -0
- package/dist/job-draft-api.js +110 -0
- package/dist/job-draft-api.js.map +1 -0
- package/dist/journeeze-api.d.ts +71 -0
- package/dist/journeeze-api.js +285 -0
- package/dist/journeeze-api.js.map +1 -0
- package/dist/journeeze-connect.d.ts +156 -0
- package/dist/journeeze-connect.js +357 -0
- package/dist/journeeze-connect.js.map +1 -0
- package/dist/journey-anchor-gate.d.ts +52 -0
- package/dist/journey-anchor-gate.js +101 -0
- package/dist/journey-anchor-gate.js.map +1 -0
- package/dist/journey-api.d.ts +1 -0
- package/dist/journey-api.js +37 -10
- package/dist/journey-api.js.map +1 -1
- package/dist/journey-cli.js +57 -5
- package/dist/journey-cli.js.map +1 -1
- package/dist/journey-heal.js +1 -1
- package/dist/journey-heal.js.map +1 -1
- package/dist/journey-lint-sarif.d.ts +3 -2
- package/dist/journey-lint-sarif.js +9 -2
- package/dist/journey-lint-sarif.js.map +1 -1
- package/dist/journey-migrate-api.d.ts +54 -0
- package/dist/journey-migrate-api.js +64 -0
- package/dist/journey-migrate-api.js.map +1 -0
- package/dist/journey-proposal-store.d.ts +9 -0
- package/dist/journey-proposal-store.js +23 -0
- package/dist/journey-proposal-store.js.map +1 -1
- package/dist/journey-result-record.d.ts +2 -0
- package/dist/journey-result-record.js +5 -0
- package/dist/journey-result-record.js.map +1 -1
- package/dist/journey-review-api.js +9 -1
- package/dist/journey-review-api.js.map +1 -1
- package/dist/journey-review.d.ts +11 -1
- package/dist/journey-review.js +77 -5
- package/dist/journey-review.js.map +1 -1
- package/dist/journey-stale-api.d.ts +40 -0
- package/dist/journey-stale-api.js +57 -0
- package/dist/journey-stale-api.js.map +1 -0
- package/dist/locator-health-api.d.ts +101 -0
- package/dist/locator-health-api.js +203 -0
- package/dist/locator-health-api.js.map +1 -0
- package/dist/locator-health.d.ts +195 -0
- package/dist/locator-health.js +419 -0
- package/dist/locator-health.js.map +1 -0
- package/dist/mcp-cli-tools.d.ts +3 -1
- package/dist/mcp-cli-tools.js +55 -5
- package/dist/mcp-cli-tools.js.map +1 -1
- package/dist/project-config.d.ts +29 -0
- package/dist/project-config.js +76 -0
- package/dist/project-config.js.map +1 -0
- package/package.json +18 -16
- package/skills/jevitate-ci-check/SKILL.md +16 -0
- package/skills/jevitate-getting-started/SKILL.md +7 -2
- package/skills/jevitate-run-journey/SKILL.md +11 -0
- package/skills/jevitate-test-campaign/SKILL.md +13 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve as resolvePath } from "node:path";
|
|
4
|
+
import { redactText } from "@jevitate/ai-core";
|
|
5
|
+
import { clock } from "@jevitate/domain";
|
|
6
|
+
import { inMcpInvocation } from "./approval-provenance.js";
|
|
7
|
+
import { resolveDataDir } from "./data-dir.js";
|
|
8
|
+
import { readMaskedLine } from "./masked-input.js";
|
|
9
|
+
import { secretCommandRunner } from "./secret-command.js";
|
|
10
|
+
/**
|
|
11
|
+
* #464 — the Journeeze connection: which hosts an upload key may go to, how the key is found, and
|
|
12
|
+
* what `jevitate connect journeeze` saves (journeeze-saas `docs/contract/catalog-bundle-upload-v1.md`).
|
|
13
|
+
*
|
|
14
|
+
* The key itself is NEVER saved by jevitate. `connect` saves a REFERENCE to where the person keeps it
|
|
15
|
+
* (an environment variable, a command that prints it, or a password-manager entry), bound to the
|
|
16
|
+
* Journeeze origin it was verified against, in `~/.jevitate/journeeze.json` (outside the repo, 0600).
|
|
17
|
+
* The reference is shaped like `@jevitate/secrets`' `SecretRef` (`{manager, key, origin, field}`):
|
|
18
|
+
* jevitate delegates to the external source on every use and never implements a vault.
|
|
19
|
+
*/
|
|
20
|
+
/** The upload key's format (contract §3): `jzu_` + 40 lowercase base32 characters. */
|
|
21
|
+
export const JOURNEEZE_KEY_RE = /^jzu_[a-z2-7]{40}$/;
|
|
22
|
+
/** Anything that looks like (part of) a key — refused wherever only a reference belongs. */
|
|
23
|
+
const KEY_LIKE_RE = /jzu_[a-z2-7]{8,}/i;
|
|
24
|
+
/** The CI env var (contract §9); read through ai-core's `CredentialStore` (never its plaintext file). */
|
|
25
|
+
export const JOURNEEZE_KEY_ENV = "JOURNEEZE_UPLOAD_KEY";
|
|
26
|
+
/** Optional base URL for CI (`publish` only); pinned like `--url`. */
|
|
27
|
+
export const JOURNEEZE_URL_ENV = "JOURNEEZE_URL";
|
|
28
|
+
export const JOURNEEZE_API_PREFIX = "/api/upload/v1";
|
|
29
|
+
/** Contract §2: the only hosts an upload key is ever sent to (plus loopback, only with `JEVITATE_JOURNEEZE_DEV=1`). */
|
|
30
|
+
export const JOURNEEZE_PINNED_ORIGINS = ["https://app.journeeze.dev", "https://app.staging.journeeze.dev"];
|
|
31
|
+
/** `JEVITATE_JOURNEEZE_DEV=1` allows a loopback Journeeze (local development of Journeeze itself). */
|
|
32
|
+
export const JOURNEEZE_DEV_ENV = "JEVITATE_JOURNEEZE_DEV";
|
|
33
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
34
|
+
/** A Journeeze-specific refusal. `code` is stable; the message never carries a key. */
|
|
35
|
+
export class JourneezeError extends Error {
|
|
36
|
+
code;
|
|
37
|
+
constructor(code, message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.code = code;
|
|
40
|
+
this.name = "JourneezeError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The Journeeze origin for a base URL, or a `JourneezeError`: HTTPS on a pinned host, or — only when
|
|
45
|
+
* `JEVITATE_JOURNEEZE_DEV=1` is set — http(s) on a loopback host (local development, contract §2;
|
|
46
|
+
* otherwise an upload key would go over plain HTTP to whatever listens on that port). No path,
|
|
47
|
+
* credentials, query or fragment.
|
|
48
|
+
*/
|
|
49
|
+
export function pinnedJourneezeOrigin(baseUrl, env = process.env) {
|
|
50
|
+
let u;
|
|
51
|
+
try {
|
|
52
|
+
u = new URL(baseUrl);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
throw new JourneezeError("E_JOURNEEZE_URL", `not a URL: ${JSON.stringify(baseUrl)}`);
|
|
56
|
+
}
|
|
57
|
+
const bare = u.username === "" && u.password === "" && u.search === "" && u.hash === "" && (u.pathname === "/" || u.pathname === "");
|
|
58
|
+
if (!bare)
|
|
59
|
+
throw new JourneezeError("E_JOURNEEZE_URL", `the Journeeze URL must be a bare origin (got ${JSON.stringify(baseUrl)})`);
|
|
60
|
+
if (LOOPBACK_HOSTS.has(u.hostname) && (u.protocol === "http:" || u.protocol === "https:")) {
|
|
61
|
+
if (env[JOURNEEZE_DEV_ENV]?.trim() === "1")
|
|
62
|
+
return u.origin;
|
|
63
|
+
throw new JourneezeError("E_JOURNEEZE_URL", `refusing ${u.origin}: a loopback Journeeze is for local development only — set ${JOURNEEZE_DEV_ENV}=1 to send an upload key there`);
|
|
64
|
+
}
|
|
65
|
+
if (u.protocol === "https:" && JOURNEEZE_PINNED_ORIGINS.includes(u.origin))
|
|
66
|
+
return u.origin;
|
|
67
|
+
throw new JourneezeError("E_JOURNEEZE_URL", `an upload key is only ever sent to ${JOURNEEZE_PINNED_ORIGINS.join(" or ")} (or a loopback host with ${JOURNEEZE_DEV_ENV}=1) — refusing ${u.origin}`);
|
|
68
|
+
}
|
|
69
|
+
export const fetchJourneezeHttp = async (req) => {
|
|
70
|
+
const headers = { ...req.headers };
|
|
71
|
+
// undici sets Content-Length itself for a byte body (a forbidden request header to set by hand).
|
|
72
|
+
delete headers["Content-Length"];
|
|
73
|
+
return fetch(req.url, {
|
|
74
|
+
method: req.method,
|
|
75
|
+
headers,
|
|
76
|
+
redirect: "manual",
|
|
77
|
+
...(req.body === undefined ? {} : { body: Buffer.from(req.body) }),
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
/** Sends `req`, refusing any response that is a redirect (the key never follows one to another origin). */
|
|
81
|
+
export async function sendPinned(http, origin, req) {
|
|
82
|
+
if (new URL(req.url).origin !== origin) {
|
|
83
|
+
throw new JourneezeError("E_JOURNEEZE_ORIGIN", `refusing to send the upload key to ${new URL(req.url).origin}: it is bound to ${origin}`);
|
|
84
|
+
}
|
|
85
|
+
const res = await http(req);
|
|
86
|
+
if (res.status === 0 || (res.status >= 300 && res.status < 400)) {
|
|
87
|
+
const to = res.headers.get("location");
|
|
88
|
+
throw new JourneezeError("E_JOURNEEZE_REDIRECT", `Journeeze answered ${req.method} ${new URL(req.url).pathname} with a redirect${to ? ` to ${safeLocation(to, req.url)}` : ""} — redirects are never followed with an upload key`);
|
|
89
|
+
}
|
|
90
|
+
return res;
|
|
91
|
+
}
|
|
92
|
+
function safeLocation(location, base) {
|
|
93
|
+
try {
|
|
94
|
+
return new URL(location, base).origin;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return "an invalid location";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** The JSON body of a response, or `{}` when it is not JSON (unknown fields are ignored, contract §8). */
|
|
101
|
+
export async function jsonBody(res) {
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(await res.text());
|
|
104
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** `code (requestId)` of an error response (contract §5), for messages. Never echoes the request. */
|
|
111
|
+
export function describeError(res, body) {
|
|
112
|
+
const code = typeof body.code === "string" ? body.code : "unknown";
|
|
113
|
+
const message = typeof body.message === "string" ? `: ${body.message.slice(0, 300)}` : "";
|
|
114
|
+
const requestId = res.headers.get("journeeze-request-id") ?? (typeof body.requestId === "string" ? body.requestId : undefined);
|
|
115
|
+
return `${res.status} ${code}${message}${requestId ? ` (request ${requestId})` : ""}`;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Parses what the person typed at the reference prompt: `env:<VAR>`, `cmd:<command>`, `op://…`,
|
|
119
|
+
* `bw:<item>` or `pass:<path>`. Anything that looks like a key itself is refused (without echoing it).
|
|
120
|
+
*/
|
|
121
|
+
export function parseKeyRef(text, origin) {
|
|
122
|
+
const t = text.trim();
|
|
123
|
+
if (KEY_LIKE_RE.test(t)) {
|
|
124
|
+
throw new JourneezeError("E_JOURNEEZE_REF", "that looks like the upload key itself — jevitate saves only WHERE the key is kept, never the key");
|
|
125
|
+
}
|
|
126
|
+
const ref = (manager, key) => {
|
|
127
|
+
if (key.trim() === "")
|
|
128
|
+
throw new JourneezeError("E_JOURNEEZE_REF", `the ${manager} reference is empty`);
|
|
129
|
+
return { manager, key: key.trim(), origin, field: "journeeze-upload-key" };
|
|
130
|
+
};
|
|
131
|
+
if (t.startsWith("env:")) {
|
|
132
|
+
const name = t.slice(4).trim();
|
|
133
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
|
|
134
|
+
throw new JourneezeError("E_JOURNEEZE_REF", `not an environment variable name: ${JSON.stringify(name)}`);
|
|
135
|
+
return ref("env", name);
|
|
136
|
+
}
|
|
137
|
+
if (t.startsWith("cmd:"))
|
|
138
|
+
return ref("cmd", t.slice(4));
|
|
139
|
+
if (t.startsWith("op://"))
|
|
140
|
+
return ref("op", t);
|
|
141
|
+
if (t.startsWith("bw:"))
|
|
142
|
+
return ref("bw", t.slice(3));
|
|
143
|
+
if (t.startsWith("pass:"))
|
|
144
|
+
return ref("pass", t.slice(5));
|
|
145
|
+
throw new JourneezeError("E_JOURNEEZE_REF", "expected env:<VAR>, cmd:<command>, op://…, bw:<item> or pass:<path>");
|
|
146
|
+
}
|
|
147
|
+
export function defaultKeySources(env = process.env) {
|
|
148
|
+
return {
|
|
149
|
+
env,
|
|
150
|
+
runCommand: secretCommandRunner(),
|
|
151
|
+
exec: (cmd, args) => new Promise((resolveOut, reject) => {
|
|
152
|
+
execFile(cmd, [...args], { timeout: 60_000, maxBuffer: 64 * 1024 }, (err, stdout) => (err ? reject(err) : resolveOut(String(stdout))));
|
|
153
|
+
}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const MANAGER_COMMANDS = {
|
|
157
|
+
op: (key) => ["op", ["read", key]],
|
|
158
|
+
bw: (key) => ["bw", ["get", "password", key]],
|
|
159
|
+
pass: (key) => ["pass", ["show", key]],
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* The key a reference points to, or a `JourneezeError` that names the reference — never the source's
|
|
163
|
+
* output (a misconfigured source may print the secret on failure). The value is checked for the key
|
|
164
|
+
* format; the caller uses it only for the `Authorization` header.
|
|
165
|
+
*/
|
|
166
|
+
export async function resolveKeyRef(ref, sources) {
|
|
167
|
+
let raw;
|
|
168
|
+
try {
|
|
169
|
+
if (ref.manager === "env")
|
|
170
|
+
raw = sources.env[ref.key];
|
|
171
|
+
else if (ref.manager === "cmd")
|
|
172
|
+
raw = await sources.runCommand(ref.key);
|
|
173
|
+
else {
|
|
174
|
+
const [cmd, args] = MANAGER_COMMANDS[ref.manager](ref.key);
|
|
175
|
+
raw = await sources.exec(cmd, args);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
const code = err?.code;
|
|
180
|
+
throw new JourneezeError("E_JOURNEEZE_KEY_UNRESOLVABLE", `could not read the upload key from ${describeRef(ref)}${typeof code === "number" || typeof code === "string" ? ` (exit code ${String(code)})` : ""}`);
|
|
181
|
+
}
|
|
182
|
+
// `pass show` prints the secret on its first line; the others print it alone.
|
|
183
|
+
const value = (raw ?? "").split(/\r?\n/).map((l) => l.trim()).find((l) => l !== "") ?? "";
|
|
184
|
+
if (value === "")
|
|
185
|
+
throw new JourneezeError("E_JOURNEEZE_KEY_UNRESOLVABLE", `${describeRef(ref)} is empty or unset`);
|
|
186
|
+
if (!JOURNEEZE_KEY_RE.test(value)) {
|
|
187
|
+
throw new JourneezeError("E_JOURNEEZE_KEY_FORMAT", `the value from ${describeRef(ref)} is not a Journeeze upload key (jzu_ followed by 40 characters)`);
|
|
188
|
+
}
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
/** A display form of a reference (never a value). */
|
|
192
|
+
export function describeRef(ref) {
|
|
193
|
+
switch (ref.manager) {
|
|
194
|
+
case "env":
|
|
195
|
+
return `environment variable ${ref.key}`;
|
|
196
|
+
case "cmd":
|
|
197
|
+
return "the saved key command";
|
|
198
|
+
default:
|
|
199
|
+
return `${ref.manager} entry ${ref.key}`;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
export function connectionsPath(deps = {}) {
|
|
203
|
+
return resolveDataDir(["journeeze.json"], deps.homedir ? { homedir: deps.homedir } : {});
|
|
204
|
+
}
|
|
205
|
+
/** The connection's lookup key: the project data dir (absolute), or `*` outside a project. */
|
|
206
|
+
export function projectKey(projectDir) {
|
|
207
|
+
return projectDir === null ? "*" : resolvePath(projectDir);
|
|
208
|
+
}
|
|
209
|
+
async function readConnections(deps) {
|
|
210
|
+
const path = connectionsPath(deps);
|
|
211
|
+
let raw;
|
|
212
|
+
try {
|
|
213
|
+
raw = await readFile(path, "utf8");
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
if (err?.code === "ENOENT")
|
|
217
|
+
return { version: 1, connections: {} };
|
|
218
|
+
throw new JourneezeError("E_JOURNEEZE_CONFIG", `cannot read ${path}`);
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const parsed = JSON.parse(raw);
|
|
222
|
+
if (parsed.version !== 1 || typeof parsed.connections !== "object" || parsed.connections === null)
|
|
223
|
+
throw new Error("shape");
|
|
224
|
+
return parsed;
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
throw new JourneezeError("E_JOURNEEZE_CONFIG", `${path} is not a jevitate Journeeze connections file — fix or delete it, then run \`jevitate connect journeeze\``);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* The saved connection for a project: ONLY that project's own (a connection made in one project, or
|
|
232
|
+
* outside any, never uploads another project's catalog). The `*` connection (made outside any
|
|
233
|
+
* project) applies only outside a project, and never inside an MCP call. A project without its own
|
|
234
|
+
* connection is not connected: a person runs `jevitate connect journeeze` in that project.
|
|
235
|
+
*/
|
|
236
|
+
export async function loadConnection(projectDir, deps = {}) {
|
|
237
|
+
if (projectDir === null && inMcpInvocation())
|
|
238
|
+
return undefined;
|
|
239
|
+
const file = await readConnections(deps);
|
|
240
|
+
const key = projectKey(projectDir);
|
|
241
|
+
return Object.prototype.hasOwnProperty.call(file.connections, key) ? file.connections[key] : undefined;
|
|
242
|
+
}
|
|
243
|
+
/** Saves a connection (a reference, never a key) — refuses anything that looks like a key. */
|
|
244
|
+
export async function saveConnection(projectDir, conn, deps = {}) {
|
|
245
|
+
const file = await readConnections(deps);
|
|
246
|
+
const next = { version: 1, connections: { ...file.connections, [projectKey(projectDir)]: conn } };
|
|
247
|
+
const text = `${JSON.stringify(next, null, 2)}\n`;
|
|
248
|
+
if (KEY_LIKE_RE.test(text.replace(/"keyPrefix": "jzu_[a-z2-7]{4}"/g, ""))) {
|
|
249
|
+
throw new JourneezeError("E_JOURNEEZE_CONFIG", "refusing to save: the connection would hold an upload key in plain text");
|
|
250
|
+
}
|
|
251
|
+
const path = connectionsPath(deps);
|
|
252
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
253
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
254
|
+
await writeFile(tmp, text, { mode: 0o600 });
|
|
255
|
+
await chmod(tmp, 0o600);
|
|
256
|
+
await rename(tmp, path);
|
|
257
|
+
return path;
|
|
258
|
+
}
|
|
259
|
+
/** Verifies a key with `GET /whoami` — the only call `connect` makes with it. */
|
|
260
|
+
export async function whoami(http, origin, key) {
|
|
261
|
+
const res = await sendPinned(http, origin, {
|
|
262
|
+
method: "GET",
|
|
263
|
+
url: `${origin}${JOURNEEZE_API_PREFIX}/whoami`,
|
|
264
|
+
headers: { Authorization: `Bearer ${key}`, Accept: "application/json" },
|
|
265
|
+
});
|
|
266
|
+
const body = await jsonBody(res);
|
|
267
|
+
if (res.status !== 200) {
|
|
268
|
+
const reconnect = res.status === 401 ? " — check the key (create a new one on the Journeeze board if it was revoked or expired)" : "";
|
|
269
|
+
throw new JourneezeError(res.status === 401 ? "E_JOURNEEZE_KEY_REFUSED" : "E_JOURNEEZE_HTTP", `Journeeze refused the key check: ${describeError(res, body)}${reconnect}`);
|
|
270
|
+
}
|
|
271
|
+
const product = body.product;
|
|
272
|
+
if (typeof product?.id !== "string" || typeof product.name !== "string") {
|
|
273
|
+
throw new JourneezeError("E_JOURNEEZE_HTTP", "Journeeze's key check returned no product");
|
|
274
|
+
}
|
|
275
|
+
const tenant = body.tenant?.name;
|
|
276
|
+
const prefix = typeof body.keyPrefix === "string" && /^jzu_[a-z2-7]{4}$/.test(body.keyPrefix) ? body.keyPrefix : key.slice(0, 8);
|
|
277
|
+
return { product: { id: product.id, name: product.name }, tenant: typeof tenant === "string" ? tenant : "", keyPrefix: prefix };
|
|
278
|
+
}
|
|
279
|
+
export function processTerminal() {
|
|
280
|
+
return {
|
|
281
|
+
isTTY: process.stdin.isTTY === true && process.stderr.isTTY === true,
|
|
282
|
+
async ask(question) {
|
|
283
|
+
const { createInterface } = await import("node:readline/promises");
|
|
284
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
285
|
+
try {
|
|
286
|
+
return await rl.question(`${question} `);
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
rl.close();
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
askHidden: (question) => readMaskedLine(process.stdin, process.stderr, question),
|
|
293
|
+
say: (line) => void process.stderr.write(`${line}\n`),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const REF_PROMPT = [
|
|
297
|
+
"Where do you keep this product's Journeeze upload key? jevitate saves only this reference, never the key:",
|
|
298
|
+
" env:<VAR> an environment variable",
|
|
299
|
+
" cmd:<command> a command that prints it",
|
|
300
|
+
" op://… | bw:<item> | pass:<path> a password manager entry",
|
|
301
|
+
].join("\n");
|
|
302
|
+
/**
|
|
303
|
+
* `jevitate connect journeeze`: the person names where the key is kept (or types the key, hidden, to
|
|
304
|
+
* check it first — then names where it is kept, which must hold the same key); the key is verified
|
|
305
|
+
* with `whoami`; the person confirms the product; only the reference is saved.
|
|
306
|
+
*/
|
|
307
|
+
export async function runConnect(baseUrl, projectDir, deps = {}) {
|
|
308
|
+
const origin = pinnedJourneezeOrigin(baseUrl);
|
|
309
|
+
const term = deps.terminal ?? processTerminal();
|
|
310
|
+
if (!term.isTTY) {
|
|
311
|
+
throw new JourneezeError("E_CONNECT_NEEDS_TTY", "connect journeeze needs a person at a terminal (it asks for the upload key); in CI set JOURNEEZE_UPLOAD_KEY instead");
|
|
312
|
+
}
|
|
313
|
+
const http = deps.http ?? fetchJourneezeHttp;
|
|
314
|
+
const sources = deps.sources ?? defaultKeySources();
|
|
315
|
+
let typed;
|
|
316
|
+
const seen = [];
|
|
317
|
+
try {
|
|
318
|
+
term.say(REF_PROMPT);
|
|
319
|
+
let answer = (await term.ask("Reference (leave empty to type the key itself and check it first):")).trim();
|
|
320
|
+
if (answer === "") {
|
|
321
|
+
typed = (await term.askHidden("Journeeze upload key (jzu_…; hidden):")).trim();
|
|
322
|
+
seen.push(typed);
|
|
323
|
+
if (!JOURNEEZE_KEY_RE.test(typed))
|
|
324
|
+
throw new JourneezeError("E_JOURNEEZE_KEY_FORMAT", "that is not a Journeeze upload key (jzu_ followed by 40 characters)");
|
|
325
|
+
const who = await whoami(http, origin, typed);
|
|
326
|
+
term.say(`The key is valid for '${who.product.name}'${who.tenant ? ` (${who.tenant})` : ""}. Now say where it is kept.`);
|
|
327
|
+
answer = (await term.ask("Reference:")).trim();
|
|
328
|
+
}
|
|
329
|
+
const ref = parseKeyRef(answer, origin);
|
|
330
|
+
const key = await resolveKeyRef(ref, sources);
|
|
331
|
+
seen.push(key);
|
|
332
|
+
if (typed !== undefined && key !== typed) {
|
|
333
|
+
throw new JourneezeError("E_JOURNEEZE_REF", `${describeRef(ref)} holds a different key than the one you typed — nothing was saved`);
|
|
334
|
+
}
|
|
335
|
+
const who = await whoami(http, origin, key);
|
|
336
|
+
const yes = (await term.ask(`Connect this project to Journeeze product '${who.product.name}'${who.tenant ? ` (${who.tenant})` : ""} at ${origin}? [y/N]`)).trim().toLowerCase();
|
|
337
|
+
if (yes !== "y" && yes !== "yes") {
|
|
338
|
+
throw new JourneezeError("E_CONNECT_DECLINED", "not connected — nothing was saved");
|
|
339
|
+
}
|
|
340
|
+
await saveConnection(projectDir, { baseUrl: origin, product: who.product, keyPrefix: who.keyPrefix, keyRef: ref, connectedAt: new Date(clock.now()).toISOString() }, deps);
|
|
341
|
+
return { baseUrl: origin, product: who.product, keyPrefix: who.keyPrefix, stored: true };
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
throw scrubError(err, seen, "E_CONNECT");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/** Re-throws `err` as a `JourneezeError` whose message has every given secret removed. */
|
|
348
|
+
export function scrubError(err, secrets, fallbackCode) {
|
|
349
|
+
const code = err instanceof JourneezeError ? err.code : fallbackCode;
|
|
350
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
351
|
+
return new JourneezeError(code, redactText(message, secrets));
|
|
352
|
+
}
|
|
353
|
+
/** Sleeps on jevitate's clock (tests inject their own). */
|
|
354
|
+
export function clockSleep(ms) {
|
|
355
|
+
return new Promise((r) => clock.setTimeout(r, ms));
|
|
356
|
+
}
|
|
357
|
+
//# sourceMappingURL=journeeze-connect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journeeze-connect.js","sourceRoot":"","sources":["../src/journeeze-connect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type AnchorLintIssue, type CatalogRefIssue, type Journey, type JourneyLintFinding } from "@jevitate/journey";
|
|
2
|
+
import { CatalogInputError, type Catalog } from "./catalog.js";
|
|
3
|
+
/**
|
|
4
|
+
* #466 — the anchor rules at approval time. The #466 ruling: new and re-promoted Journeys follow
|
|
5
|
+
* the 0.10 anchor rules (`anchorLintIssues`, plus the catalog's `jobStep`/`serves` references,
|
|
6
|
+
* `journeyRefIssues`); existing ones still load and run, and lint and the review sheet warn with the
|
|
7
|
+
* fix. So `journey promote` ENFORCES on a new promotion (no prior approval) and on a re-promotion
|
|
8
|
+
* whose content changed beyond step ids, and WARNS (passes) otherwise — including the #467 backfill:
|
|
9
|
+
* a re-approval whose only change since the approved content is step ids added (`isStepIdOnlyChange`).
|
|
10
|
+
*/
|
|
11
|
+
/** #466: a promotion refused for the 0.10 anchor rules. A catalog-input refusal: exit 64, fix the Journey. */
|
|
12
|
+
export declare class AnchorRulesError extends CatalogInputError {
|
|
13
|
+
readonly anchorIssues: readonly AnchorLintIssue[];
|
|
14
|
+
readonly refIssues: readonly CatalogRefIssue[];
|
|
15
|
+
readonly code: string;
|
|
16
|
+
constructor(message: string, anchorIssues: readonly AnchorLintIssue[], refIssues: readonly CatalogRefIssue[]);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* #466: whether `next` differs from the previously approved content only by step ids (its steps'
|
|
20
|
+
* and its anchors' `stepId`s) — the #467 backfill. `prev` is the approved Journey (the `.approved/`
|
|
21
|
+
* snapshot) or the approval's content hash (`journeyReviewHash`, of a Journey that had no ids then).
|
|
22
|
+
* True also when nothing changed at all.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isStepIdOnlyChange(prev: Journey | string, next: Journey): boolean;
|
|
25
|
+
/** #466: how a promotion treats the anchor rules — enforce them, or only warn (lint/review sheet). */
|
|
26
|
+
export type AnchorRuleMode = "enforce" | "warn";
|
|
27
|
+
/**
|
|
28
|
+
* #466: the mode for promoting `subject` (as it would be stored: ids minted, anchors stamped) given
|
|
29
|
+
* the Journey's prior approval and approved snapshot. No prior approval: enforce. Unchanged, or
|
|
30
|
+
* changed only by step ids: warn. Otherwise: enforce.
|
|
31
|
+
*/
|
|
32
|
+
export declare function anchorRuleMode(subject: Journey, prior: {
|
|
33
|
+
readonly approvedHash?: string;
|
|
34
|
+
readonly approvedSnapshot?: Journey | null;
|
|
35
|
+
}): AnchorRuleMode;
|
|
36
|
+
/** #466: the 0.10 anchor-rule problems of `journey` — its own and its catalog references (loaded only when it has some). */
|
|
37
|
+
export declare function journeyAnchorIssues(journey: Journey, opts: {
|
|
38
|
+
readonly catalogDir: string | null;
|
|
39
|
+
readonly journeysDir: string;
|
|
40
|
+
readonly enforce?: boolean;
|
|
41
|
+
readonly catalog?: Catalog;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
anchorIssues: AnchorLintIssue[];
|
|
44
|
+
refIssues: CatalogRefIssue[];
|
|
45
|
+
}>;
|
|
46
|
+
/** #466: refuses the promotion when an enforced anchor rule fails (an `error`); warnings pass. */
|
|
47
|
+
export declare function assertAnchorRules(id: string, issues: {
|
|
48
|
+
readonly anchorIssues: readonly AnchorLintIssue[];
|
|
49
|
+
readonly refIssues: readonly CatalogRefIssue[];
|
|
50
|
+
}): void;
|
|
51
|
+
/** #466: the catalog anchor/serves reference problems as lint warnings (`anchor-job-step`, `serves-outcome`). */
|
|
52
|
+
export declare function refLintFindings(issues: readonly CatalogRefIssue[]): JourneyLintFinding[];
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { anchorLintIssues, stripStepIds } from "@jevitate/journey";
|
|
2
|
+
import { CatalogInputError, catalogJourney } from "./catalog.js";
|
|
3
|
+
import { loadCatalog } from "./catalog-api.js";
|
|
4
|
+
import { journeyRefIssues } from "./catalog-refs.js";
|
|
5
|
+
import { journeyReviewHash } from "./journey-review.js";
|
|
6
|
+
/**
|
|
7
|
+
* #466 — the anchor rules at approval time. The #466 ruling: new and re-promoted Journeys follow
|
|
8
|
+
* the 0.10 anchor rules (`anchorLintIssues`, plus the catalog's `jobStep`/`serves` references,
|
|
9
|
+
* `journeyRefIssues`); existing ones still load and run, and lint and the review sheet warn with the
|
|
10
|
+
* fix. So `journey promote` ENFORCES on a new promotion (no prior approval) and on a re-promotion
|
|
11
|
+
* whose content changed beyond step ids, and WARNS (passes) otherwise — including the #467 backfill:
|
|
12
|
+
* a re-approval whose only change since the approved content is step ids added (`isStepIdOnlyChange`).
|
|
13
|
+
*/
|
|
14
|
+
/** #466: a promotion refused for the 0.10 anchor rules. A catalog-input refusal: exit 64, fix the Journey. */
|
|
15
|
+
export class AnchorRulesError extends CatalogInputError {
|
|
16
|
+
anchorIssues;
|
|
17
|
+
refIssues;
|
|
18
|
+
code = "E_JOURNEY_ANCHOR_RULES";
|
|
19
|
+
constructor(message, anchorIssues, refIssues) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.anchorIssues = anchorIssues;
|
|
22
|
+
this.refIssues = refIssues;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* #466: whether `next` differs from the previously approved content only by step ids (its steps'
|
|
27
|
+
* and its anchors' `stepId`s) — the #467 backfill. `prev` is the approved Journey (the `.approved/`
|
|
28
|
+
* snapshot) or the approval's content hash (`journeyReviewHash`, of a Journey that had no ids then).
|
|
29
|
+
* True also when nothing changed at all.
|
|
30
|
+
*/
|
|
31
|
+
export function isStepIdOnlyChange(prev, next) {
|
|
32
|
+
const nextBare = journeyReviewHash(stripStepIds(next));
|
|
33
|
+
if (typeof prev !== "string")
|
|
34
|
+
return journeyReviewHash(stripStepIds(prev)) === nextBare;
|
|
35
|
+
// A hash cannot be stripped: it matches `next` with every id stripped (approved before ids), or
|
|
36
|
+
// with only the anchors' (approved with step ids, before promote stamped anchors).
|
|
37
|
+
const hash = prev.trim().toLowerCase();
|
|
38
|
+
return hash === nextBare || hash === journeyReviewHash(stripAnchorStepIds(next));
|
|
39
|
+
}
|
|
40
|
+
function stripAnchorStepIds(j) {
|
|
41
|
+
const anchors = j.metadata.anchors;
|
|
42
|
+
return anchors === undefined ? j : { ...j, metadata: { ...j.metadata, anchors: anchors.map(({ stepId: _id, ...rest }) => rest) } };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* #466: the mode for promoting `subject` (as it would be stored: ids minted, anchors stamped) given
|
|
46
|
+
* the Journey's prior approval and approved snapshot. No prior approval: enforce. Unchanged, or
|
|
47
|
+
* changed only by step ids: warn. Otherwise: enforce.
|
|
48
|
+
*/
|
|
49
|
+
export function anchorRuleMode(subject, prior) {
|
|
50
|
+
if (prior.approvedHash === undefined)
|
|
51
|
+
return "enforce";
|
|
52
|
+
if (prior.approvedHash === journeyReviewHash(subject))
|
|
53
|
+
return "warn";
|
|
54
|
+
if (isStepIdOnlyChange(prior.approvedHash, subject))
|
|
55
|
+
return "warn";
|
|
56
|
+
if (prior.approvedSnapshot != null && journeyReviewHash(prior.approvedSnapshot) === prior.approvedHash && isStepIdOnlyChange(prior.approvedSnapshot, subject))
|
|
57
|
+
return "warn";
|
|
58
|
+
return "enforce";
|
|
59
|
+
}
|
|
60
|
+
/** Whether a Journey references the catalog in a way `journeyRefIssues` checks (so the catalog is worth loading). */
|
|
61
|
+
function needsCatalog(j) {
|
|
62
|
+
const m = j.metadata;
|
|
63
|
+
return m.job !== undefined || (m.serves ?? []).length > 0 || (m.anchors ?? []).some((a) => a.jobStep !== undefined);
|
|
64
|
+
}
|
|
65
|
+
/** #466: the 0.10 anchor-rule problems of `journey` — its own and its catalog references (loaded only when it has some). */
|
|
66
|
+
export async function journeyAnchorIssues(journey, opts) {
|
|
67
|
+
const enforce = opts.enforce === true;
|
|
68
|
+
const anchorIssues = anchorLintIssues(journey, { enforce });
|
|
69
|
+
if (!needsCatalog(journey))
|
|
70
|
+
return { anchorIssues, refIssues: [] };
|
|
71
|
+
const catalog = opts.catalog ?? (await loadCatalog(opts.catalogDir, opts.journeysDir));
|
|
72
|
+
return { anchorIssues, refIssues: journeyRefIssues(catalog, catalogJourney(journey), { enforce }) };
|
|
73
|
+
}
|
|
74
|
+
/** One problem as one line: `metadata.anchors[0].name: … — fix: …`. */
|
|
75
|
+
function line(path, message, fix) {
|
|
76
|
+
return `${path}: ${message}${fix === undefined ? "" : ` — fix: ${fix}`}`;
|
|
77
|
+
}
|
|
78
|
+
/** #466: refuses the promotion when an enforced anchor rule fails (an `error`); warnings pass. */
|
|
79
|
+
export function assertAnchorRules(id, issues) {
|
|
80
|
+
const anchors = issues.anchorIssues.filter((i) => i.severity === "error");
|
|
81
|
+
const refs = issues.refIssues.filter((i) => i.severity === "error");
|
|
82
|
+
if (anchors.length + refs.length === 0)
|
|
83
|
+
return;
|
|
84
|
+
const lines = [...anchors.map((i) => line(i.path, i.message, i.fix)), ...refs.map((i) => line(i.path, i.message, i.fix))];
|
|
85
|
+
throw new AnchorRulesError(`journey '${id}' does not follow the anchor rules (#466), which a new or changed Journey must: ${lines.join("; ")} — fix the Journey, then review it again (jevitate journey review ${id})`, anchors, refs);
|
|
86
|
+
}
|
|
87
|
+
/** #466: the catalog anchor/serves reference problems as lint warnings (`anchor-job-step`, `serves-outcome`). */
|
|
88
|
+
export function refLintFindings(issues) {
|
|
89
|
+
return issues
|
|
90
|
+
.filter((i) => i.code.startsWith("journey."))
|
|
91
|
+
.map((i) => {
|
|
92
|
+
const anchorMatch = /^metadata\.anchors\[(\d+)\]/.exec(i.path);
|
|
93
|
+
return {
|
|
94
|
+
rule: anchorMatch === null ? "serves-outcome" : "anchor-job-step",
|
|
95
|
+
level: "warning",
|
|
96
|
+
message: `${i.path}: ${i.message}`,
|
|
97
|
+
...(i.fix === undefined ? {} : { fix: i.fix }),
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=journey-anchor-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journey-anchor-gate.js","sourceRoot":"","sources":["../src/journey-anchor-gate.ts"],"names":[],"mappings":"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"}
|
package/dist/journey-api.d.ts
CHANGED
|
@@ -199,6 +199,7 @@ export declare function prefixParams(full: Journey, prefix: Journey, params: Rec
|
|
|
199
199
|
*/
|
|
200
200
|
export declare function lintJourneyById(dir: string, id: string, opts?: {
|
|
201
201
|
readRequests?: readonly string[];
|
|
202
|
+
catalogDir?: string | null;
|
|
202
203
|
}): Promise<JourneyLintResult>;
|
|
203
204
|
/**
|
|
204
205
|
* Promotes a local Journey (#124, mirrors `promoteMissionTarget` in
|