Fix and apply editorconfig

This commit is contained in:
Andras Schmelczer 2025-12-07 14:44:42 +00:00
parent 4150eb2720
commit ce31969a44
11 changed files with 423 additions and 418 deletions

View file

@ -11,5 +11,6 @@ indent_style = space
indent_size = 4 indent_size = 4
tab_width = 4 tab_width = 4
[*.{yml,yaml}] [*.{yml,yaml,md}]
indent_size = 2 indent_size = 2
tab_width = 2

View file

@ -22,7 +22,7 @@ jobs:
with: with:
node-version: "22.x" node-version: "22.x"
check-latest: true check-latest: true
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:

View file

@ -125,37 +125,37 @@ sequenceDiagram
``` ```
┌─────────┐ ┌─────────┐
│ Client │ │ Client │
└───┬────┘ └───┬─-───┘
│ 1. Detect file change │ 1. Detect file change
├─► 2. Read file content ├─► 2. Read file content
├─► 3. Create upload message ├─► 3. Create upload message
│ { │ {
│ type: "upload_file", │ type: "upload_file",
│ path: "notes/daily.md", │ path: "notes/daily.md",
│ content: "...", │ content: "...",
│ version: 42, │ version: 42,
│ timestamp: "2024-01-01T12:00:00Z" │ timestamp: "2024-01-01T12:00:00Z"
│ } │ }
┌─────────┐ ┌─────────┐
│ Server │ │ Server │
└───┬────┘ └───┬────-
│ 4. Validate message │ 4. Validate message
├─► 5. Check permissions ├─► 5. Check permissions
├─► 6. Apply OT (if conflicts) ├─► 6. Apply OT (if conflicts)
├─► 7. Store in database ├─► 7. Store in database
├─► 8. Update version ├─► 8. Update version
├─► 9. Broadcast to clients ├─► 9. Broadcast to clients
└─► 10. Send ACK to uploader └─► 10. Send ACK to uploader
``` ```
### Download ### Download
@ -163,36 +163,36 @@ sequenceDiagram
``` ```
┌─────────┐ ┌─────────┐
│ Server │ │ Server │
└───┬────┘ └───┬─-───┘
│ 1. File updated by another client │ 1. File updated by another client
├─► 2. Broadcast notification ├─► 2. Broadcast notification
│ { │ {
│ type: "file_updated", │ type: "file_updated",
│ path: "notes/daily.md", │ path: "notes/daily.md",
│ version: 43 │ version: 43
│ } │ }
┌─────────┐ ┌─────────┐
│ Client │ │ Client │
└───┬────┘ └───┬─-───┘
│ 3. Receive notification │ 3. Receive notification
├─► 4. Request file download ├─► 4. Request file download
│ { │ {
│ type: "download_file", │ type: "download_file",
│ path: "notes/daily.md", │ path: "notes/daily.md",
│ version: 43 │ version: 43
│ } │ }
┌─────────┐ ┌─────────┐
│ Server │ │ Server │
└───┬────┘ └───┬─=───┘
│ 5. Retrieve from database │ 5. Retrieve from database
└─► 6. Send file content └─► 6. Send file content
{ {
type: "file_content", type: "file_content",
path: "notes/daily.md", path: "notes/daily.md",
@ -201,9 +201,9 @@ sequenceDiagram
} }
┌─────────┐ ┌─────────┐
│ Client │ │ Client │
└────┬─── └───-─┬───┘
│ 7. Write to filesystem │ 7. Write to filesystem
└─► 8. Update local metadata └─► 8. Update local metadata
@ -215,30 +215,30 @@ sequenceDiagram
┌─────────┐ ┌─────────┐
│ Client │ │ Client │
└────┬────┘ └────┬────┘
│ 1. File deleted locally │ 1. File deleted locally
├─► 2. Send delete message ├─► 2. Send delete message
│ { │ {
│ type: "delete_file", │ type: "delete_file",
│ path: "notes/old.md" │ path: "notes/old.md"
│ } │ }
┌─────────┐ ┌─────────┐
│ Server │ │ Server │
└────┬────┘ └────┬────┘
│ 3. Mark as deleted in DB │ 3. Mark as deleted in DB
│ (soft delete for history) │ (soft delete for history)
├─► 4. Broadcast deletion ├─► 4. Broadcast deletion
└─► 5. ACK to sender └─► 5. ACK to sender
┌─────────┐ ┌─────────┐
│ Other │ │ Other │
│ Clients │ │ Clients │
└────┬────┘ └────┬────┘
│ 6. Delete local file │ 6. Delete local file
└─► 7. Update metadata └─► 7. Update metadata
@ -252,32 +252,32 @@ sequenceDiagram
Time → Time →
Client A Server Client B Client A Server Client B
│ │ │ │ │ │
│ Edit file v10 │ │ │ Edit file v10 │ │
│ "Add line A" │ │ Edit file v10 │ "Add line A" │ │ Edit file v10
│ │ │ "Add line B" │ │ │ "Add line B"
│ │ │ │ │ │
├─── Upload @ t1 ─────────►│ │ ├─── Upload @ t1 ─────────►│ │
│ │◄────── Upload @ t2 ────────┤ │ │◄────── Upload @ t2 ────────┤
│ │ │ │ │ │
│ │ 1. Receive both edits │ │ │ 1. Receive both edits │
│ │ (based on v10) │ │ │ (based on v10) │
│ │ │ │ │ │
│ │ 2. Apply first edit │ │ │ 2. Apply first edit │
│ │ → v11 (line A added) │ │ │ → v11 (line A added) │
│ │ │ │ │ │
│ │ 3. Transform second edit │ │ │ 3. Transform second edit │
│ │ against first │ │ │ against first │
│ │ │ │ │ │
│ │ 4. Apply transformed edit │ │ │ 4. Apply transformed edit │
│ │ → v12 (both lines) │ │ │ → v12 (both lines) │
│ │ │ │ │ │
│◄──── v12 content ────────┤ │ │◄──── v12 content ────────┤ │
│ ├───── v12 content ─────────►│ │ ├───── v12 content ─────────►│
│ │ │ │ │ │
│ Apply v12 │ │ Apply v12 │ Apply v12 │ │ Apply v12
│ (has both lines) │ │ (has both lines) │ (has both lines) │ │ (has both lines)
│ │ │ │ │ │
``` ```
### Conflict Resolution Steps ### Conflict Resolution Steps
@ -361,11 +361,11 @@ VALUES (?, ?, ?);
```json ```json
{ {
"type": "upload_file", "type": "upload_file",
"path": "notes/example.md", "path": "notes/example.md",
"content": "File content here...", "content": "File content here...",
"base_version": 10, "base_version": 10,
"timestamp": "2024-01-01T12:00:00Z" "timestamp": "2024-01-01T12:00:00Z"
} }
``` ```
@ -373,8 +373,8 @@ VALUES (?, ?, ?);
```json ```json
{ {
"type": "download_file", "type": "download_file",
"path": "notes/example.md" "path": "notes/example.md"
} }
``` ```
@ -382,8 +382,8 @@ VALUES (?, ?, ?);
```json ```json
{ {
"type": "delete_file", "type": "delete_file",
"path": "notes/old.md" "path": "notes/old.md"
} }
``` ```
@ -391,8 +391,8 @@ VALUES (?, ?, ?);
```json ```json
{ {
"type": "list_files", "type": "list_files",
"since_version": 0 "since_version": 0
} }
``` ```
@ -402,11 +402,11 @@ VALUES (?, ?, ?);
```json ```json
{ {
"type": "file_updated", "type": "file_updated",
"path": "notes/example.md", "path": "notes/example.md",
"version": 11, "version": 11,
"size": 1024, "size": 1024,
"hash": "abc123..." "hash": "abc123..."
} }
``` ```
@ -414,10 +414,10 @@ VALUES (?, ?, ?);
```json ```json
{ {
"type": "file_content", "type": "file_content",
"path": "notes/example.md", "path": "notes/example.md",
"content": "Updated content...", "content": "Updated content...",
"version": 11 "version": 11
} }
``` ```
@ -425,9 +425,9 @@ VALUES (?, ?, ?);
```json ```json
{ {
"type": "file_deleted", "type": "file_deleted",
"path": "notes/old.md", "path": "notes/old.md",
"version": 12 "version": 12
} }
``` ```
@ -435,9 +435,9 @@ VALUES (?, ?, ?);
```json ```json
{ {
"type": "sync_complete", "type": "sync_complete",
"total_files": 150, "total_files": 150,
"current_version": 200 "current_version": 200
} }
``` ```
@ -445,9 +445,9 @@ VALUES (?, ?, ?);
```json ```json
{ {
"type": "error", "type": "error",
"message": "File too large", "message": "File too large",
"code": "FILE_TOO_LARGE" "code": "FILE_TOO_LARGE"
} }
``` ```

