diff --git a/apply-package.mjs b/apply-package.mjs new file mode 100644 index 0000000..e79e269 --- /dev/null +++ b/apply-package.mjs @@ -0,0 +1,42 @@ +// Copy package.json dependencies and devDependencies from the repo root to the target sub-apps package.json, and set their versions to match the repo root version with a suffix. +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const repoRoot = __dirname; +const repoPackageJsonPath = path.join(repoRoot, "package.json"); +const repoPackageJson = JSON.parse(fs.readFileSync(repoPackageJsonPath, "utf-8")); +const devDependenciesToCopy = repoPackageJson.devDependencies || {}; +const dependenciesToCopy = repoPackageJson.dependencies || {}; + +const TARGET_APPS = ["cli", "webapp", "webpeer"]; + +for (const app of TARGET_APPS) { + const appDir = path.join(repoRoot, "src", "apps", app); + const packageJsonPath = path.join(appDir, "package.json"); + if (!fs.existsSync(packageJsonPath)) { + continue; + } + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); + + packageJson.dependencies = { + ...packageJson.dependencies, + ...dependenciesToCopy, + }; + packageJson.devDependencies = { + ...packageJson.devDependencies, + ...devDependenciesToCopy, + }; + packageJson.version = `${repoPackageJson.version}-${app}`; + + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 4), "utf-8"); + console.log(`Applied package.json dependencies and version from repo root to ${app} package.json`); +} + +console.log("\nApplied dependencies and version to all target applications."); +console.log( + "Please do not forget to pick dependencies that are actually needed in the target package.json, and remove the ones that are not needed." +); diff --git a/eslint.config.common.mjs b/eslint.config.common.mjs index 97d2ca7..d8c6044 100644 --- a/eslint.config.common.mjs +++ b/eslint.config.common.mjs @@ -1,23 +1,74 @@ -import tsParser from "@typescript-eslint/parser"; -import tsPlugin from "@typescript-eslint/eslint-plugin"; -import svelteParser from "svelte-eslint-parser"; +const restrictedGlobalsOptions = [ + { + name: "app", + message: "Avoid using the global app object. Instead use the reference provided by your plugin instance.", + }, + "warn", + { + name: "fetch", + message: "Use the built-in `requestUrl` function instead of `fetch` for network requests in Obsidian.", + }, + { + name: "localStorage", + message: + "Prefer `App#saveLocalStorage` / `App#loadLocalStorage` functions to write / read localStorage data that's unique to a vault.", + }, +]; +const restrictedImportsOptions = [ + { + name: "axios", + message: "Use the built-in `requestUrl` function instead of `axios`.", + }, + { + name: "superagent", + message: "Use the built-in `requestUrl` function instead of `superagent`.", + }, + { + name: "got", + message: "Use the built-in `requestUrl` function instead of `got`.", + }, + { + name: "ofetch", + message: "Use the built-in `requestUrl` function instead of `ofetch`.", + }, + { + name: "ky", + message: "Use the built-in `requestUrl` function instead of `ky`.", + }, + { + name: "node-fetch", + message: "Use the built-in `requestUrl` function instead of `node-fetch`.", + }, + { + name: "moment", + message: "The 'moment' package is bundled with Obsidian. Please import it from 'obsidian' instead.", + }, +]; const warnWhileDev = "off"; -export const tsBaseRules = { +/** + * @type {import("eslint").Linter.RulesRecord} + */ +export const baseRules = { // -- Base rules (turned off in favour of TS specific versions or explicitly disabled). "no-unused-vars": "off", "no-unused-labels": "off", "no-prototype-builtins": "off", "require-await": "off", - // -- TypeScript specific rules + // -- TypeScript specific rules (Gradual adoption of stricter rules, currently set to 'warn' for a while). "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-redundant-type-constituents": "warn", + // -- TypeScript specific rules + // @typescript-eslint/no-unsafe-* rules and @typescript-eslint/no-explicit-any: + // This project contains a lot of library-sh code where the use of `any` is often necessary and justified. + // Rules is now set to 'off' for a while. "@typescript-eslint/no-unsafe-argument": "off", "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-member-access": "off", "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-unsafe-assignment": "off", + // -- Reasonable rules. "@typescript-eslint/no-deprecated": warnWhileDev, "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], "@typescript-eslint/ban-ts-comment": "off", @@ -30,38 +81,63 @@ export const tsBaseRules = { // -- General rules "no-async-promise-executor": warnWhileDev, "no-constant-condition": ["error", { checkLoops: false }], + // -- Disabled rules + // no-undef: This option breaks the global declarations for the library files and is not worth the effort to fix at this time. "no-undef": "off", }; -export const tsBaseConfig = { - files: ["**/*.ts"], - plugins: { - "@typescript-eslint": tsPlugin, - }, - languageOptions: { - parser: tsParser, - parserOptions: { - project: "./tsconfig.json", - rootDir: "./", - }, - }, - rules: tsBaseRules, -}; +/** + * @type {import("eslint").Linter.RulesRecord} + */ +export const obsidianRules = { + // -- Obsidian rules + // obsidianmd/no-unsupported-api: usually this project checks for API support at runtime, so this rule is not critical but can be helpful to catch potential issues. + "obsidianmd/no-unsupported-api": warnWhileDev, -export const svelteBaseConfig = { - files: ["**/*.svelte"], - plugins: { - "@typescript-eslint": tsPlugin, - }, - languageOptions: { - parser: svelteParser, - parserOptions: { - parser: tsParser, - extraFileExtensions: [".svelte"], - rootDir: "./", - }, - }, - rules: { - "no-unused-vars": "off", - }, + // -- Plugin specific overrides + "obsidianmd/rule-custom-message": "off", + "obsidianmd/ui/sentence-case": "off", + "obsidianmd/no-plugin-as-component": "off", + + // -- Temporary overrides for migration + "obsidianmd/no-static-styles-assignment": "off", +}; +/** + * @type {(base:string) => import("eslint").Linter.RulesRecord} + */ +export const ImportAliasRules = (base) => ({ + "@dword-design/import-alias/prefer-alias": [ + "error", + { + aliasForSubpaths: true, + alias: { + "@": `${base}/src`, + "@lib": `${base}/src/lib/src`, + }, + }, + ], +}); +/** + * @type {import("eslint").Linter.RulesRecord} + */ +export const CommunityReviewRecommendedRules = { + "no-unused-vars": "off", + "no-prototype-bultins": "off", + "no-self-compare": "warn", + "no-eval": "error", + "no-implied-eval": "error", + "prefer-const": "off", + "no-implicit-globals": "error", + "no-console": "off", // overridden by obsidianmd/rule-custom-message + "no-restricted-globals": ["error", ...restrictedGlobalsOptions], + "no-restricted-imports": ["error", ...restrictedImportsOptions], + "no-alert": "error", + "no-undef": "error", + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-deprecated": "error", + "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }], + "@typescript-eslint/require-await": "off", + "@typescript-eslint/no-explicit-any": ["error", { fixToUnknown: true }], + // "import/no-nodejs-modules": "off", + // "import/no-extraneous-dependencies": "error", }; diff --git a/eslint.config.mjs b/eslint.config.mjs index 1c1e1b3..624bd2a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,7 +5,7 @@ import { defineConfig, globalIgnores } from "eslint/config"; import * as sveltePlugin from "eslint-plugin-svelte"; import svelteParser from "svelte-eslint-parser"; import importAlias from "@dword-design/eslint-plugin-import-alias"; -import { tsBaseConfig, svelteBaseConfig } from "./eslint.config.common.mjs"; +import { baseRules, ImportAliasRules, obsidianRules } from "./eslint.config.common.mjs"; const warnWhileDev = "off"; // Change to "warn" to enable warnings for rules that are currently disabled. export default defineConfig([ globalIgnores([ @@ -58,59 +58,45 @@ export default defineConfig([ ...obsidianmd.configs.recommended, importAlias.configs.recommended, { - ...tsBaseConfig, + files: ["**/*.ts"], + // ignores:["src/lib/**/*.ts"], // Exclude library files from root linting (they have different environments and rules). languageOptions: { - ...tsBaseConfig.languageOptions, globals: { ...globals.browser, PouchDB: "readonly" }, + parser: tsParser, + parserOptions: { + project: "./tsconfig.json", + rootDir: "./", + }, }, - plugins: { - ...tsBaseConfig.plugins, + linterOptions: { + reportUnusedDisableDirectives: false, }, rules: { - ...tsBaseConfig.rules, - // -- Obsidian rules - "obsidianmd/no-unsupported-api": warnWhileDev, - "obsidianmd/rule-custom-message": "off", - "obsidianmd/ui/sentence-case": "off", - "obsidianmd/no-plugin-as-component": "off", - "obsidianmd/no-static-styles-assignment": "off", - + ...baseRules, + ...obsidianRules, // -- Project specific rules - "@dword-design/import-alias/prefer-alias": [ - "error", - { - aliasForSubpaths: true, - alias: { - "@": "./src", - "@lib": "./src/lib/src", - }, - }, - ], + ...ImportAliasRules("."), }, }, { - ...svelteBaseConfig, + files: ["**/*.svelte"], languageOptions: { - ...svelteBaseConfig.languageOptions, globals: { ...globals.browser, PouchDB: "readonly" }, - }, - plugins: { - ...svelteBaseConfig.plugins, + parser: svelteParser, + parserOptions: { + parser: tsParser, + extraFileExtensions: [".svelte"], + project: "./tsconfig.json", + rootDir: "./", + }, }, rules: { - ...svelteBaseConfig.rules, - "obsidianmd/no-plugin-as-component": "off", - "obsidianmd/ui/sentence-case": "off", - "@dword-design/import-alias/prefer-alias": [ - "error", - { - aliasForSubpaths: true, - alias: { - "@": "./src", - "@lib": "./src/lib/src", - }, - }, - ], + // no-unused-vars: + // Svelte template's declarations have a lot of false positives and the rule is not worth the effort to fix at this time. + // it may improve in the future with some options as like ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],] + "no-unused-vars": "off", + ...obsidianRules, + ...ImportAliasRules("."), }, }, ]); diff --git a/package-lock.json b/package-lock.json index f0add2a..bd797f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16624,22 +16624,181 @@ }, "src/apps/webapp": { "name": "livesync-webapp", - "version": "0.0.1", + "version": "0.25.74-webapp", + "dependencies": { + "@aws-sdk/client-s3": "^3.808.0", + "@smithy/fetch-http-handler": "^5.3.10", + "@smithy/md5-js": "^4.2.9", + "@smithy/middleware-apply-body-checksum": "^4.3.9", + "@smithy/protocol-http": "^5.3.9", + "@smithy/querystring-builder": "^4.2.9", + "@smithy/types": "^4.14.3", + "@smithy/util-retry": "^4.4.5", + "@trystero-p2p/nostr": "^0.24.0", + "chokidar": "^4.0.0", + "diff-match-patch": "^1.0.5", + "fflate": "^0.8.2", + "idb": "^8.0.3", + "markdown-it": "^14.1.1", + "minimatch": "^10.2.2", + "obsidian": "^1.12.3", + "octagonal-wheels": "^0.1.46", + "pouchdb-adapter-leveldb": "^9.0.0", + "qrcode-generator": "^1.4.4", + "werift": "^0.23.0", + "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" + }, "devDependencies": { + "@dword-design/eslint-plugin-import-alias": "^8.1.8", + "@eslint/js": "^9.39.3", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@tsconfig/svelte": "^5.0.8", + "@types/deno": "^2.5.0", + "@types/diff-match-patch": "^1.0.36", + "@types/markdown-it": "^14.1.2", + "@types/micromatch": "^4.0.10", + "@types/node": "^24.10.13", + "@types/pouchdb": "^6.4.2", + "@types/pouchdb-adapter-http": "^6.1.6", + "@types/pouchdb-adapter-idb": "^6.1.7", + "@types/pouchdb-browser": "^6.1.5", + "@types/pouchdb-core": "^7.0.15", + "@types/pouchdb-mapreduce": "^6.1.10", + "@types/pouchdb-replication": "^6.4.7", + "@types/transform-pouch": "^1.0.6", + "@typescript-eslint/eslint-plugin": "^8.61.0", + "@typescript-eslint/parser": "8.61.0", + "@vitest/browser": "^4.1.8", + "@vitest/browser-playwright": "^4.1.8", + "@vitest/coverage-v8": "^4.1.8", + "dotenv-cli": "^11.0.0", + "esbuild": "0.25.0", + "esbuild-plugin-inline-worker": "^0.1.1", + "esbuild-svelte": "^0.9.4", + "eslint": "^9.39.3", + "eslint-plugin-obsidianmd": "^0.3.0", + "eslint-plugin-svelte": "^3.15.0", + "events": "^3.3.0", + "globals": "^14.0.0", + "playwright": "^1.58.2", + "postcss": "^8.5.6", + "pouchdb-adapter-http": "^9.0.0", + "pouchdb-adapter-idb": "^9.0.0", + "pouchdb-adapter-indexeddb": "^9.0.0", + "pouchdb-adapter-memory": "^9.0.0", + "pouchdb-core": "^9.0.0", + "pouchdb-errors": "^9.0.0", + "pouchdb-find": "^9.0.0", + "pouchdb-mapreduce": "^9.0.0", + "pouchdb-merge": "^9.0.0", + "pouchdb-replication": "^9.0.0", + "pouchdb-utils": "^9.0.0", + "prettier": "3.8.1", + "rollup-plugin-copy": "^3.5.0", + "svelte": "5.41.1", + "svelte-check": "^4.4.3", + "svelte-eslint-parser": "^1.8.0", + "svelte-preprocess": "^6.0.3", + "terser": "^5.39.0", + "tinyglobby": "^0.2.15", + "transform-pouch": "^2.0.0", + "tsx": "^4.21.0", "typescript": "5.9.3", - "vite": "^7.3.1" + "typescript-eslint": "^8.61.0", + "vite": "^7.3.1", + "vite-plugin-istanbul": "^8.0.0", + "vitest": "^4.1.8", + "webdriverio": "^9.27.0", + "yaml": "^2.8.2" } }, "src/apps/webpeer": { - "version": "0.0.0", + "version": "0.25.74-webpeer", + "dependencies": { + "@aws-sdk/client-s3": "^3.808.0", + "@smithy/fetch-http-handler": "^5.3.10", + "@smithy/md5-js": "^4.2.9", + "@smithy/middleware-apply-body-checksum": "^4.3.9", + "@smithy/protocol-http": "^5.3.9", + "@smithy/querystring-builder": "^4.2.9", + "@smithy/types": "^4.14.3", + "@smithy/util-retry": "^4.4.5", + "@trystero-p2p/nostr": "^0.24.0", + "chokidar": "^4.0.0", + "diff-match-patch": "^1.0.5", + "fflate": "^0.8.2", + "idb": "^8.0.3", + "markdown-it": "^14.1.1", + "minimatch": "^10.2.2", + "obsidian": "^1.12.3", + "octagonal-wheels": "^0.1.46", + "pouchdb-adapter-leveldb": "^9.0.0", + "qrcode-generator": "^1.4.4", + "werift": "^0.23.0", + "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" + }, "devDependencies": { + "@dword-design/eslint-plugin-import-alias": "^8.1.8", + "@eslint/js": "^9.39.3", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tsconfig/svelte": "^5.0.8", + "@types/deno": "^2.5.0", + "@types/diff-match-patch": "^1.0.36", + "@types/markdown-it": "^14.1.2", + "@types/micromatch": "^4.0.10", + "@types/node": "^24.10.13", + "@types/pouchdb": "^6.4.2", + "@types/pouchdb-adapter-http": "^6.1.6", + "@types/pouchdb-adapter-idb": "^6.1.7", + "@types/pouchdb-browser": "^6.1.5", + "@types/pouchdb-core": "^7.0.15", + "@types/pouchdb-mapreduce": "^6.1.10", + "@types/pouchdb-replication": "^6.4.7", + "@types/transform-pouch": "^1.0.6", + "@typescript-eslint/eslint-plugin": "^8.61.0", + "@typescript-eslint/parser": "8.61.0", + "@vitest/browser": "^4.1.8", + "@vitest/browser-playwright": "^4.1.8", + "@vitest/coverage-v8": "^4.1.8", + "dotenv-cli": "^11.0.0", + "esbuild": "0.25.0", + "esbuild-plugin-inline-worker": "^0.1.1", + "esbuild-svelte": "^0.9.4", + "eslint": "^9.39.3", + "eslint-plugin-obsidianmd": "^0.3.0", "eslint-plugin-svelte": "^3.15.0", + "events": "^3.3.0", + "globals": "^14.0.0", + "playwright": "^1.58.2", + "postcss": "^8.5.6", + "pouchdb-adapter-http": "^9.0.0", + "pouchdb-adapter-idb": "^9.0.0", + "pouchdb-adapter-indexeddb": "^9.0.0", + "pouchdb-adapter-memory": "^9.0.0", + "pouchdb-core": "^9.0.0", + "pouchdb-errors": "^9.0.0", + "pouchdb-find": "^9.0.0", + "pouchdb-mapreduce": "^9.0.0", + "pouchdb-merge": "^9.0.0", + "pouchdb-replication": "^9.0.0", + "pouchdb-utils": "^9.0.0", + "prettier": "3.8.1", + "rollup-plugin-copy": "^3.5.0", "svelte": "5.41.1", "svelte-check": "^4.4.3", + "svelte-eslint-parser": "^1.8.0", + "svelte-preprocess": "^6.0.3", + "terser": "^5.39.0", + "tinyglobby": "^0.2.15", + "transform-pouch": "^2.0.0", + "tsx": "^4.21.0", "typescript": "5.9.3", - "vite": "^7.3.1" + "typescript-eslint": "^8.61.0", + "vite": "^7.3.1", + "vite-plugin-istanbul": "^8.0.0", + "vitest": "^4.1.8", + "webdriverio": "^9.27.0", + "yaml": "^2.8.2" } } } diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 001e19c..411283d 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -115,7 +115,8 @@ cd obsidian-livesync # If you already cloned without submodules, run this once instead git submodule update --init --recursive -# Install dependencies from the repository root +# Install dependencies (now the CLI is a on the same monorepo, shared dependencies will be hoisted to the root node_modules) +cd src/apps/cli npm install # Build the CLI from its package directory diff --git a/src/apps/cli/adapters/NodeConversionAdapter.ts b/src/apps/cli/adapters/NodeConversionAdapter.ts index a11b280..8578e14 100644 --- a/src/apps/cli/adapters/NodeConversionAdapter.ts +++ b/src/apps/cli/adapters/NodeConversionAdapter.ts @@ -2,6 +2,7 @@ import * as path from "path"; import type { UXFileInfoStub, UXFolderInfo } from "@lib/common/models/fileaccess.type"; import type { IConversionAdapter } from "@lib/serviceModules/adapters"; import type { NodeFile, NodeFolder } from "./NodeTypes"; +import type { FilePathWithPrefix } from "@lib/common/models/db.type"; /** * Conversion adapter implementation for Node.js @@ -22,7 +23,7 @@ export class NodeConversionAdapter implements IConversionAdapter { @@ -77,7 +77,7 @@ export class NodeFileSystemAdapter implements IFileSystemAdapter { - return file.stat; + return await Promise.resolve(file.stat); } async reconcileInternalFile(p: string): Promise { diff --git a/src/apps/cli/adapters/NodeTypeGuardAdapter.ts b/src/apps/cli/adapters/NodeTypeGuardAdapter.ts index 8f1e970..db8ea8c 100644 --- a/src/apps/cli/adapters/NodeTypeGuardAdapter.ts +++ b/src/apps/cli/adapters/NodeTypeGuardAdapter.ts @@ -5,11 +5,17 @@ import type { NodeFile, NodeFolder } from "./NodeTypes"; * Type guard adapter implementation for Node.js */ export class NodeTypeGuardAdapter implements ITypeGuardAdapter { - isFile(file: any): file is NodeFile { - return file && typeof file === "object" && "path" in file && "stat" in file && !file.isFolder; + isFile(file: unknown): file is NodeFile { + return !!( + file && + typeof file === "object" && + "path" in file && + "stat" in file && + !("isFolder" in file && file.isFolder === true) + ); } - isFolder(item: any): item is NodeFolder { - return item && typeof item === "object" && "path" in item && item.isFolder === true; + isFolder(item: unknown): item is NodeFolder { + return !!(item && typeof item === "object" && "path" in item && "isFolder" in item && item.isFolder === true); } } diff --git a/src/apps/cli/adapters/NodeVaultAdapter.ts b/src/apps/cli/adapters/NodeVaultAdapter.ts index 064952e..f91077c 100644 --- a/src/apps/cli/adapters/NodeVaultAdapter.ts +++ b/src/apps/cli/adapters/NodeVaultAdapter.ts @@ -2,7 +2,8 @@ import * as fs from "fs/promises"; import * as path from "path"; import type { UXDataWriteOptions } from "@lib/common/models/fileaccess.type"; import type { IVaultAdapter } from "@lib/serviceModules/adapters"; -import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes"; +import type { NodeFile, NodeFolder } from "./NodeTypes"; +import type { FilePath } from "@lib/common/types"; /** * Vault adapter implementation for Node.js @@ -70,7 +71,7 @@ export class NodeVaultAdapter implements IVaultAdapter { const stat = await fs.stat(fullPath); return { - path: p as any, + path: p as FilePath, stat: { size: stat.size, mtime: Math.floor(stat.mtimeMs), @@ -93,7 +94,7 @@ export class NodeVaultAdapter implements IVaultAdapter { const stat = await fs.stat(fullPath); return { - path: p as any, + path: p as FilePath, stat: { size: stat.size, mtime: Math.floor(stat.mtimeMs), @@ -118,7 +119,7 @@ export class NodeVaultAdapter implements IVaultAdapter { await this.delete(file, force); } - trigger(name: string, ...data: any[]): any { + trigger(name: string, ...data: unknown[]): unknown { // No-op in CLI version (no event system) return undefined; } diff --git a/src/apps/cli/commands/p2p.ts b/src/apps/cli/commands/p2p.ts index 9c1606d..e579ba4 100644 --- a/src/apps/cli/commands/p2p.ts +++ b/src/apps/cli/commands/p2p.ts @@ -1,7 +1,7 @@ import { P2P_DEFAULT_SETTINGS } from "@lib/common/models/setting.const.defaults"; import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; import type { ServiceContext } from "@lib/services/base/ServiceBase"; -import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; +import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; type CLIP2PPeer = { peerId: string; name: string; @@ -19,7 +19,7 @@ export function parseTimeoutSeconds(value: string, commandName: string): number return timeoutSec; } -function validateP2PSettings(core: LiveSyncBaseCore) { +function validateP2PSettings(core: LiveSyncBaseCore) { const settings = core.services.setting.currentSettings(); if (!settings.P2P_Enabled) { throw new Error("P2P is disabled in settings (P2P_Enabled=false)"); @@ -31,7 +31,7 @@ function validateP2PSettings(core: LiveSyncBaseCore) { settings.P2P_IsHeadless = true; } -async function createReplicator(core: LiveSyncBaseCore): Promise { +async function createReplicator(core: LiveSyncBaseCore): Promise { validateP2PSettings(core); const replicator = await core.services.replicator.getNewReplicator(); if (!replicator) { @@ -50,7 +50,7 @@ function getSortedPeers(replicator: LiveSyncTrysteroReplicator): CLIP2PPeer[] { } export async function collectPeers( - core: LiveSyncBaseCore, + core: LiveSyncBaseCore, timeoutSec: number ): Promise { const replicator = await createReplicator(core); @@ -79,7 +79,7 @@ function resolvePeer(peers: CLIP2PPeer[], peerToken: string): CLIP2PPeer | undef } export async function syncWithPeer( - core: LiveSyncBaseCore, + core: LiveSyncBaseCore, peerToken: string, timeoutSec: number ): Promise { @@ -105,11 +105,11 @@ export async function syncWithPeer( const pullResult = await replicator.replicateFrom(targetPeer.peerId, false); if (pullResult && "error" in pullResult && pullResult.error) { - throw pullResult.error; + throw pullResult.error as Error; } - const pushResult = (await replicator.requestSynchroniseToPeer(targetPeer.peerId)) as any; + const pushResult = await replicator.requestSynchroniseToPeer(targetPeer.peerId); if (!pushResult || pushResult.ok !== true) { - throw pushResult?.error ?? new Error("P2P sync failed while requesting remote sync"); + throw (pushResult?.error as Error) ?? new Error("P2P sync failed while requesting remote sync"); } return targetPeer; @@ -118,7 +118,7 @@ export async function syncWithPeer( } } -export async function openP2PHost(core: LiveSyncBaseCore): Promise { +export async function openP2PHost(core: LiveSyncBaseCore): Promise { const replicator = await createReplicator(core); await replicator.open(); return replicator; diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index 0cd3193..254c5a6 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -15,6 +15,9 @@ import * as path from "path"; import { collectPeers, openP2PHost, parseTimeoutSeconds, syncWithPeer } from "./p2p"; import type { CLICommandContext, CLIOptions } from "./types"; import { promptForPassphrase, readStdinAsUtf8, toArrayBuffer, toDatabaseRelativePath } from "./utils"; +import type { EntryMilestoneInfo } from "@lib/common/models/db.definition"; +import type { LiveSyncCouchDBReplicator } from "@lib/replication/couchdb/LiveSyncReplicator"; +import type { LiveSyncJournalReplicator } from "@lib/replication/journal/LiveSyncJournalReplicator"; function redactConnectionString(uri: string): string { return uri.replace(/\/\/([^@/]+)@/u, "//***@"); @@ -35,16 +38,20 @@ async function verifyRemoteState( } try { - let milestone: any; + let milestone: EntryMilestoneInfo | null | false = null; if (settings.remoteType === REMOTE_COUCHDB) { - const dbRet = await (replicator as any).connectRemoteCouchDBWithSetting(settings, false, true); + const dbRet = await (replicator as LiveSyncCouchDBReplicator).connectRemoteCouchDBWithSetting( + settings, + false, + true + ); if (typeof dbRet === "string") { process.stderr.write(`[Verification] Failed to connect to remote CouchDB: ${dbRet}\n`); return false; } milestone = await dbRet.db.get(MILESTONE_DOCID); } else if (settings.remoteType === REMOTE_MINIO) { - milestone = await (replicator as any).client.downloadJson("_00000000-milestone.json"); + milestone = await (replicator as LiveSyncJournalReplicator).client.downloadJson("_00000000-milestone.json"); } if (milestone) { @@ -59,8 +66,10 @@ async function verifyRemoteState( process.stderr.write("[Verification] Milestone document not found on remote.\n"); return false; } - } catch (e: any) { - process.stderr.write(`[Verification] Failed to fetch milestone document: ${e?.message || e}\n`); + } catch (e: unknown) { + process.stderr.write( + `[Verification] Failed to fetch milestone document: ${e instanceof Error ? e.message : String(e)}\n` + ); return false; } } @@ -71,7 +80,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext await core.services.control.activated; if (options.command === "daemon") { - const log = (msg: unknown) => console.error(`[Daemon] ${msg}`); + const log = (msg: unknown) => console.error(`[Daemon] ${msg instanceof Error ? msg.message : String(msg)}`); // Skip the config mismatch dialog — the daemon cannot resolve it interactively // and the default "Dismiss" action would block replication. The daemon should @@ -90,7 +99,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext // 2. Mirror scan to reconcile PouchDB ↔ local filesystem. const errorManager = new UnresolvedErrorManager(core.services.appLifecycle); log("Running mirror scan..."); - const scanOk = await performFullScan(core as any, log, errorManager, false, true); + const scanOk = await performFullScan(core, log, errorManager, false, true); if (!scanOk) { console.error("[Daemon] Mirror scan failed, cannot continue"); return false; @@ -147,12 +156,12 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext ); } } - pollTimer = setTimeout(poll, currentIntervalMs); + pollTimer = setTimeout(poll as unknown as () => void, currentIntervalMs); }; - let pollTimer: ReturnType = setTimeout(poll, currentIntervalMs); + let pollTimer: ReturnType = setTimeout(poll as unknown as () => void, currentIntervalMs); core.services.appLifecycle.onUnload.addHandler(async () => { clearTimeout(pollTimer); - return true; + return await Promise.resolve(true); }); } else { log("LiveSync mode: restoring sync settings and starting _changes feed"); @@ -198,7 +207,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext } const timeoutSec = parseTimeoutSeconds(options.commandArgs[0], "p2p-peers"); console.error(`[Command] p2p-peers timeout=${timeoutSec}s`); - const peers = await collectPeers(core as any, timeoutSec); + const peers = await collectPeers(core, timeoutSec); if (peers.length > 0) { process.stdout.write(peers.map((peer) => `[peer]\t${peer.peerId}\t${peer.name}`).join("\n") + "\n"); } @@ -215,14 +224,14 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext } const timeoutSec = parseTimeoutSeconds(options.commandArgs[1], "p2p-sync"); console.error(`[Command] p2p-sync peer=${peerToken} timeout=${timeoutSec}s`); - const peer = await syncWithPeer(core as any, peerToken, timeoutSec); + const peer = await syncWithPeer(core, peerToken, timeoutSec); console.error(`[Done] P2P sync completed with ${peer.name} (${peer.peerId})`); return true; } if (options.command === "p2p-host") { console.error("[Command] p2p-host"); - await openP2PHost(core as any); + await openP2PHost(core); console.error("[Ready] P2P host is running. Press Ctrl+C to stop."); await new Promise(() => {}); return true; @@ -435,9 +444,10 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (docPath !== targetPath) continue; const filename = path.basename(docPath); - const conflictsText = (doc._conflicts?.length ?? 0) > 0 ? doc._conflicts.join("\n ") : "N/A"; + const conflicts = doc._conflicts ?? []; + const conflictsText = conflicts.length > 0 ? conflicts.join("\n ") : "N/A"; const children = "children" in doc ? doc.children : []; - const rawDoc = await core.services.database.localDatabase.getRaw(doc._id, { + const rawDoc = await core.services.database.localDatabase.getRaw(doc._id, { revs_info: true, }); const pastRevisions = (rawDoc._revs_info ?? []) @@ -491,6 +501,10 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext process.stderr.write(`[Info] File not found: ${targetPath}\n`); return false; } + if (!currentMeta._rev) { + process.stderr.write(`[Info] Current revision not found for ${targetPath}\n`); + return false; + } const conflicts = await core.serviceModules.databaseFileAccess.getConflictedRevs(targetPath); const candidateRevisions = [currentMeta._rev, ...conflicts]; @@ -520,9 +534,9 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (options.command === "mirror") { console.error("[Command] mirror"); - const log = (msg: unknown) => console.error(`[Mirror] ${msg}`); + const log = (msg: unknown) => console.error(`[Mirror] ${msg instanceof Error ? msg.message : String(msg)}`); const errorManager = new UnresolvedErrorManager(core.services.appLifecycle); - return await performFullScan(core as any, log, errorManager, false, true); + return await performFullScan(core, log, errorManager, false, true); } if (options.command === "remote-add") { diff --git a/src/apps/cli/commands/types.ts b/src/apps/cli/commands/types.ts index 725bdf1..d9f5f26 100644 --- a/src/apps/cli/commands/types.ts +++ b/src/apps/cli/commands/types.ts @@ -1,6 +1,6 @@ import type { ObsidianLiveSyncSettings } from "@lib/common/models/setting.type"; import { ServiceContext } from "@lib/services/base/ServiceBase"; -import { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; +import { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; export type CLICommand = | "daemon" | "sync" @@ -46,7 +46,7 @@ export interface CLIOptions { export interface CLICommandContext { databasePath: string; vaultPath: string; - core: LiveSyncBaseCore; + core: LiveSyncBaseCore; settingsPath: string; originalSyncSettings: Pick< ObsidianLiveSyncSettings, diff --git a/src/apps/cli/entrypoint.ts b/src/apps/cli/entrypoint.ts index 9cfc06e..521a48c 100644 --- a/src/apps/cli/entrypoint.ts +++ b/src/apps/cli/entrypoint.ts @@ -1,4 +1,6 @@ #!/usr/bin/env node +// Polyfill WebRTC in Node.js environment for CLI app. +/* eslint-disable @typescript-eslint/no-explicit-any */ import * as polyfill from "werift"; import { main } from "./main"; diff --git a/src/apps/cli/eslint.config.mjs b/src/apps/cli/eslint.config.mjs index e591fc7..d4fc474 100644 --- a/src/apps/cli/eslint.config.mjs +++ b/src/apps/cli/eslint.config.mjs @@ -1,19 +1,49 @@ -import { tsBaseConfig } from "../../../eslint.config.common.mjs"; -import globals from "globals"; +import importAlias from "@dword-design/eslint-plugin-import-alias"; +import tsParser from "@typescript-eslint/parser"; +// import obsidianmd from "eslint-plugin-obsidianmd"; +// const obsidianRules = obsidianmd.configs.recommended.find((config) => config.rules)?.rules || {}; +// console.dir(obsidianRules); import { defineConfig, globalIgnores } from "eslint/config"; - +import globals from "globals"; +import tseslint from "typescript-eslint"; +import { baseRules, CommunityReviewRecommendedRules, ImportAliasRules } from "../../../eslint.config.common.mjs"; export default defineConfig([ globalIgnores([ "dist", "node_modules", "test", - "testdeno" + "testdeno", + "**/*.test.ts", + "**/*.spec.ts", + "**/test.ts", + "**/tests.ts", + "**/*.js", + "**/*.mjs", ]), + ...tseslint.configs.recommendedTypeChecked, + importAlias.configs.recommended, { - ...tsBaseConfig, + files: ["**/*.ts"], + // ignores:["src/lib/**/*.ts"], // Exclude library files from root linting (they have different environments and rules). languageOptions: { - ...tsBaseConfig.languageOptions, - globals: { ...globals.node }, + globals: { ...globals.node, PouchDB: "readonly" }, + parser: tsParser, + parserOptions: { + project: "./tsconfig.json", + rootDir: "../../../", + }, }, - } + linterOptions: { + reportUnusedDisableDirectives: false, + }, + rules: { + ...baseRules, + // ...obsidianRules, + // -- Project specific rules + ...ImportAliasRules("../../../"), + // cli specific rules + "@typescript-eslint/no-this-alias": "off", // This rule is often inconvenient in CLI code where `this` is commonly used in various contexts, including callbacks and class methods. + ...CommunityReviewRecommendedRules, + }, + }, ]); diff --git a/src/apps/cli/lib/pouchdb-node.ts b/src/apps/cli/lib/pouchdb-node.ts index 137644e..5b314aa 100644 --- a/src/apps/cli/lib/pouchdb-node.ts +++ b/src/apps/cli/lib/pouchdb-node.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import PouchDB from "pouchdb-core"; import HttpPouch from "pouchdb-adapter-http"; @@ -85,7 +86,6 @@ PouchDB.prototype.purgeMulti = adapterFun( ); } //@ts-ignore - // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; const tasks = docs.map( (param) => () => diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index db88848..0d1d640 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -4,10 +4,11 @@ */ import * as fs from "fs/promises"; +import * as fsSync from "fs"; import * as path from "path"; import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub"; import { configureNodeLocalStorage, ensureGlobalNodeLocalStorage } from "./services/NodeLocalStorage"; -import { LiveSyncBaseCore } from "../../LiveSyncBaseCore"; +import { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules"; import { DEFAULT_SETTINGS } from "@lib/common/models/setting.const.defaults"; import type { LOG_LEVEL } from "@lib/common/logger"; @@ -178,11 +179,13 @@ export function parseArgs(): CLIOptions { interval = n; break; } + // fallthrough case "--debug": + // @ts-ignore - fallthrough intended for debug implying verbose case "-d": // debugging automatically enables verbose logging, as it is intended for debugging issues. debug = true; - // falls through + // fallthrough case "--verbose": case "-v": verbose = true; @@ -193,6 +196,7 @@ export function parseArgs(): CLIOptions { break; default: { if (!databasePath) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any if (command === "daemon" && VALID_COMMANDS.has(token as any)) { command = token as CLICommand; break; @@ -204,6 +208,7 @@ export function parseArgs(): CLIOptions { databasePath = token; break; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any if (command === "daemon" && VALID_COMMANDS.has(token as any)) { command = token as CLICommand; break; @@ -248,8 +253,8 @@ async function createDefaultSettingsFile(options: CLIOptions) { try { await fs.stat(targetPath); throw new Error(`Settings file already exists: ${targetPath} (use --force to overwrite)`); - } catch (ex: any) { - if (!(ex && ex?.code === "ENOENT")) { + } catch (ex: unknown) { + if (!(ex && (ex as { code?: string }).code === "ENOENT")) { throw ex; } } @@ -304,7 +309,7 @@ export async function main() { } // Resolve database path - const databasePath = path.resolve(options.databasePath!); + const databasePath = path.resolve(options.databasePath || ""); // Check if database directory exists try { const stat = await fs.stat(databasePath); @@ -312,7 +317,7 @@ export async function main() { console.error(`Error: ${databasePath} is not a directory`); process.exit(1); } - } catch (error) { + } catch { console.error(`Error: Database directory ${databasePath} does not exist`); process.exit(1); } @@ -333,7 +338,7 @@ export async function main() { ? path.resolve(options.commandArgs[0]) : options.vaultPath ? path.resolve(options.vaultPath) - : databasePath!; + : databasePath; // Check if vault directory exists try { @@ -342,7 +347,7 @@ export async function main() { console.error(`Error: Vault path ${vaultPath} is not a directory`); process.exit(1); } - } catch (error) { + } catch { console.error(`Error: Vault directory ${vaultPath} does not exist`); process.exit(1); } @@ -380,7 +385,7 @@ export async function main() { levelStr = "Urgent"; break; default: - levelStr = `${level}`; + levelStr = `${level as unknown as string}`; } const prefix = `(${levelStr})`; if (level <= LOG_LEVEL_INFO) { @@ -424,7 +429,7 @@ export async function main() { // Force disable IndexedDB adapter in CLI environment data.useIndexedDBAdapter = false; return data; - } catch (error) { + } catch { if (options.verbose) { console.error(`[Settings] File not found, using defaults`); } @@ -436,13 +441,14 @@ export async function main() { // Create LiveSync core const core = new LiveSyncBaseCore( serviceHubInstance, - (core: LiveSyncBaseCore, serviceHub: InjectableServiceHub) => { + (core: LiveSyncBaseCore, serviceHub: InjectableServiceHub) => { return initialiseServiceModulesCLI(vaultPath, core, serviceHub, ignoreRules, watchEnabled); }, (core) => [], - () => [], // No add-ons + () => [] as never[], // No add-ons (core) => { // Register P2P replicator feature. + // eslint-disable-next-line @typescript-eslint/no-unused-vars const _replicator = useP2PReplicatorFeature(core); // Add target filter to prevent internal files are handled core.services.vault.isTargetFile.addHandler(async (target) => { @@ -463,12 +469,12 @@ export async function main() { if (ignoreRules) { const rules = ignoreRules; core.services.vault.isTargetFile.addHandler(async (target) => { + await Promise.resolve(); const targetPath = stripAllPrefixes(getPathFromUXFileInfo(target)); if (rules.shouldIgnore(targetPath)) { return false; } - // undefined = pass through to next handler in chain - return undefined; + return true; }, 0); } } @@ -488,13 +494,13 @@ export async function main() { } }; - process.on("SIGINT", () => shutdown("SIGINT")); - process.on("SIGTERM", () => shutdown("SIGTERM")); + process.on("SIGINT", () => void shutdown("SIGINT")); + process.on("SIGTERM", () => void shutdown("SIGTERM")); // Save the settings file before any lifecycle events can mutate and persist them. // suspendAllSync and other lifecycle hooks clobber sync settings in memory, and // various code paths persist the clobbered state to disk. We restore on shutdown. - const settingsBackup = await fs.readFile(settingsPath, "utf-8").catch(() => null!); + const settingsBackup = await fs.readFile(settingsPath, "utf-8").catch(() => null); // Restore settings file on any exit to undo lifecycle mutations. // Write to a temp path first so a crash mid-write doesn't leave a truncated file. @@ -502,8 +508,8 @@ export async function main() { if (settingsBackup) { const tmpPath = settingsPath + ".tmp"; try { - require("fs").writeFileSync(tmpPath, settingsBackup, "utf-8"); - require("fs").renameSync(tmpPath, settingsPath); + fsSync.writeFileSync(tmpPath, settingsBackup, "utf-8"); + fsSync.renameSync(tmpPath, settingsPath); } catch (err) { console.error("[Settings] Failed to restore settings on exit:", err); } diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts index 388efab..66cc712 100644 --- a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts @@ -14,19 +14,30 @@ import { watch as chokidarWatch, type FSWatcher } from "chokidar"; import type { Stats } from "fs"; import * as fs from "fs/promises"; import * as path from "path"; -import type { NodeFile, NodeFolder } from "../adapters/NodeTypes"; -import type { IgnoreRules } from "../serviceModules/IgnoreRules"; +import type { NodeFile, NodeFolder } from "@cli/adapters/NodeTypes"; +import type { IgnoreRules } from "@cli/serviceModules/IgnoreRules"; /** * CLI-specific type guard adapter */ class CLITypeGuardAdapter implements IStorageEventTypeGuardAdapter { - isFile(file: any): file is NodeFile { - return file && typeof file === "object" && "path" in file && "stat" in file && !file.isFolder; + isFile(file: unknown): file is NodeFile { + return !!( + file && + typeof file === "object" && + "path" in file && + "stat" in file && + !(file as { isFolder?: boolean }).isFolder + ); } - isFolder(item: any): item is NodeFolder { - return item && typeof item === "object" && "path" in item && item.isFolder === true; + isFolder(item: unknown): item is NodeFolder { + return !!( + item && + typeof item === "object" && + "path" in item && + (item as { isFolder?: boolean }).isFolder === true + ); } } diff --git a/src/apps/cli/managers/StorageEventManagerCLI.ts b/src/apps/cli/managers/StorageEventManagerCLI.ts index 3680770..8a43c39 100644 --- a/src/apps/cli/managers/StorageEventManagerCLI.ts +++ b/src/apps/cli/managers/StorageEventManagerCLI.ts @@ -1,7 +1,7 @@ import { StorageEventManagerBase, type StorageEventManagerBaseDependencies } from "@lib/managers/StorageEventManager"; import type { ServiceContext } from "@lib/services/base/ServiceBase"; -import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; -import type { IgnoreRules } from "../serviceModules/IgnoreRules"; +import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "@/LiveSyncBaseCore"; +import type { IgnoreRules } from "@cli/serviceModules/IgnoreRules"; import { CLIStorageEventManagerAdapter } from "./CLIStorageEventManagerAdapter"; // import type { IMinimumLiveSyncCommands } from "@lib/services/base/IService"; diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index e5d0ff4..6be2594 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -6,7 +6,7 @@ "type": "module", "scripts": { "dev": "vite", - "package:apply-repo": "node scripts/apply-package.mjs", + "package:apply-repo": "node ../../../apply-package.mjs", "prebuild": "node scripts/check-submodule.mjs", "build": "vite build", "preview": "vite preview", diff --git a/src/apps/cli/scripts/apply-package.mjs b/src/apps/cli/scripts/apply-package.mjs deleted file mode 100644 index 426deab..0000000 --- a/src/apps/cli/scripts/apply-package.mjs +++ /dev/null @@ -1,28 +0,0 @@ -// Copy package.json dependencies and devDependencies from the repo root to the cli package.json, and set the version to match the repo root version with a -cli suffix. -import fs from "node:fs"; -import path from "node:path"; -import process from "node:process"; - -const cliDir = process.cwd(); -const repoRoot = path.resolve(cliDir, "../../.."); -const repoPackageJsonPath = path.join(repoRoot, "package.json"); -const repoPackageJson = JSON.parse(fs.readFileSync(repoPackageJsonPath, "utf-8")); -const devDependenciesToCopy = repoPackageJson.devDependencies || {}; -const dependenciesToCopy = repoPackageJson.dependencies || {}; -const packageJsonPath = path.join(cliDir, "package.json"); -const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); -packageJson.dependencies = { - ...packageJson.dependencies, - ...dependenciesToCopy, -}; -packageJson.devDependencies = { - ...packageJson.devDependencies, - ...devDependenciesToCopy, -}; -packageJson.version = `${repoPackageJson.version}-cli`; -fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 4), "utf-8"); - -console.log("Applied package.json dependencies and version from repo root to cli package.json"); -console.log( - "Please do not forget to pick dependencies that are actually needed in the cli package.json, and remove the ones that are not needed." -); diff --git a/src/apps/cli/serviceModules/CLIServiceModules.ts b/src/apps/cli/serviceModules/CLIServiceModules.ts index 834d8c2..6745026 100644 --- a/src/apps/cli/serviceModules/CLIServiceModules.ts +++ b/src/apps/cli/serviceModules/CLIServiceModules.ts @@ -3,9 +3,9 @@ import { StorageAccessManager } from "@lib/managers/StorageProcessingManager"; import { ServiceRebuilder } from "@lib/serviceModules/Rebuilder"; import type { ServiceContext } from "@lib/services/base/ServiceBase"; import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub"; -import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; -import { ServiceFileHandler } from "../../../serviceModules/FileHandler"; -import { StorageEventManagerCLI } from "../managers/StorageEventManagerCLI"; +import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; +import { ServiceFileHandler } from "@/serviceModules/FileHandler"; +import { StorageEventManagerCLI } from "@cli/managers/StorageEventManagerCLI"; import { ServiceDatabaseFileAccessCLI } from "./DatabaseFileAccess"; import { FileAccessCLI } from "./FileAccessCLI"; import type { IgnoreRules } from "./IgnoreRules"; @@ -22,7 +22,7 @@ import { ServiceFileAccessCLI } from "./ServiceFileAccessImpl"; */ export function initialiseServiceModulesCLI( basePath: string, - core: LiveSyncBaseCore, + core: LiveSyncBaseCore, services: InjectableServiceHub, ignoreRules?: IgnoreRules, watchEnabled: boolean = false @@ -81,7 +81,7 @@ export function initialiseServiceModulesCLI( }); // File handler (platform-independent) - const fileHandler = new (ServiceFileHandler as any)({ + const fileHandler = new ServiceFileHandler({ API: services.API, databaseFileAccess: databaseFileAccess, conflict: services.conflict, diff --git a/src/apps/cli/serviceModules/FileAccessCLI.ts b/src/apps/cli/serviceModules/FileAccessCLI.ts index 3aec6e0..36d56de 100644 --- a/src/apps/cli/serviceModules/FileAccessCLI.ts +++ b/src/apps/cli/serviceModules/FileAccessCLI.ts @@ -1,5 +1,5 @@ import { FileAccessBase, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase"; -import { NodeFileSystemAdapter } from "../adapters/NodeFileSystemAdapter"; +import { NodeFileSystemAdapter } from "@cli/adapters/NodeFileSystemAdapter"; /** * CLI-specific implementation of FileAccessBase diff --git a/src/apps/cli/serviceModules/ServiceFileAccessImpl.ts b/src/apps/cli/serviceModules/ServiceFileAccessImpl.ts index 718fc44..087af5e 100644 --- a/src/apps/cli/serviceModules/ServiceFileAccessImpl.ts +++ b/src/apps/cli/serviceModules/ServiceFileAccessImpl.ts @@ -1,5 +1,5 @@ import { ServiceFileAccessBase, type StorageAccessBaseDependencies } from "@lib/serviceModules/ServiceFileAccessBase"; -import { NodeFileSystemAdapter } from "../adapters/NodeFileSystemAdapter"; +import { NodeFileSystemAdapter } from "@cli/adapters/NodeFileSystemAdapter"; /** * CLI-specific implementation of ServiceFileAccess diff --git a/src/apps/cli/services/NodeKeyValueDBService.ts b/src/apps/cli/services/NodeKeyValueDBService.ts index 3d99191..676e754 100644 --- a/src/apps/cli/services/NodeKeyValueDBService.ts +++ b/src/apps/cli/services/NodeKeyValueDBService.ts @@ -115,23 +115,25 @@ class NodeFileKeyValueDatabase implements KeyValueDatabase { } async get(key: IDBValidKey): Promise { - return this.data.get(this.asKeyString(key)) as T; + return await Promise.resolve(this.data.get(this.asKeyString(key)) as T); } async set(key: IDBValidKey, value: T): Promise { this.data.set(this.asKeyString(key), value); this.flush(); - return key; + return await Promise.resolve(key); } async del(key: IDBValidKey): Promise { this.data.delete(this.asKeyString(key)); this.flush(); + await Promise.resolve(); } async clear(): Promise { this.data.clear(); this.flush(); + await Promise.resolve(); } private isIDBKeyRangeLike(value: unknown): value is { lower?: IDBValidKey; upper?: IDBValidKey } { @@ -143,10 +145,13 @@ class NodeFileKeyValueDatabase implements KeyValueDatabase { let filtered = allKeys; if (typeof query !== "undefined") { if (this.isIDBKeyRangeLike(query)) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string const lower = query.lower?.toString() ?? ""; + // eslint-disable-next-line @typescript-eslint/no-base-to-string const upper = query.upper?.toString() ?? "\uffff"; filtered = filtered.filter((key) => key >= lower && key <= upper); } else { + // eslint-disable-next-line @typescript-eslint/no-base-to-string const exact = query.toString(); filtered = filtered.filter((key) => key === exact); } @@ -154,16 +159,18 @@ class NodeFileKeyValueDatabase implements KeyValueDatabase { if (typeof count === "number") { filtered = filtered.slice(0, count); } - return filtered; + return await Promise.resolve(filtered); } async close(): Promise { this.flush(); + await Promise.resolve(); } async destroy(): Promise { this.data.clear(); nodeFs.rmSync(this.filePath, { force: true }); + await Promise.resolve(); } } @@ -178,7 +185,7 @@ export class NodeKeyValueDBService implements IKeyValueDBService { private _kvDB: KeyValueDatabase | undefined; - private _simpleStore: SimpleStore | undefined; + private _simpleStore: SimpleStore | undefined; private filePath: string; private _log = createInstanceLogFunction("NodeKeyValueDBService"); @@ -210,11 +217,11 @@ export class NodeKeyValueDBService private async openKeyValueDB(): Promise { try { this._kvDB = new NodeFileKeyValueDatabase(this.filePath); - return true; + return await Promise.resolve(true); } catch (ex) { this._log("Failed to open Node key-value database", LOG_LEVEL_NOTICE); this._log(ex, LOG_LEVEL_VERBOSE); - return false; + return await Promise.resolve(false); } } @@ -248,7 +255,7 @@ export class NodeKeyValueDBService if (!(await this.openKeyValueDB())) { return false; } - this._simpleStore = this.openSimpleStore("os"); + this._simpleStore = this.openSimpleStore("os"); return true; } @@ -264,13 +271,14 @@ export class NodeKeyValueDBService get: async (key: string): Promise => { return await getDB().get(`${prefix}${key}`); }, - set: async (key: string, value: any): Promise => { + set: async (key: string, value: T): Promise => { await getDB().set(`${prefix}${key}`, value); }, delete: async (key: string): Promise => { await getDB().del(`${prefix}${key}`); }, keys: async (from: string | undefined, to: string | undefined, count?: number): Promise => { + // eslint-disable-next-line @typescript-eslint/no-base-to-string const allKeys = (await getDB().keys(undefined, count)).map((e) => e.toString()); const lower = `${prefix}${from ?? ""}`; const upper = `${prefix}${to ?? "\uffff"}`; diff --git a/src/apps/cli/services/NodeLocalStorage.ts b/src/apps/cli/services/NodeLocalStorage.ts index 3e96822..53bb122 100644 --- a/src/apps/cli/services/NodeLocalStorage.ts +++ b/src/apps/cli/services/NodeLocalStorage.ts @@ -84,7 +84,9 @@ function createNodeLocalStorageShim(): LocalStorageShape { } export function ensureGlobalNodeLocalStorage() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any if (!("localStorage" in globalThis) || typeof (globalThis as any).localStorage?.getItem !== "function") { + // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).localStorage = createNodeLocalStorageShim(); } } diff --git a/src/apps/cli/services/NodeServiceHub.ts b/src/apps/cli/services/NodeServiceHub.ts index f28d25f..78bc51c 100644 --- a/src/apps/cli/services/NodeServiceHub.ts +++ b/src/apps/cli/services/NodeServiceHub.ts @@ -197,10 +197,12 @@ export class NodeServiceHub extends InjectableServ path, API, config, + // eslint-disable-next-line @typescript-eslint/no-explicit-any keyValueDB: keyValueDB as any, control, }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any super(context, serviceInstancesToInit as any); } } diff --git a/src/apps/cli/tsconfig.json b/src/apps/cli/tsconfig.json index 054d9c4..d43ba43 100644 --- a/src/apps/cli/tsconfig.json +++ b/src/apps/cli/tsconfig.json @@ -15,17 +15,18 @@ "noEmit": true, /* Linting */ - "strict": false, + "strict": true, // "noImplicitAny": false, "noUnusedLocals": false, "noUnusedParameters": false, - "noFallthroughCasesInSwitch": true - - // /* Path mapping */ - // "paths": { - // "@/*": ["../../*"], - // "@lib/*": ["../../lib/src/*"] - // } + "noFallthroughCasesInSwitch": true, + // "rootDir": "../../../", + /* Path mapping */ + "paths": { + "@/*": ["../../*"], + "@lib/*": ["../../lib/src/*"], + "@cli/*": ["./*"] + } }, "include": ["*.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules", "dist", "test", "testdeno"] diff --git a/src/apps/cli/vite.config.ts b/src/apps/cli/vite.config.ts index 01992e1..6ec231c 100644 --- a/src/apps/cli/vite.config.ts +++ b/src/apps/cli/vite.config.ts @@ -72,6 +72,7 @@ export default defineConfig({ fflate: resolve(__dirname, "../../../node_modules/fflate/lib/node.cjs"), "@": resolve(__dirname, "../../"), "@lib": resolve(__dirname, "../../lib/src"), + "@cli": resolve(__dirname, "../../apps/cli"), "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts", }, }, diff --git a/src/apps/webapp/adapters/FSAPIConversionAdapter.ts b/src/apps/webapp/adapters/FSAPIConversionAdapter.ts index f0136ff..39cadd0 100644 --- a/src/apps/webapp/adapters/FSAPIConversionAdapter.ts +++ b/src/apps/webapp/adapters/FSAPIConversionAdapter.ts @@ -1,6 +1,7 @@ import type { UXFileInfoStub, UXFolderInfo } from "@lib/common/types"; import type { IConversionAdapter } from "@lib/serviceModules/adapters"; import type { FSAPIFile, FSAPIFolder } from "./FSAPITypes"; +import type { FilePathWithPrefix } from "@lib/common/models/db.type"; /** * Conversion adapter implementation for FileSystem API @@ -28,7 +29,7 @@ export class FSAPIConversionAdapter implements IConversionAdapter { - const pathStr = p as string; + const pathStr = p; // Check cache first const cached = this.handleCache.get(pathStr); @@ -173,7 +173,9 @@ export class FSAPIFileSystemAdapter implements IFileSystemAdapter AsyncIterable<[string, FileSystemHandle]> } + ).entries()) { const entryPath = relativePath ? `${relativePath}/${name}` : name; if (entry.kind === "directory") { diff --git a/src/apps/webapp/adapters/FSAPIStorageAdapter.ts b/src/apps/webapp/adapters/FSAPIStorageAdapter.ts index 6767410..6e42330 100644 --- a/src/apps/webapp/adapters/FSAPIStorageAdapter.ts +++ b/src/apps/webapp/adapters/FSAPIStorageAdapter.ts @@ -195,7 +195,9 @@ export class FSAPIStorageAdapter implements IStorageAdapter { const folders: string[] = []; // Use AsyncIterator instead of .values() for better compatibility - for await (const [name, entry] of (dirHandle as any).entries()) { + for await (const [name, entry] of ( + dirHandle as unknown as { entries: () => AsyncIterable<[string, FileSystemHandle]> } + ).entries()) { const entryPath = basePath ? `${basePath}/${name}` : name; if (entry.kind === "directory") { diff --git a/src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts b/src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts index 06fa9b1..7e4b93e 100644 --- a/src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts +++ b/src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts @@ -5,13 +5,25 @@ import type { FSAPIFile, FSAPIFolder } from "./FSAPITypes"; * Type guard adapter implementation for FileSystem API */ export class FSAPITypeGuardAdapter implements ITypeGuardAdapter { - isFile(file: any): file is FSAPIFile { - return ( - file && typeof file === "object" && "path" in file && "stat" in file && "handle" in file && !file.isFolder + isFile(file: unknown): file is FSAPIFile { + return !!( + file && + typeof file === "object" && + "path" in file && + "stat" in file && + "handle" in file && + !("isFolder" in file && file.isFolder === true) ); } - isFolder(item: any): item is FSAPIFolder { - return item && typeof item === "object" && "path" in item && item.isFolder === true && "handle" in item; + isFolder(item: unknown): item is FSAPIFolder { + return !!( + item && + typeof item === "object" && + "path" in item && + "isFolder" in item && + item.isFolder === true && + "handle" in item + ); } } diff --git a/src/apps/webapp/adapters/FSAPIVaultAdapter.ts b/src/apps/webapp/adapters/FSAPIVaultAdapter.ts index acc6ebd..0965210 100644 --- a/src/apps/webapp/adapters/FSAPIVaultAdapter.ts +++ b/src/apps/webapp/adapters/FSAPIVaultAdapter.ts @@ -116,7 +116,7 @@ export class FSAPIVaultAdapter implements IVaultAdapter { await this.delete(file, force); } - trigger(name: string, ...data: any[]): any { + trigger(name: string, ...data: unknown[]): unknown { // No-op in webapp version (no event system yet) return undefined; } diff --git a/src/apps/webapp/bootstrap.ts b/src/apps/webapp/bootstrap.ts index b3fa072..e05a91e 100644 --- a/src/apps/webapp/bootstrap.ts +++ b/src/apps/webapp/bootstrap.ts @@ -120,20 +120,18 @@ async function initializeVaultSelector(): Promise { await renderHistoryList(); } -window.addEventListener("load", async () => { - try { - await initializeVaultSelector(); - } catch (error) { +window.addEventListener("load", () => { + initializeVaultSelector().catch((error) => { console.error("Failed to initialize vault selector:", error); setStatus("error", `Initialization failed: ${String(error)}`); - } + }); }); window.addEventListener("beforeunload", () => { void app?.shutdown(); }); -(window as any).livesyncApp = { +(window as unknown as { livesyncApp: unknown }).livesyncApp = { getApp: () => app, historyStore, }; diff --git a/src/apps/webapp/eslint.config.mjs b/src/apps/webapp/eslint.config.mjs index e3eb3c6..c1c0d80 100644 --- a/src/apps/webapp/eslint.config.mjs +++ b/src/apps/webapp/eslint.config.mjs @@ -1,18 +1,43 @@ -import { tsBaseConfig } from "../../../eslint.config.common.mjs"; -import globals from "globals"; +import importAlias from "@dword-design/eslint-plugin-import-alias"; +import tsParser from "@typescript-eslint/parser"; import { defineConfig, globalIgnores } from "eslint/config"; +import globals from "globals"; +import tseslint from "typescript-eslint"; +import { baseRules, CommunityReviewRecommendedRules, ImportAliasRules } from "../../../eslint.config.common.mjs"; export default defineConfig([ globalIgnores([ "dist", "node_modules", - "test" + "test", + "**/*.test.ts", + "**/*.spec.ts", + "**/test.ts", + "**/tests.ts", + "**/*.js", + "**/*.mjs", + "vite.config.ts", + "playwright.config.ts", ]), + ...tseslint.configs.recommendedTypeChecked, + importAlias.configs.recommended, { - ...tsBaseConfig, + files: ["**/*.ts"], languageOptions: { - ...tsBaseConfig.languageOptions, - globals: { ...globals.browser }, + globals: { ...globals.browser, PouchDB: "readonly" }, + parser: tsParser, + parserOptions: { + project: "./tsconfig.json", + rootDir: "../../../", + }, }, - } + linterOptions: { + reportUnusedDisableDirectives: false, + }, + rules: { + ...baseRules, + ...ImportAliasRules("../../../"), + ...CommunityReviewRecommendedRules, + }, + }, ]); diff --git a/src/apps/webapp/main.ts b/src/apps/webapp/main.ts index ea32268..02e9736 100644 --- a/src/apps/webapp/main.ts +++ b/src/apps/webapp/main.ts @@ -50,7 +50,7 @@ const DEFAULT_SETTINGS: Partial = { class LiveSyncWebApp { private rootHandle: FileSystemDirectoryHandle; - private core: LiveSyncBaseCore | null = null; + private core: LiveSyncBaseCore | null = null; private serviceHub: BrowserServiceHub | null = null; constructor(rootHandle: FileSystemDirectoryHandle) { @@ -98,17 +98,19 @@ class LiveSyncWebApp { }); // App lifecycle handlers - this.serviceHub.appLifecycle.scheduleRestart.setHandler(async () => { - console.log("[AppLifecycle] Restart requested"); - await this.shutdown(); - await this.initialize(); - setTimeout(() => { - window.location.reload(); - }, 1000); + this.serviceHub.appLifecycle.scheduleRestart.setHandler(() => { + void (async () => { + console.log("[AppLifecycle] Restart requested"); + await this.shutdown(); + await this.initialize(); + setTimeout(() => { + window.location.reload(); + }, 1000); + })(); }); // Create LiveSync core - this.core = new LiveSyncBaseCore( + this.core = new LiveSyncBaseCore( this.serviceHub, (core, serviceHub) => { return initialiseServiceModulesFSAPI(this.rootHandle, core, serviceHub); @@ -128,7 +130,7 @@ class LiveSyncWebApp { // new ModuleReplicatorP2P(core), // Register P2P replicator for CLI (useP2PReplicator is not used here) new SetupManager(core), ], - () => [], // No add-ons + () => [] as never[], // No add-ons (core) => { useOfflineScanner(core); useRedFlagFeatures(core); @@ -206,7 +208,8 @@ class LiveSyncWebApp { } // Scan the directory to populate file cache - const fileAccess = (this.core as any)._serviceModules?.storageAccess?.vaultAccess; + const fileAccess = (this.core as unknown as { _serviceModules?: any })._serviceModules?.storageAccess + ?.vaultAccess; if (fileAccess?.fsapiAdapter) { console.log("[Scanning] Scanning vault directory..."); await fileAccess.fsapiAdapter.scanDirectory(); @@ -224,7 +227,8 @@ class LiveSyncWebApp { console.log("[Shutdown] Shutting down..."); // Stop file watching - const storageEventManager = (this.core as any)._serviceModules?.storageAccess?.storageEventManager; + const storageEventManager = (this.core as unknown as { _serviceModules?: any })._serviceModules + ?.storageAccess?.storageEventManager; if (storageEventManager?.cleanup) { await storageEventManager.cleanup(); } diff --git a/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts b/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts index e33f5e6..6644dac 100644 --- a/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts +++ b/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts @@ -10,20 +10,32 @@ import type { IStorageEventWatchHandlers, } from "@lib/managers/adapters"; import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager"; -import type { FSAPIFile, FSAPIFolder } from "../adapters/FSAPITypes"; +import type { FSAPIFile, FSAPIFolder } from "@/apps/webapp/adapters/FSAPITypes"; /** * FileSystem API-specific type guard adapter */ class FSAPITypeGuardAdapter implements IStorageEventTypeGuardAdapter { - isFile(file: any): file is FSAPIFile { - return ( - file && typeof file === "object" && "path" in file && "stat" in file && "handle" in file && !file.isFolder + isFile(file: unknown): file is FSAPIFile { + return !!( + file && + typeof file === "object" && + "path" in file && + "stat" in file && + "handle" in file && + !("isFolder" in file && file.isFolder === true) ); } - isFolder(item: any): item is FSAPIFolder { - return item && typeof item === "object" && "path" in item && item.isFolder === true && "handle" in item; + isFolder(item: unknown): item is FSAPIFolder { + return !!( + item && + typeof item === "object" && + "path" in item && + "isFolder" in item && + item.isFolder === true && + "handle" in item + ); } } @@ -143,22 +155,40 @@ class FSAPIConverterAdapter implements IStorageEventConverterAdapter * FileSystem API-specific watch adapter using FileSystemObserver (Chrome only) */ class FSAPIWatchAdapter implements IStorageEventWatchAdapter { - private observer: any = null; // FileSystemObserver type + private observer: { + observe: (handle: FileSystemDirectoryHandle, options: { recursive: boolean }) => Promise; + disconnect: () => void; + } | null = null; constructor(private rootHandle: FileSystemDirectoryHandle) {} async beginWatch(handlers: IStorageEventWatchHandlers): Promise { // Use FileSystemObserver if available (Chrome 124+) - if (typeof (window as any).FileSystemObserver === "undefined") { + const win = window as unknown as { + FileSystemObserver?: new ( + callback: ( + records: { + root: FileSystemDirectoryHandle; + changedHandle: FileSystemFileHandle; + relativePathComponents: string[]; + type: string; + }[] + ) => Promise + ) => { + observe: (handle: FileSystemDirectoryHandle, options: { recursive: boolean }) => Promise; + disconnect: () => void; + }; + }; + if (typeof win.FileSystemObserver === "undefined") { console.log("[FSAPIWatchAdapter] FileSystemObserver not available, file watching disabled"); console.log("[FSAPIWatchAdapter] Consider using Chrome 124+ for real-time file watching"); return Promise.resolve(); } try { - const FileSystemObserver = (window as any).FileSystemObserver; + const FileSystemObserver = win.FileSystemObserver; - this.observer = new FileSystemObserver(async (records: any[]) => { + this.observer = new FileSystemObserver(async (records) => { for (const record of records) { const handle = record.root; const changedHandle = record.changedHandle; @@ -181,7 +211,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter { if (changedHandle && changedHandle.kind === "file") { const file = await changedHandle.getFile(); const fileInfo = { - path: relativePath as any, + path: relativePath, stat: { size: file.size, mtime: file.lastModified, @@ -192,23 +222,23 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter { }; if (type === "appeared") { - await handlers.onCreate(fileInfo, undefined); + handlers.onCreate(fileInfo, undefined); } else { - await handlers.onChange(fileInfo, undefined); + handlers.onChange(fileInfo, undefined); } } } else if (type === "disappeared") { const fileInfo = { - path: relativePath as any, + path: relativePath, stat: { size: 0, mtime: Date.now(), ctime: Date.now(), type: "file" as const, }, - handle: null as any, + handle: null as unknown, }; - await handlers.onDelete(fileInfo, undefined); + handlers.onDelete(fileInfo, undefined); } else if (type === "moved") { // Handle as delete + create // Note: FileSystemObserver provides both old and new paths in some cases @@ -216,7 +246,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter { if (changedHandle && changedHandle.kind === "file") { const file = await changedHandle.getFile(); const fileInfo = { - path: relativePath as any, + path: relativePath, stat: { size: file.size, mtime: file.lastModified, @@ -225,7 +255,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter { }, handle: changedHandle, }; - await handlers.onChange(fileInfo, undefined); + handlers.onChange(fileInfo, undefined); } } } catch (error) { diff --git a/src/apps/webapp/managers/StorageEventManagerFSAPI.ts b/src/apps/webapp/managers/StorageEventManagerFSAPI.ts index fb2761f..8a7585a 100644 --- a/src/apps/webapp/managers/StorageEventManagerFSAPI.ts +++ b/src/apps/webapp/managers/StorageEventManagerFSAPI.ts @@ -30,7 +30,7 @@ export class StorageEventManagerFSAPI extends StorageEventManagerBase Promise }).stopWatch?.(); } } } diff --git a/src/apps/webapp/package.json b/src/apps/webapp/package.json index 4830b94..b85d623 100644 --- a/src/apps/webapp/package.json +++ b/src/apps/webapp/package.json @@ -1,7 +1,7 @@ { "name": "livesync-webapp", "private": true, - "version": "0.0.1", + "version": "0.25.74-webapp", "type": "module", "description": "Browser-based Self-hosted LiveSync using FileSystem API", "scripts": { @@ -11,11 +11,93 @@ "run:docker": "docker run -p 8002:80 livesync-webapp", "preview": "vite preview", "check": "tsc --noEmit", - "lint": "eslint --cache ." + "lint": "eslint --cache .", + "package:apply-repo": "node ../../../apply-package.mjs" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.808.0", + "@smithy/fetch-http-handler": "^5.3.10", + "@smithy/md5-js": "^4.2.9", + "@smithy/middleware-apply-body-checksum": "^4.3.9", + "@smithy/protocol-http": "^5.3.9", + "@smithy/querystring-builder": "^4.2.9", + "@smithy/types": "^4.14.3", + "@smithy/util-retry": "^4.4.5", + "@trystero-p2p/nostr": "^0.24.0", + "chokidar": "^4.0.0", + "diff-match-patch": "^1.0.5", + "fflate": "^0.8.2", + "idb": "^8.0.3", + "markdown-it": "^14.1.1", + "minimatch": "^10.2.2", + "obsidian": "^1.12.3", + "octagonal-wheels": "^0.1.46", + "pouchdb-adapter-leveldb": "^9.0.0", + "qrcode-generator": "^1.4.4", + "werift": "^0.23.0", + "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" }, - "dependencies": {}, "devDependencies": { "typescript": "5.9.3", - "vite": "^7.3.1" + "vite": "^7.3.1", + "@dword-design/eslint-plugin-import-alias": "^8.1.8", + "@eslint/js": "^9.39.3", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@tsconfig/svelte": "^5.0.8", + "@types/deno": "^2.5.0", + "@types/diff-match-patch": "^1.0.36", + "@types/markdown-it": "^14.1.2", + "@types/micromatch": "^4.0.10", + "@types/node": "^24.10.13", + "@types/pouchdb": "^6.4.2", + "@types/pouchdb-adapter-http": "^6.1.6", + "@types/pouchdb-adapter-idb": "^6.1.7", + "@types/pouchdb-browser": "^6.1.5", + "@types/pouchdb-core": "^7.0.15", + "@types/pouchdb-mapreduce": "^6.1.10", + "@types/pouchdb-replication": "^6.4.7", + "@types/transform-pouch": "^1.0.6", + "@typescript-eslint/eslint-plugin": "^8.61.0", + "@typescript-eslint/parser": "8.61.0", + "@vitest/browser": "^4.1.8", + "@vitest/browser-playwright": "^4.1.8", + "@vitest/coverage-v8": "^4.1.8", + "dotenv-cli": "^11.0.0", + "esbuild": "0.25.0", + "esbuild-plugin-inline-worker": "^0.1.1", + "esbuild-svelte": "^0.9.4", + "eslint": "^9.39.3", + "eslint-plugin-obsidianmd": "^0.3.0", + "eslint-plugin-svelte": "^3.15.0", + "events": "^3.3.0", + "globals": "^14.0.0", + "playwright": "^1.58.2", + "postcss": "^8.5.6", + "pouchdb-adapter-http": "^9.0.0", + "pouchdb-adapter-idb": "^9.0.0", + "pouchdb-adapter-indexeddb": "^9.0.0", + "pouchdb-adapter-memory": "^9.0.0", + "pouchdb-core": "^9.0.0", + "pouchdb-errors": "^9.0.0", + "pouchdb-find": "^9.0.0", + "pouchdb-mapreduce": "^9.0.0", + "pouchdb-merge": "^9.0.0", + "pouchdb-replication": "^9.0.0", + "pouchdb-utils": "^9.0.0", + "prettier": "3.8.1", + "rollup-plugin-copy": "^3.5.0", + "svelte": "5.41.1", + "svelte-check": "^4.4.3", + "svelte-eslint-parser": "^1.8.0", + "svelte-preprocess": "^6.0.3", + "terser": "^5.39.0", + "tinyglobby": "^0.2.15", + "transform-pouch": "^2.0.0", + "tsx": "^4.21.0", + "typescript-eslint": "^8.61.0", + "vite-plugin-istanbul": "^8.0.0", + "vitest": "^4.1.8", + "webdriverio": "^9.27.0", + "yaml": "^2.8.2" } } diff --git a/src/apps/webapp/serviceModules/FSAPIServiceModules.ts b/src/apps/webapp/serviceModules/FSAPIServiceModules.ts index 26a0a2f..5857a17 100644 --- a/src/apps/webapp/serviceModules/FSAPIServiceModules.ts +++ b/src/apps/webapp/serviceModules/FSAPIServiceModules.ts @@ -7,7 +7,7 @@ import type { ServiceContext } from "@lib/services/base/ServiceBase"; import { FileAccessFSAPI } from "./FileAccessFSAPI"; import { ServiceFileAccessFSAPI } from "./ServiceFileAccessImpl"; import { ServiceDatabaseFileAccessFSAPI } from "./DatabaseFileAccess"; -import { StorageEventManagerFSAPI } from "../managers/StorageEventManagerFSAPI"; +import { StorageEventManagerFSAPI } from "@/apps/webapp/managers/StorageEventManagerFSAPI"; import type { ServiceModules } from "@lib/interfaces/ServiceModule"; import { ServiceFileHandler } from "@/serviceModules/FileHandler"; @@ -22,7 +22,7 @@ import { ServiceFileHandler } from "@/serviceModules/FileHandler"; */ export function initialiseServiceModulesFSAPI( rootHandle: FileSystemDirectoryHandle, - core: LiveSyncBaseCore, + core: LiveSyncBaseCore, services: InjectableServiceHub ): ServiceModules { const storageAccessManager = new StorageAccessManager(); @@ -67,7 +67,7 @@ export function initialiseServiceModulesFSAPI( }); // File handler (platform-independent) - const fileHandler = new (ServiceFileHandler as any)({ + const fileHandler = new ServiceFileHandler({ API: services.API, databaseFileAccess: databaseFileAccess, conflict: services.conflict, diff --git a/src/apps/webapp/serviceModules/FileAccessFSAPI.ts b/src/apps/webapp/serviceModules/FileAccessFSAPI.ts index 9d1561e..0ae67a8 100644 --- a/src/apps/webapp/serviceModules/FileAccessFSAPI.ts +++ b/src/apps/webapp/serviceModules/FileAccessFSAPI.ts @@ -1,5 +1,5 @@ import { FileAccessBase, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase"; -import { FSAPIFileSystemAdapter } from "../adapters/FSAPIFileSystemAdapter"; +import { FSAPIFileSystemAdapter } from "@/apps/webapp/adapters/FSAPIFileSystemAdapter"; /** * FileSystem API-specific implementation of FileAccessBase diff --git a/src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts b/src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts index f5396e2..62fea06 100644 --- a/src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts +++ b/src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts @@ -1,5 +1,5 @@ import { ServiceFileAccessBase, type StorageAccessBaseDependencies } from "@lib/serviceModules/ServiceFileAccessBase"; -import { FSAPIFileSystemAdapter } from "../adapters/FSAPIFileSystemAdapter"; +import { FSAPIFileSystemAdapter } from "@/apps/webapp/adapters/FSAPIFileSystemAdapter"; /** * FileSystem API-specific implementation of ServiceFileAccess diff --git a/src/apps/webapp/test-entry.ts b/src/apps/webapp/test-entry.ts index 3dcb8fe..9651fd1 100644 --- a/src/apps/webapp/test-entry.ts +++ b/src/apps/webapp/test-entry.ts @@ -27,11 +27,45 @@ function stripPrefix(raw: string): string { return raw.replace(/^[^:]+:/, ""); } +interface TestCore { + services: { + replication: { + databaseQueueCount?: { value: number }; + storageApplyingCount?: { value: number }; + replicate: (force: boolean) => Promise; + }; + fileProcessing: { + totalQueued?: { value: number }; + batched?: { value: number }; + processing?: { value: number }; + }; + database: { + localDatabase: { + findAllNormalDocs: (options: { conflicts: boolean }) => AsyncIterable<{ + _deleted?: boolean; + deleted?: boolean; + path?: string; + _rev?: string; + _conflicts?: string[]; + size?: number; + mtime?: number; + }>; + }; + }; + }; + serviceModules: { + databaseFileAccess: { + storeContent: (path: string, content: string) => Promise; + delete: (path: string) => Promise; + }; + }; +} + /** * Poll every 300 ms until all known processing queues are drained, or until * the timeout elapses. Mirrors `waitForIdle` in the existing vitest harness. */ -async function waitForIdle(core: any, timeoutMs = 60_000): Promise { +async function waitForIdle(core: TestCore, timeoutMs = 60_000): Promise { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const q = @@ -46,8 +80,8 @@ async function waitForIdle(core: any, timeoutMs = 60_000): Promise { throw new Error(`waitForIdle timed out after ${timeoutMs} ms`); } -function getCore(): any { - const core = (app as any)?.core; +function getCore(): TestCore { + const core = (app as unknown as { core: TestCore })?.core; if (!core) throw new Error("Vault not initialised – call livesyncTest.init() first"); return core; } @@ -140,17 +174,14 @@ const livesyncTest: LiveSyncTestAPI = { async putFile(vaultPath: string, content: string): Promise { const core = getCore(); - const result = await core.serviceModules.databaseFileAccess.storeContent( - vaultPath as FilePathWithPrefix, - content - ); + const result = await core.serviceModules.databaseFileAccess.storeContent(vaultPath, content); await waitForIdle(core); return result !== false; }, async deleteFile(vaultPath: string): Promise { const core = getCore(); - const result = await core.serviceModules.databaseFileAccess.delete(vaultPath as FilePathWithPrefix); + const result = await core.serviceModules.databaseFileAccess.delete(vaultPath); await waitForIdle(core); return result !== false; }, @@ -200,4 +231,4 @@ const livesyncTest: LiveSyncTestAPI = { }; // Expose on window for Playwright page.evaluate() calls. -(window as any).livesyncTest = livesyncTest; +(window as unknown as { livesyncTest: unknown }).livesyncTest = livesyncTest; diff --git a/src/apps/webapp/vaultSelector.ts b/src/apps/webapp/vaultSelector.ts index 79764a9..957b541 100644 --- a/src/apps/webapp/vaultSelector.ts +++ b/src/apps/webapp/vaultSelector.ts @@ -30,7 +30,10 @@ function randomId(): string { } async function hasReadWritePermission(handle: FileSystemDirectoryHandle, requestIfNeeded: boolean): Promise { - const h = handle as any; + const h = handle as unknown as { + queryPermission?: (options: { mode: "read" | "readwrite" }) => Promise; + requestPermission?: (options: { mode: "read" | "readwrite" }) => Promise; + }; if (typeof h.queryPermission === "function") { const queried = await h.queryPermission({ mode: "readwrite" }); if (queried === "granted") { @@ -89,11 +92,15 @@ export class VaultHistoryStore { async getVaultHistory(): Promise { 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 items: VaultHistoryItem[] = []; for (let i = 0; i < keys.length; i++) { - const key = String(keys[i]); + const keyVal = keys[i]; + if (typeof keyVal !== "string" && typeof keyVal !== "number") { + continue; + } + const key = String(keyVal); const id = parseVaultId(key); const value = values[i] as Partial | undefined; if (!id || !value || !value.handle || !value.name) { @@ -170,7 +177,14 @@ export class VaultHistoryStore { } async pickNewVault(): Promise { - const picker = (window as any).showDirectoryPicker; + const picker = ( + window as unknown as { + showDirectoryPicker?: (options?: { + mode?: "read" | "readwrite"; + startIn?: "documents" | "desktop" | "downloads" | "music" | "pictures" | "videos"; + }) => Promise; + } + ).showDirectoryPicker; if (typeof picker !== "function") { throw new Error("FileSystem API showDirectoryPicker is not supported in this browser"); } diff --git a/src/apps/webpeer/eslint.config.mjs b/src/apps/webpeer/eslint.config.mjs index c6b8b5d..762a9e0 100644 --- a/src/apps/webpeer/eslint.config.mjs +++ b/src/apps/webpeer/eslint.config.mjs @@ -1,28 +1,61 @@ -import { tsBaseConfig, svelteBaseConfig } from "../../../eslint.config.common.mjs"; -import globals from "globals"; +import importAlias from "@dword-design/eslint-plugin-import-alias"; +import tsParser from "@typescript-eslint/parser"; import { defineConfig, globalIgnores } from "eslint/config"; +import globals from "globals"; +import tseslint from "typescript-eslint"; import * as sveltePlugin from "eslint-plugin-svelte"; +import svelteParser from "svelte-eslint-parser"; +import { baseRules, CommunityReviewRecommendedRules, ImportAliasRules } from "../../../eslint.config.common.mjs"; export default defineConfig([ globalIgnores([ "dist", - "node_modules" + "node_modules", + "vite.config.ts", + "svelte.config.js", + "**/*.js", + "**/*.mjs", ]), + ...tseslint.configs.recommendedTypeChecked, ...sveltePlugin.configs["flat/base"], + importAlias.configs.recommended, { - ...tsBaseConfig, files: ["src/**/*.ts"], languageOptions: { - ...tsBaseConfig.languageOptions, - globals: { ...globals.browser }, + globals: { ...globals.browser, PouchDB: "readonly" }, + parser: tsParser, + parserOptions: { + project: "./tsconfig.app.json", + rootDir: "../../../", + }, + }, + linterOptions: { + reportUnusedDisableDirectives: false, + }, + rules: { + ...baseRules, + ...ImportAliasRules("../../../"), + ...CommunityReviewRecommendedRules, + "no-restricted-globals": "off", }, }, { - ...svelteBaseConfig, files: ["src/**/*.svelte"], languageOptions: { - ...svelteBaseConfig.languageOptions, - globals: { ...globals.browser }, + globals: { ...globals.browser, PouchDB: "readonly" }, + parser: svelteParser, + parserOptions: { + parser: tsParser, + extraFileExtensions: [".svelte"], + project: "./tsconfig.app.json", + rootDir: "../../../", + }, + }, + rules: { + "no-unused-vars": "off", + ...ImportAliasRules("../../../"), + ...CommunityReviewRecommendedRules, + "no-restricted-globals": "off", }, }, ]); diff --git a/src/apps/webpeer/package.json b/src/apps/webpeer/package.json index d1f3e4d..3e87250 100644 --- a/src/apps/webpeer/package.json +++ b/src/apps/webpeer/package.json @@ -1,7 +1,7 @@ { "name": "webpeer", "private": true, - "version": "0.0.0", + "version": "0.25.74-webpeer", "type": "module", "scripts": { "dev": "vite", @@ -10,9 +10,32 @@ "run:docker": "docker run -p 8001:80 livesync-webpeer", "preview": "vite preview", "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json", - "lint": "eslint --cache src" + "lint": "eslint --cache src", + "package:apply-repo": "node ../../../apply-package.mjs" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.808.0", + "@smithy/fetch-http-handler": "^5.3.10", + "@smithy/md5-js": "^4.2.9", + "@smithy/middleware-apply-body-checksum": "^4.3.9", + "@smithy/protocol-http": "^5.3.9", + "@smithy/querystring-builder": "^4.2.9", + "@smithy/types": "^4.14.3", + "@smithy/util-retry": "^4.4.5", + "@trystero-p2p/nostr": "^0.24.0", + "chokidar": "^4.0.0", + "diff-match-patch": "^1.0.5", + "fflate": "^0.8.2", + "idb": "^8.0.3", + "markdown-it": "^14.1.1", + "minimatch": "^10.2.2", + "obsidian": "^1.12.3", + "octagonal-wheels": "^0.1.46", + "pouchdb-adapter-leveldb": "^9.0.0", + "qrcode-generator": "^1.4.4", + "werift": "^0.23.0", + "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" }, - "dependencies": {}, "devDependencies": { "eslint-plugin-svelte": "^3.15.0", "@sveltejs/vite-plugin-svelte": "^6.2.4", @@ -20,7 +43,61 @@ "svelte": "5.41.1", "svelte-check": "^4.4.3", "typescript": "5.9.3", - "vite": "^7.3.1" + "vite": "^7.3.1", + "@dword-design/eslint-plugin-import-alias": "^8.1.8", + "@eslint/js": "^9.39.3", + "@types/deno": "^2.5.0", + "@types/diff-match-patch": "^1.0.36", + "@types/markdown-it": "^14.1.2", + "@types/micromatch": "^4.0.10", + "@types/node": "^24.10.13", + "@types/pouchdb": "^6.4.2", + "@types/pouchdb-adapter-http": "^6.1.6", + "@types/pouchdb-adapter-idb": "^6.1.7", + "@types/pouchdb-browser": "^6.1.5", + "@types/pouchdb-core": "^7.0.15", + "@types/pouchdb-mapreduce": "^6.1.10", + "@types/pouchdb-replication": "^6.4.7", + "@types/transform-pouch": "^1.0.6", + "@typescript-eslint/eslint-plugin": "^8.61.0", + "@typescript-eslint/parser": "8.61.0", + "@vitest/browser": "^4.1.8", + "@vitest/browser-playwright": "^4.1.8", + "@vitest/coverage-v8": "^4.1.8", + "dotenv-cli": "^11.0.0", + "esbuild": "0.25.0", + "esbuild-plugin-inline-worker": "^0.1.1", + "esbuild-svelte": "^0.9.4", + "eslint": "^9.39.3", + "eslint-plugin-obsidianmd": "^0.3.0", + "events": "^3.3.0", + "globals": "^14.0.0", + "playwright": "^1.58.2", + "postcss": "^8.5.6", + "pouchdb-adapter-http": "^9.0.0", + "pouchdb-adapter-idb": "^9.0.0", + "pouchdb-adapter-indexeddb": "^9.0.0", + "pouchdb-adapter-memory": "^9.0.0", + "pouchdb-core": "^9.0.0", + "pouchdb-errors": "^9.0.0", + "pouchdb-find": "^9.0.0", + "pouchdb-mapreduce": "^9.0.0", + "pouchdb-merge": "^9.0.0", + "pouchdb-replication": "^9.0.0", + "pouchdb-utils": "^9.0.0", + "prettier": "3.8.1", + "rollup-plugin-copy": "^3.5.0", + "svelte-eslint-parser": "^1.8.0", + "svelte-preprocess": "^6.0.3", + "terser": "^5.39.0", + "tinyglobby": "^0.2.15", + "transform-pouch": "^2.0.0", + "tsx": "^4.21.0", + "typescript-eslint": "^8.61.0", + "vite-plugin-istanbul": "^8.0.0", + "vitest": "^4.1.8", + "webdriverio": "^9.27.0", + "yaml": "^2.8.2" }, "imports": { "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts", diff --git a/src/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts index 11c9a9c..2840e94 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -10,14 +10,16 @@ import { import { eventHub } from "@lib/hub/hub"; import type { Confirm } from "@lib/interfaces/Confirm"; -import { LOG_LEVEL_NOTICE, Logger } from "@lib/common/logger"; +import { LOG_LEVEL_NOTICE, Logger, type LOG_LEVEL } from "@lib/common/logger"; import { storeP2PStatusLine } from "./CommandsShim"; import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, type PeerStatus, type PluginShim, } from "@lib/replication/trystero/P2PReplicatorPaneCommon"; -import { P2PLogCollector, type P2PReplicatorBase, useP2PReplicator } from "@lib/replication/trystero/P2PReplicatorCore"; +import { P2PLogCollector } from "@lib/replication/trystero/P2PLogCollector"; +import type { P2PReplicatorBase } from "@lib/replication/trystero/P2PReplicatorBase"; +import { useP2PReplicator } from "@lib/replication/trystero/P2PReplicatorCore"; import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; @@ -26,6 +28,7 @@ import { BrowserServiceHub } from "@lib/services/BrowserServices"; import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types"; import { ServiceContext } from "@lib/services/base/ServiceBase"; import type { InjectableServiceHub } from "@lib/services/InjectableServices"; +import type { NecessaryServices } from "@lib/interfaces/ServiceModule"; import { Menu } from "@lib/services/implements/browser/Menu"; import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2"; import type { BrowserAPIService } from "@lib/services/implements/browser/BrowserAPIService"; @@ -63,7 +66,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase { } return this.db; } - _simpleStore!: SimpleStore; + _simpleStore!: SimpleStore; async closeDB() { if (this.db) { @@ -80,7 +83,20 @@ export class P2PReplicatorShim implements P2PReplicatorBase { replicator, p2pLogCollector, storeP2PStatusLine: p2pStatusLine, - } = useP2PReplicator({ services: this.services } as any); + } = useP2PReplicator({ services: this.services } as unknown as NecessaryServices< + | "API" + | "appLifecycle" + | "setting" + | "vault" + | "database" + | "databaseEvents" + | "keyValueDB" + | "replication" + | "config" + | "UI" + | "replicator", + never + >); this._liveSyncReplicator = replicator; this.p2pLogCollector = p2pLogCollector; p2pLogCollector.p2pReplicationLine.onChanged((line) => { @@ -95,15 +111,15 @@ export class P2PReplicatorShim implements P2PReplicatorBase { (this.services.API as BrowserAPIService).getSystemVaultName.setHandler( () => "p2p-livesync-web-peer" ); - const repStore = SimpleStoreIDBv2.open("p2p-livesync-web-peer"); + const repStore = SimpleStoreIDBv2.open("p2p-livesync-web-peer"); this._simpleStore = repStore; let _settings = { ...P2P_DEFAULT_SETTINGS, additionalSuffixOfDatabaseName: "" } as ObsidianLiveSyncSettings; - this.services.setting.settings = _settings as any; - (this.services.setting as InjectableSettingService).saveData.setHandler(async (data) => { + this.services.setting.settings = _settings; + (this.services.setting as InjectableSettingService).saveData.setHandler(async (data) => { await repStore.set("settings", data); eventHub.emitEvent(EVENT_SETTING_SAVED, data); }); - (this.services.setting as InjectableSettingService).loadData.setHandler(async () => { + (this.services.setting as InjectableSettingService).loadData.setHandler(async () => { const settings = { ..._settings, ...((await repStore.get("settings")) as ObsidianLiveSyncSettings) }; return settings; }); @@ -145,7 +161,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase { return this; } - _log(msg: any, level?: any): void { + _log(msg: unknown, level?: LOG_LEVEL): void { Logger(msg, level); } _notice(msg: string, key?: string): void { @@ -154,7 +170,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase { getSettings(): P2PSyncSetting { return this.settings; } - simpleStore(): SimpleStore { + simpleStore(): SimpleStore { return this._simpleStore; } handleReplicatedDocuments(_docs: EntryDoc[]): Promise { @@ -278,7 +294,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase { } await this.services.setting.applyExternalSettings(remoteConfig, true); if (yn !== DROP) { - await this.plugin.core.services.appLifecycle.scheduleRestart(); + this.plugin.core.services.appLifecycle.scheduleRestart(); } } else { Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE); diff --git a/src/apps/webpeer/src/SyncMain.svelte b/src/apps/webpeer/src/SyncMain.svelte index 538fff3..fb4b3a8 100644 --- a/src/apps/webpeer/src/SyncMain.svelte +++ b/src/apps/webpeer/src/SyncMain.svelte @@ -5,18 +5,20 @@ import { cmdSyncShim } from "./P2PReplicatorShim"; import { eventHub } from "@lib/hub/hub"; import { EVENT_LAYOUT_READY } from "@lib/events/coreEvents"; + import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; + import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; let synchronised = $state(cmdSyncShim.init()); onMount(() => { eventHub.emitEvent(EVENT_LAYOUT_READY); return () => { - synchronised.then((e) => e.close()); + void synchronised.then((e) => e.close()); }; }); let elP: HTMLDivElement; logs.subscribe((log) => { - tick().then(() => elP?.scrollTo({ top: elP.scrollHeight })); + void tick().then(() => elP?.scrollTo({ top: elP.scrollHeight })); }); let statusLine = $state(""); storeP2PStatusLine.subscribe((status) => { @@ -27,7 +29,10 @@
{#await synchronised then cmdSync} - + {:catch error}

{error.message}

{/await} diff --git a/src/apps/webpeer/src/UITest.svelte b/src/apps/webpeer/src/UITest.svelte index 0e6af79..afbf7bd 100644 --- a/src/apps/webpeer/src/UITest.svelte +++ b/src/apps/webpeer/src/UITest.svelte @@ -6,13 +6,13 @@ const context = getDialogContext(); async function testUI() { - const confirm = await context.services.confirm; + const confirm = context.services.confirm; const ret = await confirm.askString("Your name", "What is your name?", "John Doe", false); result = ret; } let resultPassword = $state(""); async function testPassword() { - const confirm = await context.services.confirm; + const confirm = context.services.confirm; const ret = await confirm.askString("passphrase", "?", "anythingonlyyouknow", true); resultPassword = ret; } @@ -52,7 +52,7 @@ .onClick(async () => {}) .setIcon(mark); }); - m.showAtPosition({ x: event.x, y: event.y }); + void m.showAtPosition({ x: event.x, y: event.y }); } diff --git a/src/apps/webpeer/tsconfig.app.json b/src/apps/webpeer/tsconfig.app.json index 5bd9975..c070f33 100644 --- a/src/apps/webpeer/tsconfig.app.json +++ b/src/apps/webpeer/tsconfig.app.json @@ -1,5 +1,5 @@ { - "extends": "@tsconfig/svelte/tsconfig.json", + "extends": "../../../tsconfig.json", "compilerOptions": { "sourceRoot": "../", "target": "ESNext", @@ -15,11 +15,13 @@ "allowJs": true, "checkJs": true, "isolatedModules": true, + "allowImportingTsExtensions": true, "moduleDetection": "force", "paths": { "@/*": ["../../*"], "@lib/*": ["../../lib/src/*"] } }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "exclude": ["node_modules", "dist"] } diff --git a/tsconfig.json b/tsconfig.json index 8094467..8d8eb4e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,8 @@ "strictFunctionTypes": true, "paths": { "@/*": ["./src/*"], - "@lib/*": ["./src/lib/src/*", "./_types/src/lib/src/*"] + "@lib/*": ["./src/lib/src/*", "./_types/src/lib/src/*"], + "@cli/*": ["./src/apps/cli/*"] } }, "include": ["**/*.ts", "test/**/*.test.ts", "**/*.unit.spec.ts", "**/*.svelte"], diff --git a/vitest.config.common.ts b/vitest.config.common.ts index 44c0747..fea1727 100644 --- a/vitest.config.common.ts +++ b/vitest.config.common.ts @@ -91,6 +91,7 @@ export default defineConfig({ alias: { "@": path.resolve(__dirname, "./src"), "@lib": path.resolve(__dirname, "./src/lib/src"), + "@cli": path.resolve(__dirname, "./src/apps/cli"), src: path.resolve(__dirname, "./src"), }, },