hunter-harness 0.2.12 → 0.2.14
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 +961 -381
- 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,74 @@ 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
|
|
3739
|
-
function
|
|
3740
|
-
|
|
4391
|
+
var CONFLICT_PREVIEW_LIMIT = 8;
|
|
4392
|
+
function conflictGroup(path) {
|
|
4393
|
+
const knowledgeMatch = path.match(/^\.harness\/knowledge\/entries\/([^/]+)\//);
|
|
4394
|
+
if (knowledgeMatch?.[1] !== void 0) {
|
|
4395
|
+
return `knowledge/${knowledgeMatch[1]}`;
|
|
4396
|
+
}
|
|
4397
|
+
if (path.startsWith(".harness/knowledge/"))
|
|
4398
|
+
return "knowledge/other";
|
|
4399
|
+
if (path === ".harness/context-index.json")
|
|
4400
|
+
return "context-index";
|
|
4401
|
+
return path.split("/", 1)[0] ?? "other";
|
|
4402
|
+
}
|
|
4403
|
+
function conflictGroups(conflicts) {
|
|
4404
|
+
const groups = {};
|
|
4405
|
+
for (const conflict2 of conflicts) {
|
|
4406
|
+
const group = conflictGroup(conflict2.path);
|
|
4407
|
+
groups[group] = (groups[group] ?? 0) + 1;
|
|
4408
|
+
}
|
|
4409
|
+
return groups;
|
|
4410
|
+
}
|
|
4411
|
+
function formatStaleBaselineMessage(conflicts = []) {
|
|
4412
|
+
const lines = [
|
|
4413
|
+
"\u670D\u52A1\u7AEF\u5B58\u5728\u672C\u5730\u5C1A\u672A\u786E\u8BA4\u7684\u66F4\u65B0\uFF0Cpush \u5DF2\u6682\u505C\u4EE5\u907F\u514D\u8986\u76D6\u5E76\u884C\u4FEE\u6539\u3002"
|
|
4414
|
+
];
|
|
4415
|
+
if (conflicts.length > 0) {
|
|
4416
|
+
lines.push(`\u68C0\u6D4B\u5230 ${conflicts.length} \u4E2A\u51B2\u7A81\uFF08\u4EC5\u5C55\u793A\u524D ${CONFLICT_PREVIEW_LIMIT} \u4E2A\uFF09\uFF1A`);
|
|
4417
|
+
for (const conflict2 of conflicts.slice(0, CONFLICT_PREVIEW_LIMIT)) {
|
|
4418
|
+
lines.push(` - ${conflict2.path}`);
|
|
4419
|
+
}
|
|
4420
|
+
if (conflicts.length > CONFLICT_PREVIEW_LIMIT) {
|
|
4421
|
+
lines.push(` - \u2026\u53E6 ${conflicts.length - CONFLICT_PREVIEW_LIMIT} \u4E2A`);
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
lines.push("\u4FDD\u7559\u672C\u5730\u5E76\u7EE7\u7EED\uFF1Anpx hunter-harness update --conflict-strategy keep-local --yes", "\u63A5\u53D7\u670D\u52A1\u7AEF\u7248\u672C\uFF1Anpx hunter-harness update --conflict-strategy accept-remote --yes", "\u9010\u9879\u5904\u7406\uFF1Anpx hunter-harness update --resolve <path>=keep-local|accept-remote");
|
|
4425
|
+
return lines.join("\n");
|
|
4426
|
+
}
|
|
4427
|
+
function staleBaselineError(code, conflicts) {
|
|
4428
|
+
const conflictList = [...conflicts ?? []];
|
|
4429
|
+
return new PushWorkflowError(formatStaleBaselineMessage(conflictList), 5, code, conflictList.length === 0 ? void 0 : {
|
|
4430
|
+
conflicts: conflictList,
|
|
4431
|
+
conflict_count: conflictList.length,
|
|
4432
|
+
conflict_groups: conflictGroups(conflictList)
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
async function syncToLatest(root, project, baseline, client, confirmConflictStrategy) {
|
|
4436
|
+
const syncResult = await synchronizeArtifacts({
|
|
4437
|
+
projectRoot: root,
|
|
4438
|
+
project,
|
|
4439
|
+
client,
|
|
4440
|
+
requestId: uuidV7(),
|
|
4441
|
+
dryRun: false,
|
|
4442
|
+
conflictStrategy: "manual",
|
|
4443
|
+
...confirmConflictStrategy === void 0 ? {} : { confirmConflictStrategy }
|
|
4444
|
+
}, baseline);
|
|
4445
|
+
if (syncResult.conflicts.length > 0) {
|
|
4446
|
+
throw staleBaselineError("PROJECT_VERSION_CONFLICT", syncResult.conflicts);
|
|
4447
|
+
}
|
|
4448
|
+
return await readBaseline(root);
|
|
4449
|
+
}
|
|
4450
|
+
async function autoRebaseIfServerAdvanced(root, project, baseline, client, remoteVersion, confirmConflictStrategy) {
|
|
4451
|
+
if (remoteVersion === baseline.complete_project_version) {
|
|
4452
|
+
return baseline;
|
|
4453
|
+
}
|
|
4454
|
+
const updated = await syncToLatest(root, project, baseline, client, confirmConflictStrategy);
|
|
4455
|
+
if (remoteVersion !== null && updated.complete_project_version !== remoteVersion) {
|
|
4456
|
+
throw staleBaselineError("PROJECT_VERSION_CONFLICT");
|
|
4457
|
+
}
|
|
4458
|
+
return updated;
|
|
3741
4459
|
}
|
|
3742
4460
|
function makePreview(baseline, files, confirmedProjectLocal, ignorePaths, deletedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3743
4461
|
const filteredFiles = {};
|
|
@@ -3777,7 +4495,7 @@ async function bindProject(root, project, baseline, projectId) {
|
|
|
3777
4495
|
return { project: nextProject, baseline: nextBaseline };
|
|
3778
4496
|
}
|
|
3779
4497
|
async function pushProject(options) {
|
|
3780
|
-
const root =
|
|
4498
|
+
const root = resolve6(options.projectRoot);
|
|
3781
4499
|
let project = await readProject(root);
|
|
3782
4500
|
let baseline = await readBaseline(root);
|
|
3783
4501
|
const profile = parseHarnessProfile(project.project.profiles[0]);
|
|
@@ -3830,9 +4548,8 @@ async function pushProject(options) {
|
|
|
3830
4548
|
const boundAtStart = project.project.project_id;
|
|
3831
4549
|
if (boundAtStart !== null) {
|
|
3832
4550
|
const remote = await client.getProject(boundAtStart, uuidV7());
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
}
|
|
4551
|
+
baseline = await autoRebaseIfServerAdvanced(root, project, baseline, client, remote.latest_project_version, options.confirmConflictStrategy);
|
|
4552
|
+
preview = makePreview(baseline, await managedFiles(root, project), options.confirmedProjectLocal ?? [], installedPaths);
|
|
3836
4553
|
}
|
|
3837
4554
|
const initialSkip = await resolveSensitiveScanSkip(preview, options);
|
|
3838
4555
|
if (initialSkip.cancelled === true) {
|
|
@@ -3844,7 +4561,7 @@ async function pushProject(options) {
|
|
|
3844
4561
|
if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
|
|
3845
4562
|
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
3846
4563
|
}
|
|
3847
|
-
const workflowPath =
|
|
4564
|
+
const workflowPath = join11(root, ".harness", "state", "local", "push-workflow.json");
|
|
3848
4565
|
const priorWorkflow = await readOptionalJson(workflowPath);
|
|
3849
4566
|
const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
|
|
3850
4567
|
const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
|
|
@@ -3935,7 +4652,9 @@ async function pushProject(options) {
|
|
|
3935
4652
|
});
|
|
3936
4653
|
}
|
|
3937
4654
|
}
|
|
3938
|
-
|
|
4655
|
+
let finalizeRetried = false;
|
|
4656
|
+
let finalized;
|
|
4657
|
+
const finalizeOnce = async () => client.finalizeProposal(session.session_id, {
|
|
3939
4658
|
schema_version: 1,
|
|
3940
4659
|
manifest_sha256: proposalManifestHash,
|
|
3941
4660
|
base_artifact_id: baseline.latest_artifact_id ?? null,
|
|
@@ -3944,7 +4663,60 @@ async function pushProject(options) {
|
|
|
3944
4663
|
...sensitiveScanSkipReason === void 0 ? {} : { sensitive_scan_skip_reason: sensitiveScanSkipReason }
|
|
3945
4664
|
} : {}
|
|
3946
4665
|
}, requestId, workflow.finalize_idempotency_key);
|
|
3947
|
-
|
|
4666
|
+
try {
|
|
4667
|
+
finalized = await finalizeOnce();
|
|
4668
|
+
} catch (error) {
|
|
4669
|
+
if (error instanceof ApiError && (error.code === "STALE_PUSH" || error.code === "PROJECT_VERSION_CONFLICT") && !finalizeRetried) {
|
|
4670
|
+
finalizeRetried = true;
|
|
4671
|
+
baseline = await syncToLatest(root, project, baseline, client, options.confirmConflictStrategy);
|
|
4672
|
+
workflow = resetSession(workflow, proposalManifestHash);
|
|
4673
|
+
await atomicWriteJson(workflowPath, workflow);
|
|
4674
|
+
session = await client.createProposalSession(projectId, {
|
|
4675
|
+
schema_version: 1,
|
|
4676
|
+
request_id: requestId,
|
|
4677
|
+
client_id: clientId,
|
|
4678
|
+
base_project_version: baseline.complete_project_version,
|
|
4679
|
+
base_manifest_hash: sha256Bytes(canonicalJson(baseline)),
|
|
4680
|
+
proposal_manifest: { files: preview.operations },
|
|
4681
|
+
artifact_manifest: { schema_version: 1, files: preview.operations }
|
|
4682
|
+
}, requestId, workflow.session_idempotency_key);
|
|
4683
|
+
workflow.session_id = session.session_id;
|
|
4684
|
+
workflow.session_expires_at = session.expires_at;
|
|
4685
|
+
workflow.max_chunk_bytes = session.max_chunk_bytes;
|
|
4686
|
+
await atomicWriteJson(workflowPath, workflow);
|
|
4687
|
+
try {
|
|
4688
|
+
finalized = await finalizeOnce();
|
|
4689
|
+
} catch (retryError) {
|
|
4690
|
+
if (retryError instanceof ApiError && (retryError.code === "STALE_PUSH" || retryError.code === "PROJECT_VERSION_CONFLICT")) {
|
|
4691
|
+
throw staleBaselineError(retryError.code);
|
|
4692
|
+
}
|
|
4693
|
+
throw retryError;
|
|
4694
|
+
}
|
|
4695
|
+
} else if (error instanceof ApiError && (error.code === "STALE_PUSH" || error.code === "PROJECT_VERSION_CONFLICT")) {
|
|
4696
|
+
throw staleBaselineError(error.code);
|
|
4697
|
+
} else {
|
|
4698
|
+
throw error;
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
let pushWarning;
|
|
4702
|
+
if (finalized.artifact_id !== null) {
|
|
4703
|
+
try {
|
|
4704
|
+
const publishedManifest = artifactManifestSchema.parse(await client.getArtifactManifest(finalized.artifact_id, requestId));
|
|
4705
|
+
const advanced = await advanceBaselineFromArtifact({
|
|
4706
|
+
projectRoot: root,
|
|
4707
|
+
manifest: publishedManifest,
|
|
4708
|
+
requestId
|
|
4709
|
+
}, baseline);
|
|
4710
|
+
if (advanced.localChanged) {
|
|
4711
|
+
pushWarning = "LOCAL_CHANGED_DURING_PUSH";
|
|
4712
|
+
} else {
|
|
4713
|
+
baseline = advanced.baseline;
|
|
4714
|
+
}
|
|
4715
|
+
} catch {
|
|
4716
|
+
pushWarning = "BASELINE_ADVANCE_DEFERRED";
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
await atomicWriteJson(join11(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
|
|
3948
4720
|
schema_version: 1,
|
|
3949
4721
|
request_id: requestId,
|
|
3950
4722
|
project_id: projectId,
|
|
@@ -3952,10 +4724,17 @@ async function pushProject(options) {
|
|
|
3952
4724
|
status: finalized.status,
|
|
3953
4725
|
artifact_id: finalized.artifact_id,
|
|
3954
4726
|
operation_count: preview.operations.length,
|
|
4727
|
+
warning: pushWarning ?? null,
|
|
3955
4728
|
recorded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3956
4729
|
});
|
|
3957
|
-
await
|
|
3958
|
-
return {
|
|
4730
|
+
await rm5(workflowPath, { force: true });
|
|
4731
|
+
return {
|
|
4732
|
+
preview,
|
|
4733
|
+
proposalId: finalized.proposal_id,
|
|
4734
|
+
projectId,
|
|
4735
|
+
artifactId: finalized.artifact_id,
|
|
4736
|
+
...pushWarning === void 0 ? {} : { warning: pushWarning }
|
|
4737
|
+
};
|
|
3959
4738
|
} catch (error) {
|
|
3960
4739
|
if (error instanceof PushWorkflowError) {
|
|
3961
4740
|
throw error;
|
|
@@ -3982,8 +4761,8 @@ async function pushProject(options) {
|
|
|
3982
4761
|
}
|
|
3983
4762
|
|
|
3984
4763
|
// ../core/dist/state/cleanup.js
|
|
3985
|
-
import { readFile as
|
|
3986
|
-
import { join as
|
|
4764
|
+
import { readFile as readFile11, readdir as readdir5, rm as rm6 } from "node:fs/promises";
|
|
4765
|
+
import { join as join12 } from "node:path";
|
|
3987
4766
|
function isSafeEntryName(name) {
|
|
3988
4767
|
return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
|
|
3989
4768
|
}
|
|
@@ -4007,7 +4786,7 @@ async function cleanupProject(options) {
|
|
|
4007
4786
|
continue;
|
|
4008
4787
|
let journal;
|
|
4009
4788
|
try {
|
|
4010
|
-
journal = JSON.parse(await
|
|
4789
|
+
journal = JSON.parse(await readFile11(join12(layout.transactions, name, "journal.json"), "utf8"));
|
|
4011
4790
|
} catch {
|
|
4012
4791
|
continue;
|
|
4013
4792
|
}
|
|
@@ -4025,7 +4804,7 @@ async function cleanupProject(options) {
|
|
|
4025
4804
|
continue;
|
|
4026
4805
|
pruned.push(entry.id);
|
|
4027
4806
|
if (!options.dryRun) {
|
|
4028
|
-
await
|
|
4807
|
+
await rm6(join12(layout.transactions, entry.id), { recursive: true, force: true });
|
|
4029
4808
|
}
|
|
4030
4809
|
}
|
|
4031
4810
|
}
|
|
@@ -4034,7 +4813,7 @@ async function cleanupProject(options) {
|
|
|
4034
4813
|
continue;
|
|
4035
4814
|
removedCache.push(name);
|
|
4036
4815
|
if (!options.dryRun) {
|
|
4037
|
-
await
|
|
4816
|
+
await rm6(join12(layout.serverArtifacts, name), { recursive: true, force: true });
|
|
4038
4817
|
}
|
|
4039
4818
|
}
|
|
4040
4819
|
return {
|
|
@@ -4088,10 +4867,10 @@ var AGENT_DESCRIPTORS = {
|
|
|
4088
4867
|
var INSTALLABLE_AGENTS = Object.keys(AGENT_DESCRIPTORS).filter((agent) => AGENT_DESCRIPTORS[agent]?.installable === true);
|
|
4089
4868
|
|
|
4090
4869
|
// ../core/dist/transaction/recovery.js
|
|
4091
|
-
import { readFile as
|
|
4092
|
-
import { join as
|
|
4870
|
+
import { readFile as readFile12, readdir as readdir6, rm as rm7, stat as stat3 } from "node:fs/promises";
|
|
4871
|
+
import { join as join13 } from "node:path";
|
|
4093
4872
|
async function recoverTransaction(projectRoot, transactionId) {
|
|
4094
|
-
const journal = JSON.parse(await
|
|
4873
|
+
const journal = JSON.parse(await readFile12(join13(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
|
|
4095
4874
|
if (journal.state === "committed") {
|
|
4096
4875
|
return { transactionId, status: "committed" };
|
|
4097
4876
|
}
|
|
@@ -4118,7 +4897,7 @@ async function listTransactions(projectRoot) {
|
|
|
4118
4897
|
const transactions = [];
|
|
4119
4898
|
for (const transactionId of names) {
|
|
4120
4899
|
try {
|
|
4121
|
-
const journal = JSON.parse(await
|
|
4900
|
+
const journal = JSON.parse(await readFile12(join13(root, transactionId, "journal.json"), "utf8"));
|
|
4122
4901
|
transactions.push({
|
|
4123
4902
|
transactionId,
|
|
4124
4903
|
kind: journal.kind,
|
|
@@ -4133,7 +4912,7 @@ async function listTransactions(projectRoot) {
|
|
|
4133
4912
|
async function pendingTransactions(projectRoot) {
|
|
4134
4913
|
return (await listTransactions(projectRoot)).filter((item2) => RECOVERY_STATES.has(item2.state));
|
|
4135
4914
|
}
|
|
4136
|
-
async function
|
|
4915
|
+
async function pathExists2(path) {
|
|
4137
4916
|
try {
|
|
4138
4917
|
await stat3(path);
|
|
4139
4918
|
return true;
|
|
@@ -4149,12 +4928,12 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
4149
4928
|
if (latest === void 0) {
|
|
4150
4929
|
throw new Error("no committed update transaction is available for rollback");
|
|
4151
4930
|
}
|
|
4152
|
-
const transactionRoot =
|
|
4153
|
-
const journal = JSON.parse(await
|
|
4154
|
-
const after = JSON.parse(await
|
|
4931
|
+
const transactionRoot = join13(stateLayout(projectRoot).transactions, latest.transactionId);
|
|
4932
|
+
const journal = JSON.parse(await readFile12(join13(transactionRoot, "journal.json"), "utf8"));
|
|
4933
|
+
const after = JSON.parse(await readFile12(join13(transactionRoot, "after", "manifest.json"), "utf8"));
|
|
4155
4934
|
for (const entry of after) {
|
|
4156
|
-
const target =
|
|
4157
|
-
const exists6 = await
|
|
4935
|
+
const target = join13(projectRoot, entry.path);
|
|
4936
|
+
const exists6 = await pathExists2(target);
|
|
4158
4937
|
if (exists6 !== entry.exists || exists6 && await sha256File(target) !== entry.hash) {
|
|
4159
4938
|
throw new Error("cannot rollback dirty path: " + entry.path);
|
|
4160
4939
|
}
|
|
@@ -4166,10 +4945,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
4166
4945
|
continue;
|
|
4167
4946
|
}
|
|
4168
4947
|
seen.add(snapshot.path);
|
|
4169
|
-
const target =
|
|
4170
|
-
const exists6 = await
|
|
4948
|
+
const target = join13(projectRoot, snapshot.path);
|
|
4949
|
+
const exists6 = await pathExists2(target);
|
|
4171
4950
|
if (snapshot.existed && snapshot.snapshot_name !== null) {
|
|
4172
|
-
const content = await
|
|
4951
|
+
const content = await readFile12(join13(transactionRoot, "before", snapshot.snapshot_name));
|
|
4173
4952
|
operations.push({
|
|
4174
4953
|
operation: exists6 ? "modify" : "add",
|
|
4175
4954
|
path: snapshot.path,
|
|
@@ -4198,7 +4977,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
4198
4977
|
if (!removable) {
|
|
4199
4978
|
continue;
|
|
4200
4979
|
}
|
|
4201
|
-
await
|
|
4980
|
+
await rm7(join13(stateLayout(projectRoot).transactions, item2.transactionId), {
|
|
4202
4981
|
recursive: true,
|
|
4203
4982
|
force: true
|
|
4204
4983
|
});
|
|
@@ -4207,31 +4986,9 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
4207
4986
|
return removed;
|
|
4208
4987
|
}
|
|
4209
4988
|
|
|
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
4989
|
// ../core/dist/update/update.js
|
|
4233
|
-
import {
|
|
4234
|
-
import { join as
|
|
4990
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
4991
|
+
import { join as join14, resolve as resolve7 } from "node:path";
|
|
4235
4992
|
import { parse as parseYaml8 } from "yaml";
|
|
4236
4993
|
var UpdateWorkflowError = class extends Error {
|
|
4237
4994
|
exitCode;
|
|
@@ -4243,90 +5000,11 @@ var UpdateWorkflowError = class extends Error {
|
|
|
4243
5000
|
this.code = code;
|
|
4244
5001
|
}
|
|
4245
5002
|
};
|
|
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
5003
|
async function updateProject(options) {
|
|
4326
|
-
const root =
|
|
5004
|
+
const root = resolve7(options.projectRoot);
|
|
4327
5005
|
let project;
|
|
4328
5006
|
try {
|
|
4329
|
-
project = projectConfigSchema.parse(parseYaml8(await
|
|
5007
|
+
project = projectConfigSchema.parse(parseYaml8(await readFile13(join14(root, ".harness", "project.yaml"), "utf8")));
|
|
4330
5008
|
} catch {
|
|
4331
5009
|
throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
4332
5010
|
}
|
|
@@ -4365,183 +5043,18 @@ async function updateProject(options) {
|
|
|
4365
5043
|
...options.fetch === void 0 ? {} : { fetch: options.fetch }
|
|
4366
5044
|
});
|
|
4367
5045
|
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
|
-
}
|
|
5046
|
+
const result = await synchronizeArtifacts({
|
|
5047
|
+
projectRoot: root,
|
|
5048
|
+
project,
|
|
5049
|
+
client,
|
|
5050
|
+
requestId,
|
|
5051
|
+
dryRun: options.dryRun,
|
|
5052
|
+
...options.conflictStrategy === void 0 ? {} : { conflictStrategy: options.conflictStrategy },
|
|
5053
|
+
...options.resolveOverrides === void 0 ? {} : { resolveOverrides: options.resolveOverrides },
|
|
5054
|
+
...options.confirmConflictStrategy === void 0 ? {} : { confirmConflictStrategy: options.confirmConflictStrategy },
|
|
5055
|
+
...options.transactionOptions === void 0 ? {} : { transactionOptions: options.transactionOptions }
|
|
5056
|
+
}, baseline);
|
|
5057
|
+
return result;
|
|
4545
5058
|
} catch (error) {
|
|
4546
5059
|
if (error instanceof UpdateWorkflowError) {
|
|
4547
5060
|
throw error;
|
|
@@ -4549,16 +5062,25 @@ async function updateProject(options) {
|
|
|
4549
5062
|
if (error instanceof ApiError) {
|
|
4550
5063
|
throw new UpdateWorkflowError(error.message, error.status === 401 || error.status === 403 ? 8 : error.status === 409 ? 5 : 4, error.code);
|
|
4551
5064
|
}
|
|
5065
|
+
if (error instanceof Error && error.message === "ARTIFACT_HASH_MISMATCH") {
|
|
5066
|
+
throw new UpdateWorkflowError("artifact blob size or hash mismatch", 4, "ARTIFACT_HASH_MISMATCH");
|
|
5067
|
+
}
|
|
4552
5068
|
if (error instanceof Error && error.name === "ZodError") {
|
|
4553
5069
|
throw new UpdateWorkflowError("artifact schema validation failed", 7, "SCHEMA_VALIDATION_FAILED");
|
|
4554
5070
|
}
|
|
5071
|
+
if (error instanceof Error && error.message.startsWith("DUPLICATE_ARTIFACT_ID")) {
|
|
5072
|
+
throw new UpdateWorkflowError(error.message, 4, "DUPLICATE_ARTIFACT_ID");
|
|
5073
|
+
}
|
|
5074
|
+
if (error instanceof Error && error.message === "MAX_SYNC_ARTIFACT_ITERATIONS_EXCEEDED") {
|
|
5075
|
+
throw new UpdateWorkflowError(error.message, 4, "SYNC_ITERATION_LIMIT");
|
|
5076
|
+
}
|
|
4555
5077
|
throw new UpdateWorkflowError(error instanceof Error ? error.message : "update failed", 4, "NETWORK_OR_SERVER_ERROR");
|
|
4556
5078
|
}
|
|
4557
5079
|
}
|
|
4558
5080
|
|
|
4559
5081
|
// src/config/init-config.ts
|
|
4560
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4561
|
-
import { readFile as
|
|
5082
|
+
import { isAbsolute as isAbsolute2, join as join15 } from "node:path";
|
|
5083
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
4562
5084
|
var InitConfigurationError = class extends Error {
|
|
4563
5085
|
exitCode;
|
|
4564
5086
|
code;
|
|
@@ -4641,9 +5163,9 @@ function hasOwn(record, key) {
|
|
|
4641
5163
|
async function resolveInitConfig(cwd, flags, prompts = {}, warnings = []) {
|
|
4642
5164
|
let fileConfig = {};
|
|
4643
5165
|
if (flags.config !== void 0) {
|
|
4644
|
-
const path = isAbsolute2(flags.config) ? flags.config :
|
|
5166
|
+
const path = isAbsolute2(flags.config) ? flags.config : join15(cwd, flags.config);
|
|
4645
5167
|
try {
|
|
4646
|
-
fileConfig = JSON.parse(await
|
|
5168
|
+
fileConfig = JSON.parse(await readFile14(path, "utf8"));
|
|
4647
5169
|
} catch (error) {
|
|
4648
5170
|
throw new InitConfigurationError(
|
|
4649
5171
|
"unable to read init config: " + (error instanceof Error ? error.message : String(error)),
|
|
@@ -4725,8 +5247,8 @@ function serializeCliResult(result) {
|
|
|
4725
5247
|
}
|
|
4726
5248
|
|
|
4727
5249
|
// src/config/codebuddy-setup.ts
|
|
4728
|
-
import { mkdir as mkdir4, readFile as
|
|
4729
|
-
import { basename as basename2, dirname as dirname4, extname, join as
|
|
5250
|
+
import { mkdir as mkdir4, readFile as readFile15, readdir as readdir7, stat as stat4, writeFile as writeFile5 } from "node:fs/promises";
|
|
5251
|
+
import { basename as basename2, dirname as dirname4, extname, join as join16 } from "node:path";
|
|
4730
5252
|
var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
|
|
4731
5253
|
"harness-general.md",
|
|
4732
5254
|
"harness-general.mdc",
|
|
@@ -4745,7 +5267,7 @@ async function exists4(path) {
|
|
|
4745
5267
|
}
|
|
4746
5268
|
async function readJsonObject(path) {
|
|
4747
5269
|
try {
|
|
4748
|
-
const parsed = JSON.parse(await
|
|
5270
|
+
const parsed = JSON.parse(await readFile15(path, "utf8"));
|
|
4749
5271
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4750
5272
|
} catch (error) {
|
|
4751
5273
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
|
|
@@ -4753,27 +5275,27 @@ async function readJsonObject(path) {
|
|
|
4753
5275
|
}
|
|
4754
5276
|
}
|
|
4755
5277
|
async function inspectCodeBuddySetup(projectRoot) {
|
|
4756
|
-
const rulesRoot =
|
|
5278
|
+
const rulesRoot = join16(projectRoot, ".claude", "rules");
|
|
4757
5279
|
let claudeRules = [];
|
|
4758
5280
|
try {
|
|
4759
5281
|
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
5282
|
} catch (error) {
|
|
4761
5283
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
4762
5284
|
}
|
|
4763
|
-
const mcp = await readJsonObject(
|
|
5285
|
+
const mcp = await readJsonObject(join16(projectRoot, ".mcp.json"));
|
|
4764
5286
|
const servers = mcp?.mcpServers;
|
|
4765
5287
|
const configured = servers !== null && typeof servers === "object" && !Array.isArray(servers) && Object.prototype.hasOwnProperty.call(servers, "codegraph");
|
|
4766
5288
|
return {
|
|
4767
5289
|
claudeRules,
|
|
4768
|
-
hasCodeGraphIndex: await exists4(
|
|
5290
|
+
hasCodeGraphIndex: await exists4(join16(projectRoot, ".codegraph")),
|
|
4769
5291
|
codeGraphConfigured: configured
|
|
4770
5292
|
};
|
|
4771
5293
|
}
|
|
4772
5294
|
function destinationTargets(root, surface, name) {
|
|
4773
5295
|
const stem = basename2(name, extname(name));
|
|
4774
5296
|
const targets = [];
|
|
4775
|
-
if (surface !== "cli") targets.push(
|
|
4776
|
-
if (surface !== "ide") targets.push(
|
|
5297
|
+
if (surface !== "cli") targets.push(join16(root, ".codebuddy", ".rules", `${stem}.mdc`));
|
|
5298
|
+
if (surface !== "ide") targets.push(join16(root, ".codebuddy", "rules", `${stem}.md`));
|
|
4777
5299
|
return targets;
|
|
4778
5300
|
}
|
|
4779
5301
|
async function applyCodeBuddySetup(options) {
|
|
@@ -4787,8 +5309,8 @@ async function applyCodeBuddySetup(options) {
|
|
|
4787
5309
|
if (options.syncClaudeRules) {
|
|
4788
5310
|
const plan = await inspectCodeBuddySetup(options.projectRoot);
|
|
4789
5311
|
for (const name of plan.claudeRules) {
|
|
4790
|
-
const source =
|
|
4791
|
-
const content = await
|
|
5312
|
+
const source = join16(options.projectRoot, ".claude", "rules", name);
|
|
5313
|
+
const content = await readFile15(source, "utf8");
|
|
4792
5314
|
if (SENSITIVE_ASSIGNMENT.test(content) || PRIVATE_KEY_BLOCK.test(content)) {
|
|
4793
5315
|
result.skippedSensitive.push(name);
|
|
4794
5316
|
continue;
|
|
@@ -4805,7 +5327,7 @@ async function applyCodeBuddySetup(options) {
|
|
|
4805
5327
|
}
|
|
4806
5328
|
}
|
|
4807
5329
|
if (options.configureCodeGraph) {
|
|
4808
|
-
const path =
|
|
5330
|
+
const path = join16(options.projectRoot, ".mcp.json");
|
|
4809
5331
|
const current = await readJsonObject(path);
|
|
4810
5332
|
if (current === null) {
|
|
4811
5333
|
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 +5351,13 @@ async function applyCodeBuddySetup(options) {
|
|
|
4829
5351
|
}
|
|
4830
5352
|
|
|
4831
5353
|
// src/commands/refresh.ts
|
|
4832
|
-
import { readFile as
|
|
4833
|
-
import { join as
|
|
5354
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
5355
|
+
import { join as join17 } from "node:path";
|
|
4834
5356
|
import { parse as parseYaml9 } from "yaml";
|
|
4835
5357
|
async function detectProject(root) {
|
|
4836
5358
|
let content;
|
|
4837
5359
|
try {
|
|
4838
|
-
content = await
|
|
5360
|
+
content = await readFile16(join17(root, ".harness", "project.yaml"), "utf8");
|
|
4839
5361
|
} catch (error) {
|
|
4840
5362
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
4841
5363
|
return { status: "absent" };
|
|
@@ -5317,6 +5839,24 @@ async function runCleanup(options, dependencies) {
|
|
|
5317
5839
|
}
|
|
5318
5840
|
|
|
5319
5841
|
// src/commands/push.ts
|
|
5842
|
+
var CONFLICT_PREVIEW_LIMIT2 = 8;
|
|
5843
|
+
async function promptStaleConflictStrategy(conflicts, dependencies) {
|
|
5844
|
+
const lines = [`\u68C0\u6D4B\u5230 ${conflicts.length} \u4E2A\u670D\u52A1\u7AEF\u5E76\u53D1\u51B2\u7A81\uFF1A`];
|
|
5845
|
+
for (const conflict2 of conflicts.slice(0, CONFLICT_PREVIEW_LIMIT2)) {
|
|
5846
|
+
lines.push(` - ${conflict2.path}`);
|
|
5847
|
+
}
|
|
5848
|
+
if (conflicts.length > CONFLICT_PREVIEW_LIMIT2) {
|
|
5849
|
+
lines.push(` - \u2026\u53E6 ${conflicts.length - CONFLICT_PREVIEW_LIMIT2} \u4E2A`);
|
|
5850
|
+
}
|
|
5851
|
+
dependencies.stderr(lines.join("\n") + "\n");
|
|
5852
|
+
const answer = await dependencies.prompt(
|
|
5853
|
+
"\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F\uFF1A\n 1. \u4FDD\u7559\u672C\u5730\u5185\u5BB9\u5E76\u7EE7\u7EED\u63A8\u9001\uFF08\u63A8\u8350\uFF09\n 2. \u63A5\u53D7\u670D\u52A1\u7AEF\u5185\u5BB9\u5E76\u7EE7\u7EED\u63A8\u9001\n 3. \u9000\u51FA\uFF0C\u7A0D\u540E\u9010\u9879\u5904\u7406\n\u8BF7\u9009\u62E9 [1]\uFF1A"
|
|
5854
|
+
);
|
|
5855
|
+
const selected = answer.trim();
|
|
5856
|
+
if (selected === "" || selected === "1") return "keep-local";
|
|
5857
|
+
if (selected === "2") return "accept-remote";
|
|
5858
|
+
return false;
|
|
5859
|
+
}
|
|
5320
5860
|
function formatFindings(details) {
|
|
5321
5861
|
if (details?.findings === void 0 || details.findings.length === 0) {
|
|
5322
5862
|
return "";
|
|
@@ -5417,7 +5957,8 @@ async function runPush(options, dependencies) {
|
|
|
5417
5957
|
const reasonAnswer = await dependencies.prompt("\u8DF3\u8FC7\u539F\u56E0\uFF08\u53EF\u9009\uFF0C\u56DE\u8F66\u8DF3\u8FC7\uFF09: ");
|
|
5418
5958
|
const reason = reasonAnswer.trim();
|
|
5419
5959
|
return reason === "" ? { skip: true } : { skip: true, reason };
|
|
5420
|
-
} }
|
|
5960
|
+
} },
|
|
5961
|
+
...options.nonInteractive === true ? {} : { confirmConflictStrategy: async (conflicts) => promptStaleConflictStrategy(conflicts, dependencies) }
|
|
5421
5962
|
});
|
|
5422
5963
|
if ("cancelled" in result && result.cancelled === true) {
|
|
5423
5964
|
return 2;
|
|
@@ -5449,7 +5990,7 @@ async function runPush(options, dependencies) {
|
|
|
5449
5990
|
warnings: result.preview.skipped,
|
|
5450
5991
|
errors: []
|
|
5451
5992
|
};
|
|
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");
|
|
5993
|
+
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
5994
|
return 0;
|
|
5454
5995
|
} catch (error) {
|
|
5455
5996
|
if (attempt === 0 && options.nonInteractive !== true && options.dryRun !== true && error instanceof PushWorkflowError && (error.code === "SERVER_URL_REQUIRED" || error.code === "TOKEN_INVALID")) {
|
|
@@ -5509,19 +6050,46 @@ async function runPush(options, dependencies) {
|
|
|
5509
6050
|
}
|
|
5510
6051
|
|
|
5511
6052
|
// src/commands/update.ts
|
|
6053
|
+
function parseResolveOverrides(entries) {
|
|
6054
|
+
const map = /* @__PURE__ */ new Map();
|
|
6055
|
+
for (const entry of entries ?? []) {
|
|
6056
|
+
const eq = entry.indexOf("=");
|
|
6057
|
+
if (eq <= 0) {
|
|
6058
|
+
throw new UpdateWorkflowError(
|
|
6059
|
+
"invalid --resolve value; expected path=keep-local|accept-remote",
|
|
6060
|
+
3,
|
|
6061
|
+
"RESOLVE_OPTION_INVALID"
|
|
6062
|
+
);
|
|
6063
|
+
}
|
|
6064
|
+
const path = entry.slice(0, eq).trim();
|
|
6065
|
+
const strategy = entry.slice(eq + 1).trim();
|
|
6066
|
+
if (path.length === 0 || strategy !== "keep-local" && strategy !== "accept-remote") {
|
|
6067
|
+
throw new UpdateWorkflowError(
|
|
6068
|
+
"invalid --resolve value; expected path=keep-local|accept-remote",
|
|
6069
|
+
3,
|
|
6070
|
+
"RESOLVE_OPTION_INVALID"
|
|
6071
|
+
);
|
|
6072
|
+
}
|
|
6073
|
+
map.set(path, strategy);
|
|
6074
|
+
}
|
|
6075
|
+
return map;
|
|
6076
|
+
}
|
|
5512
6077
|
async function runUpdate(options, dependencies) {
|
|
5513
6078
|
const requestId = uuidV7();
|
|
5514
6079
|
if (options.nonInteractive === true && options.yes !== true && options.dryRun !== true) {
|
|
5515
6080
|
dependencies.stderr("\u975E\u4EA4\u4E92\u6A21\u5F0F\u66F4\u65B0\u9700\u8981 --yes\n");
|
|
5516
6081
|
return 2;
|
|
5517
6082
|
}
|
|
6083
|
+
const resolveOverrides = parseResolveOverrides(options.resolve);
|
|
5518
6084
|
const execute = async (dryRun) => updateProject({
|
|
5519
6085
|
projectRoot: dependencies.cwd,
|
|
5520
6086
|
...options.serverUrl === void 0 ? {} : { serverUrl: options.serverUrl },
|
|
5521
6087
|
...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
|
|
5522
6088
|
env: dependencies.env,
|
|
5523
6089
|
dryRun,
|
|
5524
|
-
fetch: dependencies.fetch
|
|
6090
|
+
fetch: dependencies.fetch,
|
|
6091
|
+
...options.conflictStrategy === void 0 ? {} : { conflictStrategy: options.conflictStrategy },
|
|
6092
|
+
...resolveOverrides.size === 0 ? {} : { resolveOverrides }
|
|
5525
6093
|
});
|
|
5526
6094
|
try {
|
|
5527
6095
|
let result;
|
|
@@ -5539,22 +6107,24 @@ async function runUpdate(options, dependencies) {
|
|
|
5539
6107
|
}
|
|
5540
6108
|
const applied = new Set(result.applied);
|
|
5541
6109
|
const skippedByPath = new Map(result.skipped.map((item2) => [item2.path, item2]));
|
|
6110
|
+
const acknowledgedByPath = new Map(result.acknowledged.map((item2) => [item2.path, item2]));
|
|
5542
6111
|
const items = result.operations.map((operation) => {
|
|
5543
6112
|
const path = operation.operation === "rename" ? operation.to_path : operation.path;
|
|
5544
6113
|
const skipped = skippedByPath.get(path);
|
|
6114
|
+
const acknowledged = acknowledgedByPath.get(path);
|
|
5545
6115
|
return {
|
|
5546
6116
|
path,
|
|
5547
6117
|
operation: operation.operation,
|
|
5548
6118
|
file_kind: operation.file_kind,
|
|
5549
6119
|
policy: "update",
|
|
5550
|
-
status: skipped !== void 0 ? "skipped" : applied.has(path) ? options.dryRun === true ? "planned" : "applied" : "already-applied",
|
|
5551
|
-
reason: skipped?.reason ?? null,
|
|
6120
|
+
status: skipped !== void 0 ? "skipped" : acknowledged !== void 0 ? "acknowledged" : applied.has(path) ? options.dryRun === true ? "planned" : "applied" : "already-applied",
|
|
6121
|
+
reason: skipped?.reason ?? acknowledged?.reason ?? null,
|
|
5552
6122
|
size_bytes: "size_bytes" in operation ? operation.size_bytes : 0
|
|
5553
6123
|
};
|
|
5554
6124
|
});
|
|
5555
|
-
const exitCode = result.
|
|
6125
|
+
const exitCode = result.conflicts.length > 0 ? 5 : 0;
|
|
5556
6126
|
const output = {
|
|
5557
|
-
schema_version:
|
|
6127
|
+
schema_version: 2,
|
|
5558
6128
|
command: "update",
|
|
5559
6129
|
request_id: requestId,
|
|
5560
6130
|
dry_run: options.dryRun === true,
|
|
@@ -5565,13 +6135,15 @@ async function runUpdate(options, dependencies) {
|
|
|
5565
6135
|
discovered: result.operations.length,
|
|
5566
6136
|
applied: options.dryRun === true ? 0 : result.applied.length,
|
|
5567
6137
|
planned: options.dryRun === true ? result.applied.length : 0,
|
|
6138
|
+
acknowledged: result.acknowledged.length,
|
|
6139
|
+
resolved: result.resolvedKeepLocal.length + result.resolvedAcceptRemote.length,
|
|
5568
6140
|
skipped: result.skipped.length
|
|
5569
6141
|
},
|
|
5570
6142
|
items,
|
|
5571
|
-
warnings: result.skipped,
|
|
6143
|
+
warnings: [...result.acknowledged, ...result.skipped],
|
|
5572
6144
|
errors: []
|
|
5573
6145
|
};
|
|
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\
|
|
6146
|
+
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
6147
|
return exitCode;
|
|
5576
6148
|
} catch (error) {
|
|
5577
6149
|
const exitCode = error instanceof UpdateWorkflowError ? error.exitCode : 1;
|
|
@@ -5601,7 +6173,7 @@ async function runUpdate(options, dependencies) {
|
|
|
5601
6173
|
|
|
5602
6174
|
// src/commands/recovery.ts
|
|
5603
6175
|
import { stat as stat5 } from "node:fs/promises";
|
|
5604
|
-
import { join as
|
|
6176
|
+
import { join as join18 } from "node:path";
|
|
5605
6177
|
async function exists5(path) {
|
|
5606
6178
|
try {
|
|
5607
6179
|
await stat5(path);
|
|
@@ -5661,7 +6233,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
5661
6233
|
return 5;
|
|
5662
6234
|
}
|
|
5663
6235
|
}
|
|
5664
|
-
const initialized = await exists5(
|
|
6236
|
+
const initialized = await exists5(join18(dependencies.cwd, ".harness", "project.yaml"));
|
|
5665
6237
|
if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
|
|
5666
6238
|
return null;
|
|
5667
6239
|
}
|
|
@@ -5704,8 +6276,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
5704
6276
|
}
|
|
5705
6277
|
|
|
5706
6278
|
// src/workflow-data/resolve.ts
|
|
5707
|
-
import { cp, mkdir as mkdir5, readdir as readdir8, readFile as
|
|
5708
|
-
import { dirname as dirname5, join as
|
|
6279
|
+
import { cp, mkdir as mkdir5, readdir as readdir8, readFile as readFile17, rm as rm8, stat as stat6 } from "node:fs/promises";
|
|
6280
|
+
import { dirname as dirname5, join as join19, relative as relative2 } from "node:path";
|
|
5709
6281
|
import { fileURLToPath } from "node:url";
|
|
5710
6282
|
var WorkflowDataResolutionError = class extends Error {
|
|
5711
6283
|
code;
|
|
@@ -5742,14 +6314,14 @@ async function listFilesRecursive(root, base = root) {
|
|
|
5742
6314
|
const entries = await readdir8(root, { withFileTypes: true });
|
|
5743
6315
|
const files = [];
|
|
5744
6316
|
for (const entry of entries) {
|
|
5745
|
-
const absolute =
|
|
6317
|
+
const absolute = join19(root, entry.name);
|
|
5746
6318
|
if (entry.isDirectory()) {
|
|
5747
6319
|
files.push(...await listFilesRecursive(absolute, base));
|
|
5748
6320
|
continue;
|
|
5749
6321
|
}
|
|
5750
6322
|
if (!entry.isFile()) continue;
|
|
5751
6323
|
const rel = relative2(base, absolute).replaceAll("\\", "/");
|
|
5752
|
-
files.push({ path: rel, content: await
|
|
6324
|
+
files.push({ path: rel, content: await readFile17(absolute, "utf8") });
|
|
5753
6325
|
}
|
|
5754
6326
|
return files;
|
|
5755
6327
|
}
|
|
@@ -5762,7 +6334,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
5762
6334
|
const manifest = await readWorkflowFamilyManifest(resourcesRoot);
|
|
5763
6335
|
const expected = manifest.content_sha256;
|
|
5764
6336
|
if (typeof expected !== "string" || expected.length === 0) return;
|
|
5765
|
-
const harnessRoot =
|
|
6337
|
+
const harnessRoot = join19(resourcesRoot, "harness");
|
|
5766
6338
|
if (!await pathExists3(harnessRoot)) {
|
|
5767
6339
|
throw new WorkflowDataResolutionError(
|
|
5768
6340
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
|
|
@@ -5784,37 +6356,37 @@ async function monorepoResourcesRoot() {
|
|
|
5784
6356
|
const here = dirname5(fileURLToPath(import.meta.url));
|
|
5785
6357
|
const candidates = [
|
|
5786
6358
|
// TypeScript source: packages/cli/src/workflow-data -> packages/workflow-data-harness.
|
|
5787
|
-
|
|
6359
|
+
join19(here, "../../../workflow-data-harness"),
|
|
5788
6360
|
// Bundled CLI: packages/cli/dist -> packages/workflow-data-harness.
|
|
5789
|
-
|
|
6361
|
+
join19(here, "../../workflow-data-harness"),
|
|
5790
6362
|
// Test/build layouts that preserve additional source directory levels.
|
|
5791
|
-
|
|
6363
|
+
join19(here, "../../../../packages/workflow-data-harness")
|
|
5792
6364
|
];
|
|
5793
6365
|
for (const candidate of candidates) {
|
|
5794
|
-
if (await pathExists3(
|
|
6366
|
+
if (await pathExists3(join19(candidate, "harness", "manifests"))) return candidate;
|
|
5795
6367
|
}
|
|
5796
6368
|
return null;
|
|
5797
6369
|
}
|
|
5798
6370
|
async function siblingWorkflowPackage(cwd) {
|
|
5799
6371
|
const here = dirname5(fileURLToPath(import.meta.url));
|
|
5800
6372
|
const candidates = [
|
|
5801
|
-
|
|
6373
|
+
join19(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
|
|
5802
6374
|
// Published layout: node_modules/hunter-harness/dist/bin.js alongside the
|
|
5803
6375
|
// scoped workflow package in node_modules/@hunter-harness/workflow-harness.
|
|
5804
|
-
|
|
6376
|
+
join19(here, "..", "..", "@hunter-harness", "workflow-harness"),
|
|
5805
6377
|
// Monorepo bundled layout: packages/cli/dist/bin.js.
|
|
5806
|
-
|
|
6378
|
+
join19(here, "..", "..", "workflow-data-harness")
|
|
5807
6379
|
];
|
|
5808
6380
|
for (const candidate of candidates) {
|
|
5809
|
-
if (await pathExists3(
|
|
6381
|
+
if (await pathExists3(join19(candidate, "harness", "manifests"))) return candidate;
|
|
5810
6382
|
}
|
|
5811
6383
|
return null;
|
|
5812
6384
|
}
|
|
5813
6385
|
async function readCachedNpmPackageVersion(cacheRoot) {
|
|
5814
|
-
const manifestPath =
|
|
6386
|
+
const manifestPath = join19(cacheRoot, "package.json");
|
|
5815
6387
|
if (!await pathExists3(manifestPath)) return null;
|
|
5816
6388
|
try {
|
|
5817
|
-
const pkg = JSON.parse(await
|
|
6389
|
+
const pkg = JSON.parse(await readFile17(manifestPath, "utf8"));
|
|
5818
6390
|
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
|
|
5819
6391
|
} catch {
|
|
5820
6392
|
return null;
|
|
@@ -5837,12 +6409,12 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
|
|
|
5837
6409
|
}
|
|
5838
6410
|
async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
|
|
5839
6411
|
await mkdir5(cacheRoot, { recursive: true });
|
|
5840
|
-
const staging =
|
|
6412
|
+
const staging = join19(cacheRoot, ".extract");
|
|
5841
6413
|
await rm8(staging, { recursive: true, force: true });
|
|
5842
6414
|
await mkdir5(staging, { recursive: true });
|
|
5843
6415
|
await extract(packageSpec, staging);
|
|
5844
|
-
const packageDir = await pathExists3(
|
|
5845
|
-
if (!await pathExists3(
|
|
6416
|
+
const packageDir = await pathExists3(join19(staging, "harness", "manifests")) ? staging : join19(staging, "package");
|
|
6417
|
+
if (!await pathExists3(join19(packageDir, "harness", "manifests"))) {
|
|
5846
6418
|
throw new WorkflowDataResolutionError(
|
|
5847
6419
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
|
|
5848
6420
|
"WORKFLOW_DATA_INVALID",
|
|
@@ -5871,8 +6443,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
|
|
|
5871
6443
|
const packageName = workflowPackageName(family, options.env);
|
|
5872
6444
|
const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
|
|
5873
6445
|
const cacheKey = packageSpec.replace("/", "+");
|
|
5874
|
-
const cacheRoot =
|
|
5875
|
-
if (await pathExists3(
|
|
6446
|
+
const cacheRoot = join19(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
|
|
6447
|
+
if (await pathExists3(join19(cacheRoot, "harness", "manifests"))) {
|
|
5876
6448
|
if (version === "latest") {
|
|
5877
6449
|
const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
|
|
5878
6450
|
if (stale) {
|
|
@@ -5918,9 +6490,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
|
|
|
5918
6490
|
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
6491
|
}
|
|
5920
6492
|
async function readWorkflowFamilyManifest(resourcesRoot) {
|
|
5921
|
-
const manifestPath =
|
|
6493
|
+
const manifestPath = join19(resourcesRoot, "hunter-workflow-family.json");
|
|
5922
6494
|
if (!await pathExists3(manifestPath)) return {};
|
|
5923
|
-
return JSON.parse(await
|
|
6495
|
+
return JSON.parse(await readFile17(manifestPath, "utf8"));
|
|
5924
6496
|
}
|
|
5925
6497
|
|
|
5926
6498
|
// src/bin.ts
|
|
@@ -6011,7 +6583,15 @@ async function runCli(argv, overrides = {}) {
|
|
|
6011
6583
|
dependencies
|
|
6012
6584
|
);
|
|
6013
6585
|
});
|
|
6014
|
-
addCommonOptions(program.command("update")).description("\u5E94\u7528\u5DF2\u6279\u51C6\u7684\u670D\u52A1\u7AEF\u4EA7\u7269").
|
|
6586
|
+
addCommonOptions(program.command("update")).description("\u5E94\u7528\u5DF2\u6279\u51C6\u7684\u670D\u52A1\u7AEF\u4EA7\u7269").option(
|
|
6587
|
+
"--conflict-strategy <strategy>",
|
|
6588
|
+
"manual | keep-local | accept-remote\uFF08\u9ED8\u8BA4 manual\uFF1Brename \u51B2\u7A81\u59CB\u7EC8 manual\uFF09"
|
|
6589
|
+
).option(
|
|
6590
|
+
"--resolve <path=strategy>",
|
|
6591
|
+
"\u5355\u8DEF\u5F84\u8986\u76D6 keep-local|accept-remote\uFF08\u53EF\u91CD\u590D\uFF09",
|
|
6592
|
+
(value, previous) => [...previous, value],
|
|
6593
|
+
[]
|
|
6594
|
+
).action(async (options) => {
|
|
6015
6595
|
exitCode = await runUpdate(
|
|
6016
6596
|
{ ...program.opts(), ...options },
|
|
6017
6597
|
dependencies
|