View file

@ -11,10 +11,10 @@ Central sync server with multiple clients. High-level architecture and design de
│ Obsidian Plugin │ Obsidian Plugin │ CLI Client │ │ Obsidian Plugin │ Obsidian Plugin │ CLI Client │
│ (User A - Device1) │ (User A - Device2│ (Server/Backup) │ │ (User A - Device1) │ (User A - Device2│ (Server/Backup) │
└──────────┬──────────┴─────────┬─────────┴──────────┬────────┘ └──────────┬──────────┴─────────┬─────────┴──────────┬────────┘
│ │ │ │ │ │
│ WebSocket │ WebSocket │ WebSocket │ WebSocket │ WebSocket │ WebSocket
│ │ │ │ │ │
└────────────────────┼────────────────────┘ └────────────────────┼────────────────────┘
┌───────────▼───────────┐ ┌───────────▼───────────┐
│ Sync Server │ │ Sync Server │

View file

@ -243,9 +243,9 @@ users:
2. Client sends authentication message: 2. Client sends authentication message:
```json ```json
{ {
"type": "auth", "type": "auth",
"token": "user-token", "token": "user-token",
"vault": "vault-name" "vault": "vault-name"
} }
``` ```
3. Server validates: 3. Server validates:

View file

@ -4,7 +4,7 @@
"module": "ESNext", "module": "ESNext",
"lib": [ "lib": [
"DOM", // to get `fetch` & `WebSocket` "DOM", // to get `fetch` & `WebSocket`
"ES2024" "ES2024"
], ],
"outDir": "./dist", "outDir": "./dist",
"rootDir": "./src", "rootDir": "./src",
@ -18,5 +18,7 @@
"declarationMap": true, "declarationMap": true,
"sourceMap": true "sourceMap": true
}, },
"exclude": ["dist"] "exclude": [
"dist"
]
} }

