mirror of
https://github.com/jacobtread/obsidian-timekeep.git
synced 2026-07-22 10:10:27 +00:00
refactor: replace jest & esbuild with vite and vitest
This commit is contained in:
parent
2229612d6f
commit
31197666d7
22 changed files with 1255 additions and 4712 deletions
|
|
@ -1,32 +0,0 @@
|
|||
/* eslint-disable */
|
||||
|
||||
const process = require("process");
|
||||
const builtins = require("builtin-modules");
|
||||
|
||||
const prod = process.argv[2] === "production";
|
||||
|
||||
module.exports = {
|
||||
platform: "browser",
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtins,
|
||||
],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
loader: { ".ttf": "dataurl" },
|
||||
};
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
const { pathsToModuleNameMapper, JestConfigWithTsJest } = require("ts-jest");
|
||||
const { compilerOptions } = require("./tsconfig.json");
|
||||
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
moduleFileExtensions: ["ts", "js", "json", "node"],
|
||||
moduleDirectories: ["node_modules", "<rootDir>"],
|
||||
extensionsToTreatAsEsm: [".ts"],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths),
|
||||
};
|
||||
5621
package-lock.json
generated
5621
package-lock.json
generated
File diff suppressed because it is too large
Load diff
18
package.json
18
package.json
|
|
@ -3,33 +3,31 @@
|
|||
"version": "1.16.0",
|
||||
"description": "Obsidian plugin for time tracking",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
"build": "node scripts/build.js",
|
||||
"version": "node scripts/version-bump.mjs && git add manifest.json versions.json",
|
||||
"version": "node scripts/version-bump.js && git add manifest.json versions.json",
|
||||
"lint": "eslint src",
|
||||
"format": "prettier -w src",
|
||||
"test": "jest --coverage"
|
||||
"test": "vitest run --coverage"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Jacobtread",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/node": "^20.19.37",
|
||||
"@types/pdfmake": "^0.3.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
"@typescript-eslint/parser": "8.57.2",
|
||||
"builtin-modules": "^3.3.0",
|
||||
"chokidar": "^5.0.0",
|
||||
"esbuild": "^0.27.4",
|
||||
"@vitest/coverage-v8": "^4.1.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-perfectionist": "^5.7.0",
|
||||
"jest": "^30.3.0",
|
||||
"obsidian": "latest",
|
||||
"prettier": "^3.8.1",
|
||||
"ts-jest": "^29.4.6",
|
||||
"typescript": "5.9.2"
|
||||
"typescript": "5.9.2",
|
||||
"vite": "^8.0.2",
|
||||
"vitest": "^4.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"moment": "^2.30.1",
|
||||
|
|
|
|||
|
|
@ -1,57 +1,37 @@
|
|||
const path = require("path");
|
||||
const fs = require('fs/promises');
|
||||
const esbuild = require("esbuild");
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import { build } from "vite";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const esbuildConfig = require('../esbuild.config');
|
||||
|
||||
async function build() {
|
||||
const rootPath = path.join(__dirname, "../");
|
||||
async function buildPlugin() {
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const rootPath = path.resolve(__dirname, "../");
|
||||
const manifestPath = path.join(rootPath, "manifest.json");
|
||||
const outputPath = path.join(rootPath, "dist");
|
||||
const srcPath = path.join(rootPath, "src");
|
||||
|
||||
// Setup output path
|
||||
const outputExists = await dirExists(outputPath);
|
||||
if (!outputExists) {
|
||||
fs.mkdir(outputPath, { recursive: true });
|
||||
}
|
||||
await ensureDir(outputPath);
|
||||
|
||||
// Perform a build
|
||||
await esbuild.build({
|
||||
...esbuildConfig,
|
||||
entryPoints:
|
||||
[
|
||||
path.join(srcPath, "main.ts"),
|
||||
path.join(srcPath, 'styles.css'),
|
||||
],
|
||||
bundle: true,
|
||||
outdir: path.join(outputPath),
|
||||
await build({
|
||||
configFile: path.resolve(rootPath, "vite.config.js"),
|
||||
});
|
||||
|
||||
// Copy files from outside build
|
||||
for (const filePath of [manifestPath]) {
|
||||
const filename = path.basename(filePath);
|
||||
const destPath = path.join(outputPath, filename);
|
||||
try {
|
||||
await fs.copyFile(filePath, destPath);
|
||||
} catch (e) {
|
||||
console.error(`❌ Failed to copy ${filename}:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function dirExists(path) {
|
||||
const destManifest = path.join(outputPath, "manifest.json");
|
||||
try {
|
||||
const stat = await fs.stat(path);
|
||||
return stat.isDirectory();
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') return false;
|
||||
throw err;
|
||||
await fs.copyFile(manifestPath, destManifest);
|
||||
console.info(`Copied manifest.json`);
|
||||
} catch (e) {
|
||||
console.error(`Failed to copy manifest.json:`, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureDir(dir) {
|
||||
try {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
build().catch((err) => {
|
||||
buildPlugin().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
125
scripts/dev.js
125
scripts/dev.js
|
|
@ -1,100 +1,77 @@
|
|||
const path = require("path");
|
||||
const fs = require('fs/promises');
|
||||
const esbuild = require("esbuild");
|
||||
const chokidar = require("chokidar");
|
||||
|
||||
const esbuildConfig = require('../esbuild.config');
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import { build } from "vite";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
async function dev() {
|
||||
const rootPath = path.join(__dirname, "../");
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const rootPath = path.resolve(__dirname, "../");
|
||||
const manifestPath = path.join(rootPath, "manifest.json");
|
||||
const outputPath = path.join(rootPath, "dist");
|
||||
const srcPath = path.join(rootPath, "src");
|
||||
|
||||
const manifestContents = await fs.readFile(manifestPath);
|
||||
const manifestContents = await fs.readFile(manifestPath, "utf-8");
|
||||
const manifest = JSON.parse(manifestContents);
|
||||
|
||||
const vaultPath = path.join(rootPath, "test-vault");
|
||||
const pluginPath = path.join(vaultPath, ".obsidian", "plugins", manifest.id);
|
||||
const pluginPath = path.join(
|
||||
vaultPath,
|
||||
".obsidian",
|
||||
"plugins",
|
||||
manifest.id
|
||||
);
|
||||
|
||||
// Setup path to the plugin
|
||||
const pluginPathExists = await dirExists(pluginPath);
|
||||
if (!pluginPathExists) {
|
||||
fs.mkdir(pluginPath, { recursive: true });
|
||||
}
|
||||
|
||||
// Setup output path
|
||||
const outputExists = await dirExists(outputPath);
|
||||
if (!outputExists) {
|
||||
fs.mkdir(outputPath, { recursive: true });
|
||||
}
|
||||
await ensureDir(pluginPath);
|
||||
await ensureDir(outputPath);
|
||||
|
||||
const inputFiles = [
|
||||
path.join(outputPath, 'main.js'),
|
||||
path.join(outputPath, 'styles.css'),
|
||||
path.join(outputPath, "main.js"),
|
||||
path.join(outputPath, "styles.css"),
|
||||
manifestPath,
|
||||
];
|
||||
|
||||
// Setup esbuild
|
||||
const ctx = await esbuild.context({
|
||||
...esbuildConfig,
|
||||
entryPoints:
|
||||
[
|
||||
path.join(srcPath, "main.ts"),
|
||||
path.join(srcPath, 'styles.css')
|
||||
],
|
||||
bundle: true,
|
||||
outdir: path.join(outputPath),
|
||||
build({
|
||||
mode: "development",
|
||||
configFile: path.resolve(rootPath, "vite.config.js"),
|
||||
build: {
|
||||
watch: {},
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: "build-finish-copy",
|
||||
closeBundle() {
|
||||
console.info("Build finished copying files");
|
||||
copyFiles();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
// Perform an initial rebuild
|
||||
await ctx.rebuild();
|
||||
|
||||
// Copy initial files
|
||||
for (const filePath of inputFiles) {
|
||||
const filename = path.basename(filePath);
|
||||
const destPath = path.join(pluginPath, filename);
|
||||
try {
|
||||
await fs.copyFile(filePath, destPath);
|
||||
} catch (e) {
|
||||
console.error(`❌ Failed to copy ${filename}:`, e.message);
|
||||
const copyFiles = async () => {
|
||||
for (const filePath of inputFiles) {
|
||||
const destPath = path.join(pluginPath, path.basename(filePath));
|
||||
try {
|
||||
await fs.copyFile(filePath, destPath);
|
||||
console.info(`✓ Updated ${path.basename(filePath)}`);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Failed to copy ${path.basename(filePath)}:`,
|
||||
e.message
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Setup watchers for changed files
|
||||
const watcher = chokidar.watch(inputFiles, { ignoreInitial: true });
|
||||
watcher.on("change", async (changedPath) => {
|
||||
const filename = path.basename(changedPath);
|
||||
const destPath = path.join(pluginPath, filename);
|
||||
try {
|
||||
await fs.copyFile(changedPath, destPath);
|
||||
console.log(`🔁 Updated ${filename}`);
|
||||
} catch (e) {
|
||||
console.error(`❌ Failed to copy ${filename}:`, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Start esbuild watching for changes and re-building
|
||||
await ctx.watch();
|
||||
|
||||
console.log("🚀 Dev server started. Watching for changes...");
|
||||
console.info("🚀 Dev server started. Watching for changes...");
|
||||
}
|
||||
|
||||
async function dirExists(path) {
|
||||
async function ensureDir(dir) {
|
||||
try {
|
||||
const stat = await fs.stat(path);
|
||||
return stat.isDirectory();
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') return false;
|
||||
throw err;
|
||||
}
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
||||
dev().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { TFile, Vault } from "obsidian";
|
||||
import { vi } from "vitest";
|
||||
|
||||
function createTFile(vault: Vault, path: string): TFile {
|
||||
const nameIndex = path.lastIndexOf("/");
|
||||
|
|
@ -30,30 +31,30 @@ export class MockVault {
|
|||
this._files = initialFiles;
|
||||
}
|
||||
|
||||
getMarkdownFiles = jest.fn(() => {
|
||||
getMarkdownFiles = vi.fn(() => {
|
||||
return Object.keys(this._files).map((path) =>
|
||||
createTFile(this.asVault(), path)
|
||||
);
|
||||
});
|
||||
|
||||
process = jest.fn(async (file: TFile, func: (data: string) => string) => {
|
||||
process = vi.fn(async (file: TFile, func: (data: string) => string) => {
|
||||
const content = await this.read(file);
|
||||
const data = func(content);
|
||||
await this.write(file, data);
|
||||
return data;
|
||||
});
|
||||
|
||||
read = jest.fn(async (file: TFile) => {
|
||||
read = vi.fn(async (file: TFile) => {
|
||||
this._cache[file.path] = this._files[file.path];
|
||||
return this._files[file.path] ?? "";
|
||||
});
|
||||
|
||||
write = jest.fn(async (file: TFile, data: string) => {
|
||||
write = vi.fn(async (file: TFile, data: string) => {
|
||||
this._files[file.path] = data;
|
||||
this._cache[file.path] = data;
|
||||
});
|
||||
|
||||
cachedRead = jest.fn(async (file: TFile) => {
|
||||
cachedRead = vi.fn(async (file: TFile) => {
|
||||
if (this._cache[file.path]) {
|
||||
return this._cache[file.path];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
|||
|
||||
import { MockVault } from "./__mocks__/obsidian";
|
||||
import { stopAllTimekeeps } from "./stopAllTimekeeps";
|
||||
import { expect, it, describe } from "vitest";
|
||||
|
||||
describe("stopAllTimekeeps", () => {
|
||||
it("should stop nothing when no markdown files", async () => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
|||
|
||||
import { MockVault } from "./__mocks__/obsidian";
|
||||
import { stopFileTimekeeps } from "./stopFileTimekeeps";
|
||||
import { expect, it, describe } from "vitest";
|
||||
|
||||
describe("stopFileTimekeeps", () => {
|
||||
it("nothing should change if the file has no timekeeps", async () => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
TimekeepSettings,
|
||||
legacySettingsCompatibility,
|
||||
} from "./settings";
|
||||
import { expect, test, describe } from "vitest";
|
||||
|
||||
describe("legacy settings compatibility conversion", () => {
|
||||
test("Empty setting", () => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import moment from "moment";
|
|||
|
||||
import { withEntry, createEntry, withSubEntry } from "./create";
|
||||
import { stripEntryRuntimeData, stripEntriesRuntimeData } from "./schema";
|
||||
import { expect, it, test, describe } from "vitest";
|
||||
|
||||
describe("createEntry", () => {
|
||||
it("creating entry should use current time", () => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
replaceTimekeepCodeblock,
|
||||
extractTimekeepCodeblocks,
|
||||
} from "./parser";
|
||||
import { expect, it, test, describe } from "vitest";
|
||||
|
||||
/**
|
||||
* Generates a code block surrounding the provided JSON
|
||||
|
|
@ -184,7 +185,7 @@ describe("loading timekeep", () => {
|
|||
it("should tolerate a timekeep with leading or trailing whitespaces", () => {
|
||||
const input = `
|
||||
\`\`\`timekeep
|
||||
\`\`\`
|
||||
\`\`\`
|
||||
`;
|
||||
// Start not code fences
|
||||
replaceTimekeepCodeblock({ entries: [] }, input, 1, 2);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
getEntryDuration,
|
||||
getTotalDuration,
|
||||
} from "./queries";
|
||||
import { expect, it, test, describe } from "vitest";
|
||||
|
||||
describe("getEntryById", () => {
|
||||
it("find top level entry", async () => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import moment from "moment";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { TIMEKEEP } from "@/timekeep/schema";
|
||||
import { expect, it, describe, vi } from "vitest";
|
||||
|
||||
jest.mock("uuid", () => ({
|
||||
v4: jest.fn(() => "mocked-uuid"),
|
||||
}));
|
||||
vi.mock(import("uuid"), async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal()),
|
||||
v4: vi.fn(() => "mocked-uuid"),
|
||||
};
|
||||
});
|
||||
|
||||
describe("schema transform", () => {
|
||||
it("transforms input with an added id", () => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
defaultSettings,
|
||||
TimekeepSettings,
|
||||
} from "@/settings";
|
||||
|
||||
import { expect, it, describe } from "vitest";
|
||||
import { getEntriesSorted } from "./sort";
|
||||
|
||||
describe("getEntriesSorted", () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { stripEntriesRuntimeData } from "./schema";
|
||||
import { startNewEntry, startNewNestedEntry } from "./start";
|
||||
import { expect, it, describe } from "vitest";
|
||||
|
||||
describe("startNewEntry", () => {
|
||||
it("starting a new entry should stop any running entries", async () => {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
setEntryCollapsed,
|
||||
stopRunningEntries,
|
||||
} from "./update";
|
||||
import { expect, it, describe } from "vitest";
|
||||
|
||||
describe("updateEntry", () => {
|
||||
it("updating existing entry should succeed", async () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { NameSegmentType, parseNameSegments } from "./name";
|
||||
import { expect, it, test, describe } from "vitest";
|
||||
|
||||
describe("parseNameSegments", () => {
|
||||
it("should parse plain text without modification", () => {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import {
|
|||
formatEditableTimestamp,
|
||||
} from "./time";
|
||||
|
||||
import { expect, it, test, describe } from "vitest";
|
||||
|
||||
it("should format time", () => {
|
||||
const input = moment("2024-03-31T02:34:45.413Z").utc();
|
||||
const expected = "24-03-31 02:34:45";
|
||||
|
|
|
|||
58
vite.config.js
Normal file
58
vite.config.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { defineConfig } from "vite";
|
||||
import { builtinModules } from "module";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export default defineConfig((env) => ({
|
||||
build: {
|
||||
outDir: "dist",
|
||||
sourcemap: env.mode === "production" ? false : "inline",
|
||||
target: "es2018",
|
||||
minify: true,
|
||||
lib: {
|
||||
entry: "src/main.ts",
|
||||
formats: ["cjs"],
|
||||
fileName: () => "main.js",
|
||||
},
|
||||
rolldownOptions: {
|
||||
input: {
|
||||
main: path.resolve(__dirname, "src/main.ts"),
|
||||
styles: path.resolve(__dirname, "src/styles.css"),
|
||||
},
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtinModules,
|
||||
],
|
||||
output: {
|
||||
exports: "auto",
|
||||
},
|
||||
},
|
||||
//
|
||||
cssCodeSplit: true,
|
||||
// Inline all assets (.ttf ...etc)
|
||||
assetsInlineLimit: Infinity,
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "src"),
|
||||
},
|
||||
},
|
||||
|
||||
test: {},
|
||||
}));
|
||||
Loading…
Reference in a new issue