feat(flake): provide .#edit-docs as a standalone docs editor (#8350)

This commit is contained in:
Elian Doran 2026-01-13 22:22:58 +02:00 committed by GitHub
commit 618459d353
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 316 additions and 80 deletions

View File

@ -165,6 +165,17 @@ pnpm install
pnpm edit-docs:edit-docs
```
Alternatively, if you have Nix installed:
```shell
# Run directly
nix run .#edit-docs
# Or install to your profile
nix profile install .#edit-docs
trilium-edit-docs
```
### Building the Executable
Download the repository, install dependencies using `pnpm` and then build the desktop app for Windows:
```shell

View File

@ -1,6 +1,6 @@
{
"name": "@triliumnext/edit-docs",
"version": "0.0.1",
"version": "0.101.3",
"private": true,
"description": "Desktop version of Trilium which imports the demo database (presented to new users at start-up) or the user guide and other documentation and saves the modifications for committing.",
"dependencies": {
@ -16,7 +16,9 @@
"fs-extra": "11.3.3"
},
"scripts": {
"edit-docs": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-docs.ts",
"build": "tsx scripts/build.ts",
"test-build": "vitest --config vitest.build.config.mts",
"edit-docs": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store tsx ../../scripts/electron-start.mts src/edit-docs.ts",
"edit-demo": "cross-env TRILIUM_PORT=37744 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-demo.ts"
}
}

View File

@ -0,0 +1,40 @@
import { writeFileSync } from "fs";
import { join } from "path";
import BuildHelper from "../../../scripts/build-utils";
import originalPackageJson from "../package.json" with { type: "json" };
const build = new BuildHelper("apps/edit-docs");
async function main() {
await build.buildBackend(["src/edit-docs.ts", "src/utils.ts"]);
// Copy assets from server (needed for DB initialization)
build.copy("/apps/server/src/assets", "assets/");
build.triggerBuildAndCopyTo("packages/share-theme", "share-theme/assets/");
build.copy("/packages/share-theme/src/templates", "share-theme/templates/");
build.copy("/node_modules/ckeditor5/dist/ckeditor5-content.css", "ckeditor5-content.css");
build.buildFrontend();
// Copy node modules dependencies
build.copyNodeModules(["better-sqlite3", "bindings", "file-uri-to-path", "@electron/remote"]);
generatePackageJson();
}
function generatePackageJson() {
const { version, author, license, description, dependencies, devDependencies } = originalPackageJson;
const packageJson = {
name: "trilium-edit-docs",
main: "edit-docs.cjs",
version,
author,
license,
description,
dependencies: {"better-sqlite3": dependencies["better-sqlite3"]},
devDependencies: {electron: devDependencies.electron},
};
writeFileSync(join(build.outDir, "package.json"), JSON.stringify(packageJson, null, "\t"), "utf-8");
}
main();

View File

@ -0,0 +1,48 @@
import { globSync } from "fs";
import { join } from "path";
import { it, describe, expect } from "vitest";
describe("Check artifacts are present", () => {
const distPath = join(__dirname, "../../dist");
it("has the necessary node modules", async () => {
const paths = [
"node_modules/better-sqlite3",
"node_modules/bindings",
"node_modules/file-uri-to-path",
"node_modules/@electron/remote"
];
ensurePathsExist(paths);
});
it("includes the client", async () => {
const paths = [
"public/assets",
"public/fonts",
"public/node_modules",
"public/src",
"public/stylesheets",
"public/translations"
];
ensurePathsExist(paths);
});
it("includes necessary assets", async () => {
const paths = [
"assets",
"share-theme",
"ckeditor5-content.css"
];
ensurePathsExist(paths);
});
function ensurePathsExist(paths: string[]) {
for (const path of paths) {
const result = globSync(join(distPath, path, "**"));
expect(result, path).not.toHaveLength(0);
}
}
});

View File

