sidecarsync 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +281 -53
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -407,7 +407,8 @@ function jjDefaultWorkspace(root) {
|
|
|
407
407
|
try {
|
|
408
408
|
if (!fs3.statSync(pointer).isFile())
|
|
409
409
|
return;
|
|
410
|
-
const
|
|
410
|
+
const repo = path3.resolve(path3.dirname(pointer), fs3.readFileSync(pointer, "utf8").trim());
|
|
411
|
+
const workspace = path3.dirname(path3.dirname(repo));
|
|
411
412
|
return fs3.existsSync(path3.join(workspace, ".jj")) ? workspace : undefined;
|
|
412
413
|
} catch {
|
|
413
414
|
return;
|
|
@@ -2628,7 +2629,26 @@ function repairLinkedCheckout(root, config, sidecarPath) {
|
|
|
2628
2629
|
}
|
|
2629
2630
|
throw new SidecarError(`sidecar checkout at ${sidecarPath} is not a usable Git checkout; if this repo moved, repair it there first (\`git worktree repair\`), or delete the checkout and run \`sidecar clone\``);
|
|
2630
2631
|
}
|
|
2631
|
-
function
|
|
2632
|
+
function checkoutIsUnlinkedFromFamily(root, config, sidecarPath) {
|
|
2633
|
+
if (isStandalone(config))
|
|
2634
|
+
return false;
|
|
2635
|
+
try {
|
|
2636
|
+
if (!fs7.statSync(path7.join(sidecarPath, ".git")).isDirectory())
|
|
2637
|
+
return false;
|
|
2638
|
+
} catch {
|
|
2639
|
+
return false;
|
|
2640
|
+
}
|
|
2641
|
+
const primary = familyPrimaryRoot(root);
|
|
2642
|
+
if (!primary)
|
|
2643
|
+
return false;
|
|
2644
|
+
try {
|
|
2645
|
+
const primaryConfig = readConfig(path7.join(primary, ".sidecar"));
|
|
2646
|
+
return primaryConfig.remote === config.remote;
|
|
2647
|
+
} catch {
|
|
2648
|
+
return false;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
function cloneOrUpdate(root, config, bootstrapMain, options) {
|
|
2632
2652
|
const sidecarPath = resolveSidecarPath(root, config);
|
|
2633
2653
|
if (fs7.existsSync(sidecarPath) && !hasGitMetadata(sidecarPath)) {
|
|
2634
2654
|
if (fs7.readdirSync(sidecarPath).length) {
|
|
@@ -2656,6 +2676,13 @@ function cloneOrUpdate(root, config, bootstrapMain) {
|
|
|
2656
2676
|
} else {
|
|
2657
2677
|
throw new SidecarError(`${sidecarPath} is not usable as a sidecar checkout`);
|
|
2658
2678
|
}
|
|
2679
|
+
if (options?.checkoutId) {
|
|
2680
|
+
fs7.writeFileSync(path7.join(gitDir(sidecarPath), "sidecar-id"), `${options.checkoutId}
|
|
2681
|
+
`, {
|
|
2682
|
+
encoding: "utf8",
|
|
2683
|
+
mode: 384
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2659
2686
|
ensureCommitIdentity(sidecarPath);
|
|
2660
2687
|
ensureRedactionFilter(sidecarPath, config.redaction);
|
|
2661
2688
|
if (bootstrapMain)
|
|
@@ -3680,8 +3707,187 @@ var init_cmd_init = __esm(() => {
|
|
|
3680
3707
|
init_redaction();
|
|
3681
3708
|
});
|
|
3682
3709
|
|
|
3683
|
-
// src/cmd-
|
|
3710
|
+
// src/cmd-refresh.ts
|
|
3684
3711
|
import fs10 from "node:fs";
|
|
3712
|
+
import path9 from "node:path";
|
|
3713
|
+
function worktreeHoldingBranch(repo, branch) {
|
|
3714
|
+
const result = git(repo, ["worktree", "list", "--porcelain"], { check: false });
|
|
3715
|
+
if (result.status !== 0)
|
|
3716
|
+
return;
|
|
3717
|
+
let current;
|
|
3718
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
3719
|
+
if (line.startsWith("worktree "))
|
|
3720
|
+
current = line.slice("worktree ".length).trim();
|
|
3721
|
+
else if (line === `branch refs/heads/${branch}`)
|
|
3722
|
+
return current;
|
|
3723
|
+
}
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
function checkoutIsOwnRepo(sidecarPath) {
|
|
3727
|
+
const top = git(sidecarPath, ["rev-parse", "--show-toplevel"], { check: false });
|
|
3728
|
+
if (top.status !== 0)
|
|
3729
|
+
return false;
|
|
3730
|
+
return realpathOr(top.stdout.trim()) === realpathOr(sidecarPath);
|
|
3731
|
+
}
|
|
3732
|
+
function unpushedCommits(sidecarPath) {
|
|
3733
|
+
if (!hasAnyCommit(sidecarPath))
|
|
3734
|
+
return 0;
|
|
3735
|
+
const counted = git(sidecarPath, ["rev-list", "--count", "HEAD", "--not", "--remotes=origin"], {
|
|
3736
|
+
check: false
|
|
3737
|
+
});
|
|
3738
|
+
return counted.status === 0 ? Number(counted.stdout.trim()) || 0 : 0;
|
|
3739
|
+
}
|
|
3740
|
+
function dependentWorktrees(sidecarPath) {
|
|
3741
|
+
try {
|
|
3742
|
+
if (!fs10.statSync(path9.join(sidecarPath, ".git")).isDirectory())
|
|
3743
|
+
return [];
|
|
3744
|
+
} catch {
|
|
3745
|
+
return [];
|
|
3746
|
+
}
|
|
3747
|
+
const result = git(sidecarPath, ["worktree", "list", "--porcelain"], { check: false });
|
|
3748
|
+
if (result.status !== 0)
|
|
3749
|
+
return [];
|
|
3750
|
+
const self = realpathOr(sidecarPath);
|
|
3751
|
+
return result.stdout.split(/\r?\n/).filter((line) => line.startsWith("worktree ")).map((line) => line.slice("worktree ".length).trim()).filter((entry) => entry && realpathOr(entry) !== self);
|
|
3752
|
+
}
|
|
3753
|
+
function existingCheckoutId(sidecarPath) {
|
|
3754
|
+
const candidates = [];
|
|
3755
|
+
const reported = git(sidecarPath, ["rev-parse", "--git-dir"], { check: false });
|
|
3756
|
+
if (reported.status === 0)
|
|
3757
|
+
candidates.push(path9.resolve(sidecarPath, reported.stdout.trim()));
|
|
3758
|
+
candidates.push(path9.join(sidecarPath, ".git"));
|
|
3759
|
+
for (const candidate of candidates) {
|
|
3760
|
+
try {
|
|
3761
|
+
const id = slug(fs10.readFileSync(path9.join(candidate, "sidecar-id"), "utf8"));
|
|
3762
|
+
if (id)
|
|
3763
|
+
return id;
|
|
3764
|
+
} catch {}
|
|
3765
|
+
}
|
|
3766
|
+
return;
|
|
3767
|
+
}
|
|
3768
|
+
function refreshCheckout(root, config) {
|
|
3769
|
+
const sidecarPath = resolveSidecarPath(root, config);
|
|
3770
|
+
if (isStandalone(config)) {
|
|
3771
|
+
throw new SidecarError("refusing to delete a standalone sidecar, which is the repo itself");
|
|
3772
|
+
}
|
|
3773
|
+
const relative = path9.relative(root, sidecarPath);
|
|
3774
|
+
if (!relative || relative.startsWith("..") || path9.isAbsolute(relative)) {
|
|
3775
|
+
throw new SidecarError(`refusing to delete ${sidecarPath}, which is not inside ${root}`);
|
|
3776
|
+
}
|
|
3777
|
+
const checkoutId = existingCheckoutId(sidecarPath);
|
|
3778
|
+
fs10.rmSync(sidecarPath, { recursive: true, force: true });
|
|
3779
|
+
const family = familySidecarCheckout(root, config);
|
|
3780
|
+
if (family) {
|
|
3781
|
+
git(family, ["worktree", "prune", "--expire", "now"], { check: false });
|
|
3782
|
+
fetch(family, true, false);
|
|
3783
|
+
}
|
|
3784
|
+
cloneOrUpdate(root, config, true, { checkoutId });
|
|
3785
|
+
logSidecarEvent("checkout-refresh", { root, sidecarPath, checkoutId: checkoutId ?? null });
|
|
3786
|
+
}
|
|
3787
|
+
function refreshStandaloneCheckout(root, config, resetInbox) {
|
|
3788
|
+
ensureCommitIdentity(root);
|
|
3789
|
+
ensureRedactionFilter(root, config.redaction);
|
|
3790
|
+
fetch(root, true, false);
|
|
3791
|
+
if (config.redaction !== "none") {
|
|
3792
|
+
logSidecarEvent("checkout-refresh", { root, standalone: true, settled: false });
|
|
3793
|
+
return `left ${config.branch} and the inbox branch untouched: settling them means switching branches, which under redaction would replace local files with their redacted pushed contents`;
|
|
3794
|
+
}
|
|
3795
|
+
ensureMainBranch(root, config);
|
|
3796
|
+
const inbox = expandInbox(config, root);
|
|
3797
|
+
if (resetInbox && branchExists(root, inbox) && branchExists(root, config.branch)) {
|
|
3798
|
+
const tip = git(root, ["rev-parse", "--short", inbox]).stdout.trim();
|
|
3799
|
+
const discarded = `refs/sidecar-discarded/${inbox}/${utcTimestamp()}-${tip}`;
|
|
3800
|
+
git(root, ["update-ref", discarded, inbox], { check: false });
|
|
3801
|
+
git(root, ["branch", "-f", inbox, config.branch]);
|
|
3802
|
+
console.log(`reset ${paint("brand", inbox)} to ${config.branch}; old tip kept at ${paint("brand", discarded)}`);
|
|
3803
|
+
}
|
|
3804
|
+
ensureInboxBranch(root, config, inbox);
|
|
3805
|
+
logSidecarEvent("checkout-refresh", { root, standalone: true, settled: true, resetInbox });
|
|
3806
|
+
return;
|
|
3807
|
+
}
|
|
3808
|
+
function cmdRefresh(args) {
|
|
3809
|
+
const parsed = parseOptions(args, {
|
|
3810
|
+
boolean: new Set(["--force", "--yes", "-y"]),
|
|
3811
|
+
value: new Set
|
|
3812
|
+
});
|
|
3813
|
+
if (parsed.positional.length)
|
|
3814
|
+
throw new SidecarError("usage: sidecar refresh [--force] [--yes]");
|
|
3815
|
+
const [root, config] = loadProject();
|
|
3816
|
+
const force = parsed.flags.has("--force");
|
|
3817
|
+
const standalone = isStandalone(config);
|
|
3818
|
+
const sidecarPath = requireSidecarCheckout(root, config);
|
|
3819
|
+
const readable = checkoutIsOwnRepo(sidecarPath);
|
|
3820
|
+
if (!readable && standalone) {
|
|
3821
|
+
throw new SidecarError(`${sidecarPath} is not a readable Git repository, and in standalone mode that repo is your own — sidecar will not rebuild it`);
|
|
3822
|
+
}
|
|
3823
|
+
if (!readable && !force) {
|
|
3824
|
+
throw new SidecarError(`${sidecarPath} is not a readable Git repository, so what it still holds cannot be checked; \`sidecar refresh --force\` replaces it anyway`);
|
|
3825
|
+
}
|
|
3826
|
+
let inbox;
|
|
3827
|
+
if (readable) {
|
|
3828
|
+
inbox = expandInbox(config, sidecarPath);
|
|
3829
|
+
fetch(sidecarPath, true, false);
|
|
3830
|
+
const unpushed = unpushedCommits(sidecarPath);
|
|
3831
|
+
const dirtyFiles = git(sidecarPath, ["status", "--porcelain"], { check: false }).stdout.split(`
|
|
3832
|
+
`).filter(Boolean).length;
|
|
3833
|
+
if ((unpushed || dirtyFiles) && !force) {
|
|
3834
|
+
const held = [
|
|
3835
|
+
unpushed ? `${unpushed} commit(s) the remote has not seen` : "",
|
|
3836
|
+
dirtyFiles ? `${dirtyFiles} uncommitted file(s)` : ""
|
|
3837
|
+
].filter(Boolean);
|
|
3838
|
+
throw new SidecarError(`this checkout still holds ${held.join(" and ")}; run \`sidecar sync\` to push them, then refresh — or \`sidecar refresh --force\` to discard them`);
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
if (standalone) {
|
|
3842
|
+
console.log(`${paint("repo", root)} is its own sidecar, so refresh does not rebuild it.`);
|
|
3843
|
+
console.log(config.redaction === "none" ? `it rewires the redaction filter and settles ${config.branch} onto ${paint("brand", `origin/${config.branch}`)}${force ? `, then resets the inbox branch to ${config.branch}` : ""}.` : `it rewires the redaction filter and, because redaction is on, leaves your branches where they are.`);
|
|
3844
|
+
} else {
|
|
3845
|
+
const dependents = dependentWorktrees(sidecarPath);
|
|
3846
|
+
if (dependents.length && !force) {
|
|
3847
|
+
throw new SidecarError(`${dependents.length} other checkout(s) share this one's Git store (${dependents.join(", ")}); refresh those working copies instead, or \`sidecar refresh --force\` to replace this one and leave them to be refreshed too`);
|
|
3848
|
+
}
|
|
3849
|
+
const family = familySidecarCheckout(root, config);
|
|
3850
|
+
const holder = inbox && family ? worktreeHoldingBranch(family, inbox) : undefined;
|
|
3851
|
+
if (holder && realpathOr(holder) !== realpathOr(sidecarPath)) {
|
|
3852
|
+
throw new SidecarError(`${inbox} is already checked out at ${holder}; give this working copy its own inbox (a {random} in the .sidecar inbox template) before refreshing`);
|
|
3853
|
+
}
|
|
3854
|
+
console.log(`refresh deletes ${paint("brand", sidecarPath)} and clones it again from ${paint("brand", config.remote)}, ${paint("attn", "discarding anything not pushed")}.`);
|
|
3855
|
+
if (family)
|
|
3856
|
+
console.log(`the rebuilt checkout will share this repo family's Git store.`);
|
|
3857
|
+
}
|
|
3858
|
+
const confirmed = parsed.flags.has("--yes") || parsed.flags.has("-y") || promptYesNoDefaultNo("continue?");
|
|
3859
|
+
if (!confirmed) {
|
|
3860
|
+
console.log("nothing changed");
|
|
3861
|
+
return 0;
|
|
3862
|
+
}
|
|
3863
|
+
let declined;
|
|
3864
|
+
withSyncLock(root, "throw", () => {
|
|
3865
|
+
if (readable && !force && isDirty(sidecarPath)) {
|
|
3866
|
+
throw new SidecarError("the sidecar checkout changed while waiting for confirmation; rerun refresh");
|
|
3867
|
+
}
|
|
3868
|
+
if (standalone)
|
|
3869
|
+
declined = refreshStandaloneCheckout(root, config, force);
|
|
3870
|
+
else
|
|
3871
|
+
refreshCheckout(root, config);
|
|
3872
|
+
});
|
|
3873
|
+
registerCurrentInstance(root, config, { event: "refresh" });
|
|
3874
|
+
console.log(`refreshed sidecar at ${paint("brand", sidecarPath)}`);
|
|
3875
|
+
if (declined)
|
|
3876
|
+
console.error(`sidecar: ${declined}`);
|
|
3877
|
+
return 0;
|
|
3878
|
+
}
|
|
3879
|
+
var init_cmd_refresh = __esm(() => {
|
|
3880
|
+
init_color();
|
|
3881
|
+
init_util();
|
|
3882
|
+
init_git();
|
|
3883
|
+
init_config();
|
|
3884
|
+
init_state();
|
|
3885
|
+
init_sync();
|
|
3886
|
+
init_ui();
|
|
3887
|
+
});
|
|
3888
|
+
|
|
3889
|
+
// src/cmd-status.ts
|
|
3890
|
+
import fs11 from "node:fs";
|
|
3685
3891
|
function statusLine(label, value, role) {
|
|
3686
3892
|
labelLine(STATUS_LABEL_WIDTH, label, value, role);
|
|
3687
3893
|
}
|
|
@@ -3720,6 +3926,9 @@ function cmdStatus(args) {
|
|
|
3720
3926
|
else
|
|
3721
3927
|
statusLine("branch", `${branch} — not the inbox branch; sync will switch back`, "attn");
|
|
3722
3928
|
statusLine("dirty", dirty ? "yes" : "no", dirty ? "attn" : "quiet");
|
|
3929
|
+
if (checkoutIsUnlinkedFromFamily(root, config, sidecarPath)) {
|
|
3930
|
+
statusLine("family", "independent clone — syncs via the remote; `sidecar refresh` links it", "attn");
|
|
3931
|
+
}
|
|
3723
3932
|
printDaemonLine();
|
|
3724
3933
|
printLastSyncLine(root);
|
|
3725
3934
|
const pending = pendingStatusInboxBranches(sidecarPath, config);
|
|
@@ -3749,6 +3958,7 @@ function cmdStatusJson() {
|
|
|
3749
3958
|
globalInstall: shouldUseGlobalRegistry() || Boolean(findGlobalSidecarExecutable()),
|
|
3750
3959
|
currentBranch: branch || undefined,
|
|
3751
3960
|
dirty: checkoutPresent ? Boolean(git(sidecarPath, ["status", "--porcelain"]).stdout.trim()) : undefined,
|
|
3961
|
+
familyLinked: checkoutPresent ? !checkoutIsUnlinkedFromFamily(root, config, sidecarPath) : undefined,
|
|
3752
3962
|
daemon: daemonHealth().text,
|
|
3753
3963
|
lastSyncAt: readInstances().find((instance) => instance.root === root)?.lastSyncAt,
|
|
3754
3964
|
pendingInbox: checkoutPresent ? pendingStatusInboxBranches(sidecarPath, config) : undefined
|
|
@@ -3898,16 +4108,16 @@ function cmdTail(args) {
|
|
|
3898
4108
|
throw new SidecarError("--lines requires a positive integer");
|
|
3899
4109
|
}
|
|
3900
4110
|
const filePath = sidecarLogPath();
|
|
3901
|
-
if (!
|
|
4111
|
+
if (!fs11.existsSync(filePath)) {
|
|
3902
4112
|
if (parsed.flags.has("-f") || parsed.flags.has("--follow")) {
|
|
3903
4113
|
followLog(filePath, 0);
|
|
3904
4114
|
return 0;
|
|
3905
4115
|
}
|
|
3906
4116
|
return 0;
|
|
3907
4117
|
}
|
|
3908
|
-
const stat =
|
|
4118
|
+
const stat = fs11.statSync(filePath);
|
|
3909
4119
|
if (stat.size > 0) {
|
|
3910
|
-
process.stdout.write(lastLines(
|
|
4120
|
+
process.stdout.write(lastLines(fs11.readFileSync(filePath, "utf8"), lines));
|
|
3911
4121
|
}
|
|
3912
4122
|
if (parsed.flags.has("-f") || parsed.flags.has("--follow")) {
|
|
3913
4123
|
followLog(filePath, stat.size);
|
|
@@ -3930,7 +4140,7 @@ function followLog(filePath, startOffset) {
|
|
|
3930
4140
|
sleep(1000);
|
|
3931
4141
|
let stat;
|
|
3932
4142
|
try {
|
|
3933
|
-
stat =
|
|
4143
|
+
stat = fs11.statSync(filePath);
|
|
3934
4144
|
} catch {
|
|
3935
4145
|
offset = 0;
|
|
3936
4146
|
continue;
|
|
@@ -3939,17 +4149,17 @@ function followLog(filePath, startOffset) {
|
|
|
3939
4149
|
offset = 0;
|
|
3940
4150
|
if (stat.size <= offset)
|
|
3941
4151
|
continue;
|
|
3942
|
-
const fd =
|
|
4152
|
+
const fd = fs11.openSync(filePath, "r");
|
|
3943
4153
|
try {
|
|
3944
4154
|
const length = stat.size - offset;
|
|
3945
4155
|
const buffer = Buffer.alloc(length);
|
|
3946
|
-
const bytesRead =
|
|
4156
|
+
const bytesRead = fs11.readSync(fd, buffer, 0, length, offset);
|
|
3947
4157
|
if (bytesRead > 0) {
|
|
3948
4158
|
process.stdout.write(buffer.subarray(0, bytesRead).toString("utf8"));
|
|
3949
4159
|
offset += bytesRead;
|
|
3950
4160
|
}
|
|
3951
4161
|
} finally {
|
|
3952
|
-
|
|
4162
|
+
fs11.closeSync(fd);
|
|
3953
4163
|
}
|
|
3954
4164
|
}
|
|
3955
4165
|
}
|
|
@@ -3980,8 +4190,8 @@ __export(exports_daemon, {
|
|
|
3980
4190
|
checkAndInstallUpdate: () => checkAndInstallUpdate,
|
|
3981
4191
|
WATCH_LIMIT: () => WATCH_LIMIT
|
|
3982
4192
|
});
|
|
3983
|
-
import
|
|
3984
|
-
import
|
|
4193
|
+
import fs12 from "node:fs";
|
|
4194
|
+
import path10 from "node:path";
|
|
3985
4195
|
import { spawn as spawn2 } from "node:child_process";
|
|
3986
4196
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3987
4197
|
async function runDaemonLoop(options) {
|
|
@@ -4073,7 +4283,7 @@ async function runCycle(state) {
|
|
|
4073
4283
|
let failed = 0;
|
|
4074
4284
|
let skipped = 0;
|
|
4075
4285
|
for (const instance of readInstances()) {
|
|
4076
|
-
if (!
|
|
4286
|
+
if (!fs12.existsSync(instance.configPath)) {
|
|
4077
4287
|
const misses = (state.misses.get(instance.root) ?? 0) + 1;
|
|
4078
4288
|
state.misses.set(instance.root, misses);
|
|
4079
4289
|
if (misses >= PRUNE_AFTER_MISSES) {
|
|
@@ -4178,7 +4388,7 @@ async function syncIfDirty(state, root, trigger) {
|
|
|
4178
4388
|
}
|
|
4179
4389
|
async function checkoutIsDirty(root) {
|
|
4180
4390
|
const sidecarPath = readInstances().find((instance) => instance.root === root)?.sidecarPath;
|
|
4181
|
-
if (!sidecarPath || !
|
|
4391
|
+
if (!sidecarPath || !fs12.existsSync(sidecarPath))
|
|
4182
4392
|
return false;
|
|
4183
4393
|
const result = await runChild("git", ["-C", sidecarPath, "status", "--porcelain"], { timeoutMs: 30000 });
|
|
4184
4394
|
return result.status === 0 && Boolean(result.stdout.trim());
|
|
@@ -4186,7 +4396,7 @@ async function checkoutIsDirty(root) {
|
|
|
4186
4396
|
function localSidecarCliPath(root) {
|
|
4187
4397
|
if (!projectDependsOnSidecar(root))
|
|
4188
4398
|
return;
|
|
4189
|
-
const candidate =
|
|
4399
|
+
const candidate = path10.join(root, "node_modules", PACKAGE_NAME, "dist", "cli.js");
|
|
4190
4400
|
if (!isFile(candidate))
|
|
4191
4401
|
return;
|
|
4192
4402
|
const localVersion = installedPackageVersion(root);
|
|
@@ -4198,7 +4408,7 @@ function currentCliPath() {
|
|
|
4198
4408
|
return process.argv[1] || fileURLToPath3(import.meta.url);
|
|
4199
4409
|
}
|
|
4200
4410
|
function selectWatchTargets(instances, limit = WATCH_LIMIT) {
|
|
4201
|
-
return [...instances].filter((instance) =>
|
|
4411
|
+
return [...instances].filter((instance) => fs12.existsSync(instance.configPath) && fs12.existsSync(instance.sidecarPath)).sort((left, right) => instanceRecency(right) - instanceRecency(left)).slice(0, limit);
|
|
4202
4412
|
}
|
|
4203
4413
|
function instanceRecency(instance) {
|
|
4204
4414
|
const time = Date.parse(instance.lastSyncAt ?? instance.updatedAt ?? instance.registeredAt);
|
|
@@ -4328,7 +4538,7 @@ async function watchRegistry(state) {
|
|
|
4328
4538
|
const watcher = chokidar.watch(sidecarStateDir(), { ignoreInitial: true, depth: 0 });
|
|
4329
4539
|
watcher.on("all", (...args) => {
|
|
4330
4540
|
const filePath = typeof args[1] === "string" ? args[1] : "";
|
|
4331
|
-
if (
|
|
4541
|
+
if (path10.basename(filePath) !== "instances.json")
|
|
4332
4542
|
return;
|
|
4333
4543
|
if (state.registryTimer)
|
|
4334
4544
|
return;
|
|
@@ -4366,18 +4576,18 @@ function compileGitignoreMatcher(lines) {
|
|
|
4366
4576
|
function watchIgnoreMatcher(sidecarPath) {
|
|
4367
4577
|
let gitignore;
|
|
4368
4578
|
try {
|
|
4369
|
-
const ignoreFile =
|
|
4370
|
-
if (
|
|
4371
|
-
gitignore = compileGitignoreMatcher(
|
|
4579
|
+
const ignoreFile = path10.join(sidecarPath, ".gitignore");
|
|
4580
|
+
if (fs12.existsSync(ignoreFile)) {
|
|
4581
|
+
gitignore = compileGitignoreMatcher(fs12.readFileSync(ignoreFile, "utf8").split(`
|
|
4372
4582
|
`));
|
|
4373
4583
|
}
|
|
4374
4584
|
} catch {}
|
|
4375
|
-
const root =
|
|
4585
|
+
const root = path10.resolve(sidecarPath);
|
|
4376
4586
|
return (candidate) => {
|
|
4377
|
-
const relative =
|
|
4587
|
+
const relative = path10.relative(root, candidate);
|
|
4378
4588
|
if (!relative)
|
|
4379
4589
|
return false;
|
|
4380
|
-
const normalized = relative.split(
|
|
4590
|
+
const normalized = relative.split(path10.sep).join("/");
|
|
4381
4591
|
if (normalized.startsWith(".."))
|
|
4382
4592
|
return true;
|
|
4383
4593
|
if (normalized === ".git" || normalized.startsWith(".git/"))
|
|
@@ -4453,10 +4663,10 @@ function restartAfterUpdate() {
|
|
|
4453
4663
|
}
|
|
4454
4664
|
async function acquireDaemonPid() {
|
|
4455
4665
|
const pidPath = daemonPidPath();
|
|
4456
|
-
|
|
4666
|
+
fs12.mkdirSync(path10.dirname(pidPath), { recursive: true });
|
|
4457
4667
|
while (true) {
|
|
4458
4668
|
try {
|
|
4459
|
-
|
|
4669
|
+
fs12.writeFileSync(pidPath, `${process.pid}
|
|
4460
4670
|
`, { encoding: "utf8", flag: "wx" });
|
|
4461
4671
|
return;
|
|
4462
4672
|
} catch (error) {
|
|
@@ -4472,7 +4682,7 @@ async function acquireDaemonPid() {
|
|
|
4472
4682
|
continue;
|
|
4473
4683
|
}
|
|
4474
4684
|
logSidecarEvent("daemon-pid-heal", { holder: holder ?? null });
|
|
4475
|
-
|
|
4685
|
+
fs12.rmSync(pidPath, { force: true });
|
|
4476
4686
|
}
|
|
4477
4687
|
}
|
|
4478
4688
|
function installShutdownHandlers() {
|
|
@@ -4487,12 +4697,12 @@ function installShutdownHandlers() {
|
|
|
4487
4697
|
function removeOwnPidFile() {
|
|
4488
4698
|
try {
|
|
4489
4699
|
if (readPid(daemonPidPath()) === process.pid)
|
|
4490
|
-
|
|
4700
|
+
fs12.rmSync(daemonPidPath(), { force: true });
|
|
4491
4701
|
} catch {}
|
|
4492
4702
|
}
|
|
4493
4703
|
function readPid(pidPath) {
|
|
4494
4704
|
try {
|
|
4495
|
-
const pid = Number(
|
|
4705
|
+
const pid = Number(fs12.readFileSync(pidPath, "utf8").trim());
|
|
4496
4706
|
return Number.isInteger(pid) && pid > 0 ? pid : undefined;
|
|
4497
4707
|
} catch {
|
|
4498
4708
|
return;
|
|
@@ -4533,21 +4743,21 @@ function runChild(command, args, options) {
|
|
|
4533
4743
|
}
|
|
4534
4744
|
function isFile(filePath) {
|
|
4535
4745
|
try {
|
|
4536
|
-
return
|
|
4746
|
+
return fs12.statSync(filePath).isFile();
|
|
4537
4747
|
} catch {
|
|
4538
4748
|
return false;
|
|
4539
4749
|
}
|
|
4540
4750
|
}
|
|
4541
4751
|
function realpathOr2(filePath) {
|
|
4542
4752
|
try {
|
|
4543
|
-
return
|
|
4753
|
+
return fs12.realpathSync(filePath);
|
|
4544
4754
|
} catch {
|
|
4545
|
-
return
|
|
4755
|
+
return path10.resolve(filePath);
|
|
4546
4756
|
}
|
|
4547
4757
|
}
|
|
4548
4758
|
function isInsidePath2(child, parent) {
|
|
4549
|
-
const relative =
|
|
4550
|
-
return Boolean(relative) && !relative.startsWith("..") && !
|
|
4759
|
+
const relative = path10.relative(parent, child);
|
|
4760
|
+
return Boolean(relative) && !relative.startsWith("..") && !path10.isAbsolute(relative);
|
|
4551
4761
|
}
|
|
4552
4762
|
function escapeRegex(value) {
|
|
4553
4763
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -4750,9 +4960,9 @@ var init_cmd_daemon = __esm(() => {
|
|
|
4750
4960
|
});
|
|
4751
4961
|
|
|
4752
4962
|
// src/cmd-sync.ts
|
|
4753
|
-
import
|
|
4963
|
+
import fs13 from "node:fs";
|
|
4754
4964
|
import os6 from "node:os";
|
|
4755
|
-
import
|
|
4965
|
+
import path11 from "node:path";
|
|
4756
4966
|
function cmdSnapshot(args) {
|
|
4757
4967
|
const parsed = parseOptions(args, {
|
|
4758
4968
|
boolean: new Set(["--push"]),
|
|
@@ -4808,6 +5018,12 @@ function cmdSync(args) {
|
|
|
4808
5018
|
if (synced) {
|
|
4809
5019
|
registerCurrentInstance(root, config, { event: "sync", lastSyncAt: nowIso() });
|
|
4810
5020
|
reportSyncHealth(root, config, { status: "ok" });
|
|
5021
|
+
if (!soft) {
|
|
5022
|
+
const sidecarPath = resolveSidecarPath(root, config);
|
|
5023
|
+
if (checkoutIsUnlinkedFromFamily(root, config, sidecarPath)) {
|
|
5024
|
+
console.log("sidecar: this checkout is an independent clone, so it settles with its siblings through the remote; `sidecar refresh` links it to the one this repo family shares");
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
4811
5027
|
}
|
|
4812
5028
|
return 0;
|
|
4813
5029
|
}
|
|
@@ -4851,7 +5067,7 @@ function cmdRedactions(args) {
|
|
|
4851
5067
|
let shown = 0;
|
|
4852
5068
|
let items = 0;
|
|
4853
5069
|
for (const relPath of files) {
|
|
4854
|
-
const delta = fileRedactionDelta(
|
|
5070
|
+
const delta = fileRedactionDelta(path11.join(sidecarPath, relPath), config.redaction);
|
|
4855
5071
|
if (!delta)
|
|
4856
5072
|
continue;
|
|
4857
5073
|
if (shown)
|
|
@@ -4871,12 +5087,12 @@ ${items} redaction(s) in ${shown} file(s) will be pushed this way (mode: ${confi
|
|
|
4871
5087
|
return 0;
|
|
4872
5088
|
}
|
|
4873
5089
|
function printRedactionDiff(original, redacted) {
|
|
4874
|
-
const scratch =
|
|
5090
|
+
const scratch = fs13.mkdtempSync(path11.join(os6.tmpdir(), "sidecar-redactions-"));
|
|
4875
5091
|
try {
|
|
4876
|
-
const localPath =
|
|
4877
|
-
const pushedPath =
|
|
4878
|
-
|
|
4879
|
-
|
|
5092
|
+
const localPath = path11.join(scratch, "local");
|
|
5093
|
+
const pushedPath = path11.join(scratch, "pushed");
|
|
5094
|
+
fs13.writeFileSync(localPath, original, "utf8");
|
|
5095
|
+
fs13.writeFileSync(pushedPath, redacted, "utf8");
|
|
4880
5096
|
const color = colorLevel() > 0 ? ["--color"] : [];
|
|
4881
5097
|
const diff = gitRaw(["diff", "--no-index", ...color, "--", localPath, pushedPath], { check: false });
|
|
4882
5098
|
const lines = diff.stdout.split(`
|
|
@@ -4887,16 +5103,16 @@ function printRedactionDiff(original, redacted) {
|
|
|
4887
5103
|
if (body)
|
|
4888
5104
|
console.log(body);
|
|
4889
5105
|
} finally {
|
|
4890
|
-
|
|
5106
|
+
fs13.rmSync(scratch, { recursive: true, force: true });
|
|
4891
5107
|
}
|
|
4892
5108
|
}
|
|
4893
5109
|
function cmdRedact(args) {
|
|
4894
5110
|
const parsed = parseOptions(args, { boolean: new Set, value: new Set(["--mode"]) });
|
|
4895
5111
|
const mode = redactionModeConfigValue(getValue(parsed, "--mode", DEFAULT_REDACTION_MODE), "--mode");
|
|
4896
|
-
const output = redactBuffer(
|
|
5112
|
+
const output = redactBuffer(fs13.readFileSync(0), mode);
|
|
4897
5113
|
let offset = 0;
|
|
4898
5114
|
while (offset < output.length) {
|
|
4899
|
-
offset +=
|
|
5115
|
+
offset += fs13.writeSync(1, output, offset, output.length - offset);
|
|
4900
5116
|
}
|
|
4901
5117
|
return 0;
|
|
4902
5118
|
}
|
|
@@ -4979,6 +5195,7 @@ var init_commands = __esm(() => {
|
|
|
4979
5195
|
init_util();
|
|
4980
5196
|
init_install();
|
|
4981
5197
|
init_cmd_init();
|
|
5198
|
+
init_cmd_refresh();
|
|
4982
5199
|
init_cmd_status();
|
|
4983
5200
|
init_cmd_daemon();
|
|
4984
5201
|
init_cmd_sync();
|
|
@@ -5025,6 +5242,16 @@ var init_commands = __esm(() => {
|
|
|
5025
5242
|
{ name: "tail", run: cmdTail, section: "sync", usage: "tail [-f|--follow] [-n|--lines count]" },
|
|
5026
5243
|
{ name: "update", run: cmdUpdate, section: "sync", usage: "update" },
|
|
5027
5244
|
{ name: "clone", run: cmdClone, section: "advanced", usage: "clone [--if-missing]" },
|
|
5245
|
+
{
|
|
5246
|
+
name: "refresh",
|
|
5247
|
+
run: cmdRefresh,
|
|
5248
|
+
section: "advanced",
|
|
5249
|
+
usage: "refresh [--force] [--yes]",
|
|
5250
|
+
notes: [
|
|
5251
|
+
"delete the sidecar checkout and clone it again",
|
|
5252
|
+
"discards anything unpushed; refuses until `sidecar sync` has run"
|
|
5253
|
+
]
|
|
5254
|
+
},
|
|
5028
5255
|
{ name: "deinit", run: cmdDeinit, section: "advanced", usage: "deinit" },
|
|
5029
5256
|
{ name: "snapshot", run: cmdSnapshot, section: "advanced", usage: "snapshot [--push] [-m message]" },
|
|
5030
5257
|
{ name: "merge", run: cmdMerge, section: "advanced", usage: "merge [--fork-files] [--no-push]" },
|
|
@@ -5095,6 +5322,7 @@ var init_cli = __esm(() => {
|
|
|
5095
5322
|
init_sync();
|
|
5096
5323
|
init_commands();
|
|
5097
5324
|
init_cmd_init();
|
|
5325
|
+
init_cmd_refresh();
|
|
5098
5326
|
init_cmd_status();
|
|
5099
5327
|
init_cmd_daemon();
|
|
5100
5328
|
init_cmd_sync();
|
|
@@ -5102,8 +5330,8 @@ var init_cli = __esm(() => {
|
|
|
5102
5330
|
|
|
5103
5331
|
// src/bin.ts
|
|
5104
5332
|
init_cli();
|
|
5105
|
-
import
|
|
5106
|
-
import
|
|
5333
|
+
import fs14 from "node:fs";
|
|
5334
|
+
import path12 from "node:path";
|
|
5107
5335
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
5108
5336
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
5109
5337
|
var SKIP_LOCAL_EXEC_ENV3 = "SIDECAR_SKIP_LOCAL_EXEC";
|
|
@@ -5132,15 +5360,15 @@ if (!process.env[SKIP_LOCAL_EXEC_ENV3]) {
|
|
|
5132
5360
|
}
|
|
5133
5361
|
process.exit(await main());
|
|
5134
5362
|
function findLocalInstall(start, self) {
|
|
5135
|
-
let current =
|
|
5363
|
+
let current = path12.resolve(start);
|
|
5136
5364
|
while (true) {
|
|
5137
5365
|
if (projectDependsOnSidecar2(current)) {
|
|
5138
|
-
const candidate =
|
|
5366
|
+
const candidate = path12.join(current, "node_modules", PACKAGE_NAME2, "dist", "cli.js");
|
|
5139
5367
|
if (isFile2(candidate) && !sameFile(candidate, self)) {
|
|
5140
5368
|
return { executable: candidate, newer: localIsNewer(current) };
|
|
5141
5369
|
}
|
|
5142
5370
|
}
|
|
5143
|
-
const parent =
|
|
5371
|
+
const parent = path12.dirname(current);
|
|
5144
5372
|
if (parent === current)
|
|
5145
5373
|
return;
|
|
5146
5374
|
current = parent;
|
|
@@ -5151,11 +5379,11 @@ function localIsNewer(projectRoot) {
|
|
|
5151
5379
|
return localVersion !== undefined && compareVersions(localVersion, packageVersion()) > 0;
|
|
5152
5380
|
}
|
|
5153
5381
|
function projectDependsOnSidecar2(projectRoot) {
|
|
5154
|
-
const manifestPath =
|
|
5382
|
+
const manifestPath = path12.join(projectRoot, "package.json");
|
|
5155
5383
|
if (!isFile2(manifestPath))
|
|
5156
5384
|
return false;
|
|
5157
5385
|
try {
|
|
5158
|
-
const manifest = JSON.parse(
|
|
5386
|
+
const manifest = JSON.parse(fs14.readFileSync(manifestPath, "utf8"));
|
|
5159
5387
|
return Boolean(manifest.dependencies?.[PACKAGE_NAME2] || manifest.devDependencies?.[PACKAGE_NAME2] || manifest.optionalDependencies?.[PACKAGE_NAME2] || manifest.peerDependencies?.[PACKAGE_NAME2]);
|
|
5160
5388
|
} catch {
|
|
5161
5389
|
return false;
|
|
@@ -5163,14 +5391,14 @@ function projectDependsOnSidecar2(projectRoot) {
|
|
|
5163
5391
|
}
|
|
5164
5392
|
function isFile2(filePath) {
|
|
5165
5393
|
try {
|
|
5166
|
-
return
|
|
5394
|
+
return fs14.statSync(filePath).isFile();
|
|
5167
5395
|
} catch {
|
|
5168
5396
|
return false;
|
|
5169
5397
|
}
|
|
5170
5398
|
}
|
|
5171
5399
|
function sameFile(first, second) {
|
|
5172
5400
|
try {
|
|
5173
|
-
return
|
|
5401
|
+
return fs14.realpathSync(first) === fs14.realpathSync(second);
|
|
5174
5402
|
} catch {
|
|
5175
5403
|
return false;
|
|
5176
5404
|
}
|