mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-06-12 09:20:14 +00:00
refactor (okay, i know that this cannot be merged ; too large diff. I will pick some cheery).
This commit is contained in:
@@ -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."
|
||||
);
|
||||
+111
-35
@@ -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",
|
||||
};
|
||||
|
||||
+27
-41
@@ -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("."),
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
Generated
+163
-4
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<NodeFile, NodeF
|
||||
path: folder.path,
|
||||
isFolder: true,
|
||||
children: [],
|
||||
parent: path.dirname(folder.path) as any,
|
||||
parent: path.dirname(folder.path) as FilePathWithPrefix,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export class NodeFileSystemAdapter implements IFileSystemAdapter<NodeFile, NodeF
|
||||
}
|
||||
|
||||
private normalisePath(p: FilePath | string): string {
|
||||
return this.path.normalisePath(p as string);
|
||||
return this.path.normalisePath(p);
|
||||
}
|
||||
|
||||
async getAbstractFileByPath(p: FilePath | string): Promise<NodeFile | null> {
|
||||
@@ -77,7 +77,7 @@ export class NodeFileSystemAdapter implements IFileSystemAdapter<NodeFile, NodeF
|
||||
}
|
||||
|
||||
async statFromNative(file: NodeFile): Promise<UXStat> {
|
||||
return file.stat;
|
||||
return await Promise.resolve(file.stat);
|
||||
}
|
||||
|
||||
async reconcileInternalFile(p: string): Promise<void> {
|
||||
|
||||
@@ -5,11 +5,17 @@ import type { NodeFile, NodeFolder } from "./NodeTypes";
|
||||
* Type guard adapter implementation for Node.js
|
||||
*/
|
||||
export class NodeTypeGuardAdapter implements ITypeGuardAdapter<NodeFile, NodeFolder> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<NodeFile> {
|
||||
|
||||
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<NodeFile> {
|
||||
|
||||
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<NodeFile> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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<ServiceContext, any>) {
|
||||
function validateP2PSettings(core: LiveSyncBaseCore<ServiceContext, never>) {
|
||||
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<ServiceContext, any>) {
|
||||
settings.P2P_IsHeadless = true;
|
||||
}
|
||||
|
||||
async function createReplicator(core: LiveSyncBaseCore<ServiceContext, any>): Promise<LiveSyncTrysteroReplicator> {
|
||||
async function createReplicator(core: LiveSyncBaseCore<ServiceContext, never>): Promise<LiveSyncTrysteroReplicator> {
|
||||
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<ServiceContext, any>,
|
||||
core: LiveSyncBaseCore<ServiceContext, never>,
|
||||
timeoutSec: number
|
||||
): Promise<CLIP2PPeer[]> {
|
||||
const replicator = await createReplicator(core);
|
||||
@@ -79,7 +79,7 @@ function resolvePeer(peers: CLIP2PPeer[], peerToken: string): CLIP2PPeer | undef
|
||||
}
|
||||
|
||||
export async function syncWithPeer(
|
||||
core: LiveSyncBaseCore<ServiceContext, any>,
|
||||
core: LiveSyncBaseCore<ServiceContext, never>,
|
||||
peerToken: string,
|
||||
timeoutSec: number
|
||||
): Promise<CLIP2PPeer> {
|
||||
@@ -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<ServiceContext, any>): Promise<LiveSyncTrysteroReplicator> {
|
||||
export async function openP2PHost(core: LiveSyncBaseCore<ServiceContext, never>): Promise<LiveSyncTrysteroReplicator> {
|
||||
const replicator = await createReplicator(core);
|
||||
await replicator.open();
|
||||
return replicator;
|
||||
|
||||
@@ -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<typeof setTimeout> = setTimeout(poll, currentIntervalMs);
|
||||
let pollTimer: ReturnType<typeof setTimeout> = 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<any>(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") {
|
||||
|
||||
@@ -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<ServiceContext, any>;
|
||||
core: LiveSyncBaseCore<ServiceContext, never>;
|
||||
settingsPath: string;
|
||||
originalSyncSettings: Pick<
|
||||
ObsidianLiveSyncSettings,
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -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) => () =>
|
||||
|
||||
+25
-19
@@ -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<NodeServiceContext, any>, serviceHub: InjectableServiceHub<NodeServiceContext>) => {
|
||||
(core: LiveSyncBaseCore<NodeServiceContext, never>, serviceHub: InjectableServiceHub<NodeServiceContext>) => {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<NodeFile, NodeFolder> {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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."
|
||||
);
|
||||
@@ -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<ServiceContext, any>,
|
||||
core: LiveSyncBaseCore<ServiceContext, never>,
|
||||
services: InjectableServiceHub<ServiceContext>,
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -115,23 +115,25 @@ class NodeFileKeyValueDatabase implements KeyValueDatabase {
|
||||
}
|
||||
|
||||
async get<T>(key: IDBValidKey): Promise<T> {
|
||||
return this.data.get(this.asKeyString(key)) as T;
|
||||
return await Promise.resolve(this.data.get(this.asKeyString(key)) as T);
|
||||
}
|
||||
|
||||
async set<T>(key: IDBValidKey, value: T): Promise<IDBValidKey> {
|
||||
this.data.set(this.asKeyString(key), value);
|
||||
this.flush();
|
||||
return key;
|
||||
return await Promise.resolve(key);
|
||||
}
|
||||
|
||||
async del(key: IDBValidKey): Promise<void> {
|
||||
this.data.delete(this.asKeyString(key));
|
||||
this.flush();
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
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<void> {
|
||||
this.flush();
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
async destroy(): Promise<void> {
|
||||
this.data.clear();
|
||||
nodeFs.rmSync(this.filePath, { force: true });
|
||||
await Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +185,7 @@ export class NodeKeyValueDBService<T extends ServiceContext = ServiceContext>
|
||||
implements IKeyValueDBService
|
||||
{
|
||||
private _kvDB: KeyValueDatabase | undefined;
|
||||
private _simpleStore: SimpleStore<any> | undefined;
|
||||
private _simpleStore: SimpleStore<unknown> | undefined;
|
||||
private filePath: string;
|
||||
private _log = createInstanceLogFunction("NodeKeyValueDBService");
|
||||
|
||||
@@ -210,11 +217,11 @@ export class NodeKeyValueDBService<T extends ServiceContext = ServiceContext>
|
||||
private async openKeyValueDB(): Promise<boolean> {
|
||||
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<T extends ServiceContext = ServiceContext>
|
||||
if (!(await this.openKeyValueDB())) {
|
||||
return false;
|
||||
}
|
||||
this._simpleStore = this.openSimpleStore<any>("os");
|
||||
this._simpleStore = this.openSimpleStore<unknown>("os");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -264,13 +271,14 @@ export class NodeKeyValueDBService<T extends ServiceContext = ServiceContext>
|
||||
get: async (key: string): Promise<T> => {
|
||||
return await getDB().get(`${prefix}${key}`);
|
||||
},
|
||||
set: async (key: string, value: any): Promise<void> => {
|
||||
set: async (key: string, value: T): Promise<void> => {
|
||||
await getDB().set(`${prefix}${key}`, value);
|
||||
},
|
||||
delete: async (key: string): Promise<void> => {
|
||||
await getDB().del(`${prefix}${key}`);
|
||||
},
|
||||
keys: async (from: string | undefined, to: string | undefined, count?: number): Promise<string[]> => {
|
||||
// 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"}`;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,10 +197,12 @@ export class NodeServiceHub<T extends NodeServiceContext> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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<FSAPIFile, FSA
|
||||
path: folder.path,
|
||||
isFolder: true,
|
||||
children: [],
|
||||
parent: parentPath as any,
|
||||
parent: parentPath as FilePathWithPrefix,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,14 +30,14 @@ export class FSAPIFileSystemAdapter implements IFileSystemAdapter<FSAPIFile, FSA
|
||||
}
|
||||
|
||||
private normalisePath(path: FilePath | string): string {
|
||||
return this.path.normalisePath(path as string);
|
||||
return this.path.normalisePath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file handle for a given path
|
||||
*/
|
||||
private async getFileHandleByPath(p: FilePath | string): Promise<FileSystemFileHandle | null> {
|
||||
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<FSAPIFile, FSA
|
||||
}
|
||||
|
||||
// Use AsyncIterator instead of .values() for better compatibility
|
||||
for await (const [name, entry] of (currentHandle as any).entries()) {
|
||||
for await (const [name, entry] of (
|
||||
currentHandle as unknown as { entries: () => AsyncIterable<[string, FileSystemHandle]> }
|
||||
).entries()) {
|
||||
const entryPath = relativePath ? `${relativePath}/${name}` : name;
|
||||
|
||||
if (entry.kind === "directory") {
|
||||
|
||||
@@ -195,7 +195,9 @@ export class FSAPIStorageAdapter implements IStorageAdapter<FSAPIStat> {
|
||||
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") {
|
||||
|
||||
@@ -5,13 +5,25 @@ import type { FSAPIFile, FSAPIFolder } from "./FSAPITypes";
|
||||
* Type guard adapter implementation for FileSystem API
|
||||
*/
|
||||
export class FSAPITypeGuardAdapter implements ITypeGuardAdapter<FSAPIFile, FSAPIFolder> {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ export class FSAPIVaultAdapter implements IVaultAdapter<FSAPIFile> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -120,20 +120,18 @@ async function initializeVaultSelector(): Promise<void> {
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
+16
-12
@@ -50,7 +50,7 @@ const DEFAULT_SETTINGS: Partial<ObsidianLiveSyncSettings> = {
|
||||
|
||||
class LiveSyncWebApp {
|
||||
private rootHandle: FileSystemDirectoryHandle;
|
||||
private core: LiveSyncBaseCore<ServiceContext, any> | null = null;
|
||||
private core: LiveSyncBaseCore<ServiceContext, never> | null = null;
|
||||
private serviceHub: BrowserServiceHub<ServiceContext> | 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<ServiceContext, never>(
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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<FSAPIFile, FSAPIFolder> {
|
||||
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<FSAPIFile>
|
||||
* 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<void>;
|
||||
disconnect: () => void;
|
||||
} | null = null;
|
||||
|
||||
constructor(private rootHandle: FileSystemDirectoryHandle) {}
|
||||
|
||||
async beginWatch(handlers: IStorageEventWatchHandlers): Promise<void> {
|
||||
// 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<void>
|
||||
) => {
|
||||
observe: (handle: FileSystemDirectoryHandle, options: { recursive: boolean }) => Promise<void>;
|
||||
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) {
|
||||
|
||||
@@ -30,7 +30,7 @@ export class StorageEventManagerFSAPI extends StorageEventManagerBase<FSAPIStora
|
||||
async cleanup() {
|
||||
// Stop file watching
|
||||
if (this.fsapiAdapter?.watch) {
|
||||
await (this.fsapiAdapter.watch as any).stopWatch?.();
|
||||
await (this.fsapiAdapter.watch as unknown as { stopWatch?: () => Promise<void> }).stopWatch?.();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ServiceContext, any>,
|
||||
core: LiveSyncBaseCore<ServiceContext, never>,
|
||||
services: InjectableServiceHub<ServiceContext>
|
||||
): 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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<boolean>;
|
||||
};
|
||||
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<boolean>;
|
||||
delete: (path: string) => Promise<boolean>;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<void> {
|
||||
async function waitForIdle(core: TestCore, timeoutMs = 60_000): Promise<void> {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() < deadline) {
|
||||
const q =
|
||||
@@ -46,8 +80,8 @@ async function waitForIdle(core: any, timeoutMs = 60_000): Promise<void> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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;
|
||||
|
||||
@@ -30,7 +30,10 @@ function randomId(): string {
|
||||
}
|
||||
|
||||
async function hasReadWritePermission(handle: FileSystemDirectoryHandle, requestIfNeeded: boolean): Promise<boolean> {
|
||||
const h = handle as any;
|
||||
const h = handle as unknown as {
|
||||
queryPermission?: (options: { mode: "read" | "readwrite" }) => Promise<PermissionState>;
|
||||
requestPermission?: (options: { mode: "read" | "readwrite" }) => Promise<PermissionState>;
|
||||
};
|
||||
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<VaultHistoryItem[]> {
|
||||
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<VaultHistoryValue> | undefined;
|
||||
if (!id || !value || !value.handle || !value.name) {
|
||||
@@ -170,7 +177,14 @@ export class VaultHistoryStore {
|
||||
}
|
||||
|
||||
async pickNewVault(): Promise<FileSystemDirectoryHandle> {
|
||||
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<FileSystemDirectoryHandle>;
|
||||
}
|
||||
).showDirectoryPicker;
|
||||
if (typeof picker !== "function") {
|
||||
throw new Error("FileSystem API showDirectoryPicker is not supported in this browser");
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<any>;
|
||||
_simpleStore!: SimpleStore<unknown>;
|
||||
|
||||
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<ServiceContext>).getSystemVaultName.setHandler(
|
||||
() => "p2p-livesync-web-peer"
|
||||
);
|
||||
const repStore = SimpleStoreIDBv2.open<any>("p2p-livesync-web-peer");
|
||||
const repStore = SimpleStoreIDBv2.open<unknown>("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<any>).saveData.setHandler(async (data) => {
|
||||
this.services.setting.settings = _settings;
|
||||
(this.services.setting as InjectableSettingService<ServiceContext>).saveData.setHandler(async (data) => {
|
||||
await repStore.set("settings", data);
|
||||
eventHub.emitEvent(EVENT_SETTING_SAVED, data);
|
||||
});
|
||||
(this.services.setting as InjectableSettingService<any>).loadData.setHandler(async () => {
|
||||
(this.services.setting as InjectableSettingService<ServiceContext>).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<any> {
|
||||
simpleStore(): SimpleStore<unknown> {
|
||||
return this._simpleStore;
|
||||
}
|
||||
handleReplicatedDocuments(_docs: EntryDoc[]): Promise<boolean> {
|
||||
@@ -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);
|
||||
|
||||
@@ -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 @@
|
||||
<main>
|
||||
<div class="control">
|
||||
{#await synchronised then cmdSync}
|
||||
<P2PReplicatorPane plugin={cmdSync.plugin} {cmdSync} core={cmdSync.plugin.core}></P2PReplicatorPane>
|
||||
<P2PReplicatorPane
|
||||
cmdSync={cmdSync as unknown as LiveSyncTrysteroReplicator}
|
||||
core={cmdSync.plugin.core as unknown as LiveSyncBaseCore}
|
||||
></P2PReplicatorPane>
|
||||
{:catch error}
|
||||
<p>{error.message}</p>
|
||||
{/await}
|
||||
|
||||
@@ -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<string | boolean>("");
|
||||
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 });
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
+2
-1
@@ -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"],
|
||||
|
||||
@@ -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"),
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user