View file

@ -2,32 +2,32 @@ const path = require("path");
const webpack = require("webpack"); const webpack = require("webpack");
module.exports = { module.exports = {
entry: { entry: {
cli: "./src/cli.ts", cli: "./src/cli.ts",
healthcheck: "./src/healthcheck.ts" healthcheck: "./src/healthcheck.ts"
}, },
target: "node", target: "node",
mode: "production", mode: "production",
optimization: { optimization: {
minimize: false minimize: false
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.ts$/, test: /\.ts$/,
use: "ts-loader" use: "ts-loader"
} }
] ]
}, },
resolve: { resolve: {
extensions: [".ts", ".js"] extensions: [".ts", ".js"]
}, },
output: { output: {
globalObject: "this", globalObject: "this",
filename: "[name].js", filename: "[name].js",
path: path.resolve(__dirname, "dist") path: path.resolve(__dirname, "dist")
}, },
plugins: [ plugins: [
new webpack.BannerPlugin({ banner: "#!/usr/bin/env node", raw: true }) new webpack.BannerPlugin({ banner: "#!/usr/bin/env node", raw: true })
] ]
}; };

View file

@ -1,9 +1,9 @@
import type { import type {
MarkdownView, MarkdownView,
Editor, Editor,
MarkdownFileInfo, MarkdownFileInfo,
TAbstractFile, TAbstractFile,
WorkspaceLeaf WorkspaceLeaf
} from "obsidian"; } from "obsidian";
import { Notice, Platform, Plugin, TFile } from "obsidian"; import { Notice, Platform, Plugin, TFile } from "obsidian";
import "../manifest.json"; import "../manifest.json";
@ -12,19 +12,19 @@ import { StatusBar } from "./views/status-bar/status-bar";
import { LogsView } from "./views/logs/logs-view"; import { LogsView } from "./views/logs/logs-view";
import { StatusDescription } from "./views/status-description/status-description"; import { StatusDescription } from "./views/status-description/status-description";
import { import {
SyncClient, SyncClient,
rateLimit, rateLimit,
DEFAULT_SETTINGS, DEFAULT_SETTINGS,
Logger, Logger,
debugging debugging
} from "sync-client"; } from "sync-client";
import { ObsidianFileSystemOperations } from "./obsidian-file-system"; import { ObsidianFileSystemOperations } from "./obsidian-file-system";
import { SyncSettingsTab } from "./views/settings/settings-tab"; import { SyncSettingsTab } from "./views/settings/settings-tab";
import { EditorStatusDisplayManager } from "./views/editor-status-display-manager/editor-status-display-manager"; import { EditorStatusDisplayManager } from "./views/editor-status-display-manager/editor-status-display-manager";
import { remoteCursorsTheme } from "./views/cursors/remote-cursor-theme"; import { remoteCursorsTheme } from "./views/cursors/remote-cursor-theme";
import { import {
remoteCursorsPlugin, remoteCursorsPlugin,
RemoteCursorsPluginValue RemoteCursorsPluginValue
} from "./views/cursors/remote-cursors-plugin"; } from "./views/cursors/remote-cursors-plugin";
import { LocalCursorUpdateListener } from "./views/cursors/local-cursor-update-listener"; import { LocalCursorUpdateListener } from "./views/cursors/local-cursor-update-listener";
import { renderCursorsInFileExplorer } from "./views/cursors/file-explorer"; import { renderCursorsInFileExplorer } from "./views/cursors/file-explorer";
@ -33,252 +33,252 @@ const MIN_WAIT_BETWEEN_UPDATES_IN_MS = 250;
const IS_DEBUG_BUILD = process.env.NODE_ENV === "development"; const IS_DEBUG_BUILD = process.env.NODE_ENV === "development";
export default class VaultLinkPlugin extends Plugin { export default class VaultLinkPlugin extends Plugin {
private readonly rateLimitedUpdatesPerFile = new Map< private readonly rateLimitedUpdatesPerFile = new Map<
string, string,
() => Promise<unknown> () => Promise<unknown>
>(); >();
private readonly syncClient: SyncClient | undefined; private readonly syncClient: SyncClient | undefined;
private settingsTab: SyncSettingsTab | undefined; private settingsTab: SyncSettingsTab | undefined;
public async onload(): Promise<void> { public async onload(): Promise<void> {
this.app.workspace.onLayoutReady(async () => { this.app.workspace.onLayoutReady(async () => {
// eslint-disable-next-line // eslint-disable-next-line
if ((globalThis as any).VAULT_LINK_RUNNING_INSTANCE) { if ((globalThis as any).VAULT_LINK_RUNNING_INSTANCE) {
new Notice( new Notice(
"Another instance of VaultLink is already running. Please disable the duplicate instance." "Another instance of VaultLink is already running. Please disable the duplicate instance."
); );
throw new Error("VaultLink instance already running"); throw new Error("VaultLink instance already running");
} }
// eslint-disable-next-line // eslint-disable-next-line
(globalThis as any).VAULT_LINK_RUNNING_INSTANCE = this; (globalThis as any).VAULT_LINK_RUNNING_INSTANCE = this;
const client = await this.createSyncClient(); const client = await this.createSyncClient();
this.registerObsidianExtensions(client); this.registerObsidianExtensions(client);
this.registerEditorEvents(client); this.registerEditorEvents(client);
this.register(async () => { this.register(async () => {
await client.waitUntilFinished(); await client.waitUntilFinished();
await client.destroy(); await client.destroy();
}); });
await client.start(); await client.start();
}); });
} }
public onUserEnable(): void { public onUserEnable(): void {
new Notice( new Notice(
"VaultLink has been enabled, check out the docs for tips on getting started!" "VaultLink has been enabled, check out the docs for tips on getting started!"
); );
void this.activateView(HistoryView.TYPE).catch((e: unknown) => { void this.activateView(HistoryView.TYPE).catch((e: unknown) => {
this.syncClient?.logger.error( this.syncClient?.logger.error(
`Failed to open history view on enable: ${e}` `Failed to open history view on enable: ${e}`
); );
}); });
void this.activateView(LogsView.TYPE).catch((e: unknown) => { void this.activateView(LogsView.TYPE).catch((e: unknown) => {
this.syncClient?.logger.error( this.syncClient?.logger.error(
`Failed to open logs view on enable: ${e}` `Failed to open logs view on enable: ${e}`
); );
}); });
this.openSettings(); this.openSettings();
} }
public openSettings(): void { public openSettings(): void {
// eslint-disable-next-line // eslint-disable-next-line
(this.app as any).setting.open(); // this is undocumented (this.app as any).setting.open(); // this is undocumented
// eslint-disable-next-line // eslint-disable-next-line
(this.app as any).setting.openTab(this.settingsTab); // this is undocumented (this.app as any).setting.openTab(this.settingsTab); // this is undocumented
} }
public closeSettings(): void { public closeSettings(): void {
// eslint-disable-next-line // eslint-disable-next-line
(this.app as any).setting.close(); // this is undocumented (this.app as any).setting.close(); // this is undocumented
} }
public async activateView(type: string): Promise<void> { public async activateView(type: string): Promise<void> {
const { workspace } = this.app; const { workspace } = this.app;
let leaf: WorkspaceLeaf | null = null; let leaf: WorkspaceLeaf | null = null;
const leaves = workspace.getLeavesOfType(type); const leaves = workspace.getLeavesOfType(type);
if (leaves.length > 0) { if (leaves.length > 0) {
[leaf] = leaves; [leaf] = leaves;
} else { } else {
leaf = workspace.getRightLeaf(false); leaf = workspace.getRightLeaf(false);
await leaf?.setViewState({ type: type, active: true }); await leaf?.setViewState({ type: type, active: true });
} }
if (leaf) { if (leaf) {
await workspace.revealLeaf(leaf); await workspace.revealLeaf(leaf);
} }
} }
private async createSyncClient(): Promise<SyncClient> { private async createSyncClient(): Promise<SyncClient> {
DEFAULT_SETTINGS.ignorePatterns.push( DEFAULT_SETTINGS.ignorePatterns.push(
".obsidian/**", ".obsidian/**",
".git/**", ".git/**",
".trash/**", ".trash/**",
"**/.DS_Store" "**/.DS_Store"
); );
const client = await SyncClient.create({ const client = await SyncClient.create({
fs: new ObsidianFileSystemOperations( fs: new ObsidianFileSystemOperations(
this.app.vault, this.app.vault,
this.app.workspace this.app.workspace
), ),
persistence: { persistence: {
load: this.loadData.bind(this), load: this.loadData.bind(this),
save: this.saveData.bind(this) save: this.saveData.bind(this)
}, },
nativeLineEndings: Platform.isWin ? "\r\n" : "\n", nativeLineEndings: Platform.isWin ? "\r\n" : "\n",
...(IS_DEBUG_BUILD ...(IS_DEBUG_BUILD
? { ? {
fetch: debugging.slowFetchFactory(1), fetch: debugging.slowFetchFactory(1),
webSocket: debugging.slowWebSocketFactory( webSocket: debugging.slowWebSocketFactory(
1, 1,
new Logger() new Logger()
) )
} }
: {}) : {})
}); });
if (IS_DEBUG_BUILD) { if (IS_DEBUG_BUILD) {
debugging.logToConsole(client); debugging.logToConsole(client);
} }
return client; return client;
} }
private registerObsidianExtensions(client: SyncClient): void { private registerObsidianExtensions(client: SyncClient): void {
const statusDescription = new StatusDescription(client); const statusDescription = new StatusDescription(client);
this.settingsTab = new SyncSettingsTab({ this.settingsTab = new SyncSettingsTab({
app: this.app, app: this.app,
plugin: this, plugin: this,
syncClient: client, syncClient: client,
statusDescription statusDescription
}); });
this.addSettingTab(this.settingsTab); this.addSettingTab(this.settingsTab);
new StatusBar(this, client); new StatusBar(this, client);
this.registerView(HistoryView.TYPE, (leaf) => { this.registerView(HistoryView.TYPE, (leaf) => {
const view = new HistoryView(client, leaf); const view = new HistoryView(client, leaf);
this.register(async () => view.onClose()); this.register(async () => view.onClose());
return view; return view;
}); });
this.registerView(LogsView.TYPE, (leaf) => new LogsView(client, leaf)); this.registerView(LogsView.TYPE, (leaf) => new LogsView(client, leaf));
this.registerEditorExtension([remoteCursorsTheme, remoteCursorsPlugin]); this.registerEditorExtension([remoteCursorsTheme, remoteCursorsPlugin]);
client.addRemoteCursorsUpdateListener((cursors) => { client.addRemoteCursorsUpdateListener((cursors) => {
RemoteCursorsPluginValue.setCursors(cursors, this.app); RemoteCursorsPluginValue.setCursors(cursors, this.app);
renderCursorsInFileExplorer(cursors, this.app); renderCursorsInFileExplorer(cursors, this.app);
}); });
const cursorListener = new LocalCursorUpdateListener( const cursorListener = new LocalCursorUpdateListener(
client, client,
this.app.workspace this.app.workspace
); );
this.register(() => { this.register(() => {
cursorListener.dispose(); cursorListener.dispose();
}); });
this.app.workspace.updateOptions(); this.app.workspace.updateOptions();
this.addRibbonIcons(); this.addRibbonIcons();
const editorStatusDisplayManager = new EditorStatusDisplayManager( const editorStatusDisplayManager = new EditorStatusDisplayManager(
this, this,
this.app.workspace, this.app.workspace,
client client
); );
this.register(() => { this.register(() => {
editorStatusDisplayManager.dispose(); editorStatusDisplayManager.dispose();
}); });
this.register(() => { this.register(() => {
// eslint-disable-next-line // eslint-disable-next-line
(globalThis as any).VAULT_LINK_RUNNING_INSTANCE = null; (globalThis as any).VAULT_LINK_RUNNING_INSTANCE = null;
}); });
} }
private addRibbonIcons(): void { private addRibbonIcons(): void {
this.addRibbonIcon( this.addRibbonIcon(
HistoryView.ICON, HistoryView.ICON,
"Open VaultLink events", "Open VaultLink events",
async (_: MouseEvent) => this.activateView(HistoryView.TYPE) async (_: MouseEvent) => this.activateView(HistoryView.TYPE)
); );
this.addRibbonIcon( this.addRibbonIcon(
LogsView.ICON, LogsView.ICON,
"Open VaultLink logs", "Open VaultLink logs",
async (_: MouseEvent) => this.activateView(LogsView.TYPE) async (_: MouseEvent) => this.activateView(LogsView.TYPE)
); );
} }
private registerEditorEvents(client: SyncClient): void { private registerEditorEvents(client: SyncClient): void {
[ [
this.app.workspace.on( this.app.workspace.on(
"editor-change", "editor-change",
async ( async (
_editor: Editor, _editor: Editor,
info: MarkdownView | MarkdownFileInfo info: MarkdownView | MarkdownFileInfo
) => { ) => {
const { file } = info; const { file } = info;
if (file) { if (file) {
await this.rateLimitedUpdate(file.path, client); await this.rateLimitedUpdate(file.path, client);
} }
} }
), ),
this.app.vault.on("create", async (file: TAbstractFile) => { this.app.vault.on("create", async (file: TAbstractFile) => {
if (file instanceof TFile) { if (file instanceof TFile) {
await client.syncLocallyCreatedFile(file.path); await client.syncLocallyCreatedFile(file.path);
} }
}), }),
this.app.vault.on("modify", async (file: TAbstractFile) => { this.app.vault.on("modify", async (file: TAbstractFile) => {
if (file instanceof TFile) { if (file instanceof TFile) {
await this.rateLimitedUpdate(file.path, client); await this.rateLimitedUpdate(file.path, client);
} }
}), }),
this.app.vault.on("delete", async (file: TAbstractFile) => { this.app.vault.on("delete", async (file: TAbstractFile) => {
await client.syncLocallyDeletedFile(file.path); await client.syncLocallyDeletedFile(file.path);
}), }),
this.app.vault.on( this.app.vault.on(
"rename", "rename",
async (file: TAbstractFile, oldPath: string) => { async (file: TAbstractFile, oldPath: string) => {
if (file instanceof TFile) { if (file instanceof TFile) {
await client.syncLocallyUpdatedFile({ await client.syncLocallyUpdatedFile({
oldPath, oldPath,
relativePath: file.path relativePath: file.path
}); });
} }
} }
) )
].forEach((event) => { ].forEach((event) => {
this.registerEvent(event); this.registerEvent(event);
}); });
} }
private async rateLimitedUpdate( private async rateLimitedUpdate(
path: string, path: string,
client: SyncClient client: SyncClient
): Promise<void> { ): Promise<void> {
if (!this.rateLimitedUpdatesPerFile.has(path)) { if (!this.rateLimitedUpdatesPerFile.has(path)) {
this.rateLimitedUpdatesPerFile.set( this.rateLimitedUpdatesPerFile.set(
path, path,
rateLimit( rateLimit(
async () => async () =>
client.syncLocallyUpdatedFile({ client.syncLocallyUpdatedFile({
relativePath: path relativePath: path
}), }),
MIN_WAIT_BETWEEN_UPDATES_IN_MS MIN_WAIT_BETWEEN_UPDATES_IN_MS
) )
); );
} }
await this.rateLimitedUpdatesPerFile.get(path)?.(); await this.rateLimitedUpdatesPerFile.get(path)?.();
} }
} }

