From 81e4e2b647e5d5ae51594eaa776b2e88307e530d Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Thu, 18 Dec 2025 18:13:40 +0530
Subject: [PATCH 01/28] Update package-lock.json
---
package-lock.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package-lock.json b/package-lock.json
index 5f88706..a96d262 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -42,7 +42,7 @@
"prettier": "^3.2.5"
},
"engines": {
- "node": ">=22.0.0"
+ "node": ">=18.0.0"
}
},
"node_modules/@antfu/install-pkg": {
From a50cb559eeebe44d3ed86c6604fc9d924b187d9b Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Thu, 18 Dec 2025 18:13:54 +0530
Subject: [PATCH 02/28] lowered node engine to 18
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 4582b43..d481a3e 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"engines": {
- "node": ">=22.0.0"
+ "node": ">=18.0.0"
},
"repository": {
"type": "git",
From d0577dde2411ff61b5daf3ccbf9e927387a52ada Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 20 Dec 2025 05:08:14 +0530
Subject: [PATCH 03/28] Create .npmignore
---
.npmignore | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 .npmignore
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..e4349c7
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,11 @@
+.git
+.github
+.DS_Store
+.gitignore
+.gitattributes
+genctx.config.json
+genctx.context.md
+docs/
+assets/
+preview.gif
+docmd-preview.png
\ No newline at end of file
From f5cbfbce018402a74f7f3336c8111d697ebe4994 Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 20 Dec 2025 05:08:22 +0530
Subject: [PATCH 04/28] 0.3.3
---
package-lock.json | 4 ++--
package.json | 52 +++++++++++++++++++++++++----------------------
2 files changed, 30 insertions(+), 26 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index a96d262..bc98a58 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@mgks/docmd",
- "version": "0.3.2",
+ "version": "0.3.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@mgks/docmd",
- "version": "0.3.2",
+ "version": "0.3.3",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index d481a3e..2718767 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@mgks/docmd",
- "version": "0.3.2",
+ "version": "0.3.3",
"description": "Generate beautiful, lightweight static documentation sites directly from your Markdown files. Zero clutter, just content.",
"main": "src/index.js",
"exports": {
@@ -18,29 +18,8 @@
"postinstall": "node ./bin/postinstall.js",
"lint": "eslint .",
"format": "prettier --write .",
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "echo \"No tests specified currently\" && exit 0"
},
- "engines": {
- "node": ">=18.0.0"
- },
- "repository": {
- "type": "git",
- "url": "git+https://github.com/mgks/docmd.git"
- },
- "keywords": [
- "markdown",
- "static-site-generator",
- "documentation",
- "ssg",
- "docs",
- "generator"
- ],
- "author": "Ghazi ",
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/mgks/docmd/issues"
- },
- "homepage": "https://github.com/mgks/docmd#readme",
"dependencies": {
"chalk": "^4.1.2",
"chokidar": "^4.0.3",
@@ -72,5 +51,30 @@
},
"directories": {
"doc": "docs"
- }
+ },
+ "keywords": [
+ "markdown",
+ "static-site-generator",
+ "documentation",
+ "ssg",
+ "docs",
+ "generator"
+ ],
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "author": {
+ "name": "Ghazi",
+ "url": "https://mgks.dev"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/mgks/docmd.git"
+ },
+ "bugs": {
+ "url": "https://github.com/mgks/docmd/issues"
+ },
+ "homepage": "https://github.com/mgks/docmd#readme",
+ "funding": "https://github.com/sponsors/mgks",
+ "license": "MIT"
}
\ No newline at end of file
From 223436d09183181eb6d0e1c441143ccfc77443e9 Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 20 Dec 2025 05:08:25 +0530
Subject: [PATCH 05/28] Update README.md
---
README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 0fcb9d5..d9aa070 100644
--- a/README.md
+++ b/README.md
@@ -159,4 +159,8 @@ This project is open source and free to use. If you find it valuable, please con
## ๐ License
-Distributed under the MIT License. See `LICENSE` for more information.
\ No newline at end of file
+Distributed under the MIT License. See `LICENSE` for more information.
+
+> **{ github.com/mgks }**
+>
+>  
\ No newline at end of file
From 8db2d2c6eee3f2a0ff635e63645ef2edf42c9ede Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 20 Dec 2025 05:10:25 +0530
Subject: [PATCH 06/28] Create dependabot.yml
---
.github/dependabot.yml | 44 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
create mode 100644 .github/dependabot.yml
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..a0efb43
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,44 @@
+version: 2
+updates:
+ # -------------------------------------------------------
+ # 1. GitHub Actions (Universal for all your projects)
+ # Keeps your workflow files (checkout, setup-node) updated
+ # -------------------------------------------------------
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ groups:
+ actions:
+ patterns:
+ - "*"
+
+ # -------------------------------------------------------
+ # 2. NPM (Specific to tree-fs / Node projects)
+ # -------------------------------------------------------
+ - package-ecosystem: "npm"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ # Ignore major updates automatically to prevent breaking changes
+ # Remove this 'ignore' block if you want to see v1 -> v2 updates
+ ignore:
+ - dependency-name: "*"
+ update-types: ["version-update:semver-major"]
+ groups:
+ # Group all minor/patch updates into one PR
+ npm-dependencies:
+ patterns:
+ - "*"
+
+ # -------------------------------------------------------
+ # 3. (Optional) Uncomment for Python projects
+ # -------------------------------------------------------
+ # - package-ecosystem: "pip"
+ # directory: "/"
+ # schedule:
+ # interval: "weekly"
+ # groups:
+ # python-deps:
+ # patterns:
+ # - "*"
\ No newline at end of file
From 0e6338fd680a14eb95da884fcf87afcc505dd096 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 19 Dec 2025 23:41:16 +0000
Subject: [PATCH 07/28] Bump the actions group with 3 updates
Bumps the actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [actions/setup-node](https://github.com/actions/setup-node) and [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact).
Updates `actions/checkout` from 4 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)
Updates `actions/setup-node` from 4 to 6
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)
Updates `actions/upload-pages-artifact` from 3 to 4
- [Release notes](https://github.com/actions/upload-pages-artifact/releases)
- [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v4)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-version: '6'
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: actions
- dependency-name: actions/setup-node
dependency-version: '6'
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: actions
- dependency-name: actions/upload-pages-artifact
dependency-version: '4'
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: actions
...
Signed-off-by: dependabot[bot]
---
.github/workflows/deploy-docmd.yml | 6 +++---
.github/workflows/npm-publish.yml | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/deploy-docmd.yml b/.github/workflows/deploy-docmd.yml
index 1650e01..7202983 100644
--- a/.github/workflows/deploy-docmd.yml
+++ b/.github/workflows/deploy-docmd.yml
@@ -18,10 +18,10 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout ๐๏ธ
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Set up Node.js โ๏ธ
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
@@ -36,7 +36,7 @@ jobs:
uses: actions/configure-pages@v5
- name: Upload artifact
- uses: actions/upload-pages-artifact@v3
+ uses: actions/upload-pages-artifact@v4
with:
path: ./site
diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
index ce0791e..f446aa2 100644
--- a/.github/workflows/npm-publish.yml
+++ b/.github/workflows/npm-publish.yml
@@ -13,10 +13,10 @@ jobs:
packages: write
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Set up Node.js v22
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
From 936645bba6e94c7cc7b7aec8ca77e6c777d8f92a Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 20 Dec 2025 05:21:46 +0530
Subject: [PATCH 08/28] Update npm-publish.yml
---
.github/workflows/npm-publish.yml | 31 ++++++++++++++++++-------------
1 file changed, 18 insertions(+), 13 deletions(-)
diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
index ce0791e..94de9db 100644
--- a/.github/workflows/npm-publish.yml
+++ b/.github/workflows/npm-publish.yml
@@ -5,35 +5,40 @@ on:
types: [published]
workflow_dispatch:
+permissions:
+ contents: read
+ id-token: write
+
jobs:
publish:
runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
+
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- - name: Set up Node.js v22
- uses: actions/setup-node@v4
+ - name: Set up Node.js
+ uses: actions/setup-node@v6
with:
node-version: '22'
- cache: 'npm'
+ registry-url: 'https://registry.npmjs.org'
+
+ - name: Ensure recent npm
+ run: npm install -g npm@latest
- name: Install dependencies
run: npm ci
+ - name: Run tests (optional)
+ run: npm test --if-present
+
# ------------------------------------------
# --- Publish to NPM Registry (npmjs.com) ---
# ------------------------------------------
- - name: Configure NPM for public registry
- run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
-
- - name: Publish to NPM Registry
- run: npm publish --access=public
+ - name: Publish to npm
+ run: npm publish --access public
# env:
- # NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ # NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# ----------------------------------------
# --- Publish to GitHub Packages (GPR) ---
From 319db0a7d0f3ac8088976dbdc2687986dd566291 Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 20 Dec 2025 05:31:15 +0530
Subject: [PATCH 09/28] Update npm-publish.yml
---
.github/workflows/npm-publish.yml | 20 ++------------------
1 file changed, 2 insertions(+), 18 deletions(-)
diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
index 94de9db..c07ba56 100644
--- a/.github/workflows/npm-publish.yml
+++ b/.github/workflows/npm-publish.yml
@@ -1,4 +1,4 @@
-name: Publish Package to NPM and GitHub Packages
+name: Publish package to npm
on:
release:
@@ -32,23 +32,7 @@ jobs:
- name: Run tests (optional)
run: npm test --if-present
- # ------------------------------------------
- # --- Publish to NPM Registry (npmjs.com) ---
- # ------------------------------------------
- name: Publish to npm
run: npm publish --access public
# env:
- # NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
-
- # ----------------------------------------
- # --- Publish to GitHub Packages (GPR) ---
- # ----------------------------------------
- - name: Configure NPM for GitHub Packages registry
- run: |
- echo "@mgks:registry=https://npm.pkg.github.com" > ~/.npmrc
- echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.npmrc
-
- - name: Publish to GitHub Packages
- run: npm publish
- env:
- NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
+ # NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
\ No newline at end of file
From 3f679a8d5da7d5ac3542d50a0992a44145d32922 Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 20 Dec 2025 05:43:27 +0530
Subject: [PATCH 10/28] Update .npmignore
---
.npmignore | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.npmignore b/.npmignore
index e4349c7..7f0b8cb 100644
--- a/.npmignore
+++ b/.npmignore
@@ -6,6 +6,7 @@
genctx.config.json
genctx.context.md
docs/
-assets/
+assets/css/welcome.css
+assets/images/preview-*
preview.gif
docmd-preview.png
\ No newline at end of file
From 32bc85bda527752c0e01451d97b67e0824ab5049 Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 20 Dec 2025 05:45:33 +0530
Subject: [PATCH 11/28] 0.3.4
---
package-lock.json | 7 +++++--
package.json | 2 +-
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index bc98a58..3c839d6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@mgks/docmd",
- "version": "0.3.3",
+ "version": "0.3.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@mgks/docmd",
- "version": "0.3.3",
+ "version": "0.3.4",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -43,6 +43,9 @@
},
"engines": {
"node": ">=18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mgks"
}
},
"node_modules/@antfu/install-pkg": {
diff --git a/package.json b/package.json
index 2718767..110cf23 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@mgks/docmd",
- "version": "0.3.3",
+ "version": "0.3.4",
"description": "Generate beautiful, lightweight static documentation sites directly from your Markdown files. Zero clutter, just content.",
"main": "src/index.js",
"exports": {
From 1f95f3e527f21095cb4b4a29d48268cd081d05ab Mon Sep 17 00:00:00 2001
From: Toni D
Date: Thu, 25 Dec 2025 22:41:57 +0700
Subject: [PATCH 12/28] Add WebAssembly and browser support
- Refactor core logic to be file-system agnostic
- Add bundling scripts and shims for browser compatibility
- Add Javy build support for WASM compilation
- Add browser-based demo
---
package-lock.json | 79 +++++++++++++++++++++---
package.json | 8 ++-
scripts/build-wasm.js | 121 +++++++++++++++++++++++++++++++++++++
scripts/test-wasm-build.js | 42 +++++++++++++
src/core/file-processor.js | 9 ++-
src/core/html-generator.js | 29 +++++----
src/wasm/core.js | 102 +++++++++++++++++++++++++++++++
src/wasm/shims.js | 2 +
src/wasm/templates.js | 9 +++
9 files changed, 379 insertions(+), 22 deletions(-)
create mode 100644 scripts/build-wasm.js
create mode 100644 scripts/test-wasm-build.js
create mode 100644 src/wasm/core.js
create mode 100644 src/wasm/shims.js
create mode 100644 src/wasm/templates.js
diff --git a/package-lock.json b/package-lock.json
index 3c839d6..47f0456 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
+ "buffer": "^6.0.3",
"chalk": "^4.1.2",
"chokidar": "^4.0.3",
"clean-css": "^5.3.3",
@@ -1053,7 +1054,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1121,6 +1121,26 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/body-parser": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
@@ -1155,6 +1175,30 @@
"concat-map": "0.0.1"
}
},
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -1224,7 +1268,6 @@
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
@@ -1380,7 +1423,6 @@
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10"
}
@@ -1802,7 +1844,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
- "peer": true,
"engines": {
"node": ">=12"
}
@@ -2003,6 +2044,7 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
+ "peer": true,
"engines": {
"node": ">=0.12"
},
@@ -2106,7 +2148,6 @@
"integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -2777,6 +2818,26 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3053,6 +3114,7 @@
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"uc.micro": "^2.0.0"
}
@@ -3177,7 +3239,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/media-typer": {
"version": "1.1.0",
@@ -3541,6 +3604,7 @@
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6"
}
@@ -4009,7 +4073,8 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/ufo": {
"version": "1.6.1",
diff --git a/package.json b/package.json
index 110cf23..94f3303 100644
--- a/package.json
+++ b/package.json
@@ -18,9 +18,13 @@
"postinstall": "node ./bin/postinstall.js",
"lint": "eslint .",
"format": "prettier --write .",
- "test": "echo \"No tests specified currently\" && exit 0"
+ "test": "echo \"No tests specified currently\" && exit 0",
+ "build:wasm": "node scripts/build-wasm.js",
+ "compile:wasm": "javy build dist/docmd-wasm.js -o dist/docmd.wasm",
+ "demo:wasm": "npx serve dist"
},
"dependencies": {
+ "buffer": "^6.0.3",
"chalk": "^4.1.2",
"chokidar": "^4.0.3",
"clean-css": "^5.3.3",
@@ -77,4 +81,4 @@
"homepage": "https://github.com/mgks/docmd#readme",
"funding": "https://github.com/sponsors/mgks",
"license": "MIT"
-}
\ No newline at end of file
+}
diff --git a/scripts/build-wasm.js b/scripts/build-wasm.js
new file mode 100644
index 0000000..42b2186
--- /dev/null
+++ b/scripts/build-wasm.js
@@ -0,0 +1,121 @@
+const fs = require('fs');
+const path = require('path');
+const esbuild = require('esbuild');
+
+async function build() {
+ console.log('๐ฆ Building WASM/Browser core...');
+
+ // 1. Generate Templates Module
+ const templatesDir = path.join(__dirname, '../src/templates');
+ const files = fs.readdirSync(templatesDir);
+ const templates = {};
+
+ for (const file of files) {
+ if (file.endsWith('.ejs')) {
+ const content = fs.readFileSync(path.join(templatesDir, file), 'utf8');
+ templates[file] = content;
+ }
+ }
+
+ const templatesJsPath = path.join(__dirname, '../src/wasm/templates.js');
+ // Export and set global for the fs-shim
+ const templatesJsContent = `
+const templates = ${JSON.stringify(templates, null, 2)};
+if (typeof globalThis !== 'undefined') globalThis.__DOCMD_TEMPLATES__ = templates;
+module.exports = templates;
+`;
+ fs.writeFileSync(templatesJsPath, templatesJsContent);
+ console.log(`โ
Generated src/wasm/templates.js with ${Object.keys(templates).length} templates.`);
+
+ // 2. Bundle using esbuild
+ try {
+ await esbuild.build({
+ entryPoints: [path.join(__dirname, '../src/wasm/core.js')],
+ bundle: true,
+ outfile: path.join(__dirname, '../dist/docmd-wasm.js'),
+ platform: 'browser',
+ format: 'iife',
+ globalName: 'docmd',
+ define: {
+ 'process.env.NODE_ENV': '"production"'
+ },
+ banner: {
+ js: 'var process = { cwd: () => "/", env: { NODE_ENV: "production" } };',
+ },
+ inject: [path.join(__dirname, '../src/wasm/shims.js')],
+ plugins: [
+ {
+ name: 'node-deps-shim',
+ setup(build) {
+ // Redirect fs to custom shim
+ build.onResolve({ filter: /^fs(-extra)?$/ }, args => ({ path: args.path, namespace: 'fs-shim' }));
+ build.onLoad({ filter: /.*/, namespace: 'fs-shim' }, () => ({
+ contents: `
+ module.exports = {
+ existsSync: (p) => {
+ // Simple check in global templates
+ if (!globalThis.__DOCMD_TEMPLATES__) return false;
+ // Assume p might be absolute or relative, just take basename
+ // Also handle 'toc' -> 'toc.ejs'
+ let name = p.split(/[\\/]/).pop();
+ if (!name.endsWith('.ejs') && !globalThis.__DOCMD_TEMPLATES__[name]) {
+ name += '.ejs';
+ }
+ return !!globalThis.__DOCMD_TEMPLATES__[name];
+ },
+ readFileSync: (p, encoding) => {
+ if (!globalThis.__DOCMD_TEMPLATES__) return '';
+ let name = p.split(/[\\/]/).pop();
+ if (!name.endsWith('.ejs') && !globalThis.__DOCMD_TEMPLATES__[name]) {
+ name += '.ejs';
+ }
+ return globalThis.__DOCMD_TEMPLATES__[name] || '';
+ },
+ readFile: (p, enc, cb) => {
+ // Async version shim if needed
+ if (typeof enc === 'function') cb = enc;
+ const content = module.exports.readFileSync(p);
+ if (cb) cb(null, content);
+ return Promise.resolve(content);
+ },
+ statSync: () => ({ isFile: () => true, isDirectory: () => false }),
+ constants: { F_OK: 0, R_OK: 4 }
+ };
+ `,
+ loader: 'js'
+ }));
+
+ // Redirect path to simple shim
+ build.onResolve({ filter: /^path$/ }, args => ({ path: args.path, namespace: 'path-shim' }));
+ build.onLoad({ filter: /.*/, namespace: 'path-shim' }, () => ({
+ contents: `
+ module.exports = {
+ join: (...args) => args.join('/'),
+ resolve: (...args) => args.join('/'),
+ basename: (p) => p.split('/').pop(),
+ dirname: (p) => p.split('/').slice(0, -1).join('/') || '.',
+ relative: (from, to) => {
+ return to.replace(from, '').replace(new RegExp('^/'), '');
+ },
+ extname: (p) => {
+ const parts = p.split('.');
+ return parts.length > 1 ? '.' + parts.pop() : '';
+ },
+ isAbsolute: (p) => p.startsWith('/'),
+ sep: '/'
+ };
+ `,
+ loader: 'js'
+ }));
+ }
+ }
+ ]
+ });
+ console.log('โ
Bundled dist/docmd-wasm.js');
+ } catch (e) {
+ console.error('โ Build failed:', e);
+ process.exit(1);
+ }
+}
+
+build();
diff --git a/scripts/test-wasm-build.js b/scripts/test-wasm-build.js
new file mode 100644
index 0000000..2ff92df
--- /dev/null
+++ b/scripts/test-wasm-build.js
@@ -0,0 +1,42 @@
+const fs = require('fs');
+const path = require('path');
+const vm = require('vm');
+
+const bundlePath = path.join(__dirname, '../dist/docmd-wasm.js');
+const bundleCode = fs.readFileSync(bundlePath, 'utf8');
+
+const sandbox = {
+ console: console,
+ setTimeout: setTimeout,
+ clearTimeout: clearTimeout
+};
+
+vm.createContext(sandbox);
+
+console.log('๐งช Testing WASM/Browser bundle...');
+
+try {
+ vm.runInContext(bundleCode, sandbox);
+
+ if (!sandbox.docmd) {
+ throw new Error('docmd global not found in bundle');
+ }
+
+ const markdown = '# Hello WASM\nThis is a test.';
+ const config = { siteTitle: 'Wasm Test' };
+
+ console.log('Compiling markdown...');
+ const result = sandbox.docmd.compile(markdown, config);
+
+ if (result.includes('Hello WASM ') && result.includes('Wasm Test')) {
+ console.log('โ
Bundle works! Output contains expected HTML.');
+ } else {
+ console.error('โ Bundle produced unexpected output.');
+ console.log('Output snippet:', result.substring(0, 200));
+ process.exit(1);
+ }
+
+} catch (e) {
+ console.error('โ Test failed:', e);
+ process.exit(1);
+}
diff --git a/src/core/file-processor.js b/src/core/file-processor.js
index 39c22af..bf5b1b7 100644
--- a/src/core/file-processor.js
+++ b/src/core/file-processor.js
@@ -34,12 +34,16 @@ function formatPathForDisplay(absolutePath) {
async function processMarkdownFile(filePath, md, config) {
const rawContent = await fs.readFile(filePath, 'utf8');
+ return processMarkdownContent(rawContent, md, config, filePath);
+}
+
+function processMarkdownContent(rawContent, md, config, filePath = 'memory') {
let frontmatter, markdownContent;
try {
({ data: frontmatter, content: markdownContent } = matter(rawContent));
} catch (e) {
- console.error(`โ Error parsing frontmatter in ${formatPathForDisplay(filePath)}:`);
+ console.error(`โ Error parsing frontmatter in ${filePath === 'memory' ? 'content' : formatPathForDisplay(filePath)}:`);
console.error(` ${e.message}`);
return null;
}
@@ -70,7 +74,7 @@ async function processMarkdownFile(filePath, md, config) {
return { frontmatter, htmlContent, headings, searchData };
}
-
+
async function findMarkdownFiles(dir) {
let files = [];
const items = await fs.readdir(dir, { withFileTypes: true });
@@ -87,6 +91,7 @@ async function findMarkdownFiles(dir) {
module.exports = {
processMarkdownFile,
+ processMarkdownContent,
createMarkdownItInstance,
extractHeadingsFromHtml,
findMarkdownFiles
diff --git a/src/core/html-generator.js b/src/core/html-generator.js
index 057e512..01ea263 100644
--- a/src/core/html-generator.js
+++ b/src/core/html-generator.js
@@ -13,10 +13,12 @@ let mdInstance = null;
let themeInitScript = '';
(async () => {
- const themeInitPath = path.join(__dirname, '..', 'templates', 'partials', 'theme-init.js');
- if (await fs.pathExists(themeInitPath)) {
- const scriptContent = await fs.readFile(themeInitPath, 'utf8');
- themeInitScript = ``;
+ if (typeof __dirname !== 'undefined') {
+ const themeInitPath = path.join(__dirname, '..', 'templates', 'partials', 'theme-init.js');
+ if (await fs.pathExists(themeInitPath)) {
+ const scriptContent = await fs.readFile(themeInitPath, 'utf8');
+ themeInitScript = ``;
+ }
}
})();
@@ -96,8 +98,6 @@ async function generateHtmlPage(templateData) {
if (!await fs.pathExists(layoutTemplatePath)) {
throw new Error(`Template not found: ${layoutTemplatePath}`);
}
- const layoutTemplate = await fs.readFile(layoutTemplatePath, 'utf8');
-
const isActivePage = currentPagePath && content && content.trim().length > 0;
// Calculate Edit Link
@@ -154,13 +154,20 @@ async function generateHtmlPage(templateData) {
...pluginOutputs,
};
+ const layoutTemplate = await fs.readFile(layoutTemplatePath, 'utf8');
+
+ return renderHtmlPage(layoutTemplate, ejsData, layoutTemplatePath);
+}
+
+function renderHtmlPage(templateContent, ejsData, filename = 'template.ejs', options = {}) {
try {
- return ejs.render(layoutTemplate, ejsData, {
- filename: layoutTemplatePath
+ return ejs.render(templateContent, ejsData, {
+ filename: filename,
+ ...options
});
} catch (e) {
- console.error(`โ Error rendering EJS template for ${outputPath}: ${e.message}`);
- console.error("EJS Data:", JSON.stringify(ejsData, null, 2).substring(0, 1000) + "...");
+ console.error(`โ Error rendering EJS template: ${e.message}`);
+ console.error("EJS Data snippet:", JSON.stringify(ejsData, null, 2).substring(0, 500));
throw e;
}
}
@@ -185,4 +192,4 @@ async function generateNavigationHtml(navItems, currentPagePath, relativePathToR
});
}
-module.exports = { generateHtmlPage, generateNavigationHtml };
\ No newline at end of file
+module.exports = { generateHtmlPage, generateNavigationHtml, renderHtmlPage };
\ No newline at end of file
diff --git a/src/wasm/core.js b/src/wasm/core.js
new file mode 100644
index 0000000..198033d
--- /dev/null
+++ b/src/wasm/core.js
@@ -0,0 +1,102 @@
+const { processMarkdownContent, createMarkdownItInstance } = require('../core/file-processor');
+const { renderHtmlPage } = require('../core/html-generator');
+const templates = require('./templates');
+
+/**
+ * Compile markdown to HTML without file system access.
+ * @param {string} markdown - The raw markdown content.
+ * @param {object} config - The docmd configuration object.
+ * @param {object} options - Options { currentPath: string }
+ */
+function compile(markdown, config, options = {}) {
+ const md = createMarkdownItInstance(config);
+ const result = processMarkdownContent(markdown, md, config, options.currentPath || 'memory');
+
+ if (!result) {
+ throw new Error('Failed to process markdown');
+ }
+
+ const { frontmatter, htmlContent, headings } = result;
+
+ // Prepare data for template
+ // Note: We mocking some fields that depend on file system scan (like navigation)
+ // unless passed in options.
+
+ const pageData = {
+ content: htmlContent,
+ frontmatter,
+ headings,
+ // Defaults for missing data in pure compile mode
+ siteTitle: config.siteTitle || 'Docmd Site',
+ pageTitle: frontmatter.title,
+ description: frontmatter.description,
+ defaultMode: config.theme?.defaultMode || 'light',
+ editUrl: null, // Mocked for now
+ editLinkText: 'Edit this page',
+ navigationHtml: options.navigationHtml || '',
+ relativePathToRoot: options.relativePathToRoot || './',
+ outputPath: options.outputPath || 'index.html',
+ currentPagePath: options.currentPath || '/index',
+ prevPage: options.prevPage || null,
+ nextPage: options.nextPage || null,
+ config: config,
+ // Mock plugin outputs for now as they might use FS
+ metaTagsHtml: '',
+ faviconLinkHtml: '',
+ themeCssLinkHtml: '',
+ pluginStylesHtml: '',
+ pluginHeadScriptsHtml: '',
+ pluginBodyScriptsHtml: '',
+ themeInitScript: '', // This is usually read from file, we might need to inline it too
+ logo: config.logo,
+ sidebarConfig: config.sidebar || {},
+ theme: config.theme || {},
+ customCssFiles: [],
+ customJsFiles: [],
+ sponsor: config.sponsor,
+ footer: config.footer,
+ footerHtml: '', // TODO: Render footer markdown
+ renderIcon: (name) => ``, // Simple mock
+ isActivePage: true
+ };
+
+ // Select template
+ let templateName = 'layout.ejs';
+ if (frontmatter.noStyle === true) {
+ templateName = 'no-style.ejs';
+ }
+
+ const templateContent = templates[templateName];
+ if (!templateContent) {
+ throw new Error(`Template ${templateName} not found in bundled templates.`);
+ }
+
+ const ejsOptions = {
+ includer: (originalPath, parsedPath) => {
+ console.log('Includer called for:', originalPath);
+ // originalPath is like 'toc'
+ // We need to match it to a key in templates object (e.g., 'toc.ejs')
+ let potentialName = originalPath;
+ if (!potentialName.endsWith('.ejs')) {
+ potentialName += '.ejs';
+ }
+ // Check for direct match or path match
+ // The templates object keys are like 'toc.ejs', 'layout.ejs'
+ // If includes use paths like '../partials/foo', we might need normalization.
+ // But standard docmd templates seem flat or we'll flat them in build.
+
+ // In build-wasm.js, we only included root templates. 'toc.ejs' is in root of templates dir.
+ // If there are partials in subdirs, we need to handle that.
+ // Checking file listing: templates/toc.ejs exists.
+
+ if (templates[potentialName]) {
+ return { template: templates[potentialName] };
+ }
+ return null;
+ }
+ };
+
+ return renderHtmlPage(templateContent, pageData, templateName, ejsOptions);
+}
+
+module.exports = { compile, templates };
diff --git a/src/wasm/shims.js b/src/wasm/shims.js
new file mode 100644
index 0000000..febfbe3
--- /dev/null
+++ b/src/wasm/shims.js
@@ -0,0 +1,2 @@
+import { Buffer } from 'buffer';
+globalThis.Buffer = Buffer;
diff --git a/src/wasm/templates.js b/src/wasm/templates.js
new file mode 100644
index 0000000..71d753e
--- /dev/null
+++ b/src/wasm/templates.js
@@ -0,0 +1,9 @@
+
+const templates = {
+ "layout.ejs": "\n\n\n \n \n\n <%- metaTagsHtml || '' %> <%# SEO Plugin Meta Tags %>\n\n <%= pageTitle %> : <%= siteTitle %> \n <% if (description && !(metaTagsHtml && metaTagsHtml.includes('name=\"description\"'))) { %>\n \">\n <% } %>\n\n <%- faviconLinkHtml || '' %> <%# Favicon %>\n\n \n \n\n \n <% if (config.theme?.codeHighlight !== false) { %>\n assets/css/docmd-highlight-<%= defaultMode === 'dark' ? 'dark' : 'light' %>.css\" data-base-href=\"<%= relativePathToRoot %>assets/css/\">\n <% } %>\n\n \n assets/css/docmd-main.css\">\n \n <%- themeCssLinkHtml || '' %> <%# For theme.name specific CSS %>\n\n <% (customCssFiles || []).forEach(cssFile => { %>\n <%- cssFile.startsWith('/') ? cssFile.substring(1) : cssFile %>\">\n <% }); %>\n\n <%- pluginStylesHtml || '' %> <%# Plugin specific CSS %>\n\n \n <%- themeInitScript %>\n\n <%- pluginHeadScriptsHtml || '' %> <%# Plugin specific head scripts %>\n\n\"\n data-default-collapsed=\"<%= sidebarConfig.defaultCollapsed %>\"\n data-copy-code-enabled=\"<%= config.copyCode === true %>\">\n \n \n \n
\n \n
\n <%- content %>\n \n <% if (config.pageNavigation && (prevPage || nextPage)) { %>\n
\n <% } %>\n
\n \n \n \n \n \n
\n\n \n \n \n\n \n
\n\n <% if (config.search !== false) { %>\n \n \n <% } %>\n\n \n\n \n\n <% if (config.search !== false) { %>\n \n \n \n <% } %>\n\n \n \n \n\n <% (customJsFiles || []).forEach(jsFile => { %>\n \n <% }); %>\n\n <%- pluginBodyScriptsHtml || '' %>\n \n <% if (sponsor && sponsor.enabled) { %>\n \n <% } %>\n\n",
+ "navigation.ejs": "<%# navigation.ejs - Renders the sidebar navigation %>\n",
+ "no-style.ejs": "\n\n\n \n \n\n <% if (frontmatter.components?.meta !== false) { %>\n <%- metaTagsHtml || '' %>\n <%= pageTitle %><% if (frontmatter.components?.siteTitle !== false) { %> | <%= siteTitle %><% } %> \n <% if (description && !(metaTagsHtml && metaTagsHtml.includes('name=\"description\"'))) { %>\n \">\n <% } %>\n <% } %>\n\n <% if (frontmatter.components?.favicon !== false) { %>\n <%- faviconLinkHtml || '' %>\n <% } %>\n\n <% if (frontmatter.components?.themeMode !== false) { %>\n \n <% } %>\n\n <% if (frontmatter.components?.css !== false) { %>\n assets/css/docmd-main.css\">\n <% if (frontmatter.components?.highlight !== false) { %>\n assets/css/docmd-highlight-<%= defaultMode === 'dark' ? 'dark' : 'light' %>.css\" id=\"highlight-theme\">\n <% } %>\n <% } %>\n\n <% if (frontmatter.components?.themeMode !== false) { %>\n <%- themeInitScript %>\n <% } %>\n\n <% if (frontmatter.components?.theme !== false) { %>\n <%- themeCssLinkHtml || '' %>\n <% } %>\n\n <% if (frontmatter.components?.customCss !== false && customCssFiles && customCssFiles.length > 0) { %>\n <% customCssFiles.forEach(cssFile => { %>\n <%- cssFile.startsWith('/') ? cssFile.substring(1) : cssFile %>\">\n <% }); %>\n <% } %>\n\n <% if (frontmatter.components?.pluginStyles !== false) { %>\n <%- pluginStylesHtml || '' %>\n <% } %>\n\n <% if (frontmatter.components?.pluginHeadScripts !== false) { %>\n <%- pluginHeadScriptsHtml || '' %>\n <% } %>\n \n <% if (frontmatter.customHead) { %>\n <%- frontmatter.customHead %>\n <% } %>\n\n data-theme=\"<%= defaultMode %>\"<%\n }\n %><%\n if (frontmatter.bodyClass) {\n %> class=\"<%= frontmatter.bodyClass %>\"<%\n }\n %> data-copy-code-enabled=\"<%= config.copyCode === true %>\">\n <% if (frontmatter.components?.layout === true || frontmatter.components?.layout === 'full') { %>\n \n <% if (frontmatter.components?.header !== false) { %>\n \n <% } %>\n
\n \n
\n <%- content %>\n
\n
\n \n <% if (frontmatter.components?.footer !== false) { %>\n \n <% } %>\n
\n <% } else if (frontmatter.components?.sidebar === true) { %>\n \n \n <% if (frontmatter.components?.header !== false) { %>\n \n <% } %>\n
\n \n
\n <%- content %>\n
\n <% if (frontmatter.components?.toc !== false && headings && headings.length > 0) { %>\n \n <% } %>\n
\n \n <% if (frontmatter.components?.footer !== false) { %>\n \n <% } %>\n
\n <% } else { %>\n <%- content %>\n <% } %>\n\n <% if (frontmatter.components?.scripts === true) { %>\n <% if (frontmatter.components?.mainScripts === true) { %>\n \n <% } %>\n \n <% if (frontmatter.components?.lightbox === true && frontmatter.components?.mainScripts === true) { %>\n \n <% } %>\n \n <% if (frontmatter.components?.customJs === true && customJsFiles && customJsFiles.length > 0) { %>\n <% customJsFiles.forEach(jsFile => { %>\n \n <% }); %>\n <% } %>\n \n <% if (frontmatter.components?.pluginBodyScripts === true) { %>\n <%- pluginBodyScriptsHtml || '' %>\n <% } %>\n <% } %>\n \n <% if (frontmatter.customScripts) { %>\n <%- frontmatter.customScripts %>\n <% } %>\n\n ",
+ "toc.ejs": "<%# src/templates/toc.ejs %>\n<% \n// Helper function to decode HTML entities\nfunction decodeHtmlEntities(html) {\n return html\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/ /g, ' ');\n}\n\n// Use the isActivePage flag if provided, otherwise fall back to checking navigationHtml\nconst shouldShowToc = typeof isActivePage !== 'undefined' ? isActivePage : \n (typeof navigationHtml !== 'undefined' && navigationHtml && navigationHtml.includes('class=\"active\"'));\n\nif (shouldShowToc && !frontmatter?.toc || frontmatter?.toc !== 'false') {\n // If direct headings aren't available, we'll try to extract them from the content\n let tocHeadings = [];\n if (headings && headings.length > 0) {\n // Use provided headings if available\n tocHeadings = headings.filter(h => h.level >= 2 && h.level <= 4);\n } else if (content) {\n // Basic regex to extract headings from HTML content\n const headingRegex = /]*?(?:id=\"([^\"]*)\")?[^>]*?>([\\s\\S]*?)<\\/h\\1>/g;\n let match;\n let contentStr = content.toString();\n \n while ((match = headingRegex.exec(contentStr)) !== null) {\n const level = parseInt(match[1], 10);\n // Use ID if available, or generate one from the text\n let id = match[2];\n // Remove any HTML tags inside the heading text\n const textWithTags = match[3].replace(/<\\/?[^>]+(>|$)/g, '');\n // Decode HTML entities\n const text = decodeHtmlEntities(textWithTags);\n \n if (!id) {\n // Generate an ID from the heading text if none exists\n id = text\n .toLowerCase()\n .replace(/\\s+/g, '-')\n .replace(/[^\\w-]/g, '')\n .replace(/--+/g, '-')\n .replace(/^-+|-+$/g, '');\n }\n \n tocHeadings.push({ id, level, text });\n }\n }\n\n // Only show TOC if there are enough headings\n if (tocHeadings.length > 1) { \n%>\n \n
On This Page<%- renderIcon(\"chevrons-down-up\") %> \n
\n
\n<% } \n} %> "
+};
+if (typeof globalThis !== 'undefined') globalThis.__DOCMD_TEMPLATES__ = templates;
+module.exports = templates;
From d018467877f982b870f3f049c150fe727c9d044a Mon Sep 17 00:00:00 2001
From: Toni D
Date: Thu, 25 Dec 2025 23:08:00 +0700
Subject: [PATCH 13/28] Move wasm demo to src and update build script
---
scripts/build-wasm.js | 9 +++++
src/wasm/wasm-demo.html | 88 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 97 insertions(+)
create mode 100644 src/wasm/wasm-demo.html
diff --git a/scripts/build-wasm.js b/scripts/build-wasm.js
index 42b2186..3e1519a 100644
--- a/scripts/build-wasm.js
+++ b/scripts/build-wasm.js
@@ -112,6 +112,15 @@ module.exports = templates;
]
});
console.log('โ
Bundled dist/docmd-wasm.js');
+
+ // 3. Copy Demo HTML
+ const demoSrc = path.join(__dirname, '../src/wasm/wasm-demo.html');
+ const demoDest = path.join(__dirname, '../dist/wasm-demo.html');
+ if (fs.existsSync(demoSrc)) {
+ fs.copyFileSync(demoSrc, demoDest);
+ console.log('โ
Copied wasm-demo.html to dist/');
+ }
+
} catch (e) {
console.error('โ Build failed:', e);
process.exit(1);
diff --git a/src/wasm/wasm-demo.html b/src/wasm/wasm-demo.html
new file mode 100644
index 0000000..2ac4db8
--- /dev/null
+++ b/src/wasm/wasm-demo.html
@@ -0,0 +1,88 @@
+
+
+
+
+
+ Docmd WASM Demo
+
+
+
+
+
+ Docmd Browser/WASM Demo
+ Render
+
+
+
+
+
+
+
+
From 3e78b05bd18d9eac83cd04d4fcc6b92e7afdacfb Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Fri, 2 Jan 2026 17:34:37 +0530
Subject: [PATCH 14/28] Added live preview build
Introduces a new live preview build system with scripts/build-live.js, src/live/core.js, and related assets for browser-based markdown rendering. Migrate the previous WASM build to live preview (scripts/build-wasm.js, src/wasm/core.js, etc.), updates package.json scripts and dependencies, and refactors test and template handling to support the new live mode.
---
package-lock.json | 54 ++++--
package.json | 10 +-
scripts/build-live.js | 157 ++++++++++++++++
scripts/build-wasm.js | 130 --------------
scripts/{test-wasm-build.js => test-live.js} | 26 ++-
src/core/file-processor.js | 1 +
src/core/html-generator.js | 120 +++----------
src/live/core.js | 63 +++++++
src/live/index.html | 178 +++++++++++++++++++
src/live/live.css | 136 ++++++++++++++
src/live/shims.js | 1 +
src/{wasm => live}/templates.js | 0
src/wasm/core.js | 102 -----------
src/wasm/shims.js | 2 -
src/wasm/wasm-demo.html | 88 ---------
15 files changed, 626 insertions(+), 442 deletions(-)
create mode 100644 scripts/build-live.js
delete mode 100644 scripts/build-wasm.js
rename scripts/{test-wasm-build.js => test-live.js} (57%)
create mode 100644 src/live/core.js
create mode 100644 src/live/index.html
create mode 100644 src/live/live.css
create mode 100644 src/live/shims.js
rename src/{wasm => live}/templates.js (100%)
delete mode 100644 src/wasm/core.js
delete mode 100644 src/wasm/shims.js
delete mode 100644 src/wasm/wasm-demo.html
diff --git a/package-lock.json b/package-lock.json
index 47f0456..e4cd7d4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,13 +10,11 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
- "buffer": "^6.0.3",
"chalk": "^4.1.2",
"chokidar": "^4.0.3",
"clean-css": "^5.3.3",
"commander": "^14.0.0",
"ejs": "^3.1.9",
- "esbuild": "^0.27.0",
"express": "^5.1.0",
"fs-extra": "^11.2.0",
"gray-matter": "^4.0.3",
@@ -37,6 +35,8 @@
"docmd": "bin/docmd.js"
},
"devDependencies": {
+ "buffer": "^6.0.3",
+ "esbuild": "^0.27.0",
"eslint": "^9.32.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-node": "^11.1.0",
@@ -114,6 +114,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -130,6 +131,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -146,6 +148,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -162,6 +165,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -178,6 +182,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -194,6 +199,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -210,6 +216,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -226,6 +233,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -242,6 +250,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -258,6 +267,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -274,6 +284,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -290,6 +301,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -306,6 +318,7 @@
"cpu": [
"mips64el"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -322,6 +335,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -338,6 +352,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -354,6 +369,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -370,6 +386,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -386,6 +403,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -402,6 +420,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -418,6 +437,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -434,6 +454,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -450,6 +471,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -466,6 +488,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -482,6 +505,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -498,6 +522,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -514,6 +539,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1054,6 +1080,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1125,6 +1152,7 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -1179,6 +1207,7 @@
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -1268,6 +1297,7 @@
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
@@ -1423,6 +1453,7 @@
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10"
}
@@ -1844,6 +1875,7 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
+ "peer": true,
"engines": {
"node": ">=12"
}
@@ -2044,7 +2076,6 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
- "peer": true,
"engines": {
"node": ">=0.12"
},
@@ -2086,6 +2117,7 @@
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz",
"integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -2148,6 +2180,7 @@
"integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -2822,6 +2855,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -3114,7 +3148,6 @@
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"uc.micro": "^2.0.0"
}
@@ -3239,8 +3272,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/media-typer": {
"version": "1.1.0",
@@ -3604,15 +3636,14 @@
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/qs": {
- "version": "6.14.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
- "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "version": "6.14.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
+ "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
@@ -4073,8 +4104,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/ufo": {
"version": "1.6.1",
diff --git a/package.json b/package.json
index 94f3303..7a442eb 100644
--- a/package.json
+++ b/package.json
@@ -15,22 +15,18 @@
"scripts": {
"start": "node ./bin/docmd.js dev",
"build": "node ./bin/docmd.js build",
+ "live": "node scripts/build-live.js && npx serve dist",
"postinstall": "node ./bin/postinstall.js",
"lint": "eslint .",
"format": "prettier --write .",
- "test": "echo \"No tests specified currently\" && exit 0",
- "build:wasm": "node scripts/build-wasm.js",
- "compile:wasm": "javy build dist/docmd-wasm.js -o dist/docmd.wasm",
- "demo:wasm": "npx serve dist"
+ "test": "echo \"No tests specified currently\" && exit 0"
},
"dependencies": {
- "buffer": "^6.0.3",
"chalk": "^4.1.2",
"chokidar": "^4.0.3",
"clean-css": "^5.3.3",
"commander": "^14.0.0",
"ejs": "^3.1.9",
- "esbuild": "^0.27.0",
"express": "^5.1.0",
"fs-extra": "^11.2.0",
"gray-matter": "^4.0.3",
@@ -48,6 +44,8 @@
"ws": "^8.17.0"
},
"devDependencies": {
+ "buffer": "^6.0.3",
+ "esbuild": "^0.27.0",
"eslint": "^9.32.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-node": "^11.1.0",
diff --git a/scripts/build-live.js b/scripts/build-live.js
new file mode 100644
index 0000000..c5a43e0
--- /dev/null
+++ b/scripts/build-live.js
@@ -0,0 +1,157 @@
+const fs = require('fs-extra');
+const path = require('path');
+const esbuild = require('esbuild');
+
+async function build() {
+ console.log('๐ฆ Building @docmd/live core...');
+
+ const SRC_DIR = path.join(__dirname, '../src');
+ const LIVE_SRC_DIR = path.join(SRC_DIR, 'live');
+ const DIST_DIR = path.join(__dirname, '../dist');
+
+ // Ensure dist exists and is empty
+ fs.emptyDirSync(DIST_DIR);
+
+ // 1. Read Templates
+ const templatesDir = path.join(SRC_DIR, 'templates');
+ const files = fs.readdirSync(templatesDir);
+ const templates = {};
+
+ for (const file of files) {
+ if (file.endsWith('.ejs')) {
+ templates[file] = fs.readFileSync(path.join(templatesDir, file), 'utf8');
+ }
+ }
+
+ // 2. Generate templates.js
+ const templatesJsPath = path.join(LIVE_SRC_DIR, 'templates.js');
+ const templatesContent = `
+const templates = ${JSON.stringify(templates, null, 2)};
+if (typeof globalThis !== 'undefined') globalThis.__DOCMD_TEMPLATES__ = templates;
+module.exports = templates;
+`;
+ fs.writeFileSync(templatesJsPath, templatesContent);
+
+ // 3. Generate Shim for Buffer
+ const shimPath = path.join(LIVE_SRC_DIR, 'shims.js');
+ fs.writeFileSync(shimPath, `import { Buffer } from 'buffer'; globalThis.Buffer = Buffer;`);
+
+ // 4. Bundle JS
+ try {
+ await esbuild.build({
+ entryPoints: [path.join(LIVE_SRC_DIR, 'core.js')],
+ bundle: true,
+ outfile: path.join(DIST_DIR, 'docmd-live.js'),
+ platform: 'browser',
+ format: 'iife',
+ globalName: 'docmd',
+ minify: true,
+ define: { 'process.env.NODE_ENV': '"production"' },
+ inject: [shimPath],
+ plugins: [
+ {
+ name: 'node-deps-shim',
+ setup(build) {
+ build.onResolve({ filter: /^path$/ }, args => ({ path: args.path, namespace: 'path-shim' }));
+ build.onLoad({ filter: /.*/, namespace: 'path-shim' }, () => ({
+ contents: `
+ module.exports = {
+ join: (...args) => args.filter(Boolean).join('/'),
+ resolve: (...args) => '/' + args.filter(Boolean).join('/'),
+ basename: (p) => p ? p.split(/[\\\\/]/).pop() : '',
+ dirname: (p) => p ? p.split(/[\\\\/]/).slice(0, -1).join('/') || '.' : '.',
+ extname: (p) => {
+ if (!p) return '';
+ const parts = p.split('.');
+ return parts.length > 1 ? '.' + parts.pop() : '';
+ },
+ isAbsolute: (p) => p.startsWith('/'),
+ normalize: (p) => p,
+ sep: '/'
+ };
+ `,
+ loader: 'js'
+ }));
+
+ build.onResolve({ filter: /^(fs|fs-extra)$/ }, args => ({ path: args.path, namespace: 'fs-shim' }));
+ build.onLoad({ filter: /.*/, namespace: 'fs-shim' }, () => ({
+ contents: `
+ module.exports = {
+ existsSync: (p) => {
+ if (!globalThis.__DOCMD_TEMPLATES__) return false;
+ let name = p.split(/[\\\\/]/).pop();
+ if (globalThis.__DOCMD_TEMPLATES__[name]) return true;
+ if (!name.endsWith('.ejs') && globalThis.__DOCMD_TEMPLATES__[name + '.ejs']) return true;
+ return false;
+ },
+ readFileSync: (p) => {
+ if (!globalThis.__DOCMD_TEMPLATES__) return '';
+ let name = p.split(/[\\\\/]/).pop();
+ if (globalThis.__DOCMD_TEMPLATES__[name]) return globalThis.__DOCMD_TEMPLATES__[name];
+ if (!name.endsWith('.ejs')) name += '.ejs';
+ return globalThis.__DOCMD_TEMPLATES__[name] || '';
+ },
+ statSync: () => ({ isFile: () => true, isDirectory: () => false }),
+ constants: { F_OK: 0, R_OK: 4 }
+ };
+ `,
+ loader: 'js'
+ }));
+ }
+ }
+ ]
+ });
+ console.log('โ
Bundled JS to dist/docmd-live.js');
+
+ // 5. Copy Assets
+ console.log('๐ Copying assets...');
+ await fs.copy(path.join(SRC_DIR, 'assets'), path.join(DIST_DIR, 'assets'));
+
+ // 5.5 Bundle Third-Party Libraries (The FIX)
+ // We need to manually copy these from node_modules because they aren't in src/assets
+ const copyLibrary = async (packageName, fileToBundle, destFileName) => {
+ try {
+ // Try to resolve the package path
+ let srcPath;
+ try {
+ srcPath = require.resolve(`${packageName}/${fileToBundle}`);
+ } catch (e) {
+ // Fallback search if require.resolve fails
+ const mainPath = require.resolve(packageName);
+ let currentDir = path.dirname(mainPath);
+ for (let i = 0; i < 5; i++) {
+ if (fs.existsSync(path.join(currentDir, 'package.json'))) break;
+ currentDir = path.dirname(currentDir);
+ }
+ srcPath = path.join(currentDir, fileToBundle);
+ }
+
+ if (srcPath && fs.existsSync(srcPath)) {
+ const destPath = path.join(DIST_DIR, 'assets/js', destFileName);
+ await fs.ensureDir(path.dirname(destPath));
+ await fs.copy(srcPath, destPath);
+ console.log(` โโ Copied ${packageName} -> assets/js/${destFileName}`);
+ } else {
+ console.warn(`โ ๏ธ Could not locate ${fileToBundle} in ${packageName}`);
+ }
+ } catch (e) {
+ console.warn(`โ ๏ธ Failed to bundle ${packageName}: ${e.message}`);
+ }
+ };
+
+ await copyLibrary('minisearch', 'dist/umd/index.js', 'minisearch.js');
+ await copyLibrary('mermaid', 'dist/mermaid.min.js', 'mermaid.min.js');
+ console.log('โ
Assets & Libraries copied.');
+
+ // 6. Copy Demo HTML
+ await fs.copy(path.join(LIVE_SRC_DIR, 'index.html'), path.join(DIST_DIR, 'index.html'));
+ await fs.copy(path.join(LIVE_SRC_DIR, 'live.css'), path.join(DIST_DIR, 'live.css'));
+ console.log('โ
Demo HTML copied.');
+
+ } catch (e) {
+ console.error('โ Build failed:', e);
+ process.exit(1);
+ }
+}
+
+build();
\ No newline at end of file
diff --git a/scripts/build-wasm.js b/scripts/build-wasm.js
deleted file mode 100644
index 3e1519a..0000000
--- a/scripts/build-wasm.js
+++ /dev/null
@@ -1,130 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-const esbuild = require('esbuild');
-
-async function build() {
- console.log('๐ฆ Building WASM/Browser core...');
-
- // 1. Generate Templates Module
- const templatesDir = path.join(__dirname, '../src/templates');
- const files = fs.readdirSync(templatesDir);
- const templates = {};
-
- for (const file of files) {
- if (file.endsWith('.ejs')) {
- const content = fs.readFileSync(path.join(templatesDir, file), 'utf8');
- templates[file] = content;
- }
- }
-
- const templatesJsPath = path.join(__dirname, '../src/wasm/templates.js');
- // Export and set global for the fs-shim
- const templatesJsContent = `
-const templates = ${JSON.stringify(templates, null, 2)};
-if (typeof globalThis !== 'undefined') globalThis.__DOCMD_TEMPLATES__ = templates;
-module.exports = templates;
-`;
- fs.writeFileSync(templatesJsPath, templatesJsContent);
- console.log(`โ
Generated src/wasm/templates.js with ${Object.keys(templates).length} templates.`);
-
- // 2. Bundle using esbuild
- try {
- await esbuild.build({
- entryPoints: [path.join(__dirname, '../src/wasm/core.js')],
- bundle: true,
- outfile: path.join(__dirname, '../dist/docmd-wasm.js'),
- platform: 'browser',
- format: 'iife',
- globalName: 'docmd',
- define: {
- 'process.env.NODE_ENV': '"production"'
- },
- banner: {
- js: 'var process = { cwd: () => "/", env: { NODE_ENV: "production" } };',
- },
- inject: [path.join(__dirname, '../src/wasm/shims.js')],
- plugins: [
- {
- name: 'node-deps-shim',
- setup(build) {
- // Redirect fs to custom shim
- build.onResolve({ filter: /^fs(-extra)?$/ }, args => ({ path: args.path, namespace: 'fs-shim' }));
- build.onLoad({ filter: /.*/, namespace: 'fs-shim' }, () => ({
- contents: `
- module.exports = {
- existsSync: (p) => {
- // Simple check in global templates
- if (!globalThis.__DOCMD_TEMPLATES__) return false;
- // Assume p might be absolute or relative, just take basename
- // Also handle 'toc' -> 'toc.ejs'
- let name = p.split(/[\\/]/).pop();
- if (!name.endsWith('.ejs') && !globalThis.__DOCMD_TEMPLATES__[name]) {
- name += '.ejs';
- }
- return !!globalThis.__DOCMD_TEMPLATES__[name];
- },
- readFileSync: (p, encoding) => {
- if (!globalThis.__DOCMD_TEMPLATES__) return '';
- let name = p.split(/[\\/]/).pop();
- if (!name.endsWith('.ejs') && !globalThis.__DOCMD_TEMPLATES__[name]) {
- name += '.ejs';
- }
- return globalThis.__DOCMD_TEMPLATES__[name] || '';
- },
- readFile: (p, enc, cb) => {
- // Async version shim if needed
- if (typeof enc === 'function') cb = enc;
- const content = module.exports.readFileSync(p);
- if (cb) cb(null, content);
- return Promise.resolve(content);
- },
- statSync: () => ({ isFile: () => true, isDirectory: () => false }),
- constants: { F_OK: 0, R_OK: 4 }
- };
- `,
- loader: 'js'
- }));
-
- // Redirect path to simple shim
- build.onResolve({ filter: /^path$/ }, args => ({ path: args.path, namespace: 'path-shim' }));
- build.onLoad({ filter: /.*/, namespace: 'path-shim' }, () => ({
- contents: `
- module.exports = {
- join: (...args) => args.join('/'),
- resolve: (...args) => args.join('/'),
- basename: (p) => p.split('/').pop(),
- dirname: (p) => p.split('/').slice(0, -1).join('/') || '.',
- relative: (from, to) => {
- return to.replace(from, '').replace(new RegExp('^/'), '');
- },
- extname: (p) => {
- const parts = p.split('.');
- return parts.length > 1 ? '.' + parts.pop() : '';
- },
- isAbsolute: (p) => p.startsWith('/'),
- sep: '/'
- };
- `,
- loader: 'js'
- }));
- }
- }
- ]
- });
- console.log('โ
Bundled dist/docmd-wasm.js');
-
- // 3. Copy Demo HTML
- const demoSrc = path.join(__dirname, '../src/wasm/wasm-demo.html');
- const demoDest = path.join(__dirname, '../dist/wasm-demo.html');
- if (fs.existsSync(demoSrc)) {
- fs.copyFileSync(demoSrc, demoDest);
- console.log('โ
Copied wasm-demo.html to dist/');
- }
-
- } catch (e) {
- console.error('โ Build failed:', e);
- process.exit(1);
- }
-}
-
-build();
diff --git a/scripts/test-wasm-build.js b/scripts/test-live.js
similarity index 57%
rename from scripts/test-wasm-build.js
rename to scripts/test-live.js
index 2ff92df..de431cb 100644
--- a/scripts/test-wasm-build.js
+++ b/scripts/test-live.js
@@ -2,18 +2,30 @@ const fs = require('fs');
const path = require('path');
const vm = require('vm');
-const bundlePath = path.join(__dirname, '../dist/docmd-wasm.js');
+const bundlePath = path.join(__dirname, '../dist/docmd-live.js');
+
+if (!fs.existsSync(bundlePath)) {
+ console.error('โ Bundle not found. Run "npm run live" or "node scripts/build-live.js" first.');
+ process.exit(1);
+}
+
const bundleCode = fs.readFileSync(bundlePath, 'utf8');
const sandbox = {
console: console,
setTimeout: setTimeout,
- clearTimeout: clearTimeout
+ clearTimeout: clearTimeout,
+ window: {},
+ self: {},
+ globalThis: {}
};
+sandbox.window = sandbox;
+sandbox.self = sandbox;
+sandbox.globalThis = sandbox;
vm.createContext(sandbox);
-console.log('๐งช Testing WASM/Browser bundle...');
+console.log('๐งช Testing Live Bundle...');
try {
vm.runInContext(bundleCode, sandbox);
@@ -22,13 +34,13 @@ try {
throw new Error('docmd global not found in bundle');
}
- const markdown = '# Hello WASM\nThis is a test.';
- const config = { siteTitle: 'Wasm Test' };
+ const markdown = '# Hello Live\nThis is a test.';
+ const config = { siteTitle: 'Live Test' };
console.log('Compiling markdown...');
const result = sandbox.docmd.compile(markdown, config);
- if (result.includes('Hello WASM ') && result.includes('Wasm Test')) {
+ if (result.includes('Hello Live ') && result.includes('Live Test')) {
console.log('โ
Bundle works! Output contains expected HTML.');
} else {
console.error('โ Bundle produced unexpected output.');
@@ -39,4 +51,4 @@ try {
} catch (e) {
console.error('โ Test failed:', e);
process.exit(1);
-}
+}
\ No newline at end of file
diff --git a/src/core/file-processor.js b/src/core/file-processor.js
index bf5b1b7..8c0a2af 100644
--- a/src/core/file-processor.js
+++ b/src/core/file-processor.js
@@ -37,6 +37,7 @@ async function processMarkdownFile(filePath, md, config) {
return processMarkdownContent(rawContent, md, config, filePath);
}
+// Pure logic, no file reading (Used by Live Editor)
function processMarkdownContent(rawContent, md, config, filePath = 'memory') {
let frontmatter, markdownContent;
diff --git a/src/core/html-generator.js b/src/core/html-generator.js
index 01ea263..aff08e1 100644
--- a/src/core/html-generator.js
+++ b/src/core/html-generator.js
@@ -8,10 +8,9 @@ const { generateSeoMetaTags } = require('../plugins/seo');
const { generateAnalyticsScripts } = require('../plugins/analytics');
const { renderIcon } = require('./icon-renderer');
-// Create a markdown instance for inline rendering
let mdInstance = null;
-
let themeInitScript = '';
+
(async () => {
if (typeof __dirname !== 'undefined') {
const themeInitPath = path.join(__dirname, '..', 'templates', 'partials', 'theme-init.js');
@@ -30,62 +29,35 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
let pluginHeadScriptsHtml = '';
let pluginBodyScriptsHtml = '';
- // Favicon (built-in handling)
if (config.favicon) {
const faviconPath = config.favicon.startsWith('/') ? config.favicon.substring(1) : config.favicon;
faviconLinkHtml = ` \n`;
}
-
- // Theme CSS (built-in handling for theme.name)
if (config.theme && config.theme.name && config.theme.name !== 'default') {
const themeCssPath = `assets/css/docmd-theme-${config.theme.name}.css`;
themeCssLinkHtml = ` \n`;
}
-
- // SEO Plugin (if configured)
if (config.plugins?.seo) {
metaTagsHtml += generateSeoMetaTags(config, pageData, relativePathToRoot);
}
-
- // Analytics Plugin (if configured)
if (config.plugins?.analytics) {
const analyticsScripts = generateAnalyticsScripts(config, pageData);
pluginHeadScriptsHtml += analyticsScripts.headScriptsHtml;
pluginBodyScriptsHtml += analyticsScripts.bodyScriptsHtml;
}
-
- return {
- metaTagsHtml,
- faviconLinkHtml,
- themeCssLinkHtml,
- pluginStylesHtml,
- pluginHeadScriptsHtml,
- pluginBodyScriptsHtml,
- };
+ return { metaTagsHtml, faviconLinkHtml, themeCssLinkHtml, pluginStylesHtml, pluginHeadScriptsHtml, pluginBodyScriptsHtml };
}
+// Main function used by CLI
async function generateHtmlPage(templateData) {
- const {
- content, siteTitle, navigationHtml,
- relativePathToRoot, config, frontmatter, outputPath,
- prevPage, nextPage, currentPagePath, headings
- } = templateData;
-
+ const { content, siteTitle, navigationHtml, relativePathToRoot, config, frontmatter, outputPath, prevPage, nextPage, currentPagePath, headings } = templateData;
const pageTitle = frontmatter.title;
- // Process plugins to get their HTML contributions
- const pluginOutputs = await processPluginHooks(
- config,
- { frontmatter, outputPath },
- relativePathToRoot
- );
+ const pluginOutputs = await processPluginHooks(config, { frontmatter, outputPath }, relativePathToRoot);
let footerHtml = '';
if (config.footer) {
- // Initialize mdInstance if not already done
- if (!mdInstance) {
- mdInstance = createMarkdownItInstance(config);
- }
+ if (!mdInstance) mdInstance = createMarkdownItInstance(config);
footerHtml = mdInstance.renderInline(config.footer);
}
@@ -94,71 +66,40 @@ async function generateHtmlPage(templateData) {
templateName = 'no-style.ejs';
}
+ // Node.js specific: Read file from disk
const layoutTemplatePath = path.join(__dirname, '..', 'templates', templateName);
if (!await fs.pathExists(layoutTemplatePath)) {
throw new Error(`Template not found: ${layoutTemplatePath}`);
}
- const isActivePage = currentPagePath && content && content.trim().length > 0;
+ const layoutTemplate = await fs.readFile(layoutTemplatePath, 'utf8');
- // Calculate Edit Link
+ const isActivePage = currentPagePath && content && content.trim().length > 0;
+
+ // Edit Link Logic (Simplified for brevity, keep your original logic here)
let editUrl = null;
let editLinkText = 'Edit this page';
-
if (config.editLink && config.editLink.enabled && config.editLink.baseUrl) {
- // Normalize URL (remove trailing slash)
- const baseUrl = config.editLink.baseUrl.replace(/\/$/, '');
-
- // Get the source file path relative to srcDir
- let relativeSourcePath = outputPath
- .replace(/\/index\.html$/, '.md') // folder/index.html -> folder.md
- .replace(/\\/g, '/'); // fix windows slashes
-
- // Special case: The root index.html comes from index.md
- if (relativeSourcePath === 'index.html') relativeSourcePath = 'index.md';
-
- // Let's assume a standard 1:1 mapping for v0.2.x
- editUrl = `${baseUrl}/${relativeSourcePath}`;
- editLinkText = config.editLink.text || editLinkText;
+ editUrl = `${config.editLink.baseUrl.replace(/\/$/, '')}/${outputPath.replace(/\/index\.html$/, '.md').replace(/\\/g, '/')}`;
+ if (outputPath.endsWith('index.html') && outputPath !== 'index.html') editUrl = editUrl.replace('.md', '/index.md');
+ if (outputPath === 'index.html') editUrl = `${config.editLink.baseUrl.replace(/\/$/, '')}/index.md`;
+ editLinkText = config.editLink.text || editLinkText;
}
const ejsData = {
- content,
- pageTitle,
- themeInitScript,
- description: frontmatter.description,
- siteTitle,
- navigationHtml,
- editUrl,
- editLinkText,
- defaultMode: config.theme?.defaultMode || 'light',
- relativePathToRoot,
- logo: config.logo,
- sidebarConfig: {
- collapsible: config.sidebar?.collapsible ?? false,
- defaultCollapsed: config.sidebar?.defaultCollapsed ?? false,
- },
- theme: config.theme,
- customCssFiles: config.theme?.customCss || [],
- customJsFiles: config.customJs || [],
- sponsor: config.sponsor,
- footer: config.footer,
- footerHtml,
- renderIcon,
- prevPage,
- nextPage,
- currentPagePath,
- headings: frontmatter.toc !== false ? (headings || []) : [],
- isActivePage,
- frontmatter,
- config: config,
- ...pluginOutputs,
+ content, pageTitle, themeInitScript, description: frontmatter.description, siteTitle, navigationHtml,
+ editUrl, editLinkText, defaultMode: config.theme?.defaultMode || 'light', relativePathToRoot,
+ logo: config.logo, sidebarConfig: config.sidebar || {}, theme: config.theme,
+ customCssFiles: config.theme?.customCss || [], customJsFiles: config.customJs || [],
+ sponsor: config.sponsor, footer: config.footer, footerHtml, renderIcon,
+ prevPage, nextPage, currentPagePath, headings: frontmatter.toc !== false ? (headings || []) : [],
+ isActivePage, frontmatter, config, ...pluginOutputs,
};
- const layoutTemplate = await fs.readFile(layoutTemplatePath, 'utf8');
-
+ // Call the pure render function
return renderHtmlPage(layoutTemplate, ejsData, layoutTemplatePath);
}
+// PURE FUNCTION: Renders string -> string (Used by WASM)
function renderHtmlPage(templateContent, ejsData, filename = 'template.ejs', options = {}) {
try {
return ejs.render(templateContent, ejsData, {
@@ -167,7 +108,6 @@ function renderHtmlPage(templateContent, ejsData, filename = 'template.ejs', opt
});
} catch (e) {
console.error(`โ Error rendering EJS template: ${e.message}`);
- console.error("EJS Data snippet:", JSON.stringify(ejsData, null, 2).substring(0, 500));
throw e;
}
}
@@ -178,18 +118,8 @@ async function generateNavigationHtml(navItems, currentPagePath, relativePathToR
throw new Error(`Navigation template not found: ${navTemplatePath}`);
}
const navTemplate = await fs.readFile(navTemplatePath, 'utf8');
-
const ejsHelpers = { renderIcon };
-
- return ejs.render(navTemplate, {
- navItems,
- currentPagePath,
- relativePathToRoot,
- config,
- ...ejsHelpers
- }, {
- filename: navTemplatePath
- });
+ return ejs.render(navTemplate, { navItems, currentPagePath, relativePathToRoot, config, ...ejsHelpers }, { filename: navTemplatePath });
}
module.exports = { generateHtmlPage, generateNavigationHtml, renderHtmlPage };
\ No newline at end of file
diff --git a/src/live/core.js b/src/live/core.js
new file mode 100644
index 0000000..aa4e487
--- /dev/null
+++ b/src/live/core.js
@@ -0,0 +1,63 @@
+const { processMarkdownContent, createMarkdownItInstance } = require('../core/file-processor');
+const { renderHtmlPage } = require('../core/html-generator');
+const templates = require('./templates');
+
+function compile(markdown, config = {}, options = {}) {
+ // Default config values for the browser
+ const defaults = {
+ siteTitle: 'Live Preview',
+ theme: { defaultMode: 'light', name: 'default' },
+ ...config
+ };
+
+ const md = createMarkdownItInstance(defaults);
+ const result = processMarkdownContent(markdown, md, defaults, 'memory');
+
+ if (!result) return 'Error parsing markdown
';
+
+ const { frontmatter, htmlContent, headings } = result;
+
+ const pageData = {
+ content: htmlContent,
+ frontmatter,
+ headings,
+ siteTitle: defaults.siteTitle,
+ pageTitle: frontmatter.title || 'Untitled',
+ description: frontmatter.description || '',
+ defaultMode: defaults.theme.defaultMode,
+ editUrl: null,
+ editLinkText: '',
+ navigationHtml: '', // Navigation is usually empty in a single-page preview
+ relativePathToRoot: options.relativePathToRoot || './', // Important for finding CSS in dist/assets
+ outputPath: 'index.html',
+ currentPagePath: '/index',
+ prevPage: null, nextPage: null,
+ config: defaults,
+ // Empty hooks
+ metaTagsHtml: '', faviconLinkHtml: '', themeCssLinkHtml: '',
+ pluginStylesHtml: '', pluginHeadScriptsHtml: '', pluginBodyScriptsHtml: '',
+ themeInitScript: '',
+ logo: defaults.logo, sidebarConfig: { collapsible: false }, theme: defaults.theme,
+ customCssFiles: [], customJsFiles: [],
+ sponsor: { enabled: false }, footer: '', footerHtml: '',
+ renderIcon: () => '', // Icons disabled in live preview to save weight
+ isActivePage: true
+ };
+
+ let templateName = frontmatter.noStyle === true ? 'no-style.ejs' : 'layout.ejs';
+ const templateContent = templates[templateName];
+
+ if (!templateContent) return `Template ${templateName} not found`;
+
+ const ejsOptions = {
+ includer: (originalPath) => {
+ let name = originalPath.endsWith('.ejs') ? originalPath : originalPath + '.ejs';
+ if (templates[name]) return { template: templates[name] };
+ return null;
+ }
+ };
+
+ return renderHtmlPage(templateContent, pageData, templateName, ejsOptions);
+}
+
+module.exports = { compile };
\ No newline at end of file
diff --git a/src/live/index.html b/src/live/index.html
new file mode 100644
index 0000000..4a69579
--- /dev/null
+++ b/src/live/index.html
@@ -0,0 +1,178 @@
+
+
+
+
+
+ Docmd Live
+
+
+
+
+
+
+
+
docmd Live
+
+
+ Split View
+ Single View
+
+
+
+
+ Editor
+ Preview
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Editor
+ Preview
+
+
+
+
+
\ No newline at end of file
diff --git a/src/live/live.css b/src/live/live.css
new file mode 100644
index 0000000..3fd317a
--- /dev/null
+++ b/src/live/live.css
@@ -0,0 +1,136 @@
+:root {
+ --header-height: 50px;
+ --border-color: #e0e0e0;
+ --bg-color: #f9fafb;
+ --primary-color: #007bff;
+ --resizer-width: 8px;
+}
+
+body {
+ margin: 0;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ overflow: hidden;
+}
+
+/* --- Top Bar --- */
+.top-bar {
+ height: var(--header-height);
+ background: #fff;
+ border-bottom: 1px solid var(--border-color);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 1rem;
+ flex-shrink: 0;
+}
+
+.logo { font-weight: 700; font-size: 1.1rem; display: flex; align-items: center; gap: 8px; }
+.logo span { background: var(--primary-color); color: white; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem; text-transform: uppercase; }
+
+.view-controls { display: flex; gap: 8px; background: #f0f0f0; padding: 3px; border-radius: 6px; }
+.view-btn { border: none; background: transparent; padding: 6px 10px; border-radius: 4px; cursor: pointer; font-size: 0.85rem; color: #666; font-weight: 500; }
+.view-btn.active { background: white; color: black; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
+
+/* --- Main Layout --- */
+.workspace {
+ flex: 1;
+ display: flex;
+ position: relative;
+ overflow: hidden;
+}
+
+.pane {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ min-width: 300px; /* Minimum width constraint */
+ background: white;
+}
+
+.pane-header {
+ padding: 8px 16px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ color: #888;
+ background: var(--bg-color);
+ border-bottom: 1px solid var(--border-color);
+ flex-shrink: 0;
+}
+
+/* Editor Pane */
+.editor-pane { width: 50%; }
+textarea#input {
+ flex: 1;
+ border: none;
+ resize: none;
+ padding: 20px;
+ font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
+ font-size: 14px;
+ line-height: 1.6;
+ outline: none;
+ background: var(--bg-color);
+}
+
+/* Preview Pane */
+.preview-pane { flex: 1; background: white; }
+iframe#preview { width: 100%; height: 100%; border: none; display: block; }
+
+/* --- Resizer Handle --- */
+.resizer {
+ width: var(--resizer-width);
+ background: var(--bg-color);
+ border-left: 1px solid var(--border-color);
+ border-right: 1px solid var(--border-color);
+ cursor: col-resize;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background 0.2s;
+ z-index: 10;
+}
+.resizer:hover, .resizer.resizing { background: #e0e0e0; }
+.resizer::after { content: "||"; color: #aaa; font-size: 10px; letter-spacing: 1px; }
+
+/* --- View Modes (Single vs Split) --- */
+
+/* Single Mode (Mobile style on Desktop) */
+body.mode-single .resizer { display: none; }
+body.mode-single .pane { width: 100% !important; min-width: 0; }
+body.mode-single .editor-pane { display: none; }
+body.mode-single .preview-pane { display: none; }
+body.mode-single.show-editor .editor-pane { display: flex; }
+body.mode-single.show-preview .preview-pane { display: flex; }
+
+/* --- Mobile Responsive Overrides --- */
+@media (max-width: 768px) {
+ /* Force single mode on mobile, hide desktop view controls */
+ .desktop-only { display: none !important; }
+ .resizer { display: none !important; }
+
+ .pane { width: 100% !important; }
+ .editor-pane { display: none; }
+ .preview-pane { display: none; }
+
+ body.mobile-tab-editor .editor-pane { display: flex; }
+ body.mobile-tab-preview .preview-pane { display: flex; }
+
+ .mobile-tabs {
+ display: flex !important;
+ position: fixed;
+ bottom: 0; left: 0; right: 0;
+ height: 50px;
+ background: white;
+ border-top: 1px solid var(--border-color);
+ z-index: 100;
+ }
+ .mobile-tab-btn { flex: 1; border: none; background: transparent; font-weight: 600; color: #888; cursor: pointer; }
+ .mobile-tab-btn.active { color: var(--primary-color); border-top: 2px solid var(--primary-color); }
+
+ .workspace { padding-bottom: 50px; } /* Space for tabs */
+}
+
+.mobile-tabs { display: none; } /* Hidden on desktop */
\ No newline at end of file
diff --git a/src/live/shims.js b/src/live/shims.js
new file mode 100644
index 0000000..a44475c
--- /dev/null
+++ b/src/live/shims.js
@@ -0,0 +1 @@
+import { Buffer } from 'buffer'; globalThis.Buffer = Buffer;
\ No newline at end of file
diff --git a/src/wasm/templates.js b/src/live/templates.js
similarity index 100%
rename from src/wasm/templates.js
rename to src/live/templates.js
diff --git a/src/wasm/core.js b/src/wasm/core.js
deleted file mode 100644
index 198033d..0000000
--- a/src/wasm/core.js
+++ /dev/null
@@ -1,102 +0,0 @@
-const { processMarkdownContent, createMarkdownItInstance } = require('../core/file-processor');
-const { renderHtmlPage } = require('../core/html-generator');
-const templates = require('./templates');
-
-/**
- * Compile markdown to HTML without file system access.
- * @param {string} markdown - The raw markdown content.
- * @param {object} config - The docmd configuration object.
- * @param {object} options - Options { currentPath: string }
- */
-function compile(markdown, config, options = {}) {
- const md = createMarkdownItInstance(config);
- const result = processMarkdownContent(markdown, md, config, options.currentPath || 'memory');
-
- if (!result) {
- throw new Error('Failed to process markdown');
- }
-
- const { frontmatter, htmlContent, headings } = result;
-
- // Prepare data for template
- // Note: We mocking some fields that depend on file system scan (like navigation)
- // unless passed in options.
-
- const pageData = {
- content: htmlContent,
- frontmatter,
- headings,
- // Defaults for missing data in pure compile mode
- siteTitle: config.siteTitle || 'Docmd Site',
- pageTitle: frontmatter.title,
- description: frontmatter.description,
- defaultMode: config.theme?.defaultMode || 'light',
- editUrl: null, // Mocked for now
- editLinkText: 'Edit this page',
- navigationHtml: options.navigationHtml || '',
- relativePathToRoot: options.relativePathToRoot || './',
- outputPath: options.outputPath || 'index.html',
- currentPagePath: options.currentPath || '/index',
- prevPage: options.prevPage || null,
- nextPage: options.nextPage || null,
- config: config,
- // Mock plugin outputs for now as they might use FS
- metaTagsHtml: '',
- faviconLinkHtml: '',
- themeCssLinkHtml: '',
- pluginStylesHtml: '',
- pluginHeadScriptsHtml: '',
- pluginBodyScriptsHtml: '',
- themeInitScript: '', // This is usually read from file, we might need to inline it too
- logo: config.logo,
- sidebarConfig: config.sidebar || {},
- theme: config.theme || {},
- customCssFiles: [],
- customJsFiles: [],
- sponsor: config.sponsor,
- footer: config.footer,
- footerHtml: '', // TODO: Render footer markdown
- renderIcon: (name) => ``, // Simple mock
- isActivePage: true
- };
-
- // Select template
- let templateName = 'layout.ejs';
- if (frontmatter.noStyle === true) {
- templateName = 'no-style.ejs';
- }
-
- const templateContent = templates[templateName];
- if (!templateContent) {
- throw new Error(`Template ${templateName} not found in bundled templates.`);
- }
-
- const ejsOptions = {
- includer: (originalPath, parsedPath) => {
- console.log('Includer called for:', originalPath);
- // originalPath is like 'toc'
- // We need to match it to a key in templates object (e.g., 'toc.ejs')
- let potentialName = originalPath;
- if (!potentialName.endsWith('.ejs')) {
- potentialName += '.ejs';
- }
- // Check for direct match or path match
- // The templates object keys are like 'toc.ejs', 'layout.ejs'
- // If includes use paths like '../partials/foo', we might need normalization.
- // But standard docmd templates seem flat or we'll flat them in build.
-
- // In build-wasm.js, we only included root templates. 'toc.ejs' is in root of templates dir.
- // If there are partials in subdirs, we need to handle that.
- // Checking file listing: templates/toc.ejs exists.
-
- if (templates[potentialName]) {
- return { template: templates[potentialName] };
- }
- return null;
- }
- };
-
- return renderHtmlPage(templateContent, pageData, templateName, ejsOptions);
-}
-
-module.exports = { compile, templates };
diff --git a/src/wasm/shims.js b/src/wasm/shims.js
deleted file mode 100644
index febfbe3..0000000
--- a/src/wasm/shims.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import { Buffer } from 'buffer';
-globalThis.Buffer = Buffer;
diff --git a/src/wasm/wasm-demo.html b/src/wasm/wasm-demo.html
deleted file mode 100644
index 2ac4db8..0000000
--- a/src/wasm/wasm-demo.html
+++ /dev/null
@@ -1,88 +0,0 @@
-
-
-
-
-
- Docmd WASM Demo
-
-
-
-
-
- Docmd Browser/WASM Demo
- Render
-
-
-
-
-
-
-
-
From 4a7f7c6b33eca772d44f3abdd634d48550e7c5d2 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 2 Jan 2026 12:08:02 +0000
Subject: [PATCH 15/28] Bump the npm-dependencies group with 8 updates
Bumps the npm-dependencies group with 8 updates:
| Package | From | To |
| --- | --- | --- |
| [commander](https://github.com/tj/commander.js) | `14.0.0` | `14.0.2` |
| [esbuild](https://github.com/evanw/esbuild) | `0.27.0` | `0.27.2` |
| [express](https://github.com/expressjs/express) | `5.1.0` | `5.2.1` |
| [fs-extra](https://github.com/jprichardson/node-fs-extra) | `11.3.0` | `11.3.3` |
| [lucide-static](https://github.com/lucide-icons/lucide) | `0.535.0` | `0.562.0` |
| [mermaid](https://github.com/mermaid-js/mermaid) | `11.12.1` | `11.12.2` |
| [eslint](https://github.com/eslint/eslint) | `9.32.0` | `9.39.2` |
| [prettier](https://github.com/prettier/prettier) | `3.6.2` | `3.7.4` |
Updates `commander` from 14.0.0 to 14.0.2
- [Release notes](https://github.com/tj/commander.js/releases)
- [Changelog](https://github.com/tj/commander.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tj/commander.js/compare/v14.0.0...v14.0.2)
Updates `esbuild` from 0.27.0 to 0.27.2
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.0...v0.27.2)
Updates `express` from 5.1.0 to 5.2.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/v5.1.0...v5.2.1)
Updates `fs-extra` from 11.3.0 to 11.3.3
- [Changelog](https://github.com/jprichardson/node-fs-extra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jprichardson/node-fs-extra/compare/11.3.0...11.3.3)
Updates `lucide-static` from 0.535.0 to 0.562.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/compare/0.535.0...0.562.0)
Updates `mermaid` from 11.12.1 to 11.12.2
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Commits](https://github.com/mermaid-js/mermaid/compare/mermaid@11.12.1...mermaid@11.12.2)
Updates `eslint` from 9.32.0 to 9.39.2
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.32.0...v9.39.2)
Updates `prettier` from 3.6.2 to 3.7.4
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.6.2...3.7.4)
---
updated-dependencies:
- dependency-name: commander
dependency-version: 14.0.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: npm-dependencies
- dependency-name: esbuild
dependency-version: 0.27.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: npm-dependencies
- dependency-name: express
dependency-version: 5.2.1
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: npm-dependencies
- dependency-name: fs-extra
dependency-version: 11.3.3
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: npm-dependencies
- dependency-name: lucide-static
dependency-version: 0.562.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: npm-dependencies
- dependency-name: mermaid
dependency-version: 11.12.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: npm-dependencies
- dependency-name: eslint
dependency-version: 9.39.2
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: npm-dependencies
- dependency-name: prettier
dependency-version: 3.7.4
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: npm-dependencies
...
Signed-off-by: dependabot[bot]
---
package-lock.json | 323 +++++++++++++++++++++++-----------------------
package.json | 2 +-
2 files changed, 164 insertions(+), 161 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index e4cd7d4..d0b79f7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,7 +19,7 @@
"fs-extra": "^11.2.0",
"gray-matter": "^4.0.3",
"highlight.js": "^11.11.1",
- "lucide-static": "^0.535.0",
+ "lucide-static": "^0.562.0",
"markdown-it-abbr": "^2.0.0",
"markdown-it-attrs": "^4.3.1",
"markdown-it-container": "^4.0.0",
@@ -108,9 +108,9 @@
"license": "Apache-2.0"
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz",
- "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
+ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
"cpu": [
"ppc64"
],
@@ -125,9 +125,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz",
- "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
+ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
"cpu": [
"arm"
],
@@ -142,9 +142,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz",
- "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
+ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
"cpu": [
"arm64"
],
@@ -159,9 +159,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz",
- "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
+ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
"cpu": [
"x64"
],
@@ -176,9 +176,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz",
- "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
+ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
"cpu": [
"arm64"
],
@@ -193,9 +193,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz",
- "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
+ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
"cpu": [
"x64"
],
@@ -210,9 +210,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz",
- "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
"cpu": [
"arm64"
],
@@ -227,9 +227,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz",
- "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
+ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
"cpu": [
"x64"
],
@@ -244,9 +244,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz",
- "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
+ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
"cpu": [
"arm"
],
@@ -261,9 +261,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz",
- "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
+ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
"cpu": [
"arm64"
],
@@ -278,9 +278,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz",
- "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
+ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
"cpu": [
"ia32"
],
@@ -295,9 +295,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz",
- "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
+ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
"cpu": [
"loong64"
],
@@ -312,9 +312,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz",
- "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
+ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
"cpu": [
"mips64el"
],
@@ -329,9 +329,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz",
- "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
+ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
"cpu": [
"ppc64"
],
@@ -346,9 +346,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz",
- "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
+ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
"cpu": [
"riscv64"
],
@@ -363,9 +363,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz",
- "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
+ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
"cpu": [
"s390x"
],
@@ -380,9 +380,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz",
- "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
+ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
"cpu": [
"x64"
],
@@ -397,9 +397,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz",
- "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
"cpu": [
"arm64"
],
@@ -414,9 +414,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz",
- "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
"cpu": [
"x64"
],
@@ -431,9 +431,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz",
- "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
"cpu": [
"arm64"
],
@@ -448,9 +448,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz",
- "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
"cpu": [
"x64"
],
@@ -465,9 +465,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz",
- "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
+ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
"cpu": [
"arm64"
],
@@ -482,9 +482,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz",
- "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
+ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
"cpu": [
"x64"
],
@@ -499,9 +499,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz",
- "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
+ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
"cpu": [
"arm64"
],
@@ -516,9 +516,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz",
- "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
+ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
"cpu": [
"ia32"
],
@@ -533,9 +533,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz",
- "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
+ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
"cpu": [
"x64"
],
@@ -550,9 +550,9 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
- "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -592,13 +592,13 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.21.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
- "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/object-schema": "^2.1.6",
+ "@eslint/object-schema": "^2.1.7",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
@@ -607,19 +607,22 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
- "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
"dev": true,
"license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": {
- "version": "0.15.1",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
- "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -654,9 +657,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.32.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz",
- "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==",
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -667,9 +670,9 @@
}
},
"node_modules/@eslint/object-schema": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
- "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -677,13 +680,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz",
- "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==",
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.15.1",
+ "@eslint/core": "^0.17.0",
"levn": "^0.4.1"
},
"engines": {
@@ -1365,9 +1368,9 @@
"license": "MIT"
},
"node_modules/commander": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz",
- "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==",
+ "version": "14.0.2",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
+ "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==",
"license": "MIT",
"engines": {
"node": ">=20"
@@ -2114,9 +2117,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz",
- "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
+ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -2127,32 +2130,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.0",
- "@esbuild/android-arm": "0.27.0",
- "@esbuild/android-arm64": "0.27.0",
- "@esbuild/android-x64": "0.27.0",
- "@esbuild/darwin-arm64": "0.27.0",
- "@esbuild/darwin-x64": "0.27.0",
- "@esbuild/freebsd-arm64": "0.27.0",
- "@esbuild/freebsd-x64": "0.27.0",
- "@esbuild/linux-arm": "0.27.0",
- "@esbuild/linux-arm64": "0.27.0",
- "@esbuild/linux-ia32": "0.27.0",
- "@esbuild/linux-loong64": "0.27.0",
- "@esbuild/linux-mips64el": "0.27.0",
- "@esbuild/linux-ppc64": "0.27.0",
- "@esbuild/linux-riscv64": "0.27.0",
- "@esbuild/linux-s390x": "0.27.0",
- "@esbuild/linux-x64": "0.27.0",
- "@esbuild/netbsd-arm64": "0.27.0",
- "@esbuild/netbsd-x64": "0.27.0",
- "@esbuild/openbsd-arm64": "0.27.0",
- "@esbuild/openbsd-x64": "0.27.0",
- "@esbuild/openharmony-arm64": "0.27.0",
- "@esbuild/sunos-x64": "0.27.0",
- "@esbuild/win32-arm64": "0.27.0",
- "@esbuild/win32-ia32": "0.27.0",
- "@esbuild/win32-x64": "0.27.0"
+ "@esbuild/aix-ppc64": "0.27.2",
+ "@esbuild/android-arm": "0.27.2",
+ "@esbuild/android-arm64": "0.27.2",
+ "@esbuild/android-x64": "0.27.2",
+ "@esbuild/darwin-arm64": "0.27.2",
+ "@esbuild/darwin-x64": "0.27.2",
+ "@esbuild/freebsd-arm64": "0.27.2",
+ "@esbuild/freebsd-x64": "0.27.2",
+ "@esbuild/linux-arm": "0.27.2",
+ "@esbuild/linux-arm64": "0.27.2",
+ "@esbuild/linux-ia32": "0.27.2",
+ "@esbuild/linux-loong64": "0.27.2",
+ "@esbuild/linux-mips64el": "0.27.2",
+ "@esbuild/linux-ppc64": "0.27.2",
+ "@esbuild/linux-riscv64": "0.27.2",
+ "@esbuild/linux-s390x": "0.27.2",
+ "@esbuild/linux-x64": "0.27.2",
+ "@esbuild/netbsd-arm64": "0.27.2",
+ "@esbuild/netbsd-x64": "0.27.2",
+ "@esbuild/openbsd-arm64": "0.27.2",
+ "@esbuild/openbsd-x64": "0.27.2",
+ "@esbuild/openharmony-arm64": "0.27.2",
+ "@esbuild/sunos-x64": "0.27.2",
+ "@esbuild/win32-arm64": "0.27.2",
+ "@esbuild/win32-ia32": "0.27.2",
+ "@esbuild/win32-x64": "0.27.2"
}
},
"node_modules/escape-html": {
@@ -2175,26 +2178,25 @@
}
},
"node_modules/eslint": {
- "version": "9.32.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz",
- "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==",
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
+ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.0",
- "@eslint/config-helpers": "^0.3.0",
- "@eslint/core": "^0.15.0",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.32.0",
- "@eslint/plugin-kit": "^0.3.4",
+ "@eslint/js": "9.39.2",
+ "@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
- "@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.6",
@@ -2436,18 +2438,19 @@
}
},
"node_modules/express": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
- "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
- "body-parser": "^2.2.0",
+ "body-parser": "^2.2.1",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
+ "depd": "^2.0.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
@@ -2627,9 +2630,9 @@
}
},
"node_modules/fs-extra": {
- "version": "11.3.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
- "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
+ "version": "11.3.3",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
+ "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
@@ -3182,9 +3185,9 @@
"license": "MIT"
},
"node_modules/lucide-static": {
- "version": "0.535.0",
- "resolved": "https://registry.npmjs.org/lucide-static/-/lucide-static-0.535.0.tgz",
- "integrity": "sha512-wlYTSPpeyMjLjQ5jgSAENQwVfURVf2XHV5TDp8YPCJBEyWz+FJGuGB5LYBgOFvWIDOMW+AIoiA8sNd8My/nxlw==",
+ "version": "0.562.0",
+ "resolved": "https://registry.npmjs.org/lucide-static/-/lucide-static-0.562.0.tgz",
+ "integrity": "sha512-TM2vNVOEsO3+ijmno7n/VmxUo0Shr9OXC/UqZc5n4xEVyXX4E4NVvXoRPAZiSsIsdvlQ7alGOcIC/QGtR+OgUQ==",
"license": "ISC"
},
"node_modules/markdown-it": {
@@ -3296,9 +3299,9 @@
}
},
"node_modules/mermaid": {
- "version": "11.12.1",
- "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.1.tgz",
- "integrity": "sha512-UlIZrRariB11TY1RtTgUWp65tphtBv4CSq7vyS2ZZ2TgoMjs2nloq+wFqxiwcxlhHUvs7DPGgMjs2aeQxz5h9g==",
+ "version": "11.12.2",
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz",
+ "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==",
"license": "MIT",
"dependencies": {
"@braintree/sanitize-url": "^7.1.1",
@@ -3593,9 +3596,9 @@
}
},
"node_modules/prettier": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
- "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "version": "3.7.4",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
+ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
"dev": true,
"license": "MIT",
"bin": {
diff --git a/package.json b/package.json
index 7a442eb..db6f913 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"fs-extra": "^11.2.0",
"gray-matter": "^4.0.3",
"highlight.js": "^11.11.1",
- "lucide-static": "^0.535.0",
+ "lucide-static": "^0.562.0",
"markdown-it-abbr": "^2.0.0",
"markdown-it-attrs": "^4.3.1",
"markdown-it-container": "^4.0.0",
From 92848134dfe06ddd234e6c4326efb648ca1030d7 Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 3 Jan 2026 00:48:18 +0530
Subject: [PATCH 16/28] Improve dev server UX and config fallback logic
Refactors the dev server for better logging, clearer watched paths, and improved live reload script. Adds fallback to legacy config.js if docmd.config.js is missing, and enhances error handling and user feedback. Also updates the live preview to set a default site title, force links to open in new tabs, and hide the sidebar header for a cleaner UI.
---
src/commands/build.js | 4 +-
src/commands/dev.js | 289 +++++++++++++-------------------------
src/core/config-loader.js | 26 +++-
src/live/index.html | 12 +-
4 files changed, 134 insertions(+), 197 deletions(-)
diff --git a/src/commands/build.js b/src/commands/build.js
index 62935ea..d8ded6e 100644
--- a/src/commands/build.js
+++ b/src/commands/build.js
@@ -369,7 +369,9 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
// Generate search index if enabled
if (config.search !== false) {
- console.log('๐ Generating search index...');
+ if (!options.isDev) {
+ console.log('๐ Generating search index...');
+ }
// Create MiniSearch instance
const miniSearch = new MiniSearch({
diff --git a/src/commands/dev.js b/src/commands/dev.js
index fb3e3ae..3bc3ab6 100644
--- a/src/commands/dev.js
+++ b/src/commands/dev.js
@@ -6,156 +6,93 @@ const WebSocket = require('ws');
const chokidar = require('chokidar');
const path = require('path');
const fs = require('fs-extra');
-const { buildSite } = require('./build'); // Re-use the build logic
+const chalk = require('chalk');
+const os = require('os');
+const { buildSite } = require('./build');
const { loadConfig } = require('../core/config-loader');
-/**
- * Format paths for display to make them relative to CWD
- * @param {string} absolutePath - The absolute path to format
- * @param {string} cwd - Current working directory
- * @returns {string} - Formatted relative path
- */
function formatPathForDisplay(absolutePath, cwd) {
- // Get the relative path from CWD
const relativePath = path.relative(cwd, absolutePath);
-
- // If it's not a subdirectory, prefix with ./ for clarity
if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
return `./${relativePath}`;
}
-
- // Return the relative path
return relativePath;
}
+function getNetworkIp() {
+ const interfaces = os.networkInterfaces();
+ for (const name of Object.keys(interfaces)) {
+ for (const iface of interfaces[name]) {
+ if (iface.family === 'IPv4' && !iface.internal) {
+ return iface.address;
+ }
+ }
+ }
+ return null;
+}
+
async function startDevServer(configPathOption, options = { preserve: false, port: undefined }) {
- let config = await loadConfig(configPathOption); // Load initial config
- const CWD = process.cwd(); // Current Working Directory where user runs `docmd dev`
+ let config = await loadConfig(configPathOption);
+ const CWD = process.cwd();
+
+ // Config Fallback for Watcher
+ let actualConfigPath = path.resolve(CWD, configPathOption);
+ if (configPathOption === 'docmd.config.js' && !await fs.pathExists(actualConfigPath)) {
+ const legacyPath = path.resolve(CWD, 'config.js');
+ if (await fs.pathExists(legacyPath)) {
+ actualConfigPath = legacyPath;
+ }
+ }
- // Function to resolve paths based on current config
const resolveConfigPaths = (currentConfig) => {
return {
outputDir: path.resolve(CWD, currentConfig.outputDir),
srcDirToWatch: path.resolve(CWD, currentConfig.srcDir),
- configFileToWatch: path.resolve(CWD, configPathOption), // Path to the config file itself
- userAssetsDir: path.resolve(CWD, 'assets'), // User's assets directory
+ configFileToWatch: actualConfigPath,
+ userAssetsDir: path.resolve(CWD, 'assets'),
};
};
let paths = resolveConfigPaths(config);
-
- // docmd's internal templates and assets (for live dev of docmd itself)
- const DOCMD_COMMANDS_DIR = path.resolve(__dirname, '..', 'commands');
- const DOCMD_CORE_DIR = path.resolve(__dirname, '..', 'core');
- const DOCMD_PLUGINS_DIR = path.resolve(__dirname, '..', 'plugins');
- const DOCMD_TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates');
- const DOCMD_ASSETS_DIR = path.resolve(__dirname, '..', 'assets');
-
+ const DOCMD_ROOT = path.resolve(__dirname, '..');
+
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
- let wsClients = new Set();
- wss.on('connection', (ws) => {
- wsClients.add(ws);
- // console.log('Client connected to WebSocket. Total clients:', wsClients.size);
- ws.on('close', () => {
- wsClients.delete(ws);
- // console.log('Client disconnected. Total clients:', wsClients.size);
- });
- ws.on('error', (error) => {
- console.error('WebSocket error on client:', error);
- });
- });
- wss.on('error', (error) => {
- console.error('WebSocket Server error:', error);
- });
-
function broadcastReload() {
- wsClients.forEach(client => {
+ wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
- try {
- client.send('reload');
- } catch (error) {
- console.error('Error sending reload command to client:', error);
- }
+ client.send('reload');
}
});
}
- // Inject live reload script into HTML responses
+ // Inject live reload script
app.use((req, res, next) => {
- if (req.path.endsWith('.html') || !req.path.includes('.')) {
+ if (req.path.endsWith('.html') || req.path === '/' || !req.path.includes('.')) {
const originalSend = res.send;
res.send = function(body) {
if (typeof body === 'string' && body.includes('
')) {
const liveReloadScript = `
`;
@@ -167,148 +104,122 @@ async function startDevServer(configPathOption, options = { preserve: false, por
next();
});
- // Add Last-Modified header to all responses for polling fallback
- app.use((req, res, next) => {
- res.setHeader('Last-Modified', new Date().toUTCString());
- next();
- });
-
- // Serve static files from the output directory
- // This middleware needs to be dynamic if outputDir changes
let staticMiddleware = express.static(paths.outputDir);
app.use((req, res, next) => staticMiddleware(req, res, next));
- // Initial build
- console.log('๐ Performing initial build for dev server...');
+ // --- 1. Initial Build ---
+ console.log(chalk.blue('๐ Performing initial build...'));
try {
- await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true }); // Use the original config path option
- console.log('โ
Initial build complete.');
+ await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true });
} catch (error) {
- console.error('โ Initial build failed:', error.message, error.stack);
- // Optionally, don't start server if initial build fails, or serve a specific error page.
+ console.error(chalk.red('โ Initial build failed:'), error.message);
}
- // Check if user assets directory exists
+ // --- 2. Setup Watcher & Logs ---
const userAssetsDirExists = await fs.pathExists(paths.userAssetsDir);
-
- // Watch for changes
- const watchedPaths = [
- paths.srcDirToWatch,
- paths.configFileToWatch,
- ];
-
- // Add user assets directory to watched paths if it exists
- if (userAssetsDirExists) {
- watchedPaths.push(paths.userAssetsDir);
- }
-
- // Add internal paths for docmd development (not shown to end users)
- const internalPaths = [DOCMD_TEMPLATES_DIR, DOCMD_ASSETS_DIR, DOCMD_COMMANDS_DIR, DOCMD_CORE_DIR, DOCMD_PLUGINS_DIR];
+ const watchedPaths = [paths.srcDirToWatch, paths.configFileToWatch];
+ if (userAssetsDirExists) watchedPaths.push(paths.userAssetsDir);
- // Only in development environments, we might want to watch internal files too
if (process.env.DOCMD_DEV === 'true') {
- watchedPaths.push(...internalPaths);
+ watchedPaths.push(
+ path.join(DOCMD_ROOT, 'templates'),
+ path.join(DOCMD_ROOT, 'assets'),
+ path.join(DOCMD_ROOT, 'core'),
+ path.join(DOCMD_ROOT, 'plugins')
+ );
}
- console.log(`๐ Watching for changes in:`);
- console.log(` - Source: ${formatPathForDisplay(paths.srcDirToWatch, CWD)}`);
- console.log(` - Config: ${formatPathForDisplay(paths.configFileToWatch, CWD)}`);
+ // LOGS: Explicitly print what we are watching
+ console.log(chalk.dim('\n๐ Watching for changes in:'));
+ console.log(chalk.dim(` - Source: ${chalk.cyan(formatPathForDisplay(paths.srcDirToWatch, CWD))}`));
+ console.log(chalk.dim(` - Config: ${chalk.cyan(formatPathForDisplay(paths.configFileToWatch, CWD))}`));
if (userAssetsDirExists) {
- console.log(` - Assets: ${formatPathForDisplay(paths.userAssetsDir, CWD)}`);
+ console.log(chalk.dim(` - Assets: ${chalk.cyan(formatPathForDisplay(paths.userAssetsDir, CWD))}`));
}
if (process.env.DOCMD_DEV === 'true') {
- console.log(` - docmd Templates: ${formatPathForDisplay(DOCMD_TEMPLATES_DIR, CWD)} (internal)`);
- console.log(` - docmd Assets: ${formatPathForDisplay(DOCMD_ASSETS_DIR, CWD)} (internal)`);
+ console.log(chalk.dim(` - docmd Internal: ${chalk.magenta(formatPathForDisplay(DOCMD_ROOT, CWD))}`));
}
+ console.log('');
const watcher = chokidar.watch(watchedPaths, {
- ignored: /(^|[\/\\])\../, // ignore dotfiles
+ ignored: /(^|[\/\\])\../,
persistent: true,
- ignoreInitial: true, // Don't trigger for initial scan
- awaitWriteFinish: { // Helps with rapid saves or large file writes
- stabilityThreshold: 100,
- pollInterval: 100
- }
+ ignoreInitial: true,
+ awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 100 }
});
watcher.on('all', async (event, filePath) => {
const relativeFilePath = path.relative(CWD, filePath);
- console.log(`๐ Detected ${event} in ${relativeFilePath}. Rebuilding...`);
+ process.stdout.write(chalk.dim(`โป Change in ${relativeFilePath}... `));
+
try {
if (filePath === paths.configFileToWatch) {
- console.log('Config file changed. Reloading configuration...');
- config = await loadConfig(configPathOption); // Reload config
+ config = await loadConfig(configPathOption);
const newPaths = resolveConfigPaths(config);
-
- // Update watcher if srcDir changed - Chokidar doesn't easily support dynamic path changes after init.
- // For simplicity, we might need to restart the watcher or inform user to restart dev server if srcDir/outputDir change.
- // For now, we'll at least update the static server path.
if (newPaths.outputDir !== paths.outputDir) {
- console.log(`Output directory changed from ${formatPathForDisplay(paths.outputDir, CWD)} to ${formatPathForDisplay(newPaths.outputDir, CWD)}. Updating static server.`);
staticMiddleware = express.static(newPaths.outputDir);
}
- // If srcDirToWatch changes, chokidar won't automatically pick it up.
- // A full dev server restart would be more robust for such config changes.
- // For now, the old srcDir will still be watched.
- paths = newPaths; // Update paths for next build reference
+ paths = newPaths;
}
- await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true }); // Re-build using the potentially updated config path
+ await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true });
broadcastReload();
- console.log('โ
Rebuild complete.');
+ process.stdout.write(chalk.green('Done.\n'));
+
} catch (error) {
- console.error('โ Rebuild failed:', error.message, error.stack);
+ console.error(chalk.red('\nโ Rebuild failed:'), error.message);
}
});
- watcher.on('error', error => console.error(`Watcher error: ${error}`));
-
- // Try different ports if the default port is in use
const PORT = options.port || process.env.PORT || 3000;
const MAX_PORT_ATTEMPTS = 10;
- let currentPort = parseInt(PORT, 10);
- // Function to try starting the server on different ports
function tryStartServer(port, attempt = 1) {
- server.listen(port)
+ // 0.0.0.0 allows network access
+ server.listen(port, '0.0.0.0')
.on('listening', async () => {
- // Check if index.html exists after initial build
const indexHtmlPath = path.join(paths.outputDir, 'index.html');
+ const networkIp = getNetworkIp();
+
+ // Use 127.0.0.1 explicitly
+ const localUrl = `http://127.0.0.1:${port}`;
+ const networkUrl = networkIp ? `http://${networkIp}:${port}` : null;
+
+ const border = chalk.gray('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ console.log(border);
+ console.log(` ${chalk.bold.green('SERVER RUNNING')} ${chalk.dim(`(v${require('../../package.json').version})`)}`);
+ console.log('');
+ console.log(` ${chalk.bold('Local:')} ${chalk.cyan(localUrl)}`);
+ if (networkUrl) {
+ console.log(` ${chalk.bold('Network:')} ${chalk.cyan(networkUrl)}`);
+ }
+ console.log('');
+ console.log(` ${chalk.dim('Serving:')} ${formatPathForDisplay(paths.outputDir, CWD)}`);
+ console.log(border);
+ console.log('');
+
if (!await fs.pathExists(indexHtmlPath)) {
- console.warn(`โ ๏ธ Warning: ${formatPathForDisplay(indexHtmlPath, CWD)} not found after initial build.
- The dev server is running, but you might see a 404 for the root page.
- Ensure your '${config.srcDir}' directory contains an 'index.md' or your navigation points to existing files.`);
+ console.warn(chalk.yellow(`โ ๏ธ Warning: Root index.html not found.`));
}
- console.log(`๐ Dev server started at http://localhost:${port}`);
- console.log(`Serving content from: ${formatPathForDisplay(paths.outputDir, CWD)}`);
- console.log(`Live reload is active. Browser will refresh automatically when files change.`);
})
.on('error', (err) => {
if (err.code === 'EADDRINUSE' && attempt < MAX_PORT_ATTEMPTS) {
- console.log(`Port ${port} is in use, trying port ${port + 1}...`);
- server.close();
tryStartServer(port + 1, attempt + 1);
} else {
- console.error(`Failed to start server: ${err.message}`);
+ console.error(chalk.red(`Failed to start server: ${err.message}`));
process.exit(1);
}
});
}
- // Start the server with port fallback
- tryStartServer(currentPort);
+ tryStartServer(parseInt(PORT, 10));
- // Graceful shutdown
process.on('SIGINT', () => {
- console.log('\n๐ Shutting down dev server...');
+ console.log(chalk.yellow('\n๐ Shutting down...'));
watcher.close();
- wss.close(() => {
- server.close(() => {
- console.log('Server closed.');
- process.exit(0);
- });
- });
+ process.exit(0);
});
}
+// Ensure this export is here!
module.exports = { startDevServer };
\ No newline at end of file
diff --git a/src/core/config-loader.js b/src/core/config-loader.js
index 6dc94dd..9ed5b33 100644
--- a/src/core/config-loader.js
+++ b/src/core/config-loader.js
@@ -5,12 +5,32 @@ const fs = require('fs-extra');
const { validateConfig } = require('./config-validator');
async function loadConfig(configPath) {
- const absoluteConfigPath = path.resolve(process.cwd(), configPath);
+ const cwd = process.cwd();
+ let absoluteConfigPath = path.resolve(cwd, configPath);
+
+ // 1. Check if the requested config file exists
if (!await fs.pathExists(absoluteConfigPath)) {
- throw new Error(`Configuration file not found at: ${absoluteConfigPath}\nRun "docmd init" to create one.`);
+ // 2. Fallback Logic:
+ // If the user didn't specify a custom path (i.e., using default 'docmd.config.js')
+ // AND 'docmd.config.js' is missing...
+ // Check if legacy 'config.js' exists.
+ if (configPath === 'docmd.config.js') {
+ const legacyPath = path.resolve(cwd, 'config.js');
+ if (await fs.pathExists(legacyPath)) {
+ // console.log('โ ๏ธ Using legacy config.js. Please rename to docmd.config.js'); // Optional warning
+ absoluteConfigPath = legacyPath;
+ } else {
+ // Neither exists
+ throw new Error(`Configuration file not found at: ${absoluteConfigPath}\nRun "docmd init" to create one.`);
+ }
+ } else {
+ // User specified a custom path that doesn't exist
+ throw new Error(`Configuration file not found at: ${absoluteConfigPath}`);
+ }
}
+
try {
- // Clear require cache to always get the freshest config
+ // Clear require cache to always get the freshest config (important for dev mode reloading)
delete require.cache[require.resolve(absoluteConfigPath)];
const config = require(absoluteConfigPath);
diff --git a/src/live/index.html b/src/live/index.html
index 4a69579..6c45d5c 100644
--- a/src/live/index.html
+++ b/src/live/index.html
@@ -74,22 +74,26 @@
function render() {
try {
let html = docmd.compile(input.value, {
- siteTitle: '',
+ siteTitle: 'My Project', // User can change this in real config, but here we set a default
search: false,
theme: { name: 'sky', defaultMode: 'light' },
sidebar: { collapsible: false }
});
- // --- INJECT CUSTOM CSS HERE ---
+ // --- INJECTIONS ---
+
+ // 1. Force links to open in new tab (Fixes infinite iframe recursion)
+ html = html.replace('
');
+
+ // 2. Hide Sidebar Header via CSS
const customStyle = `
`;
html = html.replace('', `${customStyle}`);
- // ------------------------------
+ // ------------------
const doc = preview.contentWindow.document;
doc.open();
From b4c8a0ac474325551e3a65ea5f12249f237daf3b Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 3 Jan 2026 00:51:20 +0530
Subject: [PATCH 17/28] workflow update
---
.github/workflows/deploy-docmd.yml | 15 ++++++++++++---
.gitignore | 2 ++
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/deploy-docmd.yml b/.github/workflows/deploy-docmd.yml
index 7202983..a92231b 100644
--- a/.github/workflows/deploy-docmd.yml
+++ b/.github/workflows/deploy-docmd.yml
@@ -2,8 +2,8 @@ name: deploy docmd
on:
push:
- branches: [main] # Your default branch
- workflow_dispatch: # Allows manual triggering
+ branches: [main]
+ workflow_dispatch:
permissions:
contents: read
@@ -26,12 +26,21 @@ jobs:
node-version: '22'
cache: 'npm'
+ - name: Update NPM to Latest ๐
+ run: npm install -g npm@latest
+
- name: Install Dependencies ๐ฆ
run: npm ci
-
+
- name: Build docmd's Own Docs ๐ ๏ธ
run: node ./bin/docmd.js build
+ - name: Build Live Editor โก
+ run: node scripts/build-live.js
+
+ - name: Inject Live Editor into Site
+ run: mv dist site/live
+
- name: Setup Pages
uses: actions/configure-pages@v5
diff --git a/.gitignore b/.gitignore
index a2d4e80..3ca3569 100644
--- a/.gitignore
+++ b/.gitignore
@@ -137,3 +137,5 @@ ROADMAP.md
.DS_Store
genctx.json
+genctx.config.json
+genctx.context.md
From c6421f391faf352c50629a8fb3b3af8f709499a1 Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 3 Jan 2026 00:51:44 +0530
Subject: [PATCH 18/28] Added live preview documentation
---
docs/comparison.md | 20 +++++-----
docs/content/live-preview.md | 71 ++++++++++++++++++++++++++++++++++++
2 files changed, 81 insertions(+), 10 deletions(-)
create mode 100644 docs/content/live-preview.md
diff --git a/docs/comparison.md b/docs/comparison.md
index 0e9c71e..3af7011 100644
--- a/docs/comparison.md
+++ b/docs/comparison.md
@@ -1,5 +1,5 @@
---
-title: "Comparison between docmd, Docusaurus, MkDocs, Mintlify and more"
+title: "Comparison"
description: "A detailed comparison of docmd against Docusaurus, MkDocs, Mintlify, and Docsify."
---
@@ -13,9 +13,9 @@ Choosing the right tool depends on your specific needs. `docmd` was built to fil
| :--- | :--- | :--- | :--- | :--- | :--- |
| **Core Tech** | Node.js (Native) | React.js | Python | Proprietary | JS (Runtime) |
| **Output** | Static HTML | React SPA (Hydrated) | Static HTML | Hosted / Next.js | None (Runtime SPA) |
-| **Setup Time** | ~2 minutes | ~15 minutes | ~10 mins (Python env) | Instant (SaaS) | Instant |
+| **Browser Engine** | **Yes (Isomorphic)** | No | No | No | Yes |
+| **Setup Time** | ~1 minute | ~15 mins | ~10 mins (Python env) | Instant (SaaS) | Instant |
| **Client JS Size** | **Tiny (< 15kb)** | Heavy (React Bundle) | Minimal | Medium | Medium |
-| **Customization** | Standard CSS/JS | React Components | Python/Jinja2 | JSON Config | Vue/Plugins |
| **Search** | **Built-in (Offline)** | Algolia (Requires Setup) | Built-in (Lunr) | Built-in | Client-side Plugin |
| **SEO** | **Excellent** | Excellent | Excellent | Excellent | **Poor** |
| **Hosting** | **Anywhere** | Anywhere | Anywhere | **Vendor Locked** | Anywhere |
@@ -23,8 +23,13 @@ Choosing the right tool depends on your specific needs. `docmd` was built to fil
## Detailed Breakdown
+### The "Live" Advantage
+Unlike Docusaurus or MkDocs, which are strictly "Build Tools" that run on your server/computer, `docmd` has a **modular, isomorphic core**.
+* **Run it anywhere:** You can run the full `docmd` compilation engine directly in a web browser.
+* **Live Previews:** This enables features like the [Live Editor](/live/), allowing you to build CMS interfaces or live preview tools for your users without needing a backend server.
+
### The Search Advantage
-* **Docusaurus and others** rely on 3rd party services like Algolia and others. This is great for enterprise scale, but for most projects, it's a hassle. You have to apply for an account, manage API keys, and configure crawlers.
+* **Docusaurus and others** often rely on 3rd party services like Algolia. This is great for enterprise scale, but for most projects, it's a hassle. You have to apply for an account, manage API keys, and configure crawlers.
* **docmd** includes a production-grade search engine out of the box. It generates a local index during the build. This means your documentation is **searchable even offline** (perfect for Intranets or air-gapped networks) and respects user privacy completely.
### vs. Docusaurus
@@ -37,15 +42,10 @@ Choosing the right tool depends on your specific needs. `docmd` was built to fil
* **Choose MkDocs if:** You are already in the Python ecosystem or need its mature plugin ecosystem immediately.
* **Choose docmd if:** You are a JavaScript/Node.js developer. You want to run `npm install` and go, without dealing with `pip`, `requirements.txt`, or Python version conflicts.
-### vs. Mintlify
-**Mintlify** is a modern, hosted documentation platform focused on design.
-* **Choose Mintlify if:** You have a budget and don't want to touch *any* code or configuration. You just want to upload markdown and pay someone to host and style it.
-* **Choose docmd if:** You want full control over your HTML/CSS and want to host your docs for **free** on GitHub Pages, Vercel, or Netlify without branding watermarks or custom domain fees.
-
### vs. Docsify
**Docsify** is a "magical" generator that parses Markdown on the fly in the browser.
* **Choose Docsify if:** You absolutely cannot run a build step (e.g., you are hosting on a legacy server that only serves static files and you can't run CI/CD).
-* **Choose docmd if:** You care about **SEO** and **Performance**. Docsify requires the user's browser to download the Markdown parser and the content before rendering anything, which is bad for search engines and users on slow connections. `docmd` builds real HTML files that load instantly.
+* **Choose docmd if:** You care about **SEO** and **Performance**. Docsify requires the user's browser to download the Markdown parser and the content before rendering anything, which is bad for search engines. `docmd` gives you the best of both worlds: Static HTML for SEO, plus a Browser Engine if you need dynamic previews.
## The docmd Philosophy
diff --git a/docs/content/live-preview.md b/docs/content/live-preview.md
new file mode 100644
index 0000000..430f4cb
--- /dev/null
+++ b/docs/content/live-preview.md
@@ -0,0 +1,71 @@
+---
+title: "Live Preview"
+description: "Run docmd entirely in the browser without a server using the new Live architecture."
+---
+
+# Live Preview & Browser Support
+
+::: button Open_Live_Editor /live/ color:#007bff
+:::
+
+Starting with version 0.3.4, `docmd` features a modular architecture that separates file system operations from core processing logic. This allows the documentation engine to run **entirely in the browser** (client-side), opening up possibilities for live editors, CMS previews, and zero-latency feedback loops.
+
+## The Live Editor
+
+`docmd` comes with a built-in "Live Editor" that demonstrates this capability. It provides a split-pane interface where you can write Markdown on the left and see the rendered documentation on the rightโinstantly, without a server round-trip.
+
+### Running the Editor Locally
+
+To launch the live editor on your machine:
+
+```bash
+docmd live
+```
+
+This command will:
+1. Bundle the core logic into `dist/docmd-live.js`.
+2. Copy necessary assets (CSS, templates).
+3. Start a local static server opening the editor.
+
+## Embedding docmd in Your Site
+
+You can use the browser-compatible bundle to add Markdown preview capabilities to your own applications.
+
+### 1. Include the Script and Assets
+
+You need to serve the `docmd-live.js` bundle and the `assets/` folder (which contains themes and styles).
+
+```html
+
+
+
+```
+
+### 2. Use the API
+
+The bundle exposes a global `docmd` object. You can use the `compile` function to transform Markdown into a full HTML page string.
+
+```javascript
+const markdown = "# Hello World\n\nThis is **live** documentation.";
+
+const config = {
+ siteTitle: 'My Live Doc',
+ theme: {
+ name: 'sky',
+ defaultMode: 'light'
+ }
+};
+
+// Compile returns the full HTML string including , , etc.
+const html = docmd.compile(markdown, config, {
+ // Optional: Help the renderer resolve relative paths
+ relativePathToRoot: './'
+});
+
+// Inject into an iframe or DOM element
+document.getElementById('preview-frame').srcdoc = html;
+```
+
+::: callout warning Limitation
+The Live browser build cannot scan your local hard drive for files. Features that rely on file scanning (like automatically generating the Sidebar Navigation based on folder structure) must be passed explicitly via the `config` object or navigation options.
+:::
\ No newline at end of file
From 84077d0a24eb24c21009dc954f98757c90e0da76 Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 3 Jan 2026 00:51:48 +0530
Subject: [PATCH 19/28] Update docmd.js
---
bin/docmd.js | 144 ++++++++++++++++++---------------------------------
1 file changed, 51 insertions(+), 93 deletions(-)
diff --git a/bin/docmd.js b/bin/docmd.js
index ea58ae4..252bbfe 100755
--- a/bin/docmd.js
+++ b/bin/docmd.js
@@ -1,113 +1,71 @@
#!/usr/bin/env node
-const { Command } = require('commander');
-const fs = require('fs-extra');
-const path = require('path');
-const { version } = require('../package.json');
-const { initProject } = require('../src/commands/init');
+const { program } = require('commander');
+// This import corresponds to module.exports = { startDevServer } in src/commands/dev.js
+const { startDevServer } = require('../src/commands/dev');
const { buildSite } = require('../src/commands/build');
-const { startDevServer } = require('../src/commands/dev');
+const { initProject } = require('../src/commands/init');
+const { version } = require('../package.json');
const { printBanner } = require('../src/core/logger');
-
-// Helper function to find the config file
-const findConfigFile = () => {
- const newConfigPath = 'docmd.config.js';
- const oldConfigPath = 'config.js';
-
- if (fs.existsSync(path.resolve(process.cwd(), newConfigPath))) {
- return newConfigPath;
- }
- if (fs.existsSync(path.resolve(process.cwd(), oldConfigPath))) {
- return oldConfigPath;
- }
-
- throw new Error('Configuration file not found. Please create a docmd.config.js file or run "docmd init".');
-};
-
-const program = new Command();
+const path = require('path');
+const { spawn } = require('child_process');
program
.name('docmd')
- .description('Generate beautiful, lightweight static documentation sites directly from your Markdown files.')
- .version(version);
+ .description('The minimalist, zero-config documentation generator')
+ .version(version, '-v, --version', 'Output the current version')
+ .helpOption('-h, --help', 'Display help for command');
program
.command('init')
- .description('Initialize a new docmd project (creates docs/ and config file)')
- .action(async () => {
- try {
- await initProject();
- console.log('โ
docmd project initialized successfully!');
- } catch (error) {
- console.error('โ Error initializing project:', error.message);
- process.exit(1);
- }
+ .description('Initialize a new documentation project')
+ .action(() => {
+ printBanner();
+ initProject();
});
program
- .command('build')
- .description('Build the static site from Markdown files and config')
- .option('-c, --config ', 'Path to config file')
- .option('-p, --preserve', 'Preserve existing asset files instead of updating them')
- .option('--no-preserve', 'Force update all asset files, overwriting existing ones')
- .option('--silent', 'Suppress log output')
- .action(async (options) => {
- try {
- if (!options.silent) { printBanner(); }
-
- const originalLog = console.log;
- if (options.silent) { console.log = () => {}; }
-
- const configPath = options.config || findConfigFile();
- console.log(`๐ Starting build process using ${configPath}...`);
- await buildSite(configPath, {
- preserve: options.preserve
- });
-
- console.log = originalLog;
- if (!options.silent) {
- console.log('โ
Build complete! Site generated in `site/` directory.');
- }
-
- } catch (error) {
- console.error('โ Build failed:', error.message);
- // console.error(error.stack);
- process.exit(1);
- }
+ .command('dev')
+ .description('Start the development server with live reload')
+ .option('-c, --config ', 'Path to configuration file', 'docmd.config.js')
+ .option('-p, --port ', 'Port to run the server on')
+ .option('--preserve', 'Preserve existing assets', false)
+ .action((options) => {
+ printBanner();
+ startDevServer(options.config, options);
});
program
- .command('dev')
- .description('Start a live preview development server')
- .option('-c, --config ', 'Path to config file')
- .option('--port ', 'Specify a port for the dev server')
- .option('-p, --preserve', 'Preserve existing asset files instead of updating them')
- .option('--no-preserve', 'Force update all asset files, overwriting existing ones')
- .option('--silent', 'Suppress log output')
- .action(async (options) => {
- try {
- if (!options.silent) { printBanner(); }
+ .command('build')
+ .description('Build the static documentation site')
+ .option('-c, --config ', 'Path to configuration file', 'docmd.config.js')
+ .option('--preserve', 'Preserve existing assets', false)
+ .action((options) => {
+ buildSite(options.config, { isDev: false, preserve: options.preserve });
+ });
- if (options.silent) {
- const originalLog = console.log;
- console.log = (message) => {
- if (message && message.includes('Dev server started at')) {
- originalLog(message);
- }
- };
+program
+ .command('live')
+ .description('Build and serve the browser-based live editor')
+ .action(() => {
+ const scriptPath = path.resolve(__dirname, '../scripts/build-live.js');
+ const distPath = path.resolve(__dirname, '../dist');
+
+ console.log('๐ Starting Live Editor build...');
+
+ // Using spawn ensures the build runs in a fresh process context
+ const build = spawn(process.execPath, [scriptPath], { stdio: 'inherit' });
+
+ build.on('close', (code) => {
+ if (code === 0) {
+ console.log('\n๐ Launching server...');
+ console.log(' Press Ctrl+C to stop.\n');
+
+ // Fix for Node DeprecationWarning regarding shell: true
+ const serveCmd = `npx serve "${distPath}"`;
+ spawn(serveCmd, { stdio: 'inherit', shell: true });
}
- const configPath = options.config || findConfigFile();
- await startDevServer(configPath, { preserve: options.preserve, port: options.port });
-
- } catch (error) {
- console.error('โ Dev server failed:', error.message);
- // console.error(error.stack);
- process.exit(1);
- }
+ });
});
-program.parse(process.argv);
-
-if (!process.argv.slice(2).length) {
- program.outputHelp();
-}
\ No newline at end of file
+program.parse();
\ No newline at end of file
From ecf75b0de2a420b7eb0f4d3d9303ced51ce953b2 Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 3 Jan 2026 00:52:17 +0530
Subject: [PATCH 20/28] Migrated from config.js
---
config.js => docmd.config.js | 1 +
1 file changed, 1 insertion(+)
rename config.js => docmd.config.js (98%)
diff --git a/config.js b/docmd.config.js
similarity index 98%
rename from config.js
rename to docmd.config.js
index 13a4d94..fdacb5d 100644
--- a/config.js
+++ b/docmd.config.js
@@ -116,6 +116,7 @@ module.exports = {
]
},
{ title: 'No-Style Pages', path: '/content/no-style-pages', icon: 'layout' },
+ { title: 'Live Preview', path: '/content/live-preview', icon: 'monitor-play' },
],
},
From 2ae6d33e30630ded1d3cd2168fa27b87659e150e Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 3 Jan 2026 00:52:28 +0530
Subject: [PATCH 21/28] Update 0.3.5
---
package-lock.json | 178 ++--------------------------------------------
package.json | 7 +-
2 files changed, 10 insertions(+), 175 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index d0b79f7..16f6bcb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@mgks/docmd",
- "version": "0.3.4",
+ "version": "0.3.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@mgks/docmd",
- "version": "0.3.4",
+ "version": "0.3.5",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -19,7 +19,8 @@
"fs-extra": "^11.2.0",
"gray-matter": "^4.0.3",
"highlight.js": "^11.11.1",
- "lucide-static": "^0.562.0",
+ "lucide-static": "^0.535.0",
+ "markdown-it": "^14.0.0",
"markdown-it-abbr": "^2.0.0",
"markdown-it-attrs": "^4.3.1",
"markdown-it-container": "^4.0.0",
@@ -38,8 +39,6 @@
"buffer": "^6.0.3",
"esbuild": "^0.27.0",
"eslint": "^9.32.0",
- "eslint-config-prettier": "^10.1.8",
- "eslint-plugin-node": "^11.1.0",
"prettier": "^3.2.5"
},
"engines": {
@@ -2238,63 +2237,6 @@
}
}
},
- "node_modules/eslint-config-prettier": {
- "version": "10.1.8",
- "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
- "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "eslint-config-prettier": "bin/cli.js"
- },
- "funding": {
- "url": "https://opencollective.com/eslint-config-prettier"
- },
- "peerDependencies": {
- "eslint": ">=7.0.0"
- }
- },
- "node_modules/eslint-plugin-es": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz",
- "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-utils": "^2.0.0",
- "regexpp": "^3.0.0"
- },
- "engines": {
- "node": ">=8.10.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/mysticatea"
- },
- "peerDependencies": {
- "eslint": ">=4.19.1"
- }
- },
- "node_modules/eslint-plugin-node": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz",
- "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-plugin-es": "^3.0.0",
- "eslint-utils": "^2.0.0",
- "ignore": "^5.1.1",
- "minimatch": "^3.0.4",
- "resolve": "^1.10.1",
- "semver": "^6.1.0"
- },
- "engines": {
- "node": ">=8.10.0"
- },
- "peerDependencies": {
- "eslint": ">=5.16.0"
- }
- },
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
@@ -2312,32 +2254,6 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/eslint-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
- "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^1.1.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/mysticatea"
- }
- },
- "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
- "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/eslint-visitor-keys": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
@@ -2936,22 +2852,6 @@
"node": ">= 0.10"
}
},
- "node_modules/is-core-module": {
- "version": "2.16.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
- "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@@ -3185,9 +3085,9 @@
"license": "MIT"
},
"node_modules/lucide-static": {
- "version": "0.562.0",
- "resolved": "https://registry.npmjs.org/lucide-static/-/lucide-static-0.562.0.tgz",
- "integrity": "sha512-TM2vNVOEsO3+ijmno7n/VmxUo0Shr9OXC/UqZc5n4xEVyXX4E4NVvXoRPAZiSsIsdvlQ7alGOcIC/QGtR+OgUQ==",
+ "version": "0.535.0",
+ "resolved": "https://registry.npmjs.org/lucide-static/-/lucide-static-0.535.0.tgz",
+ "integrity": "sha512-wlYTSPpeyMjLjQ5jgSAENQwVfURVf2XHV5TDp8YPCJBEyWz+FJGuGB5LYBgOFvWIDOMW+AIoiA8sNd8My/nxlw==",
"license": "ISC"
},
"node_modules/markdown-it": {
@@ -3536,13 +3436,6 @@
"node": ">=8"
}
},
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/path-to-regexp": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
@@ -3695,40 +3588,6 @@
"url": "https://paulmillr.com/funding/"
}
},
- "node_modules/regexpp": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
- "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/mysticatea"
- }
- },
- "node_modules/resolve": {
- "version": "1.22.10",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
- "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-core-module": "^2.16.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -3818,16 +3677,6 @@
"node": ">=4"
}
},
- "node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
"node_modules/send": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
@@ -4036,19 +3885,6 @@
"node": ">=8"
}
},
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/tinyexec": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
diff --git a/package.json b/package.json
index db6f913..3460056 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@mgks/docmd",
- "version": "0.3.4",
+ "version": "0.3.5",
"description": "Generate beautiful, lightweight static documentation sites directly from your Markdown files. Zero clutter, just content.",
"main": "src/index.js",
"exports": {
@@ -31,7 +31,8 @@
"fs-extra": "^11.2.0",
"gray-matter": "^4.0.3",
"highlight.js": "^11.11.1",
- "lucide-static": "^0.562.0",
+ "lucide-static": "^0.535.0",
+ "markdown-it": "^14.0.0",
"markdown-it-abbr": "^2.0.0",
"markdown-it-attrs": "^4.3.1",
"markdown-it-container": "^4.0.0",
@@ -47,8 +48,6 @@
"buffer": "^6.0.3",
"esbuild": "^0.27.0",
"eslint": "^9.32.0",
- "eslint-config-prettier": "^10.1.8",
- "eslint-plugin-node": "^11.1.0",
"prettier": "^3.2.5"
},
"directories": {
From fc789991a50dfae3e09e862d9208c61507ea8ef3 Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 3 Jan 2026 00:53:06 +0530
Subject: [PATCH 22/28] Update README.md
---
README.md | 192 ++++++++++++++++++++++++++----------------------------
1 file changed, 92 insertions(+), 100 deletions(-)
diff --git a/README.md b/README.md
index d9aa070..7db9802 100644
--- a/README.md
+++ b/README.md
@@ -1,68 +1,63 @@
-
-
+
-
+
-
-
-
- The minimalist, zero-config documentation generator for Node.js developers.
-
- Turn Markdown into beautiful, blazing-fast websites in seconds.
-
-
+
+ The minimalist, zero-config documentation generator.
+
+
-
-
-
+
+
+
+
View Live Demo โข
- Documentation โข
+ Read Documentation โข
+ Live Editor โข
Report Bug
-
-
-
+
+
docmd noStyle page preview in dark mode
-## ๐ Why docmd?
-
-Most documentation tools today are too heavy (React hydration, massive bundles) or require ecosystems you don't use (Python/Ruby).
-
-**docmd** fills the gap. It is a native Node.js tool that generates **pure, static HTML**.
+## Features
-* โก **Blazing Fast:** No hydration delay. Instant page loads.
-* ๐ **Offline Search:** Powerful full-text search included automatically.
-* ๐ **Zero Config:** Works out of the box with sensible defaults.
-* ๐จ **Theming:** Built-in light/dark modes and multiple themes (`sky`, `ruby`, `retro`).
-* ๐ฆ **Node.js Native:** No Python, no Gemfiles. Just `npm install`.
-* ๐งฉ **Rich Content:** Built-in support for Callouts, Cards, Tabs, Steps, and Changelogs.
+- **Zero Config**: Works out of the box with sensible defaults. Just `init` and go.
+- **Blazing Fast**: Generates **pure, static HTML**. No React hydration lag, no heavy bundles.
+- **Smart Search**: Built-in, **offline-capable** full-text search with fuzzy matching. No API keys required.
+- **Isomorphic Core**: Runs anywhereโNode.js CLI, CI/CD pipelines, or **directly in the browser** via WASM.
+- **Rich Content**: Built-in support for Callouts, Cards, Tabs, Steps, Changelogs, and Mermaid diagrams.
+- **Theming**: Beautiful light/dark modes and multiple pre-built themes (`sky`, `ruby`, `retro`).
-## ๐ Quick Start
+## Quick Start
-You don't need to install anything globally to try it out.
+**Installation:**
```bash
-# 1. Initialize a new project
-npx @mgks/docmd init my-docs
+npm install -g @mgks/docmd
+```
-# 2. Enter directory
-cd my-docs
+**Run:**
-# 3. Start the dev server
-npm start
+```bash
+docmd init my-docs # Initialize a new project
+cd my-docs # Enter directory
+docmd dev # Start live-reloading server
+docmd build # Generate static site for deployment
+docmd live # Launch live editor to preview and design pages
```
-**Dev server output:**
+**Dev Server:**
-```
+```js
_ _
_| |___ ___ _____ _| |
@@ -72,92 +67,89 @@ npm start
v0.x.x
-๐ Performing initial build for dev server...
-โ
Generated sitemap at ./site/sitemap.xml
-โ
Initial build complete.
-๐ Watching for changes in:
- - Source: ./docs
- - Config: ./docmd.config.js
- - Assets: ./assets
- - docmd Templates: ./src/templates (internal)
- - docmd Assets: ./src/assets (internal)
-๐ Dev server started at http://localhost:3000
-Serving content from: ./site
-Live reload is active. Browser will refresh automatically when files change.
-```
-
-## โจ Features
+๐ Performing initial build...
-| Feature | Description |
-| :--- | :--- |
-| **Markdown First** | Standard Markdown + Frontmatter. No proprietary syntax to learn. |
-| **Smart CLI** | Intelligent config validation catches typos before they break your build. |
-| **Custom Containers** | Use `::: callout`, `::: card`, `::: steps`, `::: tabs`, `::: collapsible`, `::: changelog`, and more to enrich content. |
-| **Smart Search** | Built-in, offline-capable full-text search with fuzzy matching and highlighting. No API keys required. |
-| **Diagrams** | Create flowcharts, relationship diagrams, journey, piecharts, graphs, timelines and more with Mermaid. |
-| **No-Style Pages** | Create custom landing pages (highly customizable custom HTML pages) without theme constraints. |
-| **Auto Dark Mode** | Respects system preference and saves user choice. |
-| **Plugins** | SEO, Sitemap, and Analytics support included out-of-the-box. |
+๐ Watching for changes in:
+ - Source: ./docs
+ - Config: ./config.js
+ - Assets: ./assets
-## ๐ Comparison
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ SERVER RUNNING (v0.3.5)
-How does `docmd` stack up against the giants?
+ Local: http://127.0.0.1:3000
+ Network: http://192.1.1.1:3000
-| Feature | docmd | Docusaurus | MkDocs (Material) | Mintlify |
-| :--- | :--- | :--- | :--- | :--- |
-| **Language** | **Node.js** | React.js | Python | Proprietary |
-| **Output** | **Static HTML** | React SPA | Static HTML | Hosted / Next.js |
-| **JS Payload** | **Tiny (< 15kb)** | Heavy | Minimal | Medium |
-| **Setup** | **~2 mins** | ~15 mins | ~10 mins | Instant (SaaS) |
-| **Cost** | **100% Free OSS** | 100% Free OSS | 100% Free OSS | Freemium |
+ Serving: ./site
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+```
-๐ *[Read the full comparison](https://docmd.mgks.dev/comparison/)*
+## Usage in Detail
-## ๐ฆ Installation
+### Project Structure
-For frequent use, install globally:
+`docmd` keeps it simple. Your content lives in `docs/`, your config in `docmd.config.js`.
```bash
-npm install -g @mgks/docmd
+my-docs/
+โโโ docs/ # Your Markdown files
+โ โโโ index.md # Homepage
+โ โโโ guide.md # Content page
+โโโ assets/ # Images and custom CSS
+โโโ docmd.config.js # Configuration
```
-### Commands
-
-* `docmd init` - Create a new documentation project.
-* `docmd dev` - Start the live-reloading local server.
-* `docmd build` - Generate static files to `site/` for deployment.
-
-## ๐จ Themes
+### Configuration
-Switching themes is as easy as changing one line in your `docmd.config.js`.
+Customize your site in seconds via `docmd.config.js`:
```javascript
module.exports = {
+ siteTitle: 'My Project',
+ srcDir: 'docs',
+ outputDir: 'site',
theme: {
- name: 'sky', // Options: 'default', 'sky', 'ruby', 'retro'
- defaultMode: 'dark'
- }
+ name: 'sky', // 'default', 'sky', 'ruby', 'retro'
+ defaultMode: 'dark', // 'light' or 'dark'
+ enableModeToggle: true
+ },
+ navigation: [
+ { title: 'Home', path: '/', icon: 'home' },
+ { title: 'Guide', path: '/guide', icon: 'book' }
+ ]
}
```
-## ๐ค Contributing
+## Live Editor
-We welcome contributions! Please see our [Contribution Guidelines](.github/CONTRIBUTING.md) for details.
+`docmd` comes with a modular architecture that allows the core engine to run client-side.
-1. Fork the repository.
-2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
-3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
-4. Push to the branch (`git push origin feature/AmazingFeature`).
-5. Open a Pull Request.
+**Launch locally:**
+```bash
+docmd live
+```
+This builds and serves a local editor where you can write Markdown and see the preview instantly without any server-side processing.
+
+**Embed in your app:**
+You can also use the `dist/docmd-live.js` bundle to add Markdown compilation capabilities to your own web applications.
+
+## Comparison
-## โค๏ธ Support
+| Feature | docmd | Docusaurus | MkDocs | Mintlify |
+| :--- | :--- | :--- | :--- | :--- |
+| **Language** | **Node.js** | React.js | Python | Proprietary |
+| **Output** | **Static HTML** | React SPA | Static HTML | Hosted |
+| **JS Payload** | **Tiny (< 15kb)** | Heavy | Minimal | Medium |
+| **Search** | **Built-in (Offline)** | Algolia (Ext) | Built-in | Built-in |
+| **Setup** | **~1 min** | ~15 mins | ~10 mins | Instant |
+| **Cost** | **Free OSS** | Free OSS | Free OSS | Freemium |
-This project is open source and free to use. If you find it valuable, please consider:
+## Community & Support
-1. โญ๏ธ **Starring the repo** on GitHub (it helps a lot!)
-2. โ **[Sponsoring the project](https://github.com/sponsors/mgks)** to support ongoing development.
+- **Contributing**: We welcome PRs! See [CONTRIBUTING.md](.github/CONTRIBUTING.md).
+- **Support**: If you find `docmd` useful, please consider [sponsoring the project](https://github.com/sponsors/mgks) or giving it a star โญ.
-## ๐ License
+## License
Distributed under the MIT License. See `LICENSE` for more information.
From b33093adebe7583951456a51f70797d3548a822a Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 3 Jan 2026 00:59:44 +0530
Subject: [PATCH 23/28] Added back support
---
src/live/index.html | 10 +++++++++-
src/live/live.css | 35 +++++++++++++++++++++++++++++++++--
2 files changed, 42 insertions(+), 3 deletions(-)
diff --git a/src/live/index.html b/src/live/index.html
index 6c45d5c..459f4d1 100644
--- a/src/live/index.html
+++ b/src/live/index.html
@@ -11,7 +11,15 @@
-
docmd Live
+
Split View
diff --git a/src/live/live.css b/src/live/live.css
index 3fd317a..3197ca0 100644
--- a/src/live/live.css
+++ b/src/live/live.css
@@ -27,8 +27,39 @@ body {
flex-shrink: 0;
}
-.logo { font-weight: 700; font-size: 1.1rem; display: flex; align-items: center; gap: 8px; }
-.logo span { background: var(--primary-color); color: white; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem; text-transform: uppercase; }
+.logo {
+ font-weight: 700;
+ font-size: 1.1rem;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.logo span {
+ background: var(--primary-color);
+ color: white;
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-size: 0.75rem;
+ text-transform: uppercase;
+}
+
+.back-link {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #666;
+ transition: color 0.2s, transform 0.2s;
+ text-decoration: none;
+ padding: 4px;
+ border-radius: 4px;
+}
+
+.back-link:hover {
+ color: var(--primary-color);
+ background: #f0f0f0;
+ transform: translateX(-2px);
+}
.view-controls { display: flex; gap: 8px; background: #f0f0f0; padding: 3px; border-radius: 6px; }
.view-btn { border: none; background: transparent; padding: 6px 10px; border-radius: 4px; cursor: pointer; font-size: 0.85rem; color: #666; font-weight: 500; }
From c82de1e4ac3774e02829aad7d565995b078b1398 Mon Sep 17 00:00:00 2001
From: Ghazi
Date: Sat, 3 Jan 2026 04:01:06 +0530
Subject: [PATCH 24/28] Dependencies update
---
src/assets/js/docmd-main.js | 141 +++++++++--------------------------
src/commands/build.js | 13 ++--
src/commands/init.js | 16 ++--
src/core/html-generator.js | 96 ++++++++++++++++++++----
src/live/index.html | 13 +++-
src/live/templates.js | 4 +-
src/templates/layout.ejs | 22 +++---
src/templates/navigation.ejs | 76 +++++++++++++++++--
8 files changed, 227 insertions(+), 154 deletions(-)
diff --git a/src/assets/js/docmd-main.js b/src/assets/js/docmd-main.js
index 06f58ed..b7d02ad 100644
--- a/src/assets/js/docmd-main.js
+++ b/src/assets/js/docmd-main.js
@@ -9,124 +9,82 @@ function initializeCollapsibleNav() {
const nav = document.querySelector('.sidebar-nav');
if (!nav) return;
- let navStates = {};
- try {
- // Use sessionStorage to remember state only for the current session
- navStates = JSON.parse(sessionStorage.getItem('docmd-nav-states')) || {};
- } catch (e) { /* silent fail */ }
+ // We NO LONGER set initial state here.
+ // The HTML arrives with style="display: block" and aria-expanded="true"
+ // pre-rendered by the build process. This eliminates the FOUC/Jitter.
nav.querySelectorAll('li.collapsible').forEach(item => {
- const navId = item.dataset.navId;
const anchor = item.querySelector('a');
const submenu = item.querySelector('.submenu');
- if (!navId || !anchor || !submenu) return;
-
- const isParentActive = item.classList.contains('active-parent');
- // Default to expanded if it's a parent of the active page, otherwise check stored state.
- let isExpanded = isParentActive || (navStates[navId] === true);
-
- const toggleSubmenu = (expand) => {
- item.setAttribute('aria-expanded', expand);
- submenu.style.display = expand ? 'block' : 'none';
- navStates[navId] = expand;
- sessionStorage.setItem('docmd-nav-states', JSON.stringify(navStates));
- };
-
- // Set initial state on page load
- toggleSubmenu(isExpanded);
+ if (!anchor || !submenu) return;
+ // Only handle CLICK events to toggle state
anchor.addEventListener('click', (e) => {
- const currentExpanded = item.getAttribute('aria-expanded') === 'true';
const href = anchor.getAttribute('href');
- const isPlaceholder = !href || href === '#' || href === '';
-
- if (!currentExpanded) {
- toggleSubmenu(true);
- } else if (isPlaceholder || e.target.closest('.collapse-icon')) {
- toggleSubmenu(false);
- }
+ // If it's a placeholder link (#) OR the user clicked the arrow icon
+ const isToggleAction = !href || href === '#' || e.target.closest('.collapse-icon');
- if (isPlaceholder || e.target.closest('.collapse-icon')) {
+ if (isToggleAction) {
e.preventDefault();
+
+ // Toggle Logic
+ const isExpanded = item.getAttribute('aria-expanded') === 'true';
+ const newState = !isExpanded;
+
+ item.setAttribute('aria-expanded', newState);
+ submenu.style.display = newState ? 'block' : 'none';
}
});
-
- /* anchor.addEventListener('click', (e) => {
- // If the click target is the icon, ALWAYS prevent navigation and toggle.
- if (e.target.closest('.collapse-icon')) {
- e.preventDefault();
- toggleSubmenu(item.getAttribute('aria-expanded') !== 'true');
- }
- // If the link is just a placeholder, also prevent navigation and toggle.
- else if (anchor.getAttribute('href') === '#') {
- e.preventDefault();
- toggleSubmenu(item.getAttribute('aria-expanded') !== 'true');
- }
- // Otherwise, let the click proceed to navigate to the link.
- });*/
});
}
// --- Mobile Menu Logic ---
function initializeMobileMenus() {
- // 1. Sidebar Toggle
const sidebarBtn = document.querySelector('.sidebar-menu-button');
const sidebar = document.querySelector('.sidebar');
if (sidebarBtn && sidebar) {
sidebarBtn.addEventListener('click', (e) => {
- e.stopPropagation(); // Prevent bubbling
+ e.stopPropagation();
sidebar.classList.toggle('mobile-expanded');
});
}
- // 2. TOC Toggle
const tocBtn = document.querySelector('.toc-menu-button');
const tocContainer = document.querySelector('.toc-container');
- // Also allow clicking the title text to toggle
const tocTitle = document.querySelector('.toc-title');
const toggleToc = (e) => {
- // Only engage on mobile view (check if button is visible)
if (window.getComputedStyle(tocBtn).display === 'none') return;
-
e.stopPropagation();
tocContainer.classList.toggle('mobile-expanded');
};
if (tocBtn && tocContainer) {
tocBtn.addEventListener('click', toggleToc);
- if (tocTitle) {
- tocTitle.addEventListener('click', toggleToc);
- }
+ if (tocTitle) tocTitle.addEventListener('click', toggleToc);
}
}
-// --- Sidebar Scroll Preservation ---
+// --- Sidebar Scroll Preservation (Instant Center) ---
function initializeSidebarScroll() {
const sidebar = document.querySelector('.sidebar');
if (!sidebar) return;
- setTimeout(() => {
- const activeElement = sidebar.querySelector('a.active') || sidebar.querySelector('.active-parent > a');
+ // Wait for the layout to be stable
+ requestAnimationFrame(() => {
+ // Find the active link
+ const activeElement = sidebar.querySelector('a.active');
if (activeElement) {
- const sidebarRect = sidebar.getBoundingClientRect();
- const elementRect = activeElement.getBoundingClientRect();
-
- // Check if the element's top or bottom is outside the sidebar's visible area
- const isNotInView = elementRect.top < sidebarRect.top || elementRect.bottom > sidebarRect.bottom;
-
- if (isNotInView) {
- activeElement.scrollIntoView({
- behavior: 'auto',
- block: 'center',
- inline: 'nearest'
- });
- }
+ activeElement.scrollIntoView({
+ behavior: 'auto', // INSTANT jump (prevents scrolling animation jitter)
+ block: 'center', // Center it vertically in the sidebar
+ inline: 'nearest'
+ });
}
- }, 10);
+ });
}
// --- Theme Toggle Logic ---
@@ -138,7 +96,6 @@ function setupThemeToggleListener() {
document.body.setAttribute('data-theme', theme);
localStorage.setItem('docmd-theme', theme);
- // Switch highlight.js theme
const highlightThemeLink = document.getElementById('highlight-theme');
if (highlightThemeLink) {
const newHref = highlightThemeLink.getAttribute('data-base-href') + `docmd-highlight-${theme}.css`;
@@ -146,7 +103,6 @@ function setupThemeToggleListener() {
}
}
- // Add click listener to the toggle button
if (themeToggleButton) {
themeToggleButton.addEventListener('click', () => {
const currentTheme = document.documentElement.getAttribute('data-theme');
@@ -161,22 +117,15 @@ function initializeSidebarToggle() {
const toggleButton = document.getElementById('sidebar-toggle-button');
const body = document.body;
- if (!body.classList.contains('sidebar-collapsible') || !toggleButton) {
- return;
- }
+ if (!body.classList.contains('sidebar-collapsible') || !toggleButton) return;
const defaultConfigCollapsed = body.dataset.defaultCollapsed === 'true';
let isCollapsed = localStorage.getItem('docmd-sidebar-collapsed');
- if (isCollapsed === null) {
- isCollapsed = defaultConfigCollapsed;
- } else {
- isCollapsed = isCollapsed === 'true';
- }
+ if (isCollapsed === null) isCollapsed = defaultConfigCollapsed;
+ else isCollapsed = isCollapsed === 'true';
- if (isCollapsed) {
- body.classList.add('sidebar-collapsed');
- }
+ if (isCollapsed) body.classList.add('sidebar-collapsed');
toggleButton.addEventListener('click', () => {
body.classList.toggle('sidebar-collapsed');
@@ -197,9 +146,7 @@ function initializeTabs() {
tabPanes.forEach(pane => pane.classList.remove('active'));
navItem.classList.add('active');
- if (tabPanes[index]) {
- tabPanes[index].classList.add('active');
- }
+ if (tabPanes[index]) tabPanes[index].classList.add('active');
});
});
});
@@ -207,9 +154,7 @@ function initializeTabs() {
// --- Copy Code Button Logic ---
function initializeCopyCodeButtons() {
- if (document.body.dataset.copyCodeEnabled !== 'true') {
- return;
- }
+ if (document.body.dataset.copyCodeEnabled !== 'true') return;
const copyIconSvg = ` `;
const checkIconSvg = ` `;
@@ -218,18 +163,12 @@ function initializeCopyCodeButtons() {
const codeElement = preElement.querySelector('code');
if (!codeElement) return;
- // Create a wrapper div around the pre element
const wrapper = document.createElement('div');
wrapper.style.position = 'relative';
wrapper.style.display = 'block';
- // Insert the wrapper before the pre element
preElement.parentNode.insertBefore(wrapper, preElement);
-
- // Move the pre element into the wrapper
wrapper.appendChild(preElement);
-
- // Remove the relative positioning from pre since wrapper handles it
preElement.style.position = 'static';
const copyButton = document.createElement('button');
@@ -260,21 +199,11 @@ function syncBodyTheme() {
if (currentTheme && document.body) {
document.body.setAttribute('data-theme', currentTheme);
}
-
- // Also ensure highlight CSS matches the current theme
- const highlightThemeLink = document.getElementById('highlight-theme');
- if (highlightThemeLink && currentTheme) {
- const baseHref = highlightThemeLink.getAttribute('data-base-href');
- if (baseHref) {
- const newHref = baseHref + `docmd-highlight-${currentTheme}.css`;
- highlightThemeLink.setAttribute('href', newHref);
- }
- }
}
// --- Main Execution ---
document.addEventListener('DOMContentLoaded', () => {
- syncBodyTheme(); // Sync body theme with html theme
+ syncBodyTheme();
setupThemeToggleListener();
initializeSidebarToggle();
initializeTabs();
diff --git a/src/commands/build.js b/src/commands/build.js
index d8ded6e..646c0dc 100644
--- a/src/commands/build.js
+++ b/src/commands/build.js
@@ -66,6 +66,7 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
const md = createMarkdownItInstance(config);
const shouldMinify = !options.isDev && config.minify !== false;
const searchIndexData = [];
+ const isOfflineMode = options.offline === true;
if (!await fs.pathExists(SRC_DIR)) {
throw new Error(`Source directory not found: ${formatPathForDisplay(SRC_DIR, CWD)}`);
@@ -287,10 +288,11 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
const currentPagePathForNav = normalizedPath;
const navigationHtml = await generateNavigationHtml(
- config.navigation,
- currentPagePathForNav,
- relativePathToRoot,
- config
+ config.navigation,
+ normalizedPath,
+ relativePathToRoot,
+ config,
+ isOfflineMode
);
// Find previous and next pages for navigation
@@ -320,9 +322,10 @@ async function buildSite(configPath, options = { isDev: false, preserve: false,
nextPage: nextPage,
currentPagePath: normalizedPath,
headings: headings || [],
+ isOfflineMode,
};
- const pageHtml = await generateHtmlPage(pageDataForTemplate);
+ const pageHtml = await generateHtmlPage(pageDataForTemplate, isOfflineMode);
await fs.ensureDir(path.dirname(finalOutputHtmlPath));
await fs.writeFile(finalOutputHtmlPath, pageHtml);
diff --git a/src/commands/init.js b/src/commands/init.js
index 07f2ef8..92185ae 100644
--- a/src/commands/init.js
+++ b/src/commands/init.js
@@ -14,10 +14,10 @@ module.exports = {
// Logo Configuration
logo: {
- light: '/assets/images/docmd-logo-light.png', // Path relative to outputDir root
- dark: '/assets/images/docmd-logo-dark.png', // Path relative to outputDir root
+ light: 'assets/images/docmd-logo-light.png', // Path relative to outputDir root
+ dark: 'assets/images/docmd-logo-dark.png', // Path relative to outputDir root
alt: 'docmd logo', // Alt text for the logo
- href: '/', // Link for the logo, defaults to site root
+ href: './', // Link for the logo, defaults to site root
},
// Directory Configuration
@@ -44,14 +44,14 @@ module.exports = {
positionMode: 'top', // 'top' or 'bottom' for the theme toggle
codeHighlight: true, // Enable/disable codeblock highlighting and import of highlight.js
customCss: [ // Array of paths to custom CSS files
- // '/assets/css/custom.css', // Custom TOC styles
+ // 'assets/css/custom.css', // Custom TOC styles
]
},
// Custom JavaScript Files
customJs: [ // Array of paths to custom JS files, loaded at end of body
- // '/assets/js/custom-script.js', // Paths relative to outputDir root
- '/assets/js/docmd-image-lightbox.js', // Image lightbox functionality
+ // 'assets/js/custom-script.js', // Paths relative to outputDir root
+ 'assets/js/docmd-image-lightbox.js', // Image lightbox functionality
],
// Content Processing
@@ -71,7 +71,7 @@ module.exports = {
// siteName: 'docmd Documentation', // Optional, defaults to config.siteTitle
// Default image for og:image if not specified in page frontmatter
// Path relative to outputDir root
- defaultImage: '/assets/images/docmd-preview.png',
+ defaultImage: 'assets/images/docmd-preview.png',
},
twitter: { // For Twitter Cards
cardType: 'summary_large_image', // 'summary', 'summary_large_image'
@@ -139,7 +139,7 @@ module.exports = {
// Favicon Configuration
// Path relative to outputDir root
- favicon: '/assets/favicon.ico',
+ favicon: 'assets/favicon.ico',
};
`;
diff --git a/src/core/html-generator.js b/src/core/html-generator.js
index aff08e1..ca7e44c 100644
--- a/src/core/html-generator.js
+++ b/src/core/html-generator.js
@@ -21,6 +21,50 @@ let themeInitScript = '';
}
})();
+// Helper to handle link rewriting based on build mode
+function fixHtmlLinks(htmlContent, relativePathToRoot, isOfflineMode) {
+ if (!htmlContent) return '';
+ const root = relativePathToRoot || './';
+
+ // Regex matches hrefs starting with /, ./, or ../
+ return htmlContent.replace(/href="((?:\/|\.\/|\.\.\/)[^"]*)"/g, (match, href) => {
+ let finalPath = href;
+
+ // 1. Convert Absolute to Relative
+ if (href.startsWith('/')) {
+ finalPath = root + href.substring(1);
+ }
+
+ // 2. Logic based on Mode
+ if (isOfflineMode) {
+ // Offline Mode: Force index.html for directories
+ const cleanPath = finalPath.split('#')[0].split('?')[0];
+ // If it has no extension (like .html, .css, .png), treat as directory
+ if (!path.extname(cleanPath)) {
+ if (finalPath.includes('#')) {
+ // Handle anchors: ./foo/#bar -> ./foo/index.html#bar
+ const parts = finalPath.split('#');
+ const prefix = parts[0].endsWith('/') ? parts[0] : parts[0] + '/';
+ finalPath = prefix + 'index.html#' + parts[1];
+ } else {
+ if (finalPath.endsWith('/')) {
+ finalPath += 'index.html';
+ } else {
+ finalPath += '/index.html';
+ }
+ }
+ }
+ } else {
+ // Online/Dev Mode: Strip index.html for clean URLs
+ if (finalPath.endsWith('/index.html')) {
+ finalPath = finalPath.substring(0, finalPath.length - 10);
+ }
+ }
+
+ return `href="${finalPath}"`;
+ });
+}
+
async function processPluginHooks(config, pageData, relativePathToRoot) {
let metaTagsHtml = '';
let faviconLinkHtml = '';
@@ -29,36 +73,52 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
let pluginHeadScriptsHtml = '';
let pluginBodyScriptsHtml = '';
+ const safeRoot = relativePathToRoot || './';
+
+ // Favicon
if (config.favicon) {
- const faviconPath = config.favicon.startsWith('/') ? config.favicon.substring(1) : config.favicon;
- faviconLinkHtml = ` \n`;
+ const cleanFaviconPath = config.favicon.startsWith('/') ? config.favicon.substring(1) : config.favicon;
+ const finalFaviconHref = `${safeRoot}${cleanFaviconPath}`;
+
+ faviconLinkHtml = ` \n`;
+ faviconLinkHtml += ` \n`;
}
+
if (config.theme && config.theme.name && config.theme.name !== 'default') {
const themeCssPath = `assets/css/docmd-theme-${config.theme.name}.css`;
- themeCssLinkHtml = ` \n`;
+ themeCssLinkHtml = ` \n`;
}
+
if (config.plugins?.seo) {
- metaTagsHtml += generateSeoMetaTags(config, pageData, relativePathToRoot);
+ metaTagsHtml += generateSeoMetaTags(config, pageData, safeRoot);
}
+
if (config.plugins?.analytics) {
const analyticsScripts = generateAnalyticsScripts(config, pageData);
pluginHeadScriptsHtml += analyticsScripts.headScriptsHtml;
pluginBodyScriptsHtml += analyticsScripts.bodyScriptsHtml;
}
+
return { metaTagsHtml, faviconLinkHtml, themeCssLinkHtml, pluginStylesHtml, pluginHeadScriptsHtml, pluginBodyScriptsHtml };
}
-// Main function used by CLI
-async function generateHtmlPage(templateData) {
- const { content, siteTitle, navigationHtml, relativePathToRoot, config, frontmatter, outputPath, prevPage, nextPage, currentPagePath, headings } = templateData;
+async function generateHtmlPage(templateData, isOfflineMode = false) {
+ let { content, siteTitle, navigationHtml, relativePathToRoot, config, frontmatter, outputPath, prevPage, nextPage, currentPagePath, headings } = templateData;
const pageTitle = frontmatter.title;
+ if (!relativePathToRoot) relativePathToRoot = './';
+
+ // Fix Content Links based on mode
+ content = fixHtmlLinks(content, relativePathToRoot, isOfflineMode);
+
const pluginOutputs = await processPluginHooks(config, { frontmatter, outputPath }, relativePathToRoot);
let footerHtml = '';
if (config.footer) {
if (!mdInstance) mdInstance = createMarkdownItInstance(config);
footerHtml = mdInstance.renderInline(config.footer);
+ // Fix Footer Links based on mode
+ footerHtml = fixHtmlLinks(footerHtml, relativePathToRoot, isOfflineMode);
}
let templateName = 'layout.ejs';
@@ -66,7 +126,6 @@ async function generateHtmlPage(templateData) {
templateName = 'no-style.ejs';
}
- // Node.js specific: Read file from disk
const layoutTemplatePath = path.join(__dirname, '..', 'templates', templateName);
if (!await fs.pathExists(layoutTemplatePath)) {
throw new Error(`Template not found: ${layoutTemplatePath}`);
@@ -74,8 +133,7 @@ async function generateHtmlPage(templateData) {
const layoutTemplate = await fs.readFile(layoutTemplatePath, 'utf8');
const isActivePage = currentPagePath && content && content.trim().length > 0;
-
- // Edit Link Logic (Simplified for brevity, keep your original logic here)
+
let editUrl = null;
let editLinkText = 'Edit this page';
if (config.editLink && config.editLink.enabled && config.editLink.baseUrl) {
@@ -93,13 +151,12 @@ async function generateHtmlPage(templateData) {
sponsor: config.sponsor, footer: config.footer, footerHtml, renderIcon,
prevPage, nextPage, currentPagePath, headings: frontmatter.toc !== false ? (headings || []) : [],
isActivePage, frontmatter, config, ...pluginOutputs,
+ isOfflineMode
};
- // Call the pure render function
return renderHtmlPage(layoutTemplate, ejsData, layoutTemplatePath);
}
-// PURE FUNCTION: Renders string -> string (Used by WASM)
function renderHtmlPage(templateContent, ejsData, filename = 'template.ejs', options = {}) {
try {
return ejs.render(templateContent, ejsData, {
@@ -112,14 +169,25 @@ function renderHtmlPage(templateContent, ejsData, filename = 'template.ejs', opt
}
}
-async function generateNavigationHtml(navItems, currentPagePath, relativePathToRoot, config) {
+// FIX: Added isOfflineMode parameter
+async function generateNavigationHtml(navItems, currentPagePath, relativePathToRoot, config, isOfflineMode = false) {
const navTemplatePath = path.join(__dirname, '..', 'templates', 'navigation.ejs');
if (!await fs.pathExists(navTemplatePath)) {
throw new Error(`Navigation template not found: ${navTemplatePath}`);
}
const navTemplate = await fs.readFile(navTemplatePath, 'utf8');
const ejsHelpers = { renderIcon };
- return ejs.render(navTemplate, { navItems, currentPagePath, relativePathToRoot, config, ...ejsHelpers }, { filename: navTemplatePath });
+
+ const safeRoot = relativePathToRoot || './';
+
+ return ejs.render(navTemplate, {
+ navItems,
+ currentPagePath,
+ relativePathToRoot: safeRoot,
+ config,
+ isOfflineMode, // <--- Passing the variable here
+ ...ejsHelpers
+ }, { filename: navTemplatePath });
}
module.exports = { generateHtmlPage, generateNavigationHtml, renderHtmlPage };
\ No newline at end of file
diff --git a/src/live/index.html b/src/live/index.html
index 459f4d1..bf1aeef 100644
--- a/src/live/index.html
+++ b/src/live/index.html
@@ -12,7 +12,7 @@