@staix/vault-mem 0.3.0 → 0.3.1
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/CHANGELOG.md +4 -0
- package/package.json +1 -1
- package/src/sync.ts +17 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.3.1] - 2026-11-01
|
|
6
|
+
|
|
7
|
+
- `sync` and `doctor` refuse a git URL rewrite (`insteadOf`/`pushInsteadOf`) only when its prefix matches the configured remote or one of origin's configured or effective URLs, explicit `pushurl` included, and name that rule (#86). An unrelated rule, such as a global `https://github.com/` to SSH rewrite with an SSH vault remote, no longer blocks sync.
|
|
8
|
+
|
|
5
9
|
## [0.3.0] - 2026-11-01
|
|
6
10
|
|
|
7
11
|
- A vault whose PII policy has members this build does not know (from a newer-format config) fails closed: note writes, `check`, tidy, `promote-from-amem`, `promote`, move and delete and log appends on it are refused with `PII_POLICY` instead of screening with an incomplete policy. Reads and `vaults pii` show still work.
|
package/package.json
CHANGED
package/src/sync.ts
CHANGED
|
@@ -52,19 +52,30 @@ function gitIn(cwd: string, env: NodeJS.ProcessEnv): Git {
|
|
|
52
52
|
return out;
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* insteadOf/pushInsteadOf rules that could rewrite any of `urls` or origin's configured URLs. Git applies a rule
|
|
57
|
+
* when its value is a literal prefix of the configured URL, so the raw config values are checked too; this covers
|
|
58
|
+
* an explicit pushurl, whose rewrite git does not report. A rule without a value is treated as matching.
|
|
59
|
+
*/
|
|
60
|
+
function applicableRewrites(lines: (args: string[]) => string[], urls: string[]): string[] {
|
|
61
|
+
const all = [...urls, ...lines(["config", "--get-all", "remote.origin.url"]), ...lines(["config", "--get-all", "remote.origin.pushurl"])];
|
|
62
|
+
return lines(["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"]).filter(line => {
|
|
63
|
+
const at = line.indexOf(" "), prefix = at < 0 ? "" : line.slice(at + 1);
|
|
64
|
+
return !prefix || all.some(url => url.startsWith(prefix));
|
|
65
|
+
});
|
|
66
|
+
}
|
|
55
67
|
/** Local-only origin validation shared by doctor and sync; never fetches or pushes. */
|
|
56
68
|
export function originCheck(root: string, remote: string, env: NodeJS.ProcessEnv = process.env): { ok: boolean; origin: string | null; fetchUrls: string[]; pushUrls: string[]; issue?: string } {
|
|
57
69
|
const git = gitIn(root, env);
|
|
58
70
|
if (realpathSync(git(["rev-parse", "--show-toplevel"]).stdout.trim()) !== root) throw new Error("vault path is not the root of its git repository");
|
|
59
71
|
const origin = git(["remote", "get-url", "origin"], { allowFail: true });
|
|
60
72
|
const originUrl = origin.status === 0 ? origin.stdout.trim() : null;
|
|
61
|
-
// Every effective fetch and push URL must be the configured remote
|
|
62
|
-
// because git does not report pushInsteadOf rewrites of an explicit pushurl.
|
|
73
|
+
// Every effective fetch and push URL must be the configured remote, and no URL rewrite may apply to it.
|
|
63
74
|
const lines = (args: string[]) => { const out = git(args, { allowFail: true }); return out.status === 0 ? out.stdout.split("\n").map(line => line.trim()).filter(Boolean) : []; };
|
|
64
75
|
const fetchUrls = lines(["remote", "get-url", "--all", "origin"]), pushUrls = lines(["remote", "get-url", "--push", "--all", "origin"]);
|
|
65
|
-
const rewrites = lines
|
|
76
|
+
const rewrites = applicableRewrites(lines, [remote, ...fetchUrls, ...pushUrls]);
|
|
66
77
|
const ok = originUrl !== null && rewrites.length === 0 && fetchUrls.length > 0 && pushUrls.length > 0 && [...fetchUrls, ...pushUrls].every(url => sameRemote(url, remote));
|
|
67
|
-
return { ok, origin: originUrl, fetchUrls, pushUrls, ...(!ok ? { issue: originUrl === null ? "repository has no origin remote" : rewrites.length ?
|
|
78
|
+
return { ok, origin: originUrl, fetchUrls, pushUrls, ...(!ok ? { issue: originUrl === null ? "repository has no origin remote" : rewrites.length ? `git url rewrite ${rewrites[0]} applies to the vault remote; refusing because the push destination cannot be verified` : "an origin fetch or push URL does not match the configured remote; nothing was fetched or pushed" } : {}) };
|
|
68
79
|
}
|
|
69
80
|
function planned(path: string): boolean { return STAGE_FILES.includes(path) || STAGE_PREFIXES.some(prefix => path.startsWith(prefix)); }
|
|
70
81
|
/** Parse `status --porcelain=v1 -z` into changed paths (renames contribute both sides). */
|
|
@@ -141,7 +152,8 @@ export function syncVault(home: string, request: { vault: string; confirm?: bool
|
|
|
141
152
|
}
|
|
142
153
|
}
|
|
143
154
|
if (!result.commit && (!hasUpstream ? false : git(["rev-list", "--count", `${upstream}..HEAD`]).stdout.trim() === "0")) return { ...result, ok: true, applied: false, pushed: false };
|
|
144
|
-
|
|
155
|
+
const pushNow = lines(["remote", "get-url", "--push", "--all", "origin"]);
|
|
156
|
+
if (pushNow.some(url => !sameRemote(url, spec.remote!)) || applicableRewrites(lines, [spec.remote, ...fetchUrls, ...pushNow]).length) return { ...result, applied: Boolean(result.commit), pushed: false, issues: [issue("REMOTE", "push destination changed before push; nothing was pushed")] };
|
|
145
157
|
const push = git(["push", "--quiet", "--porcelain", "origin", `HEAD:refs/heads/${result.branch}`], { allowFail: true });
|
|
146
158
|
if (push.status !== 0) return { ...result, applied: Boolean(result.commit), pushed: false, issues: [issue("PUSH", `push failed: ${push.stderr.trim().split("\n").at(-1)?.slice(0, 300) ?? "error"}; the local commit was kept`)] };
|
|
147
159
|
// Verify the remote really has our head before reporting success.
|