View file

@ -1,17 +1,17 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"module": "ESNext", "module": "ESNext",
"target": "ES2023", "target": "ES2023",
"strict": true, "strict": true,
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"lib": [ "lib": [
"DOM", "DOM",
"ES2024" "ES2024"
] ]
}, },
"exclude": [ "exclude": [
"./dist" "./dist"
] ]
} }

View file

@ -2,7 +2,6 @@
set -e set -e
# Parse arguments
FIX_MODE=false FIX_MODE=false
if [[ "$1" == "--fix" ]]; then if [[ "$1" == "--fix" ]]; then
FIX_MODE=true FIX_MODE=true
@ -33,12 +32,16 @@ else
npm ci npm ci
fi fi
echo "Checking .editorconfig compliance" cd ..
# Use git ls-files to only check tracked files, respecting .gitignore
if [[ "$FIX_MODE" == true ]]; then if [[ "$FIX_MODE" == true ]]; then
npx eclint fix '../**/*' '!../node_modules/**' '!../frontend/node_modules/**' '!../sync-server/target/**' '!../frontend/dist/**' '!../.git/**' git ls-files | xargs npx eclint fix
else else
npx eclint check '../**/*' '!../node_modules/**' '!../frontend/node_modules/**' '!../sync-server/target/**' '!../frontend/dist/**' '!../.git/**' git ls-files | xargs npx eclint check
fi fi
cd frontend
npm run build npm run build
npm run test npm run test
npm run lint npm run lint

View file

@ -109,4 +109,3 @@ while true; do
sleep 0.2 sleep 0.2
done done