@fabricorg/cli 0.5.1 → 0.6.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/fabric.js +1801 -707
- package/dist/fabric.js.map +1 -1
- package/dist/packages/cli/tsconfig.tsbuildinfo +1 -0
- package/package.json +4 -4
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/fabric.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin/fabric.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command22 } from "commander";
|
|
5
5
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@fabricorg/cli",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.6.0",
|
|
10
10
|
description: "CLI for the Fabric AI platform",
|
|
11
11
|
keywords: [
|
|
12
12
|
"fabric",
|
|
@@ -38,7 +38,7 @@ var package_default = {
|
|
|
38
38
|
build: "tsup",
|
|
39
39
|
dev: "tsup --watch",
|
|
40
40
|
"pack:deployment": "node scripts/pack-deployment.mjs",
|
|
41
|
-
"type-check": "
|
|
41
|
+
"type-check": "tsgo --noEmit",
|
|
42
42
|
test: "vitest run",
|
|
43
43
|
"test:watch": "vitest",
|
|
44
44
|
fabric: "node --import tsx/esm src/bin/fabric.ts"
|
|
@@ -847,6 +847,32 @@ var InstructionsResource = class {
|
|
|
847
847
|
`/projects/${encodeURIComponent(projectId)}/instructions/published${buildQuery3(options)}`
|
|
848
848
|
);
|
|
849
849
|
}
|
|
850
|
+
/**
|
|
851
|
+
* The credential-free metadata for a repository that Fabric reads directly.
|
|
852
|
+
* It resolves the configured branch once but never creates a snapshot,
|
|
853
|
+
* manifest, archive or provider write.
|
|
854
|
+
*/
|
|
855
|
+
getRepositoryState(projectId, options = {}) {
|
|
856
|
+
return this.http.get(
|
|
857
|
+
`/projects/${encodeURIComponent(projectId)}/instructions/repository${buildQuery3(options)}`
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Bounded repository metadata at one commit. The first call returns the
|
|
862
|
+
* commit pin; repeat calls must carry that pin so a moving branch cannot
|
|
863
|
+
* mix tree metadata with later file bytes.
|
|
864
|
+
*/
|
|
865
|
+
listRepositoryFiles(projectId, options = {}) {
|
|
866
|
+
return this.http.get(
|
|
867
|
+
`/projects/${encodeURIComponent(projectId)}/instructions/repository/files${buildQuery3(options)}`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
/** Read one text file from an already pinned direct repository commit. */
|
|
871
|
+
getRepositoryFile(projectId, options) {
|
|
872
|
+
return this.http.get(
|
|
873
|
+
`/projects/${encodeURIComponent(projectId)}/instructions/repository/file${buildQuery3(options)}`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
850
876
|
/**
|
|
851
877
|
* Which projects use the repository a checkout was cloned from.
|
|
852
878
|
*
|
|
@@ -947,6 +973,13 @@ var InstructionsResource = class {
|
|
|
947
973
|
* against the five-per-proposer cap. The `snapshotId` a retry returns is
|
|
948
974
|
* therefore the same `snapshotId`.
|
|
949
975
|
*/
|
|
976
|
+
submitRepositoryChange(projectId, nativeBase, changes, options = {}) {
|
|
977
|
+
const { note, ...context } = options;
|
|
978
|
+
return this.http.post(
|
|
979
|
+
`/projects/${encodeURIComponent(projectId)}/instructions/changes${buildQuery3(context)}`,
|
|
980
|
+
{ nativeBase, changes, ...note ? { note } : {} }
|
|
981
|
+
);
|
|
982
|
+
}
|
|
950
983
|
submitChange(projectId, baseSnapshotId, changes, options = {}) {
|
|
951
984
|
const { note, ...context } = options;
|
|
952
985
|
return this.http.post(
|
|
@@ -2368,12 +2401,13 @@ var CALLBACK_PATH = "/callback";
|
|
|
2368
2401
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2369
2402
|
var PAGE = (title, body) => `<!doctype html><meta charset="utf-8"><title>${title}</title><body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1rem"><h1 style="font-weight:400">${title}</h1><p>${body}</p></body>`;
|
|
2370
2403
|
async function startLoopbackListener(options) {
|
|
2404
|
+
const callbackPath = options.callbackPath ?? CALLBACK_PATH;
|
|
2371
2405
|
let settle = () => {
|
|
2372
2406
|
};
|
|
2373
2407
|
const result = new Promise((resolve, reject) => {
|
|
2374
2408
|
settle = (outcome) => {
|
|
2375
2409
|
if ("code" in outcome) {
|
|
2376
|
-
resolve(
|
|
2410
|
+
resolve(outcome);
|
|
2377
2411
|
} else {
|
|
2378
2412
|
reject(outcome.error);
|
|
2379
2413
|
}
|
|
@@ -2383,13 +2417,13 @@ async function startLoopbackListener(options) {
|
|
|
2383
2417
|
});
|
|
2384
2418
|
const server = createServer((request, response) => {
|
|
2385
2419
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
2386
|
-
if (request.method !== "GET" || url.pathname !==
|
|
2420
|
+
if (request.method !== "GET" || url.pathname !== callbackPath) {
|
|
2387
2421
|
response.writeHead(404).end();
|
|
2388
2422
|
return;
|
|
2389
2423
|
}
|
|
2390
2424
|
const error = url.searchParams.get("error");
|
|
2391
2425
|
const state = url.searchParams.get("state");
|
|
2392
|
-
const code = url.searchParams.get("code");
|
|
2426
|
+
const code = url.searchParams.get(options.codeParam ?? "code");
|
|
2393
2427
|
if (state !== options.state) {
|
|
2394
2428
|
response.writeHead(400, { "Content-Type": "text/html; charset=utf-8" }).end(
|
|
2395
2429
|
PAGE(
|
|
@@ -2428,12 +2462,20 @@ async function startLoopbackListener(options) {
|
|
|
2428
2462
|
"You can close this tab and return to your terminal."
|
|
2429
2463
|
)
|
|
2430
2464
|
);
|
|
2431
|
-
settle({ code });
|
|
2465
|
+
settle({ code, params: url.searchParams });
|
|
2432
2466
|
});
|
|
2433
|
-
|
|
2467
|
+
const listen = (port2) => new Promise((resolve, reject) => {
|
|
2434
2468
|
server.once("error", reject);
|
|
2435
|
-
server.listen(
|
|
2469
|
+
server.listen(port2, "127.0.0.1", () => {
|
|
2470
|
+
server.off("error", reject);
|
|
2471
|
+
resolve();
|
|
2472
|
+
});
|
|
2436
2473
|
});
|
|
2474
|
+
if (options.preferredPort === void 0) {
|
|
2475
|
+
await listen(0);
|
|
2476
|
+
} else {
|
|
2477
|
+
await listen(options.preferredPort).catch(() => listen(0));
|
|
2478
|
+
}
|
|
2437
2479
|
const { port } = server.address();
|
|
2438
2480
|
const timer = setTimeout(
|
|
2439
2481
|
() => settle({
|
|
@@ -2451,7 +2493,7 @@ async function startLoopbackListener(options) {
|
|
|
2451
2493
|
};
|
|
2452
2494
|
result.then(close, close);
|
|
2453
2495
|
return {
|
|
2454
|
-
redirectUri: `http://127.0.0.1:${port}${
|
|
2496
|
+
redirectUri: `http://127.0.0.1:${port}${callbackPath}`,
|
|
2455
2497
|
result,
|
|
2456
2498
|
close
|
|
2457
2499
|
};
|
|
@@ -2501,6 +2543,7 @@ var REQUESTED_SCOPES = [
|
|
|
2501
2543
|
"instructions:write",
|
|
2502
2544
|
"offline_access"
|
|
2503
2545
|
];
|
|
2546
|
+
var REPOSITORY_TRANSPORT_SCOPE = "repositories:read";
|
|
2504
2547
|
var CLIENT_NAME = "Fabric CLI";
|
|
2505
2548
|
var UNUSABLE_CLIENT_ERRORS = /* @__PURE__ */ new Set([
|
|
2506
2549
|
"invalid_client",
|
|
@@ -2542,7 +2585,7 @@ function forTerminal(text3) {
|
|
|
2542
2585
|
}
|
|
2543
2586
|
return printable2;
|
|
2544
2587
|
}
|
|
2545
|
-
async function registerClient(fetchImpl, metadata, redirectUri, signal) {
|
|
2588
|
+
async function registerClient(fetchImpl, metadata, redirectUri, signal, scopes) {
|
|
2546
2589
|
const response = await fetchImpl(metadata.registration_endpoint, {
|
|
2547
2590
|
method: "POST",
|
|
2548
2591
|
headers: {
|
|
@@ -2555,8 +2598,8 @@ async function registerClient(fetchImpl, metadata, redirectUri, signal) {
|
|
|
2555
2598
|
token_endpoint_auth_method: "none",
|
|
2556
2599
|
grant_types: ["authorization_code", "refresh_token"],
|
|
2557
2600
|
response_types: ["code"],
|
|
2558
|
-
scope:
|
|
2559
|
-
|
|
2601
|
+
scope: scopes.join(" "),
|
|
2602
|
+
application_type: "native"
|
|
2560
2603
|
}),
|
|
2561
2604
|
signal
|
|
2562
2605
|
});
|
|
@@ -2634,6 +2677,7 @@ async function postTokenRequest(fetchImpl, endpoint, form, signal) {
|
|
|
2634
2677
|
async function loginWithBrowser(options) {
|
|
2635
2678
|
const fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
|
|
2636
2679
|
const now = options.now ?? Date.now;
|
|
2680
|
+
const scopes = options.scopes ?? REQUESTED_SCOPES;
|
|
2637
2681
|
const metadata = await discoverAuthorizationServer(options.baseUrl, {
|
|
2638
2682
|
fetch: fetchImpl,
|
|
2639
2683
|
signal: options.signal
|
|
@@ -2651,7 +2695,7 @@ async function loginWithBrowser(options) {
|
|
|
2651
2695
|
response_type: "code",
|
|
2652
2696
|
client_id: clientId,
|
|
2653
2697
|
redirect_uri: listener.redirectUri,
|
|
2654
|
-
scope:
|
|
2698
|
+
scope: scopes.join(" "),
|
|
2655
2699
|
state,
|
|
2656
2700
|
code_challenge: codeChallengeS256(verifier),
|
|
2657
2701
|
code_challenge_method: "S256",
|
|
@@ -2661,7 +2705,8 @@ async function loginWithBrowser(options) {
|
|
|
2661
2705
|
};
|
|
2662
2706
|
try {
|
|
2663
2707
|
const previous = options.previous;
|
|
2664
|
-
const
|
|
2708
|
+
const needsScopedRegistration = options.scopes !== void 0;
|
|
2709
|
+
const reusable = !needsScopedRegistration && previous !== void 0 && previous.tokenEndpoint === metadata.token_endpoint && sameLoopbackRedirect(previous.redirectUri, listener.redirectUri);
|
|
2665
2710
|
let clientId;
|
|
2666
2711
|
let registeredRedirectUri;
|
|
2667
2712
|
if (reusable && !await clientIsUnusable(
|
|
@@ -2676,7 +2721,8 @@ async function loginWithBrowser(options) {
|
|
|
2676
2721
|
fetchImpl,
|
|
2677
2722
|
metadata,
|
|
2678
2723
|
listener.redirectUri,
|
|
2679
|
-
options.signal
|
|
2724
|
+
options.signal,
|
|
2725
|
+
scopes
|
|
2680
2726
|
);
|
|
2681
2727
|
registeredRedirectUri = listener.redirectUri;
|
|
2682
2728
|
}
|
|
@@ -2844,6 +2890,13 @@ async function refreshAccessToken(needsRefresh, partial = {}, signal) {
|
|
|
2844
2890
|
{ lockPath: deps.lockPath, signal }
|
|
2845
2891
|
);
|
|
2846
2892
|
}
|
|
2893
|
+
async function projectAccessToken(origin, projectId, signal) {
|
|
2894
|
+
return refreshAccessToken(
|
|
2895
|
+
(current) => current.expiresAt - Date.now() <= REFRESH_WINDOW_MS,
|
|
2896
|
+
{ origin, projectId },
|
|
2897
|
+
signal
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2847
2900
|
function createOAuthFetch(partial = {}, apiFetch = (input, init) => fetch(input, init)) {
|
|
2848
2901
|
const deps = defaults(partial);
|
|
2849
2902
|
const send = (input, init, token) => {
|
|
@@ -3583,8 +3636,9 @@ function decideCheckoutVerdict(input) {
|
|
|
3583
3636
|
|
|
3584
3637
|
// src/lib/user-agent.ts
|
|
3585
3638
|
var INSTRUCTIONS_STREAMING_CAPABILITY = "instructions-stream-v1";
|
|
3639
|
+
var INSTRUCTIONS_REPOSITORY_DIRECT_CAPABILITY = "instructions-repository-direct-v1";
|
|
3586
3640
|
function cliUserAgent(version = package_default.version, nodeVersion = process.versions.node, platform = process.platform) {
|
|
3587
|
-
return `fabric-cli/${version} (node/${nodeVersion}; ${platform}; ${INSTRUCTIONS_STREAMING_CAPABILITY})`;
|
|
3641
|
+
return `fabric-cli/${version} (node/${nodeVersion}; ${platform}; ${INSTRUCTIONS_STREAMING_CAPABILITY}; ${INSTRUCTIONS_REPOSITORY_DIRECT_CAPABILITY})`;
|
|
3588
3642
|
}
|
|
3589
3643
|
var MAX_NOTICE_CHARS = 300;
|
|
3590
3644
|
var notice = null;
|
|
@@ -3954,7 +4008,8 @@ async function signInWithBrowser(input) {
|
|
|
3954
4008
|
previous: getRegisteredClient(input.origin, input.project),
|
|
3955
4009
|
openBrowser,
|
|
3956
4010
|
announce: input.announce,
|
|
3957
|
-
signal: input.signal
|
|
4011
|
+
signal: input.signal,
|
|
4012
|
+
...input.scopes === void 0 ? {} : { scopes: input.scopes }
|
|
3958
4013
|
});
|
|
3959
4014
|
if (!isIssuedBy(credentials.issuer, input.origin)) {
|
|
3960
4015
|
throw new SignInRefusedError(input.origin, "issued-elsewhere");
|
|
@@ -3978,6 +4033,10 @@ async function signInWithBrowser(input) {
|
|
|
3978
4033
|
await revokeOAuthSession(credentials);
|
|
3979
4034
|
throw new SignInRefusedError(input.origin, "rejected");
|
|
3980
4035
|
}
|
|
4036
|
+
if (input.scopes?.some((scope) => !me.scopes.includes(scope))) {
|
|
4037
|
+
await revokeOAuthSession(credentials);
|
|
4038
|
+
throw new SignInRefusedError(input.origin, "rejected");
|
|
4039
|
+
}
|
|
3981
4040
|
saveOAuth(credentials, {
|
|
3982
4041
|
...input.explicit ? { baseUrl: input.baseUrl } : { origin: input.origin },
|
|
3983
4042
|
...input.project === void 0 ? {} : { projectId: input.project }
|
|
@@ -4569,10 +4628,359 @@ function buildCompletionCommand() {
|
|
|
4569
4628
|
return completion;
|
|
4570
4629
|
}
|
|
4571
4630
|
|
|
4631
|
+
// src/commands/connect/chatgpt.ts
|
|
4632
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4633
|
+
import { Command as Command8 } from "commander";
|
|
4634
|
+
|
|
4635
|
+
// src/lib/chatgpt-plan/flow.ts
|
|
4636
|
+
import { randomBytes as randomBytes2, randomUUID as randomUUID2 } from "crypto";
|
|
4637
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
4638
|
+
import { dirname, join } from "path";
|
|
4639
|
+
var CHATGPT_ISSUER = "https://auth.openai.com";
|
|
4640
|
+
var AUTHORIZE_URL = `${CHATGPT_ISSUER}/api/accounts/authorize`;
|
|
4641
|
+
var TOKEN_URL = `${CHATGPT_ISSUER}/api/accounts/oauth/token`;
|
|
4642
|
+
var RESOURCE = "https://api.openai.com/v1";
|
|
4643
|
+
var CHATGPT_PLAN_SCOPE = "chatgpt.tokens.use.direct";
|
|
4644
|
+
var SCOPES = `openid profile email offline_access resource.invoke ${CHATGPT_PLAN_SCOPE}`;
|
|
4645
|
+
var DYNAMIC_CLIENT_ID = "dynamic_agent_client";
|
|
4646
|
+
var CHATGPT_CALLBACK_PATH = "/auth/callback";
|
|
4647
|
+
var CHATGPT_CALLBACK_PORT = 1455;
|
|
4648
|
+
var AGENT_NAME = "Fabric";
|
|
4649
|
+
function registrationPath() {
|
|
4650
|
+
return join(dirname(getConfigPath()), "chatgpt-plan-host.json");
|
|
4651
|
+
}
|
|
4652
|
+
async function loadRegistration(path29 = registrationPath()) {
|
|
4653
|
+
try {
|
|
4654
|
+
return JSON.parse(await readFile(path29, "utf8"));
|
|
4655
|
+
} catch {
|
|
4656
|
+
return { hostId: `urn:uuid:${randomUUID2()}` };
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4659
|
+
async function saveRegistration(registration, path29 = registrationPath()) {
|
|
4660
|
+
await mkdir(dirname(path29), { recursive: true, mode: 448 });
|
|
4661
|
+
await writeFile(path29, JSON.stringify(registration), { mode: 384 });
|
|
4662
|
+
}
|
|
4663
|
+
function randomToken() {
|
|
4664
|
+
return randomBytes2(24).toString("base64url");
|
|
4665
|
+
}
|
|
4666
|
+
function buildAuthorizeUrl(params) {
|
|
4667
|
+
const query = new URLSearchParams({
|
|
4668
|
+
client_id: params.clientId,
|
|
4669
|
+
ext_agent_host_id: params.hostId,
|
|
4670
|
+
response_type: "code",
|
|
4671
|
+
redirect_uri: params.redirectUri,
|
|
4672
|
+
scope: SCOPES,
|
|
4673
|
+
resource: RESOURCE,
|
|
4674
|
+
state: params.state,
|
|
4675
|
+
nonce: params.nonce,
|
|
4676
|
+
code_challenge: params.codeChallenge,
|
|
4677
|
+
code_challenge_method: "S256"
|
|
4678
|
+
});
|
|
4679
|
+
if (params.clientId === DYNAMIC_CLIENT_ID) {
|
|
4680
|
+
query.set("agent_name_hint", AGENT_NAME);
|
|
4681
|
+
}
|
|
4682
|
+
return `${AUTHORIZE_URL}?${query.toString()}`;
|
|
4683
|
+
}
|
|
4684
|
+
function issuedClientId(params, registration) {
|
|
4685
|
+
const issued = params.get("client_id") ?? registration.clientId;
|
|
4686
|
+
if (!issued || issued === DYNAMIC_CLIENT_ID) {
|
|
4687
|
+
throw new Error(
|
|
4688
|
+
"OpenAI did not issue a client id for this machine; run the command again"
|
|
4689
|
+
);
|
|
4690
|
+
}
|
|
4691
|
+
if (registration.clientId && issued !== registration.clientId) {
|
|
4692
|
+
throw new Error(
|
|
4693
|
+
"The sign-in came back for a different client than this machine's; run again with --new-registration"
|
|
4694
|
+
);
|
|
4695
|
+
}
|
|
4696
|
+
return issued;
|
|
4697
|
+
}
|
|
4698
|
+
async function exchangeCode(params, fetchImpl = fetch) {
|
|
4699
|
+
const response = await fetchImpl(TOKEN_URL, {
|
|
4700
|
+
method: "POST",
|
|
4701
|
+
headers: {
|
|
4702
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
4703
|
+
accept: "application/json"
|
|
4704
|
+
},
|
|
4705
|
+
body: new URLSearchParams({
|
|
4706
|
+
grant_type: "authorization_code",
|
|
4707
|
+
client_id: params.clientId,
|
|
4708
|
+
code: params.code,
|
|
4709
|
+
code_verifier: params.codeVerifier,
|
|
4710
|
+
redirect_uri: params.redirectUri,
|
|
4711
|
+
resource: RESOURCE
|
|
4712
|
+
}).toString(),
|
|
4713
|
+
signal: AbortSignal.timeout(15e3)
|
|
4714
|
+
});
|
|
4715
|
+
const text3 = await response.text();
|
|
4716
|
+
if (!response.ok) {
|
|
4717
|
+
let code = `HTTP ${response.status}`;
|
|
4718
|
+
try {
|
|
4719
|
+
const parsed = JSON.parse(text3);
|
|
4720
|
+
if (typeof parsed.error === "string") {
|
|
4721
|
+
code = parsed.error;
|
|
4722
|
+
}
|
|
4723
|
+
} catch {
|
|
4724
|
+
}
|
|
4725
|
+
throw new Error(`OpenAI refused the sign-in code (${code})`);
|
|
4726
|
+
}
|
|
4727
|
+
return JSON.parse(text3);
|
|
4728
|
+
}
|
|
4729
|
+
function checkIdToken(idToken, expected) {
|
|
4730
|
+
const payload = idToken.split(".")[1];
|
|
4731
|
+
if (!payload) {
|
|
4732
|
+
throw new Error("OpenAI returned a malformed ID token");
|
|
4733
|
+
}
|
|
4734
|
+
const claims = JSON.parse(
|
|
4735
|
+
Buffer.from(payload, "base64url").toString("utf8")
|
|
4736
|
+
);
|
|
4737
|
+
if (claims.nonce !== expected.nonce) {
|
|
4738
|
+
throw new Error("The ID token does not belong to this sign-in");
|
|
4739
|
+
}
|
|
4740
|
+
if (!claims.sub) {
|
|
4741
|
+
throw new Error("The ID token names no account");
|
|
4742
|
+
}
|
|
4743
|
+
if (expected.subject && claims.sub !== expected.subject) {
|
|
4744
|
+
throw new Error(
|
|
4745
|
+
"Signed in as a different ChatGPT account than this machine's; run again with --new-registration to switch"
|
|
4746
|
+
);
|
|
4747
|
+
}
|
|
4748
|
+
return { sub: claims.sub, email: claims.email };
|
|
4749
|
+
}
|
|
4750
|
+
function buildUploadPayload(tokens, registration) {
|
|
4751
|
+
if (!tokens.refresh_token || !tokens.id_token) {
|
|
4752
|
+
throw new Error("OpenAI's reply is missing the refresh or ID token");
|
|
4753
|
+
}
|
|
4754
|
+
const scopes = (tokens.scope ?? "").split(" ").filter(Boolean);
|
|
4755
|
+
if (!scopes.includes(CHATGPT_PLAN_SCOPE)) {
|
|
4756
|
+
throw new Error(
|
|
4757
|
+
"ChatGPT plan usage was not allowed at sign-in; nothing was sent to Fabric"
|
|
4758
|
+
);
|
|
4759
|
+
}
|
|
4760
|
+
return {
|
|
4761
|
+
accessToken: tokens.access_token,
|
|
4762
|
+
refreshToken: tokens.refresh_token,
|
|
4763
|
+
idToken: tokens.id_token,
|
|
4764
|
+
tokenType: tokens.token_type,
|
|
4765
|
+
expiresIn: tokens.expires_in,
|
|
4766
|
+
scopes,
|
|
4767
|
+
clientId: registration.clientId,
|
|
4768
|
+
hostId: registration.hostId,
|
|
4769
|
+
...tokens.earliest_refresh_at !== void 0 && {
|
|
4770
|
+
earliestRefreshAt: tokens.earliest_refresh_at
|
|
4771
|
+
}
|
|
4772
|
+
};
|
|
4773
|
+
}
|
|
4774
|
+
var FABRIC_APPROVAL_CALLBACK_PATH = "/fabric/callback";
|
|
4775
|
+
function buildApprovalUrl(origin, params) {
|
|
4776
|
+
const query = new URLSearchParams({
|
|
4777
|
+
port: String(params.port),
|
|
4778
|
+
state: params.state,
|
|
4779
|
+
...params.sharedOrganizationSlug !== void 0 && {
|
|
4780
|
+
shared: "1",
|
|
4781
|
+
org: params.sharedOrganizationSlug
|
|
4782
|
+
}
|
|
4783
|
+
});
|
|
4784
|
+
return `${origin}/connect/chatgpt?${query.toString()}`;
|
|
4785
|
+
}
|
|
4786
|
+
async function uploadChatGptPlan(params, fetchImpl = fetch) {
|
|
4787
|
+
const response = await fetchImpl(
|
|
4788
|
+
`${params.origin}/api/connect/chatgpt/credentials`,
|
|
4789
|
+
{
|
|
4790
|
+
method: "POST",
|
|
4791
|
+
headers: {
|
|
4792
|
+
"content-type": "application/json",
|
|
4793
|
+
authorization: `Bearer ${params.ticket}`
|
|
4794
|
+
},
|
|
4795
|
+
body: JSON.stringify(params.payload),
|
|
4796
|
+
signal: AbortSignal.timeout(3e4)
|
|
4797
|
+
}
|
|
4798
|
+
);
|
|
4799
|
+
const body = await response.json().catch(() => ({}));
|
|
4800
|
+
if (!response.ok) {
|
|
4801
|
+
throw new Error(
|
|
4802
|
+
body.error ?? `Fabric refused the ChatGPT plan sign-in (HTTP ${response.status})`
|
|
4803
|
+
);
|
|
4804
|
+
}
|
|
4805
|
+
return body;
|
|
4806
|
+
}
|
|
4807
|
+
|
|
4808
|
+
// src/commands/connect/chatgpt.ts
|
|
4809
|
+
var CHATGPT_PLAN_NOT_ENABLED = "ChatGPT plan isn't enabled for any of your organizations. Ask your Fabric admin to enable it.";
|
|
4810
|
+
var CHATGPT_PLAN_SHARED_NOT_ALLOWED = "You can connect a shared ChatGPT plan account only to an organization you administer, with ChatGPT plan pooling enabled.";
|
|
4811
|
+
function connectOptionsError(opts) {
|
|
4812
|
+
if (opts.shared && !opts.org) {
|
|
4813
|
+
return "--shared needs --org <slug>: the organization the account is shared with.";
|
|
4814
|
+
}
|
|
4815
|
+
if (opts.org && !opts.shared) {
|
|
4816
|
+
return "--org is used only with --shared, to connect an account the organization shares.";
|
|
4817
|
+
}
|
|
4818
|
+
return null;
|
|
4819
|
+
}
|
|
4820
|
+
function open(url, what) {
|
|
4821
|
+
process.stdout.write(
|
|
4822
|
+
`Opening your browser to ${what}. If it does not open, visit:
|
|
4823
|
+
${url}
|
|
4824
|
+
`
|
|
4825
|
+
);
|
|
4826
|
+
try {
|
|
4827
|
+
openBrowser(url);
|
|
4828
|
+
} catch {
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
async function approveInFabric(origin, sharedOrganizationSlug) {
|
|
4832
|
+
const state = createState();
|
|
4833
|
+
const listener = await startLoopbackListener({
|
|
4834
|
+
state,
|
|
4835
|
+
callbackPath: FABRIC_APPROVAL_CALLBACK_PATH,
|
|
4836
|
+
codeParam: "ticket"
|
|
4837
|
+
});
|
|
4838
|
+
const port = Number(new URL(listener.redirectUri).port);
|
|
4839
|
+
open(
|
|
4840
|
+
buildApprovalUrl(origin, { port, state, sharedOrganizationSlug }),
|
|
4841
|
+
"approve the connection in Fabric"
|
|
4842
|
+
);
|
|
4843
|
+
try {
|
|
4844
|
+
return (await listener.result).code;
|
|
4845
|
+
} catch (err) {
|
|
4846
|
+
if (err.message.includes("chatgpt_plan_not_enabled")) {
|
|
4847
|
+
throw new Error(CHATGPT_PLAN_NOT_ENABLED);
|
|
4848
|
+
}
|
|
4849
|
+
if (err.message.includes("chatgpt_plan_shared_not_allowed")) {
|
|
4850
|
+
throw new Error(CHATGPT_PLAN_SHARED_NOT_ALLOWED);
|
|
4851
|
+
}
|
|
4852
|
+
throw err;
|
|
4853
|
+
} finally {
|
|
4854
|
+
listener.close();
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
function describeOrganizations(result) {
|
|
4858
|
+
const enabled = result.organizations.filter((org) => org.enabled);
|
|
4859
|
+
if (enabled.length === 0) {
|
|
4860
|
+
return "Choose where to use it in Fabric settings \u2192 Account \u2192 AI providers.";
|
|
4861
|
+
}
|
|
4862
|
+
return `Your own AI work in ${enabled.map((org) => org.name).join(", ")} now uses your ChatGPT plan.`;
|
|
4863
|
+
}
|
|
4864
|
+
function describeSharedAccount(result) {
|
|
4865
|
+
const account = result.email ? ` (${result.email})` : "";
|
|
4866
|
+
const { organization, created } = result.shared;
|
|
4867
|
+
return `${created ? "Connected" : "Reconnected"} the ChatGPT account${account} as a shared account of ${organization.name}. Choose which work it serves in ${organization.name}'s settings \u2192 AI providers.`;
|
|
4868
|
+
}
|
|
4869
|
+
async function connect(opts) {
|
|
4870
|
+
const optionsError = connectOptionsError(opts);
|
|
4871
|
+
if (optionsError) {
|
|
4872
|
+
printError(optionsError, 2);
|
|
4873
|
+
}
|
|
4874
|
+
const origin = normalizeOrigin(
|
|
4875
|
+
opts.baseUrl ?? getBaseUrl() ?? DEFAULT_ORIGIN
|
|
4876
|
+
);
|
|
4877
|
+
if (origin === null) {
|
|
4878
|
+
printError(BAD_BASE_URL_LINE, 2);
|
|
4879
|
+
}
|
|
4880
|
+
const sharedOrganizationSlug = opts.shared ? opts.org : void 0;
|
|
4881
|
+
let ticket;
|
|
4882
|
+
try {
|
|
4883
|
+
ticket = await approveInFabric(origin, sharedOrganizationSlug);
|
|
4884
|
+
} catch (err) {
|
|
4885
|
+
printError(err.message, 1);
|
|
4886
|
+
}
|
|
4887
|
+
const stored = await loadRegistration();
|
|
4888
|
+
let registration = stored;
|
|
4889
|
+
if (sharedOrganizationSlug) {
|
|
4890
|
+
registration = { hostId: `urn:uuid:${randomUUID3()}` };
|
|
4891
|
+
} else if (opts.newRegistration) {
|
|
4892
|
+
registration = { hostId: stored.hostId };
|
|
4893
|
+
}
|
|
4894
|
+
const verifier = createCodeVerifier();
|
|
4895
|
+
const state = createState();
|
|
4896
|
+
const nonce = randomToken();
|
|
4897
|
+
const listener = await startLoopbackListener({
|
|
4898
|
+
state,
|
|
4899
|
+
callbackPath: CHATGPT_CALLBACK_PATH,
|
|
4900
|
+
preferredPort: CHATGPT_CALLBACK_PORT
|
|
4901
|
+
});
|
|
4902
|
+
open(
|
|
4903
|
+
buildAuthorizeUrl({
|
|
4904
|
+
clientId: registration.clientId ?? DYNAMIC_CLIENT_ID,
|
|
4905
|
+
hostId: registration.hostId,
|
|
4906
|
+
redirectUri: listener.redirectUri,
|
|
4907
|
+
state,
|
|
4908
|
+
nonce,
|
|
4909
|
+
codeChallenge: codeChallengeS256(verifier)
|
|
4910
|
+
}),
|
|
4911
|
+
"sign in with ChatGPT"
|
|
4912
|
+
);
|
|
4913
|
+
let result;
|
|
4914
|
+
let clientId;
|
|
4915
|
+
let subject;
|
|
4916
|
+
try {
|
|
4917
|
+
const { code, params } = await listener.result;
|
|
4918
|
+
clientId = issuedClientId(params, registration);
|
|
4919
|
+
if (!sharedOrganizationSlug) {
|
|
4920
|
+
await saveRegistration({ ...registration, clientId });
|
|
4921
|
+
}
|
|
4922
|
+
const tokens = await exchangeCode({
|
|
4923
|
+
clientId,
|
|
4924
|
+
code,
|
|
4925
|
+
codeVerifier: verifier,
|
|
4926
|
+
redirectUri: listener.redirectUri
|
|
4927
|
+
});
|
|
4928
|
+
subject = checkIdToken(tokens.id_token ?? "", {
|
|
4929
|
+
nonce,
|
|
4930
|
+
subject: registration.subject
|
|
4931
|
+
}).sub;
|
|
4932
|
+
result = await uploadChatGptPlan({
|
|
4933
|
+
origin,
|
|
4934
|
+
ticket,
|
|
4935
|
+
payload: buildUploadPayload(tokens, {
|
|
4936
|
+
clientId,
|
|
4937
|
+
hostId: registration.hostId
|
|
4938
|
+
})
|
|
4939
|
+
});
|
|
4940
|
+
} catch (err) {
|
|
4941
|
+
printError(err.message, 1);
|
|
4942
|
+
} finally {
|
|
4943
|
+
listener.close();
|
|
4944
|
+
}
|
|
4945
|
+
if ("shared" in result) {
|
|
4946
|
+
printSuccess(describeSharedAccount(result));
|
|
4947
|
+
return;
|
|
4948
|
+
}
|
|
4949
|
+
await saveRegistration({ hostId: registration.hostId, clientId, subject });
|
|
4950
|
+
printSuccess(
|
|
4951
|
+
`Connected your ChatGPT plan${result.email ? ` (${result.email})` : ""} to Fabric.`
|
|
4952
|
+
);
|
|
4953
|
+
process.stdout.write(`${describeOrganizations(result)}
|
|
4954
|
+
`);
|
|
4955
|
+
}
|
|
4956
|
+
function buildConnectCommand() {
|
|
4957
|
+
const connectCommand = new Command8("connect").description(
|
|
4958
|
+
"Connect a personal AI subscription to Fabric"
|
|
4959
|
+
);
|
|
4960
|
+
connectCommand.addCommand(
|
|
4961
|
+
new Command8("chatgpt").description(
|
|
4962
|
+
"Approve in Fabric, sign in with ChatGPT and run your own Fabric AI work on your plan"
|
|
4963
|
+
).option(
|
|
4964
|
+
"--new-registration",
|
|
4965
|
+
"Register this machine with OpenAI again, for example to switch accounts"
|
|
4966
|
+
).option(
|
|
4967
|
+
"--base-url <url>",
|
|
4968
|
+
"Deployment to connect, such as https://example.com"
|
|
4969
|
+
).option(
|
|
4970
|
+
"--org <slug>",
|
|
4971
|
+
"With --shared: the organization to share the account with"
|
|
4972
|
+
).option(
|
|
4973
|
+
"--shared",
|
|
4974
|
+
"Connect a ChatGPT account the organization shares, instead of your own plan (organization admins)"
|
|
4975
|
+
).action(connect)
|
|
4976
|
+
);
|
|
4977
|
+
return connectCommand;
|
|
4978
|
+
}
|
|
4979
|
+
|
|
4572
4980
|
// src/commands/context/index.ts
|
|
4573
4981
|
import { lstat as lstat3 } from "fs/promises";
|
|
4574
4982
|
import path6 from "path";
|
|
4575
|
-
import { Command as
|
|
4983
|
+
import { Command as Command9 } from "commander";
|
|
4576
4984
|
|
|
4577
4985
|
// src/lib/command-boundary.ts
|
|
4578
4986
|
function outputFormatFor(command) {
|
|
@@ -4643,13 +5051,13 @@ function asCliFailure(error) {
|
|
|
4643
5051
|
import path3 from "path";
|
|
4644
5052
|
|
|
4645
5053
|
// src/lib/instructions/safe-write.ts
|
|
4646
|
-
import { createHash as createHash2, randomBytes as
|
|
5054
|
+
import { createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
|
|
4647
5055
|
import { constants } from "fs";
|
|
4648
5056
|
import {
|
|
4649
5057
|
lstat,
|
|
4650
|
-
mkdir,
|
|
4651
|
-
open,
|
|
4652
|
-
readFile,
|
|
5058
|
+
mkdir as mkdir2,
|
|
5059
|
+
open as open2,
|
|
5060
|
+
readFile as readFile2,
|
|
4653
5061
|
realpath,
|
|
4654
5062
|
rename,
|
|
4655
5063
|
unlink
|
|
@@ -4810,7 +5218,7 @@ function permissionBits(mode) {
|
|
|
4810
5218
|
return mode & 4095;
|
|
4811
5219
|
}
|
|
4812
5220
|
async function resolveDestinationRoot(destination) {
|
|
4813
|
-
await
|
|
5221
|
+
await mkdir2(destination, { recursive: true });
|
|
4814
5222
|
return realpath(destination);
|
|
4815
5223
|
}
|
|
4816
5224
|
async function assertSafeTarget(root, relativePath) {
|
|
@@ -4875,7 +5283,7 @@ async function openFileSafely(root, relativePath) {
|
|
|
4875
5283
|
}
|
|
4876
5284
|
let handle;
|
|
4877
5285
|
try {
|
|
4878
|
-
handle = await
|
|
5286
|
+
handle = await open2(
|
|
4879
5287
|
check.path,
|
|
4880
5288
|
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
|
|
4881
5289
|
);
|
|
@@ -5002,7 +5410,7 @@ async function makeParents(root, relativePath) {
|
|
|
5002
5410
|
current = path2.join(current, segment);
|
|
5003
5411
|
const stats = await lstatOrNull(current);
|
|
5004
5412
|
if (stats === null) {
|
|
5005
|
-
await
|
|
5413
|
+
await mkdir2(current).catch(async (error) => {
|
|
5006
5414
|
if (error.code !== "EEXIST") {
|
|
5007
5415
|
throw error;
|
|
5008
5416
|
}
|
|
@@ -5043,11 +5451,11 @@ async function writeFileSafely(input) {
|
|
|
5043
5451
|
const directoryBefore = await lstatOrNull(directory);
|
|
5044
5452
|
const temporary = path2.join(
|
|
5045
5453
|
directory,
|
|
5046
|
-
`.${path2.basename(target)}.${
|
|
5454
|
+
`.${path2.basename(target)}.${randomBytes3(8).toString("hex")}.tmp`
|
|
5047
5455
|
);
|
|
5048
5456
|
let handle = null;
|
|
5049
5457
|
try {
|
|
5050
|
-
handle = await
|
|
5458
|
+
handle = await open2(temporary, "wx", 384);
|
|
5051
5459
|
await handle.writeFile(input.bytes);
|
|
5052
5460
|
const bits = permissionBits(input.mode);
|
|
5053
5461
|
if (bits !== null && process.platform !== "win32") {
|
|
@@ -5106,7 +5514,7 @@ async function deleteFileSafely(root, relativePath, expectedSha256) {
|
|
|
5106
5514
|
}
|
|
5107
5515
|
let actual;
|
|
5108
5516
|
try {
|
|
5109
|
-
actual = createHash2("sha256").update(await
|
|
5517
|
+
actual = createHash2("sha256").update(await readFile2(check.path)).digest("hex");
|
|
5110
5518
|
} catch (error) {
|
|
5111
5519
|
if (error.code === "ENOENT") {
|
|
5112
5520
|
return "missing";
|
|
@@ -6528,7 +6936,7 @@ function collectPattern(value, previous) {
|
|
|
6528
6936
|
return [...previous, value];
|
|
6529
6937
|
}
|
|
6530
6938
|
function buildContextCommand() {
|
|
6531
|
-
const context = new
|
|
6939
|
+
const context = new Command9("context").description(
|
|
6532
6940
|
"Sync local knowledge files into a project's Context"
|
|
6533
6941
|
);
|
|
6534
6942
|
context.command("push").description(
|
|
@@ -6796,9 +7204,9 @@ function throwIfIncomplete(results, prune, pruneTargets) {
|
|
|
6796
7204
|
}
|
|
6797
7205
|
|
|
6798
7206
|
// src/commands/ctx/index.ts
|
|
6799
|
-
import { Command as
|
|
7207
|
+
import { Command as Command10 } from "commander";
|
|
6800
7208
|
function buildCtxCommand() {
|
|
6801
|
-
const ctx = new
|
|
7209
|
+
const ctx = new Command10("ctx").description(
|
|
6802
7210
|
"Manage the organization commands run in"
|
|
6803
7211
|
);
|
|
6804
7212
|
ctx.command("current").description("Show the active default context").action(() => {
|
|
@@ -6818,7 +7226,7 @@ function buildCtxCommand() {
|
|
|
6818
7226
|
process.stdout.write(`Context: org:${stored.slug}
|
|
6819
7227
|
`);
|
|
6820
7228
|
});
|
|
6821
|
-
const use = new
|
|
7229
|
+
const use = new Command10("use").description(
|
|
6822
7230
|
"Set the default organization for commands"
|
|
6823
7231
|
);
|
|
6824
7232
|
use.command("personal").description("Retired \u2014 context is organization-only").action(() => {
|
|
@@ -6836,9 +7244,9 @@ function buildCtxCommand() {
|
|
|
6836
7244
|
}
|
|
6837
7245
|
|
|
6838
7246
|
// src/commands/features/index.ts
|
|
6839
|
-
import { Command as
|
|
7247
|
+
import { Command as Command11 } from "commander";
|
|
6840
7248
|
function buildFeaturesCommand() {
|
|
6841
|
-
const features = new
|
|
7249
|
+
const features = new Command11("features").description(
|
|
6842
7250
|
"Manage project features"
|
|
6843
7251
|
);
|
|
6844
7252
|
features.command("list").description("List features in a project").requiredOption("-p, --project <id>", "Project ID").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
@@ -7025,9 +7433,9 @@ function buildFeaturesCommand() {
|
|
|
7025
7433
|
}
|
|
7026
7434
|
|
|
7027
7435
|
// src/commands/frames/index.ts
|
|
7028
|
-
import { Command as
|
|
7436
|
+
import { Command as Command12 } from "commander";
|
|
7029
7437
|
function buildFramesCommand() {
|
|
7030
|
-
const frames = new
|
|
7438
|
+
const frames = new Command12("frames").description(
|
|
7031
7439
|
"Manage AI-generated frames and slideshows"
|
|
7032
7440
|
);
|
|
7033
7441
|
frames.command("list").description("List frames and slideshows").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--limit <n>", "Max results (default 50)", "50").option(
|
|
@@ -7103,7 +7511,7 @@ function buildFramesCommand() {
|
|
|
7103
7511
|
// src/commands/instructions/index.ts
|
|
7104
7512
|
import { readdir as readdir3 } from "fs/promises";
|
|
7105
7513
|
import path28 from "path";
|
|
7106
|
-
import { Command as
|
|
7514
|
+
import { Command as Command13 } from "commander";
|
|
7107
7515
|
|
|
7108
7516
|
// src/lib/environment.ts
|
|
7109
7517
|
function isFlagSet(value) {
|
|
@@ -7171,7 +7579,7 @@ var STRIPPED_VARIABLES = /* @__PURE__ */ new Set([
|
|
|
7171
7579
|
"GIT_CONFIG_SYSTEM",
|
|
7172
7580
|
"GIT_CONFIG_NOSYSTEM"
|
|
7173
7581
|
]);
|
|
7174
|
-
var STRIPPED_PATTERN = /^
|
|
7582
|
+
var STRIPPED_PATTERN = /^GIT_CONFIG_(?:KEY|VALUE)_\d+$/;
|
|
7175
7583
|
var CREDENTIAL_PROGRAMS = /* @__PURE__ */ new Set(["GIT_ASKPASS", "SSH_ASKPASS"]);
|
|
7176
7584
|
var SSH_PROGRAM_CHOICES = /* @__PURE__ */ new Set(["GIT_SSH_COMMAND", "GIT_SSH"]);
|
|
7177
7585
|
function gitEnvironment(source = process.env, options = {}) {
|
|
@@ -7182,7 +7590,7 @@ function gitEnvironment(source = process.env, options = {}) {
|
|
|
7182
7590
|
env2[name] = value;
|
|
7183
7591
|
continue;
|
|
7184
7592
|
}
|
|
7185
|
-
if (STRIPPED_VARIABLES.has(upper) || STRIPPED_PATTERN.test(upper)) {
|
|
7593
|
+
if (STRIPPED_VARIABLES.has(upper) || upper.startsWith("FABRIC_") || upper.startsWith("GIT_TRACE") || STRIPPED_PATTERN.test(upper)) {
|
|
7186
7594
|
continue;
|
|
7187
7595
|
}
|
|
7188
7596
|
env2[name] = value;
|
|
@@ -7204,6 +7612,34 @@ function gitEnvironment(source = process.env, options = {}) {
|
|
|
7204
7612
|
function hookWriteEnvironment(source = process.env) {
|
|
7205
7613
|
return gitEnvironment(source, { write: true, unattended: true });
|
|
7206
7614
|
}
|
|
7615
|
+
function gatewayConfig(authorization) {
|
|
7616
|
+
if (authorization === void 0) {
|
|
7617
|
+
return { args: [], env: {} };
|
|
7618
|
+
}
|
|
7619
|
+
try {
|
|
7620
|
+
const url = new URL(authorization.url);
|
|
7621
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || authorization.authorization === "" || /[\r\n]/.test(authorization.authorization)) {
|
|
7622
|
+
return null;
|
|
7623
|
+
}
|
|
7624
|
+
const base = url.toString().replace(/\/$/, "");
|
|
7625
|
+
return {
|
|
7626
|
+
args: [
|
|
7627
|
+
"-c",
|
|
7628
|
+
`http.${base}.extraHeader=`,
|
|
7629
|
+
`--config-env=http.${base}.extraHeader=FABRIC_GIT_AUTH_HEADER`,
|
|
7630
|
+
"-c",
|
|
7631
|
+
"http.followRedirects=false",
|
|
7632
|
+
"-c",
|
|
7633
|
+
"credential.helper="
|
|
7634
|
+
],
|
|
7635
|
+
env: {
|
|
7636
|
+
FABRIC_GIT_AUTH_HEADER: `Authorization: ${authorization.authorization}`
|
|
7637
|
+
}
|
|
7638
|
+
};
|
|
7639
|
+
} catch {
|
|
7640
|
+
return null;
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7207
7643
|
function runGit(cwd, args, deadline, options = {}) {
|
|
7208
7644
|
const remaining = deadline - Date.now();
|
|
7209
7645
|
if (remaining <= 0) {
|
|
@@ -7213,6 +7649,14 @@ function runGit(cwd, args, deadline, options = {}) {
|
|
|
7213
7649
|
});
|
|
7214
7650
|
}
|
|
7215
7651
|
return new Promise((resolve) => {
|
|
7652
|
+
const gateway = gatewayConfig(options.httpAuthorization);
|
|
7653
|
+
if (gateway === null) {
|
|
7654
|
+
resolve({
|
|
7655
|
+
kind: "unavailable",
|
|
7656
|
+
reason: "invalid Fabric Git transport"
|
|
7657
|
+
});
|
|
7658
|
+
return;
|
|
7659
|
+
}
|
|
7216
7660
|
let settled = false;
|
|
7217
7661
|
const finish = (result) => {
|
|
7218
7662
|
if (!settled) {
|
|
@@ -7226,6 +7670,7 @@ function runGit(cwd, args, deadline, options = {}) {
|
|
|
7226
7670
|
child = spawn2(
|
|
7227
7671
|
"git",
|
|
7228
7672
|
[
|
|
7673
|
+
...gateway.args,
|
|
7229
7674
|
// Windows Git can report modified long-path files from its file
|
|
7230
7675
|
// system cache even when Git's diff is empty. This command-local
|
|
7231
7676
|
// setting keeps inspection truthful without changing the user's
|
|
@@ -7238,7 +7683,10 @@ function runGit(cwd, args, deadline, options = {}) {
|
|
|
7238
7683
|
],
|
|
7239
7684
|
{
|
|
7240
7685
|
cwd,
|
|
7241
|
-
env:
|
|
7686
|
+
env: {
|
|
7687
|
+
...options.write && options.unattended ? hookWriteEnvironment(process.env) : gitEnvironment(process.env, options),
|
|
7688
|
+
...gateway.env
|
|
7689
|
+
},
|
|
7242
7690
|
stdio: ["ignore", "pipe", "pipe"],
|
|
7243
7691
|
shell: false,
|
|
7244
7692
|
windowsHide: true
|
|
@@ -7250,9 +7698,13 @@ function runGit(cwd, args, deadline, options = {}) {
|
|
|
7250
7698
|
}
|
|
7251
7699
|
const out = [];
|
|
7252
7700
|
let outBytes = 0;
|
|
7701
|
+
let outOverflow = false;
|
|
7253
7702
|
const err = [];
|
|
7254
7703
|
let errBytes = 0;
|
|
7255
7704
|
child.stdout?.on("data", (chunk) => {
|
|
7705
|
+
if (chunk.length > STDOUT_CAP_BYTES - outBytes) {
|
|
7706
|
+
outOverflow = true;
|
|
7707
|
+
}
|
|
7256
7708
|
if (outBytes < STDOUT_CAP_BYTES) {
|
|
7257
7709
|
const room = STDOUT_CAP_BYTES - outBytes;
|
|
7258
7710
|
out.push(chunk.subarray(0, room));
|
|
@@ -7280,6 +7732,13 @@ function runGit(cwd, args, deadline, options = {}) {
|
|
|
7280
7732
|
});
|
|
7281
7733
|
child.on("close", (code) => {
|
|
7282
7734
|
clearTimeout(escalate);
|
|
7735
|
+
if (outOverflow) {
|
|
7736
|
+
finish({
|
|
7737
|
+
kind: "unavailable",
|
|
7738
|
+
reason: "git output exceeded the supported size"
|
|
7739
|
+
});
|
|
7740
|
+
return;
|
|
7741
|
+
}
|
|
7283
7742
|
finish({
|
|
7284
7743
|
kind: "exited",
|
|
7285
7744
|
code: code ?? -1,
|
|
@@ -7340,6 +7799,37 @@ async function simple(root, args, deadline) {
|
|
|
7340
7799
|
};
|
|
7341
7800
|
}
|
|
7342
7801
|
|
|
7802
|
+
// src/lib/instructions/git-diff.ts
|
|
7803
|
+
async function changedTrackedPaths(root, deadline) {
|
|
7804
|
+
const result = await simple(
|
|
7805
|
+
root,
|
|
7806
|
+
[
|
|
7807
|
+
"diff",
|
|
7808
|
+
"--name-only",
|
|
7809
|
+
"--no-renames",
|
|
7810
|
+
"--no-ext-diff",
|
|
7811
|
+
"--no-textconv",
|
|
7812
|
+
"--relative",
|
|
7813
|
+
"-z",
|
|
7814
|
+
"HEAD",
|
|
7815
|
+
"--",
|
|
7816
|
+
"."
|
|
7817
|
+
],
|
|
7818
|
+
deadline
|
|
7819
|
+
);
|
|
7820
|
+
if (result.kind !== "ok") return result;
|
|
7821
|
+
if (result.value.code !== 0) {
|
|
7822
|
+
return {
|
|
7823
|
+
kind: "unavailable",
|
|
7824
|
+
reason: "Git could not compare this checkout to HEAD"
|
|
7825
|
+
};
|
|
7826
|
+
}
|
|
7827
|
+
return {
|
|
7828
|
+
kind: "ok",
|
|
7829
|
+
value: result.value.stdout.split("\0").filter(Boolean)
|
|
7830
|
+
};
|
|
7831
|
+
}
|
|
7832
|
+
|
|
7343
7833
|
// src/lib/instructions/git-write.ts
|
|
7344
7834
|
function fetchFailureOf(stderr) {
|
|
7345
7835
|
if (/\(non-fast-forward\)|\[rejected\]|cannot fast-forward/i.test(stderr)) {
|
|
@@ -7371,6 +7861,9 @@ function mergeFailureOf(stderr) {
|
|
|
7371
7861
|
)) {
|
|
7372
7862
|
return "busy";
|
|
7373
7863
|
}
|
|
7864
|
+
if (/would be overwritten by merge/i.test(stderr) && /local changes|untracked working tree files/i.test(stderr)) {
|
|
7865
|
+
return "local-changes";
|
|
7866
|
+
}
|
|
7374
7867
|
if (/Not possible to fast-forward|refusing to merge unrelated histories|diverging branches/i.test(
|
|
7375
7868
|
stderr
|
|
7376
7869
|
)) {
|
|
@@ -7378,6 +7871,20 @@ function mergeFailureOf(stderr) {
|
|
|
7378
7871
|
}
|
|
7379
7872
|
return "other";
|
|
7380
7873
|
}
|
|
7874
|
+
function blockedFilesOf(stderr) {
|
|
7875
|
+
const files = [];
|
|
7876
|
+
let listing = false;
|
|
7877
|
+
for (const line2 of stderr.split(/\r?\n/)) {
|
|
7878
|
+
if (/would be overwritten by merge:\s*$/i.test(line2)) {
|
|
7879
|
+
listing = true;
|
|
7880
|
+
} else if (listing && /^\s+\S/.test(line2)) {
|
|
7881
|
+
files.push(line2.trim());
|
|
7882
|
+
} else {
|
|
7883
|
+
listing = false;
|
|
7884
|
+
}
|
|
7885
|
+
}
|
|
7886
|
+
return files;
|
|
7887
|
+
}
|
|
7381
7888
|
async function fetchRef(root, remote, ref, deadline) {
|
|
7382
7889
|
if (!isRemoteName(remote) || !isBranchLiteral(ref)) {
|
|
7383
7890
|
return { kind: "unavailable", reason: "not a branch this will fetch" };
|
|
@@ -7419,6 +7926,47 @@ async function fetchRef(root, remote, ref, deadline) {
|
|
|
7419
7926
|
}
|
|
7420
7927
|
return { kind: "fetched", tip: sha };
|
|
7421
7928
|
}
|
|
7929
|
+
async function fetchRefFromUrl(root, url, remote, ref, deadline, httpAuthorization) {
|
|
7930
|
+
if (!isRemoteName(remote) || !isBranchLiteral(ref)) {
|
|
7931
|
+
return { kind: "unavailable", reason: "not a branch this will fetch" };
|
|
7932
|
+
}
|
|
7933
|
+
const tracking = `refs/remotes/${remote}/${ref}`;
|
|
7934
|
+
const fetched = await runGit(
|
|
7935
|
+
root,
|
|
7936
|
+
[
|
|
7937
|
+
"-c",
|
|
7938
|
+
"gc.auto=0",
|
|
7939
|
+
"-c",
|
|
7940
|
+
"maintenance.auto=false",
|
|
7941
|
+
"fetch",
|
|
7942
|
+
"--no-recurse-submodules",
|
|
7943
|
+
"--",
|
|
7944
|
+
url,
|
|
7945
|
+
`refs/heads/${ref}:${tracking}`
|
|
7946
|
+
],
|
|
7947
|
+
deadline,
|
|
7948
|
+
{ write: true, unattended: true, httpAuthorization }
|
|
7949
|
+
);
|
|
7950
|
+
if (fetched.kind === "unavailable") {
|
|
7951
|
+
return fetched.reason === "git timed out" ? { kind: "timed-out" } : { kind: "unavailable", reason: fetched.reason };
|
|
7952
|
+
}
|
|
7953
|
+
if (fetched.code !== 0) {
|
|
7954
|
+
return { kind: "failed", reason: fetchFailureOf(fetched.stderr) };
|
|
7955
|
+
}
|
|
7956
|
+
const tip = await runGit(
|
|
7957
|
+
root,
|
|
7958
|
+
["rev-parse", "--verify", "-q", `${tracking}^{commit}`],
|
|
7959
|
+
deadline
|
|
7960
|
+
);
|
|
7961
|
+
if (tip.kind === "unavailable") {
|
|
7962
|
+
return tip.reason === "git timed out" ? { kind: "timed-out" } : { kind: "unavailable", reason: tip.reason };
|
|
7963
|
+
}
|
|
7964
|
+
const [sha] = lines(tip.stdout);
|
|
7965
|
+
if (tip.code !== 0 || !sha || !COMMIT_SHA.test(sha)) {
|
|
7966
|
+
return { kind: "failed", reason: "missing-ref" };
|
|
7967
|
+
}
|
|
7968
|
+
return { kind: "fetched", tip: sha };
|
|
7969
|
+
}
|
|
7422
7970
|
async function fastForwardTo(root, sha, deadline) {
|
|
7423
7971
|
if (!COMMIT_SHA.test(sha)) {
|
|
7424
7972
|
return { kind: "unavailable", reason: "not a commit name" };
|
|
@@ -7433,7 +7981,8 @@ async function fastForwardTo(root, sha, deadline) {
|
|
|
7433
7981
|
return merged.reason === "git timed out" ? { kind: "timed-out" } : { kind: "unavailable", reason: merged.reason };
|
|
7434
7982
|
}
|
|
7435
7983
|
if (merged.code !== 0) {
|
|
7436
|
-
|
|
7984
|
+
const reason = mergeFailureOf(merged.stderr);
|
|
7985
|
+
return reason === "local-changes" ? { kind: "failed", reason, files: blockedFilesOf(merged.stderr) } : { kind: "failed", reason };
|
|
7437
7986
|
}
|
|
7438
7987
|
const head = await runGit(
|
|
7439
7988
|
root,
|
|
@@ -7460,7 +8009,6 @@ var OPERATION_MARKERS = [
|
|
|
7460
8009
|
// HEAD markers above name which one while a step is stopped.
|
|
7461
8010
|
{ marker: "sequencer", operation: "cherry-pick" }
|
|
7462
8011
|
];
|
|
7463
|
-
var OWN_PATHS = [".claude/settings.local.json", ".codex/hooks.json"];
|
|
7464
8012
|
async function nearestExistingDirectory(dir) {
|
|
7465
8013
|
let current = path7.resolve(dir);
|
|
7466
8014
|
for (; ; ) {
|
|
@@ -7627,28 +8175,26 @@ async function isAncestor(root, ancestorSha, descendantSha, deadline) {
|
|
|
7627
8175
|
return { kind: "unavailable", reason: "the commit is not in this clone" };
|
|
7628
8176
|
}
|
|
7629
8177
|
async function isClean(root, deadline) {
|
|
7630
|
-
const
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
"--
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
deadline
|
|
7651
|
-
);
|
|
8178
|
+
for (const scope of [[], ["--cached"]]) {
|
|
8179
|
+
const result = await simple(
|
|
8180
|
+
root,
|
|
8181
|
+
["diff", "--quiet", "--ignore-submodules=none", ...scope],
|
|
8182
|
+
deadline
|
|
8183
|
+
);
|
|
8184
|
+
if (result.kind !== "ok") {
|
|
8185
|
+
return result;
|
|
8186
|
+
}
|
|
8187
|
+
if (result.value.code === 1) {
|
|
8188
|
+
return { kind: "ok", value: false };
|
|
8189
|
+
}
|
|
8190
|
+
if (result.value.code !== 0) {
|
|
8191
|
+
return {
|
|
8192
|
+
kind: "unavailable",
|
|
8193
|
+
reason: `git exited with status ${result.value.code}`
|
|
8194
|
+
};
|
|
8195
|
+
}
|
|
8196
|
+
}
|
|
8197
|
+
return { kind: "ok", value: true };
|
|
7652
8198
|
}
|
|
7653
8199
|
async function trackedPaths(root, relativePaths, deadline) {
|
|
7654
8200
|
if (relativePaths.length === 0) {
|
|
@@ -7721,23 +8267,6 @@ async function checkoutAppearsIncomplete(root, deadline) {
|
|
|
7721
8267
|
value: headTree.value.stdout !== "" && staged.value.code === 1 && untracked.value.stdout !== ""
|
|
7722
8268
|
};
|
|
7723
8269
|
}
|
|
7724
|
-
async function porcelainIsEmpty(root, args, deadline) {
|
|
7725
|
-
const result = await simple(
|
|
7726
|
-
root,
|
|
7727
|
-
["status", "--porcelain=v1", "--ignore-submodules=none", ...args],
|
|
7728
|
-
deadline
|
|
7729
|
-
);
|
|
7730
|
-
if (result.kind !== "ok") {
|
|
7731
|
-
return result;
|
|
7732
|
-
}
|
|
7733
|
-
if (result.value.code !== 0) {
|
|
7734
|
-
return {
|
|
7735
|
-
kind: "unavailable",
|
|
7736
|
-
reason: `git exited with status ${result.value.code}`
|
|
7737
|
-
};
|
|
7738
|
-
}
|
|
7739
|
-
return { kind: "ok", value: result.value.stdout.trim() === "" };
|
|
7740
|
-
}
|
|
7741
8270
|
async function operationInProgress(root, deadline) {
|
|
7742
8271
|
const result = await simple(
|
|
7743
8272
|
root,
|
|
@@ -7964,8 +8493,9 @@ function cloneFailureOf(stderr) {
|
|
|
7964
8493
|
}
|
|
7965
8494
|
return "other";
|
|
7966
8495
|
}
|
|
7967
|
-
async function cloneInto(dir, url, ref, deadline) {
|
|
7968
|
-
|
|
8496
|
+
async function cloneInto(dir, url, ref, deadline, options = {}) {
|
|
8497
|
+
const validUrl = options.httpAuthorization ? options.httpAuthorization.url === url : cloneableUrl(url) === url;
|
|
8498
|
+
if (!validUrl || !isBranchLiteral(ref)) {
|
|
7969
8499
|
return {
|
|
7970
8500
|
kind: "unavailable",
|
|
7971
8501
|
reason: "not a repository this will clone"
|
|
@@ -7990,13 +8520,28 @@ async function cloneInto(dir, url, ref, deadline) {
|
|
|
7990
8520
|
"."
|
|
7991
8521
|
],
|
|
7992
8522
|
deadline,
|
|
7993
|
-
{ write: true }
|
|
8523
|
+
{ write: true, ...options }
|
|
7994
8524
|
);
|
|
7995
8525
|
if (result.kind === "unavailable") {
|
|
7996
8526
|
return { kind: "unavailable", reason: result.reason };
|
|
7997
8527
|
}
|
|
7998
8528
|
return result.code === 0 ? { kind: "cloned" } : { kind: "failed", reason: cloneFailureOf(result.stderr) };
|
|
7999
8529
|
}
|
|
8530
|
+
async function setRemoteUrl(root, remote, url, deadline) {
|
|
8531
|
+
if (!isRemoteName(remote) || cloneableUrl(url) !== url) {
|
|
8532
|
+
return { kind: "unavailable", reason: "not a repository remote" };
|
|
8533
|
+
}
|
|
8534
|
+
const result = await runGit(
|
|
8535
|
+
root,
|
|
8536
|
+
["remote", "set-url", remote, url],
|
|
8537
|
+
deadline,
|
|
8538
|
+
{ write: true }
|
|
8539
|
+
);
|
|
8540
|
+
if (result.kind === "unavailable") {
|
|
8541
|
+
return result;
|
|
8542
|
+
}
|
|
8543
|
+
return result.code === 0 ? { kind: "ok", value: void 0 } : { kind: "unavailable", reason: "git refused the provider remote" };
|
|
8544
|
+
}
|
|
8000
8545
|
|
|
8001
8546
|
// src/lib/instructions/outcome.ts
|
|
8002
8547
|
var HOOK_PREFIX = "fabric: coding instructions";
|
|
@@ -8005,6 +8550,15 @@ var TRAIT_TEXT = {
|
|
|
8005
8550
|
sparse: " (sparse checkout)",
|
|
8006
8551
|
superproject: " (inside a superproject)"
|
|
8007
8552
|
};
|
|
8553
|
+
var MAX_BLOCKED_FILES = 3;
|
|
8554
|
+
function blockedFilesText(files) {
|
|
8555
|
+
if (files.length === 0) {
|
|
8556
|
+
return "";
|
|
8557
|
+
}
|
|
8558
|
+
const named = files.slice(0, MAX_BLOCKED_FILES).map(shown).join(", ");
|
|
8559
|
+
const more = files.length - MAX_BLOCKED_FILES;
|
|
8560
|
+
return ` (${named}${more > 0 ? ` and ${more} more` : ""})`;
|
|
8561
|
+
}
|
|
8008
8562
|
function shown(value) {
|
|
8009
8563
|
return sanitizeDisplayText(value, 255);
|
|
8010
8564
|
}
|
|
@@ -8154,8 +8708,10 @@ var LINES = {
|
|
|
8154
8708
|
return reason;
|
|
8155
8709
|
}
|
|
8156
8710
|
},
|
|
8157
|
-
"ff-merge-failed": ({ reason, ref, remote, commands }) => {
|
|
8711
|
+
"ff-merge-failed": ({ reason, files, ref, remote, commands }) => {
|
|
8158
8712
|
switch (reason) {
|
|
8713
|
+
case "local-changes":
|
|
8714
|
+
return `${HOOK_PREFIX}: ${ref} is behind ${remote}/${ref}, but local changes would be overwritten${blockedFilesText(files ?? [])}, so nothing was updated. Commit or stash them, then run: ${commands.pull}`;
|
|
8159
8715
|
case "diverged":
|
|
8160
8716
|
return `${HOOK_PREFIX}: ${ref} and ${remote}/${ref} have diverged, so nothing was updated. Run: ${commands.rebase}, or merge ${remote}/${ref} yourself.`;
|
|
8161
8717
|
case "timeout":
|
|
@@ -8956,7 +9512,7 @@ function adoptionFor(input) {
|
|
|
8956
9512
|
}
|
|
8957
9513
|
|
|
8958
9514
|
// src/lib/instructions/agent-mcp-config.ts
|
|
8959
|
-
import { readFile as
|
|
9515
|
+
import { readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
8960
9516
|
import path10 from "path";
|
|
8961
9517
|
var MAX_CONFIG_BYTES = 16 * 1024 * 1024;
|
|
8962
9518
|
async function readText(file) {
|
|
@@ -8965,7 +9521,7 @@ async function readText(file) {
|
|
|
8965
9521
|
if (!info.isFile() || info.size > MAX_CONFIG_BYTES) {
|
|
8966
9522
|
return { kind: "unusable" };
|
|
8967
9523
|
}
|
|
8968
|
-
return { kind: "text", text: await
|
|
9524
|
+
return { kind: "text", text: await readFile3(file, "utf8") };
|
|
8969
9525
|
} catch (error) {
|
|
8970
9526
|
return error.code === "ENOENT" ? { kind: "absent" } : { kind: "unusable" };
|
|
8971
9527
|
}
|
|
@@ -9106,7 +9662,7 @@ async function readRegistration(input) {
|
|
|
9106
9662
|
|
|
9107
9663
|
// src/lib/instructions/hook.ts
|
|
9108
9664
|
import { createHash as createHash4 } from "crypto";
|
|
9109
|
-
import { mkdir as
|
|
9665
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
9110
9666
|
import { tmpdir } from "os";
|
|
9111
9667
|
import path11 from "path";
|
|
9112
9668
|
|
|
@@ -9139,7 +9695,7 @@ function subcommandsForEvent(event) {
|
|
|
9139
9695
|
}
|
|
9140
9696
|
async function withHookConfigLock(root, posixPath, work) {
|
|
9141
9697
|
const directory = path11.join(tmpdir(), "fabric-instructions-hook-locks");
|
|
9142
|
-
await
|
|
9698
|
+
await mkdir3(directory, { recursive: true });
|
|
9143
9699
|
const identity = createHash4("sha256").update(path11.resolve(root)).update("\0").update(posixPath).digest("hex");
|
|
9144
9700
|
const lockPath2 = path11.join(directory, `${identity}.lock`);
|
|
9145
9701
|
try {
|
|
@@ -10041,7 +10597,7 @@ import { createHash as createHash5 } from "crypto";
|
|
|
10041
10597
|
import { chmod } from "fs/promises";
|
|
10042
10598
|
|
|
10043
10599
|
// src/lib/instructions/lock.ts
|
|
10044
|
-
import { readFile as
|
|
10600
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
10045
10601
|
import path14 from "path";
|
|
10046
10602
|
var LOCK_DIRECTORY = ".fabric";
|
|
10047
10603
|
var LOCK_FILENAME = "instructions.lock";
|
|
@@ -10056,7 +10612,7 @@ async function readLock(destination) {
|
|
|
10056
10612
|
const file = lockPath(destination);
|
|
10057
10613
|
let raw;
|
|
10058
10614
|
try {
|
|
10059
|
-
raw = await
|
|
10615
|
+
raw = await readFile4(file, "utf8");
|
|
10060
10616
|
} catch (error) {
|
|
10061
10617
|
if (error.code === "ENOENT") {
|
|
10062
10618
|
return null;
|
|
@@ -13158,7 +13714,7 @@ function replacePath(text3, target, replacement) {
|
|
|
13158
13714
|
}
|
|
13159
13715
|
|
|
13160
13716
|
// src/lib/instructions/fast-forward-run.ts
|
|
13161
|
-
import { mkdir as
|
|
13717
|
+
import { mkdir as mkdir6, realpath as realpath4 } from "fs/promises";
|
|
13162
13718
|
import path21 from "path";
|
|
13163
13719
|
|
|
13164
13720
|
// src/lib/instructions/fast-forward.ts
|
|
@@ -13192,9 +13748,6 @@ function fastForwardEligibility(facts) {
|
|
|
13192
13748
|
if (facts.upstream !== `${remote}/${ref}`) {
|
|
13193
13749
|
return no("upstream-mismatch");
|
|
13194
13750
|
}
|
|
13195
|
-
if (!state.clean) {
|
|
13196
|
-
return no("dirty");
|
|
13197
|
-
}
|
|
13198
13751
|
if (facts.lockFiles) {
|
|
13199
13752
|
return no("git-busy");
|
|
13200
13753
|
}
|
|
@@ -13313,11 +13866,12 @@ function fastForwardLines(outcome, context) {
|
|
|
13313
13866
|
case "merge-failed": {
|
|
13314
13867
|
const text3 = outcomeLine("ff-merge-failed", {
|
|
13315
13868
|
reason: outcome.reason,
|
|
13869
|
+
files: outcome.files,
|
|
13316
13870
|
ref: context.ref,
|
|
13317
13871
|
remote: context.remote,
|
|
13318
13872
|
commands: context.commands
|
|
13319
13873
|
});
|
|
13320
|
-
if (outcome.reason === "diverged") {
|
|
13874
|
+
if (outcome.reason === "diverged" || outcome.reason === "local-changes") {
|
|
13321
13875
|
stdout.push(text3);
|
|
13322
13876
|
otherLine();
|
|
13323
13877
|
} else {
|
|
@@ -13351,7 +13905,7 @@ function fastForwardLines(outcome, context) {
|
|
|
13351
13905
|
}
|
|
13352
13906
|
|
|
13353
13907
|
// src/lib/instructions/fast-forward-memory.ts
|
|
13354
|
-
import { mkdir as
|
|
13908
|
+
import { mkdir as mkdir4, readFile as readFile5, rename as rename2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
13355
13909
|
import path19 from "path";
|
|
13356
13910
|
function noticeFile(commonDir2) {
|
|
13357
13911
|
return path19.join(commonDir2, "fabric", "ff-notice.json");
|
|
@@ -13361,22 +13915,22 @@ function isRecord4(value) {
|
|
|
13361
13915
|
return false;
|
|
13362
13916
|
}
|
|
13363
13917
|
const record = value;
|
|
13364
|
-
return record.v === 1 && typeof record.projectId === "string" && typeof record.publishedVersion === "number" && typeof record.reason === "string";
|
|
13918
|
+
return record.v === 1 && typeof record.projectId === "string" && typeof record.publishedVersion === "number" && (record.directCommitSha === void 0 || typeof record.directCommitSha === "string") && typeof record.reason === "string";
|
|
13365
13919
|
}
|
|
13366
13920
|
async function isNewNotice(file, key) {
|
|
13367
13921
|
try {
|
|
13368
|
-
const parsed = JSON.parse(await
|
|
13369
|
-
return !(isRecord4(parsed) && parsed.projectId === key.projectId && parsed.publishedVersion === key.publishedVersion && parsed.reason === key.reason);
|
|
13922
|
+
const parsed = JSON.parse(await readFile5(file, "utf8"));
|
|
13923
|
+
return !(isRecord4(parsed) && parsed.projectId === key.projectId && parsed.publishedVersion === key.publishedVersion && parsed.directCommitSha === key.directCommitSha && parsed.reason === key.reason);
|
|
13370
13924
|
} catch {
|
|
13371
13925
|
return true;
|
|
13372
13926
|
}
|
|
13373
13927
|
}
|
|
13374
13928
|
async function rememberNotice(file, key) {
|
|
13375
13929
|
try {
|
|
13376
|
-
await
|
|
13930
|
+
await mkdir4(path19.dirname(file), { recursive: true });
|
|
13377
13931
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
13378
13932
|
const record = { v: 1, ...key };
|
|
13379
|
-
await
|
|
13933
|
+
await writeFile2(temporary, `${JSON.stringify(record)}
|
|
13380
13934
|
`, {
|
|
13381
13935
|
mode: 384
|
|
13382
13936
|
});
|
|
@@ -13397,7 +13951,7 @@ var hookTiming = {
|
|
|
13397
13951
|
};
|
|
13398
13952
|
|
|
13399
13953
|
// src/lib/instructions/hook-trace.ts
|
|
13400
|
-
import { mkdir as
|
|
13954
|
+
import { mkdir as mkdir5, readFile as readFile6, rename as rename3, writeFile as writeFile3 } from "fs/promises";
|
|
13401
13955
|
import path20 from "path";
|
|
13402
13956
|
var TRACE_LIMIT = 50;
|
|
13403
13957
|
function traceFile(configDir) {
|
|
@@ -13405,7 +13959,7 @@ function traceFile(configDir) {
|
|
|
13405
13959
|
}
|
|
13406
13960
|
async function appendTrace(file, entry) {
|
|
13407
13961
|
try {
|
|
13408
|
-
const previous = await
|
|
13962
|
+
const previous = await readFile6(file, "utf8").catch(() => "");
|
|
13409
13963
|
const kept = previous.split("\n").filter((line2) => {
|
|
13410
13964
|
try {
|
|
13411
13965
|
return typeof JSON.parse(line2)?.outcome === "string";
|
|
@@ -13414,9 +13968,9 @@ async function appendTrace(file, entry) {
|
|
|
13414
13968
|
}
|
|
13415
13969
|
}).slice(-(TRACE_LIMIT - 1));
|
|
13416
13970
|
kept.push(JSON.stringify(entry));
|
|
13417
|
-
await
|
|
13971
|
+
await mkdir5(path20.dirname(file), { recursive: true, mode: 448 });
|
|
13418
13972
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
13419
|
-
await
|
|
13973
|
+
await writeFile3(temporary, `${kept.join("\n")}
|
|
13420
13974
|
`, { mode: 384 });
|
|
13421
13975
|
await rename3(temporary, file);
|
|
13422
13976
|
} catch {
|
|
@@ -13425,6 +13979,7 @@ async function appendTrace(file, entry) {
|
|
|
13425
13979
|
|
|
13426
13980
|
// src/lib/instructions/fast-forward-run.ts
|
|
13427
13981
|
var MERGE_RESERVE_MS = 1500;
|
|
13982
|
+
var MERGE_FLOOR_MS = 500;
|
|
13428
13983
|
var FF_LOCK_STALE_MS = 3e4;
|
|
13429
13984
|
var show2 = (value, max = 200) => sanitizeDisplayText(value, max);
|
|
13430
13985
|
async function samePath(a, b) {
|
|
@@ -13476,8 +14031,19 @@ async function readFacts(run3, deadline) {
|
|
|
13476
14031
|
heldElsewhere
|
|
13477
14032
|
};
|
|
13478
14033
|
}
|
|
13479
|
-
function unanswered(deadline, head) {
|
|
13480
|
-
return Date.now() >= deadline ? { outcome: { kind: "deadline" }, head } : { outcome: { kind: "not-safe", reason: "git-busy" }, head };
|
|
14034
|
+
function unanswered(deadline, head, stage) {
|
|
14035
|
+
return Date.now() >= deadline ? { outcome: { kind: "deadline", stage }, head } : { outcome: { kind: "not-safe", reason: "git-busy" }, head };
|
|
14036
|
+
}
|
|
14037
|
+
async function tipRelation(root, head, tip, deadline) {
|
|
14038
|
+
if (tip === head) {
|
|
14039
|
+
return "current";
|
|
14040
|
+
}
|
|
14041
|
+
const forward = await isAncestor(root, head, tip, deadline);
|
|
14042
|
+
if (forward.kind !== "ok" || forward.value) {
|
|
14043
|
+
return "ahead";
|
|
14044
|
+
}
|
|
14045
|
+
const behind = await isAncestor(root, tip, head, deadline);
|
|
14046
|
+
return behind.kind === "ok" && behind.value ? "current" : "diverged";
|
|
13481
14047
|
}
|
|
13482
14048
|
async function underLock(run3, first) {
|
|
13483
14049
|
const { classification, repository, deadline } = run3;
|
|
@@ -13486,7 +14052,7 @@ async function underLock(run3, first) {
|
|
|
13486
14052
|
const ref = repository.ref;
|
|
13487
14053
|
const facts = await readFacts(run3, deadline);
|
|
13488
14054
|
if (facts === null) {
|
|
13489
|
-
return unanswered(deadline, first.state.head);
|
|
14055
|
+
return unanswered(deadline, first.state.head, "read");
|
|
13490
14056
|
}
|
|
13491
14057
|
const gate = fastForwardEligibility(facts);
|
|
13492
14058
|
if (!gate.eligible) {
|
|
@@ -13504,12 +14070,19 @@ async function underLock(run3, first) {
|
|
|
13504
14070
|
}
|
|
13505
14071
|
const fetchDeadline = deadline - MERGE_RESERVE_MS;
|
|
13506
14072
|
if (Date.now() >= fetchDeadline) {
|
|
13507
|
-
return { outcome: { kind: "deadline" }, head };
|
|
14073
|
+
return { outcome: { kind: "deadline", stage: "fetch" }, head };
|
|
13508
14074
|
}
|
|
13509
|
-
const fetched = await
|
|
14075
|
+
const fetched = run3.gitTransport ? await fetchRefFromUrl(
|
|
14076
|
+
root,
|
|
14077
|
+
run3.gitTransport.url,
|
|
14078
|
+
remote,
|
|
14079
|
+
ref,
|
|
14080
|
+
fetchDeadline,
|
|
14081
|
+
run3.gitTransport
|
|
14082
|
+
) : await fetchRef(root, remote, ref, fetchDeadline);
|
|
13510
14083
|
switch (fetched.kind) {
|
|
13511
14084
|
case "timed-out":
|
|
13512
|
-
return { outcome: { kind: "deadline" }, head };
|
|
14085
|
+
return { outcome: { kind: "deadline", stage: "fetch" }, head };
|
|
13513
14086
|
case "unavailable":
|
|
13514
14087
|
return {
|
|
13515
14088
|
outcome: { kind: "fetch-failed", reason: "other" },
|
|
@@ -13532,30 +14105,26 @@ async function underLock(run3, first) {
|
|
|
13532
14105
|
return fetched;
|
|
13533
14106
|
}
|
|
13534
14107
|
const tip = fetched.tip;
|
|
13535
|
-
const
|
|
13536
|
-
|
|
13537
|
-
|
|
14108
|
+
const relation = await tipRelation(root, head, tip, deadline);
|
|
14109
|
+
const changed = await factsStillMatch(run3, facts, deadline);
|
|
14110
|
+
if (changed !== null) {
|
|
14111
|
+
return changed;
|
|
13538
14112
|
}
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
return { outcome: { kind: "deadline" }, head };
|
|
13552
|
-
}
|
|
13553
|
-
const beforeMerge = await factsStillMatch(run3, facts, deadline);
|
|
13554
|
-
if (beforeMerge !== null) {
|
|
13555
|
-
return beforeMerge;
|
|
14113
|
+
switch (relation) {
|
|
14114
|
+
case "current":
|
|
14115
|
+
return { outcome: { kind: "already-current" }, head };
|
|
14116
|
+
case "diverged":
|
|
14117
|
+
return {
|
|
14118
|
+
outcome: { kind: "merge-failed", reason: "diverged" },
|
|
14119
|
+
head
|
|
14120
|
+
};
|
|
14121
|
+
case "ahead":
|
|
14122
|
+
break;
|
|
14123
|
+
default:
|
|
14124
|
+
return relation;
|
|
13556
14125
|
}
|
|
13557
|
-
if (deadline - Date.now() <
|
|
13558
|
-
return { outcome: { kind: "deadline" }, head };
|
|
14126
|
+
if (deadline - Date.now() < MERGE_FLOOR_MS) {
|
|
14127
|
+
return { outcome: { kind: "deadline", stage: "merge" }, head };
|
|
13559
14128
|
}
|
|
13560
14129
|
const merged = await fastForwardTo(root, tip, deadline);
|
|
13561
14130
|
switch (merged.kind) {
|
|
@@ -13583,7 +14152,11 @@ async function underLock(run3, first) {
|
|
|
13583
14152
|
};
|
|
13584
14153
|
}
|
|
13585
14154
|
return {
|
|
13586
|
-
outcome: {
|
|
14155
|
+
outcome: {
|
|
14156
|
+
kind: "merge-failed",
|
|
14157
|
+
reason: merged.reason,
|
|
14158
|
+
...merged.files === void 0 ? {} : { files: merged.files }
|
|
14159
|
+
},
|
|
13587
14160
|
head
|
|
13588
14161
|
};
|
|
13589
14162
|
default:
|
|
@@ -13593,7 +14166,7 @@ async function underLock(run3, first) {
|
|
|
13593
14166
|
async function factsStillMatch(run3, expected, deadline) {
|
|
13594
14167
|
const facts = await readFacts(run3, deadline);
|
|
13595
14168
|
if (facts === null) {
|
|
13596
|
-
return unanswered(deadline, expected.state.head);
|
|
14169
|
+
return unanswered(deadline, expected.state.head, "merge");
|
|
13597
14170
|
}
|
|
13598
14171
|
const gate = fastForwardEligibility(facts);
|
|
13599
14172
|
if (!gate.eligible) {
|
|
@@ -13619,7 +14192,7 @@ async function decide(run3) {
|
|
|
13619
14192
|
}
|
|
13620
14193
|
const facts = await readFacts(run3, deadline);
|
|
13621
14194
|
if (facts === null) {
|
|
13622
|
-
return unanswered(deadline, head);
|
|
14195
|
+
return unanswered(deadline, head, "read");
|
|
13623
14196
|
}
|
|
13624
14197
|
const gate = fastForwardEligibility(facts);
|
|
13625
14198
|
if (!gate.eligible) {
|
|
@@ -13630,11 +14203,11 @@ async function decide(run3) {
|
|
|
13630
14203
|
}
|
|
13631
14204
|
const common = await commonDir(root, deadline);
|
|
13632
14205
|
if (common.kind !== "ok") {
|
|
13633
|
-
return unanswered(deadline, head);
|
|
14206
|
+
return unanswered(deadline, head, "read");
|
|
13634
14207
|
}
|
|
13635
14208
|
const directory = path21.join(common.value, "fabric");
|
|
13636
14209
|
try {
|
|
13637
|
-
await
|
|
14210
|
+
await mkdir6(directory, { recursive: true });
|
|
13638
14211
|
return await withExclusiveLock(() => underLock(run3, facts), {
|
|
13639
14212
|
lockPath: path21.join(directory, "ff.lock"),
|
|
13640
14213
|
staleMs: FF_LOCK_STALE_MS,
|
|
@@ -13660,6 +14233,8 @@ function reasonOf(outcome) {
|
|
|
13660
14233
|
case "fetch-failed":
|
|
13661
14234
|
case "merge-failed":
|
|
13662
14235
|
return outcome.reason;
|
|
14236
|
+
case "deadline":
|
|
14237
|
+
return outcome.stage;
|
|
13663
14238
|
default:
|
|
13664
14239
|
return null;
|
|
13665
14240
|
}
|
|
@@ -13668,8 +14243,8 @@ function noticeReason(outcome, lag) {
|
|
|
13668
14243
|
if (outcome.kind === "not-safe") {
|
|
13669
14244
|
return `not-safe:${outcome.reason}`;
|
|
13670
14245
|
}
|
|
13671
|
-
if (outcome.kind === "merge-failed" && outcome.reason === "diverged") {
|
|
13672
|
-
return
|
|
14246
|
+
if (outcome.kind === "merge-failed" && (outcome.reason === "diverged" || outcome.reason === "local-changes")) {
|
|
14247
|
+
return `merge-failed:${outcome.reason}`;
|
|
13673
14248
|
}
|
|
13674
14249
|
if ((outcome.kind === "fast-forwarded" || outcome.kind === "already-current") && lag !== null) {
|
|
13675
14250
|
return `fabric-lags:${lag}`;
|
|
@@ -13694,7 +14269,8 @@ async function runFastForward(run3) {
|
|
|
13694
14269
|
const decision = await decide(run3);
|
|
13695
14270
|
const { outcome } = decision;
|
|
13696
14271
|
const source = snapshot?.source;
|
|
13697
|
-
const
|
|
14272
|
+
const snapshotSha = source?.kind === "REPOSITORY" && source.current && isCommitSha(source.commitSha) ? source.commitSha : null;
|
|
14273
|
+
const publishedSha = run3.directCommitSha ?? snapshotSha;
|
|
13698
14274
|
const moved = outcome.kind === "fast-forwarded" || outcome.kind === "already-current";
|
|
13699
14275
|
const head = outcome.kind === "fast-forwarded" ? outcome.to : decision.head;
|
|
13700
14276
|
let publishedInHistory = null;
|
|
@@ -13707,7 +14283,7 @@ async function runFastForward(run3) {
|
|
|
13707
14283
|
);
|
|
13708
14284
|
publishedInHistory = answer.kind === "ok" ? answer.value : null;
|
|
13709
14285
|
}
|
|
13710
|
-
const lag = moved ? fabricCopyLag({
|
|
14286
|
+
const lag = moved && snapshot !== void 0 ? fabricCopyLag({
|
|
13711
14287
|
headSha: head,
|
|
13712
14288
|
publishedSha,
|
|
13713
14289
|
publishedInHistory,
|
|
@@ -13744,6 +14320,7 @@ async function runFastForward(run3) {
|
|
|
13744
14320
|
const key = {
|
|
13745
14321
|
projectId: run3.projectId,
|
|
13746
14322
|
publishedVersion: snapshot ? Number(snapshot.version) : 0,
|
|
14323
|
+
...run3.directCommitSha === void 0 ? {} : { directCommitSha: run3.directCommitSha },
|
|
13747
14324
|
reason
|
|
13748
14325
|
};
|
|
13749
14326
|
if (await isNewNotice(notices, key)) {
|
|
@@ -13766,7 +14343,7 @@ async function runFastForward(run3) {
|
|
|
13766
14343
|
}
|
|
13767
14344
|
|
|
13768
14345
|
// src/lib/instructions/git-exclude.ts
|
|
13769
|
-
import { appendFile, lstat as lstat6, mkdir as
|
|
14346
|
+
import { appendFile, lstat as lstat6, mkdir as mkdir7, readFile as readFile7 } from "fs/promises";
|
|
13770
14347
|
import path22 from "path";
|
|
13771
14348
|
function relativeToTopLevel(toplevel, directory, file) {
|
|
13772
14349
|
return path22.relative(toplevel, path22.join(directory, file)).split(path22.sep).join("/");
|
|
@@ -13803,7 +14380,7 @@ async function excludeLocalFiles(input) {
|
|
|
13803
14380
|
if (file.kind !== "ok") {
|
|
13804
14381
|
return { kind: "failed", entries: missing };
|
|
13805
14382
|
}
|
|
13806
|
-
await
|
|
14383
|
+
await mkdir7(path22.dirname(file.value), { recursive: true });
|
|
13807
14384
|
const existing = await lstat6(file.value).catch((error) => {
|
|
13808
14385
|
if (error.code === "ENOENT") {
|
|
13809
14386
|
return null;
|
|
@@ -13813,7 +14390,7 @@ async function excludeLocalFiles(input) {
|
|
|
13813
14390
|
if (existing !== null && !existing.isFile()) {
|
|
13814
14391
|
return { kind: "failed", entries: missing };
|
|
13815
14392
|
}
|
|
13816
|
-
const text3 = existing === null ? "" : await
|
|
14393
|
+
const text3 = existing === null ? "" : await readFile7(file.value, "utf8");
|
|
13817
14394
|
const present = new Set(text3.split(/\r?\n/));
|
|
13818
14395
|
const toAdd = missing.filter((entry) => !present.has(entry));
|
|
13819
14396
|
if (toAdd.length === 0) {
|
|
@@ -13829,7 +14406,7 @@ async function excludeLocalFiles(input) {
|
|
|
13829
14406
|
}
|
|
13830
14407
|
|
|
13831
14408
|
// src/lib/instructions/hook-launcher.ts
|
|
13832
|
-
import { mkdir as
|
|
14409
|
+
import { mkdir as mkdir8, readFile as readFile8, rename as rename4, rm as rm3, writeFile as writeFile4 } from "fs/promises";
|
|
13833
14410
|
import path23 from "path";
|
|
13834
14411
|
var NOT_ON_PATH_WARNING = "`fabric` is not on this machine's PATH, so this session hook will not run. Run the setup line from the project's Connect dialog instead: it keeps a copy of this deployment's CLI where the hook finds it.";
|
|
13835
14412
|
var COPY_IN_USE_WARNING = "The CLI copy the session hook runs is in use and could not be replaced, so the earlier copy was kept. Run this again when no coding session is starting.";
|
|
@@ -13850,15 +14427,15 @@ function samePath2(a, b) {
|
|
|
13850
14427
|
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
13851
14428
|
}
|
|
13852
14429
|
async function writeCopy(bytes, destination) {
|
|
13853
|
-
await
|
|
14430
|
+
await mkdir8(path23.dirname(destination), { recursive: true });
|
|
13854
14431
|
const temporary = `${destination}.${process.pid}.tmp`;
|
|
13855
|
-
await
|
|
14432
|
+
await writeFile4(temporary, bytes, { mode: 420 });
|
|
13856
14433
|
try {
|
|
13857
14434
|
await rename4(temporary, destination);
|
|
13858
14435
|
return true;
|
|
13859
14436
|
} catch (error) {
|
|
13860
14437
|
await rm3(temporary, { force: true });
|
|
13861
|
-
const existing = await
|
|
14438
|
+
const existing = await readFile8(destination).catch(() => null);
|
|
13862
14439
|
if (existing === null) {
|
|
13863
14440
|
throw error;
|
|
13864
14441
|
}
|
|
@@ -13881,7 +14458,7 @@ async function resolveHookLauncher(input) {
|
|
|
13881
14458
|
return { prefix: `node ${word}`, warning: null };
|
|
13882
14459
|
}
|
|
13883
14460
|
const current = await writeCopy(
|
|
13884
|
-
await
|
|
14461
|
+
await readFile8(input.bundle.scriptPath),
|
|
13885
14462
|
destination
|
|
13886
14463
|
);
|
|
13887
14464
|
return {
|
|
@@ -13934,7 +14511,7 @@ async function installHooks(input) {
|
|
|
13934
14511
|
// src/lib/instructions/lesson-prompt.ts
|
|
13935
14512
|
import { createHash as createHash11 } from "crypto";
|
|
13936
14513
|
import { createReadStream } from "fs";
|
|
13937
|
-
import { mkdir as
|
|
14514
|
+
import { mkdir as mkdir9, readdir as readdir2, stat as stat5, unlink as unlink2, writeFile as writeFile5 } from "fs/promises";
|
|
13938
14515
|
import path24 from "path";
|
|
13939
14516
|
import { createInterface } from "readline";
|
|
13940
14517
|
function parseStopHookInput(text3) {
|
|
@@ -14084,7 +14661,7 @@ async function runLessonPromptInner(input) {
|
|
|
14084
14661
|
return { output: null, why: "stop-hook-active" };
|
|
14085
14662
|
}
|
|
14086
14663
|
try {
|
|
14087
|
-
await
|
|
14664
|
+
await mkdir9(input.markerDir, { recursive: true });
|
|
14088
14665
|
} catch (error) {
|
|
14089
14666
|
debugLog(`marker directory unavailable: ${describeError(error)}`);
|
|
14090
14667
|
return { output: null, why: "marker-unavailable" };
|
|
@@ -14108,7 +14685,7 @@ async function runLessonPromptInner(input) {
|
|
|
14108
14685
|
return { output: null, why: "no-edits" };
|
|
14109
14686
|
}
|
|
14110
14687
|
try {
|
|
14111
|
-
await
|
|
14688
|
+
await writeFile5(markerPath, "", { flag: "wx" });
|
|
14112
14689
|
} catch (error) {
|
|
14113
14690
|
debugLog(`could not write marker: ${describeError(error)}`);
|
|
14114
14691
|
return { output: null, why: "marker-unavailable" };
|
|
@@ -14143,287 +14720,72 @@ var machine = {
|
|
|
14143
14720
|
platform: () => process.platform
|
|
14144
14721
|
};
|
|
14145
14722
|
|
|
14146
|
-
// src/lib/instructions/
|
|
14147
|
-
|
|
14148
|
-
|
|
14149
|
-
|
|
14150
|
-
|
|
14151
|
-
|
|
14152
|
-
|
|
14153
|
-
|
|
14154
|
-
|
|
14155
|
-
|
|
14156
|
-
|
|
14157
|
-
|
|
14158
|
-
|
|
14159
|
-
CLOSE_REQUESTED: true,
|
|
14160
|
-
BLOCKED: true,
|
|
14161
|
-
MERGED: true,
|
|
14162
|
-
CLOSED: true,
|
|
14163
|
-
CANCELED: true
|
|
14164
|
-
};
|
|
14165
|
-
function isKnown(known, value) {
|
|
14166
|
-
return typeof value === "string" && Object.hasOwn(known, value);
|
|
14167
|
-
}
|
|
14168
|
-
var OpenProposalDeadline = class extends Error {
|
|
14169
|
-
constructor(ms) {
|
|
14170
|
-
super(`it did not answer within ${Math.ceil(ms / 1e3)} s`);
|
|
14171
|
-
this.name = "OpenProposalDeadline";
|
|
14172
|
-
}
|
|
14173
|
-
};
|
|
14174
|
-
async function lookUpOpenProposals(client, projectId, options = {}) {
|
|
14175
|
-
const ms = openProposalTiming.deadlineMs;
|
|
14176
|
-
const controller = new AbortController();
|
|
14177
|
-
const timer = setTimeout(
|
|
14178
|
-
() => controller.abort(new OpenProposalDeadline(ms)),
|
|
14179
|
-
ms
|
|
14180
|
-
);
|
|
14181
|
-
let answer;
|
|
14182
|
-
try {
|
|
14183
|
-
answer = await client.instructions.getOpenProposals(projectId, {
|
|
14184
|
-
org: options.org,
|
|
14185
|
-
signal: controller.signal
|
|
14186
|
-
});
|
|
14187
|
-
} catch (error) {
|
|
14188
|
-
if (controller.signal.aborted) {
|
|
14189
|
-
return {
|
|
14190
|
-
kind: "unavailable",
|
|
14191
|
-
reason: describeError(controller.signal.reason)
|
|
14192
|
-
};
|
|
14193
|
-
}
|
|
14194
|
-
if (error.status === 404) {
|
|
14195
|
-
return {
|
|
14196
|
-
kind: "unavailable",
|
|
14197
|
-
reason: "this Fabric server does not list open proposals yet"
|
|
14198
|
-
};
|
|
14199
|
-
}
|
|
14200
|
-
const status = error?.status;
|
|
14201
|
-
return {
|
|
14202
|
-
kind: "unavailable",
|
|
14203
|
-
reason: typeof status === "number" && status > 0 ? `the server answered ${status}` : "the request did not go through"
|
|
14204
|
-
};
|
|
14205
|
-
} finally {
|
|
14206
|
-
clearTimeout(timer);
|
|
14207
|
-
}
|
|
14208
|
-
if (!Array.isArray(answer) || !answer.every(isOpenProposal)) {
|
|
14209
|
-
return {
|
|
14210
|
-
kind: "unavailable",
|
|
14211
|
-
reason: "the server's answer was not a list of proposals"
|
|
14212
|
-
};
|
|
14723
|
+
// src/lib/instructions/native-push-plan.ts
|
|
14724
|
+
import { createHash as createHash13 } from "crypto";
|
|
14725
|
+
|
|
14726
|
+
// src/lib/instructions/push.ts
|
|
14727
|
+
import { createHash as createHash12 } from "crypto";
|
|
14728
|
+
var MAX_PUSH_CHANGES = 50;
|
|
14729
|
+
var MAX_INLINE_PUSH_BYTES = 2 * 1024 * 1024;
|
|
14730
|
+
function assertPushablePath(candidate, source) {
|
|
14731
|
+
const check = checkRelativePath(candidate);
|
|
14732
|
+
if (!check.ok) {
|
|
14733
|
+
throw new Error(
|
|
14734
|
+
`Refusing to push: ${describeRejection(check)}. ${source}`
|
|
14735
|
+
);
|
|
14213
14736
|
}
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14737
|
+
if (isReservedPath(candidate)) {
|
|
14738
|
+
throw new Error(
|
|
14739
|
+
`Refusing to push: ${describeRejection({
|
|
14740
|
+
ok: false,
|
|
14741
|
+
reason: "reserved_path",
|
|
14742
|
+
detail: candidate
|
|
14743
|
+
})}. ${source}`
|
|
14744
|
+
);
|
|
14219
14745
|
}
|
|
14220
|
-
const proposal = value;
|
|
14221
|
-
const pullRequest = proposal.pullRequest;
|
|
14222
|
-
return typeof proposal.snapshotId === "string" && typeof proposal.version === "number" && typeof proposal.baseSnapshotId === "string" && isKnown(SNAPSHOT_STATUSES, proposal.status) && (pullRequest === null || typeof pullRequest === "object" && pullRequest !== void 0 && isKnown(PULL_REQUEST_STATES, pullRequest.state) && (pullRequest.url === null || typeof pullRequest.url === "string")) && Array.isArray(proposal.changes) && proposal.changes.every(isChange);
|
|
14223
14746
|
}
|
|
14224
|
-
function
|
|
14225
|
-
|
|
14226
|
-
|
|
14747
|
+
function encodeContent(bytes) {
|
|
14748
|
+
const text3 = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
14749
|
+
const roundTrip = new TextEncoder().encode(text3);
|
|
14750
|
+
if (roundTrip.length === bytes.length && roundTrip.every((byte, index) => byte === bytes[index])) {
|
|
14751
|
+
return { content: text3, encoding: "utf8" };
|
|
14227
14752
|
}
|
|
14228
|
-
|
|
14229
|
-
|
|
14753
|
+
return {
|
|
14754
|
+
content: Buffer.from(bytes).toString("base64"),
|
|
14755
|
+
encoding: "base64"
|
|
14756
|
+
};
|
|
14230
14757
|
}
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
import { createInterface as createInterface2 } from "readline/promises";
|
|
14234
|
-
function canPrompt(streams = {
|
|
14235
|
-
stdin: process.stdin,
|
|
14236
|
-
stderr: process.stderr
|
|
14237
|
-
}) {
|
|
14238
|
-
return streams.stdin?.isTTY === true && streams.stderr?.isTTY === true;
|
|
14758
|
+
function sha256Of(bytes) {
|
|
14759
|
+
return createHash12("sha256").update(bytes).digest("hex");
|
|
14239
14760
|
}
|
|
14240
|
-
|
|
14241
|
-
|
|
14242
|
-
|
|
14243
|
-
|
|
14244
|
-
});
|
|
14245
|
-
try {
|
|
14246
|
-
return (await reader.question(question)).trim();
|
|
14247
|
-
} finally {
|
|
14248
|
-
reader.close();
|
|
14249
|
-
}
|
|
14761
|
+
function refuseStaleLedger(detail) {
|
|
14762
|
+
throw new Error(
|
|
14763
|
+
`Refusing to push: the sync lock does not match the published version \u2014 ${detail}. Run \`${fabricCommand("instructions sync")}\` and try again. Nothing was read and nothing was sent.`
|
|
14764
|
+
);
|
|
14250
14765
|
}
|
|
14251
|
-
|
|
14252
|
-
|
|
14253
|
-
|
|
14254
|
-
`
|
|
14766
|
+
function assertLockMatchesManifest(lock, manifest) {
|
|
14767
|
+
const published = new Map(
|
|
14768
|
+
manifest.map((entry) => [entry.path, entry.sha256])
|
|
14255
14769
|
);
|
|
14256
|
-
for (const [
|
|
14257
|
-
|
|
14258
|
-
|
|
14770
|
+
for (const [path29, entry] of Object.entries(lock.files)) {
|
|
14771
|
+
const expected = published.get(path29);
|
|
14772
|
+
if (expected === void 0) {
|
|
14773
|
+
refuseStaleLedger(
|
|
14774
|
+
`it lists ${path29}, which version ${lock.snapshotVersion} does not contain`
|
|
14775
|
+
);
|
|
14776
|
+
}
|
|
14777
|
+
if (expected !== entry.sha256) {
|
|
14778
|
+
refuseStaleLedger(
|
|
14779
|
+
`its record of ${path29} is not the published file`
|
|
14780
|
+
);
|
|
14781
|
+
}
|
|
14259
14782
|
}
|
|
14260
|
-
const
|
|
14261
|
-
|
|
14262
|
-
|
|
14263
|
-
}
|
|
14264
|
-
|
|
14265
|
-
|
|
14266
|
-
}
|
|
14267
|
-
|
|
14268
|
-
// src/lib/instructions/pull-request-wait.ts
|
|
14269
|
-
var PULL_REQUEST_POLL_MS = 2e3;
|
|
14270
|
-
var PULL_REQUEST_WAIT_MS = 6e4;
|
|
14271
|
-
var SETTLED_STATES = /* @__PURE__ */ new Set([
|
|
14272
|
-
"OPEN",
|
|
14273
|
-
"BLOCKED",
|
|
14274
|
-
"MERGED",
|
|
14275
|
-
"CLOSED",
|
|
14276
|
-
"CANCELED",
|
|
14277
|
-
"CLOSE_REQUESTED"
|
|
14278
|
-
]);
|
|
14279
|
-
var STOPPED_BRANCH_STATES = /* @__PURE__ */ new Set([
|
|
14280
|
-
"BLOCKED",
|
|
14281
|
-
"MERGED",
|
|
14282
|
-
"CLOSED",
|
|
14283
|
-
"CANCELED",
|
|
14284
|
-
"CLOSE_REQUESTED"
|
|
14285
|
-
]);
|
|
14286
|
-
function branchStoppedByFailure(branch) {
|
|
14287
|
-
const failure = branch.failure;
|
|
14288
|
-
if (failure === null) {
|
|
14289
|
-
return false;
|
|
14290
|
-
}
|
|
14291
|
-
return failure.code === "REPOSITORY_CHANGED" || branch.pullRequest === null && !failure.retryable;
|
|
14292
|
-
}
|
|
14293
|
-
function isSettled(pullRequest) {
|
|
14294
|
-
if (!SETTLED_STATES.has(pullRequest.state)) {
|
|
14295
|
-
return false;
|
|
14296
|
-
}
|
|
14297
|
-
const branch = pullRequest.branch;
|
|
14298
|
-
if (pullRequest.state === "OPEN" && branch) {
|
|
14299
|
-
return branch.pullRequest?.state === "OPEN" || STOPPED_BRANCH_STATES.has(branch.state) || branchStoppedByFailure(branch);
|
|
14300
|
-
}
|
|
14301
|
-
return true;
|
|
14302
|
-
}
|
|
14303
|
-
var PullRequestWaitDeadline = class extends Error {
|
|
14304
|
-
constructor() {
|
|
14305
|
-
super("the wait for the pull request reached its deadline");
|
|
14306
|
-
this.name = "PullRequestWaitDeadline";
|
|
14307
|
-
}
|
|
14308
|
-
};
|
|
14309
|
-
function deadlineSignal(ms) {
|
|
14310
|
-
const controller = new AbortController();
|
|
14311
|
-
const timer = setTimeout(
|
|
14312
|
-
() => controller.abort(new PullRequestWaitDeadline()),
|
|
14313
|
-
ms
|
|
14314
|
-
);
|
|
14315
|
-
return { signal: controller.signal, clear: () => clearTimeout(timer) };
|
|
14316
|
-
}
|
|
14317
|
-
function delay(ms) {
|
|
14318
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
14319
|
-
}
|
|
14320
|
-
async function waitForPullRequest(client, projectId, snapshotId, options = {}) {
|
|
14321
|
-
const deadline = Date.now() + PULL_REQUEST_WAIT_MS;
|
|
14322
|
-
let last2 = null;
|
|
14323
|
-
for (; ; ) {
|
|
14324
|
-
const remaining = deadline - Date.now();
|
|
14325
|
-
if (remaining <= 0) {
|
|
14326
|
-
return { kind: "timed_out", pullRequest: last2 };
|
|
14327
|
-
}
|
|
14328
|
-
const expiry = deadlineSignal(remaining);
|
|
14329
|
-
let pullRequest;
|
|
14330
|
-
try {
|
|
14331
|
-
pullRequest = await client.instructions.getProposalPullRequest(
|
|
14332
|
-
projectId,
|
|
14333
|
-
snapshotId,
|
|
14334
|
-
{ org: options.org, signal: expiry.signal }
|
|
14335
|
-
);
|
|
14336
|
-
} catch (error) {
|
|
14337
|
-
if (expiry.signal.aborted) {
|
|
14338
|
-
return { kind: "timed_out", pullRequest: last2 };
|
|
14339
|
-
}
|
|
14340
|
-
throw error;
|
|
14341
|
-
} finally {
|
|
14342
|
-
expiry.clear();
|
|
14343
|
-
}
|
|
14344
|
-
if (pullRequest === null) {
|
|
14345
|
-
return { kind: "none" };
|
|
14346
|
-
}
|
|
14347
|
-
options.observe?.(pullRequest);
|
|
14348
|
-
if (isSettled(pullRequest)) {
|
|
14349
|
-
return { kind: "settled", pullRequest };
|
|
14350
|
-
}
|
|
14351
|
-
last2 = pullRequest;
|
|
14352
|
-
if (deadline - Date.now() <= PULL_REQUEST_POLL_MS) {
|
|
14353
|
-
return { kind: "timed_out", pullRequest: last2 };
|
|
14354
|
-
}
|
|
14355
|
-
await delay(PULL_REQUEST_POLL_MS);
|
|
14356
|
-
}
|
|
14357
|
-
}
|
|
14358
|
-
function splitMessage(message) {
|
|
14359
|
-
const [title = "", ...rest] = message.replace(/\r\n?/g, "\n").split("\n");
|
|
14360
|
-
const body = rest.join("\n").replace(/^\n+/, "");
|
|
14361
|
-
return body === "" ? { title } : { title, body };
|
|
14362
|
-
}
|
|
14363
|
-
|
|
14364
|
-
// src/lib/instructions/push.ts
|
|
14365
|
-
import { createHash as createHash12 } from "crypto";
|
|
14366
|
-
var MAX_PUSH_CHANGES = 50;
|
|
14367
|
-
var MAX_INLINE_PUSH_BYTES = 2 * 1024 * 1024;
|
|
14368
|
-
function assertPushablePath(candidate, source) {
|
|
14369
|
-
const check = checkRelativePath(candidate);
|
|
14370
|
-
if (!check.ok) {
|
|
14371
|
-
throw new Error(
|
|
14372
|
-
`Refusing to push: ${describeRejection(check)}. ${source}`
|
|
14373
|
-
);
|
|
14374
|
-
}
|
|
14375
|
-
if (isReservedPath(candidate)) {
|
|
14376
|
-
throw new Error(
|
|
14377
|
-
`Refusing to push: ${describeRejection({
|
|
14378
|
-
ok: false,
|
|
14379
|
-
reason: "reserved_path",
|
|
14380
|
-
detail: candidate
|
|
14381
|
-
})}. ${source}`
|
|
14382
|
-
);
|
|
14383
|
-
}
|
|
14384
|
-
}
|
|
14385
|
-
function encodeContent(bytes) {
|
|
14386
|
-
const text3 = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
14387
|
-
const roundTrip = new TextEncoder().encode(text3);
|
|
14388
|
-
if (roundTrip.length === bytes.length && roundTrip.every((byte, index) => byte === bytes[index])) {
|
|
14389
|
-
return { content: text3, encoding: "utf8" };
|
|
14390
|
-
}
|
|
14391
|
-
return {
|
|
14392
|
-
content: Buffer.from(bytes).toString("base64"),
|
|
14393
|
-
encoding: "base64"
|
|
14394
|
-
};
|
|
14395
|
-
}
|
|
14396
|
-
function sha256Of(bytes) {
|
|
14397
|
-
return createHash12("sha256").update(bytes).digest("hex");
|
|
14398
|
-
}
|
|
14399
|
-
function refuseStaleLedger(detail) {
|
|
14400
|
-
throw new Error(
|
|
14401
|
-
`Refusing to push: the sync lock does not match the published version \u2014 ${detail}. Run \`${fabricCommand("instructions sync")}\` and try again. Nothing was read and nothing was sent.`
|
|
14402
|
-
);
|
|
14403
|
-
}
|
|
14404
|
-
function assertLockMatchesManifest(lock, manifest) {
|
|
14405
|
-
const published = new Map(
|
|
14406
|
-
manifest.map((entry) => [entry.path, entry.sha256])
|
|
14407
|
-
);
|
|
14408
|
-
for (const [path29, entry] of Object.entries(lock.files)) {
|
|
14409
|
-
const expected = published.get(path29);
|
|
14410
|
-
if (expected === void 0) {
|
|
14411
|
-
refuseStaleLedger(
|
|
14412
|
-
`it lists ${path29}, which version ${lock.snapshotVersion} does not contain`
|
|
14413
|
-
);
|
|
14414
|
-
}
|
|
14415
|
-
if (expected !== entry.sha256) {
|
|
14416
|
-
refuseStaleLedger(
|
|
14417
|
-
`its record of ${path29} is not the published file`
|
|
14418
|
-
);
|
|
14419
|
-
}
|
|
14420
|
-
}
|
|
14421
|
-
for (const entry of manifest) {
|
|
14422
|
-
if (!Object.hasOwn(lock.files, entry.path)) {
|
|
14423
|
-
refuseStaleLedger(
|
|
14424
|
-
`version ${lock.snapshotVersion} contains ${entry.path}, which the lock does not list`
|
|
14425
|
-
);
|
|
14426
|
-
}
|
|
14783
|
+
for (const entry of manifest) {
|
|
14784
|
+
if (!Object.hasOwn(lock.files, entry.path)) {
|
|
14785
|
+
refuseStaleLedger(
|
|
14786
|
+
`version ${lock.snapshotVersion} contains ${entry.path}, which the lock does not list`
|
|
14787
|
+
);
|
|
14788
|
+
}
|
|
14427
14789
|
}
|
|
14428
14790
|
}
|
|
14429
14791
|
async function computePushPlan(input) {
|
|
@@ -14501,95 +14863,731 @@ async function computePushPlan(input) {
|
|
|
14501
14863
|
sha256: fingerprint.sha256
|
|
14502
14864
|
});
|
|
14503
14865
|
}
|
|
14504
|
-
return { entries, unchanged };
|
|
14866
|
+
return { entries, unchanged };
|
|
14867
|
+
}
|
|
14868
|
+
function pushContentBytes(entries) {
|
|
14869
|
+
return entries.reduce(
|
|
14870
|
+
(total, entry) => total + (entry.action === "put" ? entry.size : 0),
|
|
14871
|
+
0
|
|
14872
|
+
);
|
|
14873
|
+
}
|
|
14874
|
+
async function materializePushChanges(input) {
|
|
14875
|
+
if (input.entries.length > MAX_PUSH_CHANGES) {
|
|
14876
|
+
throw new Error(`Too many changes to push (${input.entries.length}).`);
|
|
14877
|
+
}
|
|
14878
|
+
if (pushContentBytes(input.entries) > MAX_INLINE_PUSH_BYTES) {
|
|
14879
|
+
throw new Error("The selected changes are too large to send inline.");
|
|
14880
|
+
}
|
|
14881
|
+
const changes = [];
|
|
14882
|
+
for (const entry of input.entries) {
|
|
14883
|
+
if (entry.action === "delete") {
|
|
14884
|
+
const current = await readFileSafely(input.root, entry.path, {
|
|
14885
|
+
maxBytes: 0
|
|
14886
|
+
});
|
|
14887
|
+
if (current !== null) {
|
|
14888
|
+
throw new Error(
|
|
14889
|
+
`Refusing to push: ${entry.path} reappeared after planning. Plan the changes again.`
|
|
14890
|
+
);
|
|
14891
|
+
}
|
|
14892
|
+
changes.push({ op: "delete", path: entry.path });
|
|
14893
|
+
continue;
|
|
14894
|
+
}
|
|
14895
|
+
const read = await readFileSafely(input.root, entry.path, {
|
|
14896
|
+
maxBytes: MAX_INLINE_PUSH_BYTES
|
|
14897
|
+
});
|
|
14898
|
+
if (read === null) {
|
|
14899
|
+
throw new Error(
|
|
14900
|
+
`Refusing to push: ${entry.path} disappeared after planning. Run \`${fabricCommand("instructions push")}\` again.`
|
|
14901
|
+
);
|
|
14902
|
+
}
|
|
14903
|
+
const sha256 = sha256Of(read.bytes);
|
|
14904
|
+
if (read.bytes.length !== entry.size || sha256 !== entry.sha256) {
|
|
14905
|
+
throw new Error(
|
|
14906
|
+
`Refusing to push: ${entry.path} changed after planning. Run \`${fabricCommand("instructions push")}\` again.`
|
|
14907
|
+
);
|
|
14908
|
+
}
|
|
14909
|
+
changes.push({
|
|
14910
|
+
op: "put",
|
|
14911
|
+
path: entry.path,
|
|
14912
|
+
...encodeContent(read.bytes)
|
|
14913
|
+
});
|
|
14914
|
+
}
|
|
14915
|
+
return changes;
|
|
14916
|
+
}
|
|
14917
|
+
var LIVE_PULL_REQUEST_STATES = /* @__PURE__ */ new Set([
|
|
14918
|
+
"QUEUED",
|
|
14919
|
+
"OPENING",
|
|
14920
|
+
"OPEN"
|
|
14921
|
+
]);
|
|
14922
|
+
var LIVE_SNAPSHOT_STATUSES = /* @__PURE__ */ new Set([
|
|
14923
|
+
"VALIDATING",
|
|
14924
|
+
"READY"
|
|
14925
|
+
]);
|
|
14926
|
+
function carryingProposals(proposals, base) {
|
|
14927
|
+
return proposals.filter(
|
|
14928
|
+
(proposal) => (typeof base === "string" ? proposal.kind !== "native" && proposal.baseSnapshotId === base : proposal.kind === "native" && proposal.nativeBase.generation === base.generation && proposal.nativeBase.commitSha === base.commitSha) && LIVE_SNAPSHOT_STATUSES.has(proposal.status) && (proposal.pullRequest === null || LIVE_PULL_REQUEST_STATES.has(proposal.pullRequest.state))
|
|
14929
|
+
).sort(
|
|
14930
|
+
(a, b) => a.kind === "native" || b.kind === "native" ? 0 : b.version - a.version
|
|
14931
|
+
);
|
|
14932
|
+
}
|
|
14933
|
+
function setAsideProposed(plan, proposals, base) {
|
|
14934
|
+
const carrying = carryingProposals(proposals, base);
|
|
14935
|
+
const carriedBy = (entry) => carrying.find(
|
|
14936
|
+
(proposal) => proposal.changes.some(
|
|
14937
|
+
(change) => change.path === entry.path && change.op === entry.action && (entry.action === "delete" || change.sha256 === entry.sha256)
|
|
14938
|
+
)
|
|
14939
|
+
);
|
|
14940
|
+
const setAside = /* @__PURE__ */ new Set();
|
|
14941
|
+
const alreadyProposed = [];
|
|
14942
|
+
for (const entry of plan.entries) {
|
|
14943
|
+
const proposal = carriedBy(entry);
|
|
14944
|
+
if (proposal === void 0) {
|
|
14945
|
+
continue;
|
|
14946
|
+
}
|
|
14947
|
+
setAside.add(entry.path);
|
|
14948
|
+
alreadyProposed.push({
|
|
14949
|
+
path: entry.path,
|
|
14950
|
+
action: entry.action,
|
|
14951
|
+
proposal: {
|
|
14952
|
+
snapshotId: proposal.kind === "native" ? proposal.operationId : proposal.snapshotId,
|
|
14953
|
+
version: proposal.kind === "native" ? null : proposal.version,
|
|
14954
|
+
pullRequest: proposal.pullRequest
|
|
14955
|
+
}
|
|
14956
|
+
});
|
|
14957
|
+
}
|
|
14958
|
+
return {
|
|
14959
|
+
plan: {
|
|
14960
|
+
entries: plan.entries.filter((entry) => !setAside.has(entry.path)),
|
|
14961
|
+
unchanged: plan.unchanged
|
|
14962
|
+
},
|
|
14963
|
+
alreadyProposed
|
|
14964
|
+
};
|
|
14965
|
+
}
|
|
14966
|
+
|
|
14967
|
+
// src/lib/instructions/native-push-plan.ts
|
|
14968
|
+
async function computeNativePushPlan(input) {
|
|
14969
|
+
const allowed = new Set(input.files.map((file) => file.path));
|
|
14970
|
+
const selected = new Set(
|
|
14971
|
+
input.changedPaths.filter((file) => allowed.has(file))
|
|
14972
|
+
);
|
|
14973
|
+
for (const file of input.added ?? []) {
|
|
14974
|
+
assertPushablePath(file, "That path cannot be pushed.");
|
|
14975
|
+
if (!allowed.has(file)) selected.add(file);
|
|
14976
|
+
}
|
|
14977
|
+
if (selected.size > MAX_PUSH_CHANGES) {
|
|
14978
|
+
throw new Error(
|
|
14979
|
+
`Too many changes to push (${selected.size} > ${MAX_PUSH_CHANGES}). Select a smaller change set.`
|
|
14980
|
+
);
|
|
14981
|
+
}
|
|
14982
|
+
const collision = findCollision([.../* @__PURE__ */ new Set([...allowed, ...selected])]);
|
|
14983
|
+
if (collision !== null) {
|
|
14984
|
+
throw new Error(
|
|
14985
|
+
`Refusing to push colliding paths: ${collision.first} and ${collision.second}.`
|
|
14986
|
+
);
|
|
14987
|
+
}
|
|
14988
|
+
const entries = [];
|
|
14989
|
+
let totalBytes = 0;
|
|
14990
|
+
for (const file of [...selected].sort()) {
|
|
14991
|
+
assertPushablePath(file, "That path cannot be pushed.");
|
|
14992
|
+
const read = await readFileSafely(input.root, file, {
|
|
14993
|
+
maxBytes: MAX_INLINE_PUSH_BYTES - totalBytes
|
|
14994
|
+
});
|
|
14995
|
+
if (read === null) {
|
|
14996
|
+
if (!allowed.has(file))
|
|
14997
|
+
throw new Error(
|
|
14998
|
+
`Refusing to push: there is no file at ${file}.`
|
|
14999
|
+
);
|
|
15000
|
+
entries.push({ path: file, action: "delete" });
|
|
15001
|
+
} else {
|
|
15002
|
+
totalBytes += read.bytes.length;
|
|
15003
|
+
entries.push({
|
|
15004
|
+
path: file,
|
|
15005
|
+
action: "put",
|
|
15006
|
+
size: read.bytes.length,
|
|
15007
|
+
sha256: createHash13("sha256").update(read.bytes).digest("hex")
|
|
15008
|
+
});
|
|
15009
|
+
}
|
|
15010
|
+
}
|
|
15011
|
+
return {
|
|
15012
|
+
entries,
|
|
15013
|
+
unchanged: input.files.filter((file) => !selected.has(file.path)).map((file) => file.path)
|
|
15014
|
+
};
|
|
15015
|
+
}
|
|
15016
|
+
|
|
15017
|
+
// src/lib/instructions/open-proposals.ts
|
|
15018
|
+
var openProposalTiming = { deadlineMs: 15e3 };
|
|
15019
|
+
var SNAPSHOT_STATUSES = {
|
|
15020
|
+
RECEIVING: true,
|
|
15021
|
+
VALIDATING: true,
|
|
15022
|
+
READY: true,
|
|
15023
|
+
REJECTED: true,
|
|
15024
|
+
FAILED: true
|
|
15025
|
+
};
|
|
15026
|
+
var PULL_REQUEST_STATES = {
|
|
15027
|
+
QUEUED: true,
|
|
15028
|
+
OPENING: true,
|
|
15029
|
+
OPEN: true,
|
|
15030
|
+
CLOSE_REQUESTED: true,
|
|
15031
|
+
BLOCKED: true,
|
|
15032
|
+
MERGED: true,
|
|
15033
|
+
CLOSED: true,
|
|
15034
|
+
CANCELED: true
|
|
15035
|
+
};
|
|
15036
|
+
function isKnown(known, value) {
|
|
15037
|
+
return typeof value === "string" && Object.hasOwn(known, value);
|
|
15038
|
+
}
|
|
15039
|
+
var OpenProposalDeadline = class extends Error {
|
|
15040
|
+
constructor(ms) {
|
|
15041
|
+
super(`it did not answer within ${Math.ceil(ms / 1e3)} s`);
|
|
15042
|
+
this.name = "OpenProposalDeadline";
|
|
15043
|
+
}
|
|
15044
|
+
};
|
|
15045
|
+
async function lookUpOpenProposals(client, projectId, options = {}) {
|
|
15046
|
+
const ms = openProposalTiming.deadlineMs;
|
|
15047
|
+
const controller = new AbortController();
|
|
15048
|
+
const timer = setTimeout(
|
|
15049
|
+
() => controller.abort(new OpenProposalDeadline(ms)),
|
|
15050
|
+
ms
|
|
15051
|
+
);
|
|
15052
|
+
let answer;
|
|
15053
|
+
try {
|
|
15054
|
+
answer = await client.instructions.getOpenProposals(projectId, {
|
|
15055
|
+
org: options.org,
|
|
15056
|
+
signal: controller.signal
|
|
15057
|
+
});
|
|
15058
|
+
} catch (error) {
|
|
15059
|
+
if (controller.signal.aborted) {
|
|
15060
|
+
return {
|
|
15061
|
+
kind: "unavailable",
|
|
15062
|
+
reason: describeError(controller.signal.reason)
|
|
15063
|
+
};
|
|
15064
|
+
}
|
|
15065
|
+
if (error.status === 404) {
|
|
15066
|
+
return {
|
|
15067
|
+
kind: "unavailable",
|
|
15068
|
+
reason: "this Fabric server does not list open proposals yet"
|
|
15069
|
+
};
|
|
15070
|
+
}
|
|
15071
|
+
const status = error?.status;
|
|
15072
|
+
return {
|
|
15073
|
+
kind: "unavailable",
|
|
15074
|
+
reason: typeof status === "number" && status > 0 ? `the server answered ${status}` : "the request did not go through"
|
|
15075
|
+
};
|
|
15076
|
+
} finally {
|
|
15077
|
+
clearTimeout(timer);
|
|
15078
|
+
}
|
|
15079
|
+
if (!Array.isArray(answer) || !answer.every(isOpenProposal)) {
|
|
15080
|
+
return {
|
|
15081
|
+
kind: "unavailable",
|
|
15082
|
+
reason: "the server's answer was not a list of proposals"
|
|
15083
|
+
};
|
|
15084
|
+
}
|
|
15085
|
+
return { kind: "found", proposals: answer };
|
|
15086
|
+
}
|
|
15087
|
+
function isOpenProposal(value) {
|
|
15088
|
+
if (typeof value !== "object" || value === null) {
|
|
15089
|
+
return false;
|
|
15090
|
+
}
|
|
15091
|
+
const proposal = value;
|
|
15092
|
+
const pullRequest = proposal.pullRequest;
|
|
15093
|
+
return (proposal.kind === "native" ? typeof proposal.operationId === "string" && isNativeBase(proposal.nativeBase) : typeof proposal.snapshotId === "string" && typeof proposal.version === "number" && typeof proposal.baseSnapshotId === "string") && isKnown(SNAPSHOT_STATUSES, proposal.status) && (pullRequest === null || typeof pullRequest === "object" && pullRequest !== void 0 && isKnown(PULL_REQUEST_STATES, pullRequest.state) && (pullRequest.url === null || typeof pullRequest.url === "string")) && Array.isArray(proposal.changes) && proposal.changes.every(isChange);
|
|
15094
|
+
}
|
|
15095
|
+
function isNativeBase(value) {
|
|
15096
|
+
if (typeof value !== "object" || value === null) return false;
|
|
15097
|
+
return "generation" in value && Number.isSafeInteger(value.generation) && typeof value.generation === "number" && value.generation > 0 && "commitSha" in value && typeof value.commitSha === "string" && /^[a-f0-9]{40}$/.test(value.commitSha);
|
|
15098
|
+
}
|
|
15099
|
+
function isChange(value) {
|
|
15100
|
+
if (typeof value !== "object" || value === null) {
|
|
15101
|
+
return false;
|
|
15102
|
+
}
|
|
15103
|
+
const change = value;
|
|
15104
|
+
return typeof change.path === "string" && (change.op === "put" && typeof change.sha256 === "string" || change.op === "delete" && change.sha256 === null);
|
|
15105
|
+
}
|
|
15106
|
+
|
|
15107
|
+
// src/lib/instructions/pull-request-wait.ts
|
|
15108
|
+
var PULL_REQUEST_POLL_MS = 2e3;
|
|
15109
|
+
var PULL_REQUEST_WAIT_MS = 6e4;
|
|
15110
|
+
var SETTLED_STATES = /* @__PURE__ */ new Set([
|
|
15111
|
+
"OPEN",
|
|
15112
|
+
"BLOCKED",
|
|
15113
|
+
"MERGED",
|
|
15114
|
+
"CLOSED",
|
|
15115
|
+
"CANCELED",
|
|
15116
|
+
"CLOSE_REQUESTED"
|
|
15117
|
+
]);
|
|
15118
|
+
var STOPPED_BRANCH_STATES = /* @__PURE__ */ new Set([
|
|
15119
|
+
"BLOCKED",
|
|
15120
|
+
"MERGED",
|
|
15121
|
+
"CLOSED",
|
|
15122
|
+
"CANCELED",
|
|
15123
|
+
"CLOSE_REQUESTED"
|
|
15124
|
+
]);
|
|
15125
|
+
function branchStoppedByFailure(branch) {
|
|
15126
|
+
const failure = branch.failure;
|
|
15127
|
+
if (failure === null) {
|
|
15128
|
+
return false;
|
|
15129
|
+
}
|
|
15130
|
+
return failure.code === "REPOSITORY_CHANGED" || branch.pullRequest === null && !failure.retryable;
|
|
15131
|
+
}
|
|
15132
|
+
function isSettled(pullRequest) {
|
|
15133
|
+
if (!SETTLED_STATES.has(pullRequest.state)) {
|
|
15134
|
+
return false;
|
|
15135
|
+
}
|
|
15136
|
+
const branch = pullRequest.branch;
|
|
15137
|
+
if (pullRequest.state === "OPEN" && branch) {
|
|
15138
|
+
return branch.pullRequest?.state === "OPEN" || STOPPED_BRANCH_STATES.has(branch.state) || branchStoppedByFailure(branch);
|
|
15139
|
+
}
|
|
15140
|
+
return true;
|
|
15141
|
+
}
|
|
15142
|
+
var PullRequestWaitDeadline = class extends Error {
|
|
15143
|
+
constructor() {
|
|
15144
|
+
super("the wait for the pull request reached its deadline");
|
|
15145
|
+
this.name = "PullRequestWaitDeadline";
|
|
15146
|
+
}
|
|
15147
|
+
};
|
|
15148
|
+
function deadlineSignal(ms) {
|
|
15149
|
+
const controller = new AbortController();
|
|
15150
|
+
const timer = setTimeout(
|
|
15151
|
+
() => controller.abort(new PullRequestWaitDeadline()),
|
|
15152
|
+
ms
|
|
15153
|
+
);
|
|
15154
|
+
return { signal: controller.signal, clear: () => clearTimeout(timer) };
|
|
15155
|
+
}
|
|
15156
|
+
function delay(ms) {
|
|
15157
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15158
|
+
}
|
|
15159
|
+
async function waitForPullRequest(client, projectId, snapshotId, options = {}) {
|
|
15160
|
+
const deadline = Date.now() + PULL_REQUEST_WAIT_MS;
|
|
15161
|
+
let last2 = null;
|
|
15162
|
+
for (; ; ) {
|
|
15163
|
+
const remaining = deadline - Date.now();
|
|
15164
|
+
if (remaining <= 0) {
|
|
15165
|
+
return { kind: "timed_out", pullRequest: last2 };
|
|
15166
|
+
}
|
|
15167
|
+
const expiry = deadlineSignal(remaining);
|
|
15168
|
+
let pullRequest;
|
|
15169
|
+
try {
|
|
15170
|
+
pullRequest = await client.instructions.getProposalPullRequest(
|
|
15171
|
+
projectId,
|
|
15172
|
+
snapshotId,
|
|
15173
|
+
{ org: options.org, signal: expiry.signal }
|
|
15174
|
+
);
|
|
15175
|
+
} catch (error) {
|
|
15176
|
+
if (expiry.signal.aborted) {
|
|
15177
|
+
return { kind: "timed_out", pullRequest: last2 };
|
|
15178
|
+
}
|
|
15179
|
+
throw error;
|
|
15180
|
+
} finally {
|
|
15181
|
+
expiry.clear();
|
|
15182
|
+
}
|
|
15183
|
+
if (pullRequest === null) {
|
|
15184
|
+
return { kind: "none" };
|
|
15185
|
+
}
|
|
15186
|
+
options.observe?.(pullRequest);
|
|
15187
|
+
if (isSettled(pullRequest)) {
|
|
15188
|
+
return { kind: "settled", pullRequest };
|
|
15189
|
+
}
|
|
15190
|
+
last2 = pullRequest;
|
|
15191
|
+
if (deadline - Date.now() <= PULL_REQUEST_POLL_MS) {
|
|
15192
|
+
return { kind: "timed_out", pullRequest: last2 };
|
|
15193
|
+
}
|
|
15194
|
+
await delay(PULL_REQUEST_POLL_MS);
|
|
15195
|
+
}
|
|
15196
|
+
}
|
|
15197
|
+
function splitMessage(message) {
|
|
15198
|
+
const [title = "", ...rest] = message.replace(/\r\n?/g, "\n").split("\n");
|
|
15199
|
+
const body = rest.join("\n").replace(/^\n+/, "");
|
|
15200
|
+
return body === "" ? { title } : { title, body };
|
|
15201
|
+
}
|
|
15202
|
+
|
|
15203
|
+
// src/lib/instructions/pull-request-verdict.ts
|
|
15204
|
+
function pushPullRequest(pullRequest, timedOut) {
|
|
15205
|
+
const branch = pullRequest.branch ?? null;
|
|
15206
|
+
return {
|
|
15207
|
+
operationId: pullRequest.operationId,
|
|
15208
|
+
state: pullRequest.state,
|
|
15209
|
+
url: branch?.pullRequest?.url ?? pullRequest.url,
|
|
15210
|
+
failure: pullRequest.failure,
|
|
15211
|
+
timedOut,
|
|
15212
|
+
branch,
|
|
15213
|
+
append: pullRequest.append ?? null
|
|
15214
|
+
};
|
|
15215
|
+
}
|
|
15216
|
+
var BLOCKED_REASONS = {
|
|
15217
|
+
ATTRIBUTION_REJECTED: "the project's name or your display name looks like an address, a link or a credential, so the commit could not name you safely",
|
|
15218
|
+
PERMISSION_REVOKED: "you no longer have permission to suggest changes to this project's repository",
|
|
15219
|
+
CONFIGURATION_CHANGED: "the project's repository settings changed after you suggested this; push again",
|
|
15220
|
+
TARGET_BRANCH_MISSING: "the repository branch the project syncs from no longer exists",
|
|
15221
|
+
BASE_COMMIT_UNAVAILABLE: "the commit this suggestion was based on is no longer in the repository; sync and push again",
|
|
15222
|
+
TREE_CONFLICT: "the repository changed the same files since your last sync; sync and push again",
|
|
15223
|
+
AUTHENTICATION_FAILED: "the project's repository connection needs to be reconnected",
|
|
15224
|
+
REPOSITORY_UNAVAILABLE: "the repository could not be reached",
|
|
15225
|
+
BRANCH_WRITE_REFUSED: "the repository refused the new branch",
|
|
15226
|
+
PR_CREATION_REFUSED: "the repository refused to open the pull request",
|
|
15227
|
+
REMOTE_REF_CONFLICT: "a branch with the same name already exists in the repository",
|
|
15228
|
+
CREATE_OUTCOME_UNKNOWN: "Fabric could not confirm whether the pull request was created",
|
|
15229
|
+
VALIDATION_TIMEOUT: "the files' checks did not finish in time",
|
|
15230
|
+
LIMITS_EXCEEDED: "the change is larger than Fabric can open as a pull request",
|
|
15231
|
+
PROVIDER_RATE_LIMITED: "the repository's provider is limiting requests",
|
|
15232
|
+
PROVIDER_TEMPORARY: "the repository's provider had a temporary problem",
|
|
15233
|
+
LOOKUP_INCONCLUSIVE: "Fabric could not confirm the pull request's state",
|
|
15234
|
+
// Member proposal branches (Fizzy #2738 spec §9). BRANCH_CONFLICT and
|
|
15235
|
+
// SUPERSEDED_BY_LATER_CHANGE name their paths, in `blockedReason`.
|
|
15236
|
+
BRANCH_MOVED: "your branch kept changing while Fabric was adding to it",
|
|
15237
|
+
BRANCH_NAME_UNAVAILABLE: "Fabric could not reserve a branch name for your pull request; ask an owner to remove old proposal branches",
|
|
15238
|
+
REPOSITORY_CHANGED: "the repository connection now points at another repository, so Fabric can no longer reach your pull request; close it in the repository, then stop tracking it in the Coding Instructions tab",
|
|
15239
|
+
PUSH_OUTCOME_UNKNOWN: "Fabric could not confirm whether this change reached your branch, which may have been changed outside Fabric; check the branch, then try again or withdraw it",
|
|
15240
|
+
BRANCH_MISSING: "your branch was deleted in the repository"
|
|
15241
|
+
};
|
|
15242
|
+
var FAILURE_PATHS_LISTED = 20;
|
|
15243
|
+
function failurePaths(failure) {
|
|
15244
|
+
const paths = failure.params?.paths;
|
|
15245
|
+
const count = failure.params?.count;
|
|
15246
|
+
if (typeof paths !== "string" || paths === "") {
|
|
15247
|
+
return "some of its files";
|
|
15248
|
+
}
|
|
15249
|
+
return typeof count === "number" && count > FAILURE_PATHS_LISTED ? `${paths} and ${count - FAILURE_PATHS_LISTED} more` : paths;
|
|
15250
|
+
}
|
|
15251
|
+
function blockedReason(failure) {
|
|
15252
|
+
switch (failure.code) {
|
|
15253
|
+
case "BRANCH_CONFLICT":
|
|
15254
|
+
return `files on your branch were changed outside Fabric (${failurePaths(failure)}), and Fabric does not overwrite them while the pull request is open; make this change on your branch in the repository, or wait until the pull request merges`;
|
|
15255
|
+
case "SUPERSEDED_BY_LATER_CHANGE":
|
|
15256
|
+
return `a newer change of yours already edits ${failurePaths(failure)}; use Try again to apply this one on top of it, or withdraw it`;
|
|
15257
|
+
default:
|
|
15258
|
+
return BLOCKED_REASONS[failure.code] ?? `it stopped with ${failure.code}`;
|
|
15259
|
+
}
|
|
15260
|
+
}
|
|
15261
|
+
function pullRequestVerdict(pullRequest, { waited, openedHere }) {
|
|
15262
|
+
const url = pullRequest.url ? `: ${pullRequest.url}` : "";
|
|
15263
|
+
const branch = pullRequest.branch;
|
|
15264
|
+
if (!waited) {
|
|
15265
|
+
return {
|
|
15266
|
+
text: "Follow its pull request in the project's Coding Instructions tab.",
|
|
15267
|
+
fails: false
|
|
15268
|
+
};
|
|
15269
|
+
}
|
|
15270
|
+
if (pullRequest.timedOut) {
|
|
15271
|
+
return {
|
|
15272
|
+
text: "The pull request is being opened; see the project's Coding Instructions tab.",
|
|
15273
|
+
fails: false
|
|
15274
|
+
};
|
|
15275
|
+
}
|
|
15276
|
+
switch (pullRequest.state) {
|
|
15277
|
+
case "OPEN": {
|
|
15278
|
+
if (!branch) {
|
|
15279
|
+
return { text: `Pull request opened${url}`, fails: false };
|
|
15280
|
+
}
|
|
15281
|
+
if (branch.state === "MERGED") {
|
|
15282
|
+
return {
|
|
15283
|
+
text: `Your pull request was merged${url} while this change was being added to it; see the project's Coding Instructions tab for whether it carried this change.`,
|
|
15284
|
+
fails: false
|
|
15285
|
+
};
|
|
15286
|
+
}
|
|
15287
|
+
if (branch.state === "CLOSED") {
|
|
15288
|
+
return {
|
|
15289
|
+
text: `Your pull request was closed without merging${url} while this change was being added to it.`,
|
|
15290
|
+
fails: false
|
|
15291
|
+
};
|
|
15292
|
+
}
|
|
15293
|
+
if (branch.failure && (branch.state === "BLOCKED" || branchStoppedByFailure(branch))) {
|
|
15294
|
+
const next = branch.failure.retryable ? "Fabric will try again on its own; see" : "See";
|
|
15295
|
+
const what = branch.pullRequest ? `Your change is on your branch, but Fabric can no longer update its pull request${url}` : "Your change is on your branch, but Fabric could not open its pull request";
|
|
15296
|
+
return {
|
|
15297
|
+
text: `${what}: ${blockedReason(branch.failure)}. ${next} the project's Coding Instructions tab.`,
|
|
15298
|
+
fails: true
|
|
15299
|
+
};
|
|
15300
|
+
}
|
|
15301
|
+
if (branch.state === "CLOSE_REQUESTED") {
|
|
15302
|
+
return {
|
|
15303
|
+
text: "Your pull request is being closed, and this change leaves with it; push again once it has closed.",
|
|
15304
|
+
fails: true
|
|
15305
|
+
};
|
|
15306
|
+
}
|
|
15307
|
+
if (branch.state === "CANCELED") {
|
|
15308
|
+
return {
|
|
15309
|
+
text: "Your branch was closed before its pull request opened; push again.",
|
|
15310
|
+
fails: true
|
|
15311
|
+
};
|
|
15312
|
+
}
|
|
15313
|
+
if (branch.pullRequest?.state === "OPEN") {
|
|
15314
|
+
return {
|
|
15315
|
+
text: openedHere ? `Opened pull request ${branch.pullRequest.url}` : `Added to your pull request ${branch.pullRequest.url}`,
|
|
15316
|
+
fails: false
|
|
15317
|
+
};
|
|
15318
|
+
}
|
|
15319
|
+
return {
|
|
15320
|
+
text: "Your change is on your branch and its pull request is being opened; see the project's Coding Instructions tab.",
|
|
15321
|
+
fails: false
|
|
15322
|
+
};
|
|
15323
|
+
}
|
|
15324
|
+
case "MERGED":
|
|
15325
|
+
return {
|
|
15326
|
+
text: `Its pull request was already merged${url}. Run \`${fabricCommand("instructions sync")}\` once the project has synced it.`,
|
|
15327
|
+
fails: false
|
|
15328
|
+
};
|
|
15329
|
+
case "CLOSED":
|
|
15330
|
+
return {
|
|
15331
|
+
text: `Its pull request was closed without merging${url}.`,
|
|
15332
|
+
fails: false
|
|
15333
|
+
};
|
|
15334
|
+
case "CANCELED":
|
|
15335
|
+
if (pullRequest.failure?.code === "ALREADY_ON_BRANCH") {
|
|
15336
|
+
return {
|
|
15337
|
+
text: branch?.pullRequest ? `Already on your branch; nothing to add. Your pull request: ${branch.pullRequest.url}` : "Already on your branch; nothing to add.",
|
|
15338
|
+
fails: false
|
|
15339
|
+
};
|
|
15340
|
+
}
|
|
15341
|
+
return {
|
|
15342
|
+
text: pullRequest.failure?.code === "VALIDATION_REJECTED" ? "The suggestion did not pass Fabric's checks, so no pull request was opened; see the project's Coding Instructions tab." : branch ? "The suggestion was withdrawn before it was added to your pull request." : "The suggestion was withdrawn before a pull request was opened.",
|
|
15343
|
+
fails: true
|
|
15344
|
+
};
|
|
15345
|
+
case "CLOSE_REQUESTED":
|
|
15346
|
+
return {
|
|
15347
|
+
text: "An earlier attempt at this change was withdrawn and its pull request is being closed; push again once it has closed.",
|
|
15348
|
+
fails: true
|
|
15349
|
+
};
|
|
15350
|
+
case "BLOCKED": {
|
|
15351
|
+
const failure = pullRequest.failure ?? branch?.failure ?? null;
|
|
15352
|
+
const reason = failure ? blockedReason(failure) : "it stopped with an unknown failure";
|
|
15353
|
+
const next = failure?.retryable ? "Fabric will try again on its own; see" : "See";
|
|
15354
|
+
const what = branch ? "Fabric could not add this change to your pull request" : "Fabric could not open the pull request";
|
|
15355
|
+
return {
|
|
15356
|
+
text: `${what}: ${reason}. ${next} the project's Coding Instructions tab.`,
|
|
15357
|
+
fails: true
|
|
15358
|
+
};
|
|
15359
|
+
}
|
|
15360
|
+
default:
|
|
15361
|
+
return {
|
|
15362
|
+
text: "The pull request is being opened; see the project's Coding Instructions tab.",
|
|
15363
|
+
fails: false
|
|
15364
|
+
};
|
|
15365
|
+
}
|
|
14505
15366
|
}
|
|
14506
|
-
|
|
14507
|
-
|
|
14508
|
-
|
|
14509
|
-
|
|
15367
|
+
|
|
15368
|
+
// src/lib/instructions/native-push.ts
|
|
15369
|
+
async function runNativeInstructionPush(input) {
|
|
15370
|
+
if (input.publish)
|
|
15371
|
+
throw new CliFailure(
|
|
15372
|
+
"Repository instructions are changed through Git commits or reviewed suggestions. Run push without --publish to open a suggestion.",
|
|
15373
|
+
2
|
|
15374
|
+
);
|
|
15375
|
+
const deadline = Date.now() + 3e4;
|
|
15376
|
+
const checkout = await classifyCheckout({
|
|
15377
|
+
destination: input.root,
|
|
15378
|
+
repository: input.repository,
|
|
15379
|
+
deadline,
|
|
15380
|
+
remote: input.remote
|
|
15381
|
+
});
|
|
15382
|
+
if (checkout.class !== "matching")
|
|
15383
|
+
throw new CliFailure(
|
|
15384
|
+
"Run this command in the attached repository's instruction folder. No files were sent.",
|
|
15385
|
+
7
|
|
15386
|
+
);
|
|
15387
|
+
const head = await headSha(checkout.toplevel, deadline);
|
|
15388
|
+
if (head.kind !== "ok" || head.value !== input.direct.currentCommitSha) {
|
|
15389
|
+
throw new CliFailure(
|
|
15390
|
+
"This checkout's HEAD does not match the repository revision read through Fabric. Update the checkout before proposing working-tree changes. Local edits were preserved.",
|
|
15391
|
+
7
|
|
15392
|
+
);
|
|
15393
|
+
}
|
|
15394
|
+
const operation = await operationInProgress(
|
|
15395
|
+
checkout.toplevel,
|
|
15396
|
+
deadline
|
|
14510
15397
|
);
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
|
|
15398
|
+
if (operation.kind !== "ok" || operation.value !== null) {
|
|
15399
|
+
throw new CliFailure(
|
|
15400
|
+
"Finish the current Git operation before proposing changes. No files were sent.",
|
|
15401
|
+
7
|
|
15402
|
+
);
|
|
14515
15403
|
}
|
|
14516
|
-
|
|
14517
|
-
|
|
15404
|
+
const changed = await changedTrackedPaths(input.root, deadline);
|
|
15405
|
+
if (changed.kind !== "ok")
|
|
15406
|
+
throw new CliFailure(
|
|
15407
|
+
"Git could not list this checkout's changes. No files were sent.",
|
|
15408
|
+
7
|
|
15409
|
+
);
|
|
15410
|
+
const nativeBase = {
|
|
15411
|
+
generation: input.direct.generation,
|
|
15412
|
+
commitSha: input.direct.currentCommitSha
|
|
15413
|
+
};
|
|
15414
|
+
const listed = await input.client.instructions.listRepositoryFiles(
|
|
15415
|
+
input.projectId,
|
|
15416
|
+
{ ...nativeBase, org: input.org }
|
|
15417
|
+
);
|
|
15418
|
+
if (listed.generation !== nativeBase.generation || listed.commitSha !== nativeBase.commitSha || listed.incomplete || listed.refusal !== null) {
|
|
15419
|
+
throw new CliFailure(
|
|
15420
|
+
"The repository tree could not be read completely at the selected revision. Refresh and try again; no files were sent.",
|
|
15421
|
+
7
|
|
15422
|
+
);
|
|
14518
15423
|
}
|
|
14519
|
-
|
|
14520
|
-
|
|
14521
|
-
|
|
14522
|
-
|
|
14523
|
-
|
|
15424
|
+
let plan = await computeNativePushPlan({
|
|
15425
|
+
root: input.root,
|
|
15426
|
+
files: listed.files,
|
|
15427
|
+
changedPaths: changed.value,
|
|
15428
|
+
added: input.add
|
|
15429
|
+
});
|
|
15430
|
+
if (plan.entries.length === 0)
|
|
15431
|
+
throw new CliFailure(
|
|
15432
|
+
"Nothing to push: Git reports no instruction content changes. Add a new file with --add <path>.",
|
|
15433
|
+
7
|
|
15434
|
+
);
|
|
15435
|
+
let alreadyProposed = [];
|
|
15436
|
+
let openProposalCheck = "skipped";
|
|
15437
|
+
if (!input.includeProposed) {
|
|
15438
|
+
const lookup = await lookUpOpenProposals(
|
|
15439
|
+
input.client,
|
|
15440
|
+
input.projectId,
|
|
15441
|
+
{ org: input.org }
|
|
15442
|
+
);
|
|
15443
|
+
if (lookup.kind === "found") {
|
|
15444
|
+
({ plan, alreadyProposed } = setAsideProposed(
|
|
15445
|
+
plan,
|
|
15446
|
+
lookup.proposals,
|
|
15447
|
+
nativeBase
|
|
15448
|
+
));
|
|
15449
|
+
openProposalCheck = "checked";
|
|
15450
|
+
} else {
|
|
15451
|
+
openProposalCheck = "unavailable";
|
|
15452
|
+
process.stderr.write(
|
|
15453
|
+
`Could not check your open proposals (${lookup.reason}); the selected changes will be sent.
|
|
15454
|
+
`
|
|
15455
|
+
);
|
|
14524
15456
|
}
|
|
14525
|
-
|
|
14526
|
-
|
|
15457
|
+
}
|
|
15458
|
+
const outcome = {
|
|
15459
|
+
projectId: input.projectId,
|
|
15460
|
+
destination: input.root,
|
|
15461
|
+
nativeBase,
|
|
15462
|
+
dryRun: Boolean(input.dryRun),
|
|
15463
|
+
put: plan.entries.filter((entry) => entry.action === "put").map((entry) => entry.path),
|
|
15464
|
+
deleted: plan.entries.filter((entry) => entry.action === "delete").map((entry) => entry.path),
|
|
15465
|
+
unchanged: plan.unchanged.length,
|
|
15466
|
+
openProposalCheck,
|
|
15467
|
+
alreadyProposed: alreadyProposed.map((entry) => ({
|
|
15468
|
+
path: entry.path,
|
|
15469
|
+
operationId: entry.proposal.snapshotId,
|
|
15470
|
+
pullRequestUrl: entry.proposal.pullRequest?.url ?? null
|
|
15471
|
+
})),
|
|
15472
|
+
operationId: null,
|
|
15473
|
+
pullRequest: null,
|
|
15474
|
+
timedOut: false
|
|
15475
|
+
};
|
|
15476
|
+
let sawBranchWithoutPullRequest = false;
|
|
15477
|
+
if (!input.dryRun && plan.entries.length > 0) {
|
|
15478
|
+
const changes = await materializePushChanges({
|
|
15479
|
+
root: input.root,
|
|
15480
|
+
entries: plan.entries
|
|
14527
15481
|
});
|
|
14528
|
-
|
|
14529
|
-
|
|
14530
|
-
|
|
15482
|
+
const currentHead = await headSha(checkout.toplevel, deadline);
|
|
15483
|
+
if (currentHead.kind !== "ok" || currentHead.value !== nativeBase.commitSha)
|
|
15484
|
+
throw new CliFailure(
|
|
15485
|
+
"HEAD changed while planning. No files were sent; run push again.",
|
|
15486
|
+
7
|
|
14531
15487
|
);
|
|
15488
|
+
const submitted = await input.client.instructions.submitRepositoryChange(
|
|
15489
|
+
input.projectId,
|
|
15490
|
+
nativeBase,
|
|
15491
|
+
changes,
|
|
15492
|
+
{
|
|
15493
|
+
org: input.org,
|
|
15494
|
+
...input.message === void 0 ? {} : { note: splitMessage(input.message) }
|
|
15495
|
+
}
|
|
15496
|
+
);
|
|
15497
|
+
outcome.operationId = submitted.operationId;
|
|
15498
|
+
if (input.wait !== false) {
|
|
15499
|
+
try {
|
|
15500
|
+
const waited = await waitForPullRequest(
|
|
15501
|
+
input.client,
|
|
15502
|
+
input.projectId,
|
|
15503
|
+
submitted.operationId,
|
|
15504
|
+
{
|
|
15505
|
+
org: input.org,
|
|
15506
|
+
observe: (status) => {
|
|
15507
|
+
if (status.branch && status.branch.pullRequest === null)
|
|
15508
|
+
sawBranchWithoutPullRequest = true;
|
|
15509
|
+
}
|
|
15510
|
+
}
|
|
15511
|
+
);
|
|
15512
|
+
if (waited.kind !== "none") {
|
|
15513
|
+
outcome.pullRequest = waited.pullRequest ? pushPullRequest(
|
|
15514
|
+
waited.pullRequest,
|
|
15515
|
+
waited.kind === "timed_out"
|
|
15516
|
+
) : null;
|
|
15517
|
+
outcome.timedOut = waited.kind === "timed_out";
|
|
15518
|
+
}
|
|
15519
|
+
} catch (error) {
|
|
15520
|
+
throw new CliFailure(
|
|
15521
|
+
`Suggestion ${submitted.operationId} was accepted, but its pull request could not be checked: ${describeError(error)}. Check the project's Coding Instructions tab.`,
|
|
15522
|
+
7
|
|
15523
|
+
);
|
|
15524
|
+
}
|
|
14532
15525
|
}
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
|
|
15526
|
+
}
|
|
15527
|
+
const verdict = outcome.pullRequest ? pullRequestVerdict(outcome.pullRequest, {
|
|
15528
|
+
waited: input.wait !== false,
|
|
15529
|
+
openedHere: sawBranchWithoutPullRequest
|
|
15530
|
+
}) : null;
|
|
15531
|
+
if (input.format === "json") printOutput(outcome, { format: "json" });
|
|
15532
|
+
else {
|
|
15533
|
+
if (input.dryRun)
|
|
15534
|
+
process.stdout.write(
|
|
15535
|
+
`Would suggest ${plan.entries.length} changes against Git ${nativeBase.commitSha.slice(0, 7)}.
|
|
15536
|
+
`
|
|
14537
15537
|
);
|
|
14538
|
-
|
|
14539
|
-
|
|
14540
|
-
|
|
14541
|
-
|
|
14542
|
-
|
|
14543
|
-
|
|
15538
|
+
else if (outcome.operationId)
|
|
15539
|
+
process.stdout.write(
|
|
15540
|
+
`Suggestion accepted: ${outcome.operationId}.
|
|
15541
|
+
`
|
|
15542
|
+
);
|
|
15543
|
+
else
|
|
15544
|
+
process.stdout.write(
|
|
15545
|
+
"These changes are already in your open suggestions.\n"
|
|
15546
|
+
);
|
|
15547
|
+
for (const entry of plan.entries)
|
|
15548
|
+
process.stdout.write(` ${entry.action} ${entry.path}
|
|
15549
|
+
`);
|
|
15550
|
+
if (verdict && !verdict.fails)
|
|
15551
|
+
process.stdout.write(`${verdict.text}
|
|
15552
|
+
`);
|
|
14544
15553
|
}
|
|
14545
|
-
|
|
15554
|
+
if (verdict?.fails) throw new CliFailure(verdict.text, 7);
|
|
14546
15555
|
}
|
|
14547
|
-
|
|
14548
|
-
|
|
14549
|
-
|
|
14550
|
-
|
|
14551
|
-
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
]);
|
|
14556
|
-
function carryingProposals(proposals, baseSnapshotId) {
|
|
14557
|
-
return proposals.filter(
|
|
14558
|
-
(proposal) => proposal.baseSnapshotId === baseSnapshotId && LIVE_SNAPSHOT_STATUSES.has(proposal.status) && (proposal.pullRequest === null || LIVE_PULL_REQUEST_STATES.has(proposal.pullRequest.state))
|
|
14559
|
-
).sort((a, b) => b.version - a.version);
|
|
15556
|
+
|
|
15557
|
+
// src/lib/instructions/prompt.ts
|
|
15558
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
15559
|
+
function canPrompt(streams = {
|
|
15560
|
+
stdin: process.stdin,
|
|
15561
|
+
stderr: process.stderr
|
|
15562
|
+
}) {
|
|
15563
|
+
return streams.stdin?.isTTY === true && streams.stderr?.isTTY === true;
|
|
14560
15564
|
}
|
|
14561
|
-
function
|
|
14562
|
-
const
|
|
14563
|
-
|
|
14564
|
-
|
|
14565
|
-
|
|
14566
|
-
|
|
15565
|
+
async function ask(question, streams) {
|
|
15566
|
+
const reader = createInterface2({
|
|
15567
|
+
input: streams.input,
|
|
15568
|
+
output: streams.output
|
|
15569
|
+
});
|
|
15570
|
+
try {
|
|
15571
|
+
return (await reader.question(question)).trim();
|
|
15572
|
+
} finally {
|
|
15573
|
+
reader.close();
|
|
15574
|
+
}
|
|
15575
|
+
}
|
|
15576
|
+
async function chooseProject(projects, streams = { input: process.stdin, output: process.stderr }) {
|
|
15577
|
+
streams.output.write(
|
|
15578
|
+
`This repository is connected to ${projects.length} projects:
|
|
15579
|
+
`
|
|
14567
15580
|
);
|
|
14568
|
-
const
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
const proposal = carriedBy(entry);
|
|
14572
|
-
if (proposal === void 0) {
|
|
14573
|
-
continue;
|
|
14574
|
-
}
|
|
14575
|
-
setAside.add(entry.path);
|
|
14576
|
-
alreadyProposed.push({
|
|
14577
|
-
path: entry.path,
|
|
14578
|
-
action: entry.action,
|
|
14579
|
-
proposal: {
|
|
14580
|
-
snapshotId: proposal.snapshotId,
|
|
14581
|
-
version: proposal.version,
|
|
14582
|
-
pullRequest: proposal.pullRequest
|
|
14583
|
-
}
|
|
14584
|
-
});
|
|
15581
|
+
for (const [index, project] of projects.entries()) {
|
|
15582
|
+
streams.output.write(` [${index + 1}] ${project.label}
|
|
15583
|
+
`);
|
|
14585
15584
|
}
|
|
14586
|
-
|
|
14587
|
-
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
|
|
14591
|
-
|
|
14592
|
-
};
|
|
15585
|
+
const answer = await ask(`Which one? [1-${projects.length}] `, streams);
|
|
15586
|
+
const number = /^\d+$/.test(answer) ? Number(answer) : 0;
|
|
15587
|
+
return number >= 1 && number <= projects.length ? number - 1 : null;
|
|
15588
|
+
}
|
|
15589
|
+
async function confirm(question, streams = { input: process.stdin, output: process.stderr }) {
|
|
15590
|
+
return /^y(es)?$/i.test(await ask(`${question} [y/N] `, streams));
|
|
14593
15591
|
}
|
|
14594
15592
|
|
|
14595
15593
|
// src/lib/instructions/resolve-project.ts
|
|
@@ -14685,8 +15683,8 @@ async function resolveProjectFromCheckout(input) {
|
|
|
14685
15683
|
}
|
|
14686
15684
|
|
|
14687
15685
|
// src/lib/instructions/self-update.ts
|
|
14688
|
-
import { createHash as
|
|
14689
|
-
import { mkdir as
|
|
15686
|
+
import { createHash as createHash14 } from "crypto";
|
|
15687
|
+
import { mkdir as mkdir10, readFile as readFile9, realpath as realpath5, writeFile as writeFile6 } from "fs/promises";
|
|
14690
15688
|
import path26 from "path";
|
|
14691
15689
|
|
|
14692
15690
|
// src/lib/instructions/npm-tarball.ts
|
|
@@ -14830,7 +15828,7 @@ async function isKeptCopy(script, copy) {
|
|
|
14830
15828
|
}
|
|
14831
15829
|
async function readState2(file) {
|
|
14832
15830
|
try {
|
|
14833
|
-
const parsed = JSON.parse(await
|
|
15831
|
+
const parsed = JSON.parse(await readFile9(file, "utf8"));
|
|
14834
15832
|
return typeof parsed.checkedAt === "number" && Number.isFinite(parsed.checkedAt) && typeof parsed.tarball === "string" ? { checkedAt: parsed.checkedAt, tarball: parsed.tarball } : null;
|
|
14835
15833
|
} catch {
|
|
14836
15834
|
return null;
|
|
@@ -14838,8 +15836,8 @@ async function readState2(file) {
|
|
|
14838
15836
|
}
|
|
14839
15837
|
async function writeState(file, state) {
|
|
14840
15838
|
try {
|
|
14841
|
-
await
|
|
14842
|
-
await
|
|
15839
|
+
await mkdir10(path26.dirname(file), { recursive: true });
|
|
15840
|
+
await writeFile6(file, `${JSON.stringify(state)}
|
|
14843
15841
|
`);
|
|
14844
15842
|
return true;
|
|
14845
15843
|
} catch {
|
|
@@ -14957,7 +15955,7 @@ async function attempt({
|
|
|
14957
15955
|
signal,
|
|
14958
15956
|
fetchImpl
|
|
14959
15957
|
);
|
|
14960
|
-
const digest = `sha512-${
|
|
15958
|
+
const digest = `sha512-${createHash14("sha512").update(tarball).digest("base64")}`;
|
|
14961
15959
|
if (digest !== served.integrity) {
|
|
14962
15960
|
throw new SelfUpdateFailure(
|
|
14963
15961
|
"the download does not match the integrity hash the deployment published"
|
|
@@ -15081,7 +16079,7 @@ var PROJECT_HELP = "Project ID (default: the project this checkout's repository
|
|
|
15081
16079
|
var BASE_URL_HELP = "Deployment to talk to, such as https://example.com (default: the one you are signed in to)";
|
|
15082
16080
|
var REMOTE_HELP = "Use this remote of the checkout (default: whichever fetches from the project's repository)";
|
|
15083
16081
|
function buildInstructionsCommand() {
|
|
15084
|
-
const instructions = new
|
|
16082
|
+
const instructions = new Command13("instructions").description(
|
|
15085
16083
|
"Keep a checkout current with a project's published coding instructions"
|
|
15086
16084
|
);
|
|
15087
16085
|
instructions.command("check").description(
|
|
@@ -15417,6 +16415,70 @@ Waiting up to 5 minutes for the sign-in to finish.
|
|
|
15417
16415
|
});
|
|
15418
16416
|
}
|
|
15419
16417
|
}
|
|
16418
|
+
async function ensureRepositoryTransportScope(opts, client) {
|
|
16419
|
+
let scopes;
|
|
16420
|
+
try {
|
|
16421
|
+
scopes = (await client.auth.whoami()).scopes;
|
|
16422
|
+
} catch (error) {
|
|
16423
|
+
throw fixedFailure(error);
|
|
16424
|
+
}
|
|
16425
|
+
if (scopes.includes(REPOSITORY_TRANSPORT_SCOPE)) {
|
|
16426
|
+
return;
|
|
16427
|
+
}
|
|
16428
|
+
if (opts.hook || runningInCi()) {
|
|
16429
|
+
throw new CliFailure(
|
|
16430
|
+
"This repository checkout needs the repositories:read capability. Run instructions init interactively to approve it, or use a CI API key with instructions:read and repositories:read from your organization's API keys settings.",
|
|
16431
|
+
7
|
|
16432
|
+
);
|
|
16433
|
+
}
|
|
16434
|
+
const origin = deploymentOrigin(opts);
|
|
16435
|
+
try {
|
|
16436
|
+
await signInWithBrowser({
|
|
16437
|
+
baseUrl: opts.baseUrl ?? getBaseUrl() ?? origin,
|
|
16438
|
+
origin,
|
|
16439
|
+
explicit: opts.baseUrl !== void 0,
|
|
16440
|
+
project: opts.project,
|
|
16441
|
+
scopes: [...REQUESTED_SCOPES, REPOSITORY_TRANSPORT_SCOPE],
|
|
16442
|
+
signal: AbortSignal.timeout(SIGN_IN_TIMEOUT_MS),
|
|
16443
|
+
announce: (url) => {
|
|
16444
|
+
process.stderr.write(
|
|
16445
|
+
`Opening your browser to approve repository access at ${origin}.
|
|
16446
|
+
If it does not open, visit:
|
|
16447
|
+
|
|
16448
|
+
${url}
|
|
16449
|
+
|
|
16450
|
+
Waiting up to 5 minutes for the sign-in to finish.
|
|
16451
|
+
|
|
16452
|
+
`
|
|
16453
|
+
);
|
|
16454
|
+
}
|
|
16455
|
+
});
|
|
16456
|
+
} catch {
|
|
16457
|
+
throw new CliFailure(
|
|
16458
|
+
"Repository access was not approved; the native checkout was left unchanged.",
|
|
16459
|
+
7
|
|
16460
|
+
);
|
|
16461
|
+
}
|
|
16462
|
+
}
|
|
16463
|
+
async function directGitTransport(opts, direct) {
|
|
16464
|
+
if (direct.gitGateway?.version !== "v1") {
|
|
16465
|
+
throw new CliFailure(
|
|
16466
|
+
"This Fabric deployment does not support repository Git transport yet. Update Fabric, then run init again.",
|
|
16467
|
+
7
|
|
16468
|
+
);
|
|
16469
|
+
}
|
|
16470
|
+
const origin = deploymentOrigin(opts);
|
|
16471
|
+
const base = new URL(
|
|
16472
|
+
`/api/v1/projects/${encodeURIComponent(opts.project)}/instructions/repository/git/${direct.generation}`,
|
|
16473
|
+
origin
|
|
16474
|
+
);
|
|
16475
|
+
const signal = opts.hook ? AbortSignal.timeout(hookDeadlineMs()) : void 0;
|
|
16476
|
+
const token = getOAuth(origin, opts.project) ? await projectAccessToken(origin, opts.project, signal) : getApiKey(origin, opts.project);
|
|
16477
|
+
if (!token) {
|
|
16478
|
+
throw new NotSignedInError(origin, opts.project);
|
|
16479
|
+
}
|
|
16480
|
+
return { url: base.toString(), authorization: `Bearer ${token}` };
|
|
16481
|
+
}
|
|
15420
16482
|
function hasSignInFor(origin, project) {
|
|
15421
16483
|
if (project === void 0) {
|
|
15422
16484
|
return Boolean(getApiKey(origin));
|
|
@@ -15528,6 +16590,7 @@ var SourceGuard = class {
|
|
|
15528
16590
|
};
|
|
15529
16591
|
function sourceIdentity(published) {
|
|
15530
16592
|
const repository = published.repository ?? null;
|
|
16593
|
+
const direct = directRepositoryRead(published);
|
|
15531
16594
|
return JSON.stringify([
|
|
15532
16595
|
published.sourceOfTruth,
|
|
15533
16596
|
repository === null ? null : [
|
|
@@ -15537,9 +16600,61 @@ function sourceIdentity(published) {
|
|
|
15537
16600
|
repository.ref,
|
|
15538
16601
|
repository.rootPath,
|
|
15539
16602
|
repository.generation
|
|
15540
|
-
]
|
|
16603
|
+
],
|
|
16604
|
+
direct === null ? null : [direct.generation, direct.currentCommitSha]
|
|
15541
16605
|
]);
|
|
15542
16606
|
}
|
|
16607
|
+
function directRepositoryRead(published) {
|
|
16608
|
+
const direct = published.direct;
|
|
16609
|
+
return published.sourceOfTruth === "REPOSITORY" && published.repository !== void 0 && published.repository !== null && direct?.availability === "READY" ? direct : null;
|
|
16610
|
+
}
|
|
16611
|
+
async function directRepositoryCheckoutStatus(input) {
|
|
16612
|
+
const commit = input.direct.currentCommitSha.slice(0, 7);
|
|
16613
|
+
const repository = repositoryName(input.repository);
|
|
16614
|
+
if (input.checkout.class !== "matching") {
|
|
16615
|
+
return {
|
|
16616
|
+
current: false,
|
|
16617
|
+
line: `Coding instructions are read directly from ${repository} at ${commit}. Open a matching native checkout before reading them locally.`
|
|
16618
|
+
};
|
|
16619
|
+
}
|
|
16620
|
+
const head = input.report.state?.head;
|
|
16621
|
+
if (head === null || head === void 0) {
|
|
16622
|
+
return {
|
|
16623
|
+
current: false,
|
|
16624
|
+
line: `Coding instructions are read directly from ${repository} at ${commit}; this checkout could not be compared to that commit.`
|
|
16625
|
+
};
|
|
16626
|
+
}
|
|
16627
|
+
const contains = await isAncestor(
|
|
16628
|
+
input.checkout.toplevel,
|
|
16629
|
+
input.direct.currentCommitSha,
|
|
16630
|
+
head,
|
|
16631
|
+
gitDeadline()
|
|
16632
|
+
);
|
|
16633
|
+
if (contains.kind === "ok" && contains.value) {
|
|
16634
|
+
return {
|
|
16635
|
+
current: true,
|
|
16636
|
+
line: `Coding instructions are read directly from ${repository} at ${commit}; this checkout already contains that commit.`
|
|
16637
|
+
};
|
|
16638
|
+
}
|
|
16639
|
+
if (contains.kind === "ok") {
|
|
16640
|
+
const behind = await isAncestor(
|
|
16641
|
+
input.checkout.toplevel,
|
|
16642
|
+
head,
|
|
16643
|
+
input.direct.currentCommitSha,
|
|
16644
|
+
gitDeadline()
|
|
16645
|
+
);
|
|
16646
|
+
if (behind.kind === "ok") {
|
|
16647
|
+
return {
|
|
16648
|
+
current: false,
|
|
16649
|
+
line: behind.value ? `Coding instructions are read directly from ${repository} at ${commit}; this checkout is behind. Run git pull --ff-only.` : `Coding instructions are read directly from ${repository} at ${commit}; this checkout has diverged. Inspect its Git history before choosing how to update it.`
|
|
16650
|
+
};
|
|
16651
|
+
}
|
|
16652
|
+
}
|
|
16653
|
+
return {
|
|
16654
|
+
current: false,
|
|
16655
|
+
line: `Coding instructions are read directly from ${repository} at ${commit}; fetch that commit with your normal Git remote before continuing.`
|
|
16656
|
+
};
|
|
16657
|
+
}
|
|
15543
16658
|
async function readLockForProject2(destination, projectId) {
|
|
15544
16659
|
const lock = await readLock(destination);
|
|
15545
16660
|
if (lock !== null && lock.projectId !== projectId) {
|
|
@@ -15609,6 +16724,35 @@ async function runCheck(opts, format) {
|
|
|
15609
16724
|
snapshot: published.snapshot,
|
|
15610
16725
|
deadline: gitDeadline()
|
|
15611
16726
|
});
|
|
16727
|
+
const direct = directRepositoryRead(published);
|
|
16728
|
+
if (direct !== null && published.repository) {
|
|
16729
|
+
const status = await directRepositoryCheckoutStatus({
|
|
16730
|
+
repository: published.repository,
|
|
16731
|
+
direct,
|
|
16732
|
+
checkout: guard.checkout ?? { class: "not-git" },
|
|
16733
|
+
report: checkout ?? await reportForClassification({
|
|
16734
|
+
classification: { class: "not-git" },
|
|
16735
|
+
repository: published.repository,
|
|
16736
|
+
snapshot: void 0,
|
|
16737
|
+
deadline: gitDeadline()
|
|
16738
|
+
})
|
|
16739
|
+
});
|
|
16740
|
+
if (format === "json") {
|
|
16741
|
+
printOutput(
|
|
16742
|
+
{
|
|
16743
|
+
projectId: opts.project,
|
|
16744
|
+
destination,
|
|
16745
|
+
direct,
|
|
16746
|
+
checkout: checkout?.json ?? null
|
|
16747
|
+
},
|
|
16748
|
+
{ format: "json" }
|
|
16749
|
+
);
|
|
16750
|
+
} else if (!opts.hook || !status.current) {
|
|
16751
|
+
process.stdout.write(`${status.line}
|
|
16752
|
+
`);
|
|
16753
|
+
}
|
|
16754
|
+
return;
|
|
16755
|
+
}
|
|
15612
16756
|
if (opts.hook && format !== "json" && checkout !== null && checkout.classification.class !== "not-git") {
|
|
15613
16757
|
if (checkout.line !== null) {
|
|
15614
16758
|
line(checkout.line);
|
|
@@ -15866,6 +17010,57 @@ async function syncOnce(opts) {
|
|
|
15866
17010
|
const guard = opts.guard ?? new SourceGuard(destination, opts.remote);
|
|
15867
17011
|
const hint = await lockDigestHint(existing, opts.project);
|
|
15868
17012
|
let published = await fetchPublished(client, opts, hint, guard);
|
|
17013
|
+
const direct = directRepositoryRead(published);
|
|
17014
|
+
const directRepository = published.repository;
|
|
17015
|
+
if (direct !== null) {
|
|
17016
|
+
if (guard.checkout === null || guard.checkout.class !== "matching" || directRepository === null || directRepository === void 0) {
|
|
17017
|
+
throw new CliFailure(
|
|
17018
|
+
"This project reads coding instructions directly from its configured repository. Fabric does not copy them into this folder; open a matching native checkout or rerun init with --clone.",
|
|
17019
|
+
7
|
|
17020
|
+
);
|
|
17021
|
+
}
|
|
17022
|
+
const report = await reportForClassification({
|
|
17023
|
+
classification: guard.checkout,
|
|
17024
|
+
repository: directRepository,
|
|
17025
|
+
snapshot: void 0,
|
|
17026
|
+
deadline: gitDeadline()
|
|
17027
|
+
});
|
|
17028
|
+
const status = await directRepositoryCheckoutStatus({
|
|
17029
|
+
repository: directRepository,
|
|
17030
|
+
direct,
|
|
17031
|
+
checkout: guard.checkout,
|
|
17032
|
+
report
|
|
17033
|
+
});
|
|
17034
|
+
const gitTransport = opts.hook && direct.gitGateway?.version === "v1" ? await directGitTransport(opts, direct) : void 0;
|
|
17035
|
+
const fastForward = opts.hook && guard.checkout.class === "matching" ? await runFastForward({
|
|
17036
|
+
classification: guard.checkout,
|
|
17037
|
+
repository: directRepository,
|
|
17038
|
+
snapshot: void 0,
|
|
17039
|
+
directCommitSha: direct.currentCommitSha,
|
|
17040
|
+
...gitTransport === void 0 ? {} : { gitTransport },
|
|
17041
|
+
report: {
|
|
17042
|
+
...report,
|
|
17043
|
+
reportKind: status.current ? "current" : "behind",
|
|
17044
|
+
line: status.current ? null : status.line
|
|
17045
|
+
},
|
|
17046
|
+
projectId: opts.project,
|
|
17047
|
+
deadline: gitDeadline(),
|
|
17048
|
+
optedOut: opts.fastForward === false,
|
|
17049
|
+
traceFile: traceFile(path28.dirname(getConfigPath()))
|
|
17050
|
+
}) : null;
|
|
17051
|
+
return {
|
|
17052
|
+
kind: "reported",
|
|
17053
|
+
destination: existing,
|
|
17054
|
+
published: false,
|
|
17055
|
+
version: null,
|
|
17056
|
+
checkout: {
|
|
17057
|
+
...report.json,
|
|
17058
|
+
line: status.current ? null : status.line
|
|
17059
|
+
},
|
|
17060
|
+
current: status.current ? status.line : null,
|
|
17061
|
+
fastForward
|
|
17062
|
+
};
|
|
17063
|
+
}
|
|
15869
17064
|
if (guard.checkout !== null && !downloadsIn(guard.checkout, !opts.hook)) {
|
|
15870
17065
|
const refused = opts.hook ? null : refuseManualSync(guard.checkout, published.repository);
|
|
15871
17066
|
if (refused !== null) {
|
|
@@ -16141,6 +17336,25 @@ async function runPush2(opts, format) {
|
|
|
16141
17336
|
const note = opts.message !== void 0 ? splitMessage(opts.message) : void 0;
|
|
16142
17337
|
const destination = destinationOf(opts);
|
|
16143
17338
|
const root = await resolveExistingRoot(destination);
|
|
17339
|
+
const client = instructionsClient(
|
|
17340
|
+
{ ...opts, hook: false },
|
|
17341
|
+
PUSH_TIMEOUT_MS2
|
|
17342
|
+
);
|
|
17343
|
+
const published = await fetchPublished(client, opts);
|
|
17344
|
+
const direct = directRepositoryRead(published);
|
|
17345
|
+
if (direct && published.repository) {
|
|
17346
|
+
await runNativeInstructionPush({
|
|
17347
|
+
...opts,
|
|
17348
|
+
client,
|
|
17349
|
+
projectId: opts.project,
|
|
17350
|
+
root,
|
|
17351
|
+
repository: published.repository,
|
|
17352
|
+
direct,
|
|
17353
|
+
org: orgSlugFor(opts),
|
|
17354
|
+
format
|
|
17355
|
+
});
|
|
17356
|
+
return;
|
|
17357
|
+
}
|
|
16144
17358
|
const lock = await readLockForProject2(root, opts.project);
|
|
16145
17359
|
if (lock === null) {
|
|
16146
17360
|
throw new CliFailure(
|
|
@@ -16148,11 +17362,6 @@ async function runPush2(opts, format) {
|
|
|
16148
17362
|
7
|
|
16149
17363
|
);
|
|
16150
17364
|
}
|
|
16151
|
-
const client = instructionsClient(
|
|
16152
|
-
{ ...opts, hook: false },
|
|
16153
|
-
PUSH_TIMEOUT_MS2
|
|
16154
|
-
);
|
|
16155
|
-
const published = await fetchPublished(client, opts);
|
|
16156
17365
|
if (!published.published || !published.snapshot) {
|
|
16157
17366
|
throw new CliFailure(
|
|
16158
17367
|
"this project has no published coding instructions to change yet; the first version is uploaded from the Coding Instructions tab",
|
|
@@ -16328,169 +17537,6 @@ async function runPush2(opts, format) {
|
|
|
16328
17537
|
throw new CliFailure(verdict.text, 7);
|
|
16329
17538
|
}
|
|
16330
17539
|
}
|
|
16331
|
-
function pushPullRequest(pullRequest, timedOut) {
|
|
16332
|
-
const branch = pullRequest.branch ?? null;
|
|
16333
|
-
return {
|
|
16334
|
-
operationId: pullRequest.operationId,
|
|
16335
|
-
state: pullRequest.state,
|
|
16336
|
-
url: branch?.pullRequest?.url ?? pullRequest.url,
|
|
16337
|
-
failure: pullRequest.failure,
|
|
16338
|
-
timedOut,
|
|
16339
|
-
branch,
|
|
16340
|
-
append: pullRequest.append ?? null
|
|
16341
|
-
};
|
|
16342
|
-
}
|
|
16343
|
-
var BLOCKED_REASONS = {
|
|
16344
|
-
ATTRIBUTION_REJECTED: "the project's name or your display name looks like an address, a link or a credential, so the commit could not name you safely",
|
|
16345
|
-
PERMISSION_REVOKED: "you no longer have permission to suggest changes to this project's repository",
|
|
16346
|
-
CONFIGURATION_CHANGED: "the project's repository settings changed after you suggested this; push again",
|
|
16347
|
-
TARGET_BRANCH_MISSING: "the repository branch the project syncs from no longer exists",
|
|
16348
|
-
BASE_COMMIT_UNAVAILABLE: "the commit this suggestion was based on is no longer in the repository; sync and push again",
|
|
16349
|
-
TREE_CONFLICT: "the repository changed the same files since your last sync; sync and push again",
|
|
16350
|
-
AUTHENTICATION_FAILED: "the project's repository connection needs to be reconnected",
|
|
16351
|
-
REPOSITORY_UNAVAILABLE: "the repository could not be reached",
|
|
16352
|
-
BRANCH_WRITE_REFUSED: "the repository refused the new branch",
|
|
16353
|
-
PR_CREATION_REFUSED: "the repository refused to open the pull request",
|
|
16354
|
-
REMOTE_REF_CONFLICT: "a branch with the same name already exists in the repository",
|
|
16355
|
-
CREATE_OUTCOME_UNKNOWN: "Fabric could not confirm whether the pull request was created",
|
|
16356
|
-
VALIDATION_TIMEOUT: "the files' checks did not finish in time",
|
|
16357
|
-
LIMITS_EXCEEDED: "the change is larger than Fabric can open as a pull request",
|
|
16358
|
-
PROVIDER_RATE_LIMITED: "the repository's provider is limiting requests",
|
|
16359
|
-
PROVIDER_TEMPORARY: "the repository's provider had a temporary problem",
|
|
16360
|
-
LOOKUP_INCONCLUSIVE: "Fabric could not confirm the pull request's state",
|
|
16361
|
-
// Member proposal branches (Fizzy #2738 spec §9). BRANCH_CONFLICT and
|
|
16362
|
-
// SUPERSEDED_BY_LATER_CHANGE name their paths, in `blockedReason`.
|
|
16363
|
-
BRANCH_MOVED: "your branch kept changing while Fabric was adding to it",
|
|
16364
|
-
BRANCH_NAME_UNAVAILABLE: "Fabric could not reserve a branch name for your pull request; ask an owner to remove old proposal branches",
|
|
16365
|
-
REPOSITORY_CHANGED: "the repository connection now points at another repository, so Fabric can no longer reach your pull request; close it in the repository, then stop tracking it in the Coding Instructions tab",
|
|
16366
|
-
PUSH_OUTCOME_UNKNOWN: "Fabric could not confirm whether this change reached your branch, which may have been changed outside Fabric; check the branch, then try again or withdraw it",
|
|
16367
|
-
BRANCH_MISSING: "your branch was deleted in the repository"
|
|
16368
|
-
};
|
|
16369
|
-
var FAILURE_PATHS_LISTED = 20;
|
|
16370
|
-
function failurePaths(failure) {
|
|
16371
|
-
const paths = failure.params?.paths;
|
|
16372
|
-
const count = failure.params?.count;
|
|
16373
|
-
if (typeof paths !== "string" || paths === "") {
|
|
16374
|
-
return "some of its files";
|
|
16375
|
-
}
|
|
16376
|
-
return typeof count === "number" && count > FAILURE_PATHS_LISTED ? `${paths} and ${count - FAILURE_PATHS_LISTED} more` : paths;
|
|
16377
|
-
}
|
|
16378
|
-
function blockedReason(failure) {
|
|
16379
|
-
switch (failure.code) {
|
|
16380
|
-
case "BRANCH_CONFLICT":
|
|
16381
|
-
return `files on your branch were changed outside Fabric (${failurePaths(failure)}), and Fabric does not overwrite them while the pull request is open; make this change on your branch in the repository, or wait until the pull request merges`;
|
|
16382
|
-
case "SUPERSEDED_BY_LATER_CHANGE":
|
|
16383
|
-
return `a newer change of yours already edits ${failurePaths(failure)}; use Try again to apply this one on top of it, or withdraw it`;
|
|
16384
|
-
default:
|
|
16385
|
-
return BLOCKED_REASONS[failure.code] ?? `it stopped with ${failure.code}`;
|
|
16386
|
-
}
|
|
16387
|
-
}
|
|
16388
|
-
function pullRequestVerdict(pullRequest, { waited, openedHere }) {
|
|
16389
|
-
const url = pullRequest.url ? `: ${pullRequest.url}` : "";
|
|
16390
|
-
const branch = pullRequest.branch;
|
|
16391
|
-
if (!waited) {
|
|
16392
|
-
return {
|
|
16393
|
-
text: "Follow its pull request in the project's Coding Instructions tab.",
|
|
16394
|
-
fails: false
|
|
16395
|
-
};
|
|
16396
|
-
}
|
|
16397
|
-
if (pullRequest.timedOut) {
|
|
16398
|
-
return {
|
|
16399
|
-
text: "The pull request is being opened; see the project's Coding Instructions tab.",
|
|
16400
|
-
fails: false
|
|
16401
|
-
};
|
|
16402
|
-
}
|
|
16403
|
-
switch (pullRequest.state) {
|
|
16404
|
-
case "OPEN": {
|
|
16405
|
-
if (!branch) {
|
|
16406
|
-
return { text: `Pull request opened${url}`, fails: false };
|
|
16407
|
-
}
|
|
16408
|
-
if (branch.state === "MERGED") {
|
|
16409
|
-
return {
|
|
16410
|
-
text: `Your pull request was merged${url} while this change was being added to it; see the project's Coding Instructions tab for whether it carried this change.`,
|
|
16411
|
-
fails: false
|
|
16412
|
-
};
|
|
16413
|
-
}
|
|
16414
|
-
if (branch.state === "CLOSED") {
|
|
16415
|
-
return {
|
|
16416
|
-
text: `Your pull request was closed without merging${url} while this change was being added to it.`,
|
|
16417
|
-
fails: false
|
|
16418
|
-
};
|
|
16419
|
-
}
|
|
16420
|
-
if (branch.failure && (branch.state === "BLOCKED" || branchStoppedByFailure(branch))) {
|
|
16421
|
-
const next = branch.failure.retryable ? "Fabric will try again on its own; see" : "See";
|
|
16422
|
-
const what = branch.pullRequest ? `Your change is on your branch, but Fabric can no longer update its pull request${url}` : "Your change is on your branch, but Fabric could not open its pull request";
|
|
16423
|
-
return {
|
|
16424
|
-
text: `${what}: ${blockedReason(branch.failure)}. ${next} the project's Coding Instructions tab.`,
|
|
16425
|
-
fails: true
|
|
16426
|
-
};
|
|
16427
|
-
}
|
|
16428
|
-
if (branch.state === "CLOSE_REQUESTED") {
|
|
16429
|
-
return {
|
|
16430
|
-
text: "Your pull request is being closed, and this change leaves with it; push again once it has closed.",
|
|
16431
|
-
fails: true
|
|
16432
|
-
};
|
|
16433
|
-
}
|
|
16434
|
-
if (branch.state === "CANCELED") {
|
|
16435
|
-
return {
|
|
16436
|
-
text: "Your branch was closed before its pull request opened; push again.",
|
|
16437
|
-
fails: true
|
|
16438
|
-
};
|
|
16439
|
-
}
|
|
16440
|
-
if (branch.pullRequest?.state === "OPEN") {
|
|
16441
|
-
return {
|
|
16442
|
-
text: openedHere ? `Opened pull request ${branch.pullRequest.url}` : `Added to your pull request ${branch.pullRequest.url}`,
|
|
16443
|
-
fails: false
|
|
16444
|
-
};
|
|
16445
|
-
}
|
|
16446
|
-
return {
|
|
16447
|
-
text: "Your change is on your branch and its pull request is being opened; see the project's Coding Instructions tab.",
|
|
16448
|
-
fails: false
|
|
16449
|
-
};
|
|
16450
|
-
}
|
|
16451
|
-
case "MERGED":
|
|
16452
|
-
return {
|
|
16453
|
-
text: `Its pull request was already merged${url}. Run \`${fabricCommand("instructions sync")}\` once the project has synced it.`,
|
|
16454
|
-
fails: false
|
|
16455
|
-
};
|
|
16456
|
-
case "CLOSED":
|
|
16457
|
-
return {
|
|
16458
|
-
text: `Its pull request was closed without merging${url}.`,
|
|
16459
|
-
fails: false
|
|
16460
|
-
};
|
|
16461
|
-
case "CANCELED":
|
|
16462
|
-
if (pullRequest.failure?.code === "ALREADY_ON_BRANCH") {
|
|
16463
|
-
return {
|
|
16464
|
-
text: branch?.pullRequest ? `Already on your branch; nothing to add. Your pull request: ${branch.pullRequest.url}` : "Already on your branch; nothing to add.",
|
|
16465
|
-
fails: false
|
|
16466
|
-
};
|
|
16467
|
-
}
|
|
16468
|
-
return {
|
|
16469
|
-
text: pullRequest.failure?.code === "VALIDATION_REJECTED" ? "The suggestion did not pass Fabric's checks, so no pull request was opened; see the project's Coding Instructions tab." : branch ? "The suggestion was withdrawn before it was added to your pull request." : "The suggestion was withdrawn before a pull request was opened.",
|
|
16470
|
-
fails: true
|
|
16471
|
-
};
|
|
16472
|
-
case "CLOSE_REQUESTED":
|
|
16473
|
-
return {
|
|
16474
|
-
text: "An earlier attempt at this change was withdrawn and its pull request is being closed; push again once it has closed.",
|
|
16475
|
-
fails: true
|
|
16476
|
-
};
|
|
16477
|
-
case "BLOCKED": {
|
|
16478
|
-
const failure = pullRequest.failure ?? branch?.failure ?? null;
|
|
16479
|
-
const reason = failure ? blockedReason(failure) : "it stopped with an unknown failure";
|
|
16480
|
-
const next = failure?.retryable ? "Fabric will try again on its own; see" : "See";
|
|
16481
|
-
const what = branch ? "Fabric could not add this change to your pull request" : "Fabric could not open the pull request";
|
|
16482
|
-
return {
|
|
16483
|
-
text: `${what}: ${reason}. ${next} the project's Coding Instructions tab.`,
|
|
16484
|
-
fails: true
|
|
16485
|
-
};
|
|
16486
|
-
}
|
|
16487
|
-
default:
|
|
16488
|
-
return {
|
|
16489
|
-
text: "The pull request is being opened; see the project's Coding Instructions tab.",
|
|
16490
|
-
fails: false
|
|
16491
|
-
};
|
|
16492
|
-
}
|
|
16493
|
-
}
|
|
16494
17540
|
function asPushFailure(error) {
|
|
16495
17541
|
const code = error.code;
|
|
16496
17542
|
switch (code) {
|
|
@@ -16583,6 +17629,8 @@ function reportPush(outcome) {
|
|
|
16583
17629
|
);
|
|
16584
17630
|
}
|
|
16585
17631
|
function proposalReference(proposal) {
|
|
17632
|
+
if (proposal.version === null)
|
|
17633
|
+
return proposal.pullRequestUrl ? `pull request ${proposal.pullRequestUrl}` : "a repository suggestion";
|
|
16586
17634
|
if (proposal.pullRequestUrl) {
|
|
16587
17635
|
return `version ${proposal.version}, pull request ${proposal.pullRequestUrl}`;
|
|
16588
17636
|
}
|
|
@@ -16594,6 +17642,7 @@ function proposalReference(proposal) {
|
|
|
16594
17642
|
function proposalReferences(alreadyProposed) {
|
|
16595
17643
|
const byVersion = /* @__PURE__ */ new Map();
|
|
16596
17644
|
for (const entry of alreadyProposed) {
|
|
17645
|
+
if (entry.proposal.version === null) continue;
|
|
16597
17646
|
byVersion.set(
|
|
16598
17647
|
entry.proposal.version,
|
|
16599
17648
|
proposalReference({
|
|
@@ -16628,15 +17677,16 @@ async function isEmptyFolder(folder) {
|
|
|
16628
17677
|
return error.code === "ENOENT";
|
|
16629
17678
|
}
|
|
16630
17679
|
}
|
|
16631
|
-
async function cloneProjectRepository(opts, destination, repository) {
|
|
17680
|
+
async function cloneProjectRepository(opts, destination, repository, transport) {
|
|
16632
17681
|
if (repository === null) {
|
|
16633
17682
|
throw outcomeFailure("no-clone-url", { repo: "its repository" });
|
|
16634
17683
|
}
|
|
16635
17684
|
const repo = repositoryName(repository);
|
|
16636
|
-
const
|
|
16637
|
-
if (
|
|
17685
|
+
const providerUrl = cloneUrlFor(repository);
|
|
17686
|
+
if (providerUrl === null) {
|
|
16638
17687
|
throw outcomeFailure("no-clone-url", { repo });
|
|
16639
17688
|
}
|
|
17689
|
+
const url = transport?.url ?? providerUrl;
|
|
16640
17690
|
const ref = sanitizeDisplayText(repository.ref, 255);
|
|
16641
17691
|
const wanted = opts.clone !== void 0 || canPrompt() && await confirm(`Clone ${repo} (${ref}) into this folder?`);
|
|
16642
17692
|
if (!wanted) {
|
|
@@ -16648,14 +17698,15 @@ async function cloneProjectRepository(opts, destination, repository) {
|
|
|
16648
17698
|
root,
|
|
16649
17699
|
url,
|
|
16650
17700
|
repository.ref,
|
|
16651
|
-
Date.now() + CLONE_TIMEOUT_MS
|
|
17701
|
+
Date.now() + CLONE_TIMEOUT_MS,
|
|
17702
|
+
transport === null ? {} : { httpAuthorization: transport }
|
|
16652
17703
|
);
|
|
16653
17704
|
if (result.kind !== "cloned") {
|
|
16654
17705
|
throw outcomeFailure("clone-failed", {
|
|
16655
17706
|
repo,
|
|
16656
17707
|
ref: repository.ref,
|
|
16657
17708
|
host: repository.host,
|
|
16658
|
-
url: shellQuote(
|
|
17709
|
+
url: shellQuote(providerUrl),
|
|
16659
17710
|
reason: result.kind === "failed" && result.reason !== "not-empty" ? result.reason : "other",
|
|
16660
17711
|
login: providerLoginCommand(
|
|
16661
17712
|
repository.provider,
|
|
@@ -16663,6 +17714,20 @@ async function cloneProjectRepository(opts, destination, repository) {
|
|
|
16663
17714
|
)
|
|
16664
17715
|
});
|
|
16665
17716
|
}
|
|
17717
|
+
if (transport !== null) {
|
|
17718
|
+
const restored = await setRemoteUrl(
|
|
17719
|
+
root,
|
|
17720
|
+
"origin",
|
|
17721
|
+
providerUrl,
|
|
17722
|
+
Date.now() + CLONE_TIMEOUT_MS
|
|
17723
|
+
);
|
|
17724
|
+
if (restored.kind !== "ok") {
|
|
17725
|
+
throw new CliFailure(
|
|
17726
|
+
"Fabric cloned the repository but could not restore its provider remote. Remove this new checkout and run init again.",
|
|
17727
|
+
7
|
|
17728
|
+
);
|
|
17729
|
+
}
|
|
17730
|
+
}
|
|
16666
17731
|
return classifyCheckout({
|
|
16667
17732
|
destination: root,
|
|
16668
17733
|
repository,
|
|
@@ -16697,6 +17762,8 @@ async function runInit(opts, format) {
|
|
|
16697
17762
|
freshCloneDestination
|
|
16698
17763
|
);
|
|
16699
17764
|
const published = await fetchPublished(client, opts, void 0, guard);
|
|
17765
|
+
const direct = directRepositoryRead(published);
|
|
17766
|
+
let repositoryTransportApproved = false;
|
|
16700
17767
|
const repository = published.repository ?? null;
|
|
16701
17768
|
let checkout = guard.checkout;
|
|
16702
17769
|
if (cloneFolder !== void 0 && checkout === null) {
|
|
@@ -16719,10 +17786,17 @@ async function runInit(opts, format) {
|
|
|
16719
17786
|
throw planned.failure;
|
|
16720
17787
|
}
|
|
16721
17788
|
if (planned.kind === "empty-folder") {
|
|
17789
|
+
let transport = null;
|
|
17790
|
+
if (direct?.gitGateway?.version === "v1") {
|
|
17791
|
+
await ensureRepositoryTransportScope(opts, client);
|
|
17792
|
+
repositoryTransportApproved = true;
|
|
17793
|
+
transport = await directGitTransport(opts, direct);
|
|
17794
|
+
}
|
|
16722
17795
|
checkout = await cloneProjectRepository(
|
|
16723
17796
|
opts,
|
|
16724
17797
|
destination,
|
|
16725
|
-
repository
|
|
17798
|
+
repository,
|
|
17799
|
+
transport
|
|
16726
17800
|
);
|
|
16727
17801
|
cloned = true;
|
|
16728
17802
|
if (cloneFolder !== void 0 && checkout.class === "unmapped") {
|
|
@@ -16819,6 +17893,10 @@ async function runInit(opts, format) {
|
|
|
16819
17893
|
);
|
|
16820
17894
|
}
|
|
16821
17895
|
}
|
|
17896
|
+
if (direct?.gitGateway?.version === "v1" && matching !== null && !repositoryTransportApproved) {
|
|
17897
|
+
await ensureRepositoryTransportScope(opts, client);
|
|
17898
|
+
repositoryTransportApproved = true;
|
|
17899
|
+
}
|
|
16822
17900
|
let outcome = null;
|
|
16823
17901
|
if (checkout === null && published.published) {
|
|
16824
17902
|
const synced = await syncOnce({ ...opts, hook: false, guard });
|
|
@@ -16943,7 +18021,22 @@ async function runInit(opts, format) {
|
|
|
16943
18021
|
line("Removed the Stop hook for lesson capture.");
|
|
16944
18022
|
}
|
|
16945
18023
|
}
|
|
16946
|
-
if (matching !== null && repository !== null) {
|
|
18024
|
+
if (matching !== null && repository !== null && direct !== null) {
|
|
18025
|
+
const report = await reportForClassification({
|
|
18026
|
+
classification: matching,
|
|
18027
|
+
repository,
|
|
18028
|
+
snapshot: void 0,
|
|
18029
|
+
deadline: gitDeadline()
|
|
18030
|
+
});
|
|
18031
|
+
line(
|
|
18032
|
+
(await directRepositoryCheckoutStatus({
|
|
18033
|
+
repository,
|
|
18034
|
+
direct,
|
|
18035
|
+
checkout: matching,
|
|
18036
|
+
report
|
|
18037
|
+
})).line
|
|
18038
|
+
);
|
|
18039
|
+
} else if (matching !== null && repository !== null) {
|
|
16947
18040
|
if (published.snapshot?.source?.kind !== "REPOSITORY") {
|
|
16948
18041
|
line(nothingPublishedLine(repository));
|
|
16949
18042
|
}
|
|
@@ -17042,9 +18135,9 @@ async function runLessonPromptCommand(opts) {
|
|
|
17042
18135
|
}
|
|
17043
18136
|
|
|
17044
18137
|
// src/commands/mcp/index.ts
|
|
17045
|
-
import { Command as
|
|
18138
|
+
import { Command as Command14 } from "commander";
|
|
17046
18139
|
function buildMcpCommand() {
|
|
17047
|
-
const mcp = new
|
|
18140
|
+
const mcp = new Command14("mcp").description(
|
|
17048
18141
|
"Manage MCP servers and configurations"
|
|
17049
18142
|
);
|
|
17050
18143
|
mcp.command("servers").description("List accessible MCP servers (system + custom)").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
@@ -17082,7 +18175,7 @@ function buildMcpCommand() {
|
|
|
17082
18175
|
});
|
|
17083
18176
|
}
|
|
17084
18177
|
);
|
|
17085
|
-
const configs = new
|
|
18178
|
+
const configs = new Command14("configs").description(
|
|
17086
18179
|
"Manage MCP configurations"
|
|
17087
18180
|
);
|
|
17088
18181
|
configs.command("list", { isDefault: true }).description("List your MCP configurations").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
@@ -17186,9 +18279,9 @@ function buildMcpCommand() {
|
|
|
17186
18279
|
}
|
|
17187
18280
|
|
|
17188
18281
|
// src/commands/orgs/index.ts
|
|
17189
|
-
import { Command as
|
|
18282
|
+
import { Command as Command15 } from "commander";
|
|
17190
18283
|
function buildOrgsCommand() {
|
|
17191
|
-
const orgs = new
|
|
18284
|
+
const orgs = new Command15("orgs").description("Manage organizations");
|
|
17192
18285
|
orgs.command("list").description("List organizations you belong to").option(
|
|
17193
18286
|
"--format <format>",
|
|
17194
18287
|
"Output format: table|json|yaml|csv",
|
|
@@ -17244,9 +18337,9 @@ function buildOrgsCommand() {
|
|
|
17244
18337
|
}
|
|
17245
18338
|
|
|
17246
18339
|
// src/commands/projects/index.ts
|
|
17247
|
-
import { Command as
|
|
18340
|
+
import { Command as Command16 } from "commander";
|
|
17248
18341
|
function buildProjectsCommand() {
|
|
17249
|
-
const projects = new
|
|
18342
|
+
const projects = new Command16("projects").description("Manage projects");
|
|
17250
18343
|
projects.command("list").description("List projects").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
17251
18344
|
"--status <status>",
|
|
17252
18345
|
"Filter by status: ACTIVE|DRAFT|COMPLETED|ARCHIVED"
|
|
@@ -17378,9 +18471,9 @@ function buildProjectsCommand() {
|
|
|
17378
18471
|
}
|
|
17379
18472
|
|
|
17380
18473
|
// src/commands/prompts/index.ts
|
|
17381
|
-
import { Command as
|
|
18474
|
+
import { Command as Command17 } from "commander";
|
|
17382
18475
|
function buildPromptsCommand() {
|
|
17383
|
-
const prompts = new
|
|
18476
|
+
const prompts = new Command17("prompts").description(
|
|
17384
18477
|
"Manage prompt templates"
|
|
17385
18478
|
);
|
|
17386
18479
|
prompts.command("list").description("List prompt templates").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--search <query>", "Search by name").option("--category <cat>", "Filter by category").option("--limit <n>", "Max results", "50").option(
|
|
@@ -17532,12 +18625,12 @@ ${content}
|
|
|
17532
18625
|
}
|
|
17533
18626
|
|
|
17534
18627
|
// src/commands/reports/index.ts
|
|
17535
|
-
import { Command as
|
|
18628
|
+
import { Command as Command18 } from "commander";
|
|
17536
18629
|
function buildReportsCommand() {
|
|
17537
|
-
const reports = new
|
|
18630
|
+
const reports = new Command18("reports").description(
|
|
17538
18631
|
"Manage report templates and executions"
|
|
17539
18632
|
);
|
|
17540
|
-
const templates = new
|
|
18633
|
+
const templates = new Command18("templates").description(
|
|
17541
18634
|
"Manage report templates"
|
|
17542
18635
|
);
|
|
17543
18636
|
templates.command("list", { isDefault: true }).description("List report templates").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--search <query>", "Search by name").option("--category <cat>", "Filter by category").option("--limit <n>", "Max results (default 20)", "20").option(
|
|
@@ -17610,7 +18703,7 @@ function buildReportsCommand() {
|
|
|
17610
18703
|
}
|
|
17611
18704
|
);
|
|
17612
18705
|
reports.addCommand(templates);
|
|
17613
|
-
const executions = new
|
|
18706
|
+
const executions = new Command18("executions").description(
|
|
17614
18707
|
"View report executions"
|
|
17615
18708
|
);
|
|
17616
18709
|
executions.command("list", { isDefault: true }).description("List report executions").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--limit <n>", "Max results (default 20)", "20").option(
|
|
@@ -17654,9 +18747,9 @@ function buildReportsCommand() {
|
|
|
17654
18747
|
}
|
|
17655
18748
|
|
|
17656
18749
|
// src/commands/skills/index.ts
|
|
17657
|
-
import { Command as
|
|
18750
|
+
import { Command as Command19 } from "commander";
|
|
17658
18751
|
function buildSkillsCommand() {
|
|
17659
|
-
const skills = new
|
|
18752
|
+
const skills = new Command19("skills").description(
|
|
17660
18753
|
"Browse skill definitions"
|
|
17661
18754
|
);
|
|
17662
18755
|
skills.command("list").description("List skills").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--search <query>", "Search by name").option("--category <cat>", "Filter by category").option("--limit <n>", "Max results", "50").option(
|
|
@@ -17722,9 +18815,9 @@ function buildSkillsCommand() {
|
|
|
17722
18815
|
}
|
|
17723
18816
|
|
|
17724
18817
|
// src/commands/workflows/index.ts
|
|
17725
|
-
import { Command as
|
|
18818
|
+
import { Command as Command20 } from "commander";
|
|
17726
18819
|
function buildWorkflowsCommand() {
|
|
17727
|
-
const workflows = new
|
|
18820
|
+
const workflows = new Command20("workflows").description("Manage workflows");
|
|
17728
18821
|
workflows.command("list").description("List workflows").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--status <status>", "Filter: DRAFT|ACTIVE|PAUSED|ARCHIVED").option("--search <query>", "Search by name").option("--limit <n>", "Max results", "20").option(
|
|
17729
18822
|
"--format <format>",
|
|
17730
18823
|
"Output format: table|json|yaml|csv",
|
|
@@ -17851,9 +18944,9 @@ function buildWorkflowsCommand() {
|
|
|
17851
18944
|
}
|
|
17852
18945
|
|
|
17853
18946
|
// src/commands/workspaces/index.ts
|
|
17854
|
-
import { Command as
|
|
18947
|
+
import { Command as Command21 } from "commander";
|
|
17855
18948
|
function buildWorkspacesCommand() {
|
|
17856
|
-
const workspaces = new
|
|
18949
|
+
const workspaces = new Command21("workspaces").description(
|
|
17857
18950
|
"Manage workspaces"
|
|
17858
18951
|
);
|
|
17859
18952
|
workspaces.command("list").description("List workspaces").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--search <query>", "Search by name").option("--status <status>", "Filter: ACTIVE|ARCHIVED").option("--limit <n>", "Max results (default 20)", "20").option(
|
|
@@ -17928,13 +19021,13 @@ function buildWorkspacesCommand() {
|
|
|
17928
19021
|
}
|
|
17929
19022
|
|
|
17930
19023
|
// src/bin/fabric.ts
|
|
17931
|
-
var program = new
|
|
19024
|
+
var program = new Command22();
|
|
17932
19025
|
program.name("fabric").description("Fabric AI platform CLI").version(package_default.version).option(
|
|
17933
19026
|
"--format <format>",
|
|
17934
19027
|
"Output format: table|json|yaml|csv",
|
|
17935
19028
|
process.env.FABRIC_FORMAT ?? "table"
|
|
17936
19029
|
);
|
|
17937
|
-
var auth = new
|
|
19030
|
+
var auth = new Command22("auth").description(
|
|
17938
19031
|
"Authenticate and manage API keys"
|
|
17939
19032
|
);
|
|
17940
19033
|
auth.addCommand(buildLoginCommand());
|
|
@@ -17942,6 +19035,7 @@ auth.addCommand(buildLogoutCommand());
|
|
|
17942
19035
|
auth.addCommand(buildWhoamiCommand());
|
|
17943
19036
|
auth.addCommand(buildKeysCommand());
|
|
17944
19037
|
program.addCommand(auth);
|
|
19038
|
+
program.addCommand(buildConnectCommand());
|
|
17945
19039
|
program.addCommand(buildCtxCommand());
|
|
17946
19040
|
program.addCommand(buildOrgsCommand());
|
|
17947
19041
|
program.addCommand(buildProjectsCommand());
|
|
@@ -17958,7 +19052,7 @@ program.addCommand(buildWorkspacesCommand());
|
|
|
17958
19052
|
program.addCommand(buildChatsCommand());
|
|
17959
19053
|
program.addCommand(buildReportsCommand());
|
|
17960
19054
|
program.addCommand(buildCompletionCommand());
|
|
17961
|
-
var config = new
|
|
19055
|
+
var config = new Command22("config").description("Show CLI configuration");
|
|
17962
19056
|
config.command("path").description("Print the path to the config file").action(() => {
|
|
17963
19057
|
process.stdout.write(`${getConfigPath()}
|
|
17964
19058
|
`);
|