Remove vulnerable development tooling and update Commonlib

This commit is contained in:
vorotamoroz
2026-10-13 08:04:51 +00:00
parent b4d00d3dda
commit aa69212db9
10 changed files with 404 additions and 897 deletions
+42
View File
@@ -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");
},
};
}
+107
View File
@@ -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" });
});
});
+62
View File
@@ -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 } };
+104
View File
@@ -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");
});
});