Refactor: separate entrypoint and main,

Fix: readlng binary file
This commit is contained in:
vorotamoroz
2026-03-12 19:41:10 +09:00
parent d4aedf59f3
commit 822d957976
8 changed files with 95 additions and 37 deletions
-3
View File
@@ -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. - `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. - `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 ## Use Cases
### 1. Bootstrap a new headless vault ### 1. Bootstrap a new headless vault
@@ -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
View 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 -21
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env node
/** /**
* Self-hosted LiveSync CLI * Self-hosted LiveSync CLI
* Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian * 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 fs from "fs/promises";
import * as path from "path"; import * as path from "path";
import { pathToFileURL } from "node:url";
import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub"; import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub";
import { LiveSyncBaseCore } from "../../LiveSyncBaseCore"; import { LiveSyncBaseCore } from "../../LiveSyncBaseCore";
import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules"; import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules";
@@ -201,7 +199,7 @@ async function createDefaultSettingsFile(options: CLIOptions) {
console.log(`[Done] Created settings file: ${targetPath}`); console.log(`[Done] Created settings file: ${targetPath}`);
} }
async function main() { export async function main() {
const options = parseArgs(); const options = parseArgs();
const avoidStdoutNoise = const avoidStdoutNoise =
options.command === "cat" || options.command === "cat" ||
@@ -373,21 +371,3 @@ async function main() {
process.exit(1); 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 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() { sanitise_cat_stdout() {
sed '/^\[CLIWatchAdapter\] File watching is not enabled in CLI version$/d' 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_SYNC="e2e/sync-info.md"
TARGET_PUSH="e2e/pushed-from-a.md" TARGET_PUSH="e2e/pushed-from-a.md"
TARGET_PUT="e2e/put-from-a.md" TARGET_PUT="e2e/put-from-a.md"
TARGET_PUSH_BINARY="e2e/pushed-from-a.bin"
TARGET_CONFLICT="e2e/conflict.md" TARGET_CONFLICT="e2e/conflict.md"
echo "[CASE] A puts and A can get info" 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 printf 'put-content-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_PUT" >/dev/null
sync_both sync_both
run_cli_b pull "$TARGET_PUSH" "$PULL_DST" >/dev/null run_cli_b pull "$TARGET_PUSH" "$PULL_DST" >/dev/null
if ! cmp -s "$PUSH_SRC" "$PULL_DST"; then assert_files_equal "$PUSH_SRC" "$PULL_DST" "B pull result does not match pushed source"
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
CAT_B_PUT="$(run_cli_b cat "$TARGET_PUT" | sanitise_cat_stdout)" 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" 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 "[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" echo "[CASE] A removes, both sync, and B can no longer cat"
run_cli_a rm "$TARGET_PUT" >/dev/null run_cli_a rm "$TARGET_PUT" >/dev/null
sync_both sync_both
+2 -2
View File
@@ -33,7 +33,7 @@ export default defineConfig({
minify: false, minify: false,
rollupOptions: { rollupOptions: {
input: { input: {
index: path.resolve(__dirname, "main.ts"), index: path.resolve(__dirname, "entrypoint.ts"),
}, },
external: (id) => { external: (id) => {
if (defaultExternal.includes(id)) return true; if (defaultExternal.includes(id)) return true;
@@ -48,7 +48,7 @@ export default defineConfig({
}, },
}, },
lib: { lib: {
entry: path.resolve(__dirname, "main.ts"), entry: path.resolve(__dirname, "entrypoint.ts"),
formats: ["cjs"], formats: ["cjs"],
fileName: "index", fileName: "index",
}, },
+1 -1
Submodule src/lib updated: 4346ead9c8...d94c9b3ed7
+20 -2
View File
@@ -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. 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). 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 ### New something
- Add `self-hosted-livesync-cli` to `src/apps/cli` as a headless, and a dedicated version. - Add `self-hosted-livesync-cli` to `src/apps/cli` as a headless, and a dedicated version.
## -- Unreleased -- ## Unnamed 11th March, 2026
11th March, 2026 11th March, 2026