mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-06-17 20:00:13 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 72033472f3 | |||
| 93dc03e86f | |||
| dae8443fe8 | |||
| 88a8bcbd5a | |||
| 4a5283543d | |||
| 7895336189 | |||
| 2d5cdccf7d | |||
| 497fd04081 |
Generated
+953
-1242
File diff suppressed because it is too large
Load Diff
+11
-10
@@ -66,7 +66,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dword-design/eslint-plugin-import-alias": "^8.1.8",
|
"@dword-design/eslint-plugin-import-alias": "^8.1.8",
|
||||||
"@eslint/js": "^9.39.3",
|
"@eslint/js": "^9.39.3",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
"@playwright/test": "^1.58.2",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||||
"@tsconfig/svelte": "^5.0.8",
|
"@tsconfig/svelte": "^5.0.8",
|
||||||
"@types/deno": "^2.5.0",
|
"@types/deno": "^2.5.0",
|
||||||
"@types/diff-match-patch": "^1.0.36",
|
"@types/diff-match-patch": "^1.0.36",
|
||||||
@@ -81,7 +82,6 @@
|
|||||||
"@types/pouchdb-mapreduce": "^6.1.10",
|
"@types/pouchdb-mapreduce": "^6.1.10",
|
||||||
"@types/pouchdb-replication": "^6.4.7",
|
"@types/pouchdb-replication": "^6.4.7",
|
||||||
"@types/transform-pouch": "^1.0.6",
|
"@types/transform-pouch": "^1.0.6",
|
||||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
|
||||||
"@typescript-eslint/parser": "8.56.1",
|
"@typescript-eslint/parser": "8.56.1",
|
||||||
"@vitest/browser": "^4.1.8",
|
"@vitest/browser": "^4.1.8",
|
||||||
"@vitest/browser-playwright": "^4.1.8",
|
"@vitest/browser-playwright": "^4.1.8",
|
||||||
@@ -92,10 +92,9 @@
|
|||||||
"esbuild-svelte": "^0.9.4",
|
"esbuild-svelte": "^0.9.4",
|
||||||
"eslint": "^9.39.3",
|
"eslint": "^9.39.3",
|
||||||
"eslint-plugin-obsidianmd": "^0.3.0",
|
"eslint-plugin-obsidianmd": "^0.3.0",
|
||||||
"eslint-plugin-svelte": "^3.15.0",
|
"eslint-plugin-svelte": "^3.19.0",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"globals": "^14.0.0",
|
"globals": "^14.0.0",
|
||||||
"@playwright/test": "^1.58.2",
|
|
||||||
"playwright": "^1.58.2",
|
"playwright": "^1.58.2",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"pouchdb-adapter-http": "^9.0.0",
|
"pouchdb-adapter-http": "^9.0.0",
|
||||||
@@ -111,7 +110,7 @@
|
|||||||
"pouchdb-utils": "^9.0.0",
|
"pouchdb-utils": "^9.0.0",
|
||||||
"prettier": "3.8.1",
|
"prettier": "3.8.1",
|
||||||
"rollup-plugin-copy": "^3.5.0",
|
"rollup-plugin-copy": "^3.5.0",
|
||||||
"svelte": "5.41.1",
|
"svelte": "5.56.3",
|
||||||
"svelte-check": "^4.6.0",
|
"svelte-check": "^4.6.0",
|
||||||
"svelte-eslint-parser": "^1.8.0",
|
"svelte-eslint-parser": "^1.8.0",
|
||||||
"svelte-preprocess": "^6.0.3",
|
"svelte-preprocess": "^6.0.3",
|
||||||
@@ -121,10 +120,12 @@
|
|||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "^8.61.0",
|
"typescript-eslint": "^8.61.0",
|
||||||
"vite": "^7.3.1",
|
"vite": "^8.0.16",
|
||||||
"vitest": "^4.1.8",
|
"vitest": "^4.1.8",
|
||||||
"webdriverio": "^9.27.0",
|
"webdriverio": "^9.27.0",
|
||||||
"yaml": "^2.8.2"
|
"yaml": "^2.8.2",
|
||||||
|
"@emnapi/core": "1.11.1",
|
||||||
|
"@emnapi/runtime": "1.11.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.808.0",
|
"@aws-sdk/client-s3": "^3.808.0",
|
||||||
@@ -139,9 +140,9 @@
|
|||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"idb": "^8.0.3",
|
"idb": "^8.0.3",
|
||||||
"markdown-it": "^14.1.1",
|
"markdown-it": "^14.2.0",
|
||||||
"minimatch": "^10.2.2",
|
"minimatch": "^10.2.5",
|
||||||
"obsidian": "^1.12.3",
|
"obsidian": "^1.13.1",
|
||||||
"octagonal-wheels": "^0.1.46",
|
"octagonal-wheels": "^0.1.46",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as path from "path";
|
|
||||||
import type { UXFileInfoStub, UXFolderInfo } from "@lib/common/types";
|
import type { UXFileInfoStub, UXFolderInfo } from "@lib/common/types";
|
||||||
import type { IConversionAdapter } from "@lib/serviceModules/adapters";
|
import type { IConversionAdapter } from "@lib/serviceModules/adapters";
|
||||||
import type { NodeFile, NodeFolder } from "./NodeTypes";
|
import type { NodeFile, NodeFolder } from "./NodeTypes";
|
||||||
|
import { path } from "../node-compat";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Conversion adapter implementation for Node.js
|
* Conversion adapter implementation for Node.js
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import * as fs from "fs/promises";
|
|
||||||
import * as path from "path";
|
|
||||||
import type { FilePath, UXStat } from "@lib/common/types";
|
import type { FilePath, UXStat } from "@lib/common/types";
|
||||||
import type { IFileSystemAdapter } from "@lib/serviceModules/adapters";
|
import type { IFileSystemAdapter } from "@lib/serviceModules/adapters";
|
||||||
import { NodePathAdapter } from "./NodePathAdapter";
|
import { NodePathAdapter } from "./NodePathAdapter";
|
||||||
@@ -8,6 +6,7 @@ import { NodeConversionAdapter } from "./NodeConversionAdapter";
|
|||||||
import { NodeStorageAdapter } from "./NodeStorageAdapter";
|
import { NodeStorageAdapter } from "./NodeStorageAdapter";
|
||||||
import { NodeVaultAdapter } from "./NodeVaultAdapter";
|
import { NodeVaultAdapter } from "./NodeVaultAdapter";
|
||||||
import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes";
|
import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes";
|
||||||
|
import { fsPromises as fs, path } from "../node-compat";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete file system adapter implementation for Node.js
|
* Complete file system adapter implementation for Node.js
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as path from "path";
|
|
||||||
import type { FilePath } from "@lib/common/types";
|
import type { FilePath } from "@lib/common/types";
|
||||||
import type { IPathAdapter } from "@lib/serviceModules/adapters";
|
import type { IPathAdapter } from "@lib/serviceModules/adapters";
|
||||||
import type { NodeFile } from "./NodeTypes";
|
import type { NodeFile } from "./NodeTypes";
|
||||||
|
import { path } from "../node-compat";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path adapter implementation for Node.js
|
* Path adapter implementation for Node.js
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import * as fs from "fs/promises";
|
|
||||||
import * as path from "path";
|
|
||||||
import type { UXDataWriteOptions } from "@lib/common/types";
|
import type { UXDataWriteOptions } from "@lib/common/types";
|
||||||
import type { IStorageAdapter } from "@lib/serviceModules/adapters";
|
import type { IStorageAdapter } from "@lib/serviceModules/adapters";
|
||||||
import type { NodeStat } from "./NodeTypes";
|
import type { NodeStat } from "./NodeTypes";
|
||||||
|
import { fsPromises as fs, path } from "../node-compat";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Storage adapter implementation for Node.js
|
* Storage adapter implementation for Node.js
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import * as fs from "fs/promises";
|
|
||||||
import * as path from "path";
|
|
||||||
import type { UXDataWriteOptions } from "@lib/common/types";
|
import type { UXDataWriteOptions } from "@lib/common/types";
|
||||||
import type { IVaultAdapter } from "@lib/serviceModules/adapters";
|
import type { IVaultAdapter } from "@lib/serviceModules/adapters";
|
||||||
import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes";
|
import type { NodeFile, NodeFolder } from "./NodeTypes";
|
||||||
|
import { fsPromises as fs, path } from "../node-compat";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vault adapter implementation for Node.js
|
* Vault adapter implementation for Node.js
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
|||||||
import { P2P_DEFAULT_SETTINGS } from "@lib/common/types";
|
import { P2P_DEFAULT_SETTINGS } from "@lib/common/types";
|
||||||
import type { ServiceContext } from "@lib/services/base/ServiceBase";
|
import type { ServiceContext } from "@lib/services/base/ServiceBase";
|
||||||
import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
||||||
import { addP2PEventHandlers } from "@lib/replication/trystero/addP2PEventHandlers";
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
type CLIP2PPeer = {
|
type CLIP2PPeer = {
|
||||||
peerId: string;
|
peerId: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function delay(ms: number): Promise<void> {
|
function delay(ms: number): Promise<void> {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => compatGlobal.setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseTimeoutSeconds(value: string, commandName: string): number {
|
export function parseTimeoutSeconds(value: string, commandName: string): number {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import * as fs from "fs/promises";
|
|
||||||
import * as path from "path";
|
|
||||||
import { decodeSettingsFromSetupURI } from "@lib/API/processSetting";
|
import { decodeSettingsFromSetupURI } from "@lib/API/processSetting";
|
||||||
import { configURIBase } from "@lib/common/models/shared.const";
|
import { configURIBase } from "@lib/common/models/shared.const";
|
||||||
import {
|
import {
|
||||||
@@ -18,6 +16,8 @@ import { promptForPassphrase, readStdinAsUtf8, toArrayBuffer, toDatabaseRelative
|
|||||||
import { collectPeers, openP2PHost, parseTimeoutSeconds, syncWithPeer } from "./p2p";
|
import { collectPeers, openP2PHost, parseTimeoutSeconds, syncWithPeer } from "./p2p";
|
||||||
import { performFullScan } from "@lib/serviceFeatures/offlineScanner";
|
import { performFullScan } from "@lib/serviceFeatures/offlineScanner";
|
||||||
import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager";
|
import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
import { fsPromises as fs, path } from "../node-compat";
|
||||||
|
|
||||||
function redactConnectionString(uri: string): string {
|
function redactConnectionString(uri: string): string {
|
||||||
return uri.replace(/\/\/([^@/]+)@/u, "//***@");
|
return uri.replace(/\/\/([^@/]+)@/u, "//***@");
|
||||||
@@ -150,11 +150,11 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pollTimer = setTimeout(poll, currentIntervalMs);
|
pollTimer = compatGlobal.setTimeout(poll, currentIntervalMs);
|
||||||
};
|
};
|
||||||
let pollTimer: ReturnType<typeof setTimeout> = setTimeout(poll, currentIntervalMs);
|
let pollTimer = compatGlobal.setTimeout(poll, currentIntervalMs);
|
||||||
core.services.appLifecycle.onUnload.addHandler(async () => {
|
core.services.appLifecycle.onUnload.addHandler(async () => {
|
||||||
clearTimeout(pollTimer);
|
compatGlobal.clearTimeout(pollTimer);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as path from "path";
|
import { path, readline } from "../node-compat";
|
||||||
import * as readline from "node:readline/promises";
|
|
||||||
|
|
||||||
export function toArrayBuffer(data: Buffer): ArrayBuffer {
|
export function toArrayBuffer(data: Buffer): ArrayBuffer {
|
||||||
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;
|
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
// eslint-disable -- This is the entry point for the CLI application.
|
||||||
import * as polyfill from "werift";
|
import * as polyfill from "werift";
|
||||||
import { main } from "./main";
|
import { main } from "./main";
|
||||||
|
|
||||||
|
|||||||
+6
-12
@@ -1,10 +1,3 @@
|
|||||||
/**
|
|
||||||
* Self-hosted LiveSync CLI
|
|
||||||
* Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as fs from "fs/promises";
|
|
||||||
import * as path from "path";
|
|
||||||
import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub";
|
import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub";
|
||||||
import { configureNodeLocalStorage, ensureGlobalNodeLocalStorage } from "./services/NodeLocalStorage";
|
import { configureNodeLocalStorage, ensureGlobalNodeLocalStorage } from "./services/NodeLocalStorage";
|
||||||
import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||||
@@ -27,6 +20,7 @@ import { getPathFromUXFileInfo } from "@lib/common/typeUtils";
|
|||||||
import { stripAllPrefixes } from "@lib/string_and_binary/path";
|
import { stripAllPrefixes } from "@lib/string_and_binary/path";
|
||||||
import { IgnoreRules } from "./serviceModules/IgnoreRules";
|
import { IgnoreRules } from "./serviceModules/IgnoreRules";
|
||||||
import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature";
|
import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature";
|
||||||
|
import { fsPromises as fs, path, fs as fsSync } from "./node-compat";
|
||||||
|
|
||||||
const SETTINGS_FILE = ".livesync/settings.json";
|
const SETTINGS_FILE = ".livesync/settings.json";
|
||||||
ensureGlobalNodeLocalStorage();
|
ensureGlobalNodeLocalStorage();
|
||||||
@@ -485,8 +479,8 @@ export async function main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
process.on("SIGINT", () => shutdown("SIGINT"));
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
||||||
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
||||||
|
|
||||||
// Save the settings file before any lifecycle events can mutate and persist them.
|
// Save the settings file before any lifecycle events can mutate and persist them.
|
||||||
// suspendAllSync and other lifecycle hooks clobber sync settings in memory, and
|
// suspendAllSync and other lifecycle hooks clobber sync settings in memory, and
|
||||||
@@ -499,8 +493,8 @@ export async function main() {
|
|||||||
if (settingsBackup) {
|
if (settingsBackup) {
|
||||||
const tmpPath = settingsPath + ".tmp";
|
const tmpPath = settingsPath + ".tmp";
|
||||||
try {
|
try {
|
||||||
require("fs").writeFileSync(tmpPath, settingsBackup, "utf-8");
|
fsSync.writeFileSync(tmpPath, settingsBackup, "utf-8");
|
||||||
require("fs").renameSync(tmpPath, settingsPath);
|
fsSync.renameSync(tmpPath, settingsPath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[Settings] Failed to restore settings on exit:", err);
|
console.error("[Settings] Failed to restore settings on exit:", err);
|
||||||
}
|
}
|
||||||
@@ -563,7 +557,7 @@ export async function main() {
|
|||||||
|
|
||||||
if (options.command === "daemon" && result) {
|
if (options.command === "daemon" && result) {
|
||||||
// Keep the process running
|
// Keep the process running
|
||||||
await new Promise(() => {});
|
await new Promise(() => { });
|
||||||
} else {
|
} else {
|
||||||
await core.services.control.onUnload();
|
await core.services.control.onUnload();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,9 @@ import type {
|
|||||||
} from "@lib/managers/adapters";
|
} from "@lib/managers/adapters";
|
||||||
import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager";
|
import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager";
|
||||||
import type { NodeFile, NodeFolder } from "@/apps/cli/adapters/NodeTypes";
|
import type { NodeFile, NodeFolder } from "@/apps/cli/adapters/NodeTypes";
|
||||||
import type { Stats } from "fs";
|
|
||||||
import * as fs from "fs/promises";
|
|
||||||
import * as path from "path";
|
|
||||||
import { watch as chokidarWatch, type FSWatcher } from "chokidar";
|
import { watch as chokidarWatch, type FSWatcher } from "chokidar";
|
||||||
import type { IgnoreRules } from "@/apps/cli/serviceModules/IgnoreRules";
|
import type { IgnoreRules } from "@/apps/cli/serviceModules/IgnoreRules";
|
||||||
|
import { fsPromises as fs, path, type Stats } from "../node-compat";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CLI-specific type guard adapter
|
* CLI-specific type guard adapter
|
||||||
@@ -101,7 +99,7 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter {
|
|||||||
private basePath: string,
|
private basePath: string,
|
||||||
private ignoreRules?: IgnoreRules,
|
private ignoreRules?: IgnoreRules,
|
||||||
private watchEnabled: boolean = false
|
private watchEnabled: boolean = false
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
private _toNodeFile(filePath: string, stats: Stats | undefined): NodeFile {
|
private _toNodeFile(filePath: string, stats: Stats | undefined): NodeFile {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/* eslint-disable obsidianmd/no-nodejs-builtins */
|
||||||
|
import * as nodeFs from "node:fs";
|
||||||
|
import * as nodeFsPromises from "node:fs/promises";
|
||||||
|
import * as nodePath from "node:path";
|
||||||
|
import * as nodeReadlinePromises from "node:readline/promises";
|
||||||
|
import type { Stats } from "node:fs";
|
||||||
|
export {
|
||||||
|
nodeFs as fs,
|
||||||
|
nodeFsPromises as fsPromises,
|
||||||
|
nodePath as path,
|
||||||
|
nodeReadlinePromises as readline,
|
||||||
|
type Stats,
|
||||||
|
};
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^4.0.0",
|
"chokidar": "^4.0.0",
|
||||||
"minimatch": "^10.2.2",
|
"minimatch": "^10.2.5",
|
||||||
"octagonal-wheels": "^0.1.46",
|
"octagonal-wheels": "^0.1.46",
|
||||||
"pouchdb-adapter-http": "^9.0.0",
|
"pouchdb-adapter-http": "^9.0.0",
|
||||||
"pouchdb-adapter-leveldb": "^9.0.0",
|
"pouchdb-adapter-leveldb": "^9.0.0",
|
||||||
@@ -55,9 +55,9 @@
|
|||||||
"werift": "^0.23.0"
|
"werift": "^0.23.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vite": "^7.3.1",
|
"vite": "^8.0.16",
|
||||||
"vitest": "^4.1.8"
|
"vitest": "^4.1.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import * as fs from "fs/promises";
|
|
||||||
import * as path from "path";
|
|
||||||
|
|
||||||
import { minimatch } from "minimatch";
|
import { minimatch } from "minimatch";
|
||||||
|
import { fsPromises as fs, path } from "../node-compat";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and evaluates ignore rules from `.livesync/ignore` inside the vault.
|
* Loads and evaluates ignore rules from `.livesync/ignore` inside the vault.
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import type { InjectableDatabaseEventService } from "@lib/services/implements/in
|
|||||||
import type { IVaultService } from "@lib/services/base/IService";
|
import type { IVaultService } from "@lib/services/base/IService";
|
||||||
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
||||||
import { createInstanceLogFunction } from "@lib/services/lib/logUtils";
|
import { createInstanceLogFunction } from "@lib/services/lib/logUtils";
|
||||||
import * as nodeFs from "node:fs";
|
import { fs as nodeFs, path as nodePath } from "../node-compat";
|
||||||
import * as nodePath from "node:path";
|
|
||||||
|
|
||||||
const NODE_KV_TYPED_KEY = "__nodeKvType";
|
const NODE_KV_TYPED_KEY = "__nodeKvType";
|
||||||
const NODE_KV_VALUES_KEY = "values";
|
const NODE_KV_VALUES_KEY = "values";
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as nodeFs from "node:fs";
|
import { fs as nodeFs, path as nodePath } from "../node-compat";
|
||||||
import * as nodePath from "node:path";
|
|
||||||
|
|
||||||
type LocalStorageShape = {
|
type LocalStorageShape = {
|
||||||
getItem(key: string): string | null;
|
getItem(key: string): string | null;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { AppLifecycleService, AppLifecycleServiceDependencies } from "@lib/services/base/AppLifecycleService";
|
import type { AppLifecycleService, AppLifecycleServiceDependencies } from "@lib/services/base/AppLifecycleService";
|
||||||
import { ServiceContext } from "@lib/services/base/ServiceBase";
|
import { ServiceContext } from "@lib/services/base/ServiceBase";
|
||||||
import * as nodePath from "node:path";
|
|
||||||
import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat";
|
import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat";
|
||||||
import { SvelteDialogManagerBase, type ComponentHasResult } from "@lib/services/implements/base/SvelteDialog";
|
import { SvelteDialogManagerBase, type ComponentHasResult } from "@lib/services/implements/base/SvelteDialog";
|
||||||
import { UIService } from "@lib/services/implements/base/UIService";
|
import { UIService } from "@lib/services/implements/base/UIService";
|
||||||
@@ -25,6 +24,7 @@ import { NodeKeyValueDBService } from "./NodeKeyValueDBService";
|
|||||||
import { NodeSettingService } from "./NodeSettingService";
|
import { NodeSettingService } from "./NodeSettingService";
|
||||||
import { DatabaseService } from "@lib/services/base/DatabaseService";
|
import { DatabaseService } from "@lib/services/base/DatabaseService";
|
||||||
import type { ObsidianLiveSyncSettings } from "@lib/common/types";
|
import type { ObsidianLiveSyncSettings } from "@lib/common/types";
|
||||||
|
import { path as nodePath } from "../node-compat";
|
||||||
|
|
||||||
export class NodeServiceContext extends ServiceContext {
|
export class NodeServiceContext extends ServiceContext {
|
||||||
databasePath: string;
|
databasePath: string;
|
||||||
|
|||||||
@@ -77,7 +77,9 @@ export class BackgroundCliProcess {
|
|||||||
if (this.combined.includes(needle)) return;
|
if (this.combined.includes(needle)) return;
|
||||||
const status = await Promise.race([
|
const status = await Promise.race([
|
||||||
this.child.status.then((s) => ({ type: "status" as const, status: s })),
|
this.child.status.then((s) => ({ type: "status" as const, status: s })),
|
||||||
new Promise<{ type: "tick" }>((resolve) => setTimeout(() => resolve({ type: "tick" }), 100)),
|
new Promise<{ type: "tick" }>((resolve) =>
|
||||||
|
setTimeout(() => resolve({ type: "tick" }), 100)
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
if (status.type === "status") {
|
if (status.type === "status") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ Deno.test("CLI file operations: push / cat / ls / info / rm / resolve / cat-rev
|
|||||||
assertEquals(data.path, REMOTE_PATH, "info .path mismatch");
|
assertEquals(data.path, REMOTE_PATH, "info .path mismatch");
|
||||||
assertEquals(data.filename, REMOTE_PATH.split("/").at(-1), "info .filename mismatch");
|
assertEquals(data.filename, REMOTE_PATH.split("/").at(-1), "info .filename mismatch");
|
||||||
assert(typeof data.size === "number" && data.size >= 0, `info .size invalid: ${data.size}`);
|
assert(typeof data.size === "number" && data.size >= 0, `info .size invalid: ${data.size}`);
|
||||||
assert(typeof data.chunks === "number" && (data.chunks as number) >= 1, `info .chunks invalid: ${data.chunks}`);
|
assert(typeof data.chunks === "number" && (data.chunks) >= 1, `info .chunks invalid: ${data.chunks}`);
|
||||||
assertEquals(data.conflicts, "N/A", "info .conflicts should be N/A");
|
assertEquals(data.conflicts, "N/A", "info .conflicts should be N/A");
|
||||||
console.log("[PASS] info output format matched");
|
console.log("[PASS] info output format matched");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { LiveSyncWebApp } from "./main";
|
import { LiveSyncWebApp } from "./main";
|
||||||
import { VaultHistoryStore, type VaultHistoryItem } from "./vaultSelector";
|
import { VaultHistoryStore, type VaultHistoryItem } from "./vaultSelector";
|
||||||
|
import { compatGlobal, _activeDocument } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
const historyStore = new VaultHistoryStore();
|
const historyStore = new VaultHistoryStore();
|
||||||
let app: LiveSyncWebApp | null = null;
|
let app: LiveSyncWebApp | null = null;
|
||||||
|
|
||||||
function getRequiredElement<T extends HTMLElement>(id: string): T {
|
function getRequiredElement<T extends HTMLElement>(id: string): T {
|
||||||
const element = document.getElementById(id);
|
const element = _activeDocument.getElementById(id);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
throw new Error(`Missing element: #${id}`);
|
throw new Error(`Missing element: #${id}`);
|
||||||
}
|
}
|
||||||
@@ -22,7 +23,7 @@ function setBusyState(isBusy: boolean): void {
|
|||||||
const pickNewBtn = getRequiredElement<HTMLButtonElement>("pick-new-vault");
|
const pickNewBtn = getRequiredElement<HTMLButtonElement>("pick-new-vault");
|
||||||
pickNewBtn.disabled = isBusy;
|
pickNewBtn.disabled = isBusy;
|
||||||
|
|
||||||
const historyButtons = document.querySelectorAll<HTMLButtonElement>(".vault-item button");
|
const historyButtons = _activeDocument.querySelectorAll<HTMLButtonElement>(".vault-item button");
|
||||||
historyButtons.forEach((button) => {
|
historyButtons.forEach((button) => {
|
||||||
button.disabled = isBusy;
|
button.disabled = isBusy;
|
||||||
});
|
});
|
||||||
@@ -45,24 +46,24 @@ async function renderHistoryList(): Promise<VaultHistoryItem[]> {
|
|||||||
emptyEl.classList.toggle("is-hidden", items.length > 0);
|
emptyEl.classList.toggle("is-hidden", items.length > 0);
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const row = document.createElement("div");
|
const row = _activeDocument.createElement("div");
|
||||||
row.className = "vault-item";
|
row.className = "vault-item";
|
||||||
|
|
||||||
const info = document.createElement("div");
|
const info = _activeDocument.createElement("div");
|
||||||
info.className = "vault-item-info";
|
info.className = "vault-item-info";
|
||||||
|
|
||||||
const name = document.createElement("div");
|
const name = _activeDocument.createElement("div");
|
||||||
name.className = "vault-item-name";
|
name.className = "vault-item-name";
|
||||||
name.textContent = item.name;
|
name.textContent = item.name;
|
||||||
|
|
||||||
const meta = document.createElement("div");
|
const meta = _activeDocument.createElement("div");
|
||||||
meta.className = "vault-item-meta";
|
meta.className = "vault-item-meta";
|
||||||
const label = item.id === lastUsedId ? "Last used" : "Used";
|
const label = item.id === lastUsedId ? "Last used" : "Used";
|
||||||
meta.textContent = `${label}: ${formatLastUsed(item.lastUsedAt)}`;
|
meta.textContent = `${label}: ${formatLastUsed(item.lastUsedAt)}`;
|
||||||
|
|
||||||
info.append(name, meta);
|
info.append(name, meta);
|
||||||
|
|
||||||
const useButton = document.createElement("button");
|
const useButton = _activeDocument.createElement("button");
|
||||||
useButton.type = "button";
|
useButton.type = "button";
|
||||||
useButton.textContent = "Use this vault";
|
useButton.textContent = "Use this vault";
|
||||||
useButton.addEventListener("click", () => {
|
useButton.addEventListener("click", () => {
|
||||||
@@ -120,7 +121,7 @@ async function initializeVaultSelector(): Promise<void> {
|
|||||||
await renderHistoryList();
|
await renderHistoryList();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("load", async () => {
|
compatGlobal.addEventListener("load", async () => {
|
||||||
try {
|
try {
|
||||||
await initializeVaultSelector();
|
await initializeVaultSelector();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -129,11 +130,11 @@ window.addEventListener("load", async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("beforeunload", () => {
|
compatGlobal.addEventListener("beforeunload", () => {
|
||||||
void app?.shutdown();
|
void app?.shutdown();
|
||||||
});
|
});
|
||||||
|
|
||||||
(window as any).livesyncApp = {
|
(compatGlobal as any).livesyncApp = {
|
||||||
getApp: () => app,
|
getApp: () => app,
|
||||||
historyStore,
|
historyStore,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { SetupManager } from "@/modules/features/SetupManager";
|
|||||||
import { useSetupManagerHandlersFeature } from "@/serviceFeatures/setupObsidian/setupManagerHandlers";
|
import { useSetupManagerHandlersFeature } from "@/serviceFeatures/setupObsidian/setupManagerHandlers";
|
||||||
import { useP2PReplicatorCommands } from "@lib/replication/trystero/useP2PReplicatorCommands";
|
import { useP2PReplicatorCommands } from "@lib/replication/trystero/useP2PReplicatorCommands";
|
||||||
import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature";
|
import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature";
|
||||||
|
import { compatGlobal, _activeDocument } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
const SETTINGS_DIR = ".livesync";
|
const SETTINGS_DIR = ".livesync";
|
||||||
const SETTINGS_FILE = "settings.json";
|
const SETTINGS_FILE = "settings.json";
|
||||||
@@ -91,7 +92,7 @@ class LiveSyncWebApp {
|
|||||||
console.log("[Settings] Loaded from .livesync/settings.json");
|
console.log("[Settings] Loaded from .livesync/settings.json");
|
||||||
return { ...DEFAULT_SETTINGS, ...data } as ObsidianLiveSyncSettings;
|
return { ...DEFAULT_SETTINGS, ...data } as ObsidianLiveSyncSettings;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.log("[Settings] Failed to load, using defaults");
|
console.log("[Settings] Failed to load, using defaults");
|
||||||
}
|
}
|
||||||
return DEFAULT_SETTINGS as ObsidianLiveSyncSettings;
|
return DEFAULT_SETTINGS as ObsidianLiveSyncSettings;
|
||||||
@@ -102,8 +103,8 @@ class LiveSyncWebApp {
|
|||||||
console.log("[AppLifecycle] Restart requested");
|
console.log("[AppLifecycle] Restart requested");
|
||||||
await this.shutdown();
|
await this.shutdown();
|
||||||
await this.initialize();
|
await this.initialize();
|
||||||
setTimeout(() => {
|
compatGlobal.setTimeout(() => {
|
||||||
window.location.reload();
|
compatGlobal.location.reload();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -169,7 +170,7 @@ class LiveSyncWebApp {
|
|||||||
const file = await fileHandle.getFile();
|
const file = await fileHandle.getFile();
|
||||||
const text = await file.text();
|
const text = await file.text();
|
||||||
return JSON.parse(text);
|
return JSON.parse(text);
|
||||||
} catch (error) {
|
} catch {
|
||||||
// File doesn't exist yet
|
// File doesn't exist yet
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -235,7 +236,7 @@ class LiveSyncWebApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private showError(message: string) {
|
private showError(message: string) {
|
||||||
const statusEl = document.getElementById("status");
|
const statusEl = _activeDocument.getElementById("status");
|
||||||
if (statusEl) {
|
if (statusEl) {
|
||||||
statusEl.className = "error";
|
statusEl.className = "error";
|
||||||
statusEl.textContent = `Error: ${message}`;
|
statusEl.textContent = `Error: ${message}`;
|
||||||
@@ -243,7 +244,7 @@ class LiveSyncWebApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private showWarning(message: string) {
|
private showWarning(message: string) {
|
||||||
const statusEl = document.getElementById("status");
|
const statusEl = _activeDocument.getElementById("status");
|
||||||
if (statusEl) {
|
if (statusEl) {
|
||||||
statusEl.className = "warning";
|
statusEl.className = "warning";
|
||||||
statusEl.textContent = `Warning: ${message}`;
|
statusEl.textContent = `Warning: ${message}`;
|
||||||
@@ -251,7 +252,7 @@ class LiveSyncWebApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private showSuccess(message: string) {
|
private showSuccess(message: string) {
|
||||||
const statusEl = document.getElementById("status");
|
const statusEl = _activeDocument.getElementById("status");
|
||||||
if (statusEl) {
|
if (statusEl) {
|
||||||
statusEl.className = "success";
|
statusEl.className = "success";
|
||||||
statusEl.textContent = message;
|
statusEl.textContent = message;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
} from "@lib/managers/adapters";
|
} from "@lib/managers/adapters";
|
||||||
import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager";
|
import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager";
|
||||||
import type { FSAPIFile, FSAPIFolder } from "@/apps/webapp/adapters/FSAPITypes";
|
import type { FSAPIFile, FSAPIFolder } from "@/apps/webapp/adapters/FSAPITypes";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FileSystem API-specific type guard adapter
|
* FileSystem API-specific type guard adapter
|
||||||
@@ -149,14 +150,14 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
|||||||
|
|
||||||
async beginWatch(handlers: IStorageEventWatchHandlers): Promise<void> {
|
async beginWatch(handlers: IStorageEventWatchHandlers): Promise<void> {
|
||||||
// Use FileSystemObserver if available (Chrome 124+)
|
// Use FileSystemObserver if available (Chrome 124+)
|
||||||
if (typeof (window as any).FileSystemObserver === "undefined") {
|
if (typeof (compatGlobal as any).FileSystemObserver === "undefined") {
|
||||||
console.log("[FSAPIWatchAdapter] FileSystemObserver not available, file watching disabled");
|
console.log("[FSAPIWatchAdapter] FileSystemObserver not available, file watching disabled");
|
||||||
console.log("[FSAPIWatchAdapter] Consider using Chrome 124+ for real-time file watching");
|
console.log("[FSAPIWatchAdapter] Consider using Chrome 124+ for real-time file watching");
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const FileSystemObserver = (window as any).FileSystemObserver;
|
const FileSystemObserver = (compatGlobal as any).FileSystemObserver;
|
||||||
|
|
||||||
this.observer = new FileSystemObserver(async (records: any[]) => {
|
this.observer = new FileSystemObserver(async (records: any[]) => {
|
||||||
for (const record of records) {
|
for (const record of records) {
|
||||||
@@ -181,7 +182,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
|||||||
if (changedHandle && changedHandle.kind === "file") {
|
if (changedHandle && changedHandle.kind === "file") {
|
||||||
const file = await changedHandle.getFile();
|
const file = await changedHandle.getFile();
|
||||||
const fileInfo = {
|
const fileInfo = {
|
||||||
path: relativePath as any,
|
path: relativePath,
|
||||||
stat: {
|
stat: {
|
||||||
size: file.size,
|
size: file.size,
|
||||||
mtime: file.lastModified,
|
mtime: file.lastModified,
|
||||||
@@ -199,7 +200,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
|||||||
}
|
}
|
||||||
} else if (type === "disappeared") {
|
} else if (type === "disappeared") {
|
||||||
const fileInfo = {
|
const fileInfo = {
|
||||||
path: relativePath as any,
|
path: relativePath,
|
||||||
stat: {
|
stat: {
|
||||||
size: 0,
|
size: 0,
|
||||||
mtime: Date.now(),
|
mtime: Date.now(),
|
||||||
@@ -216,7 +217,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
|||||||
if (changedHandle && changedHandle.kind === "file") {
|
if (changedHandle && changedHandle.kind === "file") {
|
||||||
const file = await changedHandle.getFile();
|
const file = await changedHandle.getFile();
|
||||||
const fileInfo = {
|
const fileInfo = {
|
||||||
path: relativePath as any,
|
path: relativePath,
|
||||||
stat: {
|
stat: {
|
||||||
size: file.size,
|
size: file.size,
|
||||||
mtime: file.lastModified,
|
mtime: file.lastModified,
|
||||||
|
|||||||
@@ -16,11 +16,11 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.58.2",
|
"@playwright/test": "^1.58.2",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||||
"playwright": "^1.58.2",
|
"playwright": "^1.58.2",
|
||||||
"svelte": "5.41.1",
|
"svelte": "5.56.3",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vite": "^7.3.1",
|
"vite": "^8.0.16",
|
||||||
"vite-plugin-istanbul": "^8.0.0"
|
"vite-plugin-istanbul": "^9.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
import { LiveSyncWebApp } from "./main";
|
import { LiveSyncWebApp } from "./main";
|
||||||
import type { ObsidianLiveSyncSettings } from "@lib/common/types";
|
import type { ObsidianLiveSyncSettings } from "@lib/common/types";
|
||||||
import type { FilePathWithPrefix } from "@lib/common/types";
|
import type { FilePathWithPrefix } from "@lib/common/types";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// Internal state – one app instance per page / browser context
|
// Internal state – one app instance per page / browser context
|
||||||
@@ -41,7 +42,7 @@ async function waitForIdle(core: any, timeoutMs = 60_000): Promise<void> {
|
|||||||
(core.services?.fileProcessing?.processing?.value ?? 0) +
|
(core.services?.fileProcessing?.processing?.value ?? 0) +
|
||||||
(core.services?.replication?.storageApplyingCount?.value ?? 0);
|
(core.services?.replication?.storageApplyingCount?.value ?? 0);
|
||||||
if (q === 0) return;
|
if (q === 0) return;
|
||||||
await new Promise<void>((r) => setTimeout(r, 300));
|
await new Promise<void>((r) => compatGlobal.setTimeout(r, 300));
|
||||||
}
|
}
|
||||||
throw new Error(`waitForIdle timed out after ${timeoutMs} ms`);
|
throw new Error(`waitForIdle timed out after ${timeoutMs} ms`);
|
||||||
}
|
}
|
||||||
@@ -116,7 +117,7 @@ export interface LiveSyncTestAPI {
|
|||||||
const livesyncTest: LiveSyncTestAPI = {
|
const livesyncTest: LiveSyncTestAPI = {
|
||||||
async init(vaultName: string, settings: Partial<ObsidianLiveSyncSettings>): Promise<void> {
|
async init(vaultName: string, settings: Partial<ObsidianLiveSyncSettings>): Promise<void> {
|
||||||
// Clean up any stale OPFS data from previous runs.
|
// Clean up any stale OPFS data from previous runs.
|
||||||
const opfsRoot = await navigator.storage.getDirectory();
|
const opfsRoot = await compatGlobal.navigator.storage.getDirectory();
|
||||||
try {
|
try {
|
||||||
await opfsRoot.removeEntry(vaultName, { recursive: true });
|
await opfsRoot.removeEntry(vaultName, { recursive: true });
|
||||||
} catch {
|
} catch {
|
||||||
@@ -200,4 +201,4 @@ const livesyncTest: LiveSyncTestAPI = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Expose on window for Playwright page.evaluate() calls.
|
// Expose on window for Playwright page.evaluate() calls.
|
||||||
(window as any).livesyncTest = livesyncTest;
|
(compatGlobal as any).livesyncTest = livesyncTest;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
/* Path mapping */
|
/* Path mapping */
|
||||||
"baseUrl": ".",
|
// "baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["../../*"],
|
"@/*": ["../../*"],
|
||||||
"@lib/*": ["../../lib/src/*"]
|
"@lib/*": ["../../lib/src/*"]
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
const HANDLE_DB_NAME = "livesync-webapp-handles";
|
const HANDLE_DB_NAME = "livesync-webapp-handles";
|
||||||
const HANDLE_STORE_NAME = "handles";
|
const HANDLE_STORE_NAME = "handles";
|
||||||
const LAST_USED_KEY = "meta:lastUsedVaultId";
|
const LAST_USED_KEY = "meta:lastUsedVaultId";
|
||||||
@@ -89,7 +91,7 @@ export class VaultHistoryStore {
|
|||||||
|
|
||||||
async getVaultHistory(): Promise<VaultHistoryItem[]> {
|
async getVaultHistory(): Promise<VaultHistoryItem[]> {
|
||||||
return this.withStore("readonly", async (store) => {
|
return this.withStore("readonly", async (store) => {
|
||||||
const keys = (await this.requestAsPromise(store.getAllKeys())) as IDBValidKey[];
|
const keys = (await this.requestAsPromise(store.getAllKeys()));
|
||||||
const values = (await this.requestAsPromise(store.getAll())) as unknown[];
|
const values = (await this.requestAsPromise(store.getAll())) as unknown[];
|
||||||
const items: VaultHistoryItem[] = [];
|
const items: VaultHistoryItem[] = [];
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
@@ -170,7 +172,7 @@ export class VaultHistoryStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async pickNewVault(): Promise<FileSystemDirectoryHandle> {
|
async pickNewVault(): Promise<FileSystemDirectoryHandle> {
|
||||||
const picker = (window as any).showDirectoryPicker;
|
const picker = (compatGlobal as any).showDirectoryPicker;
|
||||||
if (typeof picker !== "function") {
|
if (typeof picker !== "function") {
|
||||||
throw new Error("FileSystem API showDirectoryPicker is not supported in this browser");
|
throw new Error("FileSystem API showDirectoryPicker is not supported in this browser");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@
|
|||||||
"octagonal-wheels": "^0.1.46"
|
"octagonal-wheels": "^0.1.46"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint-plugin-svelte": "^3.15.0",
|
"eslint-plugin-svelte": "^3.19.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||||
"@tsconfig/svelte": "^5.0.8",
|
"@tsconfig/svelte": "^5.0.8",
|
||||||
"svelte": "5.41.1",
|
"svelte": "5.56.3",
|
||||||
"svelte-check": "^4.6.0",
|
"svelte-check": "^4.6.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vite": "^7.3.1"
|
"vite": "^8.0.16"
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts",
|
"../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts",
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ import {
|
|||||||
type PeerStatus,
|
type PeerStatus,
|
||||||
type PluginShim,
|
type PluginShim,
|
||||||
} from "@lib/replication/trystero/P2PReplicatorPaneCommon";
|
} from "@lib/replication/trystero/P2PReplicatorPaneCommon";
|
||||||
import { P2PLogCollector, type P2PReplicatorBase, useP2PReplicator } from "@lib/replication/trystero/P2PReplicatorCore";
|
import { useP2PReplicator } from "@lib/replication/trystero/P2PReplicatorCore";
|
||||||
|
import { P2PLogCollector } from "@lib/replication/trystero/P2PLogCollector";
|
||||||
|
import type { P2PReplicatorBase } from "@lib/replication/trystero/P2PReplicatorBase.ts";
|
||||||
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
||||||
import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
|
import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
|
||||||
import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents";
|
import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents";
|
||||||
@@ -31,6 +33,7 @@ import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2";
|
|||||||
import type { BrowserAPIService } from "@lib/services/implements/browser/BrowserAPIService";
|
import type { BrowserAPIService } from "@lib/services/implements/browser/BrowserAPIService";
|
||||||
import type { InjectableSettingService } from "@lib/services/implements/injectable/InjectableSettingService";
|
import type { InjectableSettingService } from "@lib/services/implements/injectable/InjectableSettingService";
|
||||||
import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
function addToList(item: string, list: string) {
|
function addToList(item: string, list: string) {
|
||||||
return unique(
|
return unique(
|
||||||
@@ -137,7 +140,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase {
|
|||||||
|
|
||||||
this._initP2PReplicator();
|
this._initP2PReplicator();
|
||||||
|
|
||||||
setTimeout(() => {
|
compatGlobal.setTimeout(() => {
|
||||||
if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) {
|
if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) {
|
||||||
void this.open();
|
void this.open();
|
||||||
}
|
}
|
||||||
@@ -164,12 +167,12 @@ export class P2PReplicatorShim implements P2PReplicatorBase {
|
|||||||
getConfig(key: string) {
|
getConfig(key: string) {
|
||||||
const vaultName = this.services.vault.getVaultName();
|
const vaultName = this.services.vault.getVaultName();
|
||||||
const dbKey = `${vaultName}-${key}`;
|
const dbKey = `${vaultName}-${key}`;
|
||||||
return localStorage.getItem(dbKey);
|
return compatGlobal.localStorage.getItem(dbKey);
|
||||||
}
|
}
|
||||||
setConfig(key: string, value: string) {
|
setConfig(key: string, value: string) {
|
||||||
const vaultName = this.services.vault.getVaultName();
|
const vaultName = this.services.vault.getVaultName();
|
||||||
const dbKey = `${vaultName}-${key}`;
|
const dbKey = `${vaultName}-${key}`;
|
||||||
localStorage.setItem(dbKey, value);
|
compatGlobal.localStorage.setItem(dbKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceName(): string {
|
getDeviceName(): string {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { mount } from "svelte";
|
import { mount } from "svelte";
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
import App from "./App.svelte";
|
import App from "./App.svelte";
|
||||||
|
import { _activeDocument } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
const app = mount(App, {
|
const app = mount(App, {
|
||||||
target: document.getElementById("app")!,
|
target: _activeDocument.getElementById("app")!,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { mount } from "svelte";
|
import { mount } from "svelte";
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
import App from "./UITest.svelte";
|
import App from "./UITest.svelte";
|
||||||
|
import { _activeDocument } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
const app = mount(App, {
|
const app = mount(App, {
|
||||||
target: document.getElementById("app")!,
|
target: _activeDocument.getElementById("app")!,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../../tsconfig.json",
|
"extends": "../../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"sourceRoot": "../",
|
// "sourceRoot": "../",
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export async function generateReport(settings: ObsidianLiveSyncSettings, core: L
|
|||||||
const r = await requestToCouchDBWithCredentials(
|
const r = await requestToCouchDBWithCredentials(
|
||||||
settings.couchDB_URI,
|
settings.couchDB_URI,
|
||||||
credential,
|
credential,
|
||||||
window.origin,
|
compatGlobal.origin,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
|
|||||||
+2
-2
@@ -132,7 +132,7 @@ export const _requestToCouchDBFetch = async (
|
|||||||
method?: string
|
method?: string
|
||||||
) => {
|
) => {
|
||||||
const utf8str = String.fromCharCode.apply(null, [...writeString(`${username}:${password}`)]);
|
const utf8str = String.fromCharCode.apply(null, [...writeString(`${username}:${password}`)]);
|
||||||
const encoded = window.btoa(utf8str);
|
const encoded = compatGlobal.btoa(utf8str);
|
||||||
const authHeader = "Basic " + encoded;
|
const authHeader = "Basic " + encoded;
|
||||||
const transformedHeaders: Record<string, string> = {
|
const transformedHeaders: Record<string, string> = {
|
||||||
authorization: authHeader,
|
authorization: authHeader,
|
||||||
@@ -214,7 +214,7 @@ import { BASE_IS_NEW, EVEN, TARGET_IS_NEW } from "@lib/common/models/shared.cons
|
|||||||
export { BASE_IS_NEW, EVEN, TARGET_IS_NEW };
|
export { BASE_IS_NEW, EVEN, TARGET_IS_NEW };
|
||||||
// Why 2000? : ZIP FILE Does not have enough resolution.
|
// Why 2000? : ZIP FILE Does not have enough resolution.
|
||||||
import { compareMTime } from "@lib/common/utils.ts";
|
import { compareMTime } from "@lib/common/utils.ts";
|
||||||
import { _fetch } from "@lib/common/coreEnvFunctions.ts";
|
import { _fetch, compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
export { compareMTime };
|
export { compareMTime };
|
||||||
function getKey(file: AnyEntry | string | UXFileInfoStub) {
|
function getKey(file: AnyEntry | string | UXFileInfoStub) {
|
||||||
const key = typeof file == "string" ? file : stripAllPrefixes(file.path);
|
const key = typeof file == "string" ? file : stripAllPrefixes(file.path);
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ export class PluginDialogModal extends Modal {
|
|||||||
|
|
||||||
override onOpen() {
|
override onOpen() {
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
this.contentEl.style.overflow = "auto";
|
this.contentEl.setCssStyles({
|
||||||
this.contentEl.style.display = "flex";
|
overflow: "auto",
|
||||||
this.contentEl.style.flexDirection = "column";
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
});
|
||||||
this.titleEl.setText("Customization Sync (Beta3)");
|
this.titleEl.setText("Customization Sync (Beta3)");
|
||||||
if (!this.component) {
|
if (!this.component) {
|
||||||
this.component = mount(PluginPane, {
|
this.component = mount(PluginPane, {
|
||||||
|
|||||||
+1
-1
Submodule src/lib updated: 2accfbce49...c926417f82
@@ -182,9 +182,9 @@ export class ModuleConflictResolver extends AbstractModule {
|
|||||||
revs.map(async (rev) => {
|
revs.map(async (rev) => {
|
||||||
const leaf = await this.core.databaseFileAccess.fetchEntryMeta(filename, rev);
|
const leaf = await this.core.databaseFileAccess.fetchEntryMeta(filename, rev);
|
||||||
if (leaf == false) {
|
if (leaf == false) {
|
||||||
return [0, rev] as [number, string];
|
return [0, rev];
|
||||||
}
|
}
|
||||||
return [leaf.mtime, rev] as [number, string];
|
return [leaf.mtime, rev];
|
||||||
})
|
})
|
||||||
)),
|
)),
|
||||||
] as [number, string][]
|
] as [number, string][]
|
||||||
|
|||||||
@@ -192,8 +192,10 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
|
|||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
this.titleEl.setText(this.title);
|
this.titleEl.setText(this.title);
|
||||||
const div = contentEl.createDiv();
|
const div = contentEl.createDiv();
|
||||||
div.style.userSelect = "text";
|
div.setCssStyles({
|
||||||
div.style["webkitUserSelect"] = "text";
|
userSelect: "text",
|
||||||
|
webkitUserSelect: "text",
|
||||||
|
});
|
||||||
void MarkdownRenderer.render(this.plugin.app, this.contentMd, div, "/", this.plugin);
|
void MarkdownRenderer.render(this.plugin.app, this.contentMd, div, "/", this.plugin);
|
||||||
const buttonSetting = new Setting(contentEl);
|
const buttonSetting = new Setting(contentEl);
|
||||||
const labelWrapper = contentEl.createDiv();
|
const labelWrapper = contentEl.createDiv();
|
||||||
@@ -202,21 +204,23 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
|
|||||||
labelEl.addClass("sls-dialogue-note-countdown");
|
labelEl.addClass("sls-dialogue-note-countdown");
|
||||||
if (!this.timeout || !this.timer) {
|
if (!this.timeout || !this.timer) {
|
||||||
labelWrapper.empty();
|
labelWrapper.empty();
|
||||||
labelWrapper.style.display = "none";
|
labelWrapper.setCssStyles({ display: "none" });
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonSetting.infoEl.style.display = "none";
|
buttonSetting.infoEl.setCssStyles({ display: "none" });
|
||||||
buttonSetting.controlEl.style.flexWrap = "wrap";
|
buttonSetting.controlEl.setCssStyles({ flexWrap: "wrap" });
|
||||||
if (this.wideButton) {
|
if (this.wideButton) {
|
||||||
buttonSetting.controlEl.style.flexDirection = "column";
|
buttonSetting.controlEl.setCssStyles({
|
||||||
buttonSetting.controlEl.style.alignItems = "center";
|
flexDirection: "column",
|
||||||
buttonSetting.controlEl.style.justifyContent = "center";
|
alignItems: "center",
|
||||||
buttonSetting.controlEl.style.flexGrow = "1";
|
justifyContent: "center",
|
||||||
|
flexGrow: "1",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
contentEl.addEventListener("click", () => {
|
contentEl.addEventListener("click", () => {
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
labelWrapper.empty();
|
labelWrapper.empty();
|
||||||
labelWrapper.style.display = "none";
|
labelWrapper.setCssStyles({ display: "none" });
|
||||||
compatGlobal.clearInterval(this.timer);
|
compatGlobal.clearInterval(this.timer);
|
||||||
this.timer = undefined;
|
this.timer = undefined;
|
||||||
this.defaultButtonComponent?.setButtonText(`${this.defaultAction}`);
|
this.defaultButtonComponent?.setButtonText(`${this.defaultAction}`);
|
||||||
@@ -238,8 +242,10 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
|
|||||||
btn.setCta();
|
btn.setCta();
|
||||||
}
|
}
|
||||||
if (this.wideButton) {
|
if (this.wideButton) {
|
||||||
btn.buttonEl.style.flexGrow = "1";
|
btn.buttonEl.setCssStyles({
|
||||||
btn.buttonEl.style.width = "100%";
|
flexGrow: "1",
|
||||||
|
width: "100%",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return btn;
|
return btn;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { FetchHttpHandler, type FetchHttpHandlerOptions } from "@smithy/fetch-ht
|
|||||||
import { HttpRequest, HttpResponse, type HttpHandlerOptions } from "@smithy/protocol-http";
|
import { HttpRequest, HttpResponse, type HttpHandlerOptions } from "@smithy/protocol-http";
|
||||||
import { buildQueryString } from "@smithy/querystring-builder";
|
import { buildQueryString } from "@smithy/querystring-builder";
|
||||||
import { requestUrl, type RequestUrlParam } from "@/deps.ts";
|
import { requestUrl, type RequestUrlParam } from "@/deps.ts";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// special handler using Obsidian requestUrl
|
// special handler using Obsidian requestUrl
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -14,7 +16,7 @@ import { requestUrl, type RequestUrlParam } from "@/deps.ts";
|
|||||||
function requestTimeout(timeoutInMs: number = 0): Promise<never> {
|
function requestTimeout(timeoutInMs: number = 0): Promise<never> {
|
||||||
return new Promise((_, reject) => {
|
return new Promise((_, reject) => {
|
||||||
if (timeoutInMs) {
|
if (timeoutInMs) {
|
||||||
window.setTimeout(() => {
|
compatGlobal.setTimeout(() => {
|
||||||
const timeoutError = new Error(`Request did not complete within ${timeoutInMs} ms`);
|
const timeoutError = new Error(`Request did not complete within ${timeoutInMs} ms`);
|
||||||
timeoutError.name = "TimeoutError";
|
timeoutError.name = "TimeoutError";
|
||||||
reject(timeoutError);
|
reject(timeoutError);
|
||||||
|
|||||||
@@ -63,12 +63,12 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
const _this = this;
|
const _this = this;
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
if (!window.CodeMirrorAdapter) {
|
if (!compatGlobal.CodeMirrorAdapter) {
|
||||||
this._log("CodeMirrorAdapter is not available");
|
this._log("CodeMirrorAdapter is not available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
window.CodeMirrorAdapter.commands.save = () => {
|
compatGlobal.CodeMirrorAdapter.commands.save = () => {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
void _this.app.commands.executeCommandById("editor:save-file");
|
void _this.app.commands.executeCommandById("editor:save-file");
|
||||||
// _this.app.performCommand('editor:save-file');
|
// _this.app.performCommand('editor:save-file');
|
||||||
@@ -86,14 +86,14 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
|
|||||||
// Already bound
|
// Already bound
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
this.plugin.registerDomEvent(activeDocument, "visibilitychange", this.watchWindowVisibility);
|
this.plugin.registerDomEvent(activeDocument, "visibilitychange", this.watchWindowVisibility);
|
||||||
this.plugin.registerDomEvent(window, "focus", () => this.setHasFocus(true));
|
this.plugin.registerDomEvent(compatGlobal, "focus", () => this.setHasFocus(true));
|
||||||
this.plugin.registerDomEvent(window, "blur", () => this.setHasFocus(false));
|
this.plugin.registerDomEvent(compatGlobal, "blur", () => this.setHasFocus(false));
|
||||||
// Already bound
|
// Already bound
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
this.plugin.registerDomEvent(window, "online", this.watchOnline);
|
this.plugin.registerDomEvent(compatGlobal, "online", this.watchOnline);
|
||||||
// Already bound
|
// Already bound
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
this.plugin.registerDomEvent(window, "offline", this.watchOnline);
|
this.plugin.registerDomEvent(compatGlobal, "offline", this.watchOnline);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasFocus = true;
|
hasFocus = true;
|
||||||
@@ -114,7 +114,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
|
|||||||
async watchOnlineAsync() {
|
async watchOnlineAsync() {
|
||||||
// If some files were failed to retrieve, scan files again.
|
// If some files were failed to retrieve, scan files again.
|
||||||
// TODO:FIXME AT V0.17.31, this logic has been disabled.
|
// TODO:FIXME AT V0.17.31, this logic has been disabled.
|
||||||
if (navigator.onLine && this.localDatabase.needScanning) {
|
if (compatGlobal.navigator.onLine && this.localDatabase.needScanning) {
|
||||||
this.localDatabase.needScanning = false;
|
this.localDatabase.needScanning = false;
|
||||||
await this.services.vault.scanVault();
|
await this.services.vault.scanVault();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -367,10 +367,10 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
*/
|
*/
|
||||||
updateDiffNavVisibility() {
|
updateDiffNavVisibility() {
|
||||||
if (this.diffNavContainer) {
|
if (this.diffNavContainer) {
|
||||||
this.diffNavContainer.style.display = this.showDiff ? "flex" : "none";
|
this.diffNavContainer.setCssStyles({ display: this.showDiff ? "flex" : "none" });
|
||||||
}
|
}
|
||||||
if (this.diffOnlyLabel) {
|
if (this.diffOnlyLabel) {
|
||||||
this.diffOnlyLabel.style.display = this.showDiff ? "inline-block" : "none";
|
this.diffOnlyLabel.setCssStyles({ display: this.showDiff ? "inline-block" : "none" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,13 +573,13 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
});
|
});
|
||||||
diffOnlyLabel.appendText("Diff only");
|
diffOnlyLabel.appendText("Diff only");
|
||||||
diffOnlyLabel.addClass("diff-only-label");
|
diffOnlyLabel.addClass("diff-only-label");
|
||||||
diffOnlyLabel.style.display = this.showDiff ? "inline-block" : "none";
|
diffOnlyLabel.setCssStyles({ display: this.showDiff ? "inline-block" : "none" });
|
||||||
this.diffOnlyLabel = diffOnlyLabel;
|
this.diffOnlyLabel = diffOnlyLabel;
|
||||||
|
|
||||||
// Diff navigation buttons
|
// Diff navigation buttons
|
||||||
this.diffNavContainer = diffOptionsRow.createDiv("");
|
this.diffNavContainer = diffOptionsRow.createDiv("");
|
||||||
this.diffNavContainer.addClass("diff-nav");
|
this.diffNavContainer.addClass("diff-nav");
|
||||||
this.diffNavContainer.style.display = this.showDiff ? "flex" : "none";
|
this.diffNavContainer.setCssStyles({ display: this.showDiff ? "flex" : "none" });
|
||||||
|
|
||||||
this.diffNavContainer.createEl("button", { text: "\u25B2 Prev" }, (e) => {
|
this.diffNavContainer.createEl("button", { text: "\u25B2 Prev" }, (e) => {
|
||||||
e.addClass("diff-nav-btn");
|
e.addClass("diff-nav-btn");
|
||||||
@@ -608,7 +608,7 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
e.addClass("mod-cta");
|
e.addClass("mod-cta");
|
||||||
e.addEventListener("click", () => {
|
e.addEventListener("click", () => {
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
await navigator.clipboard.writeText(this.currentText);
|
await compatGlobal.navigator.clipboard.writeText(this.currentText);
|
||||||
Logger(`Old content copied to clipboard`, LOG_LEVEL_NOTICE);
|
Logger(`Old content copied to clipboard`, LOG_LEVEL_NOTICE);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
|
|
||||||
const showStatusOnEditor = this.settings?.showStatusOnEditor ?? false;
|
const showStatusOnEditor = this.settings?.showStatusOnEditor ?? false;
|
||||||
if (this.statusDiv) {
|
if (this.statusDiv) {
|
||||||
this.statusDiv.style.display = showStatusOnEditor ? "" : "none";
|
this.statusDiv.setCssStyles({ display: showStatusOnEditor ? "" : "none" });
|
||||||
}
|
}
|
||||||
if (!showStatusOnEditor) {
|
if (!showStatusOnEditor) {
|
||||||
this.messageArea.innerText = "";
|
this.messageArea.innerText = "";
|
||||||
@@ -351,7 +351,7 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
nextFrameQueue: ReturnType<typeof requestAnimationFrame> | undefined = undefined;
|
nextFrameQueue: ReturnType<typeof compatGlobal.requestAnimationFrame> | undefined = undefined;
|
||||||
logLines: { ttl: number; message: string }[] = [];
|
logLines: { ttl: number; message: string }[] = [];
|
||||||
|
|
||||||
applyStatusBarText() {
|
applyStatusBarText() {
|
||||||
@@ -371,7 +371,7 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
|
|
||||||
this.statusBar?.setText(newMsg.split("\n")[0]);
|
this.statusBar?.setText(newMsg.split("\n")[0]);
|
||||||
if (this.statusDiv) {
|
if (this.statusDiv) {
|
||||||
this.statusDiv.style.display = this.settings?.showStatusOnEditor ? "" : "none";
|
this.statusDiv.setCssStyles({ display: this.settings?.showStatusOnEditor ? "" : "none" });
|
||||||
}
|
}
|
||||||
if (this.settings?.showStatusOnEditor && this.statusDiv) {
|
if (this.settings?.showStatusOnEditor && this.statusDiv) {
|
||||||
if (this.settings.showLongerLogInsideEditor) {
|
if (this.settings.showLongerLogInsideEditor) {
|
||||||
@@ -472,7 +472,7 @@ ${stringifyYaml(info)}
|
|||||||
this.messageArea = this.statusDiv.createDiv({ cls: "livesync-status-messagearea" });
|
this.messageArea = this.statusDiv.createDiv({ cls: "livesync-status-messagearea" });
|
||||||
this.logMessage = this.statusDiv.createDiv({ cls: "livesync-status-logmessage" });
|
this.logMessage = this.statusDiv.createDiv({ cls: "livesync-status-logmessage" });
|
||||||
this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" });
|
this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" });
|
||||||
this.statusDiv.style.display = this.settings?.showStatusOnEditor ? "" : "none";
|
this.statusDiv.setCssStyles({ display: this.settings?.showStatusOnEditor ? "" : "none" });
|
||||||
}
|
}
|
||||||
eventHub.onEvent(EVENT_LAYOUT_READY, () => this.adjustStatusDivPosition());
|
eventHub.onEvent(EVENT_LAYOUT_READY, () => this.adjustStatusDivPosition());
|
||||||
if (this.settings?.showStatusOnStatusbar) {
|
if (this.settings?.showStatusOnStatusbar) {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export function paneSetup(
|
|||||||
cls: "sls-troubleshoot-preview",
|
cls: "sls-troubleshoot-preview",
|
||||||
});
|
});
|
||||||
const loadMarkdownPage = async (pathAll: string, basePathParam: string = "") => {
|
const loadMarkdownPage = async (pathAll: string, basePathParam: string = "") => {
|
||||||
troubleShootEl.style.minHeight = troubleShootEl.clientHeight + "px";
|
troubleShootEl.setCssStyles({ minHeight: troubleShootEl.clientHeight + "px" });
|
||||||
troubleShootEl.empty();
|
troubleShootEl.empty();
|
||||||
const fullPath = pathAll.startsWith("/") ? pathAll : `${basePathParam}/${pathAll}`;
|
const fullPath = pathAll.startsWith("/") ? pathAll : `${basePathParam}/${pathAll}`;
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ export function paneSetup(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
troubleShootEl.style.minHeight = "";
|
troubleShootEl.setCssStyles({ minHeight: "" });
|
||||||
};
|
};
|
||||||
void loadMarkdownPage(topPath);
|
void loadMarkdownPage(topPath);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { ObsidianLiveSyncSettings } from "@lib/common/types";
|
|||||||
import { fireAndForget, parseHeaderValues } from "@lib/common/utils";
|
import { fireAndForget, parseHeaderValues } from "@lib/common/utils";
|
||||||
import { isCloudantURI } from "@lib/pouchdb/utils_couchdb";
|
import { isCloudantURI } from "@lib/pouchdb/utils_couchdb";
|
||||||
import { generateCredentialObject } from "@lib/replication/httplib";
|
import { generateCredentialObject } from "@lib/replication/httplib";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
export const checkConfig = async (
|
export const checkConfig = async (
|
||||||
checkResultDiv: HTMLDivElement | undefined,
|
checkResultDiv: HTMLDivElement | undefined,
|
||||||
@@ -35,7 +36,7 @@ export const checkConfig = async (
|
|||||||
const r = await requestToCouchDBWithCredentials(
|
const r = await requestToCouchDBWithCredentials(
|
||||||
editingSettings.couchDB_URI,
|
editingSettings.couchDB_URI,
|
||||||
credential,
|
credential,
|
||||||
window.origin,
|
compatGlobal.origin,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -218,7 +219,7 @@ export const checkConfig = async (
|
|||||||
isSuccessful = false;
|
isSuccessful = false;
|
||||||
}
|
}
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.msgConnectionCheck"), ["ob-btn-config-head"]);
|
addResult($msg("obsidianLiveSyncSettingTab.msgConnectionCheck"), ["ob-btn-config-head"]);
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.msgCurrentOrigin", { origin: window.location.origin }));
|
addResult($msg("obsidianLiveSyncSettingTab.msgCurrentOrigin", { origin: compatGlobal.location.origin }));
|
||||||
|
|
||||||
// Request header check
|
// Request header check
|
||||||
const origins = ["app://obsidian.md", "capacitor://localhost", "http://localhost"];
|
const origins = ["app://obsidian.md", "capacitor://localhost", "http://localhost"];
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import type { ObsidianLiveSyncSettings } from "@lib/common/types";
|
|||||||
import { parseHeaderValues } from "@lib/common/utils";
|
import { parseHeaderValues } from "@lib/common/utils";
|
||||||
import { isCloudantURI } from "@lib/pouchdb/utils_couchdb";
|
import { isCloudantURI } from "@lib/pouchdb/utils_couchdb";
|
||||||
import { generateCredentialObject } from "@lib/replication/httplib";
|
import { generateCredentialObject } from "@lib/replication/httplib";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
export type ResultMessage = { message: string; classes: string[] };
|
export type ResultMessage = { message: string; classes: string[] };
|
||||||
export type ResultErrorMessage = { message: string; result: "error"; classes: string[] };
|
export type ResultErrorMessage = { message: string; result: "error"; classes: string[] };
|
||||||
export type ResultOk = { message: string; result: "ok"; value?: any };
|
export type ResultOk = { message: string; result: "ok"; value?: any };
|
||||||
@@ -93,7 +95,7 @@ export const checkConfig = async (editingSettings: ObsidianLiveSyncSettings) =>
|
|||||||
const r = await requestToCouchDBWithCredentials(
|
const r = await requestToCouchDBWithCredentials(
|
||||||
editingSettings.couchDB_URI,
|
editingSettings.couchDB_URI,
|
||||||
credential,
|
credential,
|
||||||
window.origin,
|
compatGlobal.origin,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -239,7 +241,7 @@ export const checkConfig = async (editingSettings: ObsidianLiveSyncSettings) =>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
addMessage($msg("obsidianLiveSyncSettingTab.msgConnectionCheck"), ["ob-btn-config-head"]);
|
addMessage($msg("obsidianLiveSyncSettingTab.msgConnectionCheck"), ["ob-btn-config-head"]);
|
||||||
addMessage($msg("obsidianLiveSyncSettingTab.msgCurrentOrigin", { origin: window.location.origin }));
|
addMessage($msg("obsidianLiveSyncSettingTab.msgCurrentOrigin", { origin: compatGlobal.location.origin }));
|
||||||
|
|
||||||
// Request header check
|
// Request header check
|
||||||
const origins = ["app://obsidian.md", "capacitor://localhost", "http://localhost"];
|
const origins = ["app://obsidian.md", "capacitor://localhost", "http://localhost"];
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# Refactoring and Code Quality Utilities
|
||||||
|
|
||||||
|
This directory contains Deno-based scripts that utilise `ts-morph` to perform codebase-wide refactoring, code quality clean-up, and static analysis.
|
||||||
|
|
||||||
|
These utilities are designed to help maintain code quality, resolve compiler warnings, and ensure popout window compatibility in the Obsidian plug-in environment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
To execute these scripts, you must have Deno installed on your system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## General Usage
|
||||||
|
|
||||||
|
By default, all refactoring scripts run in **dry-run mode**. They will output the proposed changes to the console without modifying any files.
|
||||||
|
|
||||||
|
To apply the changes to the files, append the `'--run'` flag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
deno run --allow-read --allow-write --allow-env <script_name>.ts --run
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Utilities Reference
|
||||||
|
|
||||||
|
### 1. Global Wrapper Refactoring (`refactor-globals.ts`)
|
||||||
|
Converts standard global variable usages to compatibility wrappers to ensure safe operation when running in Obsidian popout windows (which run in separate window contexts).
|
||||||
|
|
||||||
|
* **Targets**: `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval`, `requestAnimationFrame`, `cancelAnimationFrame`, `localStorage`, `navigator`, `location`, `window`, `globalThis`, and `document`.
|
||||||
|
* **Actions**:
|
||||||
|
* Replaces global namespace references (like `window` and `globalThis`) with `compatGlobal`.
|
||||||
|
* Replaces `document` with `_activeDocument` (from `@lib/common/coreEnvFunctions.ts`).
|
||||||
|
* Injects or updates the necessary imports in modified files.
|
||||||
|
* **Command**:
|
||||||
|
```bash
|
||||||
|
deno run --allow-read --allow-write --allow-env refactor-globals.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Element Style Normalisation (`refactor-styles.ts`)
|
||||||
|
Converts direct style assignments on HTML/SVG elements to use the plug-in's `setCssStyles` helper.
|
||||||
|
|
||||||
|
* **Actions**:
|
||||||
|
* Replaces statements like `element.style.color = 'red';` with `element.setCssStyles({ color: 'red' });`.
|
||||||
|
* Groups multiple consecutive style assignments on the same element into a single call.
|
||||||
|
* Supports both static keys and computed bracket properties.
|
||||||
|
* **Command**:
|
||||||
|
```bash
|
||||||
|
deno run --allow-read --allow-write --allow-env refactor-styles.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Redundant Assertions Cleanup (`refactor-assertions.ts`)
|
||||||
|
Finds and removes type assertions that are redundant because the expression already evaluates to the asserted type.
|
||||||
|
|
||||||
|
* **Actions**:
|
||||||
|
* Removes redundant `as Type` or `<Type>` assertions.
|
||||||
|
* Preserves critical literal assertions such as `as const` and `<const>`.
|
||||||
|
* **Command**:
|
||||||
|
```bash
|
||||||
|
deno run --allow-read --allow-write --allow-env refactor-assertions.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Unused Code Refactoring (`refactor-unused.ts`)
|
||||||
|
Cleans up unused imports and catch variables to reduce bundle size and warnings.
|
||||||
|
|
||||||
|
* **Actions**:
|
||||||
|
* Converts unused catch variables to simple catch statements (e.g. `catch (error)` -> `catch`).
|
||||||
|
* Removes unused items in named imports, handling alias bindings (e.g. `import { A as B }`) correctly.
|
||||||
|
* Deletes empty import declarations resulting from the named import clean-up.
|
||||||
|
* **Command**:
|
||||||
|
```bash
|
||||||
|
deno run --allow-read --allow-write --allow-env refactor-unused.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Explicit Any Detection (`detect-any.ts`)
|
||||||
|
Scans the codebase and logs all occurrences of explicit `any` types.
|
||||||
|
|
||||||
|
* **Actions**:
|
||||||
|
* Identifies uses of the `any` keyword in TypeScript and Svelte files.
|
||||||
|
* Logs the filename, line number, and matching code line for audit purposes.
|
||||||
|
* **Command**:
|
||||||
|
```bash
|
||||||
|
deno run --allow-read --allow-env detect-any.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Import Normalisation (`normalise-imports.ts`)
|
||||||
|
Ensures that all import statements are standardised across the codebase, resolving paths to aliases such as `@lib/` and `@/` where applicable.
|
||||||
|
|
||||||
|
* **Command**:
|
||||||
|
```bash
|
||||||
|
deno run --allow-read --allow-write --allow-env normalise-imports.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. CLI Node.js Import Redirection (`refactor-cli-node-imports.ts`)
|
||||||
|
Redirects direct Node.js built-in module imports (like `fs` and `path`) within the CLI codebase to use a single barrel file (`src/apps/cli/node-compat.ts`).
|
||||||
|
|
||||||
|
* **Actions**:
|
||||||
|
* Finds imports of Node.js built-in APIs (`fs`, `fs/promises`, `path`, and `readline/promises`) in CLI source files.
|
||||||
|
* Replaces them with imports from the local `node-compat.ts` barrel file.
|
||||||
|
* This eliminates duplicate browser-targeted linter warnings on Node.js built-ins in the CLI workspace, keeping linter ignores consolidated.
|
||||||
|
* **Command**:
|
||||||
|
```bash
|
||||||
|
deno run --allow-read --allow-write --allow-env refactor-cli-node-imports.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Safety and Exclusions
|
||||||
|
|
||||||
|
* **Tests Excluded**: All scripts automatically skip files located in `_test/` or `testdeno/` folders, as well as files ending with `.spec.ts` or `.test.ts`.
|
||||||
|
* **Submodule Caution**: Some tools will run against the `src/lib/` submodule. Ensure you verify changes inside the submodule prior to committing.
|
||||||
|
* **Verification**: Always run `npm run check` and `npm run test:unit` after performing refactoring tasks to verify that type safety and tests remain intact.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Detect explicit usage of 'any' type in the codebase.
|
||||||
|
// Use this script by running `deno run --allow-read --allow-env detect-any.ts` from the utilsdeno directory.
|
||||||
|
import { Project, SyntaxKind } from "npm:ts-morph";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const project = new Project({ tsConfigFilePath: "../tsconfig.json" });
|
||||||
|
project.addSourceFilesAtPaths("../src/**/*.ts");
|
||||||
|
project.addSourceFilesAtPaths("../src/**/*.svelte");
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const projectRoot = path.resolve(__dirname, "..");
|
||||||
|
|
||||||
|
function toPosixPath(filePath: string): string {
|
||||||
|
return filePath.replace(/\\/g, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
const posixProjectRoot = toPosixPath(projectRoot);
|
||||||
|
const posixSrc = `${posixProjectRoot}/src`;
|
||||||
|
|
||||||
|
let anyCount = 0;
|
||||||
|
|
||||||
|
for (const sourceFile of project.getSourceFiles()) {
|
||||||
|
const filePath = sourceFile.getFilePath();
|
||||||
|
const posixFilePath = toPosixPath(filePath);
|
||||||
|
|
||||||
|
if (!posixFilePath.startsWith(posixSrc)) continue;
|
||||||
|
if (
|
||||||
|
posixFilePath.includes("/_test/") ||
|
||||||
|
posixFilePath.endsWith(".spec.ts") ||
|
||||||
|
posixFilePath.endsWith(".test.ts")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const anyNodes = sourceFile.getDescendantsOfKind(SyntaxKind.AnyKeyword);
|
||||||
|
if (anyNodes.length > 0) {
|
||||||
|
console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`);
|
||||||
|
for (const anyNode of anyNodes) {
|
||||||
|
const { line } = sourceFile.getLineAndColumnAtPos(anyNode.getStart());
|
||||||
|
const lineText = sourceFile.getFullText().split(/\r?\n/)[line - 1];
|
||||||
|
console.log(` Line ${line}: ${lineText.trim()}`);
|
||||||
|
anyCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\nTotal explicit 'any' usages found: ${anyCount}`);
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// Refactor unnecessary type assertions (e.g. `expr as Type` where type of `expr` is already `Type`).
|
||||||
|
// Use this script by running `deno run --allow-read --allow-write --allow-env refactor-assertions.ts` from the utilsdeno directory.
|
||||||
|
// Run with --run flag to apply changes.
|
||||||
|
import { Project, SyntaxKind, Node } from "npm:ts-morph";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const isDryRun = !Deno.args.includes("--run");
|
||||||
|
|
||||||
|
if (isDryRun) {
|
||||||
|
console.log("=== DRY RUN MODE ===");
|
||||||
|
console.log(
|
||||||
|
"To apply changes, run with: deno run --allow-read --allow-write --allow-env refactor-assertions.ts --run\n"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("=== RUN MODE: WILL MODIFY FILES ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = new Project({ tsConfigFilePath: "../tsconfig.json" });
|
||||||
|
project.addSourceFilesAtPaths("../src/**/*.ts");
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const projectRoot = path.resolve(__dirname, "..");
|
||||||
|
|
||||||
|
function toPosixPath(filePath: string): string {
|
||||||
|
return filePath.replace(/\\/g, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
const posixProjectRoot = toPosixPath(projectRoot);
|
||||||
|
const posixSrc = `${posixProjectRoot}/src`;
|
||||||
|
|
||||||
|
let modifiedFilesCount = 0;
|
||||||
|
|
||||||
|
for (const sourceFile of project.getSourceFiles()) {
|
||||||
|
const filePath = sourceFile.getFilePath();
|
||||||
|
const posixFilePath = toPosixPath(filePath);
|
||||||
|
|
||||||
|
if (!posixFilePath.startsWith(posixSrc)) continue;
|
||||||
|
if (posixFilePath.includes("/_test/") || posixFilePath.endsWith(".spec.ts") || posixFilePath.endsWith(".test.ts")) continue;
|
||||||
|
|
||||||
|
// Find AsExpression (expr as Type) and TypeAssertion (<Type>expr)
|
||||||
|
const asExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.AsExpression);
|
||||||
|
const typeAssertions = sourceFile.getDescendantsOfKind(SyntaxKind.TypeAssertion);
|
||||||
|
const allAssertions = [...asExpressions, ...typeAssertions];
|
||||||
|
|
||||||
|
const nodesToRemove: Node[] = [];
|
||||||
|
|
||||||
|
for (const node of allAssertions) {
|
||||||
|
const expr = node.getExpression();
|
||||||
|
const exprType = expr.getType();
|
||||||
|
const assertType = node.getType();
|
||||||
|
|
||||||
|
// Skip `as const` or `<const>` assertions
|
||||||
|
const typeNode = (node as any).getTypeNode?.();
|
||||||
|
if (typeNode && typeNode.getText() === "const") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare type texts to find redundant assertions
|
||||||
|
const exprTypeText = exprType.getText();
|
||||||
|
const assertTypeText = assertType.getText();
|
||||||
|
|
||||||
|
if (exprTypeText === assertTypeText) {
|
||||||
|
nodesToRemove.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodesToRemove.length > 0) {
|
||||||
|
console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`);
|
||||||
|
|
||||||
|
// Reverse nodes order to keep indices/references valid when modifying
|
||||||
|
const sortedNodes = [...nodesToRemove].sort((a, b) => b.getStart() - a.getStart());
|
||||||
|
|
||||||
|
for (const node of sortedNodes) {
|
||||||
|
const { line } = sourceFile.getLineAndColumnAtPos(node.getStart());
|
||||||
|
const exprText = node.getExpression().getText();
|
||||||
|
console.log(` Line ${line}: "${node.getText()}" -> "${exprText}"`);
|
||||||
|
|
||||||
|
if (!isDryRun) {
|
||||||
|
node.replaceWithText(exprText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedFilesCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\nTotal files to modify: ${modifiedFilesCount}`);
|
||||||
|
|
||||||
|
if (!isDryRun) {
|
||||||
|
project.saveSync();
|
||||||
|
console.log("All changes successfully saved.");
|
||||||
|
} else {
|
||||||
|
console.log("Dry run complete. No changes were written to files.");
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
// Refactor Node.js imports in the CLI application to use the barrel compatibility file.
|
||||||
|
// Use this script by running `deno run --allow-read --allow-write --allow-env refactor-cli-node-imports.ts` from the utilsdeno directory.
|
||||||
|
// Run with --run flag to apply changes.
|
||||||
|
import { Project, SyntaxKind, Node } from "npm:ts-morph";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const isDryRun = !Deno.args.includes("--run");
|
||||||
|
|
||||||
|
if (isDryRun) {
|
||||||
|
console.log("=== DRY RUN MODE ===");
|
||||||
|
console.log(
|
||||||
|
"To apply changes, run with: deno run --allow-read --allow-write --allow-env refactor-cli-node-imports.ts --run\n"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("=== RUN MODE: WILL MODIFY FILES ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = new Project({ tsConfigFilePath: "../tsconfig.json" });
|
||||||
|
project.addSourceFilesAtPaths("../src/apps/cli/**/*.ts");
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const projectRoot = path.resolve(__dirname, "..");
|
||||||
|
const nodeCompatPath = path.resolve(projectRoot, "src", "apps", "cli", "node-compat.ts");
|
||||||
|
|
||||||
|
function toPosixPath(filePath: string): string {
|
||||||
|
return filePath.replace(/\\/g, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
const posixProjectRoot = toPosixPath(projectRoot);
|
||||||
|
const posixSrc = `${posixProjectRoot}/src`;
|
||||||
|
|
||||||
|
function getRelativeImportPath(fromFile: string, toFile: string): string {
|
||||||
|
let rel = path.relative(path.dirname(fromFile), toFile);
|
||||||
|
rel = rel.replace(/\\/g, "/");
|
||||||
|
if (!rel.startsWith(".") && !rel.startsWith("/")) {
|
||||||
|
rel = "./" + rel;
|
||||||
|
}
|
||||||
|
if (rel.endsWith(".ts")) {
|
||||||
|
rel = rel.slice(0, -3);
|
||||||
|
}
|
||||||
|
return rel;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modifiedFilesCount = 0;
|
||||||
|
|
||||||
|
for (const sourceFile of project.getSourceFiles()) {
|
||||||
|
const filePath = sourceFile.getFilePath();
|
||||||
|
const posixFilePath = toPosixPath(filePath);
|
||||||
|
|
||||||
|
// Only process CLI source files under src/apps/cli/
|
||||||
|
if (!posixFilePath.includes("/src/apps/cli/")) continue;
|
||||||
|
if (
|
||||||
|
posixFilePath.endsWith("node-compat.ts") ||
|
||||||
|
posixFilePath.endsWith("vite.config.ts") ||
|
||||||
|
posixFilePath.endsWith(".spec.ts") ||
|
||||||
|
posixFilePath.endsWith(".test.ts") ||
|
||||||
|
posixFilePath.includes("/_test/") ||
|
||||||
|
posixFilePath.includes("/testdeno/") ||
|
||||||
|
posixFilePath.includes("/test/")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const importDeclarations = sourceFile.getImportDeclarations();
|
||||||
|
const targetImports: any[] = [];
|
||||||
|
const namedImportsToAdd: string[] = [];
|
||||||
|
|
||||||
|
for (const impDecl of importDeclarations) {
|
||||||
|
const specifier = impDecl.getModuleSpecifierValue();
|
||||||
|
|
||||||
|
// Check if it's a Node.js built-in module we want to redirect
|
||||||
|
let exportedName = "";
|
||||||
|
if (specifier === "fs/promises" || specifier === "node:fs/promises") {
|
||||||
|
exportedName = "fsPromises";
|
||||||
|
} else if (specifier === "fs" || specifier === "node:fs") {
|
||||||
|
exportedName = "fs";
|
||||||
|
} else if (specifier === "path" || specifier === "node:path") {
|
||||||
|
exportedName = "path";
|
||||||
|
} else if (specifier === "node:readline/promises") {
|
||||||
|
exportedName = "readline";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportedName) {
|
||||||
|
const localName = impDecl.getNamespaceImport()?.getText() || impDecl.getDefaultImport()?.getText();
|
||||||
|
if (localName) {
|
||||||
|
targetImports.push({ impDecl, exportedName, localName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetImports.length > 0) {
|
||||||
|
console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`);
|
||||||
|
|
||||||
|
for (const { impDecl, exportedName, localName } of targetImports) {
|
||||||
|
const { line } = sourceFile.getLineAndColumnAtPos(impDecl.getStart());
|
||||||
|
console.log(` Line ${line}: Redirecting "${impDecl.getText()}"`);
|
||||||
|
|
||||||
|
if (exportedName === localName) {
|
||||||
|
namedImportsToAdd.push(exportedName);
|
||||||
|
} else {
|
||||||
|
namedImportsToAdd.push(`${exportedName} as ${localName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDryRun) {
|
||||||
|
impDecl.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const relImportPath = getRelativeImportPath(filePath, nodeCompatPath);
|
||||||
|
console.log(` Adding: import { ${namedImportsToAdd.join(", ")} } from "${relImportPath}"`);
|
||||||
|
|
||||||
|
if (!isDryRun) {
|
||||||
|
sourceFile.addImportDeclaration({
|
||||||
|
namedImports: namedImportsToAdd,
|
||||||
|
moduleSpecifier: relImportPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedFilesCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\nTotal files to modify: ${modifiedFilesCount}`);
|
||||||
|
|
||||||
|
if (!isDryRun) {
|
||||||
|
project.saveSync();
|
||||||
|
console.log("All changes successfully saved.");
|
||||||
|
} else {
|
||||||
|
console.log("Dry run complete. No changes were written to files.");
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
// Refactor global variables (setTimeout, document, navigator, etc.) to use compatGlobal.
|
||||||
|
// Use this script by running `deno run --allow-read --allow-write --allow-run refactor-globals.ts` from the utilsdeno directory.
|
||||||
|
// Run with --run flag to apply changes.
|
||||||
|
import { Project, SyntaxKind, Node } from "npm:ts-morph";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const isDryRun = !Deno.args.includes("--run");
|
||||||
|
|
||||||
|
if (isDryRun) {
|
||||||
|
console.log("=== DRY RUN MODE ===");
|
||||||
|
console.log(
|
||||||
|
"To apply changes, run with: deno run --allow-read --allow-write --allow-run refactor-globals.ts --run\n"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("=== RUN MODE: WILL MODIFY FILES ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = new Project({ tsConfigFilePath: "../tsconfig.json" });
|
||||||
|
|
||||||
|
// Manually add files under src/ to ensure those excluded by tsconfig.json are processed if needed.
|
||||||
|
project.addSourceFilesAtPaths("../src/**/*.ts");
|
||||||
|
project.addSourceFilesAtPaths("../src/**/*.svelte");
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const projectRoot = path.resolve(__dirname, "..");
|
||||||
|
|
||||||
|
function toPosixPath(filePath: string): string {
|
||||||
|
return filePath.replace(/\\/g, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
const posixProjectRoot = toPosixPath(projectRoot);
|
||||||
|
const posixSrc = `${posixProjectRoot}/src`;
|
||||||
|
const posixLibSrc = `${posixProjectRoot}/src/lib`;
|
||||||
|
|
||||||
|
const TARGET_GLOBALS = new Set([
|
||||||
|
"setTimeout",
|
||||||
|
"clearTimeout",
|
||||||
|
"setInterval",
|
||||||
|
"clearInterval",
|
||||||
|
"requestAnimationFrame",
|
||||||
|
"cancelAnimationFrame",
|
||||||
|
"localStorage",
|
||||||
|
"navigator",
|
||||||
|
"location",
|
||||||
|
"document",
|
||||||
|
"window",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let modifiedFilesCount = 0;
|
||||||
|
|
||||||
|
for (const sourceFile of project.getSourceFiles()) {
|
||||||
|
const filePath = sourceFile.getFilePath();
|
||||||
|
const posixFilePath = toPosixPath(filePath);
|
||||||
|
|
||||||
|
// Only process files inside the project src directory.
|
||||||
|
if (!posixFilePath.startsWith(posixSrc)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude coreEnvFunctions.ts to avoid self-referential definitions
|
||||||
|
if (posixFilePath.endsWith("/coreEnvFunctions.ts") || posixFilePath.endsWith("/coreEnvFunctions")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude unit and integration test files
|
||||||
|
if (
|
||||||
|
posixFilePath.endsWith(".spec.ts") ||
|
||||||
|
posixFilePath.endsWith(".test.ts") ||
|
||||||
|
posixFilePath.includes("/_test/") ||
|
||||||
|
posixFilePath.includes("/testdeno/")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all identifier nodes
|
||||||
|
const identifiers = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier);
|
||||||
|
const nodesToReplace: { node: Node; replacement: string }[] = [];
|
||||||
|
|
||||||
|
for (const idNode of identifiers) {
|
||||||
|
const name = idNode.getText();
|
||||||
|
if (!TARGET_GLOBALS.has(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = idNode.getParent();
|
||||||
|
if (!parent) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Skip if it is the property name in a PropertyAccessExpression (e.g. the "setTimeout" in "obj.setTimeout")
|
||||||
|
if (parent.getKind() === SyntaxKind.PropertyAccessExpression) {
|
||||||
|
const propAccess = parent.asKindOrThrow(SyntaxKind.PropertyAccessExpression);
|
||||||
|
if (propAccess.getNameNode() === idNode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.5. Skip if it is the right-hand side of a QualifiedName (e.g. the "requestAnimationFrame" in "typeof compatGlobal.requestAnimationFrame")
|
||||||
|
if (parent.getKind() === SyntaxKind.QualifiedName) {
|
||||||
|
const qualified = parent.asKindOrThrow(SyntaxKind.QualifiedName);
|
||||||
|
if (qualified.getRight() === idNode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Skip if it is the operand of a typeof expression (e.g. "typeof window")
|
||||||
|
if (parent.getKind() === SyntaxKind.TypeOfExpression) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Skip if it is a declaration name node
|
||||||
|
const kind = parent.getKind();
|
||||||
|
if (
|
||||||
|
kind === SyntaxKind.VariableDeclaration ||
|
||||||
|
kind === SyntaxKind.Parameter ||
|
||||||
|
kind === SyntaxKind.FunctionDeclaration ||
|
||||||
|
kind === SyntaxKind.MethodDeclaration ||
|
||||||
|
kind === SyntaxKind.PropertyDeclaration ||
|
||||||
|
kind === SyntaxKind.ClassDeclaration ||
|
||||||
|
kind === SyntaxKind.InterfaceDeclaration ||
|
||||||
|
kind === SyntaxKind.TypeAliasDeclaration ||
|
||||||
|
kind === SyntaxKind.ImportSpecifier ||
|
||||||
|
kind === SyntaxKind.ExportSpecifier ||
|
||||||
|
kind === SyntaxKind.MethodSignature ||
|
||||||
|
kind === SyntaxKind.PropertySignature ||
|
||||||
|
kind === SyntaxKind.PropertyAssignment
|
||||||
|
) {
|
||||||
|
if ((parent as any).getNameNode?.() === idNode || (parent as any).getName?.() === name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Verify it is a global variable reference using definitions
|
||||||
|
let isGlobal = false;
|
||||||
|
try {
|
||||||
|
const definitions = idNode.getDefinitions();
|
||||||
|
isGlobal =
|
||||||
|
definitions.length === 0 ||
|
||||||
|
definitions.every((def) => {
|
||||||
|
const sf = def.getSourceFile();
|
||||||
|
if (!sf) return true;
|
||||||
|
const path = sf.getFilePath();
|
||||||
|
return path.includes("node_modules/typescript/lib/") || path.includes("node_modules/@types/");
|
||||||
|
});
|
||||||
|
} catch (_err) {
|
||||||
|
// If checking definitions fails, assume it is local/imported to be safe
|
||||||
|
isGlobal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isGlobal) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine replacement
|
||||||
|
let replacement = "";
|
||||||
|
if (name === "window" || name === "globalThis") {
|
||||||
|
replacement = "compatGlobal";
|
||||||
|
} else if (name === "document") {
|
||||||
|
replacement = "_activeDocument";
|
||||||
|
} else {
|
||||||
|
replacement = `compatGlobal.${name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodesToReplace.push({ node: idNode, replacement });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodesToReplace.length > 0) {
|
||||||
|
console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`);
|
||||||
|
for (const { node, replacement } of nodesToReplace) {
|
||||||
|
const { line } = sourceFile.getLineAndColumnAtPos(node.getStart());
|
||||||
|
console.log(` Line ${line}: "${node.getText()}" -> "${replacement}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDryRun) {
|
||||||
|
// Apply replacements
|
||||||
|
// Note: replaceWithText changes AST, so we replace them directly
|
||||||
|
for (const { node, replacement } of nodesToReplace) {
|
||||||
|
node.replaceWithText(replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine what needs to be imported based on replacements
|
||||||
|
const needsCompatGlobal = nodesToReplace.some((r) => r.replacement.includes("compatGlobal"));
|
||||||
|
const needsActiveDocument = nodesToReplace.some((r) => r.replacement.includes("_activeDocument"));
|
||||||
|
|
||||||
|
const requiredImports: string[] = [];
|
||||||
|
if (needsCompatGlobal) requiredImports.push("compatGlobal");
|
||||||
|
if (needsActiveDocument) requiredImports.push("_activeDocument");
|
||||||
|
|
||||||
|
if (requiredImports.length > 0) {
|
||||||
|
const existingImport = sourceFile.getImportDeclarations().find((imp) => {
|
||||||
|
const spec = imp.getModuleSpecifierValue();
|
||||||
|
return spec === "@lib/common/coreEnvFunctions" || spec === "@lib/common/coreEnvFunctions.ts";
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingImport) {
|
||||||
|
for (const nameToImport of requiredImports) {
|
||||||
|
const alreadyImported = existingImport
|
||||||
|
.getNamedImports()
|
||||||
|
.some((ni) => ni.getName() === nameToImport);
|
||||||
|
if (!alreadyImported) {
|
||||||
|
existingImport.addNamedImport(nameToImport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sourceFile.addImportDeclaration({
|
||||||
|
namedImports: requiredImports,
|
||||||
|
moduleSpecifier: "@lib/common/coreEnvFunctions.ts",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedFilesCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\nTotal files to modify: ${modifiedFilesCount}`);
|
||||||
|
|
||||||
|
if (!isDryRun) {
|
||||||
|
project.saveSync();
|
||||||
|
console.log("All changes successfully saved.");
|
||||||
|
} else {
|
||||||
|
console.log("Dry run complete. No changes were written to files.");
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
// Refactor element.style.XXXX = YYYY to element.setCssStyles({ XXXX: YYYY }).
|
||||||
|
// Use this script by running `deno run --allow-read --allow-write --allow-run refactor-styles.ts` from the utilsdeno directory.
|
||||||
|
// Run with --run flag to apply changes.
|
||||||
|
import { Project, SyntaxKind, Node, Expression } from "npm:ts-morph";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const isDryRun = !Deno.args.includes("--run");
|
||||||
|
|
||||||
|
if (isDryRun) {
|
||||||
|
console.log("=== DRY RUN MODE ===");
|
||||||
|
console.log(
|
||||||
|
"To apply changes, run with: deno run --allow-read --allow-write --allow-run refactor-styles.ts --run\n"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("=== RUN MODE: WILL MODIFY FILES ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = new Project({ tsConfigFilePath: "../tsconfig.json" });
|
||||||
|
|
||||||
|
// Manually add files under src/ to ensure those excluded by tsconfig.json are processed if needed.
|
||||||
|
project.addSourceFilesAtPaths("../src/**/*.ts");
|
||||||
|
project.addSourceFilesAtPaths("../src/**/*.svelte");
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const projectRoot = path.resolve(__dirname, "..");
|
||||||
|
|
||||||
|
function toPosixPath(filePath: string): string {
|
||||||
|
return filePath.replace(/\\/g, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
const posixProjectRoot = toPosixPath(projectRoot);
|
||||||
|
const posixSrc = `${posixProjectRoot}/src`;
|
||||||
|
const posixLibSrc = `${posixProjectRoot}/src/lib`;
|
||||||
|
|
||||||
|
function matchStyleAccess(node: Node): { element: Node; propertyName: string; isComputed: boolean } | undefined {
|
||||||
|
if (Node.isPropertyAccessExpression(node)) {
|
||||||
|
const expr = node.getExpression();
|
||||||
|
if (Node.isPropertyAccessExpression(expr) && expr.getName() === "style") {
|
||||||
|
return {
|
||||||
|
element: expr.getExpression(),
|
||||||
|
propertyName: node.getName(),
|
||||||
|
isComputed: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (Node.isElementAccessExpression(node)) {
|
||||||
|
const expr = node.getExpression();
|
||||||
|
if (Node.isPropertyAccessExpression(expr) && expr.getName() === "style") {
|
||||||
|
const arg = node.getArgumentExpression();
|
||||||
|
if (arg) {
|
||||||
|
return {
|
||||||
|
element: expr.getExpression(),
|
||||||
|
propertyName: arg.getText(),
|
||||||
|
isComputed: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStyleAssignment(statement: Node) {
|
||||||
|
if (!Node.isExpressionStatement(statement)) return undefined;
|
||||||
|
const expr = statement.getExpression();
|
||||||
|
if (!Node.isBinaryExpression(expr)) return undefined;
|
||||||
|
if (expr.getOperatorToken().getKind() !== SyntaxKind.EqualsToken) return undefined;
|
||||||
|
|
||||||
|
const styleAccess = matchStyleAccess(expr.getLeft());
|
||||||
|
if (!styleAccess) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
elementText: styleAccess.element.getText(),
|
||||||
|
property: styleAccess.propertyName,
|
||||||
|
valueText: expr.getRight().getText(),
|
||||||
|
isComputed: styleAccess.isComputed,
|
||||||
|
statementNode: statement,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StyleGroup {
|
||||||
|
elementText: string;
|
||||||
|
assignments: {
|
||||||
|
property: string;
|
||||||
|
valueText: string;
|
||||||
|
isComputed: boolean;
|
||||||
|
statementNode: Node;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
let modifiedFilesCount = 0;
|
||||||
|
|
||||||
|
for (const sourceFile of project.getSourceFiles()) {
|
||||||
|
const filePath = sourceFile.getFilePath();
|
||||||
|
const posixFilePath = toPosixPath(filePath);
|
||||||
|
|
||||||
|
// Only process files inside the project src directory.
|
||||||
|
if (!posixFilePath.startsWith(posixSrc)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude unit and integration test files
|
||||||
|
if (
|
||||||
|
posixFilePath.endsWith(".spec.ts") ||
|
||||||
|
posixFilePath.endsWith(".test.ts") ||
|
||||||
|
posixFilePath.includes("/_test/") ||
|
||||||
|
posixFilePath.includes("/testdeno/")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all blocks, case clauses, and the source file itself
|
||||||
|
const containers = [
|
||||||
|
sourceFile,
|
||||||
|
...sourceFile.getDescendantsOfKind(SyntaxKind.Block),
|
||||||
|
...sourceFile.getDescendantsOfKind(SyntaxKind.CaseClause),
|
||||||
|
...sourceFile.getDescendantsOfKind(SyntaxKind.DefaultClause),
|
||||||
|
];
|
||||||
|
|
||||||
|
const fileGroups: StyleGroup[] = [];
|
||||||
|
|
||||||
|
for (const container of containers) {
|
||||||
|
const statements = container.getStatements();
|
||||||
|
let i = 0;
|
||||||
|
while (i < statements.length) {
|
||||||
|
const assignment = getStyleAssignment(statements[i]);
|
||||||
|
if (assignment) {
|
||||||
|
const currentGroup: StyleGroup = {
|
||||||
|
elementText: assignment.elementText,
|
||||||
|
assignments: [
|
||||||
|
{
|
||||||
|
property: assignment.property,
|
||||||
|
valueText: assignment.valueText,
|
||||||
|
isComputed: assignment.isComputed,
|
||||||
|
statementNode: assignment.statementNode,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Look ahead to collect consecutive assignments to the same element
|
||||||
|
let j = i + 1;
|
||||||
|
while (j < statements.length) {
|
||||||
|
const nextAssignment = getStyleAssignment(statements[j]);
|
||||||
|
if (nextAssignment && nextAssignment.elementText === assignment.elementText) {
|
||||||
|
currentGroup.assignments.push({
|
||||||
|
property: nextAssignment.property,
|
||||||
|
valueText: nextAssignment.valueText,
|
||||||
|
isComputed: nextAssignment.isComputed,
|
||||||
|
statementNode: nextAssignment.statementNode,
|
||||||
|
});
|
||||||
|
j++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileGroups.push(currentGroup);
|
||||||
|
i = j;
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileGroups.length > 0) {
|
||||||
|
console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`);
|
||||||
|
|
||||||
|
// Process groups in reverse order to keep Node references valid when removing
|
||||||
|
const reversedGroups = [...fileGroups].reverse();
|
||||||
|
|
||||||
|
for (const group of reversedGroups) {
|
||||||
|
const props = group.assignments.map((c) => {
|
||||||
|
if (c.isComputed) {
|
||||||
|
if (
|
||||||
|
(c.property.startsWith("'") && c.property.endsWith("'")) ||
|
||||||
|
(c.property.startsWith('"') && c.property.endsWith('"')) ||
|
||||||
|
(c.property.startsWith("`") && c.property.endsWith("`"))
|
||||||
|
) {
|
||||||
|
return `${c.property}: ${c.valueText}`;
|
||||||
|
}
|
||||||
|
return `[${c.property}]: ${c.valueText}`;
|
||||||
|
}
|
||||||
|
return `${c.property}: ${c.valueText}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
let newText = "";
|
||||||
|
if (props.length === 1) {
|
||||||
|
newText = `${group.elementText}.setCssStyles({ ${props[0]} });`;
|
||||||
|
} else {
|
||||||
|
newText = `${group.elementText}.setCssStyles({\n ${props.join(",\n ")}\n});`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstNode = group.assignments[0].statementNode;
|
||||||
|
const { line } = sourceFile.getLineAndColumnAtPos(firstNode.getStart());
|
||||||
|
|
||||||
|
console.log(` Line ${line}: Replacing consecutive style assignments on "${group.elementText}" with:`);
|
||||||
|
console.log(
|
||||||
|
newText
|
||||||
|
.split("\n")
|
||||||
|
.map((l) => ` ${l}`)
|
||||||
|
.join("\n")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDryRun) {
|
||||||
|
firstNode.replaceWithText(newText);
|
||||||
|
for (let k = 1; k < group.assignments.length; k++) {
|
||||||
|
group.assignments[k].statementNode.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedFilesCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\nTotal files to modify: ${modifiedFilesCount}`);
|
||||||
|
|
||||||
|
if (!isDryRun) {
|
||||||
|
project.saveSync();
|
||||||
|
console.log("All changes successfully saved.");
|
||||||
|
} else {
|
||||||
|
console.log("Dry run complete. No changes were written to files.");
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
// Refactor unused catch variables and unused imports in the codebase.
|
||||||
|
// Use this script by running `deno run --allow-read --allow-write --allow-env refactor-unused.ts` from the utilsdeno directory.
|
||||||
|
// Run with --run flag to apply changes.
|
||||||
|
import { Project, SyntaxKind, Node } from "npm:ts-morph";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const isDryRun = !Deno.args.includes("--run");
|
||||||
|
|
||||||
|
if (isDryRun) {
|
||||||
|
console.log("=== DRY RUN MODE ===");
|
||||||
|
console.log(
|
||||||
|
"To apply changes, run with: deno run --allow-read --allow-write --allow-env refactor-unused.ts --run\n"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("=== RUN MODE: WILL MODIFY FILES ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = new Project({ tsConfigFilePath: "../tsconfig.json" });
|
||||||
|
// Only add .ts files to avoid Svelte-markup-blindness references
|
||||||
|
project.addSourceFilesAtPaths("../src/**/*.ts");
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const projectRoot = path.resolve(__dirname, "..");
|
||||||
|
|
||||||
|
function toPosixPath(filePath: string): string {
|
||||||
|
return filePath.replace(/\\/g, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
const posixProjectRoot = toPosixPath(projectRoot);
|
||||||
|
const posixSrc = `${posixProjectRoot}/src`;
|
||||||
|
|
||||||
|
let modifiedFilesCount = 0;
|
||||||
|
|
||||||
|
for (const sourceFile of project.getSourceFiles()) {
|
||||||
|
const filePath = sourceFile.getFilePath();
|
||||||
|
const posixFilePath = toPosixPath(filePath);
|
||||||
|
|
||||||
|
if (!posixFilePath.startsWith(posixSrc)) continue;
|
||||||
|
if (posixFilePath.includes("/_test/") || posixFilePath.endsWith(".spec.ts") || posixFilePath.endsWith(".test.ts")) continue;
|
||||||
|
|
||||||
|
let fileModified = false;
|
||||||
|
|
||||||
|
// 1. Find unused catch variables: catch (error) -> catch
|
||||||
|
const catchClauses = sourceFile.getDescendantsOfKind(SyntaxKind.CatchClause);
|
||||||
|
const catchVarsToRemove: Node[] = [];
|
||||||
|
|
||||||
|
for (const catchClause of catchClauses) {
|
||||||
|
const varDec = catchClause.getVariableDeclaration();
|
||||||
|
if (varDec) {
|
||||||
|
const varName = varDec.getName();
|
||||||
|
// Count references within the catch clause itself
|
||||||
|
const count = catchClause.getDescendantsOfKind(SyntaxKind.Identifier)
|
||||||
|
.filter((id) => id.getText() === varName)
|
||||||
|
.length;
|
||||||
|
if (count === 1) { // Only the declaration itself
|
||||||
|
catchVarsToRemove.push(varDec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (catchVarsToRemove.length > 0) {
|
||||||
|
if (!fileModified) {
|
||||||
|
console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`);
|
||||||
|
fileModified = true;
|
||||||
|
}
|
||||||
|
for (const varDec of catchVarsToRemove) {
|
||||||
|
const { line } = sourceFile.getLineAndColumnAtPos(varDec.getStart());
|
||||||
|
console.log(` Line ${line}: Unused catch variable "${varDec.getText()}" -> Remove it`);
|
||||||
|
if (!isDryRun) {
|
||||||
|
varDec.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Find unused named imports
|
||||||
|
const importDeclarations = sourceFile.getImportDeclarations();
|
||||||
|
const importsToRemove: { namedImport: any; impDecl: any }[] = [];
|
||||||
|
const modifiedDecls = new Set<any>();
|
||||||
|
|
||||||
|
for (const impDecl of importDeclarations) {
|
||||||
|
const namedImports = impDecl.getNamedImports();
|
||||||
|
if (namedImports.length === 0) continue;
|
||||||
|
|
||||||
|
for (const namedImport of namedImports) {
|
||||||
|
const importName = namedImport.getAliasNode()?.getText() ?? namedImport.getName();
|
||||||
|
// Count references in the entire file
|
||||||
|
const count = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier)
|
||||||
|
.filter((id) => id.getText() === importName)
|
||||||
|
.length;
|
||||||
|
if (count === 1) { // Only the import specifier itself
|
||||||
|
importsToRemove.push({ namedImport, impDecl });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importsToRemove.length > 0) {
|
||||||
|
if (!fileModified) {
|
||||||
|
console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`);
|
||||||
|
fileModified = true;
|
||||||
|
}
|
||||||
|
for (const { namedImport, impDecl } of importsToRemove) {
|
||||||
|
const { line } = sourceFile.getLineAndColumnAtPos(namedImport.getStart());
|
||||||
|
console.log(` Line ${line}: Unused named import "${namedImport.getText()}" -> Remove it`);
|
||||||
|
if (!isDryRun) {
|
||||||
|
namedImport.remove();
|
||||||
|
modifiedDecls.add(impDecl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Clean up empty import declarations (only those we actually modified)
|
||||||
|
if (!isDryRun && fileModified && modifiedDecls.size > 0) {
|
||||||
|
for (const impDecl of modifiedDecls) {
|
||||||
|
if (
|
||||||
|
impDecl.getNamedImports().length === 0 &&
|
||||||
|
!impDecl.getDefaultImport() &&
|
||||||
|
!impDecl.getNamespaceImport()
|
||||||
|
) {
|
||||||
|
impDecl.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileModified) {
|
||||||
|
modifiedFilesCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\nTotal files to modify: ${modifiedFilesCount}`);
|
||||||
|
|
||||||
|
if (!isDryRun) {
|
||||||
|
project.saveSync();
|
||||||
|
console.log("All changes successfully saved.");
|
||||||
|
} else {
|
||||||
|
console.log("Dry run complete. No changes were written to files.");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user