mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-04-26 10:58:34 +00:00
Refactor: separate entrypoint and main,
Fix: readlng binary file
This commit is contained in:
@@ -187,9 +187,6 @@ TODO: Conflict and resolution checks for real local databases.
|
||||
- `serve`: Start CLI in server mode, exposing REST APIs for remote, and batch operations.
|
||||
- `cause-conflicted <vaultPath>`: Mark a file as conflicted without changing its content, to trigger conflict resolution in Obsidian.
|
||||
|
||||
## Current Limitations and known issues
|
||||
- Binary files are not supported yet (it seems... but I haven't tested this yet).
|
||||
|
||||
## Use Cases
|
||||
|
||||
### 1. Bootstrap a new headless vault
|
||||
|
||||
40
src/apps/cli/adapters/NodeStorageAdapter.unit.spec.ts
Normal file
40
src/apps/cli/adapters/NodeStorageAdapter.unit.spec.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { NodeStorageAdapter } from "./NodeStorageAdapter";
|
||||
|
||||
describe("NodeStorageAdapter binary I/O", () => {
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
async function createAdapter() {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "livesync-cli-node-storage-"));
|
||||
tempDirs.push(tempDir);
|
||||
return new NodeStorageAdapter(tempDir);
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
it("writes and reads binary data without corruption", async () => {
|
||||
const adapter = await createAdapter();
|
||||
const expected = Uint8Array.from([0x00, 0x7f, 0x80, 0xff, 0x42]);
|
||||
|
||||
await adapter.writeBinary("files/blob.bin", expected.buffer.slice(0));
|
||||
const result = await adapter.readBinary("files/blob.bin");
|
||||
|
||||
expect(Array.from(new Uint8Array(result))).toEqual(Array.from(expected));
|
||||
});
|
||||
|
||||
it("returns an ArrayBuffer with the exact file length", async () => {
|
||||
const adapter = await createAdapter();
|
||||
const expected = Uint8Array.from([0x10, 0x20, 0x30]);
|
||||
|
||||
await adapter.writeBinary("files/small.bin", expected.buffer.slice(0));
|
||||
const result = await adapter.readBinary("files/small.bin");
|
||||
|
||||
expect(result.byteLength).toBe(expected.byteLength);
|
||||
expect(Array.from(new Uint8Array(result))).toEqual([0x10, 0x20, 0x30]);
|
||||
});
|
||||
});
|
||||
7
src/apps/cli/entrypoint.ts
Normal file
7
src/apps/cli/entrypoint.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
import { main } from "./main";
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`[Fatal Error]`, error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Self-hosted LiveSync CLI
|
||||
* Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian
|
||||
@@ -22,7 +21,6 @@ if (!("localStorage" in globalThis)) {
|
||||
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub";
|
||||
import { LiveSyncBaseCore } from "../../LiveSyncBaseCore";
|
||||
import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules";
|
||||
@@ -201,7 +199,7 @@ async function createDefaultSettingsFile(options: CLIOptions) {
|
||||
console.log(`[Done] Created settings file: ${targetPath}`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
export async function main() {
|
||||
const options = parseArgs();
|
||||
const avoidStdoutNoise =
|
||||
options.command === "cat" ||
|
||||
@@ -373,21 +371,3 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run main only when invoked as the entrypoint, not when imported by tests.
|
||||
const isEntryPoint = (() => {
|
||||
const argv1 = process.argv[1];
|
||||
if (!argv1) return false;
|
||||
try {
|
||||
return import.meta.url === pathToFileURL(argv1).href;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
if (isEntryPoint) {
|
||||
main().catch((error) => {
|
||||
console.error(`[Fatal Error]`, error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -134,6 +134,18 @@ assert_command_fails() {
|
||||
fi
|
||||
}
|
||||
|
||||
assert_files_equal() {
|
||||
local expected_file="$1"
|
||||
local actual_file="$2"
|
||||
local message="$3"
|
||||
if ! cmp -s "$expected_file" "$actual_file"; then
|
||||
echo "[FAIL] $message" >&2
|
||||
echo "[FAIL] expected sha256: $(sha256sum "$expected_file" | awk '{print $1}')" >&2
|
||||
echo "[FAIL] actual sha256: $(sha256sum "$actual_file" | awk '{print $1}')" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
sanitise_cat_stdout() {
|
||||
sed '/^\[CLIWatchAdapter\] File watching is not enabled in CLI version$/d'
|
||||
}
|
||||
@@ -295,6 +307,7 @@ TARGET_A_ONLY="e2e/a-only-info.md"
|
||||
TARGET_SYNC="e2e/sync-info.md"
|
||||
TARGET_PUSH="e2e/pushed-from-a.md"
|
||||
TARGET_PUT="e2e/put-from-a.md"
|
||||
TARGET_PUSH_BINARY="e2e/pushed-from-a.bin"
|
||||
TARGET_CONFLICT="e2e/conflict.md"
|
||||
|
||||
echo "[CASE] A puts and A can get info"
|
||||
@@ -318,18 +331,21 @@ run_cli_a push "$PUSH_SRC" "$TARGET_PUSH" >/dev/null
|
||||
printf 'put-content-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_PUT" >/dev/null
|
||||
sync_both
|
||||
run_cli_b pull "$TARGET_PUSH" "$PULL_DST" >/dev/null
|
||||
if ! cmp -s "$PUSH_SRC" "$PULL_DST"; then
|
||||
echo "[FAIL] B pull result does not match pushed source" >&2
|
||||
echo "--- source ---" >&2
|
||||
cat "$PUSH_SRC" >&2
|
||||
echo "--- pulled ---" >&2
|
||||
cat "$PULL_DST" >&2
|
||||
exit 1
|
||||
fi
|
||||
assert_files_equal "$PUSH_SRC" "$PULL_DST" "B pull result does not match pushed source"
|
||||
CAT_B_PUT="$(run_cli_b cat "$TARGET_PUT" | sanitise_cat_stdout)"
|
||||
assert_equal "put-content-$DB_SUFFIX" "$CAT_B_PUT" "B cat should return A put content"
|
||||
echo "[PASS] push/pull and put/cat across vaults"
|
||||
|
||||
echo "[CASE] A pushes binary, both sync, and B can pull identical bytes"
|
||||
PUSH_BINARY_SRC="$WORK_DIR/push-source.bin"
|
||||
PULL_BINARY_DST="$WORK_DIR/pull-destination.bin"
|
||||
head -c 4096 /dev/urandom > "$PUSH_BINARY_SRC"
|
||||
run_cli_a push "$PUSH_BINARY_SRC" "$TARGET_PUSH_BINARY" >/dev/null
|
||||
sync_both
|
||||
run_cli_b pull "$TARGET_PUSH_BINARY" "$PULL_BINARY_DST" >/dev/null
|
||||
assert_files_equal "$PUSH_BINARY_SRC" "$PULL_BINARY_DST" "B pull result does not match pushed binary source"
|
||||
echo "[PASS] binary push/pull across vaults"
|
||||
|
||||
echo "[CASE] A removes, both sync, and B can no longer cat"
|
||||
run_cli_a rm "$TARGET_PUT" >/dev/null
|
||||
sync_both
|
||||
|
||||
@@ -33,7 +33,7 @@ export default defineConfig({
|
||||
minify: false,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: path.resolve(__dirname, "main.ts"),
|
||||
index: path.resolve(__dirname, "entrypoint.ts"),
|
||||
},
|
||||
external: (id) => {
|
||||
if (defaultExternal.includes(id)) return true;
|
||||
@@ -48,7 +48,7 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, "main.ts"),
|
||||
entry: path.resolve(__dirname, "entrypoint.ts"),
|
||||
formats: ["cjs"],
|
||||
fileName: "index",
|
||||
},
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: 4346ead9c8...d94c9b3ed7
22
updates.md
22
updates.md
@@ -3,7 +3,25 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||
|
||||
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
|
||||
|
||||
## -- Unreleased2 --
|
||||
## Unnamed 12th March, 2026
|
||||
|
||||
12th March, 2026
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Journal Sync had not been working on some timing, due to a compatibility issue (for a long time).
|
||||
|
||||
### Internal behaviour change (or fix)
|
||||
|
||||
- Journal Replicator now yields true after the replication is done.
|
||||
|
||||
### CLI
|
||||
|
||||
- Add more tests.
|
||||
- Object Storage support has also been confirmed (and fixed) in CLI.
|
||||
- Yes, we have finally managed to 'get one file'.
|
||||
|
||||
## Unnamed 11th March, 2026
|
||||
|
||||
11th March, 2026 (second commit).
|
||||
|
||||
@@ -23,7 +41,7 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid
|
||||
### New something
|
||||
- Add `self-hosted-livesync-cli` to `src/apps/cli` as a headless, and a dedicated version.
|
||||
|
||||
## -- Unreleased --
|
||||
## Unnamed 11th March, 2026
|
||||
|
||||
11th March, 2026
|
||||
|
||||
|
||||
Reference in New Issue
Block a user