chore(git-sync): drop stray build/ artifacts re-introduced during rebase
build/ is gitignored and compiled in CI/Docker; a few files leaked back into the tree while replaying commits onto develop. Remove them so the package keeps a single source of truth (src/).
This commit is contained in:
70
packages/git-sync/build/engine/cycle.d.ts
vendored
70
packages/git-sync/build/engine/cycle.d.ts
vendored
@@ -1,70 +0,0 @@
|
|||||||
import { VaultGit } from "./git.js";
|
|
||||||
import { GitSyncClient } from "./client.types.js";
|
|
||||||
import { Settings } from "./settings.js";
|
|
||||||
/**
|
|
||||||
* Absolute-path filesystem primitives the cycle needs. Injected (not imported)
|
|
||||||
* so the engine stays IO-free and unit-testable. `mkdir` is recursive; `rm` is
|
|
||||||
* force (a missing file is a no-op).
|
|
||||||
*/
|
|
||||||
export interface CycleFs {
|
|
||||||
readFile: (absPath: string) => Promise<string>;
|
|
||||||
writeFile: (absPath: string, text: string) => Promise<void>;
|
|
||||||
mkdir: (absDir: string) => Promise<void>;
|
|
||||||
rm: (absPath: string) => Promise<void>;
|
|
||||||
}
|
|
||||||
export interface RunCycleDeps {
|
|
||||||
spaceId: string;
|
|
||||||
/** The Docmost seam (reads for pull, writes for push). */
|
|
||||||
client: GitSyncClient;
|
|
||||||
/** The per-space git vault (a real working repo). */
|
|
||||||
vault: VaultGit;
|
|
||||||
/** Engine settings; `vaultPath` roots the relPath -> absolute-path mapping. */
|
|
||||||
settings: Settings;
|
|
||||||
fs: CycleFs;
|
|
||||||
log: (line: string) => void;
|
|
||||||
/**
|
|
||||||
* Delete-cap hook (the ONLY caller-specific policy). Called with the push
|
|
||||||
* dry-run's planned delete count (`Number.POSITIVE_INFINITY` when the dry-run
|
|
||||||
* itself failed, so the hook can fail safe) and the live client; returns the
|
|
||||||
* client to use for the REAL apply. The default (omitted) applies every op
|
|
||||||
* unmodified. gitmost uses it to neutralize deletes when over its cap.
|
|
||||||
*
|
|
||||||
* When omitted, NO dry-run is performed (one fewer push planning pass).
|
|
||||||
*/
|
|
||||||
resolveApplyClient?: (plannedDeletes: number, client: GitSyncClient) => GitSyncClient;
|
|
||||||
}
|
|
||||||
export interface RunCycleResult {
|
|
||||||
ran: boolean;
|
|
||||||
/** Set when the cycle short-circuited without running pull/push. */
|
|
||||||
skipped?: "merge-in-progress";
|
|
||||||
pull?: {
|
|
||||||
written: number;
|
|
||||||
deleted: number;
|
|
||||||
conflict: boolean;
|
|
||||||
};
|
|
||||||
push?: {
|
|
||||||
mode: string;
|
|
||||||
failures: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Run ONE full reconcile cycle for a space: PULL (Docmost -> vault) then PUSH
|
|
||||||
* (vault -> Docmost), under the engine's required branch choreography. This is
|
|
||||||
* the single entry point the app drives — it owns the staging order so it can
|
|
||||||
* never drift from the engine it ships with.
|
|
||||||
*
|
|
||||||
* Staging (the ⭐ data-loss-critical order, SPEC §6/§9):
|
|
||||||
* 1. assertGitAvailable + ensureRepo (the git state store must exist).
|
|
||||||
* 2. refuse on an unresolved merge (a prior conflicting pull); next checkout
|
|
||||||
* would fail otherwise.
|
|
||||||
* 3. ensureBranch('docmost','main') + checkout('docmost'). Pull writes MUST
|
|
||||||
* land on `docmost`, not `main`: applyPullActions commits on `docmost`,
|
|
||||||
* then checks out `main` and merges docmost -> main. Writing Docmost
|
|
||||||
* content straight onto `main` would clobber local file edits before push
|
|
||||||
* can diff them.
|
|
||||||
* 4. PULL: readExisting -> listSpaceTree -> computePullActions -> apply.
|
|
||||||
* 5. PUSH: optional dry-run to feed the delete-cap hook, then the real apply.
|
|
||||||
*
|
|
||||||
* Lock + cap POLICY live in the caller; this owns only the mechanics.
|
|
||||||
*/
|
|
||||||
export declare function runCycle(deps: RunCycleDeps): Promise<RunCycleResult>;
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
import { readExisting, computePullActions, applyPullActions } from "./pull.js";
|
|
||||||
import { runPush } from "./push.js";
|
|
||||||
/**
|
|
||||||
* Run ONE full reconcile cycle for a space: PULL (Docmost -> vault) then PUSH
|
|
||||||
* (vault -> Docmost), under the engine's required branch choreography. This is
|
|
||||||
* the single entry point the app drives — it owns the staging order so it can
|
|
||||||
* never drift from the engine it ships with.
|
|
||||||
*
|
|
||||||
* Staging (the ⭐ data-loss-critical order, SPEC §6/§9):
|
|
||||||
* 1. assertGitAvailable + ensureRepo (the git state store must exist).
|
|
||||||
* 2. refuse on an unresolved merge (a prior conflicting pull); next checkout
|
|
||||||
* would fail otherwise.
|
|
||||||
* 3. ensureBranch('docmost','main') + checkout('docmost'). Pull writes MUST
|
|
||||||
* land on `docmost`, not `main`: applyPullActions commits on `docmost`,
|
|
||||||
* then checks out `main` and merges docmost -> main. Writing Docmost
|
|
||||||
* content straight onto `main` would clobber local file edits before push
|
|
||||||
* can diff them.
|
|
||||||
* 4. PULL: readExisting -> listSpaceTree -> computePullActions -> apply.
|
|
||||||
* 5. PUSH: optional dry-run to feed the delete-cap hook, then the real apply.
|
|
||||||
*
|
|
||||||
* Lock + cap POLICY live in the caller; this owns only the mechanics.
|
|
||||||
*/
|
|
||||||
export async function runCycle(deps) {
|
|
||||||
const { spaceId, client, vault, settings, fs, log, resolveApplyClient } = deps;
|
|
||||||
const vaultRoot = settings.vaultPath;
|
|
||||||
const abs = (relPath) => `${vaultRoot}/${relPath}`;
|
|
||||||
// 1. The engine state store is git: make sure the repo + branches exist
|
|
||||||
// before any tracked-file listing or diff.
|
|
||||||
await vault.assertGitAvailable();
|
|
||||||
await vault.ensureRepo();
|
|
||||||
// 2. Refuse to run on top of an unresolved merge (SPEC §9): a prior
|
|
||||||
// conflicting pull leaves the vault mid-merge; the next checkout would fail.
|
|
||||||
if (await vault.isMergeInProgress()) {
|
|
||||||
log(`vault has an unresolved merge — resolve it (or 'git merge --abort') ` +
|
|
||||||
`and re-run (SPEC §9); skipping cycle.`);
|
|
||||||
return { ran: false, skipped: "merge-in-progress" };
|
|
||||||
}
|
|
||||||
// 3. Pull writes happen on `docmost`; be on it BEFORE applying (see docstring).
|
|
||||||
await vault.ensureBranch("docmost", "main");
|
|
||||||
await vault.checkout("docmost");
|
|
||||||
// 4. PULL --------------------------------------------------------------------
|
|
||||||
const existing = await readExisting({
|
|
||||||
listTracked: () => vault.listTrackedFiles("*.md"),
|
|
||||||
readFile: (relPath) => fs.readFile(abs(relPath)),
|
|
||||||
});
|
|
||||||
const tree = await client.listSpaceTree(spaceId);
|
|
||||||
const pullActions = computePullActions({
|
|
||||||
pages: tree.pages,
|
|
||||||
treeComplete: tree.complete,
|
|
||||||
existing,
|
|
||||||
});
|
|
||||||
const pullResult = await applyPullActions({
|
|
||||||
client,
|
|
||||||
git: vault,
|
|
||||||
writeFile: (absPath, text) => fs.writeFile(absPath, text),
|
|
||||||
mkdir: (absDir) => fs.mkdir(absDir),
|
|
||||||
rm: (absPath) => fs.rm(absPath),
|
|
||||||
}, pullActions, vaultRoot);
|
|
||||||
// 5. PUSH --------------------------------------------------------------------
|
|
||||||
const pushDeps = {
|
|
||||||
settings,
|
|
||||||
git: vault,
|
|
||||||
makeClient: () => client,
|
|
||||||
readFile: (relPath) => fs.readFile(abs(relPath)),
|
|
||||||
writeFile: (relPath, text) => fs.writeFile(abs(relPath), text),
|
|
||||||
log,
|
|
||||||
};
|
|
||||||
let applyClient = client;
|
|
||||||
if (resolveApplyClient) {
|
|
||||||
// Plan the push as a DRY-RUN first to read the delete count, then let the
|
|
||||||
// caller decide the apply client (e.g. neutralize deletes over a cap). A
|
|
||||||
// failed dry-run yields Infinity so the hook can fail safe.
|
|
||||||
let plannedDeletes;
|
|
||||||
try {
|
|
||||||
const dry = await runPush(pushDeps, { dryRun: true });
|
|
||||||
plannedDeletes = dry.planned?.deletes ?? 0;
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
log(`push dry-run planning failed (${err instanceof Error ? err.message : String(err)}); deferring deletion policy to the cap hook (fail-safe).`);
|
|
||||||
plannedDeletes = Number.POSITIVE_INFINITY;
|
|
||||||
}
|
|
||||||
applyClient = resolveApplyClient(plannedDeletes, client);
|
|
||||||
}
|
|
||||||
const pushResult = await runPush({ ...pushDeps, makeClient: () => applyClient }, { dryRun: false });
|
|
||||||
return {
|
|
||||||
ran: true,
|
|
||||||
pull: {
|
|
||||||
written: pullResult.written,
|
|
||||||
deleted: pullResult.deleted,
|
|
||||||
conflict: pullResult.merge.conflict,
|
|
||||||
},
|
|
||||||
push: {
|
|
||||||
mode: pushResult.mode,
|
|
||||||
failures: pushResult.failures?.length ?? 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
50
packages/git-sync/build/lib/page-file.d.ts
vendored
50
packages/git-sync/build/lib/page-file.d.ts
vendored
@@ -1,50 +0,0 @@
|
|||||||
/**
|
|
||||||
* The native-Obsidian page-file format (design: docs/backlog/git-sync-thin-meta.md).
|
|
||||||
* A page file is CLEAN markdown with a minimal YAML frontmatter carrying ONLY the
|
|
||||||
* page's durable identity:
|
|
||||||
*
|
|
||||||
* ---
|
|
||||||
* gitmost_id: 019ef6fc-2638-7ce1-9ce3-2756ce038480
|
|
||||||
* ---
|
|
||||||
* <clean markdown body>
|
|
||||||
*
|
|
||||||
* Everything else is derived (title = filename, parentPageId = enclosing folder,
|
|
||||||
* spaceId = the vault, updatedAt = git). `gitmost_id` (a Docmost pageId) is the
|
|
||||||
* only non-derivable bit and travels WITH the file so identity survives any move,
|
|
||||||
* even one git's rename detection misses. Third-party editors (Obsidian, …) see
|
|
||||||
* clean markdown; the frontmatter is hidden in their preview.
|
|
||||||
*
|
|
||||||
* No backward-compat with the old `docmost:meta` format: vaults are a cache, wiped
|
|
||||||
* and rebuilt native. A file WITHOUT a `gitmost_id` frontmatter is an un-tracked
|
|
||||||
* (e.g. hand-written) file -> the caller ADOPTS it (creates a page, writes the id).
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* The frontmatter key carrying the Docmost pageId. NAMESPACED (not a bare `id`)
|
|
||||||
* so it never collides with a user's own frontmatter fields.
|
|
||||||
*/
|
|
||||||
export declare const ID_KEY = "gitmost_id";
|
|
||||||
/**
|
|
||||||
* Parse a page file into its identity (`id`) and clean markdown `body`. Tolerant:
|
|
||||||
* a file with no frontmatter (a hand-written third-party file) returns `id: null`
|
|
||||||
* and the whole text as the body — the caller then ADOPTS it (creates a page,
|
|
||||||
* writes the id back).
|
|
||||||
*
|
|
||||||
* KNOWN LIMITATION (phase 4 — adoption, see docs/backlog/git-sync-thin-meta.md):
|
|
||||||
* a leading frontmatter block is stripped from `body` even when it carries NO
|
|
||||||
* `gitmost_id` but DOES carry the user's own Obsidian properties (`tags:` etc.).
|
|
||||||
* On adoption those fields are not yet round-tripped — `serializePageFile`
|
|
||||||
* write-back persists only `gitmost_id`. Preserving arbitrary user frontmatter
|
|
||||||
* across the Docmost round-trip (BOTH adoption write-back AND the next pull's
|
|
||||||
* re-serialize) is deferred to the adoption phase; until then, do NOT roll the
|
|
||||||
* native format onto a real Obsidian vault whose notes carry properties.
|
|
||||||
*/
|
|
||||||
export declare function parsePageFile(full: string): {
|
|
||||||
id: string | null;
|
|
||||||
body: string;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Serialize a page into the thin format: `id` frontmatter + a blank line + the
|
|
||||||
* clean body + a trailing newline. Deterministic so an unchanged page re-syncs to
|
|
||||||
* byte-identical output (no churn — the loop-guard relies on it).
|
|
||||||
*/
|
|
||||||
export declare function serializePageFile(id: string, body: string): string;
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/**
|
|
||||||
* The native-Obsidian page-file format (design: docs/backlog/git-sync-thin-meta.md).
|
|
||||||
* A page file is CLEAN markdown with a minimal YAML frontmatter carrying ONLY the
|
|
||||||
* page's durable identity:
|
|
||||||
*
|
|
||||||
* ---
|
|
||||||
* gitmost_id: 019ef6fc-2638-7ce1-9ce3-2756ce038480
|
|
||||||
* ---
|
|
||||||
* <clean markdown body>
|
|
||||||
*
|
|
||||||
* Everything else is derived (title = filename, parentPageId = enclosing folder,
|
|
||||||
* spaceId = the vault, updatedAt = git). `gitmost_id` (a Docmost pageId) is the
|
|
||||||
* only non-derivable bit and travels WITH the file so identity survives any move,
|
|
||||||
* even one git's rename detection misses. Third-party editors (Obsidian, …) see
|
|
||||||
* clean markdown; the frontmatter is hidden in their preview.
|
|
||||||
*
|
|
||||||
* No backward-compat with the old `docmost:meta` format: vaults are a cache, wiped
|
|
||||||
* and rebuilt native. A file WITHOUT a `gitmost_id` frontmatter is an un-tracked
|
|
||||||
* (e.g. hand-written) file -> the caller ADOPTS it (creates a page, writes the id).
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* The frontmatter key carrying the Docmost pageId. NAMESPACED (not a bare `id`)
|
|
||||||
* so it never collides with a user's own frontmatter fields.
|
|
||||||
*/
|
|
||||||
export const ID_KEY = "gitmost_id";
|
|
||||||
/** Leading YAML frontmatter block: `---\n…\n---` at the very start of the file. */
|
|
||||||
const FRONTMATTER_RE = /^?---\n([\s\S]*?)\n---\n?/;
|
|
||||||
/** The top-level `<ID_KEY>: <value>` line inside the frontmatter (quotes optional). */
|
|
||||||
function readIdFromYaml(yaml) {
|
|
||||||
const re = new RegExp(`^${ID_KEY}:\\s*(.+?)\\s*$`);
|
|
||||||
for (const line of yaml.split("\n")) {
|
|
||||||
const m = line.match(re);
|
|
||||||
if (m) {
|
|
||||||
const v = m[1].trim().replace(/^["']|["']$/g, "");
|
|
||||||
return v === "" ? null : v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Parse a page file into its identity (`id`) and clean markdown `body`. Tolerant:
|
|
||||||
* a file with no frontmatter (a hand-written third-party file) returns `id: null`
|
|
||||||
* and the whole text as the body — the caller then ADOPTS it (creates a page,
|
|
||||||
* writes the id back).
|
|
||||||
*
|
|
||||||
* KNOWN LIMITATION (phase 4 — adoption, see docs/backlog/git-sync-thin-meta.md):
|
|
||||||
* a leading frontmatter block is stripped from `body` even when it carries NO
|
|
||||||
* `gitmost_id` but DOES carry the user's own Obsidian properties (`tags:` etc.).
|
|
||||||
* On adoption those fields are not yet round-tripped — `serializePageFile`
|
|
||||||
* write-back persists only `gitmost_id`. Preserving arbitrary user frontmatter
|
|
||||||
* across the Docmost round-trip (BOTH adoption write-back AND the next pull's
|
|
||||||
* re-serialize) is deferred to the adoption phase; until then, do NOT roll the
|
|
||||||
* native format onto a real Obsidian vault whose notes carry properties.
|
|
||||||
*/
|
|
||||||
export function parsePageFile(full) {
|
|
||||||
const text = (full ?? "").replace(/\r\n/g, "\n");
|
|
||||||
// Native format: a `gitmost_id` YAML frontmatter. Anything else (no frontmatter,
|
|
||||||
// or frontmatter without the key) is an un-tracked file -> adopt.
|
|
||||||
const fm = text.match(FRONTMATTER_RE);
|
|
||||||
if (fm) {
|
|
||||||
return { id: readIdFromYaml(fm[1]), body: text.slice(fm[0].length).trim() };
|
|
||||||
}
|
|
||||||
return { id: null, body: text.trim() };
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Serialize a page into the thin format: `id` frontmatter + a blank line + the
|
|
||||||
* clean body + a trailing newline. Deterministic so an unchanged page re-syncs to
|
|
||||||
* byte-identical output (no churn — the loop-guard relies on it).
|
|
||||||
*/
|
|
||||||
export function serializePageFile(id, body) {
|
|
||||||
return `---\n${ID_KEY}: ${id}\n---\n\n${body.trim()}\n`;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user