@ -1,14 +1,17 @@
import fs from "fs/promises";
import fsExtra from "fs-extra";
import path from "path";
import type { NoteMetaFile } from "@triliumnext/server/src/services/meta/note_meta.js";
import { initializeTranslations } from "@triliumnext/server/src/services/i18n.js";
import debounce from "@triliumnext/client/src/services/debounce.js";
import { extractZip, importData, initializeDatabase, startElectron } from "./utils.js";
import cls from "@triliumnext/server/src/services/cls.js";
import type { AdvancedExportOptions, ExportFormat } from "@triliumnext/server/src/services/export/zip/abstract_provider.js";
import { initializeTranslations } from "@triliumnext/server/src/services/i18n.js";
import { parseNoteMetaFile } from "@triliumnext/server/src/services/in_app_help.js";
import type { NoteMetaFile } from "@triliumnext/server/src/services/meta/note_meta.js";
import type NoteMeta from "@triliumnext/server/src/services/meta/note_meta.js";
import fs from "fs/promises";
import fsExtra from "fs-extra";
import yaml from "js-yaml";
import path from "path";
import packageJson from "../package.json" with { type: "json" };
import { extractZip, importData, initializeDatabase, startElectron } from "./utils.js";
interface NoteMapping {
rootNoteId: string;
@ -18,47 +21,113 @@ interface NoteMapping {
exportOnly?: boolean;
}
const { DOCS_ROOT, USER_GUIDE_ROOT } = process.env;
if (!DOCS_ROOT || !USER_GUIDE_ROOT) {
throw new Error("Missing DOCS_ROOT or USER_GUIDE_ROOT environment variable.");
interface Config {
baseUrl: string;
noteMappings: NoteMapping[];
}
const BASE_URL = "https://docs.triliumnotes.org";
// Parse command-line arguments
function parseArgs() {
const args = process.argv.slice(2);
let configPath: string | undefined;
let showHelp = false;
let showVersion = false;
const NOTE_MAPPINGS: NoteMapping[] = [
{
rootNoteId: "pOsGYCXsbNQG",
path: path.join(__dirname, DOCS_ROOT, "User Guide"),
format: "markdown"
},
{
rootNoteId: "pOsGYCXsbNQG",
path: path.join(__dirname, USER_GUIDE_ROOT),
format: "html",
ignoredFiles: ["index.html", "navigation.html", "style.css", "User Guide.html"],
exportOnly: true
},
{
rootNoteId: "jdjRLhLV3TtI",
path: path.join(__dirname, DOCS_ROOT, "Developer Guide"),
format: "markdown"
},
{
rootNoteId: "hD3V4hiu2VW4",
path: path.join(__dirname, DOCS_ROOT, "Release Notes"),
format: "markdown"
for (let i = 0; i < args.length; i++) {
if (args[i] === '--config' || args[i] === '-c') {
configPath = args[i + 1];
if (!configPath) {
console.error("Error: --config/-c requires a path argument");
process.exit(1);
}
i++; // Skip the next argument as it's the value
} else if (args[i] === '--help' || args[i] === '-h') {
showHelp = true;
} else if (args[i] === '--version' || args[i] === '-v') {
showVersion = true;
}
}
];
return { configPath, showHelp, showVersion };
}
function getVersion(): string {
return packageJson.version;
}
function printHelp() {
const version = getVersion();
console.log(`
Usage: trilium-edit-docs [options]
Options:
-c, --config <path> Path to the configuration file (default: edit-docs-config.yaml in the root)
-h, --help Display this help message
-v, --version Display version information
Version: ${version}
`);
}
function printVersion() {
const version = getVersion();
console.log(version);
}
const { configPath, showHelp, showVersion } = parseArgs();
if (showHelp) {
printHelp();
process.exit(0);
} else if (showVersion) {
printVersion();
process.exit(0);
}
// Configuration variables to be initialized
let BASE_URL: string;
let NOTE_MAPPINGS: NoteMapping[];
// Load configuration from edit-docs-config.yaml
async function loadConfig() {
let CONFIG_PATH = configPath
? path.resolve(configPath)
: path.join(process.cwd(), "edit-docs-config.yaml");
const exists = await fs.access(CONFIG_PATH).then(() => true).catch(() => false);
if (!exists && !configPath) {
// Fallback to project root if running from within a subproject
CONFIG_PATH = path.join(__dirname, "../../../edit-docs-config.yaml");
}
const configContent = await fs.readFile(CONFIG_PATH, "utf-8");
const config = yaml.load(configContent) as Config;
BASE_URL = config.baseUrl;
// Resolve all paths relative to the config file's directory (for flexibility with external configs)
const CONFIG_DIR = path.dirname(CONFIG_PATH);
NOTE_MAPPINGS = config.noteMappings.map((mapping) => ({
...mapping,
path: path.resolve(CONFIG_DIR, mapping.path)
}));
}
async function main() {
await loadConfig();
const initializedPromise = startElectron(() => {
// Wait for the import to be finished and the application to be loaded before we listen to changes.
setTimeout(() => registerHandlers(), 10_000);
setTimeout(() => {
registerHandlers();
}, 10_000);
});
await initializeTranslations();
await initializeDatabase(true);
// Wait for becca to be loaded before importing data
const beccaLoader = await import("@triliumnext/server/src/becca/becca_loader.js");
await beccaLoader.beccaLoaded;
cls.init(async () => {
for (const mapping of NOTE_MAPPINGS) {
if (!mapping.exportOnly) {
@ -142,7 +211,7 @@ async function exportData(noteId: string, format: ExportFormat, outputPath: stri
}
}
const minifyMeta = (format === "html");
const minifyMeta = (format === "html" || format === "share");
await cleanUpMeta(outputPath, minifyMeta);
}
@ -195,7 +264,6 @@ async function registerHandlers() {
return;
}
console.log("Got entity changed", e.entityName, e.entity.title);
debouncer();
});
}

View File

@ -7,18 +7,21 @@ import type { WriteStream } from "fs";
import fs from "fs/promises";
import fsExtra from "fs-extra";
import path from "path";
import { resolve } from "path";
import { deferred, DeferredPromise } from "../../../packages/commons/src";
import { deferred, type DeferredPromise } from "../../../packages/commons/src/index.js";
export function initializeDatabase(skipDemoDb: boolean) {
return new Promise<void>(async (resolve) => {
const sqlInit = (await import("@triliumnext/server/src/services/sql_init.js")).default;
cls.init(async () => {
if (!sqlInit.isDbInitialized()) {
await sqlInit.createInitialDatabase(skipDemoDb);
}
resolve();
export function initializeDatabase(skipDemoDb: boolean): Promise<void> {
return new Promise<void>((resolve) => {
import("@triliumnext/server/src/services/sql_init.js").then((m) => {
const sqlInit = m.default;
cls.init(async () => {
if (!sqlInit.isDbInitialized()) {
sqlInit.createInitialDatabase(skipDemoDb).then(() => resolve());
} else {
sqlInit.dbReady.resolve();
resolve();
}
});
});
});
}
@ -78,7 +81,6 @@ async function createImportZip(path: string) {
zlib: { level: 0 }
});
console.log("Archive path is ", resolve(path));
archive.directory(path, "/");
const outputStream = fsExtra.createWriteStream(inputFile);
@ -93,9 +95,11 @@ async function createImportZip(path: string) {
}
function waitForEnd(archive: Archiver, stream: WriteStream) {
return new Promise<void>(async (res, rej) => {
stream.on("finish", () => res());
await archive.finalize();
return new Promise<void>((res, rej) => {
stream.on("finish", res);
stream.on("error", rej);
archive.on("error", rej);
archive.finalize().catch(rej);
});
}

View File

@ -0,0 +1,17 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/edit-docs',
plugins: [],
test: {
watch: false,
globals: true,
environment: "node",
include: ['spec/build-checks/**'],
reporters: [
"verbose"
]
},
}));

24
edit-docs-config.yaml Normal file
View File

@ -0,0 +1,24 @@
baseUrl: "https://docs.triliumnotes.org"
noteMappings:
- rootNoteId: "pOsGYCXsbNQG"
path: "docs/User Guide"
format: "markdown"
- rootNoteId: "pOsGYCXsbNQG"
path: "apps/server/src/assets/doc_notes/en/User Guide"
format: "html"
ignoredFiles:
- "index.html"
- "navigation.html"
- "style.css"
- "User Guide.html"
exportOnly: true
- rootNoteId: "jdjRLhLV3TtI"
path: "docs/Developer Guide"
format: "markdown"
- rootNoteId: "hD3V4hiu2VW4"
path: "docs/Release Notes"
format: "markdown"

View File

@ -112,7 +112,7 @@
nodejs.python
removeReferencesTo
]
++ lib.optionals (app == "desktop") [
++ lib.optionals (app == "desktop" || app == "edit-docs") [
copyDesktopItems
# required for NIXOS_OZONE_WL expansion
# https://github.com/NixOS/nixpkgs/issues/172583
@ -252,10 +252,33 @@
--add-flags $out/opt/trilium-server/main.cjs
'';
};
edit-docs = makeApp {
app = "edit-docs";
preBuildCommands = ''
export npm_config_nodedir=${electron.headers}
pnpm postinstall
'';
buildTask = "edit-docs:build";
mainProgram = "trilium-edit-docs";
installCommands = ''
#remove-references-to -t ${electron.headers} apps/edit-docs/dist/node_modules/better-sqlite3/build/config.gypi
#remove-references-to -t ${nodejs.python} apps/edit-docs/dist/node_modules/better-sqlite3/build/config.gypi
mkdir -p $out/{bin,opt/trilium-edit-docs}
cp --archive apps/edit-docs/dist/* $out/opt/trilium-edit-docs
makeShellWrapper ${lib.getExe electron} $out/bin/trilium-edit-docs \
--set-default ELECTRON_IS_DEV 0 \
--set TRILIUM_RESOURCE_DIR $out/opt/trilium-edit-docs \
--add-flags $out/opt/trilium-edit-docs/edit-docs.cjs
'';
};
in
{
packages.desktop = desktop;
packages.server = server;
packages.edit-docs = edit-docs;
packages.default = desktop;

View File

@ -17,6 +17,8 @@
"desktop:start": "pnpm run --filter desktop dev",
"desktop:build": "pnpm run --filter desktop build",
"desktop:start-prod": "pnpm run --filter desktop start-prod",
"edit-docs:edit-docs": "pnpm run --filter edit-docs edit-docs",
"edit-docs:build": "pnpm run --filter edit-docs build",
"website:start": "pnpm run --filter website dev",
"website:build": "pnpm run --filter website build",
"electron:build": "pnpm desktop:build",
@ -28,7 +30,6 @@
"chore:update-version": "tsx ./scripts/update-version.ts",
"docs:build": "pnpm run --filter build-docs start",
"docs:preview": "pnpm http-server site -p 9000",
"edit-docs:edit-docs": "pnpm run --filter edit-docs edit-docs",
"edit-docs:edit-demo": "pnpm run --filter edit-docs edit-demo",
"test:all": "pnpm test:parallel && pnpm test:sequential",
"test:parallel": "pnpm --filter=!server --filter=!ckeditor5-mermaid --filter=!ckeditor5-math --parallel test",
@ -48,6 +49,7 @@
"@playwright/test": "1.57.0",
"@triliumnext/server": "workspace:*",
"@types/express": "5.0.6",
"@types/js-yaml": "4.0.9",
"@types/node": "24.10.7",
"@vitest/browser-webdriverio": "4.0.17",
"@vitest/coverage-v8": "4.0.17",
@ -64,6 +66,7 @@
"happy-dom": "20.1.0",
"http-server": "14.1.1",
"jiti": "2.6.1",
"js-yaml": "4.1.1",
"jsonc-eslint-parser": "2.4.2",
"react-refresh": "0.18.0",
"rollup-plugin-webpack-stats": "2.1.9",

43
pnpm-lock.yaml generated
View File

@ -49,6 +49,9 @@ importers:
'@types/express':
specifier: 5.0.6
version: 5.0.6
'@types/js-yaml':
specifier: 4.0.9
version: 4.0.9
'@types/node':
specifier: 24.10.7
version: 24.10.7
@ -97,6 +100,9 @@ importers:
jiti:
specifier: 2.6.1
version: 2.6.1
js-yaml:
specifier: 4.1.1
version: 4.1.1
jsonc-eslint-parser:
specifier: 2.4.2
version: 2.4.2
@ -5372,6 +5378,9 @@ packages:
'@types/jquery@3.5.33':
resolution: {integrity: sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==}
'@types/js-yaml@4.0.9':
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@ -15006,6 +15015,8 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.3.0
'@ckeditor/ckeditor5-upload': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-ai@47.3.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)':
dependencies:
@ -15146,6 +15157,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
'@ckeditor/ckeditor5-widget': 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-cloud-services@47.3.0':
dependencies:
@ -15344,6 +15357,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-classic@47.3.0':
dependencies:
@ -15353,6 +15368,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-decoupled@47.3.0':
dependencies:
@ -15594,8 +15611,6 @@ snapshots:
'@ckeditor/ckeditor5-widget': 47.3.0
ckeditor5: 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-import-word@47.3.0':
dependencies:
@ -15621,8 +15636,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-inspector@5.0.0': {}
@ -15632,8 +15645,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-line-height@47.3.0':
dependencies:
@ -15658,8 +15669,6 @@ snapshots:
'@ckeditor/ckeditor5-widget': 47.3.0
ckeditor5: 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-list-multi-level@47.3.0':
dependencies:
@ -15683,8 +15692,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-markdown-gfm@47.3.0':
dependencies:
@ -15813,8 +15820,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
'@ckeditor/ckeditor5-widget': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-pagination@47.3.0':
dependencies:
@ -15922,8 +15927,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-slash-command@47.3.0':
dependencies:
@ -15936,8 +15939,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-source-editing-enhanced@47.3.0':
dependencies:
@ -15985,8 +15986,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-table@47.3.0':
dependencies:
@ -16113,8 +16112,6 @@ snapshots:
'@ckeditor/ckeditor5-engine': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-widget@47.3.0':
dependencies:
@ -16134,8 +16131,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@codemirror/autocomplete@6.18.6':
dependencies:
@ -19984,6 +19979,8 @@ snapshots:
dependencies:
'@types/sizzle': 2.3.9
'@types/js-yaml@4.0.9': {}
'@types/json-schema@7.0.15': {}
'@types/jsonfile@6.1.4':
@ -21650,8 +21647,6 @@ snapshots:
ckeditor5-collaboration@47.3.0:
dependencies:
'@ckeditor/ckeditor5-collaboration-core': 47.3.0
transitivePeerDependencies:
- supports-color
ckeditor5-premium-features@47.3.0(bufferutil@4.0.9)(ckeditor5@47.3.0)(utf-8-validate@6.0.5):
dependencies:

View File

@ -3,7 +3,8 @@ import { getElectronPath, isNixOS } from "./utils.mjs";
const LD_LIBRARY_PATH = isNixOS() && execSync("nix eval --raw nixpkgs#gcc.cc.lib").toString("utf-8") + "/lib";
execSync(`${getElectronPath()} ${process.argv[2]} --no-sandbox`, {
const args = process.argv.slice(2);
execSync(`${getElectronPath()} ${args.join(" ")} --no-sandbox`, {
stdio: "inherit",
env: {
...process.env,

View File

@ -26,7 +26,7 @@ function getVersion(packageJsonPath: string) {
function main() {
const version = getVersion(join(__dirname, "..", "package.json"));
for (const appName of ["server", "client", "desktop"]) {
for (const appName of ["server", "client", "desktop", "edit-docs"]) {
patchPackageJson(join(__dirname, "..", "apps", appName, "package.json"), version);
}