hunter-harness 0.2.12 → 0.2.13
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/bin.js +901 -380
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1115,7 +1115,7 @@ async function assertNoSymlinks(root, relativePath) {
|
|
|
1115
1115
|
async function withRetry(action, options) {
|
|
1116
1116
|
const attempts = options.attempts ?? 3;
|
|
1117
1117
|
const sleep = options.sleep ?? (async (milliseconds) => {
|
|
1118
|
-
await new Promise((
|
|
1118
|
+
await new Promise((resolve8) => setTimeout(resolve8, milliseconds));
|
|
1119
1119
|
});
|
|
1120
1120
|
let lastError;
|
|
1121
1121
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
@@ -3457,8 +3457,8 @@ function resolvePushAuth(input) {
|
|
|
3457
3457
|
}
|
|
3458
3458
|
|
|
3459
3459
|
// ../core/dist/push/push.js
|
|
3460
|
-
import { lstat as
|
|
3461
|
-
import { join as
|
|
3460
|
+
import { lstat as lstat3, readFile as readFile10, readdir as readdir4, rm as rm5 } from "node:fs/promises";
|
|
3461
|
+
import { join as join11, relative, resolve as resolve6 } from "node:path";
|
|
3462
3462
|
import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
|
|
3463
3463
|
|
|
3464
3464
|
// ../core/dist/state/baseline.js
|
|
@@ -3519,6 +3519,659 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
3519
3519
|
throw new Error("unable to acquire protocol lock");
|
|
3520
3520
|
}
|
|
3521
3521
|
|
|
3522
|
+
// ../core/dist/sync/synchronize.js
|
|
3523
|
+
import { lstat as lstat2, readFile as readFile9, rm as rm4 } from "node:fs/promises";
|
|
3524
|
+
import { join as join10, resolve as resolve5 } from "node:path";
|
|
3525
|
+
|
|
3526
|
+
// ../core/dist/update/conflicts.js
|
|
3527
|
+
function operationTargetPath(operation) {
|
|
3528
|
+
return operation.operation === "rename" ? operation.to_path : operation.path;
|
|
3529
|
+
}
|
|
3530
|
+
function operationSourcePath(operation) {
|
|
3531
|
+
return operation.operation === "rename" ? operation.from_path : operation.path;
|
|
3532
|
+
}
|
|
3533
|
+
function operationAlreadyApplied(operation, baseline, projectVersion) {
|
|
3534
|
+
const target = baseline.files[operationTargetPath(operation)];
|
|
3535
|
+
if (operation.operation === "delete") {
|
|
3536
|
+
return target?.deleted === true && target.last_applied_version === projectVersion;
|
|
3537
|
+
}
|
|
3538
|
+
return target?.deleted === false && target?.last_applied_version === projectVersion && target.baseline_hash === operation.content_sha256;
|
|
3539
|
+
}
|
|
3540
|
+
function managedBlockDirty(currentContent, managedBlockHash) {
|
|
3541
|
+
if (managedBlockHash === void 0) {
|
|
3542
|
+
return true;
|
|
3543
|
+
}
|
|
3544
|
+
const block = extractManagedBlock(currentContent);
|
|
3545
|
+
return block === null || sha256Bytes(block) !== managedBlockHash;
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
// ../core/dist/sync/artifact-rebase.js
|
|
3549
|
+
function expectedBase(operation) {
|
|
3550
|
+
return operation.operation === "add" ? null : operation.base_content_sha256;
|
|
3551
|
+
}
|
|
3552
|
+
function contentHash(operation) {
|
|
3553
|
+
return operation.operation === "delete" ? null : operation.content_sha256;
|
|
3554
|
+
}
|
|
3555
|
+
function baselineEntryFor(operation, finalContent, projectVersion) {
|
|
3556
|
+
const block = finalContent === null ? null : extractManagedBlock(finalContent);
|
|
3557
|
+
const remoteHash = contentHash(operation);
|
|
3558
|
+
const localHash = finalContent === null ? null : sha256Bytes(finalContent);
|
|
3559
|
+
return {
|
|
3560
|
+
baseline_hash: remoteHash,
|
|
3561
|
+
local_hash_at_apply: localHash,
|
|
3562
|
+
file_kind: operation.file_kind,
|
|
3563
|
+
last_applied_version: projectVersion,
|
|
3564
|
+
deleted: operation.operation === "delete",
|
|
3565
|
+
...block === null ? {} : { managed_block_hash: sha256Bytes(block) }
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
function acknowledgedEntry(operation, context, projectVersion, reason) {
|
|
3569
|
+
void reason;
|
|
3570
|
+
const localContent = operation.operation === "delete" ? context.sourceContent : context.targetContent ?? context.sourceContent;
|
|
3571
|
+
const block = localContent === null ? null : extractManagedBlock(localContent);
|
|
3572
|
+
return {
|
|
3573
|
+
baseline_hash: contentHash(operation),
|
|
3574
|
+
local_hash_at_apply: localContent === null ? null : sha256Bytes(localContent),
|
|
3575
|
+
file_kind: operation.file_kind,
|
|
3576
|
+
last_applied_version: projectVersion,
|
|
3577
|
+
deleted: operation.operation === "delete",
|
|
3578
|
+
...block === null ? {} : { managed_block_hash: sha256Bytes(block) }
|
|
3579
|
+
};
|
|
3580
|
+
}
|
|
3581
|
+
function deletedSourceEntry(operation, projectVersion) {
|
|
3582
|
+
return {
|
|
3583
|
+
baseline_hash: null,
|
|
3584
|
+
local_hash_at_apply: null,
|
|
3585
|
+
file_kind: operation.file_kind,
|
|
3586
|
+
last_applied_version: projectVersion,
|
|
3587
|
+
deleted: true
|
|
3588
|
+
};
|
|
3589
|
+
}
|
|
3590
|
+
function isRenameTargetCollision(operation, targetContent, incomingHash) {
|
|
3591
|
+
if (operation.operation !== "rename" || targetContent === null) {
|
|
3592
|
+
return false;
|
|
3593
|
+
}
|
|
3594
|
+
return incomingHash === null || sha256Bytes(targetContent) !== incomingHash;
|
|
3595
|
+
}
|
|
3596
|
+
function resolveDecision(path, reason, operation, conflictStrategy, resolveOverrides) {
|
|
3597
|
+
if (reason === "target-collision") {
|
|
3598
|
+
return "conflict";
|
|
3599
|
+
}
|
|
3600
|
+
if (reason === "local-dirty" && operation.operation === "rename") {
|
|
3601
|
+
return "conflict";
|
|
3602
|
+
}
|
|
3603
|
+
const override = resolveOverrides?.get(path);
|
|
3604
|
+
if (override !== void 0) {
|
|
3605
|
+
return override;
|
|
3606
|
+
}
|
|
3607
|
+
if (conflictStrategy === "keep-local") {
|
|
3608
|
+
return "keep-local";
|
|
3609
|
+
}
|
|
3610
|
+
if (conflictStrategy === "accept-remote") {
|
|
3611
|
+
return "accept-remote";
|
|
3612
|
+
}
|
|
3613
|
+
return "conflict";
|
|
3614
|
+
}
|
|
3615
|
+
function computeFinalContent(operation, policy, targetContent, sourceContent, incoming) {
|
|
3616
|
+
const incomingHash = contentHash(operation);
|
|
3617
|
+
let equivalent = incomingHash !== null && targetContent !== null && sha256Bytes(targetContent) === incomingHash;
|
|
3618
|
+
let finalContent = incoming;
|
|
3619
|
+
if (policy.update_policy === "managed-block-only") {
|
|
3620
|
+
if (operation.operation === "delete") {
|
|
3621
|
+
finalContent = sourceContent === null ? null : removeManagedBlock(sourceContent);
|
|
3622
|
+
equivalent = finalContent === sourceContent;
|
|
3623
|
+
} else if (incoming !== null) {
|
|
3624
|
+
const incomingBlock = extractManagedBlock(incoming) ?? incoming.trim();
|
|
3625
|
+
const incomingId = extractSingleManagedBlockById(incoming)?.id;
|
|
3626
|
+
const blockId = operation.operation === "add" || operation.operation === "modify" ? operation.block_id ?? incomingId : void 0;
|
|
3627
|
+
finalContent = blockId !== void 0 ? upsertManagedBlockById(targetContent ?? "", blockId, incomingBlock) : upsertManagedBlock(targetContent ?? "", incomingBlock);
|
|
3628
|
+
equivalent = finalContent === targetContent;
|
|
3629
|
+
}
|
|
3630
|
+
} else if (operation.operation === "delete" && sourceContent === null) {
|
|
3631
|
+
equivalent = true;
|
|
3632
|
+
finalContent = null;
|
|
3633
|
+
} else if (equivalent) {
|
|
3634
|
+
finalContent = targetContent;
|
|
3635
|
+
}
|
|
3636
|
+
return { finalContent, equivalent };
|
|
3637
|
+
}
|
|
3638
|
+
function isDirty(operation, policy, previous, sourceContent, targetContent, incoming) {
|
|
3639
|
+
const incomingHash = contentHash(operation);
|
|
3640
|
+
let equivalent = incomingHash !== null && targetContent !== null && sha256Bytes(targetContent) === incomingHash;
|
|
3641
|
+
let dirty = false;
|
|
3642
|
+
if (operation.operation === "add") {
|
|
3643
|
+
if (targetContent !== null && !equivalent) {
|
|
3644
|
+
if (policy.update_policy === "managed-block-only" && incoming !== null) {
|
|
3645
|
+
const incomingBlock = extractManagedBlock(incoming) ?? incoming.trim();
|
|
3646
|
+
equivalent = extractManagedBlock(targetContent) === incomingBlock;
|
|
3647
|
+
dirty = !equivalent;
|
|
3648
|
+
} else {
|
|
3649
|
+
dirty = true;
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
} else if (equivalent) {
|
|
3653
|
+
dirty = false;
|
|
3654
|
+
} else if (sourceContent === null) {
|
|
3655
|
+
dirty = operation.operation !== "delete";
|
|
3656
|
+
} else if (policy.update_policy === "managed-block-only") {
|
|
3657
|
+
dirty = previous?.managed_block_hash === void 0 ? (previous?.local_hash_at_apply ?? previous?.baseline_hash) !== sha256Bytes(sourceContent) : managedBlockDirty(sourceContent, previous.managed_block_hash);
|
|
3658
|
+
} else {
|
|
3659
|
+
dirty = (previous?.local_hash_at_apply ?? previous?.baseline_hash) !== sha256Bytes(sourceContent);
|
|
3660
|
+
}
|
|
3661
|
+
if (operation.operation === "rename" && targetContent !== null && !equivalent) {
|
|
3662
|
+
dirty = true;
|
|
3663
|
+
}
|
|
3664
|
+
return { dirty, equivalent };
|
|
3665
|
+
}
|
|
3666
|
+
function pushBaselineUpdates(plan, operation, target, entry) {
|
|
3667
|
+
plan.baselineUpdates.push({ path: target, entry });
|
|
3668
|
+
if (operation.operation === "rename") {
|
|
3669
|
+
plan.baselineUpdates.push({
|
|
3670
|
+
path: operation.from_path,
|
|
3671
|
+
entry: deletedSourceEntry(operation, entry.last_applied_version ?? null)
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
function recordWrite(plan, bucket, operation, target, content, equivalent, entry) {
|
|
3676
|
+
const write = { operation, path: target, content, equivalent };
|
|
3677
|
+
plan[bucket].push(write);
|
|
3678
|
+
pushBaselineUpdates(plan, operation, target, entry);
|
|
3679
|
+
}
|
|
3680
|
+
function planArtifactRebase(input) {
|
|
3681
|
+
const plan = {
|
|
3682
|
+
applied: [],
|
|
3683
|
+
acknowledged: [],
|
|
3684
|
+
resolvedKeepLocal: [],
|
|
3685
|
+
resolvedAcceptRemote: [],
|
|
3686
|
+
alreadyApplied: [],
|
|
3687
|
+
conflicts: [],
|
|
3688
|
+
baselineUpdates: [],
|
|
3689
|
+
baselineAdvanced: false
|
|
3690
|
+
};
|
|
3691
|
+
for (const context of input.contexts) {
|
|
3692
|
+
const operation = context.operation;
|
|
3693
|
+
const incoming = context.incomingContent;
|
|
3694
|
+
const source = operationSourcePath(operation);
|
|
3695
|
+
const target = operationTargetPath(operation);
|
|
3696
|
+
const policy = classifyFile(target);
|
|
3697
|
+
const incomingHash = contentHash(operation);
|
|
3698
|
+
if (operationAlreadyApplied(operation, input.baseline, input.projectVersion)) {
|
|
3699
|
+
plan.alreadyApplied.push({ path: target, operation });
|
|
3700
|
+
continue;
|
|
3701
|
+
}
|
|
3702
|
+
const staticDecision = decideUpdate(policy, false);
|
|
3703
|
+
if (!staticDecision.apply) {
|
|
3704
|
+
const acknowledgeReason = staticDecision.reason === "protocol-only" ? "protocol-only" : "policy-never";
|
|
3705
|
+
const entry2 = acknowledgedEntry(operation, context, input.projectVersion, acknowledgeReason);
|
|
3706
|
+
plan.acknowledged.push({
|
|
3707
|
+
operation,
|
|
3708
|
+
path: target,
|
|
3709
|
+
content: null,
|
|
3710
|
+
equivalent: true,
|
|
3711
|
+
acknowledgeReason
|
|
3712
|
+
});
|
|
3713
|
+
pushBaselineUpdates(plan, operation, target, entry2);
|
|
3714
|
+
continue;
|
|
3715
|
+
}
|
|
3716
|
+
const previous = input.baseline.files[source];
|
|
3717
|
+
const expected = expectedBase(operation);
|
|
3718
|
+
const actualBaseline = previous?.baseline_hash ?? null;
|
|
3719
|
+
if (expected !== actualBaseline) {
|
|
3720
|
+
const decision = resolveDecision(target, "baseline-diverged", operation, input.conflictStrategy, input.resolveOverrides);
|
|
3721
|
+
if (decision === "conflict") {
|
|
3722
|
+
const conflict2 = {
|
|
3723
|
+
path: target,
|
|
3724
|
+
operation: operation.operation,
|
|
3725
|
+
reason: "baseline-diverged",
|
|
3726
|
+
actualBaselineHash: actualBaseline
|
|
3727
|
+
};
|
|
3728
|
+
if (expected !== null) {
|
|
3729
|
+
conflict2.expectedBaselineHash = expected;
|
|
3730
|
+
}
|
|
3731
|
+
plan.conflicts.push(conflict2);
|
|
3732
|
+
continue;
|
|
3733
|
+
}
|
|
3734
|
+
if (decision === "keep-local") {
|
|
3735
|
+
const localContent = operation.operation === "rename" ? context.sourceContent : context.targetContent ?? context.sourceContent;
|
|
3736
|
+
const entry2 = baselineEntryFor(operation, localContent, input.projectVersion);
|
|
3737
|
+
recordWrite(plan, "resolvedKeepLocal", operation, target, localContent, true, entry2);
|
|
3738
|
+
continue;
|
|
3739
|
+
}
|
|
3740
|
+
if (decision === "accept-remote") {
|
|
3741
|
+
const { finalContent: finalContent2, equivalent: equivalent2 } = computeFinalContent(operation, policy, context.targetContent, context.sourceContent, incoming);
|
|
3742
|
+
const entry2 = baselineEntryFor(operation, finalContent2, input.projectVersion);
|
|
3743
|
+
recordWrite(plan, "resolvedAcceptRemote", operation, target, finalContent2, equivalent2, entry2);
|
|
3744
|
+
continue;
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
const targetCollision = isRenameTargetCollision(operation, context.targetContent, incomingHash);
|
|
3748
|
+
if (targetCollision) {
|
|
3749
|
+
plan.conflicts.push({
|
|
3750
|
+
path: target,
|
|
3751
|
+
operation: operation.operation,
|
|
3752
|
+
reason: "target-collision"
|
|
3753
|
+
});
|
|
3754
|
+
continue;
|
|
3755
|
+
}
|
|
3756
|
+
const { dirty, equivalent: preEquivalent } = isDirty(operation, policy, previous, context.sourceContent, context.targetContent, incoming);
|
|
3757
|
+
if (dirty) {
|
|
3758
|
+
const reason = operation.operation === "rename" && context.targetContent !== null && !preEquivalent ? "target-collision" : "local-dirty";
|
|
3759
|
+
const decision = resolveDecision(target, reason, operation, input.conflictStrategy, input.resolveOverrides);
|
|
3760
|
+
if (decision === "conflict") {
|
|
3761
|
+
plan.conflicts.push({
|
|
3762
|
+
path: target,
|
|
3763
|
+
operation: operation.operation,
|
|
3764
|
+
reason
|
|
3765
|
+
});
|
|
3766
|
+
continue;
|
|
3767
|
+
}
|
|
3768
|
+
if (decision === "keep-local") {
|
|
3769
|
+
const localContent = operation.operation === "delete" ? context.sourceContent : operation.operation === "rename" ? context.sourceContent : context.targetContent ?? context.sourceContent;
|
|
3770
|
+
const entry3 = baselineEntryFor(operation, localContent, input.projectVersion);
|
|
3771
|
+
recordWrite(plan, "resolvedKeepLocal", operation, target, localContent, true, entry3);
|
|
3772
|
+
continue;
|
|
3773
|
+
}
|
|
3774
|
+
const { finalContent: finalContent2, equivalent: equivalent2 } = computeFinalContent(operation, policy, context.targetContent, context.sourceContent, incoming);
|
|
3775
|
+
const entry2 = baselineEntryFor(operation, finalContent2, input.projectVersion);
|
|
3776
|
+
recordWrite(plan, "resolvedAcceptRemote", operation, target, finalContent2, equivalent2, entry2);
|
|
3777
|
+
continue;
|
|
3778
|
+
}
|
|
3779
|
+
const { finalContent, equivalent } = computeFinalContent(operation, policy, context.targetContent, context.sourceContent, incoming);
|
|
3780
|
+
const entry = baselineEntryFor(operation, finalContent, input.projectVersion);
|
|
3781
|
+
if (equivalent) {
|
|
3782
|
+
plan.alreadyApplied.push({ path: target, operation });
|
|
3783
|
+
pushBaselineUpdates(plan, operation, target, entry);
|
|
3784
|
+
continue;
|
|
3785
|
+
}
|
|
3786
|
+
recordWrite(plan, "applied", operation, target, finalContent, equivalent, entry);
|
|
3787
|
+
}
|
|
3788
|
+
plan.baselineAdvanced = plan.conflicts.length === 0;
|
|
3789
|
+
return plan;
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
// ../core/dist/sync/synchronize.js
|
|
3793
|
+
var MAX_SYNC_ARTIFACT_ITERATIONS = 50;
|
|
3794
|
+
async function pathExists(path) {
|
|
3795
|
+
try {
|
|
3796
|
+
await lstat2(path);
|
|
3797
|
+
return true;
|
|
3798
|
+
} catch (error) {
|
|
3799
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
3800
|
+
return false;
|
|
3801
|
+
}
|
|
3802
|
+
throw error;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
async function optionalContent(root, path) {
|
|
3806
|
+
const full = join10(root, path);
|
|
3807
|
+
return await pathExists(full) ? readFile9(full, "utf8") : null;
|
|
3808
|
+
}
|
|
3809
|
+
function manifestPayloadHash(manifest) {
|
|
3810
|
+
const payload = { ...manifest };
|
|
3811
|
+
delete payload.manifest_sha256;
|
|
3812
|
+
return sha256Bytes(canonicalJson(payload));
|
|
3813
|
+
}
|
|
3814
|
+
async function loadBlob(root, client, artifactId, operation, requestId, dryRun) {
|
|
3815
|
+
if (operation.operation === "delete") {
|
|
3816
|
+
return null;
|
|
3817
|
+
}
|
|
3818
|
+
const hash = operation.content_sha256;
|
|
3819
|
+
const cacheRoot = join10(root, ".harness", "cache", "server-artifacts", artifactId);
|
|
3820
|
+
const cachePath = join10(cacheRoot, "blobs", hash.replace(":", "_"));
|
|
3821
|
+
if (await pathExists(cachePath) && await sha256File(cachePath) === hash) {
|
|
3822
|
+
return readFile9(cachePath, "utf8");
|
|
3823
|
+
}
|
|
3824
|
+
const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
|
|
3825
|
+
if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
|
|
3826
|
+
await rm4(cachePath, { force: true });
|
|
3827
|
+
throw new Error("ARTIFACT_HASH_MISMATCH");
|
|
3828
|
+
}
|
|
3829
|
+
if (!dryRun) {
|
|
3830
|
+
await atomicWriteFile(cachePath, bytes);
|
|
3831
|
+
}
|
|
3832
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
3833
|
+
}
|
|
3834
|
+
function writeFromPlan(item2, keepLocalNoWrite) {
|
|
3835
|
+
if (keepLocalNoWrite || item2.equivalent) {
|
|
3836
|
+
return null;
|
|
3837
|
+
}
|
|
3838
|
+
const operation = item2.operation;
|
|
3839
|
+
const target = operationTargetPath(operation);
|
|
3840
|
+
if (operation.operation === "delete") {
|
|
3841
|
+
return { operation: "delete", path: target };
|
|
3842
|
+
}
|
|
3843
|
+
if (operation.operation === "rename") {
|
|
3844
|
+
return {
|
|
3845
|
+
operation: "rename",
|
|
3846
|
+
from_path: operation.from_path,
|
|
3847
|
+
to_path: operation.to_path,
|
|
3848
|
+
content: item2.content ?? ""
|
|
3849
|
+
};
|
|
3850
|
+
}
|
|
3851
|
+
return {
|
|
3852
|
+
operation: "modify",
|
|
3853
|
+
path: target,
|
|
3854
|
+
content: item2.content ?? ""
|
|
3855
|
+
};
|
|
3856
|
+
}
|
|
3857
|
+
function collectWrites(plan) {
|
|
3858
|
+
const ops = [];
|
|
3859
|
+
for (const item2 of plan.applied) {
|
|
3860
|
+
const op = writeFromPlan(item2, false);
|
|
3861
|
+
if (op !== null)
|
|
3862
|
+
ops.push(op);
|
|
3863
|
+
}
|
|
3864
|
+
for (const item2 of plan.resolvedAcceptRemote) {
|
|
3865
|
+
const op = writeFromPlan(item2, false);
|
|
3866
|
+
if (op !== null)
|
|
3867
|
+
ops.push(op);
|
|
3868
|
+
}
|
|
3869
|
+
for (const item2 of plan.resolvedKeepLocal) {
|
|
3870
|
+
const op = writeFromPlan(item2, true);
|
|
3871
|
+
if (op !== null)
|
|
3872
|
+
ops.push(op);
|
|
3873
|
+
}
|
|
3874
|
+
return ops;
|
|
3875
|
+
}
|
|
3876
|
+
function pathsFromPlan(plan) {
|
|
3877
|
+
const applied = plan.applied.map((item2) => item2.path);
|
|
3878
|
+
const acknowledged = plan.acknowledged.map((item2) => ({
|
|
3879
|
+
path: item2.path,
|
|
3880
|
+
operation: item2.operation.operation,
|
|
3881
|
+
reason: item2.acknowledgeReason ?? "policy-never"
|
|
3882
|
+
}));
|
|
3883
|
+
return {
|
|
3884
|
+
applied,
|
|
3885
|
+
acknowledged,
|
|
3886
|
+
resolvedKeepLocal: plan.resolvedKeepLocal.map((item2) => item2.path),
|
|
3887
|
+
resolvedAcceptRemote: plan.resolvedAcceptRemote.map((item2) => item2.path),
|
|
3888
|
+
alreadyApplied: plan.alreadyApplied.map((item2) => item2.path)
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
async function buildContexts(root, manifest, client, requestId, dryRun) {
|
|
3892
|
+
const contexts = [];
|
|
3893
|
+
for (const operation of manifest.files) {
|
|
3894
|
+
const source = operationSourcePath(operation);
|
|
3895
|
+
const target = operationTargetPath(operation);
|
|
3896
|
+
const incoming = await loadBlob(root, client, manifest.artifact_id, operation, requestId, dryRun);
|
|
3897
|
+
contexts.push({
|
|
3898
|
+
operation,
|
|
3899
|
+
incomingContent: incoming,
|
|
3900
|
+
sourceContent: await optionalContent(root, source),
|
|
3901
|
+
targetContent: target === source ? await optionalContent(root, source) : await optionalContent(root, target)
|
|
3902
|
+
});
|
|
3903
|
+
}
|
|
3904
|
+
return contexts;
|
|
3905
|
+
}
|
|
3906
|
+
function applyBaselineUpdates(baseline, plan) {
|
|
3907
|
+
const next = baselineManifestSchema.parse(structuredClone(baseline));
|
|
3908
|
+
for (const update of plan.baselineUpdates) {
|
|
3909
|
+
next.files[update.path] = update.entry;
|
|
3910
|
+
}
|
|
3911
|
+
return next;
|
|
3912
|
+
}
|
|
3913
|
+
async function planSingleArtifact(root, baseline, manifest, client, requestId, dryRun, conflictStrategy, resolveOverrides) {
|
|
3914
|
+
if (manifest.project_version === null) {
|
|
3915
|
+
throw new Error("artifact manifest missing project_version");
|
|
3916
|
+
}
|
|
3917
|
+
const contexts = await buildContexts(root, manifest, client, requestId, dryRun);
|
|
3918
|
+
return planArtifactRebase({
|
|
3919
|
+
baseline,
|
|
3920
|
+
projectVersion: manifest.project_version,
|
|
3921
|
+
contexts,
|
|
3922
|
+
conflictStrategy,
|
|
3923
|
+
...resolveOverrides === void 0 ? {} : { resolveOverrides }
|
|
3924
|
+
});
|
|
3925
|
+
}
|
|
3926
|
+
async function saveConflictReport(root, requestId, manifest, plan) {
|
|
3927
|
+
const reportPath = join10(root, ".harness", "reports", "conflicts-" + requestId + ".json");
|
|
3928
|
+
await atomicWriteJson(reportPath, {
|
|
3929
|
+
schema_version: 1,
|
|
3930
|
+
request_id: requestId,
|
|
3931
|
+
artifact_id: manifest.artifact_id,
|
|
3932
|
+
project_version: manifest.project_version,
|
|
3933
|
+
conflicts: plan.conflicts,
|
|
3934
|
+
resolve_hint: "npx hunter-harness update --resolve <path>=keep-local|accept-remote"
|
|
3935
|
+
});
|
|
3936
|
+
}
|
|
3937
|
+
async function synchronizeArtifacts(options, initialBaseline) {
|
|
3938
|
+
const root = resolve5(options.projectRoot);
|
|
3939
|
+
const projectId = options.project.project.project_id;
|
|
3940
|
+
if (projectId === null) {
|
|
3941
|
+
throw new Error("PROJECT_NOT_BOUND");
|
|
3942
|
+
}
|
|
3943
|
+
let baseline = initialBaseline;
|
|
3944
|
+
const seenArtifactIds = /* @__PURE__ */ new Set();
|
|
3945
|
+
const aggregate = {
|
|
3946
|
+
requestId: options.requestId,
|
|
3947
|
+
projectId,
|
|
3948
|
+
artifactsProcessed: 0,
|
|
3949
|
+
applied: [],
|
|
3950
|
+
acknowledged: [],
|
|
3951
|
+
resolvedKeepLocal: [],
|
|
3952
|
+
resolvedAcceptRemote: [],
|
|
3953
|
+
alreadyApplied: [],
|
|
3954
|
+
conflicts: [],
|
|
3955
|
+
skipped: [],
|
|
3956
|
+
operations: [],
|
|
3957
|
+
artifactId: null,
|
|
3958
|
+
observedProjectVersion: null,
|
|
3959
|
+
transactionId: null,
|
|
3960
|
+
dryRun: options.dryRun,
|
|
3961
|
+
baselineAdvanced: false
|
|
3962
|
+
};
|
|
3963
|
+
const conflictStrategy = options.conflictStrategy ?? "manual";
|
|
3964
|
+
for (let iteration = 0; iteration < MAX_SYNC_ARTIFACT_ITERATIONS; iteration++) {
|
|
3965
|
+
const discovery = await options.client.getUpdateManifest(projectId, {
|
|
3966
|
+
base_project_version: baseline.complete_project_version,
|
|
3967
|
+
base_manifest_hash: sha256Bytes(canonicalJson(baseline)),
|
|
3968
|
+
adapter: options.project.adapters.enabled[0] ?? "claude-code",
|
|
3969
|
+
profile: options.project.project.profiles[0] ?? "general"
|
|
3970
|
+
}, options.requestId);
|
|
3971
|
+
aggregate.observedProjectVersion = discovery.observed_project_version;
|
|
3972
|
+
if (!discovery.delta_available || discovery.artifact_id === null) {
|
|
3973
|
+
aggregate.baselineAdvanced = aggregate.conflicts.length === 0;
|
|
3974
|
+
return aggregate;
|
|
3975
|
+
}
|
|
3976
|
+
if (seenArtifactIds.has(discovery.artifact_id)) {
|
|
3977
|
+
throw new Error("DUPLICATE_ARTIFACT_ID:" + discovery.artifact_id);
|
|
3978
|
+
}
|
|
3979
|
+
seenArtifactIds.add(discovery.artifact_id);
|
|
3980
|
+
if (options.stopAfterArtifactId === discovery.artifact_id) {
|
|
3981
|
+
return aggregate;
|
|
3982
|
+
}
|
|
3983
|
+
const manifest = artifactManifestSchema.parse(await options.client.getArtifactManifest(discovery.artifact_id, options.requestId));
|
|
3984
|
+
if (manifest.artifact_id !== discovery.artifact_id || manifest.project_id !== projectId || manifest.project_version === null || manifestPayloadHash(manifest) !== manifest.manifest_sha256) {
|
|
3985
|
+
throw new Error("ARTIFACT_HASH_MISMATCH");
|
|
3986
|
+
}
|
|
3987
|
+
let plan = await planSingleArtifact(root, baseline, manifest, options.client, options.requestId, options.dryRun, conflictStrategy, options.resolveOverrides);
|
|
3988
|
+
if (plan.conflicts.length > 0 && options.confirmConflictStrategy !== void 0) {
|
|
3989
|
+
const confirmed = await options.confirmConflictStrategy(plan.conflicts);
|
|
3990
|
+
if (confirmed === false) {
|
|
3991
|
+
aggregate.conflicts = plan.conflicts;
|
|
3992
|
+
aggregate.skipped = plan.conflicts.map((item2) => ({
|
|
3993
|
+
path: item2.path,
|
|
3994
|
+
operation: item2.operation,
|
|
3995
|
+
reason: item2.reason
|
|
3996
|
+
}));
|
|
3997
|
+
aggregate.operations = manifest.files;
|
|
3998
|
+
aggregate.artifactId = manifest.artifact_id;
|
|
3999
|
+
return aggregate;
|
|
4000
|
+
}
|
|
4001
|
+
plan = await planSingleArtifact(root, baseline, manifest, options.client, options.requestId, options.dryRun, confirmed, options.resolveOverrides);
|
|
4002
|
+
}
|
|
4003
|
+
const paths = pathsFromPlan(plan);
|
|
4004
|
+
aggregate.applied.push(...paths.applied);
|
|
4005
|
+
aggregate.acknowledged.push(...paths.acknowledged);
|
|
4006
|
+
aggregate.resolvedKeepLocal.push(...paths.resolvedKeepLocal);
|
|
4007
|
+
aggregate.resolvedAcceptRemote.push(...paths.resolvedAcceptRemote);
|
|
4008
|
+
aggregate.alreadyApplied.push(...paths.alreadyApplied);
|
|
4009
|
+
aggregate.operations = manifest.files;
|
|
4010
|
+
aggregate.artifactId = manifest.artifact_id;
|
|
4011
|
+
aggregate.artifactsProcessed += 1;
|
|
4012
|
+
if (plan.conflicts.length > 0) {
|
|
4013
|
+
aggregate.conflicts = plan.conflicts;
|
|
4014
|
+
aggregate.skipped = plan.conflicts.map((item2) => ({
|
|
4015
|
+
path: item2.path,
|
|
4016
|
+
operation: item2.operation,
|
|
4017
|
+
reason: item2.reason
|
|
4018
|
+
}));
|
|
4019
|
+
if (conflictStrategy !== "manual") {
|
|
4020
|
+
if (!options.dryRun) {
|
|
4021
|
+
await saveConflictReport(root, options.requestId, manifest, plan);
|
|
4022
|
+
}
|
|
4023
|
+
return aggregate;
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
const hasApplicableWork = plan.applied.length > 0 || plan.acknowledged.length > 0 || plan.resolvedKeepLocal.length > 0 || plan.resolvedAcceptRemote.length > 0 || plan.baselineUpdates.length > 0;
|
|
4027
|
+
if (plan.conflicts.length > 0 && !hasApplicableWork) {
|
|
4028
|
+
if (!options.dryRun) {
|
|
4029
|
+
await saveConflictReport(root, options.requestId, manifest, plan);
|
|
4030
|
+
}
|
|
4031
|
+
return aggregate;
|
|
4032
|
+
}
|
|
4033
|
+
if (options.dryRun) {
|
|
4034
|
+
baseline = applyBaselineUpdates(baseline, plan);
|
|
4035
|
+
if (plan.baselineAdvanced && plan.conflicts.length === 0) {
|
|
4036
|
+
baseline.complete_project_version = manifest.project_version;
|
|
4037
|
+
baseline.artifact_manifest_hash = manifest.manifest_sha256;
|
|
4038
|
+
baseline.latest_artifact_id = manifest.artifact_id;
|
|
4039
|
+
}
|
|
4040
|
+
if (plan.conflicts.length > 0) {
|
|
4041
|
+
return aggregate;
|
|
4042
|
+
}
|
|
4043
|
+
continue;
|
|
4044
|
+
}
|
|
4045
|
+
await atomicWriteJson(join10(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
|
|
4046
|
+
const lock = await acquireProtocolLock(root, "update", { requestId: options.requestId });
|
|
4047
|
+
try {
|
|
4048
|
+
const nextBaseline = applyBaselineUpdates(baseline, plan);
|
|
4049
|
+
if (plan.baselineAdvanced && plan.conflicts.length === 0) {
|
|
4050
|
+
nextBaseline.complete_project_version = manifest.project_version;
|
|
4051
|
+
nextBaseline.artifact_manifest_hash = manifest.manifest_sha256;
|
|
4052
|
+
nextBaseline.latest_artifact_id = manifest.artifact_id;
|
|
4053
|
+
}
|
|
4054
|
+
const transactionId = "tx_sync_" + Date.now() + "_" + options.requestId;
|
|
4055
|
+
const reportPath = ".harness/reports/update-" + options.requestId + ".json";
|
|
4056
|
+
const report = {
|
|
4057
|
+
schema_version: 2,
|
|
4058
|
+
request_id: options.requestId,
|
|
4059
|
+
artifact_id: manifest.artifact_id,
|
|
4060
|
+
observed_project_version: manifest.project_version,
|
|
4061
|
+
status: plan.conflicts.length === 0 ? "applied" : "partial_due_to_conflicts",
|
|
4062
|
+
applied: paths.applied,
|
|
4063
|
+
acknowledged: paths.acknowledged,
|
|
4064
|
+
resolved_keep_local: paths.resolvedKeepLocal,
|
|
4065
|
+
resolved_accept_remote: paths.resolvedAcceptRemote,
|
|
4066
|
+
already_applied: paths.alreadyApplied,
|
|
4067
|
+
conflicts: plan.conflicts,
|
|
4068
|
+
skipped: aggregate.skipped,
|
|
4069
|
+
transaction_id: transactionId
|
|
4070
|
+
};
|
|
4071
|
+
const fileOps = collectWrites(plan);
|
|
4072
|
+
fileOps.push({
|
|
4073
|
+
operation: "modify",
|
|
4074
|
+
path: ".harness/state/baseline/manifest.json",
|
|
4075
|
+
content: JSON.stringify(nextBaseline, null, 2) + "\n"
|
|
4076
|
+
}, {
|
|
4077
|
+
operation: "add",
|
|
4078
|
+
path: reportPath,
|
|
4079
|
+
content: JSON.stringify(report, null, 2) + "\n"
|
|
4080
|
+
}, {
|
|
4081
|
+
operation: "modify",
|
|
4082
|
+
path: ".harness/state/local/last-update.json",
|
|
4083
|
+
content: JSON.stringify(report, null, 2) + "\n"
|
|
4084
|
+
});
|
|
4085
|
+
await runTransaction(root, fileOps, {
|
|
4086
|
+
id: transactionId,
|
|
4087
|
+
kind: "update",
|
|
4088
|
+
...options.transactionOptions ?? {}
|
|
4089
|
+
});
|
|
4090
|
+
aggregate.transactionId = transactionId;
|
|
4091
|
+
baseline = nextBaseline;
|
|
4092
|
+
if (plan.conflicts.length > 0) {
|
|
4093
|
+
await saveConflictReport(root, options.requestId, manifest, plan);
|
|
4094
|
+
aggregate.baselineAdvanced = false;
|
|
4095
|
+
return aggregate;
|
|
4096
|
+
}
|
|
4097
|
+
} finally {
|
|
4098
|
+
await lock.release();
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
throw new Error("MAX_SYNC_ARTIFACT_ITERATIONS_EXCEEDED");
|
|
4102
|
+
}
|
|
4103
|
+
function baselineEntryFromOperation(manifest, operation, localContent) {
|
|
4104
|
+
const block = localContent === null ? null : extractManagedBlock(localContent);
|
|
4105
|
+
return {
|
|
4106
|
+
baseline_hash: operation.operation === "delete" ? null : operation.content_sha256,
|
|
4107
|
+
local_hash_at_apply: localContent === null ? null : sha256Bytes(localContent),
|
|
4108
|
+
file_kind: operation.file_kind,
|
|
4109
|
+
last_applied_version: manifest.project_version,
|
|
4110
|
+
deleted: operation.operation === "delete",
|
|
4111
|
+
...block === null ? {} : { managed_block_hash: sha256Bytes(block) }
|
|
4112
|
+
};
|
|
4113
|
+
}
|
|
4114
|
+
async function advanceBaselineFromArtifact(options, baseline) {
|
|
4115
|
+
const root = resolve5(options.projectRoot);
|
|
4116
|
+
if (options.manifest.project_version === null) {
|
|
4117
|
+
throw new Error("artifact manifest missing project_version");
|
|
4118
|
+
}
|
|
4119
|
+
let localChanged = false;
|
|
4120
|
+
for (const operation of options.manifest.files) {
|
|
4121
|
+
const source = operationSourcePath(operation);
|
|
4122
|
+
const target = operationTargetPath(operation);
|
|
4123
|
+
const sourceContent = await optionalContent(root, source);
|
|
4124
|
+
const targetContent = target === source ? sourceContent : await optionalContent(root, target);
|
|
4125
|
+
if (operation.operation === "delete") {
|
|
4126
|
+
if (sourceContent !== null) {
|
|
4127
|
+
localChanged = true;
|
|
4128
|
+
}
|
|
4129
|
+
continue;
|
|
4130
|
+
}
|
|
4131
|
+
const expectedHash = operation.content_sha256;
|
|
4132
|
+
const actualContent = targetContent ?? sourceContent;
|
|
4133
|
+
const actualHash = actualContent === null ? null : sha256Bytes(actualContent);
|
|
4134
|
+
if (actualHash !== expectedHash) {
|
|
4135
|
+
localChanged = true;
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
if (localChanged) {
|
|
4139
|
+
return { baseline, localChanged: true, transactionId: null };
|
|
4140
|
+
}
|
|
4141
|
+
const nextBaseline = baselineManifestSchema.parse(structuredClone(baseline));
|
|
4142
|
+
for (const operation of options.manifest.files) {
|
|
4143
|
+
const source = operationSourcePath(operation);
|
|
4144
|
+
const target = operationTargetPath(operation);
|
|
4145
|
+
const sourceContent = await optionalContent(root, source);
|
|
4146
|
+
const targetContent = target === source ? sourceContent : await optionalContent(root, target);
|
|
4147
|
+
const localContent = operation.operation === "delete" ? null : targetContent ?? sourceContent;
|
|
4148
|
+
nextBaseline.files[target] = baselineEntryFromOperation(options.manifest, operation, localContent);
|
|
4149
|
+
if (operation.operation === "rename") {
|
|
4150
|
+
nextBaseline.files[operation.from_path] = {
|
|
4151
|
+
baseline_hash: null,
|
|
4152
|
+
local_hash_at_apply: null,
|
|
4153
|
+
file_kind: operation.file_kind,
|
|
4154
|
+
last_applied_version: options.manifest.project_version,
|
|
4155
|
+
deleted: true
|
|
4156
|
+
};
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
nextBaseline.complete_project_version = options.manifest.project_version;
|
|
4160
|
+
nextBaseline.artifact_manifest_hash = options.manifest.manifest_sha256;
|
|
4161
|
+
nextBaseline.latest_artifact_id = options.manifest.artifact_id;
|
|
4162
|
+
const transactionId = "tx_push_baseline_" + Date.now();
|
|
4163
|
+
await runTransaction(root, [{
|
|
4164
|
+
operation: "modify",
|
|
4165
|
+
path: ".harness/state/baseline/manifest.json",
|
|
4166
|
+
content: JSON.stringify(nextBaseline, null, 2) + "\n"
|
|
4167
|
+
}], {
|
|
4168
|
+
id: transactionId,
|
|
4169
|
+
kind: "update",
|
|
4170
|
+
...options.transactionOptions ?? {}
|
|
4171
|
+
});
|
|
4172
|
+
return { baseline: nextBaseline, localChanged: false, transactionId };
|
|
4173
|
+
}
|
|
4174
|
+
|
|
3522
4175
|
// ../core/dist/push/push.js
|
|
3523
4176
|
var PushWorkflowError = class extends Error {
|
|
3524
4177
|
exitCode;
|
|
@@ -3545,7 +4198,7 @@ var SHARED_MANAGED_FILES = [
|
|
|
3545
4198
|
];
|
|
3546
4199
|
async function exists3(path) {
|
|
3547
4200
|
try {
|
|
3548
|
-
await
|
|
4201
|
+
await lstat3(path);
|
|
3549
4202
|
return true;
|
|
3550
4203
|
} catch (error) {
|
|
3551
4204
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -3559,7 +4212,7 @@ async function walkFiles(root, current, output) {
|
|
|
3559
4212
|
return;
|
|
3560
4213
|
}
|
|
3561
4214
|
for (const item2 of await readdir4(current, { withFileTypes: true })) {
|
|
3562
|
-
const path =
|
|
4215
|
+
const path = join11(current, item2.name);
|
|
3563
4216
|
if (item2.isSymbolicLink()) {
|
|
3564
4217
|
throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
|
|
3565
4218
|
}
|
|
@@ -3581,7 +4234,7 @@ async function walkHarnessEntries(root, directory, output) {
|
|
|
3581
4234
|
return;
|
|
3582
4235
|
for (const item2 of await readdir4(directory, { withFileTypes: true })) {
|
|
3583
4236
|
if (item2.name.startsWith("harness-")) {
|
|
3584
|
-
const path =
|
|
4237
|
+
const path = join11(directory, item2.name);
|
|
3585
4238
|
if (item2.isDirectory()) {
|
|
3586
4239
|
await walkFiles(root, path, output);
|
|
3587
4240
|
} else if (item2.isFile()) {
|
|
@@ -3591,7 +4244,7 @@ async function walkHarnessEntries(root, directory, output) {
|
|
|
3591
4244
|
}
|
|
3592
4245
|
}
|
|
3593
4246
|
async function managedFiles(projectRoot, project) {
|
|
3594
|
-
const root =
|
|
4247
|
+
const root = resolve6(projectRoot);
|
|
3595
4248
|
const paths = [];
|
|
3596
4249
|
const adapters = getAdapters(enabledHarnessAgents(project));
|
|
3597
4250
|
const managedFiles2 = [
|
|
@@ -3600,25 +4253,25 @@ async function managedFiles(projectRoot, project) {
|
|
|
3600
4253
|
...adapters.some((adapter) => adapter.name === "codebuddy") ? ["CODEBUDDY.md"] : []
|
|
3601
4254
|
];
|
|
3602
4255
|
for (const path of managedFiles2) {
|
|
3603
|
-
if (await exists3(
|
|
4256
|
+
if (await exists3(join11(root, path))) {
|
|
3604
4257
|
paths.push(path);
|
|
3605
4258
|
}
|
|
3606
4259
|
}
|
|
3607
4260
|
for (const path of SHARED_MANAGED_ROOTS) {
|
|
3608
|
-
await walkFiles(root,
|
|
4261
|
+
await walkFiles(root, join11(root, path), paths);
|
|
3609
4262
|
}
|
|
3610
4263
|
for (const adapter of adapters) {
|
|
3611
4264
|
if (adapter.rulesRoot !== null) {
|
|
3612
|
-
await walkFiles(root,
|
|
4265
|
+
await walkFiles(root, join11(root, adapter.rulesRoot), paths);
|
|
3613
4266
|
}
|
|
3614
|
-
await walkHarnessEntries(root,
|
|
4267
|
+
await walkHarnessEntries(root, join11(root, adapter.skillsRoot), paths);
|
|
3615
4268
|
if (adapter.agentsRoot !== null) {
|
|
3616
|
-
await walkHarnessEntries(root,
|
|
4269
|
+
await walkHarnessEntries(root, join11(root, adapter.agentsRoot), paths);
|
|
3617
4270
|
}
|
|
3618
4271
|
}
|
|
3619
4272
|
const result = {};
|
|
3620
4273
|
for (const path of [...new Set(paths)].sort()) {
|
|
3621
|
-
result[path] = await
|
|
4274
|
+
result[path] = await readFile10(join11(root, path), "utf8");
|
|
3622
4275
|
}
|
|
3623
4276
|
return result;
|
|
3624
4277
|
}
|
|
@@ -3627,14 +4280,14 @@ function proposalBaseline(baseline) {
|
|
|
3627
4280
|
}
|
|
3628
4281
|
async function readProject(root) {
|
|
3629
4282
|
try {
|
|
3630
|
-
return projectConfigSchema.parse(parseYaml5(await
|
|
4283
|
+
return projectConfigSchema.parse(parseYaml5(await readFile10(join11(root, ".harness", "project.yaml"), "utf8")));
|
|
3631
4284
|
} catch {
|
|
3632
4285
|
throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
3633
4286
|
}
|
|
3634
4287
|
}
|
|
3635
4288
|
async function readOptionalJson(path) {
|
|
3636
4289
|
try {
|
|
3637
|
-
return JSON.parse(await
|
|
4290
|
+
return JSON.parse(await readFile10(path, "utf8"));
|
|
3638
4291
|
} catch (error) {
|
|
3639
4292
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
3640
4293
|
return null;
|
|
@@ -3646,7 +4299,7 @@ async function clientIdFor(root, explicit) {
|
|
|
3646
4299
|
if (explicit !== void 0) {
|
|
3647
4300
|
return explicit;
|
|
3648
4301
|
}
|
|
3649
|
-
const path =
|
|
4302
|
+
const path = join11(root, ".harness", "state", "local", "client.json");
|
|
3650
4303
|
const existing = await readOptionalJson(path);
|
|
3651
4304
|
if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
|
|
3652
4305
|
return existing.client_id;
|
|
@@ -3735,9 +4388,34 @@ function assertPreviewAllowed(preview, skip) {
|
|
|
3735
4388
|
}
|
|
3736
4389
|
}
|
|
3737
4390
|
var CREDENTIALS_HINT = "\u53EF\u5728\u4EA4\u4E92\u6A21\u5F0F\u4E0B\u5F55\u5165\uFF0C\u6216\u5199\u5165 .harness/credentials.local.yaml\uFF08\u52FF\u63D0\u4EA4 git\uFF09";
|
|
3738
|
-
var STALE_BASELINE_MESSAGE = "\u670D\u52A1\u7AEF\u5DF2\
|
|
3739
|
-
function staleBaselineError(code) {
|
|
3740
|
-
|
|
4391
|
+
var STALE_BASELINE_MESSAGE = "\u670D\u52A1\u7AEF artifact \u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5148\u6267\u884C npx hunter-harness update\uFF08\u51B2\u7A81\u53EF\u7528 update --resolve <path>=keep-local|accept-remote\uFF09\u518D\u63A8";
|
|
4392
|
+
function staleBaselineError(code, conflicts) {
|
|
4393
|
+
const conflictHint = conflicts !== void 0 && conflicts.length > 0 ? " \u51B2\u7A81\u6587\u4EF6\uFF1A" + conflicts.map((item2) => item2.path).join(", ") : "";
|
|
4394
|
+
return new PushWorkflowError(STALE_BASELINE_MESSAGE + conflictHint, 5, code);
|
|
4395
|
+
}
|
|
4396
|
+
async function syncToLatest(root, project, baseline, client) {
|
|
4397
|
+
const syncResult = await synchronizeArtifacts({
|
|
4398
|
+
projectRoot: root,
|
|
4399
|
+
project,
|
|
4400
|
+
client,
|
|
4401
|
+
requestId: uuidV7(),
|
|
4402
|
+
dryRun: false,
|
|
4403
|
+
conflictStrategy: "manual"
|
|
4404
|
+
}, baseline);
|
|
4405
|
+
if (syncResult.conflicts.length > 0) {
|
|
4406
|
+
throw staleBaselineError("PROJECT_VERSION_CONFLICT", syncResult.conflicts);
|
|
4407
|
+
}
|
|
4408
|
+
return await readBaseline(root);
|
|
4409
|
+
}
|
|
4410
|
+
async function autoRebaseIfServerAdvanced(root, project, baseline, client, remoteVersion) {
|
|
4411
|
+
if (remoteVersion === baseline.complete_project_version) {
|
|
4412
|
+
return baseline;
|
|
4413
|
+
}
|
|
4414
|
+
const updated = await syncToLatest(root, project, baseline, client);
|
|
4415
|
+
if (remoteVersion !== null && updated.complete_project_version !== remoteVersion) {
|
|
4416
|
+
throw staleBaselineError("PROJECT_VERSION_CONFLICT");
|
|
4417
|
+
}
|
|
4418
|
+
return updated;
|
|
3741
4419
|
}
|
|
3742
4420
|
function makePreview(baseline, files, confirmedProjectLocal, ignorePaths, deletedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3743
4421
|
const filteredFiles = {};
|
|
@@ -3777,7 +4455,7 @@ async function bindProject(root, project, baseline, projectId) {
|
|
|
3777
4455
|
return { project: nextProject, baseline: nextBaseline };
|
|
3778
4456
|
}
|
|
3779
4457
|
async function pushProject(options) {
|
|
3780
|
-
const root =
|
|
4458
|
+
const root = resolve6(options.projectRoot);
|
|
3781
4459
|
let project = await readProject(root);
|
|
3782
4460
|
let baseline = await readBaseline(root);
|
|
3783
4461
|
const profile = parseHarnessProfile(project.project.profiles[0]);
|
|
@@ -3830,9 +4508,8 @@ async function pushProject(options) {
|
|
|
3830
4508
|
const boundAtStart = project.project.project_id;
|
|
3831
4509
|
if (boundAtStart !== null) {
|
|
3832
4510
|
const remote = await client.getProject(boundAtStart, uuidV7());
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
}
|
|
4511
|
+
baseline = await autoRebaseIfServerAdvanced(root, project, baseline, client, remote.latest_project_version);
|
|
4512
|
+
preview = makePreview(baseline, await managedFiles(root, project), options.confirmedProjectLocal ?? [], installedPaths);
|
|
3836
4513
|
}
|
|
3837
4514
|
const initialSkip = await resolveSensitiveScanSkip(preview, options);
|
|
3838
4515
|
if (initialSkip.cancelled === true) {
|
|
@@ -3844,7 +4521,7 @@ async function pushProject(options) {
|
|
|
3844
4521
|
if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
|
|
3845
4522
|
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
3846
4523
|
}
|
|
3847
|
-
const workflowPath =
|
|
4524
|
+
const workflowPath = join11(root, ".harness", "state", "local", "push-workflow.json");
|
|
3848
4525
|
const priorWorkflow = await readOptionalJson(workflowPath);
|
|
3849
4526
|
const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
|
|
3850
4527
|
const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
|
|
@@ -3935,7 +4612,9 @@ async function pushProject(options) {
|
|
|
3935
4612
|
});
|
|
3936
4613
|
}
|
|
3937
4614
|
}
|
|
3938
|
-
|
|
4615
|
+
let finalizeRetried = false;
|
|
4616
|
+
let finalized;
|
|
4617
|
+
const finalizeOnce = async () => client.finalizeProposal(session.session_id, {
|
|
3939
4618
|
schema_version: 1,
|
|
3940
4619
|
manifest_sha256: proposalManifestHash,
|
|
3941
4620
|
base_artifact_id: baseline.latest_artifact_id ?? null,
|
|
@@ -3944,7 +4623,60 @@ async function pushProject(options) {
|
|
|
3944
4623
|
...sensitiveScanSkipReason === void 0 ? {} : { sensitive_scan_skip_reason: sensitiveScanSkipReason }
|
|
3945
4624
|
} : {}
|
|
3946
4625
|
}, requestId, workflow.finalize_idempotency_key);
|
|
3947
|
-
|
|
4626
|
+
try {
|
|
4627
|
+
finalized = await finalizeOnce();
|
|
4628
|
+
} catch (error) {
|
|
4629
|
+
if (error instanceof ApiError && (error.code === "STALE_PUSH" || error.code === "PROJECT_VERSION_CONFLICT") && !finalizeRetried) {
|
|
4630
|
+
finalizeRetried = true;
|
|
4631
|
+
baseline = await syncToLatest(root, project, baseline, client);
|
|
4632
|
+
workflow = resetSession(workflow, proposalManifestHash);
|
|
4633
|
+
await atomicWriteJson(workflowPath, workflow);
|
|
4634
|
+
session = await client.createProposalSession(projectId, {
|
|
4635
|
+
schema_version: 1,
|
|
4636
|
+
request_id: requestId,
|
|
4637
|
+
client_id: clientId,
|
|
4638
|
+
base_project_version: baseline.complete_project_version,
|
|
4639
|
+
base_manifest_hash: sha256Bytes(canonicalJson(baseline)),
|
|
4640
|
+
proposal_manifest: { files: preview.operations },
|
|
4641
|
+
artifact_manifest: { schema_version: 1, files: preview.operations }
|
|
4642
|
+
}, requestId, workflow.session_idempotency_key);
|
|
4643
|
+
workflow.session_id = session.session_id;
|
|
4644
|
+
workflow.session_expires_at = session.expires_at;
|
|
4645
|
+
workflow.max_chunk_bytes = session.max_chunk_bytes;
|
|
4646
|
+
await atomicWriteJson(workflowPath, workflow);
|
|
4647
|
+
try {
|
|
4648
|
+
finalized = await finalizeOnce();
|
|
4649
|
+
} catch (retryError) {
|
|
4650
|
+
if (retryError instanceof ApiError && (retryError.code === "STALE_PUSH" || retryError.code === "PROJECT_VERSION_CONFLICT")) {
|
|
4651
|
+
throw staleBaselineError(retryError.code);
|
|
4652
|
+
}
|
|
4653
|
+
throw retryError;
|
|
4654
|
+
}
|
|
4655
|
+
} else if (error instanceof ApiError && (error.code === "STALE_PUSH" || error.code === "PROJECT_VERSION_CONFLICT")) {
|
|
4656
|
+
throw staleBaselineError(error.code);
|
|
4657
|
+
} else {
|
|
4658
|
+
throw error;
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
let pushWarning;
|
|
4662
|
+
if (finalized.artifact_id !== null) {
|
|
4663
|
+
try {
|
|
4664
|
+
const publishedManifest = artifactManifestSchema.parse(await client.getArtifactManifest(finalized.artifact_id, requestId));
|
|
4665
|
+
const advanced = await advanceBaselineFromArtifact({
|
|
4666
|
+
projectRoot: root,
|
|
4667
|
+
manifest: publishedManifest,
|
|
4668
|
+
requestId
|
|
4669
|
+
}, baseline);
|
|
4670
|
+
if (advanced.localChanged) {
|
|
4671
|
+
pushWarning = "LOCAL_CHANGED_DURING_PUSH";
|
|
4672
|
+
} else {
|
|
4673
|
+
baseline = advanced.baseline;
|
|
4674
|
+
}
|
|
4675
|
+
} catch {
|
|
4676
|
+
pushWarning = "BASELINE_ADVANCE_DEFERRED";
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
await atomicWriteJson(join11(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
|
|
3948
4680
|
schema_version: 1,
|
|
3949
4681
|
request_id: requestId,
|
|
3950
4682
|
project_id: projectId,
|
|
@@ -3952,10 +4684,17 @@ async function pushProject(options) {
|
|
|
3952
4684
|
status: finalized.status,
|
|
3953
4685
|
artifact_id: finalized.artifact_id,
|
|
3954
4686
|
operation_count: preview.operations.length,
|
|
4687
|
+
warning: pushWarning ?? null,
|
|
3955
4688
|
recorded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3956
4689
|
});
|
|
3957
|
-
await
|
|
3958
|
-
return {
|
|
4690
|
+
await rm5(workflowPath, { force: true });
|
|
4691
|
+
return {
|
|
4692
|
+
preview,
|
|
4693
|
+
proposalId: finalized.proposal_id,
|
|
4694
|
+
projectId,
|
|
4695
|
+
artifactId: finalized.artifact_id,
|
|
4696
|
+
...pushWarning === void 0 ? {} : { warning: pushWarning }
|
|
4697
|
+
};
|
|
3959
4698
|
} catch (error) {
|
|
3960
4699
|
if (error instanceof PushWorkflowError) {
|
|
3961
4700
|
throw error;
|
|
@@ -3982,8 +4721,8 @@ async function pushProject(options) {
|
|
|
3982
4721
|
}
|
|
3983
4722
|
|
|
3984
4723
|
// ../core/dist/state/cleanup.js
|
|
3985
|
-
import { readFile as
|
|
3986
|
-
import { join as
|
|
4724
|
+
import { readFile as readFile11, readdir as readdir5, rm as rm6 } from "node:fs/promises";
|
|
4725
|
+
import { join as join12 } from "node:path";
|
|
3987
4726
|
function isSafeEntryName(name) {
|
|
3988
4727
|
return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
|
|
3989
4728
|
}
|
|
@@ -4007,7 +4746,7 @@ async function cleanupProject(options) {
|
|
|
4007
4746
|
continue;
|
|
4008
4747
|
let journal;
|
|
4009
4748
|
try {
|
|
4010
|
-
journal = JSON.parse(await
|
|
4749
|
+
journal = JSON.parse(await readFile11(join12(layout.transactions, name, "journal.json"), "utf8"));
|
|
4011
4750
|
} catch {
|
|
4012
4751
|
continue;
|
|
4013
4752
|
}
|
|
@@ -4025,7 +4764,7 @@ async function cleanupProject(options) {
|
|
|
4025
4764
|
continue;
|
|
4026
4765
|
pruned.push(entry.id);
|
|
4027
4766
|
if (!options.dryRun) {
|
|
4028
|
-
await
|
|
4767
|
+
await rm6(join12(layout.transactions, entry.id), { recursive: true, force: true });
|
|
4029
4768
|
}
|
|
4030
4769
|
}
|
|
4031
4770
|
}
|
|
@@ -4034,7 +4773,7 @@ async function cleanupProject(options) {
|
|
|
4034
4773
|
continue;
|
|
4035
4774
|
removedCache.push(name);
|
|
4036
4775
|
if (!options.dryRun) {
|
|
4037
|
-
await
|
|
4776
|
+
await rm6(join12(layout.serverArtifacts, name), { recursive: true, force: true });
|
|
4038
4777
|
}
|
|
4039
4778
|
}
|
|
4040
4779
|
return {
|
|
@@ -4088,10 +4827,10 @@ var AGENT_DESCRIPTORS = {
|
|
|
4088
4827
|
var INSTALLABLE_AGENTS = Object.keys(AGENT_DESCRIPTORS).filter((agent) => AGENT_DESCRIPTORS[agent]?.installable === true);
|
|
4089
4828
|
|
|
4090
4829
|
// ../core/dist/transaction/recovery.js
|
|
4091
|
-
import { readFile as
|
|
4092
|
-
import { join as
|
|
4830
|
+
import { readFile as readFile12, readdir as readdir6, rm as rm7, stat as stat3 } from "node:fs/promises";
|
|
4831
|
+
import { join as join13 } from "node:path";
|
|
4093
4832
|
async function recoverTransaction(projectRoot, transactionId) {
|
|
4094
|
-
const journal = JSON.parse(await
|
|
4833
|
+
const journal = JSON.parse(await readFile12(join13(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
|
|
4095
4834
|
if (journal.state === "committed") {
|
|
4096
4835
|
return { transactionId, status: "committed" };
|
|
4097
4836
|
}
|
|
@@ -4118,7 +4857,7 @@ async function listTransactions(projectRoot) {
|
|
|
4118
4857
|
const transactions = [];
|
|
4119
4858
|
for (const transactionId of names) {
|
|
4120
4859
|
try {
|
|
4121
|
-
const journal = JSON.parse(await
|
|
4860
|
+
const journal = JSON.parse(await readFile12(join13(root, transactionId, "journal.json"), "utf8"));
|
|
4122
4861
|
transactions.push({
|
|
4123
4862
|
transactionId,
|
|
4124
4863
|
kind: journal.kind,
|
|
@@ -4133,7 +4872,7 @@ async function listTransactions(projectRoot) {
|
|
|
4133
4872
|
async function pendingTransactions(projectRoot) {
|
|
4134
4873
|
return (await listTransactions(projectRoot)).filter((item2) => RECOVERY_STATES.has(item2.state));
|
|
4135
4874
|
}
|
|
4136
|
-
async function
|
|
4875
|
+
async function pathExists2(path) {
|
|
4137
4876
|
try {
|
|
4138
4877
|
await stat3(path);
|
|
4139
4878
|
return true;
|
|
@@ -4149,12 +4888,12 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
4149
4888
|
if (latest === void 0) {
|
|
4150
4889
|
throw new Error("no committed update transaction is available for rollback");
|
|
4151
4890
|
}
|
|
4152
|
-
const transactionRoot =
|
|
4153
|
-
const journal = JSON.parse(await
|
|
4154
|
-
const after = JSON.parse(await
|
|
4891
|
+
const transactionRoot = join13(stateLayout(projectRoot).transactions, latest.transactionId);
|
|
4892
|
+
const journal = JSON.parse(await readFile12(join13(transactionRoot, "journal.json"), "utf8"));
|
|
4893
|
+
const after = JSON.parse(await readFile12(join13(transactionRoot, "after", "manifest.json"), "utf8"));
|
|
4155
4894
|
for (const entry of after) {
|
|
4156
|
-
const target =
|
|
4157
|
-
const exists6 = await
|
|
4895
|
+
const target = join13(projectRoot, entry.path);
|
|
4896
|
+
const exists6 = await pathExists2(target);
|
|
4158
4897
|
if (exists6 !== entry.exists || exists6 && await sha256File(target) !== entry.hash) {
|
|
4159
4898
|
throw new Error("cannot rollback dirty path: " + entry.path);
|
|
4160
4899
|
}
|
|
@@ -4166,10 +4905,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
4166
4905
|
continue;
|
|
4167
4906
|
}
|
|
4168
4907
|
seen.add(snapshot.path);
|
|
4169
|
-
const target =
|
|
4170
|
-
const exists6 = await
|
|
4908
|
+
const target = join13(projectRoot, snapshot.path);
|
|
4909
|
+
const exists6 = await pathExists2(target);
|
|
4171
4910
|
if (snapshot.existed && snapshot.snapshot_name !== null) {
|
|
4172
|
-
const content = await
|
|
4911
|
+
const content = await readFile12(join13(transactionRoot, "before", snapshot.snapshot_name));
|
|
4173
4912
|
operations.push({
|
|
4174
4913
|
operation: exists6 ? "modify" : "add",
|
|
4175
4914
|
path: snapshot.path,
|
|
@@ -4198,7 +4937,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
4198
4937
|
if (!removable) {
|
|
4199
4938
|
continue;
|
|
4200
4939
|
}
|
|
4201
|
-
await
|
|
4940
|
+
await rm7(join13(stateLayout(projectRoot).transactions, item2.transactionId), {
|
|
4202
4941
|
recursive: true,
|
|
4203
4942
|
force: true
|
|
4204
4943
|
});
|
|
@@ -4207,31 +4946,9 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
4207
4946
|
return removed;
|
|
4208
4947
|
}
|
|
4209
4948
|
|
|
4210
|
-
// ../core/dist/update/conflicts.js
|
|
4211
|
-
function operationTargetPath(operation) {
|
|
4212
|
-
return operation.operation === "rename" ? operation.to_path : operation.path;
|
|
4213
|
-
}
|
|
4214
|
-
function operationSourcePath(operation) {
|
|
4215
|
-
return operation.operation === "rename" ? operation.from_path : operation.path;
|
|
4216
|
-
}
|
|
4217
|
-
function operationAlreadyApplied(operation, baseline, projectVersion) {
|
|
4218
|
-
const target = baseline.files[operationTargetPath(operation)];
|
|
4219
|
-
if (operation.operation === "delete") {
|
|
4220
|
-
return target?.deleted === true && target.last_applied_version === projectVersion;
|
|
4221
|
-
}
|
|
4222
|
-
return target?.deleted === false && target?.last_applied_version === projectVersion && target.baseline_hash === operation.content_sha256;
|
|
4223
|
-
}
|
|
4224
|
-
function managedBlockDirty(currentContent, managedBlockHash) {
|
|
4225
|
-
if (managedBlockHash === void 0) {
|
|
4226
|
-
return true;
|
|
4227
|
-
}
|
|
4228
|
-
const block = extractManagedBlock(currentContent);
|
|
4229
|
-
return block === null || sha256Bytes(block) !== managedBlockHash;
|
|
4230
|
-
}
|
|
4231
|
-
|
|
4232
4949
|
// ../core/dist/update/update.js
|
|
4233
|
-
import {
|
|
4234
|
-
import { join as
|
|
4950
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
4951
|
+
import { join as join14, resolve as resolve7 } from "node:path";
|
|
4235
4952
|
import { parse as parseYaml8 } from "yaml";
|
|
4236
4953
|
var UpdateWorkflowError = class extends Error {
|
|
4237
4954
|
exitCode;
|
|
@@ -4243,90 +4960,11 @@ var UpdateWorkflowError = class extends Error {
|
|
|
4243
4960
|
this.code = code;
|
|
4244
4961
|
}
|
|
4245
4962
|
};
|
|
4246
|
-
async function pathExists2(path) {
|
|
4247
|
-
try {
|
|
4248
|
-
await lstat3(path);
|
|
4249
|
-
return true;
|
|
4250
|
-
} catch (error) {
|
|
4251
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
4252
|
-
return false;
|
|
4253
|
-
}
|
|
4254
|
-
throw error;
|
|
4255
|
-
}
|
|
4256
|
-
}
|
|
4257
|
-
async function optionalContent(path) {
|
|
4258
|
-
return await pathExists2(path) ? readFile12(path, "utf8") : null;
|
|
4259
|
-
}
|
|
4260
|
-
function manifestPayloadHash(manifest) {
|
|
4261
|
-
const payload = { ...manifest };
|
|
4262
|
-
delete payload.manifest_sha256;
|
|
4263
|
-
return sha256Bytes(canonicalJson(payload));
|
|
4264
|
-
}
|
|
4265
|
-
function expectedBase(operation) {
|
|
4266
|
-
return operation.operation === "add" ? null : operation.base_content_sha256;
|
|
4267
|
-
}
|
|
4268
|
-
function contentHash(operation) {
|
|
4269
|
-
return operation.operation === "delete" ? null : operation.content_sha256;
|
|
4270
|
-
}
|
|
4271
|
-
async function loadBlob(root, client, artifactId, operation, requestId, dryRun) {
|
|
4272
|
-
if (operation.operation === "delete") {
|
|
4273
|
-
return null;
|
|
4274
|
-
}
|
|
4275
|
-
const hash = operation.content_sha256;
|
|
4276
|
-
const cacheRoot = join13(root, ".harness", "cache", "server-artifacts", artifactId);
|
|
4277
|
-
const cachePath = join13(cacheRoot, "blobs", hash.replace(":", "_"));
|
|
4278
|
-
if (await pathExists2(cachePath) && await sha256File(cachePath) === hash) {
|
|
4279
|
-
return readFile12(cachePath, "utf8");
|
|
4280
|
-
}
|
|
4281
|
-
const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
|
|
4282
|
-
if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
|
|
4283
|
-
await rm7(cachePath, { force: true });
|
|
4284
|
-
throw new UpdateWorkflowError("artifact blob size or hash mismatch", 4, "ARTIFACT_HASH_MISMATCH");
|
|
4285
|
-
}
|
|
4286
|
-
if (!dryRun) {
|
|
4287
|
-
await atomicWriteFile(cachePath, bytes);
|
|
4288
|
-
}
|
|
4289
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
4290
|
-
}
|
|
4291
|
-
function nextBaselineEntry(operation, finalContent, projectVersion) {
|
|
4292
|
-
const block = finalContent === null ? null : extractManagedBlock(finalContent);
|
|
4293
|
-
return {
|
|
4294
|
-
baseline_hash: contentHash(operation),
|
|
4295
|
-
local_hash_at_apply: finalContent === null ? null : sha256Bytes(finalContent),
|
|
4296
|
-
file_kind: operation.file_kind,
|
|
4297
|
-
last_applied_version: projectVersion,
|
|
4298
|
-
deleted: operation.operation === "delete",
|
|
4299
|
-
...block === null ? {} : { managed_block_hash: sha256Bytes(block) }
|
|
4300
|
-
};
|
|
4301
|
-
}
|
|
4302
|
-
function transactionOperation(item2) {
|
|
4303
|
-
if (item2.equivalent) {
|
|
4304
|
-
return null;
|
|
4305
|
-
}
|
|
4306
|
-
const operation = item2.operation;
|
|
4307
|
-
const target = operationTargetPath(operation);
|
|
4308
|
-
if (operation.operation === "delete") {
|
|
4309
|
-
return item2.finalContent === null ? { operation: "delete", path: target } : { operation: "modify", path: target, content: item2.finalContent };
|
|
4310
|
-
}
|
|
4311
|
-
if (operation.operation === "rename") {
|
|
4312
|
-
return {
|
|
4313
|
-
operation: "rename",
|
|
4314
|
-
from_path: operation.from_path,
|
|
4315
|
-
to_path: operation.to_path,
|
|
4316
|
-
content: item2.finalContent ?? item2.content ?? ""
|
|
4317
|
-
};
|
|
4318
|
-
}
|
|
4319
|
-
return {
|
|
4320
|
-
operation: "modify",
|
|
4321
|
-
path: target,
|
|
4322
|
-
content: item2.finalContent ?? item2.content ?? ""
|
|
4323
|
-
};
|
|
4324
|
-
}
|
|
4325
4963
|
async function updateProject(options) {
|
|
4326
|
-
const root =
|
|
4964
|
+
const root = resolve7(options.projectRoot);
|
|
4327
4965
|
let project;
|
|
4328
4966
|
try {
|
|
4329
|
-
project = projectConfigSchema.parse(parseYaml8(await
|
|
4967
|
+
project = projectConfigSchema.parse(parseYaml8(await readFile13(join14(root, ".harness", "project.yaml"), "utf8")));
|
|
4330
4968
|
} catch {
|
|
4331
4969
|
throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
4332
4970
|
}
|
|
@@ -4365,183 +5003,18 @@ async function updateProject(options) {
|
|
|
4365
5003
|
...options.fetch === void 0 ? {} : { fetch: options.fetch }
|
|
4366
5004
|
});
|
|
4367
5005
|
try {
|
|
4368
|
-
const
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
operations: [],
|
|
4381
|
-
applied: [],
|
|
4382
|
-
skipped: [],
|
|
4383
|
-
transactionId: null,
|
|
4384
|
-
dryRun: options.dryRun
|
|
4385
|
-
};
|
|
4386
|
-
}
|
|
4387
|
-
const manifest = artifactManifestSchema.parse(await client.getArtifactManifest(discovery.artifact_id, requestId));
|
|
4388
|
-
if (manifest.artifact_id !== discovery.artifact_id || manifest.project_id !== project.project.project_id || manifest.project_version === null || manifestPayloadHash(manifest) !== manifest.manifest_sha256) {
|
|
4389
|
-
throw new UpdateWorkflowError("artifact manifest integrity check failed", 4, "ARTIFACT_HASH_MISMATCH");
|
|
4390
|
-
}
|
|
4391
|
-
const blobs = /* @__PURE__ */ new Map();
|
|
4392
|
-
for (const operation of manifest.files) {
|
|
4393
|
-
const policy = classifyFile(operationTargetPath(operation));
|
|
4394
|
-
if (decideUpdate(policy, false).apply) {
|
|
4395
|
-
blobs.set(operation, await loadBlob(root, client, manifest.artifact_id, operation, requestId, options.dryRun));
|
|
4396
|
-
}
|
|
4397
|
-
}
|
|
4398
|
-
const prepared = [];
|
|
4399
|
-
const skipped = [];
|
|
4400
|
-
for (const operation of manifest.files) {
|
|
4401
|
-
if (operationAlreadyApplied(operation, baseline, manifest.project_version)) {
|
|
4402
|
-
continue;
|
|
4403
|
-
}
|
|
4404
|
-
const source = operationSourcePath(operation);
|
|
4405
|
-
const target = operationTargetPath(operation);
|
|
4406
|
-
const policy = classifyFile(target);
|
|
4407
|
-
const staticDecision = decideUpdate(policy, false);
|
|
4408
|
-
if (!staticDecision.apply) {
|
|
4409
|
-
skipped.push({ path: target, operation: operation.operation, reason: staticDecision.reason });
|
|
4410
|
-
continue;
|
|
4411
|
-
}
|
|
4412
|
-
const previous = baseline.files[source];
|
|
4413
|
-
if (expectedBase(operation) !== (previous?.baseline_hash ?? null)) {
|
|
4414
|
-
skipped.push({ path: target, operation: operation.operation, reason: "baseline-diverged" });
|
|
4415
|
-
continue;
|
|
4416
|
-
}
|
|
4417
|
-
const sourceContent = await optionalContent(join13(root, source));
|
|
4418
|
-
const targetContent = target === source ? sourceContent : await optionalContent(join13(root, target));
|
|
4419
|
-
const incoming = blobs.get(operation) ?? null;
|
|
4420
|
-
const incomingHash = contentHash(operation);
|
|
4421
|
-
let equivalent = incomingHash !== null && targetContent !== null && sha256Bytes(targetContent) === incomingHash;
|
|
4422
|
-
let dirty = false;
|
|
4423
|
-
if (operation.operation === "add") {
|
|
4424
|
-
if (targetContent !== null && !equivalent) {
|
|
4425
|
-
if (policy.update_policy === "managed-block-only" && incoming !== null) {
|
|
4426
|
-
const incomingBlock = extractManagedBlock(incoming) ?? incoming.trim();
|
|
4427
|
-
equivalent = extractManagedBlock(targetContent) === incomingBlock;
|
|
4428
|
-
dirty = !equivalent;
|
|
4429
|
-
} else {
|
|
4430
|
-
dirty = true;
|
|
4431
|
-
}
|
|
4432
|
-
}
|
|
4433
|
-
} else if (sourceContent === null) {
|
|
4434
|
-
dirty = operation.operation !== "delete";
|
|
4435
|
-
} else if (policy.update_policy === "managed-block-only") {
|
|
4436
|
-
dirty = previous?.managed_block_hash === void 0 ? (previous?.local_hash_at_apply ?? previous?.baseline_hash) !== sha256Bytes(sourceContent) : managedBlockDirty(sourceContent, previous.managed_block_hash);
|
|
4437
|
-
} else {
|
|
4438
|
-
dirty = (previous?.local_hash_at_apply ?? previous?.baseline_hash) !== sha256Bytes(sourceContent);
|
|
4439
|
-
}
|
|
4440
|
-
if (operation.operation === "rename" && targetContent !== null && !equivalent) {
|
|
4441
|
-
dirty = true;
|
|
4442
|
-
}
|
|
4443
|
-
if (dirty) {
|
|
4444
|
-
skipped.push({ path: target, operation: operation.operation, reason: "local-dirty" });
|
|
4445
|
-
continue;
|
|
4446
|
-
}
|
|
4447
|
-
let finalContent = incoming;
|
|
4448
|
-
if (policy.update_policy === "managed-block-only") {
|
|
4449
|
-
if (operation.operation === "delete") {
|
|
4450
|
-
finalContent = sourceContent === null ? null : removeManagedBlock(sourceContent);
|
|
4451
|
-
equivalent = finalContent === sourceContent;
|
|
4452
|
-
} else if (incoming !== null) {
|
|
4453
|
-
const incomingBlock = extractManagedBlock(incoming) ?? incoming.trim();
|
|
4454
|
-
const incomingId = extractSingleManagedBlockById(incoming)?.id;
|
|
4455
|
-
const blockId = operation.operation === "add" || operation.operation === "modify" ? operation.block_id ?? incomingId : void 0;
|
|
4456
|
-
finalContent = blockId !== void 0 ? upsertManagedBlockById(targetContent ?? "", blockId, incomingBlock) : upsertManagedBlock(targetContent ?? "", incomingBlock);
|
|
4457
|
-
equivalent = finalContent === targetContent;
|
|
4458
|
-
}
|
|
4459
|
-
} else if (operation.operation === "delete" && sourceContent === null) {
|
|
4460
|
-
equivalent = true;
|
|
4461
|
-
}
|
|
4462
|
-
prepared.push({ operation, content: incoming, finalContent, equivalent });
|
|
4463
|
-
}
|
|
4464
|
-
const applied = prepared.map((item2) => operationTargetPath(item2.operation));
|
|
4465
|
-
if (options.dryRun) {
|
|
4466
|
-
return {
|
|
4467
|
-
requestId,
|
|
4468
|
-
projectId: project.project.project_id,
|
|
4469
|
-
artifactId: manifest.artifact_id,
|
|
4470
|
-
observedProjectVersion: manifest.project_version,
|
|
4471
|
-
operations: manifest.files,
|
|
4472
|
-
applied,
|
|
4473
|
-
skipped,
|
|
4474
|
-
transactionId: null,
|
|
4475
|
-
dryRun: true
|
|
4476
|
-
};
|
|
4477
|
-
}
|
|
4478
|
-
await atomicWriteJson(join13(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
|
|
4479
|
-
const lock = await acquireProtocolLock(root, "update", { requestId });
|
|
4480
|
-
try {
|
|
4481
|
-
const nextBaseline = baselineManifestSchema.parse(structuredClone(baseline));
|
|
4482
|
-
for (const item2 of prepared) {
|
|
4483
|
-
const target = operationTargetPath(item2.operation);
|
|
4484
|
-
nextBaseline.files[target] = nextBaselineEntry(item2.operation, item2.finalContent, manifest.project_version);
|
|
4485
|
-
if (item2.operation.operation === "rename") {
|
|
4486
|
-
nextBaseline.files[item2.operation.from_path] = {
|
|
4487
|
-
baseline_hash: null,
|
|
4488
|
-
local_hash_at_apply: null,
|
|
4489
|
-
file_kind: item2.operation.file_kind,
|
|
4490
|
-
last_applied_version: manifest.project_version,
|
|
4491
|
-
deleted: true
|
|
4492
|
-
};
|
|
4493
|
-
}
|
|
4494
|
-
}
|
|
4495
|
-
if (skipped.length === 0) {
|
|
4496
|
-
nextBaseline.complete_project_version = manifest.project_version;
|
|
4497
|
-
nextBaseline.artifact_manifest_hash = manifest.manifest_sha256;
|
|
4498
|
-
nextBaseline.latest_artifact_id = manifest.artifact_id;
|
|
4499
|
-
}
|
|
4500
|
-
const transactionId = "tx_update_" + Date.now() + "_" + uuidV7();
|
|
4501
|
-
const reportPath = ".harness/reports/update-" + requestId + ".json";
|
|
4502
|
-
const report = {
|
|
4503
|
-
schema_version: 1,
|
|
4504
|
-
request_id: requestId,
|
|
4505
|
-
artifact_id: manifest.artifact_id,
|
|
4506
|
-
observed_project_version: manifest.project_version,
|
|
4507
|
-
status: skipped.length === 0 ? "applied" : "partial_due_to_dirty",
|
|
4508
|
-
applied,
|
|
4509
|
-
skipped,
|
|
4510
|
-
transaction_id: transactionId
|
|
4511
|
-
};
|
|
4512
|
-
const operations = prepared.map((item2) => transactionOperation(item2)).filter((item2) => item2 !== null);
|
|
4513
|
-
operations.push({
|
|
4514
|
-
operation: "modify",
|
|
4515
|
-
path: ".harness/state/baseline/manifest.json",
|
|
4516
|
-
content: JSON.stringify(nextBaseline, null, 2) + "\n"
|
|
4517
|
-
}, {
|
|
4518
|
-
operation: "add",
|
|
4519
|
-
path: reportPath,
|
|
4520
|
-
content: JSON.stringify(report, null, 2) + "\n"
|
|
4521
|
-
}, {
|
|
4522
|
-
operation: "modify",
|
|
4523
|
-
path: ".harness/state/local/last-update.json",
|
|
4524
|
-
content: JSON.stringify(report, null, 2) + "\n"
|
|
4525
|
-
});
|
|
4526
|
-
await runTransaction(root, operations, {
|
|
4527
|
-
id: transactionId,
|
|
4528
|
-
kind: "update",
|
|
4529
|
-
...options.transactionOptions ?? {}
|
|
4530
|
-
});
|
|
4531
|
-
return {
|
|
4532
|
-
requestId,
|
|
4533
|
-
projectId: project.project.project_id,
|
|
4534
|
-
artifactId: manifest.artifact_id,
|
|
4535
|
-
observedProjectVersion: manifest.project_version,
|
|
4536
|
-
operations: manifest.files,
|
|
4537
|
-
applied,
|
|
4538
|
-
skipped,
|
|
4539
|
-
transactionId,
|
|
4540
|
-
dryRun: false
|
|
4541
|
-
};
|
|
4542
|
-
} finally {
|
|
4543
|
-
await lock.release();
|
|
4544
|
-
}
|
|
5006
|
+
const result = await synchronizeArtifacts({
|
|
5007
|
+
projectRoot: root,
|
|
5008
|
+
project,
|
|
5009
|
+
client,
|
|
5010
|
+
requestId,
|
|
5011
|
+
dryRun: options.dryRun,
|
|
5012
|
+
...options.conflictStrategy === void 0 ? {} : { conflictStrategy: options.conflictStrategy },
|
|
5013
|
+
...options.resolveOverrides === void 0 ? {} : { resolveOverrides: options.resolveOverrides },
|
|
5014
|
+
...options.confirmConflictStrategy === void 0 ? {} : { confirmConflictStrategy: options.confirmConflictStrategy },
|
|
5015
|
+
...options.transactionOptions === void 0 ? {} : { transactionOptions: options.transactionOptions }
|
|
5016
|
+
}, baseline);
|
|
5017
|
+
return result;
|
|
4545
5018
|
} catch (error) {
|
|
4546
5019
|
if (error instanceof UpdateWorkflowError) {
|
|
4547
5020
|
throw error;
|
|
@@ -4549,16 +5022,25 @@ async function updateProject(options) {
|
|
|
4549
5022
|
if (error instanceof ApiError) {
|
|
4550
5023
|
throw new UpdateWorkflowError(error.message, error.status === 401 || error.status === 403 ? 8 : error.status === 409 ? 5 : 4, error.code);
|
|
4551
5024
|
}
|
|
5025
|
+
if (error instanceof Error && error.message === "ARTIFACT_HASH_MISMATCH") {
|
|
5026
|
+
throw new UpdateWorkflowError("artifact blob size or hash mismatch", 4, "ARTIFACT_HASH_MISMATCH");
|
|
5027
|
+
}
|
|
4552
5028
|
if (error instanceof Error && error.name === "ZodError") {
|
|
4553
5029
|
throw new UpdateWorkflowError("artifact schema validation failed", 7, "SCHEMA_VALIDATION_FAILED");
|
|
4554
5030
|
}
|
|
5031
|
+
if (error instanceof Error && error.message.startsWith("DUPLICATE_ARTIFACT_ID")) {
|
|
5032
|
+
throw new UpdateWorkflowError(error.message, 4, "DUPLICATE_ARTIFACT_ID");
|
|
5033
|
+
}
|
|
5034
|
+
if (error instanceof Error && error.message === "MAX_SYNC_ARTIFACT_ITERATIONS_EXCEEDED") {
|
|
5035
|
+
throw new UpdateWorkflowError(error.message, 4, "SYNC_ITERATION_LIMIT");
|
|
5036
|
+
}
|
|
4555
5037
|
throw new UpdateWorkflowError(error instanceof Error ? error.message : "update failed", 4, "NETWORK_OR_SERVER_ERROR");
|
|
4556
5038
|
}
|
|
4557
5039
|
}
|
|
4558
5040
|
|
|
4559
5041
|
// src/config/init-config.ts
|
|
4560
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4561
|
-
import { readFile as
|
|
5042
|
+
import { isAbsolute as isAbsolute2, join as join15 } from "node:path";
|
|
5043
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
4562
5044
|
var InitConfigurationError = class extends Error {
|
|
4563
5045
|
exitCode;
|
|
4564
5046
|
code;
|
|
@@ -4641,9 +5123,9 @@ function hasOwn(record, key) {
|
|
|
4641
5123
|
async function resolveInitConfig(cwd, flags, prompts = {}, warnings = []) {
|
|
4642
5124
|
let fileConfig = {};
|
|
4643
5125
|
if (flags.config !== void 0) {
|
|
4644
|
-
const path = isAbsolute2(flags.config) ? flags.config :
|
|
5126
|
+
const path = isAbsolute2(flags.config) ? flags.config : join15(cwd, flags.config);
|
|
4645
5127
|
try {
|
|
4646
|
-
fileConfig = JSON.parse(await
|
|
5128
|
+
fileConfig = JSON.parse(await readFile14(path, "utf8"));
|
|
4647
5129
|
} catch (error) {
|
|
4648
5130
|
throw new InitConfigurationError(
|
|
4649
5131
|
"unable to read init config: " + (error instanceof Error ? error.message : String(error)),
|
|
@@ -4725,8 +5207,8 @@ function serializeCliResult(result) {
|
|
|
4725
5207
|
}
|
|
4726
5208
|
|
|
4727
5209
|
// src/config/codebuddy-setup.ts
|
|
4728
|
-
import { mkdir as mkdir4, readFile as
|
|
4729
|
-
import { basename as basename2, dirname as dirname4, extname, join as
|
|
5210
|
+
import { mkdir as mkdir4, readFile as readFile15, readdir as readdir7, stat as stat4, writeFile as writeFile5 } from "node:fs/promises";
|
|
5211
|
+
import { basename as basename2, dirname as dirname4, extname, join as join16 } from "node:path";
|
|
4730
5212
|
var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
|
|
4731
5213
|
"harness-general.md",
|
|
4732
5214
|
"harness-general.mdc",
|
|
@@ -4745,7 +5227,7 @@ async function exists4(path) {
|
|
|
4745
5227
|
}
|
|
4746
5228
|
async function readJsonObject(path) {
|
|
4747
5229
|
try {
|
|
4748
|
-
const parsed = JSON.parse(await
|
|
5230
|
+
const parsed = JSON.parse(await readFile15(path, "utf8"));
|
|
4749
5231
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4750
5232
|
} catch (error) {
|
|
4751
5233
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
|
|
@@ -4753,27 +5235,27 @@ async function readJsonObject(path) {
|
|
|
4753
5235
|
}
|
|
4754
5236
|
}
|
|
4755
5237
|
async function inspectCodeBuddySetup(projectRoot) {
|
|
4756
|
-
const rulesRoot =
|
|
5238
|
+
const rulesRoot = join16(projectRoot, ".claude", "rules");
|
|
4757
5239
|
let claudeRules = [];
|
|
4758
5240
|
try {
|
|
4759
5241
|
claudeRules = (await readdir7(rulesRoot, { withFileTypes: true })).filter((entry) => entry.isFile() && [".md", ".mdc"].includes(extname(entry.name).toLowerCase())).map((entry) => entry.name).filter((name) => !MANAGED_RULE_NAMES.has(name)).sort();
|
|
4760
5242
|
} catch (error) {
|
|
4761
5243
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
4762
5244
|
}
|
|
4763
|
-
const mcp = await readJsonObject(
|
|
5245
|
+
const mcp = await readJsonObject(join16(projectRoot, ".mcp.json"));
|
|
4764
5246
|
const servers = mcp?.mcpServers;
|
|
4765
5247
|
const configured = servers !== null && typeof servers === "object" && !Array.isArray(servers) && Object.prototype.hasOwnProperty.call(servers, "codegraph");
|
|
4766
5248
|
return {
|
|
4767
5249
|
claudeRules,
|
|
4768
|
-
hasCodeGraphIndex: await exists4(
|
|
5250
|
+
hasCodeGraphIndex: await exists4(join16(projectRoot, ".codegraph")),
|
|
4769
5251
|
codeGraphConfigured: configured
|
|
4770
5252
|
};
|
|
4771
5253
|
}
|
|
4772
5254
|
function destinationTargets(root, surface, name) {
|
|
4773
5255
|
const stem = basename2(name, extname(name));
|
|
4774
5256
|
const targets = [];
|
|
4775
|
-
if (surface !== "cli") targets.push(
|
|
4776
|
-
if (surface !== "ide") targets.push(
|
|
5257
|
+
if (surface !== "cli") targets.push(join16(root, ".codebuddy", ".rules", `${stem}.mdc`));
|
|
5258
|
+
if (surface !== "ide") targets.push(join16(root, ".codebuddy", "rules", `${stem}.md`));
|
|
4777
5259
|
return targets;
|
|
4778
5260
|
}
|
|
4779
5261
|
async function applyCodeBuddySetup(options) {
|
|
@@ -4787,8 +5269,8 @@ async function applyCodeBuddySetup(options) {
|
|
|
4787
5269
|
if (options.syncClaudeRules) {
|
|
4788
5270
|
const plan = await inspectCodeBuddySetup(options.projectRoot);
|
|
4789
5271
|
for (const name of plan.claudeRules) {
|
|
4790
|
-
const source =
|
|
4791
|
-
const content = await
|
|
5272
|
+
const source = join16(options.projectRoot, ".claude", "rules", name);
|
|
5273
|
+
const content = await readFile15(source, "utf8");
|
|
4792
5274
|
if (SENSITIVE_ASSIGNMENT.test(content) || PRIVATE_KEY_BLOCK.test(content)) {
|
|
4793
5275
|
result.skippedSensitive.push(name);
|
|
4794
5276
|
continue;
|
|
@@ -4805,7 +5287,7 @@ async function applyCodeBuddySetup(options) {
|
|
|
4805
5287
|
}
|
|
4806
5288
|
}
|
|
4807
5289
|
if (options.configureCodeGraph) {
|
|
4808
|
-
const path =
|
|
5290
|
+
const path = join16(options.projectRoot, ".mcp.json");
|
|
4809
5291
|
const current = await readJsonObject(path);
|
|
4810
5292
|
if (current === null) {
|
|
4811
5293
|
result.warnings.push(".mcp.json \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u5DF2\u4FDD\u7559\u539F\u6587\u4EF6\u5E76\u8DF3\u8FC7 CodeGraph MCP \u914D\u7F6E");
|
|
@@ -4829,13 +5311,13 @@ async function applyCodeBuddySetup(options) {
|
|
|
4829
5311
|
}
|
|
4830
5312
|
|
|
4831
5313
|
// src/commands/refresh.ts
|
|
4832
|
-
import { readFile as
|
|
4833
|
-
import { join as
|
|
5314
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
5315
|
+
import { join as join17 } from "node:path";
|
|
4834
5316
|
import { parse as parseYaml9 } from "yaml";
|
|
4835
5317
|
async function detectProject(root) {
|
|
4836
5318
|
let content;
|
|
4837
5319
|
try {
|
|
4838
|
-
content = await
|
|
5320
|
+
content = await readFile16(join17(root, ".harness", "project.yaml"), "utf8");
|
|
4839
5321
|
} catch (error) {
|
|
4840
5322
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
4841
5323
|
return { status: "absent" };
|
|
@@ -5449,7 +5931,7 @@ async function runPush(options, dependencies) {
|
|
|
5449
5931
|
warnings: result.preview.skipped,
|
|
5450
5932
|
errors: []
|
|
5451
5933
|
};
|
|
5452
|
-
dependencies.stdout(options.json === true ? serializeCliResult(output) : options.dryRun === true ? "Push preview contains " + items.length + " operations.\n" : "Pushed artifact " + result.artifactId + " (proposal " + result.proposalId + ").\n");
|
|
5934
|
+
dependencies.stdout(options.json === true ? serializeCliResult(output) : options.dryRun === true ? "Push preview contains " + items.length + " operations.\n" : "Pushed artifact " + ("artifactId" in result ? String(result.artifactId) : "unknown") + " (proposal " + result.proposalId + ").\n");
|
|
5453
5935
|
return 0;
|
|
5454
5936
|
} catch (error) {
|
|
5455
5937
|
if (attempt === 0 && options.nonInteractive !== true && options.dryRun !== true && error instanceof PushWorkflowError && (error.code === "SERVER_URL_REQUIRED" || error.code === "TOKEN_INVALID")) {
|
|
@@ -5509,19 +5991,46 @@ async function runPush(options, dependencies) {
|
|
|
5509
5991
|
}
|
|
5510
5992
|
|
|
5511
5993
|
// src/commands/update.ts
|
|
5994
|
+
function parseResolveOverrides(entries) {
|
|
5995
|
+
const map = /* @__PURE__ */ new Map();
|
|
5996
|
+
for (const entry of entries ?? []) {
|
|
5997
|
+
const eq = entry.indexOf("=");
|
|
5998
|
+
if (eq <= 0) {
|
|
5999
|
+
throw new UpdateWorkflowError(
|
|
6000
|
+
"invalid --resolve value; expected path=keep-local|accept-remote",
|
|
6001
|
+
3,
|
|
6002
|
+
"RESOLVE_OPTION_INVALID"
|
|
6003
|
+
);
|
|
6004
|
+
}
|
|
6005
|
+
const path = entry.slice(0, eq).trim();
|
|
6006
|
+
const strategy = entry.slice(eq + 1).trim();
|
|
6007
|
+
if (path.length === 0 || strategy !== "keep-local" && strategy !== "accept-remote") {
|
|
6008
|
+
throw new UpdateWorkflowError(
|
|
6009
|
+
"invalid --resolve value; expected path=keep-local|accept-remote",
|
|
6010
|
+
3,
|
|
6011
|
+
"RESOLVE_OPTION_INVALID"
|
|
6012
|
+
);
|
|
6013
|
+
}
|
|
6014
|
+
map.set(path, strategy);
|
|
6015
|
+
}
|
|
6016
|
+
return map;
|
|
6017
|
+
}
|
|
5512
6018
|
async function runUpdate(options, dependencies) {
|
|
5513
6019
|
const requestId = uuidV7();
|
|
5514
6020
|
if (options.nonInteractive === true && options.yes !== true && options.dryRun !== true) {
|
|
5515
6021
|
dependencies.stderr("\u975E\u4EA4\u4E92\u6A21\u5F0F\u66F4\u65B0\u9700\u8981 --yes\n");
|
|
5516
6022
|
return 2;
|
|
5517
6023
|
}
|
|
6024
|
+
const resolveOverrides = parseResolveOverrides(options.resolve);
|
|
5518
6025
|
const execute = async (dryRun) => updateProject({
|
|
5519
6026
|
projectRoot: dependencies.cwd,
|
|
5520
6027
|
...options.serverUrl === void 0 ? {} : { serverUrl: options.serverUrl },
|
|
5521
6028
|
...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
|
|
5522
6029
|
env: dependencies.env,
|
|
5523
6030
|
dryRun,
|
|
5524
|
-
fetch: dependencies.fetch
|
|
6031
|
+
fetch: dependencies.fetch,
|
|
6032
|
+
...options.conflictStrategy === void 0 ? {} : { conflictStrategy: options.conflictStrategy },
|
|
6033
|
+
...resolveOverrides.size === 0 ? {} : { resolveOverrides }
|
|
5525
6034
|
});
|
|
5526
6035
|
try {
|
|
5527
6036
|
let result;
|
|
@@ -5539,22 +6048,24 @@ async function runUpdate(options, dependencies) {
|
|
|
5539
6048
|
}
|
|
5540
6049
|
const applied = new Set(result.applied);
|
|
5541
6050
|
const skippedByPath = new Map(result.skipped.map((item2) => [item2.path, item2]));
|
|
6051
|
+
const acknowledgedByPath = new Map(result.acknowledged.map((item2) => [item2.path, item2]));
|
|
5542
6052
|
const items = result.operations.map((operation) => {
|
|
5543
6053
|
const path = operation.operation === "rename" ? operation.to_path : operation.path;
|
|
5544
6054
|
const skipped = skippedByPath.get(path);
|
|
6055
|
+
const acknowledged = acknowledgedByPath.get(path);
|
|
5545
6056
|
return {
|
|
5546
6057
|
path,
|
|
5547
6058
|
operation: operation.operation,
|
|
5548
6059
|
file_kind: operation.file_kind,
|
|
5549
6060
|
policy: "update",
|
|
5550
|
-
status: skipped !== void 0 ? "skipped" : applied.has(path) ? options.dryRun === true ? "planned" : "applied" : "already-applied",
|
|
5551
|
-
reason: skipped?.reason ?? null,
|
|
6061
|
+
status: skipped !== void 0 ? "skipped" : acknowledged !== void 0 ? "acknowledged" : applied.has(path) ? options.dryRun === true ? "planned" : "applied" : "already-applied",
|
|
6062
|
+
reason: skipped?.reason ?? acknowledged?.reason ?? null,
|
|
5552
6063
|
size_bytes: "size_bytes" in operation ? operation.size_bytes : 0
|
|
5553
6064
|
};
|
|
5554
6065
|
});
|
|
5555
|
-
const exitCode = result.
|
|
6066
|
+
const exitCode = result.conflicts.length > 0 ? 5 : 0;
|
|
5556
6067
|
const output = {
|
|
5557
|
-
schema_version:
|
|
6068
|
+
schema_version: 2,
|
|
5558
6069
|
command: "update",
|
|
5559
6070
|
request_id: requestId,
|
|
5560
6071
|
dry_run: options.dryRun === true,
|
|
@@ -5565,13 +6076,15 @@ async function runUpdate(options, dependencies) {
|
|
|
5565
6076
|
discovered: result.operations.length,
|
|
5566
6077
|
applied: options.dryRun === true ? 0 : result.applied.length,
|
|
5567
6078
|
planned: options.dryRun === true ? result.applied.length : 0,
|
|
6079
|
+
acknowledged: result.acknowledged.length,
|
|
6080
|
+
resolved: result.resolvedKeepLocal.length + result.resolvedAcceptRemote.length,
|
|
5568
6081
|
skipped: result.skipped.length
|
|
5569
6082
|
},
|
|
5570
6083
|
items,
|
|
5571
|
-
warnings: result.skipped,
|
|
6084
|
+
warnings: [...result.acknowledged, ...result.skipped],
|
|
5572
6085
|
errors: []
|
|
5573
6086
|
};
|
|
5574
|
-
dependencies.stdout(options.json === true ? serializeCliResult(output) : result.artifactId === null ? "\u6CA1\u6709\u53EF\u5E94\u7528\u7684\u5DF2\u6279\u51C6\u66F4\u65B0\u3002\n" : "\u66F4\u65B0\u5B8C\u6210\uFF1A\u5DF2\u5E94\u7528 " + result.applied.length + " \u4E2A\u6761\u76EE\uFF0C\
|
|
6087
|
+
dependencies.stdout(options.json === true ? serializeCliResult(output) : result.artifactId === null ? "\u6CA1\u6709\u53EF\u5E94\u7528\u7684\u5DF2\u6279\u51C6\u66F4\u65B0\u3002\n" : "\u66F4\u65B0\u5B8C\u6210\uFF1A\u5DF2\u5E94\u7528 " + result.applied.length + " \u4E2A\u6761\u76EE\uFF0C\u5DF2\u786E\u8BA4 " + result.acknowledged.length + " \u4E2A\uFF0C\u51B2\u7A81 " + result.conflicts.length + " \u4E2A\u3002\n");
|
|
5575
6088
|
return exitCode;
|
|
5576
6089
|
} catch (error) {
|
|
5577
6090
|
const exitCode = error instanceof UpdateWorkflowError ? error.exitCode : 1;
|
|
@@ -5601,7 +6114,7 @@ async function runUpdate(options, dependencies) {
|
|
|
5601
6114
|
|
|
5602
6115
|
// src/commands/recovery.ts
|
|
5603
6116
|
import { stat as stat5 } from "node:fs/promises";
|
|
5604
|
-
import { join as
|
|
6117
|
+
import { join as join18 } from "node:path";
|
|
5605
6118
|
async function exists5(path) {
|
|
5606
6119
|
try {
|
|
5607
6120
|
await stat5(path);
|
|
@@ -5661,7 +6174,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
5661
6174
|
return 5;
|
|
5662
6175
|
}
|
|
5663
6176
|
}
|
|
5664
|
-
const initialized = await exists5(
|
|
6177
|
+
const initialized = await exists5(join18(dependencies.cwd, ".harness", "project.yaml"));
|
|
5665
6178
|
if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
|
|
5666
6179
|
return null;
|
|
5667
6180
|
}
|
|
@@ -5704,8 +6217,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
5704
6217
|
}
|
|
5705
6218
|
|
|
5706
6219
|
// src/workflow-data/resolve.ts
|
|
5707
|
-
import { cp, mkdir as mkdir5, readdir as readdir8, readFile as
|
|
5708
|
-
import { dirname as dirname5, join as
|
|
6220
|
+
import { cp, mkdir as mkdir5, readdir as readdir8, readFile as readFile17, rm as rm8, stat as stat6 } from "node:fs/promises";
|
|
6221
|
+
import { dirname as dirname5, join as join19, relative as relative2 } from "node:path";
|
|
5709
6222
|
import { fileURLToPath } from "node:url";
|
|
5710
6223
|
var WorkflowDataResolutionError = class extends Error {
|
|
5711
6224
|
code;
|
|
@@ -5742,14 +6255,14 @@ async function listFilesRecursive(root, base = root) {
|
|
|
5742
6255
|
const entries = await readdir8(root, { withFileTypes: true });
|
|
5743
6256
|
const files = [];
|
|
5744
6257
|
for (const entry of entries) {
|
|
5745
|
-
const absolute =
|
|
6258
|
+
const absolute = join19(root, entry.name);
|
|
5746
6259
|
if (entry.isDirectory()) {
|
|
5747
6260
|
files.push(...await listFilesRecursive(absolute, base));
|
|
5748
6261
|
continue;
|
|
5749
6262
|
}
|
|
5750
6263
|
if (!entry.isFile()) continue;
|
|
5751
6264
|
const rel = relative2(base, absolute).replaceAll("\\", "/");
|
|
5752
|
-
files.push({ path: rel, content: await
|
|
6265
|
+
files.push({ path: rel, content: await readFile17(absolute, "utf8") });
|
|
5753
6266
|
}
|
|
5754
6267
|
return files;
|
|
5755
6268
|
}
|
|
@@ -5762,7 +6275,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
5762
6275
|
const manifest = await readWorkflowFamilyManifest(resourcesRoot);
|
|
5763
6276
|
const expected = manifest.content_sha256;
|
|
5764
6277
|
if (typeof expected !== "string" || expected.length === 0) return;
|
|
5765
|
-
const harnessRoot =
|
|
6278
|
+
const harnessRoot = join19(resourcesRoot, "harness");
|
|
5766
6279
|
if (!await pathExists3(harnessRoot)) {
|
|
5767
6280
|
throw new WorkflowDataResolutionError(
|
|
5768
6281
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
|
|
@@ -5784,37 +6297,37 @@ async function monorepoResourcesRoot() {
|
|
|
5784
6297
|
const here = dirname5(fileURLToPath(import.meta.url));
|
|
5785
6298
|
const candidates = [
|
|
5786
6299
|
// TypeScript source: packages/cli/src/workflow-data -> packages/workflow-data-harness.
|
|
5787
|
-
|
|
6300
|
+
join19(here, "../../../workflow-data-harness"),
|
|
5788
6301
|
// Bundled CLI: packages/cli/dist -> packages/workflow-data-harness.
|
|
5789
|
-
|
|
6302
|
+
join19(here, "../../workflow-data-harness"),
|
|
5790
6303
|
// Test/build layouts that preserve additional source directory levels.
|
|
5791
|
-
|
|
6304
|
+
join19(here, "../../../../packages/workflow-data-harness")
|
|
5792
6305
|
];
|
|
5793
6306
|
for (const candidate of candidates) {
|
|
5794
|
-
if (await pathExists3(
|
|
6307
|
+
if (await pathExists3(join19(candidate, "harness", "manifests"))) return candidate;
|
|
5795
6308
|
}
|
|
5796
6309
|
return null;
|
|
5797
6310
|
}
|
|
5798
6311
|
async function siblingWorkflowPackage(cwd) {
|
|
5799
6312
|
const here = dirname5(fileURLToPath(import.meta.url));
|
|
5800
6313
|
const candidates = [
|
|
5801
|
-
|
|
6314
|
+
join19(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
|
|
5802
6315
|
// Published layout: node_modules/hunter-harness/dist/bin.js alongside the
|
|
5803
6316
|
// scoped workflow package in node_modules/@hunter-harness/workflow-harness.
|
|
5804
|
-
|
|
6317
|
+
join19(here, "..", "..", "@hunter-harness", "workflow-harness"),
|
|
5805
6318
|
// Monorepo bundled layout: packages/cli/dist/bin.js.
|
|
5806
|
-
|
|
6319
|
+
join19(here, "..", "..", "workflow-data-harness")
|
|
5807
6320
|
];
|
|
5808
6321
|
for (const candidate of candidates) {
|
|
5809
|
-
if (await pathExists3(
|
|
6322
|
+
if (await pathExists3(join19(candidate, "harness", "manifests"))) return candidate;
|
|
5810
6323
|
}
|
|
5811
6324
|
return null;
|
|
5812
6325
|
}
|
|
5813
6326
|
async function readCachedNpmPackageVersion(cacheRoot) {
|
|
5814
|
-
const manifestPath =
|
|
6327
|
+
const manifestPath = join19(cacheRoot, "package.json");
|
|
5815
6328
|
if (!await pathExists3(manifestPath)) return null;
|
|
5816
6329
|
try {
|
|
5817
|
-
const pkg = JSON.parse(await
|
|
6330
|
+
const pkg = JSON.parse(await readFile17(manifestPath, "utf8"));
|
|
5818
6331
|
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
|
|
5819
6332
|
} catch {
|
|
5820
6333
|
return null;
|
|
@@ -5837,12 +6350,12 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
|
|
|
5837
6350
|
}
|
|
5838
6351
|
async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
|
|
5839
6352
|
await mkdir5(cacheRoot, { recursive: true });
|
|
5840
|
-
const staging =
|
|
6353
|
+
const staging = join19(cacheRoot, ".extract");
|
|
5841
6354
|
await rm8(staging, { recursive: true, force: true });
|
|
5842
6355
|
await mkdir5(staging, { recursive: true });
|
|
5843
6356
|
await extract(packageSpec, staging);
|
|
5844
|
-
const packageDir = await pathExists3(
|
|
5845
|
-
if (!await pathExists3(
|
|
6357
|
+
const packageDir = await pathExists3(join19(staging, "harness", "manifests")) ? staging : join19(staging, "package");
|
|
6358
|
+
if (!await pathExists3(join19(packageDir, "harness", "manifests"))) {
|
|
5846
6359
|
throw new WorkflowDataResolutionError(
|
|
5847
6360
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
|
|
5848
6361
|
"WORKFLOW_DATA_INVALID",
|
|
@@ -5871,8 +6384,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
|
|
|
5871
6384
|
const packageName = workflowPackageName(family, options.env);
|
|
5872
6385
|
const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
|
|
5873
6386
|
const cacheKey = packageSpec.replace("/", "+");
|
|
5874
|
-
const cacheRoot =
|
|
5875
|
-
if (await pathExists3(
|
|
6387
|
+
const cacheRoot = join19(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
|
|
6388
|
+
if (await pathExists3(join19(cacheRoot, "harness", "manifests"))) {
|
|
5876
6389
|
if (version === "latest") {
|
|
5877
6390
|
const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
|
|
5878
6391
|
if (stale) {
|
|
@@ -5918,9 +6431,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
|
|
|
5918
6431
|
return `\u65E0\u6CD5\u83B7\u53D6\u5DE5\u4F5C\u6D41\u6570\u636E\u5305 ${packageSpec}\uFF1A\u672C\u5730\u7F13\u5B58\u4E0D\u5B58\u5728\u4E14\u83B7\u53D6\u5931\u8D25\u3002\u53EF${hintRoot}\u3002\u539F\u56E0\uFF1A${code !== "" ? code + " " : ""}${detail}`;
|
|
5919
6432
|
}
|
|
5920
6433
|
async function readWorkflowFamilyManifest(resourcesRoot) {
|
|
5921
|
-
const manifestPath =
|
|
6434
|
+
const manifestPath = join19(resourcesRoot, "hunter-workflow-family.json");
|
|
5922
6435
|
if (!await pathExists3(manifestPath)) return {};
|
|
5923
|
-
return JSON.parse(await
|
|
6436
|
+
return JSON.parse(await readFile17(manifestPath, "utf8"));
|
|
5924
6437
|
}
|
|
5925
6438
|
|
|
5926
6439
|
// src/bin.ts
|
|
@@ -6011,7 +6524,15 @@ async function runCli(argv, overrides = {}) {
|
|
|
6011
6524
|
dependencies
|
|
6012
6525
|
);
|
|
6013
6526
|
});
|
|
6014
|
-
addCommonOptions(program.command("update")).description("\u5E94\u7528\u5DF2\u6279\u51C6\u7684\u670D\u52A1\u7AEF\u4EA7\u7269").
|
|
6527
|
+
addCommonOptions(program.command("update")).description("\u5E94\u7528\u5DF2\u6279\u51C6\u7684\u670D\u52A1\u7AEF\u4EA7\u7269").option(
|
|
6528
|
+
"--conflict-strategy <strategy>",
|
|
6529
|
+
"manual | keep-local | accept-remote\uFF08\u9ED8\u8BA4 manual\uFF1Brename \u51B2\u7A81\u59CB\u7EC8 manual\uFF09"
|
|
6530
|
+
).option(
|
|
6531
|
+
"--resolve <path=strategy>",
|
|
6532
|
+
"\u5355\u8DEF\u5F84\u8986\u76D6 keep-local|accept-remote\uFF08\u53EF\u91CD\u590D\uFF09",
|
|
6533
|
+
(value, previous) => [...previous, value],
|
|
6534
|
+
[]
|
|
6535
|
+
).action(async (options) => {
|
|
6015
6536
|
exitCode = await runUpdate(
|
|
6016
6537
|
{ ...program.opts(), ...options },
|
|
6017
6538
|
dependencies
|