@foldrun/core 1.4.2 → 2.1.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/index.d.ts +0 -3
- package/dist/index.js +0 -3
- package/dist/src/catalog.d.ts +7 -0
- package/dist/src/catalog.js +16 -1
- package/dist/src/deploy.d.ts +6 -27
- package/dist/src/deploy.js +14 -56
- package/dist/src/history.d.ts +30 -10
- package/dist/src/history.js +232 -99
- package/dist/src/library.d.ts +0 -13
- package/dist/src/library.js +0 -50
- package/dist/src/model-credential.js +1 -1
- package/dist/src/model-env.js +1 -1
- package/dist/src/platform.d.ts +1 -3
- package/dist/src/platform.js +0 -1
- package/dist/src/run-container.d.ts +31 -5
- package/dist/src/run-container.js +71 -3
- package/dist/src/runner.d.ts +2 -0
- package/dist/src/runner.js +20 -12
- package/dist/src/secrets.js +1 -12
- package/dist/src/starter.js +2 -2
- package/dist/src/step-exec.d.ts +23 -5
- package/dist/src/step-exec.js +30 -23
- package/dist/src/store.d.ts +61 -22
- package/dist/src/store.js +105 -25
- package/dist/src/tool-loop.d.ts +4 -1
- package/dist/src/tool-loop.js +2 -0
- package/dist/src/translator.d.ts +12 -0
- package/dist/src/translator.js +36 -13
- package/index.ts +0 -3
- package/package.json +1 -3
- package/src/catalog.ts +18 -2
- package/src/deploy.ts +13 -74
- package/src/history.ts +230 -108
- package/src/library.ts +0 -44
- package/src/model-credential.ts +1 -1
- package/src/model-env.ts +1 -1
- package/src/platform.ts +1 -4
- package/src/run-container.ts +78 -5
- package/src/runner.ts +22 -12
- package/src/secrets.ts +1 -11
- package/src/starter.ts +2 -2
- package/src/step-exec.ts +44 -27
- package/src/store.ts +145 -37
- package/src/tool-loop.ts +6 -1
- package/src/translator.ts +39 -15
- package/dist/src/git-hook.d.ts +0 -1
- package/dist/src/git-hook.js +0 -70
- package/dist/src/git.d.ts +0 -37
- package/dist/src/git.js +0 -101
- package/dist/src/gitrepo.d.ts +0 -113
- package/dist/src/gitrepo.js +0 -404
- package/dist/src/tar.d.ts +0 -30
- package/dist/src/tar.js +0 -112
- package/src/git-hook.ts +0 -73
- package/src/git.ts +0 -117
- package/src/gitrepo.ts +0 -439
- package/src/tar.ts +0 -125
package/src/translator.ts
CHANGED
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
// and their results, streaming — and drops what a Chat-Completions endpoint
|
|
23
23
|
// has no word for (cache_control, top_k, server-side tools, thinking on
|
|
24
24
|
// endpoints that lack reasoning_effort). Anything dropped is said once in
|
|
25
|
-
// the run trace.
|
|
25
|
+
// the run trace. Those endpoints cache prompt prefixes on their own side;
|
|
26
|
+
// the cached share they report comes back as cache_read_input_tokens, so a
|
|
27
|
+
// step's record says how much of its context was re-read. The translation is pure functions over JSON so the tests
|
|
26
28
|
// can drive every mapping without a network; the server around them is a
|
|
27
29
|
// few dozen lines.
|
|
28
30
|
|
|
@@ -37,6 +39,34 @@ import { captureRefusalHeaders, isRefusalStatus, refusalLine } from "./refusal.t
|
|
|
37
39
|
|
|
38
40
|
type Json = Record<string, unknown>;
|
|
39
41
|
|
|
42
|
+
/**
|
|
43
|
+
* An OpenAI-shaped usage object as Anthropic's: fresh input, cached input,
|
|
44
|
+
* output. Both OpenAI shapes count the cached part INSIDE the prompt total
|
|
45
|
+
* (Chat's prompt_tokens_details.cached_tokens, Responses'
|
|
46
|
+
* input_tokens_details.cached_tokens; DeepSeek says prompt_cache_hit_tokens),
|
|
47
|
+
* so it is taken out of input here or the step would count it twice.
|
|
48
|
+
*/
|
|
49
|
+
export function anthropicUsage(u: Json | undefined, wire: TranslatorWire): { input: number; cached: number; output: number } {
|
|
50
|
+
if (!u) return { input: 0, cached: 0, output: 0 };
|
|
51
|
+
const total = Number((wire === "chat" ? u.prompt_tokens : u.input_tokens) ?? 0);
|
|
52
|
+
const details = (wire === "chat" ? u.prompt_tokens_details : u.input_tokens_details) as Json | undefined;
|
|
53
|
+
const raw = Number(details?.cached_tokens ?? u.prompt_cache_hit_tokens ?? 0);
|
|
54
|
+
const cached = Number.isFinite(raw) ? Math.max(0, Math.min(raw, total)) : 0;
|
|
55
|
+
return {
|
|
56
|
+
input: total - cached,
|
|
57
|
+
cached,
|
|
58
|
+
output: Number((wire === "chat" ? u.completion_tokens : u.output_tokens) ?? 0),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function usageBlock(u: { input: number; cached: number; output: number } | null): Json {
|
|
63
|
+
return {
|
|
64
|
+
input_tokens: u?.input ?? 0,
|
|
65
|
+
output_tokens: u?.output ?? 0,
|
|
66
|
+
...(u && u.cached > 0 ? { cache_read_input_tokens: u.cached } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
40
70
|
export type TranslatorWire = "chat" | "responses";
|
|
41
71
|
|
|
42
72
|
export interface TranslatorSpec {
|
|
@@ -493,10 +523,7 @@ export function fromChatCompletion(res: Json, requestedModel: string, stopSequen
|
|
|
493
523
|
content,
|
|
494
524
|
stop_reason: stop.reason,
|
|
495
525
|
stop_sequence: stop.sequence,
|
|
496
|
-
usage:
|
|
497
|
-
input_tokens: Number(usage.prompt_tokens ?? 0),
|
|
498
|
-
output_tokens: Number(usage.completion_tokens ?? 0),
|
|
499
|
-
},
|
|
526
|
+
usage: usageBlock(anthropicUsage(usage, "chat")),
|
|
500
527
|
};
|
|
501
528
|
}
|
|
502
529
|
|
|
@@ -515,7 +542,7 @@ export class StreamTranslator {
|
|
|
515
542
|
private tools = new Map<number, { block: number; argsSeen: boolean }>();
|
|
516
543
|
/** The last choice's stop fields: finish_reason and any matched stop. */
|
|
517
544
|
private finish: Json = {};
|
|
518
|
-
private usage: { input: number; output: number } | null = null;
|
|
545
|
+
private usage: { input: number; cached: number; output: number } | null = null;
|
|
519
546
|
private id = `msg_${crypto.randomBytes(8).toString("hex")}`;
|
|
520
547
|
private readonly model: string;
|
|
521
548
|
private readonly stops: string[];
|
|
@@ -561,7 +588,7 @@ export class StreamTranslator {
|
|
|
561
588
|
const out = this.start(chunk);
|
|
562
589
|
const u = chunk.usage as Json | undefined;
|
|
563
590
|
if (u && (u.prompt_tokens !== undefined || u.completion_tokens !== undefined)) {
|
|
564
|
-
this.usage =
|
|
591
|
+
this.usage = anthropicUsage(u, "chat");
|
|
565
592
|
}
|
|
566
593
|
const choice = ((chunk.choices as Json[] | undefined) ?? [])[0];
|
|
567
594
|
if (!choice) return out;
|
|
@@ -650,7 +677,7 @@ export class StreamTranslator {
|
|
|
650
677
|
this.event("message_delta", {
|
|
651
678
|
type: "message_delta",
|
|
652
679
|
delta: { stop_reason: stop.reason, stop_sequence: stop.sequence },
|
|
653
|
-
usage:
|
|
680
|
+
usage: usageBlock(this.usage),
|
|
654
681
|
}),
|
|
655
682
|
);
|
|
656
683
|
out.push(this.event("message_stop", { type: "message_stop" }));
|
|
@@ -691,10 +718,7 @@ export function fromResponses(res: Json, requestedModel: string): Json {
|
|
|
691
718
|
content,
|
|
692
719
|
stop_reason: stop,
|
|
693
720
|
stop_sequence: null,
|
|
694
|
-
usage:
|
|
695
|
-
input_tokens: Number(usage.input_tokens ?? 0),
|
|
696
|
-
output_tokens: Number(usage.output_tokens ?? 0),
|
|
697
|
-
},
|
|
721
|
+
usage: usageBlock(anthropicUsage(usage, "responses")),
|
|
698
722
|
};
|
|
699
723
|
}
|
|
700
724
|
|
|
@@ -710,7 +734,7 @@ export class ResponsesStreamTranslator {
|
|
|
710
734
|
private nextIndex = 0;
|
|
711
735
|
/** Responses output_index → Anthropic block index and kind. */
|
|
712
736
|
private blocks = new Map<number, { block: number; kind: "text" | "tool"; argsSeen: boolean }>();
|
|
713
|
-
private usage: { input: number; output: number } | null = null;
|
|
737
|
+
private usage: { input: number; cached: number; output: number } | null = null;
|
|
714
738
|
private incomplete: string | null = null;
|
|
715
739
|
private sawTool = false;
|
|
716
740
|
private sawRefusal = false;
|
|
@@ -812,7 +836,7 @@ export class ResponsesStreamTranslator {
|
|
|
812
836
|
if (type === "response.completed" || type === "response.incomplete" || type === "response.failed") {
|
|
813
837
|
const r = (ev.response as Json | undefined) ?? {};
|
|
814
838
|
const u = r.usage as Json | undefined;
|
|
815
|
-
if (u) this.usage =
|
|
839
|
+
if (u) this.usage = anthropicUsage(u, "responses");
|
|
816
840
|
const reason = (r.incomplete_details as Json | undefined)?.reason;
|
|
817
841
|
if (typeof reason === "string") this.incomplete = reason;
|
|
818
842
|
return out;
|
|
@@ -834,7 +858,7 @@ export class ResponsesStreamTranslator {
|
|
|
834
858
|
this.event("message_delta", {
|
|
835
859
|
type: "message_delta",
|
|
836
860
|
delta: { stop_reason: reason, stop_sequence: null },
|
|
837
|
-
usage:
|
|
861
|
+
usage: usageBlock(this.usage),
|
|
838
862
|
}),
|
|
839
863
|
);
|
|
840
864
|
out.push(this.event("message_stop", { type: "message_stop" }));
|
package/dist/src/git-hook.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/src/git-hook.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
// The pre-receive check, run by git inside a push.
|
|
2
|
-
//
|
|
3
|
-
// git feeds "<old> <new> <ref>" lines on stdin. For a push to main, read the
|
|
4
|
-
// tree at the new commit straight from the object store and run the deploy
|
|
5
|
-
// checks on it. Any issue: print them and exit non-zero — the push is refused
|
|
6
|
-
// and the person sees exactly what a dashboard deploy would have told them,
|
|
7
|
-
// in their terminal, before anything changed.
|
|
8
|
-
//
|
|
9
|
-
// Runs as its own process under git's environment. GIT_DIR is set by git.
|
|
10
|
-
import fs from "node:fs";
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
import { spawnSync } from "node:child_process";
|
|
13
|
-
import { deployIssues, runsInFlight } from "./deploy.js";
|
|
14
|
-
const dir = process.env.GIT_DIR;
|
|
15
|
-
if (!dir)
|
|
16
|
-
process.exit(0);
|
|
17
|
-
// `_library.git` is the account shelf, not a workspace: it holds tools,
|
|
18
|
-
// skills and knowledge and has no agents by definition. The workspace rules
|
|
19
|
-
// refused every push to it with "no agents". Its sync (syncLibraryFromTree)
|
|
20
|
-
// validates what it accepts on receive; nothing here to add yet.
|
|
21
|
-
// Inside a hook git sets GIT_DIR relative to the repo ("."), so resolve it
|
|
22
|
-
// against the cwd before asking for its name.
|
|
23
|
-
if (path.basename(path.resolve(process.cwd(), dir)) === "_library.git")
|
|
24
|
-
process.exit(0);
|
|
25
|
-
const input = fs.readFileSync(0, "utf8");
|
|
26
|
-
const git = (args) => {
|
|
27
|
-
const r = spawnSync("git", ["--git-dir", dir, ...args], { maxBuffer: 64 * 1024 * 1024 });
|
|
28
|
-
return r.status === 0 ? r.stdout.toString() : null;
|
|
29
|
-
};
|
|
30
|
-
// The client is gone by the time the deploy runs, so this is the only moment
|
|
31
|
-
// a person can be told their push will not take effect yet. A warning, not a
|
|
32
|
-
// refusal: the commit is fine and the scheduler applies it when the runs end.
|
|
33
|
-
const here = path.basename(path.resolve(process.cwd(), dir));
|
|
34
|
-
if (here.endsWith(".git")) {
|
|
35
|
-
const workspace = here.slice(0, -4);
|
|
36
|
-
const tenant = path.basename(path.resolve(process.cwd(), dir, "..", ".."));
|
|
37
|
-
try {
|
|
38
|
-
const live = runsInFlight(tenant, workspace);
|
|
39
|
-
if (live.length) {
|
|
40
|
-
process.stderr.write(`\nfoldrun: ${live.length} run${live.length === 1 ? " is" : "s are"} in flight, so this push is not live yet.\n` +
|
|
41
|
-
` It is on main and will be applied automatically when they finish.\n\n`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// A warning that cannot be computed must never fail a push.
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
let refused = false;
|
|
49
|
-
for (const line of input.split("\n").filter(Boolean)) {
|
|
50
|
-
const [, newSha, ref] = line.split(" ");
|
|
51
|
-
if (ref !== "refs/heads/main" || /^0+$/.test(newSha))
|
|
52
|
-
continue;
|
|
53
|
-
const tree = git(["ls-tree", "-r", "--name-only", newSha]) ?? "";
|
|
54
|
-
const files = tree
|
|
55
|
-
.split("\n")
|
|
56
|
-
.filter(Boolean)
|
|
57
|
-
.flatMap((p) => {
|
|
58
|
-
const content = git(["show", `${newSha}:${p}`]);
|
|
59
|
-
return content === null ? [] : [{ path: p, content }];
|
|
60
|
-
});
|
|
61
|
-
const issues = deployIssues(files);
|
|
62
|
-
if (issues.length) {
|
|
63
|
-
refused = true;
|
|
64
|
-
process.stderr.write(`\nfoldrun: refusing this push — ${issues.length} problem${issues.length === 1 ? "" : "s"} the checks would have stopped at deploy:\n`);
|
|
65
|
-
for (const i of issues)
|
|
66
|
-
process.stderr.write(` ${i.where}: ${i.message}\n`);
|
|
67
|
-
process.stderr.write(`\nFix and push again. (foldrun check runs the same rules locally.)\n\n`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
process.exit(refused ? 1 : 0);
|
package/dist/src/git.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The secret a repository signs its pushes with.
|
|
3
|
-
*
|
|
4
|
-
* Derived rather than stored, exactly like a flow's webhook token: HMAC of the
|
|
5
|
-
* workspace's identity under the install key. Nothing new to persist, unique
|
|
6
|
-
* per workspace, and rotating FOLDRUN_SECRET_KEY invalidates every connection
|
|
7
|
-
* at once.
|
|
8
|
-
*/
|
|
9
|
-
export declare function gitSecret(tenant: string, workspace: string): string;
|
|
10
|
-
export declare function gitHookPath(tenant: string, workspace: string): string;
|
|
11
|
-
/**
|
|
12
|
-
* Whether a request really came from the repository it claims to.
|
|
13
|
-
*
|
|
14
|
-
* Compared in constant time: a byte-by-byte early return leaks the expected
|
|
15
|
-
* signature to anyone willing to time enough requests. The raw body is
|
|
16
|
-
* required — re-serializing parsed JSON changes bytes GitHub signed.
|
|
17
|
-
*/
|
|
18
|
-
export declare function verifySignature(secret: string, rawBody: string, header: string | null): boolean;
|
|
19
|
-
export interface PushEvent {
|
|
20
|
-
repo: string;
|
|
21
|
-
/** Full ref, e.g. refs/heads/main. */
|
|
22
|
-
ref: string;
|
|
23
|
-
branch: string;
|
|
24
|
-
commit: string;
|
|
25
|
-
message: string | null;
|
|
26
|
-
pusher: string | null;
|
|
27
|
-
}
|
|
28
|
-
/** What we need out of a GitHub push payload, or null if it isn't one. */
|
|
29
|
-
export declare function parsePush(payload: unknown): PushEvent | null;
|
|
30
|
-
/**
|
|
31
|
-
* The repository at one commit, as a gzipped tarball.
|
|
32
|
-
*
|
|
33
|
-
* A token is only needed for a private repository. It is read from the
|
|
34
|
-
* environment rather than taken as an argument so a caller cannot be tricked
|
|
35
|
-
* into forwarding one somewhere else.
|
|
36
|
-
*/
|
|
37
|
-
export declare function fetchTarball(repo: string, commit: string): Promise<Buffer>;
|
package/dist/src/git.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
// git push → deployed.
|
|
2
|
-
//
|
|
3
|
-
// A workspace is markdown, so there is no build to run: a push is a fetch of
|
|
4
|
-
// the tree at that commit, a check, and a swap. This module is the GitHub half
|
|
5
|
-
// — the secret a repository signs with, the payload shape, and pulling the
|
|
6
|
-
// tarball. `deploy.ts` decides whether the result is allowed to go live.
|
|
7
|
-
//
|
|
8
|
-
// The tarball, not a clone: one HTTP call, no git binary, no working copy kept
|
|
9
|
-
// on the server. A markdown workspace is a few hundred kilobytes.
|
|
10
|
-
import crypto from "node:crypto";
|
|
11
|
-
import { installKey } from "./webhook.js";
|
|
12
|
-
/** GitHub refuses to fetch anything larger, and neither should we. */
|
|
13
|
-
const MAX_TARBALL = 25 * 1024 * 1024;
|
|
14
|
-
/**
|
|
15
|
-
* The secret a repository signs its pushes with.
|
|
16
|
-
*
|
|
17
|
-
* Derived rather than stored, exactly like a flow's webhook token: HMAC of the
|
|
18
|
-
* workspace's identity under the install key. Nothing new to persist, unique
|
|
19
|
-
* per workspace, and rotating FOLDRUN_SECRET_KEY invalidates every connection
|
|
20
|
-
* at once.
|
|
21
|
-
*/
|
|
22
|
-
export function gitSecret(tenant, workspace) {
|
|
23
|
-
return crypto
|
|
24
|
-
.createHmac("sha256", installKey())
|
|
25
|
-
.update(`git/${tenant}/${workspace}`)
|
|
26
|
-
.digest("hex");
|
|
27
|
-
}
|
|
28
|
-
export function gitHookPath(tenant, workspace) {
|
|
29
|
-
return `/api/git/${tenant}/${workspace}`;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Whether a request really came from the repository it claims to.
|
|
33
|
-
*
|
|
34
|
-
* Compared in constant time: a byte-by-byte early return leaks the expected
|
|
35
|
-
* signature to anyone willing to time enough requests. The raw body is
|
|
36
|
-
* required — re-serializing parsed JSON changes bytes GitHub signed.
|
|
37
|
-
*/
|
|
38
|
-
export function verifySignature(secret, rawBody, header) {
|
|
39
|
-
if (!header)
|
|
40
|
-
return false;
|
|
41
|
-
const expected = `sha256=${crypto.createHmac("sha256", secret).update(rawBody).digest("hex")}`;
|
|
42
|
-
const a = Buffer.from(expected);
|
|
43
|
-
const b = Buffer.from(header);
|
|
44
|
-
return a.length === b.length && crypto.timingSafeEqual(a, b);
|
|
45
|
-
}
|
|
46
|
-
/** What we need out of a GitHub push payload, or null if it isn't one. */
|
|
47
|
-
export function parsePush(payload) {
|
|
48
|
-
if (!payload || typeof payload !== "object")
|
|
49
|
-
return null;
|
|
50
|
-
const p = payload;
|
|
51
|
-
const repo = p.repository?.full_name;
|
|
52
|
-
const ref = p.ref;
|
|
53
|
-
const commit = p.after;
|
|
54
|
-
if (typeof repo !== "string" || typeof ref !== "string" || typeof commit !== "string") {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
// A deleted branch reports all zeros and ships no tree.
|
|
58
|
-
if (/^0+$/.test(commit))
|
|
59
|
-
return null;
|
|
60
|
-
return {
|
|
61
|
-
repo,
|
|
62
|
-
ref,
|
|
63
|
-
branch: ref.replace(/^refs\/heads\//, ""),
|
|
64
|
-
commit,
|
|
65
|
-
message: typeof p.head_commit?.message === "string" ? p.head_commit.message : null,
|
|
66
|
-
pusher: typeof p.pusher?.name === "string" ? p.pusher.name : null,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* The repository at one commit, as a gzipped tarball.
|
|
71
|
-
*
|
|
72
|
-
* A token is only needed for a private repository. It is read from the
|
|
73
|
-
* environment rather than taken as an argument so a caller cannot be tricked
|
|
74
|
-
* into forwarding one somewhere else.
|
|
75
|
-
*/
|
|
76
|
-
export async function fetchTarball(repo, commit) {
|
|
77
|
-
if (!/^[\w.-]+\/[\w.-]+$/.test(repo))
|
|
78
|
-
throw new Error(`refusing to fetch "${repo}"`);
|
|
79
|
-
if (!/^[0-9a-f]{7,40}$/i.test(commit))
|
|
80
|
-
throw new Error(`refusing to fetch commit "${commit}"`);
|
|
81
|
-
const headers = {
|
|
82
|
-
accept: "application/vnd.github+json",
|
|
83
|
-
"user-agent": "foldrun",
|
|
84
|
-
"x-github-api-version": "2022-11-28",
|
|
85
|
-
};
|
|
86
|
-
const token = process.env.GITHUB_TOKEN;
|
|
87
|
-
if (token)
|
|
88
|
-
headers.authorization = `Bearer ${token}`;
|
|
89
|
-
const res = await fetch(`https://api.github.com/repos/${repo}/tarball/${commit}`, { headers });
|
|
90
|
-
if (!res.ok) {
|
|
91
|
-
throw new Error(`GitHub returned ${res.status} for ${repo}@${commit.slice(0, 8)}` +
|
|
92
|
-
(res.status === 404 && !token
|
|
93
|
-
? " — a private repository needs GITHUB_TOKEN set on the server"
|
|
94
|
-
: ""));
|
|
95
|
-
}
|
|
96
|
-
const buf = Buffer.from(await res.arrayBuffer());
|
|
97
|
-
if (buf.length > MAX_TARBALL) {
|
|
98
|
-
throw new Error(`${repo} is ${Math.round(buf.length / 1e6)}MB — too large to deploy`);
|
|
99
|
-
}
|
|
100
|
-
return buf;
|
|
101
|
-
}
|
package/dist/src/gitrepo.d.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import type { DiffOp, Revision, RevisionFile, RevisionSummary } from "./history.ts";
|
|
2
|
-
/** Where a workspace's bare repository lives. `@library` is the account shelf. */
|
|
3
|
-
export declare function repoDir(tenant: string, scope: string): string;
|
|
4
|
-
/** Is there a git binary? Checked once; an install without one keeps the journal. */
|
|
5
|
-
export declare function gitAvailable(): boolean;
|
|
6
|
-
export declare function repoExists(tenant: string, scope: string): boolean;
|
|
7
|
-
/** Create the repository if it is not there. Idempotent. */
|
|
8
|
-
export declare function ensureRepo(tenant: string, scope: string): string;
|
|
9
|
-
export declare function headSha(tenant: string, scope: string, ref?: string): string | null;
|
|
10
|
-
/**
|
|
11
|
-
* Apply a change set as one commit on main, using plumbing against a scratch
|
|
12
|
-
* index seeded from HEAD. No working copy, no checkout: each changed path is
|
|
13
|
-
* hashed straight from its content into the object store.
|
|
14
|
-
*
|
|
15
|
-
* Returns the new commit, or null when the tree did not change.
|
|
16
|
-
*/
|
|
17
|
-
export declare function commitChanges(tenant: string, scope: string, files: RevisionFile[], meta?: {
|
|
18
|
-
by?: string;
|
|
19
|
-
message?: string;
|
|
20
|
-
at?: string;
|
|
21
|
-
}): string | null;
|
|
22
|
-
/** Commits on main, newest first, as the same summary the journal gave. */
|
|
23
|
-
export declare function listCommits(tenant: string, scope: string, opts?: {
|
|
24
|
-
forPath?: string;
|
|
25
|
-
limit?: number;
|
|
26
|
-
ref?: string;
|
|
27
|
-
}): RevisionSummary[];
|
|
28
|
-
/** One commit in full: every changed file with its before and after. */
|
|
29
|
-
export declare function readCommit(tenant: string, scope: string, sha: string, forPath?: string): Revision | null;
|
|
30
|
-
export interface RefInfo {
|
|
31
|
-
name: string;
|
|
32
|
-
sha: string;
|
|
33
|
-
/** Commit subject and time, for a branch list that says what each is. */
|
|
34
|
-
subject: string;
|
|
35
|
-
at: string;
|
|
36
|
-
}
|
|
37
|
-
export declare function listRefs(tenant: string, scope: string, kind: "heads" | "tags"): RefInfo[];
|
|
38
|
-
/**
|
|
39
|
-
* The commit a restore means by `to`, on main's history:
|
|
40
|
-
*
|
|
41
|
-
* a sha or a prefix of one that commit
|
|
42
|
-
* a branch or tag name where it points
|
|
43
|
-
* an ISO date or time the last commit on main at or before it
|
|
44
|
-
* "3d", "12h", "90m", "2w" the same, that long ago
|
|
45
|
-
*
|
|
46
|
-
* Null when nothing matches — before the first commit, or a name that is
|
|
47
|
-
* not there. Throws on a `to` that cannot be any of these, so a typo is a
|
|
48
|
-
* 400 rather than a silent "nothing found".
|
|
49
|
-
*/
|
|
50
|
-
export declare function resolveRestorePoint(tenant: string, scope: string, to: string, now?: number): {
|
|
51
|
-
sha: string;
|
|
52
|
-
at: string;
|
|
53
|
-
by: string;
|
|
54
|
-
message: string;
|
|
55
|
-
} | null;
|
|
56
|
-
/** The files at a commit, for a browser or a deploy. */
|
|
57
|
-
export declare function listTree(tenant: string, scope: string, ref?: string): {
|
|
58
|
-
path: string;
|
|
59
|
-
mode: string;
|
|
60
|
-
}[];
|
|
61
|
-
export declare function readBlob(tenant: string, scope: string, ref: string, p: string): string | null;
|
|
62
|
-
/** The whole tree at a ref as deploy files — what a push turns into a workspace. */
|
|
63
|
-
export declare function filesAt(tenant: string, scope: string, ref: string): {
|
|
64
|
-
path: string;
|
|
65
|
-
content: string;
|
|
66
|
-
}[];
|
|
67
|
-
/**
|
|
68
|
-
* What a ref would change on main: every file that differs, with both sides.
|
|
69
|
-
* The branch's own commits are `listCommits({ ref })`; this is the net
|
|
70
|
-
* effect, which is what a reviewer reads.
|
|
71
|
-
*/
|
|
72
|
-
export declare function diffRefs(tenant: string, scope: string, base: string, head: string): RevisionFile[];
|
|
73
|
-
/**
|
|
74
|
-
* Merge a branch into main and return the new main. A fast-forward when it
|
|
75
|
-
* is one; otherwise a merge commit whose tree is the BRANCH's — the branch
|
|
76
|
-
* is what the person reviewed and chose, and a three-way textual merge of
|
|
77
|
-
* markdown with no one to resolve conflicts would deploy text nobody wrote.
|
|
78
|
-
* Both parents are recorded, so history shows the merge for what it was.
|
|
79
|
-
*/
|
|
80
|
-
export declare function mergeBranch(tenant: string, scope: string, branch: string, meta?: {
|
|
81
|
-
by?: string;
|
|
82
|
-
}): {
|
|
83
|
-
sha: string;
|
|
84
|
-
fastForward: boolean;
|
|
85
|
-
};
|
|
86
|
-
export declare function deleteBranch(tenant: string, scope: string, branch: string): void;
|
|
87
|
-
/** Push main to another remote — a GitHub or GitLab mirror. Blocking; callers
|
|
88
|
-
* run it off the request path. The token never touches disk: it is in the
|
|
89
|
-
* URL for this one process and nowhere else. */
|
|
90
|
-
export declare function pushMirror(tenant: string, scope: string, url: string): {
|
|
91
|
-
ok: boolean;
|
|
92
|
-
detail: string;
|
|
93
|
-
};
|
|
94
|
-
/**
|
|
95
|
-
* Where the compiled pre-receive check is, found on disk rather than through
|
|
96
|
-
* require.resolve — a bundler rewrites require.resolve("pkg") into a module
|
|
97
|
-
* id, and the hook was once told to run a script called "51113". Searched
|
|
98
|
-
* from the working directory upward: the monorepo layout, then an installed
|
|
99
|
-
* package. Empty when there is no compiled check (a dev server on the source
|
|
100
|
-
* tree), in which case a push is checked at deploy instead of refused — the
|
|
101
|
-
* same rules, one step later.
|
|
102
|
-
*/
|
|
103
|
-
export declare function hookScriptPath(): string;
|
|
104
|
-
/**
|
|
105
|
-
* The pre-receive hook: a push that fails the deploy checks is refused with
|
|
106
|
-
* the errors in the pusher's terminal, instead of accepted and then not
|
|
107
|
-
* deployed. The hook shells out to node with the compiled check, so it runs
|
|
108
|
-
* the same rules as a dashboard deploy; which node and which script are
|
|
109
|
-
* passed in the environment by the HTTP route, because a hook inherits git's
|
|
110
|
-
* environment and nothing else.
|
|
111
|
-
*/
|
|
112
|
-
export declare function installHooks(dir: string): void;
|
|
113
|
-
export { type DiffOp };
|