mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-17 06:42:31 +00:00
Remove vulnerable development tooling and update Commonlib
This commit is contained in:
@@ -106,13 +106,10 @@ export const obsidianRules = {
|
||||
* @type {(base:string) => import("eslint").Linter.RulesRecord}
|
||||
*/
|
||||
export const ImportAliasRules = (base) => ({
|
||||
"@dword-design/import-alias/prefer-alias": [
|
||||
"livesync-imports/prefer-source-alias": [
|
||||
"error",
|
||||
{
|
||||
aliasForSubpaths: true,
|
||||
alias: {
|
||||
"@": `${base}/src`,
|
||||
},
|
||||
sourceRoot: `${base}/src`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
+5
-2
@@ -4,7 +4,7 @@ import globals from "globals";
|
||||
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 importAlias from "./utils/import-alias.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.
|
||||
const lintProjects = [
|
||||
@@ -56,7 +56,10 @@ export default defineConfig([
|
||||
]),
|
||||
...sveltePlugin.configs["flat/base"],
|
||||
...obsidianmd.configs.recommended,
|
||||
importAlias.configs.recommended,
|
||||
{
|
||||
plugins: { "livesync-imports": importAlias },
|
||||
rules: { "livesync-imports/prefer-source-alias": "error" },
|
||||
},
|
||||
{
|
||||
files: ["**/*.ts"],
|
||||
languageOptions: {
|
||||
|
||||
Generated
+74
-877
File diff suppressed because it is too large
Load Diff
+1
-4
@@ -123,7 +123,6 @@
|
||||
"author": "vorotamoroz",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@dword-design/eslint-plugin-import-alias": "^8.1.8",
|
||||
"@emnapi/core": "1.11.1",
|
||||
"@emnapi/runtime": "1.11.1",
|
||||
"@eslint/js": "^9.39.3",
|
||||
@@ -132,7 +131,6 @@
|
||||
"@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",
|
||||
@@ -168,7 +166,6 @@
|
||||
"pouchdb-replication": "^9.0.0",
|
||||
"pouchdb-utils": "^9.0.0",
|
||||
"prettier": "3.8.1",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"svelte": "5.56.3",
|
||||
"svelte-check": "^4.6.0",
|
||||
"svelte-eslint-parser": "^1.8.0",
|
||||
@@ -193,7 +190,7 @@
|
||||
"@smithy/types": "^4.14.3",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@vrtmrz/browser-ui-kit": "0.1.0",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.38",
|
||||
"@vrtmrz/livesync-commonlib": "0.1.39",
|
||||
"@vrtmrz/obsidian-plugin-kit": "0.1.4",
|
||||
"@vrtmrz/ui-interactions": "0.1.2",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
|
||||
@@ -19,6 +19,11 @@ Earlier releases remain available in the 1.0 release history, the 1.0 preview hi
|
||||
- We now apply the settings for protecting sensitive configuration together, so a successful Apply keeps the saved settings under the selected passphrase. If Apply fails, the edits remain available to retry. (#1238)
|
||||
- We can now enter a missing or unusable custom configuration passphrase at start-up to unlock protected settings. Cancelling the custom-key recovery prompt preserves the encrypted configuration and stops that start-up attempt; headless command-line start-up cannot complete that prompt when recovery input is required. (#1239)
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Build copying and import alias checks now use repository-owned helpers, removing the vulnerable `braces` dependency from development tooling.
|
||||
- Updated compatible development dependencies with available security fixes.
|
||||
|
||||
## 1.0.35
|
||||
|
||||
5th October, 2026
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { access, copyFile, mkdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import type { Plugin } from "vite";
|
||||
|
||||
const BUILD_FILES = ["manifest.json", "main.js", "styles.css"] as const;
|
||||
|
||||
function hasErrorCode(error: unknown, code: string): boolean {
|
||||
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
||||
}
|
||||
|
||||
export function copyBuildFiles(destinations: readonly string[], sourceDirectory = process.cwd()): Plugin {
|
||||
return {
|
||||
name: "copy-build-files",
|
||||
async writeBundle() {
|
||||
let copied = false;
|
||||
for (const destination of destinations) {
|
||||
let destinationCreated = false;
|
||||
for (const file of BUILD_FILES) {
|
||||
const source = join(sourceDirectory, file);
|
||||
try {
|
||||
await access(source);
|
||||
} catch (error) {
|
||||
if (hasErrorCode(error, "ENOENT")) continue;
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!destinationCreated) {
|
||||
await mkdir(destination, { recursive: true });
|
||||
destinationCreated = true;
|
||||
}
|
||||
|
||||
const target = join(destination, file);
|
||||
await copyFile(source, target);
|
||||
if (!copied) console.log("copied:");
|
||||
copied = true;
|
||||
console.log(` ${source} → ${target}`);
|
||||
}
|
||||
}
|
||||
if (!copied) console.log("no items to copy");
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { Plugin } from "vite";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { copyBuildFiles } from "./copy-build-files";
|
||||
|
||||
const BUILD_FILES = ["manifest.json", "main.js", "styles.css"] as const;
|
||||
|
||||
let temporaryDirectory: string;
|
||||
|
||||
function createCopyPlugin(sourceDirectory: string, destinations: string[]): Plugin {
|
||||
return copyBuildFiles(destinations, sourceDirectory);
|
||||
}
|
||||
|
||||
async function runWriteBundle(plugin: Plugin): Promise<void> {
|
||||
const hook = plugin.writeBundle as
|
||||
| ((this: Plugin) => unknown)
|
||||
| { handler?: (this: Plugin) => unknown }
|
||||
| undefined;
|
||||
const handler = typeof hook === "function" ? hook : hook?.handler;
|
||||
if (!handler) {
|
||||
throw new Error("The copy plug-in does not define a writeBundle hook");
|
||||
}
|
||||
await handler.call(plugin);
|
||||
}
|
||||
|
||||
describe("build file copying", () => {
|
||||
beforeEach(async () => {
|
||||
temporaryDirectory = await mkdtemp(join(tmpdir(), "livesync-copy-build-files-"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(temporaryDirectory, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("does nothing when no destinations are configured", async () => {
|
||||
const plugin = createCopyPlugin(join(temporaryDirectory, "missing-source"), []);
|
||||
|
||||
await expect(runWriteBundle(plugin)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("copies the fixed build files to nested destinations and overwrites them on repeated builds", async () => {
|
||||
const sourceDirectory = join(temporaryDirectory, "build");
|
||||
const destinations = [
|
||||
join(temporaryDirectory, "vault-one", ".obsidian", "plugins", "livesync"),
|
||||
join(temporaryDirectory, "vault-two", "nested", ".obsidian", "plugins", "livesync"),
|
||||
];
|
||||
await mkdir(sourceDirectory, { recursive: true });
|
||||
await Promise.all(BUILD_FILES.map((file) => writeFile(join(sourceDirectory, file), `first ${file}`)));
|
||||
await mkdir(destinations[0], { recursive: true });
|
||||
await writeFile(join(destinations[0], "manifest.json"), "stale manifest");
|
||||
|
||||
const plugin = createCopyPlugin(sourceDirectory, destinations);
|
||||
expect(plugin.writeBundle).toBeDefined();
|
||||
await runWriteBundle(plugin);
|
||||
|
||||
for (const destination of destinations) {
|
||||
for (const file of BUILD_FILES) {
|
||||
await expect(readFile(join(destination, file), "utf8")).resolves.toBe(`first ${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
await writeFile(join(sourceDirectory, "main.js"), "second main.js");
|
||||
await runWriteBundle(plugin);
|
||||
|
||||
for (const destination of destinations) {
|
||||
await expect(readFile(join(destination, "main.js"), "utf8")).resolves.toBe("second main.js");
|
||||
}
|
||||
});
|
||||
|
||||
it("skips missing source files", async () => {
|
||||
const sourceDirectory = join(temporaryDirectory, "partial-build");
|
||||
const destination = join(temporaryDirectory, "vault", ".obsidian", "plugins", "livesync");
|
||||
await mkdir(sourceDirectory, { recursive: true });
|
||||
await writeFile(join(sourceDirectory, "manifest.json"), "manifest");
|
||||
|
||||
await runWriteBundle(createCopyPlugin(sourceDirectory, [destination]));
|
||||
|
||||
await expect(readFile(join(destination, "manifest.json"), "utf8")).resolves.toBe("manifest");
|
||||
await expect(readFile(join(destination, "main.js"), "utf8")).rejects.toMatchObject({ code: "ENOENT" });
|
||||
await expect(readFile(join(destination, "styles.css"), "utf8")).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
});
|
||||
|
||||
it("propagates source path errors", async () => {
|
||||
const invalidSourceDirectory = join(temporaryDirectory, "source-is-a-file");
|
||||
await writeFile(invalidSourceDirectory, "not a directory");
|
||||
|
||||
await expect(
|
||||
runWriteBundle(createCopyPlugin(invalidSourceDirectory, [join(temporaryDirectory, "vault")]))
|
||||
).rejects.toMatchObject({ code: "ENOTDIR" });
|
||||
});
|
||||
|
||||
it("propagates destination errors", async () => {
|
||||
const sourceDirectory = join(temporaryDirectory, "build");
|
||||
const destinationParent = join(temporaryDirectory, "destination-is-a-file");
|
||||
await mkdir(sourceDirectory, { recursive: true });
|
||||
await writeFile(join(sourceDirectory, "manifest.json"), "manifest");
|
||||
await writeFile(destinationParent, "not a directory");
|
||||
|
||||
await expect(
|
||||
runWriteBundle(createCopyPlugin(sourceDirectory, [join(destinationParent, "nested")]))
|
||||
).rejects.toMatchObject({ code: "ENOTDIR" });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import path from "node:path";
|
||||
|
||||
/**
|
||||
* @param {string} sourcePath
|
||||
* @param {string} filename
|
||||
* @param {string} cwd
|
||||
* @param {string} sourceRoot
|
||||
* @returns {string | null}
|
||||
*/
|
||||
export function resolveSourceAlias(sourcePath, filename, cwd, sourceRoot = "./src") {
|
||||
if (!/^(?:\.\/)?\.\.(?:\/|$)/u.test(sourcePath)) return null;
|
||||
const paths = /^[A-Za-z]:[\\/]|^\\\\/u.test(filename) ? path.win32 : path;
|
||||
const root = paths.resolve(cwd, sourceRoot);
|
||||
const importedPath = paths.resolve(paths.dirname(filename), sourcePath);
|
||||
const relative = paths.relative(root, importedPath);
|
||||
if (relative === ".." || relative.startsWith(`..${paths.sep}`) || paths.isAbsolute(relative)) return null;
|
||||
return relative ? `@/${relative.split(paths.sep).join("/")}` : "@";
|
||||
}
|
||||
|
||||
/** @type {import("eslint").Rule.RuleModule} */
|
||||
const preferSourceAlias = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
fixable: "code",
|
||||
docs: { description: "Prefer the source alias for relative parent imports within this repository" },
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: { sourceRoot: { type: "string" } },
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
messages: {
|
||||
parentImport: "Unexpected parent import '{{sourcePath}}'. Use '{{rewrittenImport}}' instead",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
if (context.filename === "<text>" || context.filename === "<input>") return {};
|
||||
return {
|
||||
ImportDeclaration(node) {
|
||||
const sourcePath = node.source.value;
|
||||
if (typeof sourcePath !== "string") return;
|
||||
const rewrittenImport = resolveSourceAlias(
|
||||
sourcePath,
|
||||
context.filename,
|
||||
context.cwd,
|
||||
context.options[0]?.sourceRoot
|
||||
);
|
||||
if (rewrittenImport === null) return;
|
||||
context.report({
|
||||
node,
|
||||
messageId: "parentImport",
|
||||
data: { sourcePath, rewrittenImport },
|
||||
fix: (fixer) =>
|
||||
fixer.replaceTextRange([node.source.range[0] + 1, node.source.range[1] - 1], rewrittenImport),
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default { rules: { "prefer-source-alias": preferSourceAlias } };
|
||||
@@ -0,0 +1,104 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { Linter } from "eslint";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import svelteParser from "svelte-eslint-parser";
|
||||
import importAlias, { resolveSourceAlias } from "./import-alias.mjs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
const cwd = process.cwd();
|
||||
const rule = importAlias.rules["prefer-source-alias"];
|
||||
|
||||
function lint(code: string, filename = "src/modules/example.ts") {
|
||||
const linter = new Linter({ cwd });
|
||||
return linter.verifyAndFix(
|
||||
code,
|
||||
[
|
||||
{
|
||||
files: ["**/*.ts", "**/*.svelte"],
|
||||
languageOptions: {
|
||||
parser: filename.endsWith(".svelte") ? svelteParser : tsParser,
|
||||
parserOptions: { parser: tsParser },
|
||||
},
|
||||
plugins: { local: { rules: { "prefer-source-alias": rule } } },
|
||||
rules: {
|
||||
"local/prefer-source-alias": ["error", { sourceRoot: "./src" }],
|
||||
},
|
||||
},
|
||||
],
|
||||
{ filename: resolve(cwd, filename) }
|
||||
);
|
||||
}
|
||||
|
||||
describe("source import aliases", () => {
|
||||
it.each([
|
||||
["src/modules/example.ts", "../common/types", "@/common/types"],
|
||||
["src/modules/example.ts", "./../common/types", "@/common/types"],
|
||||
["src/modules/example.ts", "..", "@"],
|
||||
["src/modules/example.ts", "../common/types.js", "@/common/types.js"],
|
||||
["src/apps/browser/main.ts", "../../common/types", "@/common/types"],
|
||||
["src/apps/cli/main.ts", "../../common/types", "@/common/types"],
|
||||
["src/apps/webapp/main.ts", "../../common/types", "@/common/types"],
|
||||
["src/apps/webpeer/src/main.ts", "../../../common/types", "@/common/types"],
|
||||
])("rewrites %s parent import %s to %s", (filename, sourcePath, expected) => {
|
||||
const result = lint(`import type { Value } from '${sourcePath}';`, filename);
|
||||
expect(result.messages).toEqual([]);
|
||||
expect(result.fixed).toBe(true);
|
||||
expect(result.output).toBe(`import type { Value } from '${expected}';`);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"./helper",
|
||||
"@/common/types",
|
||||
"@vrtmrz/livesync-commonlib/common/types",
|
||||
"node:path",
|
||||
"../../test/helper",
|
||||
])("preserves import %s", (sourcePath) => {
|
||||
const code = `import value from "${sourcePath}";`;
|
||||
const result = lint(code);
|
||||
expect(result.messages).toEqual([]);
|
||||
expect(result.fixed).toBe(false);
|
||||
expect(result.output).toBe(code);
|
||||
});
|
||||
|
||||
it("does not treat a sibling directory whose name begins with src as source-owned", () => {
|
||||
const code = 'import value from "../../src-other/helper";';
|
||||
const result = lint(code);
|
||||
expect(result.messages).toEqual([]);
|
||||
expect(result.fixed).toBe(false);
|
||||
expect(result.output).toBe(code);
|
||||
});
|
||||
|
||||
it("rewrites imports inside Svelte scripts", () => {
|
||||
const result = lint(
|
||||
'<script lang="ts">import type { Value } from "../../common/types";</script>',
|
||||
"src/components/dialog/Example.svelte"
|
||||
);
|
||||
expect(result.messages).toEqual([]);
|
||||
expect(result.fixed).toBe(true);
|
||||
expect(result.output).toBe('<script lang="ts">import type { Value } from "@/common/types";</script>');
|
||||
});
|
||||
|
||||
it.each([
|
||||
["C:\\live\\src\\modules\\file.ts", "../common/types", "C:\\live", "@/common/types"],
|
||||
["C:\\live\\src\\apps\\webpeer\\src\\main.ts", "../../../common/types", "C:\\live", "@/common/types"],
|
||||
["C:\\live\\src\\modules\\file.ts", "../../src-other/helper", "C:\\live", null],
|
||||
[
|
||||
"\\\\server\\share\\live\\src\\modules\\file.ts",
|
||||
"../common/types",
|
||||
"\\\\server\\share\\live",
|
||||
"@/common/types",
|
||||
],
|
||||
])("handles Windows source paths in %s", (filename, sourcePath, root, expected) => {
|
||||
expect(resolveSourceAlias(sourcePath, filename, root)).toBe(expected);
|
||||
});
|
||||
|
||||
it("uses the configured source root", () => {
|
||||
const result = resolveSourceAlias(
|
||||
"../common/types",
|
||||
resolve(cwd, "nested/src/modules/file.ts"),
|
||||
cwd,
|
||||
"nested/src"
|
||||
);
|
||||
expect(result).toBe("@/common/types");
|
||||
});
|
||||
});
|
||||
+2
-9
@@ -2,11 +2,11 @@ import { defineConfig } from "vitest/config";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import { sveltePreprocess } from "svelte-preprocess";
|
||||
import inlineWorkerPlugin from "esbuild-plugin-inline-worker";
|
||||
import copy from "rollup-plugin-copy";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import fs from "node:fs";
|
||||
import { platform } from "node:process";
|
||||
import { copyBuildFiles } from "./utils/copy-build-files";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
function readJson(filePath: string) {
|
||||
@@ -117,14 +117,7 @@ export default defineConfig(({ mode }) => {
|
||||
compilerOptions: { css: "injected", preserveComments: false },
|
||||
}),
|
||||
|
||||
copy({
|
||||
targets: ["manifest.json", "main.js", "styles.css"]
|
||||
.map((file) => PATH_TEST_INSTALL.map((dest) => ({ src: file, dest: dest })))
|
||||
.flat(),
|
||||
// Copy after the build is complete
|
||||
hook: "writeBundle",
|
||||
verbose: true,
|
||||
}),
|
||||
copyBuildFiles(PATH_TEST_INSTALL),
|
||||
],
|
||||
|
||||
resolve: {
|
||||
|
||||
Reference in New Issue
Block a user