hunter-harness 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +951 -401
- 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) {
|
|
@@ -1230,6 +1230,9 @@ var HunterHarnessApiClient = class {
|
|
|
1230
1230
|
body
|
|
1231
1231
|
});
|
|
1232
1232
|
}
|
|
1233
|
+
async getProject(projectId, requestId) {
|
|
1234
|
+
return this.request("GET", "/api/v1/projects/" + encodeURIComponent(projectId), { requestId });
|
|
1235
|
+
}
|
|
1233
1236
|
async createProposalSession(projectId, body, requestId, idempotencyKey) {
|
|
1234
1237
|
return this.request("POST", "/api/v1/projects/" + encodeURIComponent(projectId) + "/proposal-sessions", { requestId, idempotencyKey, body });
|
|
1235
1238
|
}
|
|
@@ -3454,8 +3457,8 @@ function resolvePushAuth(input) {
|
|
|
3454
3457
|
}
|
|
3455
3458
|
|
|
3456
3459
|
// ../core/dist/push/push.js
|
|
3457
|
-
import { lstat as
|
|
3458
|
-
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";
|
|
3459
3462
|
import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
|
|
3460
3463
|
|
|
3461
3464
|
// ../core/dist/state/baseline.js
|
|
@@ -3516,6 +3519,659 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
3516
3519
|
throw new Error("unable to acquire protocol lock");
|
|
3517
3520
|
}
|
|
3518
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
|
+
|
|
3519
4175
|
// ../core/dist/push/push.js
|
|
3520
4176
|
var PushWorkflowError = class extends Error {
|
|
3521
4177
|
exitCode;
|
|
@@ -3542,7 +4198,7 @@ var SHARED_MANAGED_FILES = [
|
|
|
3542
4198
|
];
|
|
3543
4199
|
async function exists3(path) {
|
|
3544
4200
|
try {
|
|
3545
|
-
await
|
|
4201
|
+
await lstat3(path);
|
|
3546
4202
|
return true;
|
|
3547
4203
|
} catch (error) {
|
|
3548
4204
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -3556,7 +4212,7 @@ async function walkFiles(root, current, output) {
|
|
|
3556
4212
|
return;
|
|
3557
4213
|
}
|
|
3558
4214
|
for (const item2 of await readdir4(current, { withFileTypes: true })) {
|
|
3559
|
-
const path =
|
|
4215
|
+
const path = join11(current, item2.name);
|
|
3560
4216
|
if (item2.isSymbolicLink()) {
|
|
3561
4217
|
throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
|
|
3562
4218
|
}
|
|
@@ -3578,7 +4234,7 @@ async function walkHarnessEntries(root, directory, output) {
|
|
|
3578
4234
|
return;
|
|
3579
4235
|
for (const item2 of await readdir4(directory, { withFileTypes: true })) {
|
|
3580
4236
|
if (item2.name.startsWith("harness-")) {
|
|
3581
|
-
const path =
|
|
4237
|
+
const path = join11(directory, item2.name);
|
|
3582
4238
|
if (item2.isDirectory()) {
|
|
3583
4239
|
await walkFiles(root, path, output);
|
|
3584
4240
|
} else if (item2.isFile()) {
|
|
@@ -3588,7 +4244,7 @@ async function walkHarnessEntries(root, directory, output) {
|
|
|
3588
4244
|
}
|
|
3589
4245
|
}
|
|
3590
4246
|
async function managedFiles(projectRoot, project) {
|
|
3591
|
-
const root =
|
|
4247
|
+
const root = resolve6(projectRoot);
|
|
3592
4248
|
const paths = [];
|
|
3593
4249
|
const adapters = getAdapters(enabledHarnessAgents(project));
|
|
3594
4250
|
const managedFiles2 = [
|
|
@@ -3597,25 +4253,25 @@ async function managedFiles(projectRoot, project) {
|
|
|
3597
4253
|
...adapters.some((adapter) => adapter.name === "codebuddy") ? ["CODEBUDDY.md"] : []
|
|
3598
4254
|
];
|
|
3599
4255
|
for (const path of managedFiles2) {
|
|
3600
|
-
if (await exists3(
|
|
4256
|
+
if (await exists3(join11(root, path))) {
|
|
3601
4257
|
paths.push(path);
|
|
3602
4258
|
}
|
|
3603
4259
|
}
|
|
3604
4260
|
for (const path of SHARED_MANAGED_ROOTS) {
|
|
3605
|
-
await walkFiles(root,
|
|
4261
|
+
await walkFiles(root, join11(root, path), paths);
|
|
3606
4262
|
}
|
|
3607
4263
|
for (const adapter of adapters) {
|
|
3608
4264
|
if (adapter.rulesRoot !== null) {
|
|
3609
|
-
await walkFiles(root,
|
|
4265
|
+
await walkFiles(root, join11(root, adapter.rulesRoot), paths);
|
|
3610
4266
|
}
|
|
3611
|
-
await walkHarnessEntries(root,
|
|
4267
|
+
await walkHarnessEntries(root, join11(root, adapter.skillsRoot), paths);
|
|
3612
4268
|
if (adapter.agentsRoot !== null) {
|
|
3613
|
-
await walkHarnessEntries(root,
|
|
4269
|
+
await walkHarnessEntries(root, join11(root, adapter.agentsRoot), paths);
|
|
3614
4270
|
}
|
|
3615
4271
|
}
|
|
3616
4272
|
const result = {};
|
|
3617
4273
|
for (const path of [...new Set(paths)].sort()) {
|
|
3618
|
-
result[path] = await
|
|
4274
|
+
result[path] = await readFile10(join11(root, path), "utf8");
|
|
3619
4275
|
}
|
|
3620
4276
|
return result;
|
|
3621
4277
|
}
|
|
@@ -3624,14 +4280,14 @@ function proposalBaseline(baseline) {
|
|
|
3624
4280
|
}
|
|
3625
4281
|
async function readProject(root) {
|
|
3626
4282
|
try {
|
|
3627
|
-
return projectConfigSchema.parse(parseYaml5(await
|
|
4283
|
+
return projectConfigSchema.parse(parseYaml5(await readFile10(join11(root, ".harness", "project.yaml"), "utf8")));
|
|
3628
4284
|
} catch {
|
|
3629
4285
|
throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
3630
4286
|
}
|
|
3631
4287
|
}
|
|
3632
4288
|
async function readOptionalJson(path) {
|
|
3633
4289
|
try {
|
|
3634
|
-
return JSON.parse(await
|
|
4290
|
+
return JSON.parse(await readFile10(path, "utf8"));
|
|
3635
4291
|
} catch (error) {
|
|
3636
4292
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
3637
4293
|
return null;
|
|
@@ -3643,7 +4299,7 @@ async function clientIdFor(root, explicit) {
|
|
|
3643
4299
|
if (explicit !== void 0) {
|
|
3644
4300
|
return explicit;
|
|
3645
4301
|
}
|
|
3646
|
-
const path =
|
|
4302
|
+
const path = join11(root, ".harness", "state", "local", "client.json");
|
|
3647
4303
|
const existing = await readOptionalJson(path);
|
|
3648
4304
|
if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
|
|
3649
4305
|
return existing.client_id;
|
|
@@ -3732,6 +4388,35 @@ function assertPreviewAllowed(preview, skip) {
|
|
|
3732
4388
|
}
|
|
3733
4389
|
}
|
|
3734
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";
|
|
4391
|
+
var STALE_BASELINE_MESSAGE = "\u670D\u52A1\u7AEF artifact \u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5148\u6267\u884C npx hunter-harness update\uFF08\u51B2\u7A81\u53EF\u7528 update --resolve <path>=keep-local|accept-remote\uFF09\u518D\u63A8";
|
|
4392
|
+
function staleBaselineError(code, conflicts) {
|
|
4393
|
+
const conflictHint = conflicts !== void 0 && conflicts.length > 0 ? " \u51B2\u7A81\u6587\u4EF6\uFF1A" + conflicts.map((item2) => item2.path).join(", ") : "";
|
|
4394
|
+
return new PushWorkflowError(STALE_BASELINE_MESSAGE + conflictHint, 5, code);
|
|
4395
|
+
}
|
|
4396
|
+
async function syncToLatest(root, project, baseline, client) {
|
|
4397
|
+
const syncResult = await synchronizeArtifacts({
|
|
4398
|
+
projectRoot: root,
|
|
4399
|
+
project,
|
|
4400
|
+
client,
|
|
4401
|
+
requestId: uuidV7(),
|
|
4402
|
+
dryRun: false,
|
|
4403
|
+
conflictStrategy: "manual"
|
|
4404
|
+
}, baseline);
|
|
4405
|
+
if (syncResult.conflicts.length > 0) {
|
|
4406
|
+
throw staleBaselineError("PROJECT_VERSION_CONFLICT", syncResult.conflicts);
|
|
4407
|
+
}
|
|
4408
|
+
return await readBaseline(root);
|
|
4409
|
+
}
|
|
4410
|
+
async function autoRebaseIfServerAdvanced(root, project, baseline, client, remoteVersion) {
|
|
4411
|
+
if (remoteVersion === baseline.complete_project_version) {
|
|
4412
|
+
return baseline;
|
|
4413
|
+
}
|
|
4414
|
+
const updated = await syncToLatest(root, project, baseline, client);
|
|
4415
|
+
if (remoteVersion !== null && updated.complete_project_version !== remoteVersion) {
|
|
4416
|
+
throw staleBaselineError("PROJECT_VERSION_CONFLICT");
|
|
4417
|
+
}
|
|
4418
|
+
return updated;
|
|
4419
|
+
}
|
|
3735
4420
|
function makePreview(baseline, files, confirmedProjectLocal, ignorePaths, deletedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3736
4421
|
const filteredFiles = {};
|
|
3737
4422
|
for (const [path, content] of Object.entries(files)) {
|
|
@@ -3770,20 +4455,18 @@ async function bindProject(root, project, baseline, projectId) {
|
|
|
3770
4455
|
return { project: nextProject, baseline: nextBaseline };
|
|
3771
4456
|
}
|
|
3772
4457
|
async function pushProject(options) {
|
|
3773
|
-
const root =
|
|
4458
|
+
const root = resolve6(options.projectRoot);
|
|
3774
4459
|
let project = await readProject(root);
|
|
3775
4460
|
let baseline = await readBaseline(root);
|
|
3776
4461
|
const profile = parseHarnessProfile(project.project.profiles[0]);
|
|
3777
4462
|
const installedPaths = profile === null ? /* @__PURE__ */ new Set() : new Set(await Promise.all(enabledHarnessAgents(project).map((agent) => managedBundleTargets(options.resourcesRoot, profile, agent))).then((targets) => targets.flatMap((target) => [...target])));
|
|
3778
4463
|
let preview = makePreview(baseline, await managedFiles(root, project), options.confirmedProjectLocal ?? [], installedPaths);
|
|
3779
|
-
const initialSkip = await resolveSensitiveScanSkip(preview, options);
|
|
3780
|
-
if (initialSkip.cancelled === true) {
|
|
3781
|
-
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
3782
|
-
}
|
|
3783
|
-
let sensitiveScanSkip = initialSkip.skip;
|
|
3784
|
-
let sensitiveScanSkipReason = initialSkip.reason;
|
|
3785
|
-
assertPreviewAllowed(preview, sensitiveScanSkip);
|
|
3786
4464
|
if (options.dryRun) {
|
|
4465
|
+
const drySkip = await resolveSensitiveScanSkip(preview, options);
|
|
4466
|
+
if (drySkip.cancelled === true) {
|
|
4467
|
+
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
4468
|
+
}
|
|
4469
|
+
assertPreviewAllowed(preview, drySkip.skip);
|
|
3787
4470
|
return { preview, proposalId: null, projectId: project.project.project_id };
|
|
3788
4471
|
}
|
|
3789
4472
|
const localCredentials = await readLocalCredentials(root);
|
|
@@ -3817,10 +4500,28 @@ async function pushProject(options) {
|
|
|
3817
4500
|
if (parsedServerUrl.protocol !== "https:") {
|
|
3818
4501
|
throw new PushWorkflowError("server_url must use HTTPS", 3, "SERVER_URL_INVALID");
|
|
3819
4502
|
}
|
|
4503
|
+
const client = new HunterHarnessApiClient({
|
|
4504
|
+
serverUrl: parsedServerUrl.toString(),
|
|
4505
|
+
token,
|
|
4506
|
+
...options.fetch === void 0 ? {} : { fetch: options.fetch }
|
|
4507
|
+
});
|
|
4508
|
+
const boundAtStart = project.project.project_id;
|
|
4509
|
+
if (boundAtStart !== null) {
|
|
4510
|
+
const remote = await client.getProject(boundAtStart, uuidV7());
|
|
4511
|
+
baseline = await autoRebaseIfServerAdvanced(root, project, baseline, client, remote.latest_project_version);
|
|
4512
|
+
preview = makePreview(baseline, await managedFiles(root, project), options.confirmedProjectLocal ?? [], installedPaths);
|
|
4513
|
+
}
|
|
4514
|
+
const initialSkip = await resolveSensitiveScanSkip(preview, options);
|
|
4515
|
+
if (initialSkip.cancelled === true) {
|
|
4516
|
+
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
4517
|
+
}
|
|
4518
|
+
let sensitiveScanSkip = initialSkip.skip;
|
|
4519
|
+
let sensitiveScanSkipReason = initialSkip.reason;
|
|
4520
|
+
assertPreviewAllowed(preview, sensitiveScanSkip);
|
|
3820
4521
|
if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
|
|
3821
4522
|
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
3822
4523
|
}
|
|
3823
|
-
const workflowPath =
|
|
4524
|
+
const workflowPath = join11(root, ".harness", "state", "local", "push-workflow.json");
|
|
3824
4525
|
const priorWorkflow = await readOptionalJson(workflowPath);
|
|
3825
4526
|
const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
|
|
3826
4527
|
const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
|
|
@@ -3831,11 +4532,6 @@ async function pushProject(options) {
|
|
|
3831
4532
|
await atomicWriteJson(workflowPath, workflow);
|
|
3832
4533
|
preview = makePreview(baseline, await managedFiles(root, project), options.confirmedProjectLocal ?? [], installedPaths, workflow.created_at);
|
|
3833
4534
|
const requestId = workflow.request_id;
|
|
3834
|
-
const client = new HunterHarnessApiClient({
|
|
3835
|
-
serverUrl: parsedServerUrl.toString(),
|
|
3836
|
-
token,
|
|
3837
|
-
...options.fetch === void 0 ? {} : { fetch: options.fetch }
|
|
3838
|
-
});
|
|
3839
4535
|
if (project.project.project_id === null) {
|
|
3840
4536
|
const resolved = await client.resolveProject({
|
|
3841
4537
|
schema_version: 1,
|
|
@@ -3916,7 +4612,9 @@ async function pushProject(options) {
|
|
|
3916
4612
|
});
|
|
3917
4613
|
}
|
|
3918
4614
|
}
|
|
3919
|
-
|
|
4615
|
+
let finalizeRetried = false;
|
|
4616
|
+
let finalized;
|
|
4617
|
+
const finalizeOnce = async () => client.finalizeProposal(session.session_id, {
|
|
3920
4618
|
schema_version: 1,
|
|
3921
4619
|
manifest_sha256: proposalManifestHash,
|
|
3922
4620
|
base_artifact_id: baseline.latest_artifact_id ?? null,
|
|
@@ -3925,7 +4623,60 @@ async function pushProject(options) {
|
|
|
3925
4623
|
...sensitiveScanSkipReason === void 0 ? {} : { sensitive_scan_skip_reason: sensitiveScanSkipReason }
|
|
3926
4624
|
} : {}
|
|
3927
4625
|
}, requestId, workflow.finalize_idempotency_key);
|
|
3928
|
-
|
|
4626
|
+
try {
|
|
4627
|
+
finalized = await finalizeOnce();
|
|
4628
|
+
} catch (error) {
|
|
4629
|
+
if (error instanceof ApiError && (error.code === "STALE_PUSH" || error.code === "PROJECT_VERSION_CONFLICT") && !finalizeRetried) {
|
|
4630
|
+
finalizeRetried = true;
|
|
4631
|
+
baseline = await syncToLatest(root, project, baseline, client);
|
|
4632
|
+
workflow = resetSession(workflow, proposalManifestHash);
|
|
4633
|
+
await atomicWriteJson(workflowPath, workflow);
|
|
4634
|
+
session = await client.createProposalSession(projectId, {
|
|
4635
|
+
schema_version: 1,
|
|
4636
|
+
request_id: requestId,
|
|
4637
|
+
client_id: clientId,
|
|
4638
|
+
base_project_version: baseline.complete_project_version,
|
|
4639
|
+
base_manifest_hash: sha256Bytes(canonicalJson(baseline)),
|
|
4640
|
+
proposal_manifest: { files: preview.operations },
|
|
4641
|
+
artifact_manifest: { schema_version: 1, files: preview.operations }
|
|
4642
|
+
}, requestId, workflow.session_idempotency_key);
|
|
4643
|
+
workflow.session_id = session.session_id;
|
|
4644
|
+
workflow.session_expires_at = session.expires_at;
|
|
4645
|
+
workflow.max_chunk_bytes = session.max_chunk_bytes;
|
|
4646
|
+
await atomicWriteJson(workflowPath, workflow);
|
|
4647
|
+
try {
|
|
4648
|
+
finalized = await finalizeOnce();
|
|
4649
|
+
} catch (retryError) {
|
|
4650
|
+
if (retryError instanceof ApiError && (retryError.code === "STALE_PUSH" || retryError.code === "PROJECT_VERSION_CONFLICT")) {
|
|
4651
|
+
throw staleBaselineError(retryError.code);
|
|
4652
|
+
}
|
|
4653
|
+
throw retryError;
|
|
4654
|
+
}
|
|
4655
|
+
} else if (error instanceof ApiError && (error.code === "STALE_PUSH" || error.code === "PROJECT_VERSION_CONFLICT")) {
|
|
4656
|
+
throw staleBaselineError(error.code);
|
|
4657
|
+
} else {
|
|
4658
|
+
throw error;
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
let pushWarning;
|
|
4662
|
+
if (finalized.artifact_id !== null) {
|
|
4663
|
+
try {
|
|
4664
|
+
const publishedManifest = artifactManifestSchema.parse(await client.getArtifactManifest(finalized.artifact_id, requestId));
|
|
4665
|
+
const advanced = await advanceBaselineFromArtifact({
|
|
4666
|
+
projectRoot: root,
|
|
4667
|
+
manifest: publishedManifest,
|
|
4668
|
+
requestId
|
|
4669
|
+
}, baseline);
|
|
4670
|
+
if (advanced.localChanged) {
|
|
4671
|
+
pushWarning = "LOCAL_CHANGED_DURING_PUSH";
|
|
4672
|
+
} else {
|
|
4673
|
+
baseline = advanced.baseline;
|
|
4674
|
+
}
|
|
4675
|
+
} catch {
|
|
4676
|
+
pushWarning = "BASELINE_ADVANCE_DEFERRED";
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
await atomicWriteJson(join11(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
|
|
3929
4680
|
schema_version: 1,
|
|
3930
4681
|
request_id: requestId,
|
|
3931
4682
|
project_id: projectId,
|
|
@@ -3933,17 +4684,24 @@ async function pushProject(options) {
|
|
|
3933
4684
|
status: finalized.status,
|
|
3934
4685
|
artifact_id: finalized.artifact_id,
|
|
3935
4686
|
operation_count: preview.operations.length,
|
|
4687
|
+
warning: pushWarning ?? null,
|
|
3936
4688
|
recorded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3937
4689
|
});
|
|
3938
|
-
await
|
|
3939
|
-
return {
|
|
4690
|
+
await rm5(workflowPath, { force: true });
|
|
4691
|
+
return {
|
|
4692
|
+
preview,
|
|
4693
|
+
proposalId: finalized.proposal_id,
|
|
4694
|
+
projectId,
|
|
4695
|
+
artifactId: finalized.artifact_id,
|
|
4696
|
+
...pushWarning === void 0 ? {} : { warning: pushWarning }
|
|
4697
|
+
};
|
|
3940
4698
|
} catch (error) {
|
|
3941
4699
|
if (error instanceof PushWorkflowError) {
|
|
3942
4700
|
throw error;
|
|
3943
4701
|
}
|
|
3944
4702
|
if (error instanceof ApiError) {
|
|
3945
|
-
if (error.code === "STALE_PUSH") {
|
|
3946
|
-
throw
|
|
4703
|
+
if (error.code === "STALE_PUSH" || error.code === "PROJECT_VERSION_CONFLICT") {
|
|
4704
|
+
throw staleBaselineError(error.code);
|
|
3947
4705
|
}
|
|
3948
4706
|
if (error.code === "SENSITIVE_CONTENT_BLOCKED") {
|
|
3949
4707
|
const details = error.details;
|
|
@@ -3963,8 +4721,8 @@ async function pushProject(options) {
|
|
|
3963
4721
|
}
|
|
3964
4722
|
|
|
3965
4723
|
// ../core/dist/state/cleanup.js
|
|
3966
|
-
import { readFile as
|
|
3967
|
-
import { join as
|
|
4724
|
+
import { readFile as readFile11, readdir as readdir5, rm as rm6 } from "node:fs/promises";
|
|
4725
|
+
import { join as join12 } from "node:path";
|
|
3968
4726
|
function isSafeEntryName(name) {
|
|
3969
4727
|
return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
|
|
3970
4728
|
}
|
|
@@ -3988,7 +4746,7 @@ async function cleanupProject(options) {
|
|
|
3988
4746
|
continue;
|
|
3989
4747
|
let journal;
|
|
3990
4748
|
try {
|
|
3991
|
-
journal = JSON.parse(await
|
|
4749
|
+
journal = JSON.parse(await readFile11(join12(layout.transactions, name, "journal.json"), "utf8"));
|
|
3992
4750
|
} catch {
|
|
3993
4751
|
continue;
|
|
3994
4752
|
}
|
|
@@ -4006,7 +4764,7 @@ async function cleanupProject(options) {
|
|
|
4006
4764
|
continue;
|
|
4007
4765
|
pruned.push(entry.id);
|
|
4008
4766
|
if (!options.dryRun) {
|
|
4009
|
-
await
|
|
4767
|
+
await rm6(join12(layout.transactions, entry.id), { recursive: true, force: true });
|
|
4010
4768
|
}
|
|
4011
4769
|
}
|
|
4012
4770
|
}
|
|
@@ -4015,7 +4773,7 @@ async function cleanupProject(options) {
|
|
|
4015
4773
|
continue;
|
|
4016
4774
|
removedCache.push(name);
|
|
4017
4775
|
if (!options.dryRun) {
|
|
4018
|
-
await
|
|
4776
|
+
await rm6(join12(layout.serverArtifacts, name), { recursive: true, force: true });
|
|
4019
4777
|
}
|
|
4020
4778
|
}
|
|
4021
4779
|
return {
|
|
@@ -4069,10 +4827,10 @@ var AGENT_DESCRIPTORS = {
|
|
|
4069
4827
|
var INSTALLABLE_AGENTS = Object.keys(AGENT_DESCRIPTORS).filter((agent) => AGENT_DESCRIPTORS[agent]?.installable === true);
|
|
4070
4828
|
|
|
4071
4829
|
// ../core/dist/transaction/recovery.js
|
|
4072
|
-
import { readFile as
|
|
4073
|
-
import { join as
|
|
4830
|
+
import { readFile as readFile12, readdir as readdir6, rm as rm7, stat as stat3 } from "node:fs/promises";
|
|
4831
|
+
import { join as join13 } from "node:path";
|
|
4074
4832
|
async function recoverTransaction(projectRoot, transactionId) {
|
|
4075
|
-
const journal = JSON.parse(await
|
|
4833
|
+
const journal = JSON.parse(await readFile12(join13(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
|
|
4076
4834
|
if (journal.state === "committed") {
|
|
4077
4835
|
return { transactionId, status: "committed" };
|
|
4078
4836
|
}
|
|
@@ -4099,7 +4857,7 @@ async function listTransactions(projectRoot) {
|
|
|
4099
4857
|
const transactions = [];
|
|
4100
4858
|
for (const transactionId of names) {
|
|
4101
4859
|
try {
|
|
4102
|
-
const journal = JSON.parse(await
|
|
4860
|
+
const journal = JSON.parse(await readFile12(join13(root, transactionId, "journal.json"), "utf8"));
|
|
4103
4861
|
transactions.push({
|
|
4104
4862
|
transactionId,
|
|
4105
4863
|
kind: journal.kind,
|
|
@@ -4114,7 +4872,7 @@ async function listTransactions(projectRoot) {
|
|
|
4114
4872
|
async function pendingTransactions(projectRoot) {
|
|
4115
4873
|
return (await listTransactions(projectRoot)).filter((item2) => RECOVERY_STATES.has(item2.state));
|
|
4116
4874
|
}
|
|
4117
|
-
async function
|
|
4875
|
+
async function pathExists2(path) {
|
|
4118
4876
|
try {
|
|
4119
4877
|
await stat3(path);
|
|
4120
4878
|
return true;
|
|
@@ -4130,12 +4888,12 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
4130
4888
|
if (latest === void 0) {
|
|
4131
4889
|
throw new Error("no committed update transaction is available for rollback");
|
|
4132
4890
|
}
|
|
4133
|
-
const transactionRoot =
|
|
4134
|
-
const journal = JSON.parse(await
|
|
4135
|
-
const after = JSON.parse(await
|
|
4891
|
+
const transactionRoot = join13(stateLayout(projectRoot).transactions, latest.transactionId);
|
|
4892
|
+
const journal = JSON.parse(await readFile12(join13(transactionRoot, "journal.json"), "utf8"));
|
|
4893
|
+
const after = JSON.parse(await readFile12(join13(transactionRoot, "after", "manifest.json"), "utf8"));
|
|
4136
4894
|
for (const entry of after) {
|
|
4137
|
-
const target =
|
|
4138
|
-
const exists6 = await
|
|
4895
|
+
const target = join13(projectRoot, entry.path);
|
|
4896
|
+
const exists6 = await pathExists2(target);
|
|
4139
4897
|
if (exists6 !== entry.exists || exists6 && await sha256File(target) !== entry.hash) {
|
|
4140
4898
|
throw new Error("cannot rollback dirty path: " + entry.path);
|
|
4141
4899
|
}
|
|
@@ -4147,10 +4905,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
4147
4905
|
continue;
|
|
4148
4906
|
}
|
|
4149
4907
|
seen.add(snapshot.path);
|
|
4150
|
-
const target =
|
|
4151
|
-
const exists6 = await
|
|
4908
|
+
const target = join13(projectRoot, snapshot.path);
|
|
4909
|
+
const exists6 = await pathExists2(target);
|
|
4152
4910
|
if (snapshot.existed && snapshot.snapshot_name !== null) {
|
|
4153
|
-
const content = await
|
|
4911
|
+
const content = await readFile12(join13(transactionRoot, "before", snapshot.snapshot_name));
|
|
4154
4912
|
operations.push({
|
|
4155
4913
|
operation: exists6 ? "modify" : "add",
|
|
4156
4914
|
path: snapshot.path,
|
|
@@ -4179,7 +4937,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
4179
4937
|
if (!removable) {
|
|
4180
4938
|
continue;
|
|
4181
4939
|
}
|
|
4182
|
-
await
|
|
4940
|
+
await rm7(join13(stateLayout(projectRoot).transactions, item2.transactionId), {
|
|
4183
4941
|
recursive: true,
|
|
4184
4942
|
force: true
|
|
4185
4943
|
});
|
|
@@ -4188,31 +4946,9 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
4188
4946
|
return removed;
|
|
4189
4947
|
}
|
|
4190
4948
|
|
|
4191
|
-
// ../core/dist/update/conflicts.js
|
|
4192
|
-
function operationTargetPath(operation) {
|
|
4193
|
-
return operation.operation === "rename" ? operation.to_path : operation.path;
|
|
4194
|
-
}
|
|
4195
|
-
function operationSourcePath(operation) {
|
|
4196
|
-
return operation.operation === "rename" ? operation.from_path : operation.path;
|
|
4197
|
-
}
|
|
4198
|
-
function operationAlreadyApplied(operation, baseline, projectVersion) {
|
|
4199
|
-
const target = baseline.files[operationTargetPath(operation)];
|
|
4200
|
-
if (operation.operation === "delete") {
|
|
4201
|
-
return target?.deleted === true && target.last_applied_version === projectVersion;
|
|
4202
|
-
}
|
|
4203
|
-
return target?.deleted === false && target?.last_applied_version === projectVersion && target.baseline_hash === operation.content_sha256;
|
|
4204
|
-
}
|
|
4205
|
-
function managedBlockDirty(currentContent, managedBlockHash) {
|
|
4206
|
-
if (managedBlockHash === void 0) {
|
|
4207
|
-
return true;
|
|
4208
|
-
}
|
|
4209
|
-
const block = extractManagedBlock(currentContent);
|
|
4210
|
-
return block === null || sha256Bytes(block) !== managedBlockHash;
|
|
4211
|
-
}
|
|
4212
|
-
|
|
4213
4949
|
// ../core/dist/update/update.js
|
|
4214
|
-
import {
|
|
4215
|
-
import { join as
|
|
4950
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
4951
|
+
import { join as join14, resolve as resolve7 } from "node:path";
|
|
4216
4952
|
import { parse as parseYaml8 } from "yaml";
|
|
4217
4953
|
var UpdateWorkflowError = class extends Error {
|
|
4218
4954
|
exitCode;
|
|
@@ -4224,107 +4960,31 @@ var UpdateWorkflowError = class extends Error {
|
|
|
4224
4960
|
this.code = code;
|
|
4225
4961
|
}
|
|
4226
4962
|
};
|
|
4227
|
-
async function pathExists2(path) {
|
|
4228
|
-
try {
|
|
4229
|
-
await lstat3(path);
|
|
4230
|
-
return true;
|
|
4231
|
-
} catch (error) {
|
|
4232
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
4233
|
-
return false;
|
|
4234
|
-
}
|
|
4235
|
-
throw error;
|
|
4236
|
-
}
|
|
4237
|
-
}
|
|
4238
|
-
async function optionalContent(path) {
|
|
4239
|
-
return await pathExists2(path) ? readFile12(path, "utf8") : null;
|
|
4240
|
-
}
|
|
4241
|
-
function manifestPayloadHash(manifest) {
|
|
4242
|
-
const payload = { ...manifest };
|
|
4243
|
-
delete payload.manifest_sha256;
|
|
4244
|
-
return sha256Bytes(canonicalJson(payload));
|
|
4245
|
-
}
|
|
4246
|
-
function expectedBase(operation) {
|
|
4247
|
-
return operation.operation === "add" ? null : operation.base_content_sha256;
|
|
4248
|
-
}
|
|
4249
|
-
function contentHash(operation) {
|
|
4250
|
-
return operation.operation === "delete" ? null : operation.content_sha256;
|
|
4251
|
-
}
|
|
4252
|
-
async function loadBlob(root, client, artifactId, operation, requestId, dryRun) {
|
|
4253
|
-
if (operation.operation === "delete") {
|
|
4254
|
-
return null;
|
|
4255
|
-
}
|
|
4256
|
-
const hash = operation.content_sha256;
|
|
4257
|
-
const cacheRoot = join13(root, ".harness", "cache", "server-artifacts", artifactId);
|
|
4258
|
-
const cachePath = join13(cacheRoot, "blobs", hash.replace(":", "_"));
|
|
4259
|
-
if (await pathExists2(cachePath) && await sha256File(cachePath) === hash) {
|
|
4260
|
-
return readFile12(cachePath, "utf8");
|
|
4261
|
-
}
|
|
4262
|
-
const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
|
|
4263
|
-
if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
|
|
4264
|
-
await rm7(cachePath, { force: true });
|
|
4265
|
-
throw new UpdateWorkflowError("artifact blob size or hash mismatch", 4, "ARTIFACT_HASH_MISMATCH");
|
|
4266
|
-
}
|
|
4267
|
-
if (!dryRun) {
|
|
4268
|
-
await atomicWriteFile(cachePath, bytes);
|
|
4269
|
-
}
|
|
4270
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
4271
|
-
}
|
|
4272
|
-
function nextBaselineEntry(operation, finalContent, projectVersion) {
|
|
4273
|
-
const block = finalContent === null ? null : extractManagedBlock(finalContent);
|
|
4274
|
-
return {
|
|
4275
|
-
baseline_hash: contentHash(operation),
|
|
4276
|
-
local_hash_at_apply: finalContent === null ? null : sha256Bytes(finalContent),
|
|
4277
|
-
file_kind: operation.file_kind,
|
|
4278
|
-
last_applied_version: projectVersion,
|
|
4279
|
-
deleted: operation.operation === "delete",
|
|
4280
|
-
...block === null ? {} : { managed_block_hash: sha256Bytes(block) }
|
|
4281
|
-
};
|
|
4282
|
-
}
|
|
4283
|
-
function transactionOperation(item2) {
|
|
4284
|
-
if (item2.equivalent) {
|
|
4285
|
-
return null;
|
|
4286
|
-
}
|
|
4287
|
-
const operation = item2.operation;
|
|
4288
|
-
const target = operationTargetPath(operation);
|
|
4289
|
-
if (operation.operation === "delete") {
|
|
4290
|
-
return item2.finalContent === null ? { operation: "delete", path: target } : { operation: "modify", path: target, content: item2.finalContent };
|
|
4291
|
-
}
|
|
4292
|
-
if (operation.operation === "rename") {
|
|
4293
|
-
return {
|
|
4294
|
-
operation: "rename",
|
|
4295
|
-
from_path: operation.from_path,
|
|
4296
|
-
to_path: operation.to_path,
|
|
4297
|
-
content: item2.finalContent ?? item2.content ?? ""
|
|
4298
|
-
};
|
|
4299
|
-
}
|
|
4300
|
-
return {
|
|
4301
|
-
operation: "modify",
|
|
4302
|
-
path: target,
|
|
4303
|
-
content: item2.finalContent ?? item2.content ?? ""
|
|
4304
|
-
};
|
|
4305
|
-
}
|
|
4306
4963
|
async function updateProject(options) {
|
|
4307
|
-
const root =
|
|
4964
|
+
const root = resolve7(options.projectRoot);
|
|
4308
4965
|
let project;
|
|
4309
4966
|
try {
|
|
4310
|
-
project = projectConfigSchema.parse(parseYaml8(await
|
|
4967
|
+
project = projectConfigSchema.parse(parseYaml8(await readFile13(join14(root, ".harness", "project.yaml"), "utf8")));
|
|
4311
4968
|
} catch {
|
|
4312
4969
|
throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
4313
4970
|
}
|
|
4314
4971
|
if (project.project.project_id === null) {
|
|
4315
4972
|
throw new UpdateWorkflowError("project is not bound to a server", 3, "PROJECT_NOT_BOUND");
|
|
4316
4973
|
}
|
|
4317
|
-
const serverUrl = options.serverUrl ?? project.server.url;
|
|
4318
4974
|
const tokenEnv = options.tokenEnv ?? project.server.token_env;
|
|
4319
|
-
if (serverUrl === null || serverUrl === void 0) {
|
|
4320
|
-
throw new UpdateWorkflowError("server_url is required", 3, "SERVER_URL_REQUIRED");
|
|
4321
|
-
}
|
|
4322
4975
|
if (!/^[A-Z_][A-Z0-9_]*$/.test(tokenEnv)) {
|
|
4323
4976
|
throw new UpdateWorkflowError("token_env is invalid", 3, "TOKEN_ENV_INVALID");
|
|
4324
4977
|
}
|
|
4325
|
-
const
|
|
4326
|
-
|
|
4327
|
-
|
|
4978
|
+
const local = await readLocalCredentials(root);
|
|
4979
|
+
const serverUrl = options.serverUrl ?? local?.server_url ?? project.server.url;
|
|
4980
|
+
if (serverUrl === null || serverUrl === void 0) {
|
|
4981
|
+
throw new UpdateWorkflowError("server_url is required", 3, "SERVER_URL_REQUIRED");
|
|
4982
|
+
}
|
|
4983
|
+
const envToken = options.env[tokenEnv]?.trim();
|
|
4984
|
+
const localToken = local?.token?.trim();
|
|
4985
|
+
const token = envToken !== void 0 && envToken !== "" ? envToken : localToken !== void 0 && localToken !== "" ? localToken : void 0;
|
|
4986
|
+
if (token === void 0) {
|
|
4987
|
+
throw new UpdateWorkflowError(`API token \u672A\u914D\u7F6E\uFF1A\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${tokenEnv}\uFF0C\u6216\u5728 .harness/credentials.local.yaml \u914D\u7F6E token\uFF08\u53EF\u901A\u8FC7 npx hunter-harness push \u5F15\u5BFC\u5199\u5165\uFF09`, 8, "TOKEN_INVALID");
|
|
4328
4988
|
}
|
|
4329
4989
|
const requestId = uuidV7();
|
|
4330
4990
|
const baseline = await readBaseline(root);
|
|
@@ -4343,183 +5003,18 @@ async function updateProject(options) {
|
|
|
4343
5003
|
...options.fetch === void 0 ? {} : { fetch: options.fetch }
|
|
4344
5004
|
});
|
|
4345
5005
|
try {
|
|
4346
|
-
const
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
operations: [],
|
|
4359
|
-
applied: [],
|
|
4360
|
-
skipped: [],
|
|
4361
|
-
transactionId: null,
|
|
4362
|
-
dryRun: options.dryRun
|
|
4363
|
-
};
|
|
4364
|
-
}
|
|
4365
|
-
const manifest = artifactManifestSchema.parse(await client.getArtifactManifest(discovery.artifact_id, requestId));
|
|
4366
|
-
if (manifest.artifact_id !== discovery.artifact_id || manifest.project_id !== project.project.project_id || manifest.project_version === null || manifestPayloadHash(manifest) !== manifest.manifest_sha256) {
|
|
4367
|
-
throw new UpdateWorkflowError("artifact manifest integrity check failed", 4, "ARTIFACT_HASH_MISMATCH");
|
|
4368
|
-
}
|
|
4369
|
-
const blobs = /* @__PURE__ */ new Map();
|
|
4370
|
-
for (const operation of manifest.files) {
|
|
4371
|
-
const policy = classifyFile(operationTargetPath(operation));
|
|
4372
|
-
if (decideUpdate(policy, false).apply) {
|
|
4373
|
-
blobs.set(operation, await loadBlob(root, client, manifest.artifact_id, operation, requestId, options.dryRun));
|
|
4374
|
-
}
|
|
4375
|
-
}
|
|
4376
|
-
const prepared = [];
|
|
4377
|
-
const skipped = [];
|
|
4378
|
-
for (const operation of manifest.files) {
|
|
4379
|
-
if (operationAlreadyApplied(operation, baseline, manifest.project_version)) {
|
|
4380
|
-
continue;
|
|
4381
|
-
}
|
|
4382
|
-
const source = operationSourcePath(operation);
|
|
4383
|
-
const target = operationTargetPath(operation);
|
|
4384
|
-
const policy = classifyFile(target);
|
|
4385
|
-
const staticDecision = decideUpdate(policy, false);
|
|
4386
|
-
if (!staticDecision.apply) {
|
|
4387
|
-
skipped.push({ path: target, operation: operation.operation, reason: staticDecision.reason });
|
|
4388
|
-
continue;
|
|
4389
|
-
}
|
|
4390
|
-
const previous = baseline.files[source];
|
|
4391
|
-
if (expectedBase(operation) !== (previous?.baseline_hash ?? null)) {
|
|
4392
|
-
skipped.push({ path: target, operation: operation.operation, reason: "baseline-diverged" });
|
|
4393
|
-
continue;
|
|
4394
|
-
}
|
|
4395
|
-
const sourceContent = await optionalContent(join13(root, source));
|
|
4396
|
-
const targetContent = target === source ? sourceContent : await optionalContent(join13(root, target));
|
|
4397
|
-
const incoming = blobs.get(operation) ?? null;
|
|
4398
|
-
const incomingHash = contentHash(operation);
|
|
4399
|
-
let equivalent = incomingHash !== null && targetContent !== null && sha256Bytes(targetContent) === incomingHash;
|
|
4400
|
-
let dirty = false;
|
|
4401
|
-
if (operation.operation === "add") {
|
|
4402
|
-
if (targetContent !== null && !equivalent) {
|
|
4403
|
-
if (policy.update_policy === "managed-block-only" && incoming !== null) {
|
|
4404
|
-
const incomingBlock = extractManagedBlock(incoming) ?? incoming.trim();
|
|
4405
|
-
equivalent = extractManagedBlock(targetContent) === incomingBlock;
|
|
4406
|
-
dirty = !equivalent;
|
|
4407
|
-
} else {
|
|
4408
|
-
dirty = true;
|
|
4409
|
-
}
|
|
4410
|
-
}
|
|
4411
|
-
} else if (sourceContent === null) {
|
|
4412
|
-
dirty = operation.operation !== "delete";
|
|
4413
|
-
} else if (policy.update_policy === "managed-block-only") {
|
|
4414
|
-
dirty = previous?.managed_block_hash === void 0 ? (previous?.local_hash_at_apply ?? previous?.baseline_hash) !== sha256Bytes(sourceContent) : managedBlockDirty(sourceContent, previous.managed_block_hash);
|
|
4415
|
-
} else {
|
|
4416
|
-
dirty = (previous?.local_hash_at_apply ?? previous?.baseline_hash) !== sha256Bytes(sourceContent);
|
|
4417
|
-
}
|
|
4418
|
-
if (operation.operation === "rename" && targetContent !== null && !equivalent) {
|
|
4419
|
-
dirty = true;
|
|
4420
|
-
}
|
|
4421
|
-
if (dirty) {
|
|
4422
|
-
skipped.push({ path: target, operation: operation.operation, reason: "local-dirty" });
|
|
4423
|
-
continue;
|
|
4424
|
-
}
|
|
4425
|
-
let finalContent = incoming;
|
|
4426
|
-
if (policy.update_policy === "managed-block-only") {
|
|
4427
|
-
if (operation.operation === "delete") {
|
|
4428
|
-
finalContent = sourceContent === null ? null : removeManagedBlock(sourceContent);
|
|
4429
|
-
equivalent = finalContent === sourceContent;
|
|
4430
|
-
} else if (incoming !== null) {
|
|
4431
|
-
const incomingBlock = extractManagedBlock(incoming) ?? incoming.trim();
|
|
4432
|
-
const incomingId = extractSingleManagedBlockById(incoming)?.id;
|
|
4433
|
-
const blockId = operation.operation === "add" || operation.operation === "modify" ? operation.block_id ?? incomingId : void 0;
|
|
4434
|
-
finalContent = blockId !== void 0 ? upsertManagedBlockById(targetContent ?? "", blockId, incomingBlock) : upsertManagedBlock(targetContent ?? "", incomingBlock);
|
|
4435
|
-
equivalent = finalContent === targetContent;
|
|
4436
|
-
}
|
|
4437
|
-
} else if (operation.operation === "delete" && sourceContent === null) {
|
|
4438
|
-
equivalent = true;
|
|
4439
|
-
}
|
|
4440
|
-
prepared.push({ operation, content: incoming, finalContent, equivalent });
|
|
4441
|
-
}
|
|
4442
|
-
const applied = prepared.map((item2) => operationTargetPath(item2.operation));
|
|
4443
|
-
if (options.dryRun) {
|
|
4444
|
-
return {
|
|
4445
|
-
requestId,
|
|
4446
|
-
projectId: project.project.project_id,
|
|
4447
|
-
artifactId: manifest.artifact_id,
|
|
4448
|
-
observedProjectVersion: manifest.project_version,
|
|
4449
|
-
operations: manifest.files,
|
|
4450
|
-
applied,
|
|
4451
|
-
skipped,
|
|
4452
|
-
transactionId: null,
|
|
4453
|
-
dryRun: true
|
|
4454
|
-
};
|
|
4455
|
-
}
|
|
4456
|
-
await atomicWriteJson(join13(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
|
|
4457
|
-
const lock = await acquireProtocolLock(root, "update", { requestId });
|
|
4458
|
-
try {
|
|
4459
|
-
const nextBaseline = baselineManifestSchema.parse(structuredClone(baseline));
|
|
4460
|
-
for (const item2 of prepared) {
|
|
4461
|
-
const target = operationTargetPath(item2.operation);
|
|
4462
|
-
nextBaseline.files[target] = nextBaselineEntry(item2.operation, item2.finalContent, manifest.project_version);
|
|
4463
|
-
if (item2.operation.operation === "rename") {
|
|
4464
|
-
nextBaseline.files[item2.operation.from_path] = {
|
|
4465
|
-
baseline_hash: null,
|
|
4466
|
-
local_hash_at_apply: null,
|
|
4467
|
-
file_kind: item2.operation.file_kind,
|
|
4468
|
-
last_applied_version: manifest.project_version,
|
|
4469
|
-
deleted: true
|
|
4470
|
-
};
|
|
4471
|
-
}
|
|
4472
|
-
}
|
|
4473
|
-
if (skipped.length === 0) {
|
|
4474
|
-
nextBaseline.complete_project_version = manifest.project_version;
|
|
4475
|
-
nextBaseline.artifact_manifest_hash = manifest.manifest_sha256;
|
|
4476
|
-
nextBaseline.latest_artifact_id = manifest.artifact_id;
|
|
4477
|
-
}
|
|
4478
|
-
const transactionId = "tx_update_" + Date.now() + "_" + uuidV7();
|
|
4479
|
-
const reportPath = ".harness/reports/update-" + requestId + ".json";
|
|
4480
|
-
const report = {
|
|
4481
|
-
schema_version: 1,
|
|
4482
|
-
request_id: requestId,
|
|
4483
|
-
artifact_id: manifest.artifact_id,
|
|
4484
|
-
observed_project_version: manifest.project_version,
|
|
4485
|
-
status: skipped.length === 0 ? "applied" : "partial_due_to_dirty",
|
|
4486
|
-
applied,
|
|
4487
|
-
skipped,
|
|
4488
|
-
transaction_id: transactionId
|
|
4489
|
-
};
|
|
4490
|
-
const operations = prepared.map((item2) => transactionOperation(item2)).filter((item2) => item2 !== null);
|
|
4491
|
-
operations.push({
|
|
4492
|
-
operation: "modify",
|
|
4493
|
-
path: ".harness/state/baseline/manifest.json",
|
|
4494
|
-
content: JSON.stringify(nextBaseline, null, 2) + "\n"
|
|
4495
|
-
}, {
|
|
4496
|
-
operation: "add",
|
|
4497
|
-
path: reportPath,
|
|
4498
|
-
content: JSON.stringify(report, null, 2) + "\n"
|
|
4499
|
-
}, {
|
|
4500
|
-
operation: "modify",
|
|
4501
|
-
path: ".harness/state/local/last-update.json",
|
|
4502
|
-
content: JSON.stringify(report, null, 2) + "\n"
|
|
4503
|
-
});
|
|
4504
|
-
await runTransaction(root, operations, {
|
|
4505
|
-
id: transactionId,
|
|
4506
|
-
kind: "update",
|
|
4507
|
-
...options.transactionOptions ?? {}
|
|
4508
|
-
});
|
|
4509
|
-
return {
|
|
4510
|
-
requestId,
|
|
4511
|
-
projectId: project.project.project_id,
|
|
4512
|
-
artifactId: manifest.artifact_id,
|
|
4513
|
-
observedProjectVersion: manifest.project_version,
|
|
4514
|
-
operations: manifest.files,
|
|
4515
|
-
applied,
|
|
4516
|
-
skipped,
|
|
4517
|
-
transactionId,
|
|
4518
|
-
dryRun: false
|
|
4519
|
-
};
|
|
4520
|
-
} finally {
|
|
4521
|
-
await lock.release();
|
|
4522
|
-
}
|
|
5006
|
+
const result = await synchronizeArtifacts({
|
|
5007
|
+
projectRoot: root,
|
|
5008
|
+
project,
|
|
5009
|
+
client,
|
|
5010
|
+
requestId,
|
|
5011
|
+
dryRun: options.dryRun,
|
|
5012
|
+
...options.conflictStrategy === void 0 ? {} : { conflictStrategy: options.conflictStrategy },
|
|
5013
|
+
...options.resolveOverrides === void 0 ? {} : { resolveOverrides: options.resolveOverrides },
|
|
5014
|
+
...options.confirmConflictStrategy === void 0 ? {} : { confirmConflictStrategy: options.confirmConflictStrategy },
|
|
5015
|
+
...options.transactionOptions === void 0 ? {} : { transactionOptions: options.transactionOptions }
|
|
5016
|
+
}, baseline);
|
|
5017
|
+
return result;
|
|
4523
5018
|
} catch (error) {
|
|
4524
5019
|
if (error instanceof UpdateWorkflowError) {
|
|
4525
5020
|
throw error;
|
|
@@ -4527,16 +5022,25 @@ async function updateProject(options) {
|
|
|
4527
5022
|
if (error instanceof ApiError) {
|
|
4528
5023
|
throw new UpdateWorkflowError(error.message, error.status === 401 || error.status === 403 ? 8 : error.status === 409 ? 5 : 4, error.code);
|
|
4529
5024
|
}
|
|
5025
|
+
if (error instanceof Error && error.message === "ARTIFACT_HASH_MISMATCH") {
|
|
5026
|
+
throw new UpdateWorkflowError("artifact blob size or hash mismatch", 4, "ARTIFACT_HASH_MISMATCH");
|
|
5027
|
+
}
|
|
4530
5028
|
if (error instanceof Error && error.name === "ZodError") {
|
|
4531
5029
|
throw new UpdateWorkflowError("artifact schema validation failed", 7, "SCHEMA_VALIDATION_FAILED");
|
|
4532
5030
|
}
|
|
5031
|
+
if (error instanceof Error && error.message.startsWith("DUPLICATE_ARTIFACT_ID")) {
|
|
5032
|
+
throw new UpdateWorkflowError(error.message, 4, "DUPLICATE_ARTIFACT_ID");
|
|
5033
|
+
}
|
|
5034
|
+
if (error instanceof Error && error.message === "MAX_SYNC_ARTIFACT_ITERATIONS_EXCEEDED") {
|
|
5035
|
+
throw new UpdateWorkflowError(error.message, 4, "SYNC_ITERATION_LIMIT");
|
|
5036
|
+
}
|
|
4533
5037
|
throw new UpdateWorkflowError(error instanceof Error ? error.message : "update failed", 4, "NETWORK_OR_SERVER_ERROR");
|
|
4534
5038
|
}
|
|
4535
5039
|
}
|
|
4536
5040
|
|
|
4537
5041
|
// src/config/init-config.ts
|
|
4538
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4539
|
-
import { readFile as
|
|
5042
|
+
import { isAbsolute as isAbsolute2, join as join15 } from "node:path";
|
|
5043
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
4540
5044
|
var InitConfigurationError = class extends Error {
|
|
4541
5045
|
exitCode;
|
|
4542
5046
|
code;
|
|
@@ -4569,13 +5073,15 @@ function normalizeProfile(value) {
|
|
|
4569
5073
|
if (value === "2" || value === "java") return "java";
|
|
4570
5074
|
throw new InitConfigurationError("\u914D\u7F6E\u7C7B\u578B\u5FC5\u987B\u4E3A general \u6216 java");
|
|
4571
5075
|
}
|
|
5076
|
+
var ALL_AGENT_TOKENS = /* @__PURE__ */ new Set(["all", "5"]);
|
|
4572
5077
|
function parseAgentsInput(raw) {
|
|
4573
5078
|
const trimmed = raw.trim();
|
|
4574
5079
|
if (trimmed === "") return ["claude-code"];
|
|
4575
|
-
if (trimmed
|
|
5080
|
+
if (ALL_AGENT_TOKENS.has(trimmed)) return [...HARNESS_AGENT_ORDER];
|
|
4576
5081
|
const agents = [];
|
|
4577
5082
|
for (const token of trimmed.split(",")) {
|
|
4578
5083
|
const value = token.trim();
|
|
5084
|
+
if (ALL_AGENT_TOKENS.has(value)) return [...HARNESS_AGENT_ORDER];
|
|
4579
5085
|
const byIndex = AGENT_BY_INDEX[value];
|
|
4580
5086
|
if (byIndex !== void 0) {
|
|
4581
5087
|
agents.push(byIndex);
|
|
@@ -4617,9 +5123,9 @@ function hasOwn(record, key) {
|
|
|
4617
5123
|
async function resolveInitConfig(cwd, flags, prompts = {}, warnings = []) {
|
|
4618
5124
|
let fileConfig = {};
|
|
4619
5125
|
if (flags.config !== void 0) {
|
|
4620
|
-
const path = isAbsolute2(flags.config) ? flags.config :
|
|
5126
|
+
const path = isAbsolute2(flags.config) ? flags.config : join15(cwd, flags.config);
|
|
4621
5127
|
try {
|
|
4622
|
-
fileConfig = JSON.parse(await
|
|
5128
|
+
fileConfig = JSON.parse(await readFile14(path, "utf8"));
|
|
4623
5129
|
} catch (error) {
|
|
4624
5130
|
throw new InitConfigurationError(
|
|
4625
5131
|
"unable to read init config: " + (error instanceof Error ? error.message : String(error)),
|
|
@@ -4701,8 +5207,8 @@ function serializeCliResult(result) {
|
|
|
4701
5207
|
}
|
|
4702
5208
|
|
|
4703
5209
|
// src/config/codebuddy-setup.ts
|
|
4704
|
-
import { mkdir as mkdir4, readFile as
|
|
4705
|
-
import { basename as basename2, dirname as dirname4, extname, join as
|
|
5210
|
+
import { mkdir as mkdir4, readFile as readFile15, readdir as readdir7, stat as stat4, writeFile as writeFile5 } from "node:fs/promises";
|
|
5211
|
+
import { basename as basename2, dirname as dirname4, extname, join as join16 } from "node:path";
|
|
4706
5212
|
var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
|
|
4707
5213
|
"harness-general.md",
|
|
4708
5214
|
"harness-general.mdc",
|
|
@@ -4721,7 +5227,7 @@ async function exists4(path) {
|
|
|
4721
5227
|
}
|
|
4722
5228
|
async function readJsonObject(path) {
|
|
4723
5229
|
try {
|
|
4724
|
-
const parsed = JSON.parse(await
|
|
5230
|
+
const parsed = JSON.parse(await readFile15(path, "utf8"));
|
|
4725
5231
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4726
5232
|
} catch (error) {
|
|
4727
5233
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
|
|
@@ -4729,27 +5235,27 @@ async function readJsonObject(path) {
|
|
|
4729
5235
|
}
|
|
4730
5236
|
}
|
|
4731
5237
|
async function inspectCodeBuddySetup(projectRoot) {
|
|
4732
|
-
const rulesRoot =
|
|
5238
|
+
const rulesRoot = join16(projectRoot, ".claude", "rules");
|
|
4733
5239
|
let claudeRules = [];
|
|
4734
5240
|
try {
|
|
4735
5241
|
claudeRules = (await readdir7(rulesRoot, { withFileTypes: true })).filter((entry) => entry.isFile() && [".md", ".mdc"].includes(extname(entry.name).toLowerCase())).map((entry) => entry.name).filter((name) => !MANAGED_RULE_NAMES.has(name)).sort();
|
|
4736
5242
|
} catch (error) {
|
|
4737
5243
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
4738
5244
|
}
|
|
4739
|
-
const mcp = await readJsonObject(
|
|
5245
|
+
const mcp = await readJsonObject(join16(projectRoot, ".mcp.json"));
|
|
4740
5246
|
const servers = mcp?.mcpServers;
|
|
4741
5247
|
const configured = servers !== null && typeof servers === "object" && !Array.isArray(servers) && Object.prototype.hasOwnProperty.call(servers, "codegraph");
|
|
4742
5248
|
return {
|
|
4743
5249
|
claudeRules,
|
|
4744
|
-
hasCodeGraphIndex: await exists4(
|
|
5250
|
+
hasCodeGraphIndex: await exists4(join16(projectRoot, ".codegraph")),
|
|
4745
5251
|
codeGraphConfigured: configured
|
|
4746
5252
|
};
|
|
4747
5253
|
}
|
|
4748
5254
|
function destinationTargets(root, surface, name) {
|
|
4749
5255
|
const stem = basename2(name, extname(name));
|
|
4750
5256
|
const targets = [];
|
|
4751
|
-
if (surface !== "cli") targets.push(
|
|
4752
|
-
if (surface !== "ide") targets.push(
|
|
5257
|
+
if (surface !== "cli") targets.push(join16(root, ".codebuddy", ".rules", `${stem}.mdc`));
|
|
5258
|
+
if (surface !== "ide") targets.push(join16(root, ".codebuddy", "rules", `${stem}.md`));
|
|
4753
5259
|
return targets;
|
|
4754
5260
|
}
|
|
4755
5261
|
async function applyCodeBuddySetup(options) {
|
|
@@ -4763,8 +5269,8 @@ async function applyCodeBuddySetup(options) {
|
|
|
4763
5269
|
if (options.syncClaudeRules) {
|
|
4764
5270
|
const plan = await inspectCodeBuddySetup(options.projectRoot);
|
|
4765
5271
|
for (const name of plan.claudeRules) {
|
|
4766
|
-
const source =
|
|
4767
|
-
const content = await
|
|
5272
|
+
const source = join16(options.projectRoot, ".claude", "rules", name);
|
|
5273
|
+
const content = await readFile15(source, "utf8");
|
|
4768
5274
|
if (SENSITIVE_ASSIGNMENT.test(content) || PRIVATE_KEY_BLOCK.test(content)) {
|
|
4769
5275
|
result.skippedSensitive.push(name);
|
|
4770
5276
|
continue;
|
|
@@ -4781,7 +5287,7 @@ async function applyCodeBuddySetup(options) {
|
|
|
4781
5287
|
}
|
|
4782
5288
|
}
|
|
4783
5289
|
if (options.configureCodeGraph) {
|
|
4784
|
-
const path =
|
|
5290
|
+
const path = join16(options.projectRoot, ".mcp.json");
|
|
4785
5291
|
const current = await readJsonObject(path);
|
|
4786
5292
|
if (current === null) {
|
|
4787
5293
|
result.warnings.push(".mcp.json \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u5DF2\u4FDD\u7559\u539F\u6587\u4EF6\u5E76\u8DF3\u8FC7 CodeGraph MCP \u914D\u7F6E");
|
|
@@ -4805,13 +5311,13 @@ async function applyCodeBuddySetup(options) {
|
|
|
4805
5311
|
}
|
|
4806
5312
|
|
|
4807
5313
|
// src/commands/refresh.ts
|
|
4808
|
-
import { readFile as
|
|
4809
|
-
import { join as
|
|
5314
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
5315
|
+
import { join as join17 } from "node:path";
|
|
4810
5316
|
import { parse as parseYaml9 } from "yaml";
|
|
4811
5317
|
async function detectProject(root) {
|
|
4812
5318
|
let content;
|
|
4813
5319
|
try {
|
|
4814
|
-
content = await
|
|
5320
|
+
content = await readFile16(join17(root, ".harness", "project.yaml"), "utf8");
|
|
4815
5321
|
} catch (error) {
|
|
4816
5322
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
4817
5323
|
return { status: "absent" };
|
|
@@ -5018,6 +5524,14 @@ var AGENT_LABELS = {
|
|
|
5018
5524
|
cursor: "Cursor",
|
|
5019
5525
|
codebuddy: "CodeBuddy"
|
|
5020
5526
|
};
|
|
5527
|
+
function agentMenuLines(installedProfiles) {
|
|
5528
|
+
const lines = HARNESS_AGENT_ORDER.map((agent, index) => {
|
|
5529
|
+
const profile = installedProfiles?.[agent];
|
|
5530
|
+
const suffix = profile === void 0 ? "" : `\uFF08\u5DF2\u5B89\u88C5\uFF1A${profile}\uFF09`;
|
|
5531
|
+
return ` ${index + 1}. ${AGENT_LABELS[agent]}${suffix}`;
|
|
5532
|
+
}).join("\n");
|
|
5533
|
+
return lines + "\n 5. \u5168\u90E8";
|
|
5534
|
+
}
|
|
5021
5535
|
async function configureCodeBuddyExtras(agents, surface, options, dependencies) {
|
|
5022
5536
|
if (!agents.includes("codebuddy")) return;
|
|
5023
5537
|
const plan = await inspectCodeBuddySetup(dependencies.cwd);
|
|
@@ -5067,7 +5581,7 @@ async function runFirstInstall(options, dependencies) {
|
|
|
5067
5581
|
options,
|
|
5068
5582
|
options.nonInteractive === true ? {} : {
|
|
5069
5583
|
agents: () => dependencies.prompt(
|
|
5070
|
-
"\u8BF7\u9009\u62E9\u76EE\u6807 Agent\uFF08\u53EF\u591A\u9009\uFF0C\u4F7F\u7528\u9017\u53F7\u5206\u9694\uFF09\n
|
|
5584
|
+
"\u8BF7\u9009\u62E9\u76EE\u6807 Agent\uFF08\u53EF\u591A\u9009\uFF0C\u4F7F\u7528\u9017\u53F7\u5206\u9694\uFF09\n" + agentMenuLines() + "\n\u8BF7\u8F93\u5165\u7F16\u53F7 [1]: "
|
|
5071
5585
|
).then((answer) => answer.trim()),
|
|
5072
5586
|
profile: () => dependencies.prompt(
|
|
5073
5587
|
"\u8BF7\u9009\u62E9 Harness \u7C7B\u578B\uFF1A\n1. \u901A\u7528\uFF08\u9ED8\u8BA4\uFF09\n2. Java\n\u8BF7\u8F93\u5165 1 \u6216 2 [1]: "
|
|
@@ -5165,10 +5679,7 @@ async function runExistingProject(options, dependencies, currentProfile, current
|
|
|
5165
5679
|
`Hunter Harness \u5F53\u524D\u914D\u7F6E\uFF1A
|
|
5166
5680
|
${currentLines}
|
|
5167
5681
|
\u8BF7\u9009\u62E9\u672C\u6B21\u8981\u65B0\u589E\u6216\u5237\u65B0\u7684\u5DE5\u5177\uFF08\u53EF\u591A\u9009\uFF0C\u9017\u53F7\u5206\u9694\uFF1B\u672A\u9009\u62E9\u7684\u5DE5\u5177\u4FDD\u6301\u4E0D\u53D8\uFF09\uFF1A
|
|
5168
|
-
|
|
5169
|
-
2. Codex
|
|
5170
|
-
3. Cursor
|
|
5171
|
-
4. CodeBuddy
|
|
5682
|
+
` + agentMenuLines(installed.profiles) + `
|
|
5172
5683
|
\u8BF7\u8F93\u5165\u7F16\u53F7 [${defaultSelection}]\uFF0C\u6216\u8F93\u5165 0 \u53D6\u6D88\uFF1A`
|
|
5173
5684
|
);
|
|
5174
5685
|
if (answer.trim() === "0" || /^c/i.test(answer.trim())) return 2;
|
|
@@ -5420,7 +5931,7 @@ async function runPush(options, dependencies) {
|
|
|
5420
5931
|
warnings: result.preview.skipped,
|
|
5421
5932
|
errors: []
|
|
5422
5933
|
};
|
|
5423
|
-
dependencies.stdout(options.json === true ? serializeCliResult(output) : options.dryRun === true ? "Push preview contains " + items.length + " operations.\n" : "Pushed artifact " + result.artifactId + " (proposal " + result.proposalId + ").\n");
|
|
5934
|
+
dependencies.stdout(options.json === true ? serializeCliResult(output) : options.dryRun === true ? "Push preview contains " + items.length + " operations.\n" : "Pushed artifact " + ("artifactId" in result ? String(result.artifactId) : "unknown") + " (proposal " + result.proposalId + ").\n");
|
|
5424
5935
|
return 0;
|
|
5425
5936
|
} catch (error) {
|
|
5426
5937
|
if (attempt === 0 && options.nonInteractive !== true && options.dryRun !== true && error instanceof PushWorkflowError && (error.code === "SERVER_URL_REQUIRED" || error.code === "TOKEN_INVALID")) {
|
|
@@ -5480,19 +5991,46 @@ async function runPush(options, dependencies) {
|
|
|
5480
5991
|
}
|
|
5481
5992
|
|
|
5482
5993
|
// src/commands/update.ts
|
|
5994
|
+
function parseResolveOverrides(entries) {
|
|
5995
|
+
const map = /* @__PURE__ */ new Map();
|
|
5996
|
+
for (const entry of entries ?? []) {
|
|
5997
|
+
const eq = entry.indexOf("=");
|
|
5998
|
+
if (eq <= 0) {
|
|
5999
|
+
throw new UpdateWorkflowError(
|
|
6000
|
+
"invalid --resolve value; expected path=keep-local|accept-remote",
|
|
6001
|
+
3,
|
|
6002
|
+
"RESOLVE_OPTION_INVALID"
|
|
6003
|
+
);
|
|
6004
|
+
}
|
|
6005
|
+
const path = entry.slice(0, eq).trim();
|
|
6006
|
+
const strategy = entry.slice(eq + 1).trim();
|
|
6007
|
+
if (path.length === 0 || strategy !== "keep-local" && strategy !== "accept-remote") {
|
|
6008
|
+
throw new UpdateWorkflowError(
|
|
6009
|
+
"invalid --resolve value; expected path=keep-local|accept-remote",
|
|
6010
|
+
3,
|
|
6011
|
+
"RESOLVE_OPTION_INVALID"
|
|
6012
|
+
);
|
|
6013
|
+
}
|
|
6014
|
+
map.set(path, strategy);
|
|
6015
|
+
}
|
|
6016
|
+
return map;
|
|
6017
|
+
}
|
|
5483
6018
|
async function runUpdate(options, dependencies) {
|
|
5484
6019
|
const requestId = uuidV7();
|
|
5485
6020
|
if (options.nonInteractive === true && options.yes !== true && options.dryRun !== true) {
|
|
5486
6021
|
dependencies.stderr("\u975E\u4EA4\u4E92\u6A21\u5F0F\u66F4\u65B0\u9700\u8981 --yes\n");
|
|
5487
6022
|
return 2;
|
|
5488
6023
|
}
|
|
6024
|
+
const resolveOverrides = parseResolveOverrides(options.resolve);
|
|
5489
6025
|
const execute = async (dryRun) => updateProject({
|
|
5490
6026
|
projectRoot: dependencies.cwd,
|
|
5491
6027
|
...options.serverUrl === void 0 ? {} : { serverUrl: options.serverUrl },
|
|
5492
6028
|
...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
|
|
5493
6029
|
env: dependencies.env,
|
|
5494
6030
|
dryRun,
|
|
5495
|
-
fetch: dependencies.fetch
|
|
6031
|
+
fetch: dependencies.fetch,
|
|
6032
|
+
...options.conflictStrategy === void 0 ? {} : { conflictStrategy: options.conflictStrategy },
|
|
6033
|
+
...resolveOverrides.size === 0 ? {} : { resolveOverrides }
|
|
5496
6034
|
});
|
|
5497
6035
|
try {
|
|
5498
6036
|
let result;
|
|
@@ -5510,22 +6048,24 @@ async function runUpdate(options, dependencies) {
|
|
|
5510
6048
|
}
|
|
5511
6049
|
const applied = new Set(result.applied);
|
|
5512
6050
|
const skippedByPath = new Map(result.skipped.map((item2) => [item2.path, item2]));
|
|
6051
|
+
const acknowledgedByPath = new Map(result.acknowledged.map((item2) => [item2.path, item2]));
|
|
5513
6052
|
const items = result.operations.map((operation) => {
|
|
5514
6053
|
const path = operation.operation === "rename" ? operation.to_path : operation.path;
|
|
5515
6054
|
const skipped = skippedByPath.get(path);
|
|
6055
|
+
const acknowledged = acknowledgedByPath.get(path);
|
|
5516
6056
|
return {
|
|
5517
6057
|
path,
|
|
5518
6058
|
operation: operation.operation,
|
|
5519
6059
|
file_kind: operation.file_kind,
|
|
5520
6060
|
policy: "update",
|
|
5521
|
-
status: skipped !== void 0 ? "skipped" : applied.has(path) ? options.dryRun === true ? "planned" : "applied" : "already-applied",
|
|
5522
|
-
reason: skipped?.reason ?? null,
|
|
6061
|
+
status: skipped !== void 0 ? "skipped" : acknowledged !== void 0 ? "acknowledged" : applied.has(path) ? options.dryRun === true ? "planned" : "applied" : "already-applied",
|
|
6062
|
+
reason: skipped?.reason ?? acknowledged?.reason ?? null,
|
|
5523
6063
|
size_bytes: "size_bytes" in operation ? operation.size_bytes : 0
|
|
5524
6064
|
};
|
|
5525
6065
|
});
|
|
5526
|
-
const exitCode = result.
|
|
6066
|
+
const exitCode = result.conflicts.length > 0 ? 5 : 0;
|
|
5527
6067
|
const output = {
|
|
5528
|
-
schema_version:
|
|
6068
|
+
schema_version: 2,
|
|
5529
6069
|
command: "update",
|
|
5530
6070
|
request_id: requestId,
|
|
5531
6071
|
dry_run: options.dryRun === true,
|
|
@@ -5536,13 +6076,15 @@ async function runUpdate(options, dependencies) {
|
|
|
5536
6076
|
discovered: result.operations.length,
|
|
5537
6077
|
applied: options.dryRun === true ? 0 : result.applied.length,
|
|
5538
6078
|
planned: options.dryRun === true ? result.applied.length : 0,
|
|
6079
|
+
acknowledged: result.acknowledged.length,
|
|
6080
|
+
resolved: result.resolvedKeepLocal.length + result.resolvedAcceptRemote.length,
|
|
5539
6081
|
skipped: result.skipped.length
|
|
5540
6082
|
},
|
|
5541
6083
|
items,
|
|
5542
|
-
warnings: result.skipped,
|
|
6084
|
+
warnings: [...result.acknowledged, ...result.skipped],
|
|
5543
6085
|
errors: []
|
|
5544
6086
|
};
|
|
5545
|
-
dependencies.stdout(options.json === true ? serializeCliResult(output) : result.artifactId === null ? "\u6CA1\u6709\u53EF\u5E94\u7528\u7684\u5DF2\u6279\u51C6\u66F4\u65B0\u3002\n" : "\u66F4\u65B0\u5B8C\u6210\uFF1A\u5DF2\u5E94\u7528 " + result.applied.length + " \u4E2A\u6761\u76EE\uFF0C\
|
|
6087
|
+
dependencies.stdout(options.json === true ? serializeCliResult(output) : result.artifactId === null ? "\u6CA1\u6709\u53EF\u5E94\u7528\u7684\u5DF2\u6279\u51C6\u66F4\u65B0\u3002\n" : "\u66F4\u65B0\u5B8C\u6210\uFF1A\u5DF2\u5E94\u7528 " + result.applied.length + " \u4E2A\u6761\u76EE\uFF0C\u5DF2\u786E\u8BA4 " + result.acknowledged.length + " \u4E2A\uFF0C\u51B2\u7A81 " + result.conflicts.length + " \u4E2A\u3002\n");
|
|
5546
6088
|
return exitCode;
|
|
5547
6089
|
} catch (error) {
|
|
5548
6090
|
const exitCode = error instanceof UpdateWorkflowError ? error.exitCode : 1;
|
|
@@ -5572,7 +6114,7 @@ async function runUpdate(options, dependencies) {
|
|
|
5572
6114
|
|
|
5573
6115
|
// src/commands/recovery.ts
|
|
5574
6116
|
import { stat as stat5 } from "node:fs/promises";
|
|
5575
|
-
import { join as
|
|
6117
|
+
import { join as join18 } from "node:path";
|
|
5576
6118
|
async function exists5(path) {
|
|
5577
6119
|
try {
|
|
5578
6120
|
await stat5(path);
|
|
@@ -5632,7 +6174,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
5632
6174
|
return 5;
|
|
5633
6175
|
}
|
|
5634
6176
|
}
|
|
5635
|
-
const initialized = await exists5(
|
|
6177
|
+
const initialized = await exists5(join18(dependencies.cwd, ".harness", "project.yaml"));
|
|
5636
6178
|
if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
|
|
5637
6179
|
return null;
|
|
5638
6180
|
}
|
|
@@ -5675,8 +6217,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
5675
6217
|
}
|
|
5676
6218
|
|
|
5677
6219
|
// src/workflow-data/resolve.ts
|
|
5678
|
-
import { cp, mkdir as mkdir5, readdir as readdir8, readFile as
|
|
5679
|
-
import { dirname as dirname5, join as
|
|
6220
|
+
import { cp, mkdir as mkdir5, readdir as readdir8, readFile as readFile17, rm as rm8, stat as stat6 } from "node:fs/promises";
|
|
6221
|
+
import { dirname as dirname5, join as join19, relative as relative2 } from "node:path";
|
|
5680
6222
|
import { fileURLToPath } from "node:url";
|
|
5681
6223
|
var WorkflowDataResolutionError = class extends Error {
|
|
5682
6224
|
code;
|
|
@@ -5713,14 +6255,14 @@ async function listFilesRecursive(root, base = root) {
|
|
|
5713
6255
|
const entries = await readdir8(root, { withFileTypes: true });
|
|
5714
6256
|
const files = [];
|
|
5715
6257
|
for (const entry of entries) {
|
|
5716
|
-
const absolute =
|
|
6258
|
+
const absolute = join19(root, entry.name);
|
|
5717
6259
|
if (entry.isDirectory()) {
|
|
5718
6260
|
files.push(...await listFilesRecursive(absolute, base));
|
|
5719
6261
|
continue;
|
|
5720
6262
|
}
|
|
5721
6263
|
if (!entry.isFile()) continue;
|
|
5722
6264
|
const rel = relative2(base, absolute).replaceAll("\\", "/");
|
|
5723
|
-
files.push({ path: rel, content: await
|
|
6265
|
+
files.push({ path: rel, content: await readFile17(absolute, "utf8") });
|
|
5724
6266
|
}
|
|
5725
6267
|
return files;
|
|
5726
6268
|
}
|
|
@@ -5733,7 +6275,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
5733
6275
|
const manifest = await readWorkflowFamilyManifest(resourcesRoot);
|
|
5734
6276
|
const expected = manifest.content_sha256;
|
|
5735
6277
|
if (typeof expected !== "string" || expected.length === 0) return;
|
|
5736
|
-
const harnessRoot =
|
|
6278
|
+
const harnessRoot = join19(resourcesRoot, "harness");
|
|
5737
6279
|
if (!await pathExists3(harnessRoot)) {
|
|
5738
6280
|
throw new WorkflowDataResolutionError(
|
|
5739
6281
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
|
|
@@ -5755,37 +6297,37 @@ async function monorepoResourcesRoot() {
|
|
|
5755
6297
|
const here = dirname5(fileURLToPath(import.meta.url));
|
|
5756
6298
|
const candidates = [
|
|
5757
6299
|
// TypeScript source: packages/cli/src/workflow-data -> packages/workflow-data-harness.
|
|
5758
|
-
|
|
6300
|
+
join19(here, "../../../workflow-data-harness"),
|
|
5759
6301
|
// Bundled CLI: packages/cli/dist -> packages/workflow-data-harness.
|
|
5760
|
-
|
|
6302
|
+
join19(here, "../../workflow-data-harness"),
|
|
5761
6303
|
// Test/build layouts that preserve additional source directory levels.
|
|
5762
|
-
|
|
6304
|
+
join19(here, "../../../../packages/workflow-data-harness")
|
|
5763
6305
|
];
|
|
5764
6306
|
for (const candidate of candidates) {
|
|
5765
|
-
if (await pathExists3(
|
|
6307
|
+
if (await pathExists3(join19(candidate, "harness", "manifests"))) return candidate;
|
|
5766
6308
|
}
|
|
5767
6309
|
return null;
|
|
5768
6310
|
}
|
|
5769
6311
|
async function siblingWorkflowPackage(cwd) {
|
|
5770
6312
|
const here = dirname5(fileURLToPath(import.meta.url));
|
|
5771
6313
|
const candidates = [
|
|
5772
|
-
|
|
6314
|
+
join19(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
|
|
5773
6315
|
// Published layout: node_modules/hunter-harness/dist/bin.js alongside the
|
|
5774
6316
|
// scoped workflow package in node_modules/@hunter-harness/workflow-harness.
|
|
5775
|
-
|
|
6317
|
+
join19(here, "..", "..", "@hunter-harness", "workflow-harness"),
|
|
5776
6318
|
// Monorepo bundled layout: packages/cli/dist/bin.js.
|
|
5777
|
-
|
|
6319
|
+
join19(here, "..", "..", "workflow-data-harness")
|
|
5778
6320
|
];
|
|
5779
6321
|
for (const candidate of candidates) {
|
|
5780
|
-
if (await pathExists3(
|
|
6322
|
+
if (await pathExists3(join19(candidate, "harness", "manifests"))) return candidate;
|
|
5781
6323
|
}
|
|
5782
6324
|
return null;
|
|
5783
6325
|
}
|
|
5784
6326
|
async function readCachedNpmPackageVersion(cacheRoot) {
|
|
5785
|
-
const manifestPath =
|
|
6327
|
+
const manifestPath = join19(cacheRoot, "package.json");
|
|
5786
6328
|
if (!await pathExists3(manifestPath)) return null;
|
|
5787
6329
|
try {
|
|
5788
|
-
const pkg = JSON.parse(await
|
|
6330
|
+
const pkg = JSON.parse(await readFile17(manifestPath, "utf8"));
|
|
5789
6331
|
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
|
|
5790
6332
|
} catch {
|
|
5791
6333
|
return null;
|
|
@@ -5808,12 +6350,12 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
|
|
|
5808
6350
|
}
|
|
5809
6351
|
async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
|
|
5810
6352
|
await mkdir5(cacheRoot, { recursive: true });
|
|
5811
|
-
const staging =
|
|
6353
|
+
const staging = join19(cacheRoot, ".extract");
|
|
5812
6354
|
await rm8(staging, { recursive: true, force: true });
|
|
5813
6355
|
await mkdir5(staging, { recursive: true });
|
|
5814
6356
|
await extract(packageSpec, staging);
|
|
5815
|
-
const packageDir = await pathExists3(
|
|
5816
|
-
if (!await pathExists3(
|
|
6357
|
+
const packageDir = await pathExists3(join19(staging, "harness", "manifests")) ? staging : join19(staging, "package");
|
|
6358
|
+
if (!await pathExists3(join19(packageDir, "harness", "manifests"))) {
|
|
5817
6359
|
throw new WorkflowDataResolutionError(
|
|
5818
6360
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
|
|
5819
6361
|
"WORKFLOW_DATA_INVALID",
|
|
@@ -5842,8 +6384,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
|
|
|
5842
6384
|
const packageName = workflowPackageName(family, options.env);
|
|
5843
6385
|
const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
|
|
5844
6386
|
const cacheKey = packageSpec.replace("/", "+");
|
|
5845
|
-
const cacheRoot =
|
|
5846
|
-
if (await pathExists3(
|
|
6387
|
+
const cacheRoot = join19(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
|
|
6388
|
+
if (await pathExists3(join19(cacheRoot, "harness", "manifests"))) {
|
|
5847
6389
|
if (version === "latest") {
|
|
5848
6390
|
const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
|
|
5849
6391
|
if (stale) {
|
|
@@ -5889,9 +6431,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
|
|
|
5889
6431
|
return `\u65E0\u6CD5\u83B7\u53D6\u5DE5\u4F5C\u6D41\u6570\u636E\u5305 ${packageSpec}\uFF1A\u672C\u5730\u7F13\u5B58\u4E0D\u5B58\u5728\u4E14\u83B7\u53D6\u5931\u8D25\u3002\u53EF${hintRoot}\u3002\u539F\u56E0\uFF1A${code !== "" ? code + " " : ""}${detail}`;
|
|
5890
6432
|
}
|
|
5891
6433
|
async function readWorkflowFamilyManifest(resourcesRoot) {
|
|
5892
|
-
const manifestPath =
|
|
6434
|
+
const manifestPath = join19(resourcesRoot, "hunter-workflow-family.json");
|
|
5893
6435
|
if (!await pathExists3(manifestPath)) return {};
|
|
5894
|
-
return JSON.parse(await
|
|
6436
|
+
return JSON.parse(await readFile17(manifestPath, "utf8"));
|
|
5895
6437
|
}
|
|
5896
6438
|
|
|
5897
6439
|
// src/bin.ts
|
|
@@ -5982,7 +6524,15 @@ async function runCli(argv, overrides = {}) {
|
|
|
5982
6524
|
dependencies
|
|
5983
6525
|
);
|
|
5984
6526
|
});
|
|
5985
|
-
addCommonOptions(program.command("update")).description("\u5E94\u7528\u5DF2\u6279\u51C6\u7684\u670D\u52A1\u7AEF\u4EA7\u7269").
|
|
6527
|
+
addCommonOptions(program.command("update")).description("\u5E94\u7528\u5DF2\u6279\u51C6\u7684\u670D\u52A1\u7AEF\u4EA7\u7269").option(
|
|
6528
|
+
"--conflict-strategy <strategy>",
|
|
6529
|
+
"manual | keep-local | accept-remote\uFF08\u9ED8\u8BA4 manual\uFF1Brename \u51B2\u7A81\u59CB\u7EC8 manual\uFF09"
|
|
6530
|
+
).option(
|
|
6531
|
+
"--resolve <path=strategy>",
|
|
6532
|
+
"\u5355\u8DEF\u5F84\u8986\u76D6 keep-local|accept-remote\uFF08\u53EF\u91CD\u590D\uFF09",
|
|
6533
|
+
(value, previous) => [...previous, value],
|
|
6534
|
+
[]
|
|
6535
|
+
).action(async (options) => {
|
|
5986
6536
|
exitCode = await runUpdate(
|
|
5987
6537
|
{ ...program.opts(), ...options },
|
|
5988
6538
|
dependencies
|