From 8c4eeb9ac624be50adb7688fa434386eaa11a7b9 Mon Sep 17 00:00:00 2001 From: canwdev <9585126+canwdev@users.noreply.github.com> Date: Wed, 22 Jul 2026 14:03:23 +0800 Subject: [PATCH] experiment: try Quartz for Obsidian vault publishing Keep docs/ as the vault source via -d docs; park this heavier stack on a side branch for comparison with MkDocs. Co-authored-by: Cursor --- .github/workflows/ci.yml | 62 +- .gitignore | 13 + .node-version | 1 + .npmrc | 1 + .prettierignore | 3 + .prettierrc | 21 + README.md | 23 +- globals.d.ts | 17 + index.d.ts | 16 + mkdocs.yml | 76 - package-lock.json | 5845 +++++++++++++ package.json | 87 + quartz.config.yaml | 304 + quartz.lock.json | 286 + quartz.ts | 5 + quartz/bootstrap-cli.mjs | 284 + quartz/bootstrap-worker.mjs | 8 + quartz/build.ts | 369 + quartz/cfg.ts | 106 + quartz/cli/args.js | 120 + quartz/cli/constants.js | 16 + quartz/cli/handlers.js | 801 ++ quartz/cli/helpers.js | 109 + quartz/cli/helpers.test.js | 237 + quartz/cli/plugin-data.js | 367 + quartz/cli/plugin-git-handlers.js | 1759 ++++ quartz/cli/templates/blog.yaml | 291 + quartz/cli/templates/default.yaml | 277 + quartz/cli/templates/obsidian.yaml | 302 + quartz/cli/templates/ttrpg.yaml | 308 + quartz/components/Body.tsx | 7 + quartz/components/ConditionalRender.tsx | 22 + quartz/components/Date.tsx | 30 + quartz/components/DesktopOnly.tsx | 18 + quartz/components/Flex.tsx | 59 + quartz/components/Head.tsx | 114 + quartz/components/Header.tsx | 22 + quartz/components/MobileOnly.tsx | 18 + quartz/components/PageList.tsx | 114 + quartz/components/Spacer.tsx | 8 + quartz/components/external.ts | 23 + quartz/components/frames/DefaultFrame.tsx | 61 + quartz/components/frames/FullWidthFrame.tsx | 51 + quartz/components/frames/MinimalFrame.tsx | 23 + quartz/components/frames/index.ts | 52 + quartz/components/frames/registry.ts | 34 + quartz/components/frames/types.ts | 43 + quartz/components/index.ts | 14 + quartz/components/pages/404.tsx | 54 + quartz/components/registry.ts | 132 + quartz/components/renderPage.test.ts | 327 + quartz/components/renderPage.tsx | 376 + quartz/components/scripts/popover.inline.ts | 141 + quartz/components/scripts/popover.test.ts | 185 + quartz/components/scripts/search.test.ts | 163 + quartz/components/scripts/spa.inline.ts | 221 + quartz/components/scripts/util.ts | 46 + quartz/components/styles/popover.scss | 89 + quartz/components/types.ts | 30 + quartz/i18n/index.ts | 90 + quartz/i18n/locales/ar-SA.ts | 93 + quartz/i18n/locales/ca-ES.ts | 87 + quartz/i18n/locales/cs-CZ.ts | 87 + quartz/i18n/locales/de-DE.ts | 87 + quartz/i18n/locales/definition.ts | 88 + quartz/i18n/locales/en-GB.ts | 87 + quartz/i18n/locales/en-US.ts | 87 + quartz/i18n/locales/es-ES.ts | 87 + quartz/i18n/locales/fa-IR.ts | 88 + quartz/i18n/locales/fi-FI.ts | 87 + quartz/i18n/locales/fr-FR.ts | 87 + quartz/i18n/locales/he-IL.ts | 88 + quartz/i18n/locales/hu-HU.ts | 85 + quartz/i18n/locales/id-ID.ts | 87 + quartz/i18n/locales/it-IT.ts | 89 + quartz/i18n/locales/ja-JP.ts | 85 + quartz/i18n/locales/kk-KZ.ts | 87 + quartz/i18n/locales/ko-KR.ts | 85 + quartz/i18n/locales/lt-LT.ts | 107 + quartz/i18n/locales/nb-NO.ts | 87 + quartz/i18n/locales/nl-NL.ts | 89 + quartz/i18n/locales/pl-PL.ts | 87 + quartz/i18n/locales/pt-BR.ts | 87 + quartz/i18n/locales/ro-RO.ts | 88 + quartz/i18n/locales/ru-RU.ts | 99 + quartz/i18n/locales/th-TH.ts | 85 + quartz/i18n/locales/tr-TR.ts | 87 + quartz/i18n/locales/uk-UA.ts | 87 + quartz/i18n/locales/vi-VN.ts | 85 + quartz/i18n/locales/zh-CN.ts | 85 + quartz/i18n/locales/zh-TW.ts | 85 + quartz/plugins/config.ts | 51 + quartz/plugins/emitters/assets.ts | 69 + quartz/plugins/emitters/componentResources.ts | 525 ++ quartz/plugins/emitters/helpers.ts | 20 + quartz/plugins/emitters/index.ts | 3 + quartz/plugins/emitters/static.ts | 23 + quartz/plugins/filters/index.ts | 1 + quartz/plugins/index.ts | 50 + quartz/plugins/loader/componentLoader.ts | 71 + quartz/plugins/loader/conditions.ts | 33 + quartz/plugins/loader/config-loader.ts | 943 ++ quartz/plugins/loader/frameLoader.ts | 47 + quartz/plugins/loader/gitLoader.ts | 1077 +++ quartz/plugins/loader/index.ts | 499 ++ quartz/plugins/loader/install-plugins.ts | 30 + quartz/plugins/loader/loader.ts | 4 + quartz/plugins/loader/types.ts | 193 + quartz/plugins/pageTypes/404.ts | 33 + quartz/plugins/pageTypes/dispatcher.ts | 339 + quartz/plugins/pageTypes/index.ts | 3 + quartz/plugins/pageTypes/matchers.ts | 39 + quartz/plugins/quartz-plugins.schema.json | 345 + quartz/plugins/transformers/index.ts | 1 + quartz/plugins/types.ts | 140 + quartz/plugins/vfile.ts | 14 + quartz/processors/emit.ts | 100 + quartz/processors/filter.ts | 24 + quartz/processors/parse.ts | 221 + quartz/static/giscus/dark.css | 99 + quartz/static/giscus/light.css | 99 + quartz/static/icon.png | Bin 0 -> 17368 bytes quartz/static/og-image.png | Bin 0 -> 46089 bytes quartz/styles/base.scss | 683 ++ quartz/styles/callouts.scss | 187 + quartz/styles/custom.scss | 3 + quartz/styles/syntax.scss | 17 + quartz/styles/variables.scss | 58 + quartz/util/clone.ts | 3 + quartz/util/ctx.ts | 65 + quartz/util/emoji.ts | 47 + quartz/util/emojimap.json | 7568 +++++++++++++++++ quartz/util/escape.ts | 1 + quartz/util/fileTrie.test.ts | 473 ++ quartz/util/fileTrie.ts | 175 + quartz/util/glob.ts | 22 + quartz/util/jsx.tsx | 38 + quartz/util/lang.ts | 1 + quartz/util/log.ts | 58 + quartz/util/path.test.ts | 394 + quartz/util/path.ts | 52 + quartz/util/perf.ts | 19 + quartz/util/random.ts | 3 + quartz/util/resources.tsx | 82 + quartz/util/slugCollisions.test.ts | 145 + quartz/util/slugCollisions.ts | 84 + quartz/util/sourcemap.ts | 18 + quartz/util/theme.ts | 351 + quartz/util/trace.ts | 43 + quartz/worker.ts | 37 + scripts/restore-html-ext.mjs | 25 + tsconfig.json | 28 + 152 files changed, 33094 insertions(+), 92 deletions(-) create mode 100644 .node-version create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 globals.d.ts create mode 100644 index.d.ts delete mode 100644 mkdocs.yml create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 quartz.config.yaml create mode 100644 quartz.lock.json create mode 100644 quartz.ts create mode 100644 quartz/bootstrap-cli.mjs create mode 100644 quartz/bootstrap-worker.mjs create mode 100644 quartz/build.ts create mode 100644 quartz/cfg.ts create mode 100644 quartz/cli/args.js create mode 100644 quartz/cli/constants.js create mode 100644 quartz/cli/handlers.js create mode 100644 quartz/cli/helpers.js create mode 100644 quartz/cli/helpers.test.js create mode 100644 quartz/cli/plugin-data.js create mode 100644 quartz/cli/plugin-git-handlers.js create mode 100644 quartz/cli/templates/blog.yaml create mode 100644 quartz/cli/templates/default.yaml create mode 100644 quartz/cli/templates/obsidian.yaml create mode 100644 quartz/cli/templates/ttrpg.yaml create mode 100644 quartz/components/Body.tsx create mode 100644 quartz/components/ConditionalRender.tsx create mode 100644 quartz/components/Date.tsx create mode 100644 quartz/components/DesktopOnly.tsx create mode 100644 quartz/components/Flex.tsx create mode 100644 quartz/components/Head.tsx create mode 100644 quartz/components/Header.tsx create mode 100644 quartz/components/MobileOnly.tsx create mode 100644 quartz/components/PageList.tsx create mode 100644 quartz/components/Spacer.tsx create mode 100644 quartz/components/external.ts create mode 100644 quartz/components/frames/DefaultFrame.tsx create mode 100644 quartz/components/frames/FullWidthFrame.tsx create mode 100644 quartz/components/frames/MinimalFrame.tsx create mode 100644 quartz/components/frames/index.ts create mode 100644 quartz/components/frames/registry.ts create mode 100644 quartz/components/frames/types.ts create mode 100644 quartz/components/index.ts create mode 100644 quartz/components/pages/404.tsx create mode 100644 quartz/components/registry.ts create mode 100644 quartz/components/renderPage.test.ts create mode 100644 quartz/components/renderPage.tsx create mode 100644 quartz/components/scripts/popover.inline.ts create mode 100644 quartz/components/scripts/popover.test.ts create mode 100644 quartz/components/scripts/search.test.ts create mode 100644 quartz/components/scripts/spa.inline.ts create mode 100644 quartz/components/scripts/util.ts create mode 100644 quartz/components/styles/popover.scss create mode 100644 quartz/components/types.ts create mode 100644 quartz/i18n/index.ts create mode 100644 quartz/i18n/locales/ar-SA.ts create mode 100644 quartz/i18n/locales/ca-ES.ts create mode 100644 quartz/i18n/locales/cs-CZ.ts create mode 100644 quartz/i18n/locales/de-DE.ts create mode 100644 quartz/i18n/locales/definition.ts create mode 100644 quartz/i18n/locales/en-GB.ts create mode 100644 quartz/i18n/locales/en-US.ts create mode 100644 quartz/i18n/locales/es-ES.ts create mode 100644 quartz/i18n/locales/fa-IR.ts create mode 100644 quartz/i18n/locales/fi-FI.ts create mode 100644 quartz/i18n/locales/fr-FR.ts create mode 100644 quartz/i18n/locales/he-IL.ts create mode 100644 quartz/i18n/locales/hu-HU.ts create mode 100644 quartz/i18n/locales/id-ID.ts create mode 100644 quartz/i18n/locales/it-IT.ts create mode 100644 quartz/i18n/locales/ja-JP.ts create mode 100644 quartz/i18n/locales/kk-KZ.ts create mode 100644 quartz/i18n/locales/ko-KR.ts create mode 100644 quartz/i18n/locales/lt-LT.ts create mode 100644 quartz/i18n/locales/nb-NO.ts create mode 100644 quartz/i18n/locales/nl-NL.ts create mode 100644 quartz/i18n/locales/pl-PL.ts create mode 100644 quartz/i18n/locales/pt-BR.ts create mode 100644 quartz/i18n/locales/ro-RO.ts create mode 100644 quartz/i18n/locales/ru-RU.ts create mode 100644 quartz/i18n/locales/th-TH.ts create mode 100644 quartz/i18n/locales/tr-TR.ts create mode 100644 quartz/i18n/locales/uk-UA.ts create mode 100644 quartz/i18n/locales/vi-VN.ts create mode 100644 quartz/i18n/locales/zh-CN.ts create mode 100644 quartz/i18n/locales/zh-TW.ts create mode 100644 quartz/plugins/config.ts create mode 100644 quartz/plugins/emitters/assets.ts create mode 100644 quartz/plugins/emitters/componentResources.ts create mode 100644 quartz/plugins/emitters/helpers.ts create mode 100644 quartz/plugins/emitters/index.ts create mode 100644 quartz/plugins/emitters/static.ts create mode 100644 quartz/plugins/filters/index.ts create mode 100644 quartz/plugins/index.ts create mode 100644 quartz/plugins/loader/componentLoader.ts create mode 100644 quartz/plugins/loader/conditions.ts create mode 100644 quartz/plugins/loader/config-loader.ts create mode 100644 quartz/plugins/loader/frameLoader.ts create mode 100644 quartz/plugins/loader/gitLoader.ts create mode 100644 quartz/plugins/loader/index.ts create mode 100644 quartz/plugins/loader/install-plugins.ts create mode 100644 quartz/plugins/loader/loader.ts create mode 100644 quartz/plugins/loader/types.ts create mode 100644 quartz/plugins/pageTypes/404.ts create mode 100644 quartz/plugins/pageTypes/dispatcher.ts create mode 100644 quartz/plugins/pageTypes/index.ts create mode 100644 quartz/plugins/pageTypes/matchers.ts create mode 100644 quartz/plugins/quartz-plugins.schema.json create mode 100644 quartz/plugins/transformers/index.ts create mode 100644 quartz/plugins/types.ts create mode 100644 quartz/plugins/vfile.ts create mode 100644 quartz/processors/emit.ts create mode 100644 quartz/processors/filter.ts create mode 100644 quartz/processors/parse.ts create mode 100644 quartz/static/giscus/dark.css create mode 100644 quartz/static/giscus/light.css create mode 100644 quartz/static/icon.png create mode 100644 quartz/static/og-image.png create mode 100644 quartz/styles/base.scss create mode 100644 quartz/styles/callouts.scss create mode 100644 quartz/styles/custom.scss create mode 100644 quartz/styles/syntax.scss create mode 100644 quartz/styles/variables.scss create mode 100644 quartz/util/clone.ts create mode 100644 quartz/util/ctx.ts create mode 100644 quartz/util/emoji.ts create mode 100644 quartz/util/emojimap.json create mode 100644 quartz/util/escape.ts create mode 100644 quartz/util/fileTrie.test.ts create mode 100644 quartz/util/fileTrie.ts create mode 100644 quartz/util/glob.ts create mode 100644 quartz/util/jsx.tsx create mode 100644 quartz/util/lang.ts create mode 100644 quartz/util/log.ts create mode 100644 quartz/util/path.test.ts create mode 100644 quartz/util/path.ts create mode 100644 quartz/util/perf.ts create mode 100644 quartz/util/random.ts create mode 100644 quartz/util/resources.tsx create mode 100644 quartz/util/slugCollisions.test.ts create mode 100644 quartz/util/slugCollisions.ts create mode 100644 quartz/util/sourcemap.ts create mode 100644 quartz/util/theme.ts create mode 100644 quartz/util/trace.ts create mode 100644 quartz/worker.ts create mode 100644 scripts/restore-html-ext.mjs create mode 100644 tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50e44f8da..fe63c75aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,17 +1,65 @@ name: ci + on: push: branches: - master - main + - dev + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + jobs: - deploy: + build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Cache Quartz plugins + uses: actions/cache@v4 + with: + path: .quartz/plugins + key: ${{ runner.os }}-plugins-${{ hashFiles('quartz.lock.json') }} + restore-keys: | + ${{ runner.os }}-plugins- + + - name: Install dependencies + run: npm ci + + - name: Install Quartz plugins + run: npx quartz plugin install --from-config + + - name: Build Quartz + run: npm run build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 with: - python-version: 3.x - - run: pip install mkdocs-material - - run: pip install mkdocs-roamlinks-plugin - - run: mkdocs gh-deploy --force + path: public + + deploy: + needs: build + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 3b584a174..cf27e825d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,16 @@ docs/.obsidian/workspace.json *.drawio.bkp docs/.obsidian/plugins/novel-word-count/data.json + +# Quartz +.DS_Store +node_modules +public +prof +tsconfig.tsbuildinfo +.quartz-cache +private/ +.replit +replit.nix +.quartz/ +quartz/cli/tui/dist/ diff --git a/.node-version b/.node-version new file mode 100644 index 000000000..aebd91c52 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +v22.16.0 diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..b6f27f135 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..3c0687a52 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +public +node_modules +.quartz-cache diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..3fdeccd6f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,21 @@ +{ + "printWidth": 100, + "quoteProps": "as-needed", + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "overrides": [ + { + "files": "*.canvas", + "options": { + "parser": "json" + } + }, + { + "files": "*.base", + "options": { + "parser": "yaml" + } + } + ] +} diff --git a/README.md b/README.md index 78c7129ed..795084354 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,25 @@ ## 个人博客 -本仓库使用了mkdocs模板,主要的 Obsidian 文档在 `docs` 目录内。 +本仓库使用 [Quartz](https://quartz.jzhao.xyz/) 将 Obsidian 笔记发布为静态站点。 -本地运行需要 python3 环境 +- 笔记目录:`docs/`(Obsidian Vault,保持原样) +- 构建时通过 `-d docs` 读取该目录,无需复制或改动笔记 + +本地运行需要 **Node.js 22+** ```shell # 安装依赖 -pip install mkdocs -pip install mkdocs-material -pip install mkdocs-roamlinks-plugin +npm ci +npx quartz plugin install --from-config + +# 本地预览 +npm run dev -# 运行 -mkdocs serve +# 构建静态站点(输出到 public/) +npm run build ``` ## 参考 -- https://squidfunk.github.io/mkdocs-material/setup/ -- https://sspai.com/prime/story/mkdocs-primer \ No newline at end of file +- https://quartz.jzhao.xyz/ +- https://quartz.jzhao.xyz/features/obsidian-compatibility diff --git a/globals.d.ts b/globals.d.ts new file mode 100644 index 000000000..6cf30f8a1 --- /dev/null +++ b/globals.d.ts @@ -0,0 +1,17 @@ +export declare global { + interface Document { + addEventListener( + type: K, + listener: (this: Document, ev: CustomEventMap[K]) => void, + ): void + removeEventListener( + type: K, + listener: (this: Document, ev: CustomEventMap[K]) => void, + ): void + dispatchEvent(ev: CustomEventMap[K] | UIEvent): void + } + interface Window { + spaNavigate(url: URL, isBack: boolean = false) + addCleanup(fn: (...args: any[]) => void) + } +} diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 000000000..1dc0e2f82 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,16 @@ +declare module "*.scss" { + const content: string + export = content +} + +// dom custom event +interface CustomEventMap { + prenav: CustomEvent<{}> + nav: CustomEvent<{ url: FullSlug }> + themechange: CustomEvent<{ theme: "light" | "dark" }> + readermodechange: CustomEvent<{ mode: "on" | "off" }> + render: CustomEvent<{}> +} + +type ContentIndex = Record +declare const fetchData: Promise diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index f638dbf15..000000000 --- a/mkdocs.yml +++ /dev/null @@ -1,76 +0,0 @@ -site_name: Canwdev's Tech Blog - Obsidian -repo_url: https://github.com/canwdev/canwdev.github.io - -theme: - name: 'material' - language: zh - palette: - # Palette toggle for automatic mode - - media: "(prefers-color-scheme)" - toggle: - icon: material/brightness-auto - name: Switch to light mode - - # Palette toggle for light mode - - media: "(prefers-color-scheme: light)" - scheme: default - primary: deep purple - accent: pink - toggle: - icon: material/brightness-7 - name: Switch to dark mode - - # Palette toggle for dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: indigo - accent: blue - toggle: - icon: material/brightness-4 - name: Switch to system preference - -markdown_extensions: - - # Python Markdown - - abbr - - admonition - - attr_list - - def_list - - footnotes - - md_in_html - - toc: - permalink: true - - # Python Markdown Extensions - - pymdownx.arithmatex: - generic: true - - pymdownx.betterem: - smart_enable: all - - pymdownx.caret - - pymdownx.details - - pymdownx.emoji: - emoji_index: !!python/name:materialx.emoji.twemoji - emoji_generator: !!python/name:materialx.emoji.to_svg - - pymdownx.highlight: - anchor_linenums: true - auto_title: true - linenums: true - - pymdownx.inlinehilite - - pymdownx.keys - - pymdownx.mark - - pymdownx.smartsymbols - - pymdownx.superfences - - pymdownx.tabbed: - alternate_style: true - - pymdownx.tasklist: - custom_checkbox: true - - pymdownx.tilde - -plugins: - - search - - roamlinks - -extra: - social: - - icon: fontawesome/brands/github - link: https://github.com/canwdev/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..c67ea31da --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5845 @@ +{ + "name": "@jackyzha0/quartz", + "version": "5.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@jackyzha0/quartz", + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "@clack/prompts": "^0.11.0", + "@floating-ui/dom": "^1.7.4", + "@myriaddreamin/rehype-typst": "^0.6.0", + "@napi-rs/simple-git": "0.1.22", + "ansi-truncate": "^1.4.0", + "async-mutex": "^0.5.0", + "chokidar": "^5.0.0", + "esbuild-sass-plugin": "^3.6.0", + "github-slugger": "^2.0.0", + "globby": "^16.1.0", + "hast-util-to-jsx-runtime": "^2.3.6", + "isomorphic-git": "^1.36.3", + "lightningcss": "^1.31.1", + "micromorph": "^0.4.5", + "minimatch": "^10.1.1", + "preact": "^10.28.2", + "preact-render-to-string": "^6.6.5", + "pretty-bytes": "^7.1.0", + "pretty-time": "^1.1.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "rfdc": "^1.4.1", + "serve-handler": "^6.1.6", + "sharp": "^0.34.5", + "source-map-support": "^0.5.21", + "to-vfile": "^8.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3", + "workerpool": "^10.0.1", + "ws": "^8.19.0", + "yaml": "^2.8.2", + "yargs": "^18.0.0" + }, + "bin": { + "quartz": "quartz/bootstrap-cli.mjs" + }, + "devDependencies": { + "@quartz-community/types": "github:quartz-community/types", + "@quartz-community/utils": "github:quartz-community/utils", + "@types/hast": "^3.0.4", + "@types/node": "^25.0.10", + "@types/pretty-time": "^1.1.5", + "@types/source-map-support": "^0.5.10", + "@types/ws": "^8.18.1", + "@types/yargs": "^17.0.35", + "esbuild": "^0.27.2", + "prettier": "^3.8.1", + "tsx": "^4.21.0", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=22", + "npm": ">=10.9.2" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", + "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)", + "peer": true + }, + "node_modules/@clack/core": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz", + "integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz", + "integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==", + "license": "MIT", + "dependencies": { + "@clack/core": "0.5.0", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@myriaddreamin/rehype-typst": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/rehype-typst/-/rehype-typst-0.6.0.tgz", + "integrity": "sha512-WQpr2j7OYtyc2Q1WOqi1wzYrBaeuAWT1Cn1Ki6VPsKoWH7O86/+zKOqltdgMpYdkav1uXYs3RfO5Ir8h0WkZyQ==", + "license": "MIT", + "dependencies": { + "@myriaddreamin/typst-ts-node-compiler": "^0.6.0", + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "https-proxy-agent": "^7.0.2", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler/-/typst-ts-node-compiler-0.6.0.tgz", + "integrity": "sha512-C40MzRKZ8pDWzrS7VOtTypGyFaHTuZFFx3o/uQ6ryS2GqZkK3vGox4lIpR7ct11UHiAjQNR3LFQ5WjQ7P3niBQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@myriaddreamin/typst-ts-node-compiler-android-arm-eabi": "0.6.0", + "@myriaddreamin/typst-ts-node-compiler-android-arm64": "0.6.0", + "@myriaddreamin/typst-ts-node-compiler-darwin-arm64": "0.6.0", + "@myriaddreamin/typst-ts-node-compiler-darwin-x64": "0.6.0", + "@myriaddreamin/typst-ts-node-compiler-linux-arm-gnueabihf": "0.6.0", + "@myriaddreamin/typst-ts-node-compiler-linux-arm64-gnu": "0.6.0", + "@myriaddreamin/typst-ts-node-compiler-linux-arm64-musl": "0.6.0", + "@myriaddreamin/typst-ts-node-compiler-linux-x64-gnu": "0.6.0", + "@myriaddreamin/typst-ts-node-compiler-linux-x64-musl": "0.6.0", + "@myriaddreamin/typst-ts-node-compiler-win32-arm64-msvc": "0.6.0", + "@myriaddreamin/typst-ts-node-compiler-win32-x64-msvc": "0.6.0" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler-android-arm-eabi": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler-android-arm-eabi/-/typst-ts-node-compiler-android-arm-eabi-0.6.0.tgz", + "integrity": "sha512-Gfrf9Fky5iYtutGWYwqRC4gvllK1p1q6YELCbycI47NCFptONI++3dfub4PixWRn9m8NrmaNFIBQSyLHWsvbLw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler-android-arm64": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler-android-arm64/-/typst-ts-node-compiler-android-arm64-0.6.0.tgz", + "integrity": "sha512-EzO6W4xELC6at30hSkkOp5BveszwCmTWceu0PMh6lPxeQF1vnjxUK60MLFfJ40zb1TOXsj4l2pbdBoGqLznC1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler-darwin-arm64": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler-darwin-arm64/-/typst-ts-node-compiler-darwin-arm64-0.6.0.tgz", + "integrity": "sha512-8tR1GqFr+q4rNZm8z0230eF7eRCVCSaUefDw1+Qw8EnDPIvwEP8bT0/u2YqHmxthfVfs1msV8hDpRKVeBa6E3g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler-darwin-x64": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler-darwin-x64/-/typst-ts-node-compiler-darwin-x64-0.6.0.tgz", + "integrity": "sha512-eytv5ifNvhux9naqEb+4pu1Z4ghQBWiybP4lT/aB44I9H5xjmtYQxiKwNBz54am6RLiMcyLpw/xFdeB13bsdWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler-linux-arm-gnueabihf": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler-linux-arm-gnueabihf/-/typst-ts-node-compiler-linux-arm-gnueabihf-0.6.0.tgz", + "integrity": "sha512-b20do+PmbsYq07QlTW8uLU3MaoAm6DSCx1IrCEAlUpNH+/29x51Rvyq5JeRrYVOtkR6BxPzyhCM79r5jOkewbQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler-linux-arm64-gnu": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler-linux-arm64-gnu/-/typst-ts-node-compiler-linux-arm64-gnu-0.6.0.tgz", + "integrity": "sha512-AM92MVfEbISYvIA8NwPl2l78nOZIh5er5qQ/NZw2kx4YgTKgklJINEPHXm/aAk7PcpX7G10P45D/xGd5KpX9HQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler-linux-arm64-musl": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler-linux-arm64-musl/-/typst-ts-node-compiler-linux-arm64-musl-0.6.0.tgz", + "integrity": "sha512-nSokVjKQR0ZH7Jub53q7he89+m72RSbL97exSedkB4OdZAi9tAxGFIgceGJuN5AC+DiNtMmqsPwlJiERUjgPhQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler-linux-x64-gnu": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler-linux-x64-gnu/-/typst-ts-node-compiler-linux-x64-gnu-0.6.0.tgz", + "integrity": "sha512-3Y2ORiYuCTzQkiHSCHWiGuzTBbNvHTB2lCr3DDsZdvTZ2LZMifPwwICN26X3tlnt6GyC3o/ejZBcMnfNqYbdCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler-linux-x64-musl": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler-linux-x64-musl/-/typst-ts-node-compiler-linux-x64-musl-0.6.0.tgz", + "integrity": "sha512-b+kTb4vI0sFTkPtIAUE+UqjhZ4kTiAkh4F/2QKnFitAsURlLcRwTcMc9NJm6SXwW1OM0nPj1IGTfUOFpqLOIPQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler-win32-arm64-msvc": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler-win32-arm64-msvc/-/typst-ts-node-compiler-win32-arm64-msvc-0.6.0.tgz", + "integrity": "sha512-04omIPrXSsRKu4XDhj1WZ9uMjdcFcejBGzyOEV351HVDqg5kxgDB32iG3oLySLrzEcbi9WwI5Si46WrW0wh4mA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@myriaddreamin/typst-ts-node-compiler-win32-x64-msvc": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-node-compiler-win32-x64-msvc/-/typst-ts-node-compiler-win32-x64-msvc-0.6.0.tgz", + "integrity": "sha512-w5UEmXSZ+Eg7Y04EzjgqeHUo7P8bNz9S1c4CUfLrbfZvbTmYNjA0WeqZJ3+tV03BSVxiPiVhrfo95sLqKISNrg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git/-/simple-git-0.1.22.tgz", + "integrity": "sha512-bMVoAKhpjTOPHkW/lprDPwv5aD4R4C3Irt8vn+SKA9wudLe9COLxOhurrKRsxmZccUbWXRF7vukNeGUAj5P8kA==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/simple-git-android-arm-eabi": "0.1.22", + "@napi-rs/simple-git-android-arm64": "0.1.22", + "@napi-rs/simple-git-darwin-arm64": "0.1.22", + "@napi-rs/simple-git-darwin-x64": "0.1.22", + "@napi-rs/simple-git-freebsd-x64": "0.1.22", + "@napi-rs/simple-git-linux-arm-gnueabihf": "0.1.22", + "@napi-rs/simple-git-linux-arm64-gnu": "0.1.22", + "@napi-rs/simple-git-linux-arm64-musl": "0.1.22", + "@napi-rs/simple-git-linux-ppc64-gnu": "0.1.22", + "@napi-rs/simple-git-linux-s390x-gnu": "0.1.22", + "@napi-rs/simple-git-linux-x64-gnu": "0.1.22", + "@napi-rs/simple-git-linux-x64-musl": "0.1.22", + "@napi-rs/simple-git-win32-arm64-msvc": "0.1.22", + "@napi-rs/simple-git-win32-ia32-msvc": "0.1.22", + "@napi-rs/simple-git-win32-x64-msvc": "0.1.22" + } + }, + "node_modules/@napi-rs/simple-git-android-arm-eabi": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm-eabi/-/simple-git-android-arm-eabi-0.1.22.tgz", + "integrity": "sha512-JQZdnDNm8o43A5GOzwN/0Tz3CDBQtBUNqzVwEopm32uayjdjxev1Csp1JeaqF3v9djLDIvsSE39ecsN2LhCKKQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-android-arm64": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm64/-/simple-git-android-arm64-0.1.22.tgz", + "integrity": "sha512-46OZ0SkhnvM+fapWjzg/eqbJvClxynUpWYyYBn4jAj7GQs1/Yyc8431spzDmkA8mL0M7Xo8SmbkzTDE7WwYAfg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-darwin-arm64": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-arm64/-/simple-git-darwin-arm64-0.1.22.tgz", + "integrity": "sha512-zH3h0C8Mkn9//MajPI6kHnttywjsBmZ37fhLX/Fiw5XKu84eHA6dRyVtMzoZxj6s+bjNTgaMgMUucxPn9ktxTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-darwin-x64": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-x64/-/simple-git-darwin-x64-0.1.22.tgz", + "integrity": "sha512-GZN7lRAkGKB6PJxWsoyeYJhh85oOOjVNyl+/uipNX8bR+mFDCqRsCE3rRCFGV9WrZUHXkcuRL2laIRn7lLi3ag==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-freebsd-x64": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-freebsd-x64/-/simple-git-freebsd-x64-0.1.22.tgz", + "integrity": "sha512-xyqX1C5I0WBrUgZONxHjZH5a4LqQ9oki3SKFAVpercVYAcx3pq6BkZy1YUOP4qx78WxU1CCNfHBN7V+XO7D99A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-arm-gnueabihf": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm-gnueabihf/-/simple-git-linux-arm-gnueabihf-0.1.22.tgz", + "integrity": "sha512-4LOtbp9ll93B9fxRvXiUJd1/RM3uafMJE7dGBZGKWBMGM76+BAcCEUv2BY85EfsU/IgopXI6n09TycRfPWOjxA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-arm64-gnu": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-gnu/-/simple-git-linux-arm64-gnu-0.1.22.tgz", + "integrity": "sha512-GVOjP/JjCzbQ0kSqao7ctC/1sodVtv5VF57rW9BFpo2y6tEYPCqHnkQkTpieuwMNe+TVOhBUC1+wH0d9/knIHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-arm64-musl": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-musl/-/simple-git-linux-arm64-musl-0.1.22.tgz", + "integrity": "sha512-MOs7fPyJiU/wqOpKzAOmOpxJ/TZfP4JwmvPad/cXTOWYwwyppMlXFRms3i98EU3HOazI/wMU2Ksfda3+TBluWA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-ppc64-gnu": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-ppc64-gnu/-/simple-git-linux-ppc64-gnu-0.1.22.tgz", + "integrity": "sha512-L59dR30VBShRUIZ5/cQHU25upNgKS0AMQ7537J6LCIUEFwwXrKORZKJ8ceR+s3Sr/4jempWVvMdjEpFDE4HYww==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-s390x-gnu": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-s390x-gnu/-/simple-git-linux-s390x-gnu-0.1.22.tgz", + "integrity": "sha512-4FHkPlCSIZUGC6HiADffbe6NVoTBMd65pIwcd40IDbtFKOgFMBA+pWRqKiQ21FERGH16Zed7XHJJoY3jpOqtmQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-x64-gnu": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-gnu/-/simple-git-linux-x64-gnu-0.1.22.tgz", + "integrity": "sha512-Ei1tM5Ho/dwknF3pOzqkNW9Iv8oFzRxE8uOhrITcdlpxRxVrBVptUF6/0WPdvd7R9747D/q61QG/AVyWsWLFKw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-x64-musl": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-musl/-/simple-git-linux-x64-musl-0.1.22.tgz", + "integrity": "sha512-zRYxg7it0p3rLyEJYoCoL2PQJNgArVLyNavHW03TFUAYkYi5bxQ/UFNVpgxMaXohr5yu7qCBqeo9j4DWeysalg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-win32-arm64-msvc": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-arm64-msvc/-/simple-git-win32-arm64-msvc-0.1.22.tgz", + "integrity": "sha512-XGFR1fj+Y9cWACcovV2Ey/R2xQOZKs8t+7KHPerYdJ4PtjVzGznI4c2EBHXtdOIYvkw7tL5rZ7FN1HJKdD5Quw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-win32-ia32-msvc": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-ia32-msvc/-/simple-git-win32-ia32-msvc-0.1.22.tgz", + "integrity": "sha512-Gqr9Y0gs6hcNBA1IXBpoqTFnnIoHuZGhrYqaZzEvGMLrTrpbXrXVEtX3DAAD2RLc1b87CPcJ49a7sre3PU3Rfw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-win32-x64-msvc": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-x64-msvc/-/simple-git-win32-x64-msvc-0.1.22.tgz", + "integrity": "sha512-hQjcreHmUcpw4UrtkOron1/TQObfe484lxiXFLLUj7aWnnnOVs1mnXq5/Bo9+3NYZldFpFRJPdPBeHCisXkKJg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@quartz-community/types": { + "version": "0.2.1", + "resolved": "git+ssh://git@github.com/quartz-community/types.git#d413569b02ff0cd9a7e8d74dee125d20a5e5ec72", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22", + "npm": ">=10.9.2" + }, + "peerDependencies": { + "unified": "^11.0.5", + "vfile": "^6.0.3" + }, + "peerDependenciesMeta": { + "unified": { + "optional": true + }, + "vfile": { + "optional": true + } + } + }, + "node_modules/@quartz-community/utils": { + "version": "0.1.0", + "resolved": "git+ssh://git@github.com/quartz-community/utils.git#e09d8b0bbccb46073b23c00affaf3e40a2c3b0c4", + "dev": true, + "license": "MIT", + "dependencies": { + "@quartz-community/types": "github:quartz-community/types" + }, + "engines": { + "node": ">=22", + "npm": ">=10.9.2" + }, + "peerDependencies": { + "github-slugger": "^2.0.0", + "hast-util-to-jsx-runtime": "^2.3.6", + "preact": "^10.0.0" + }, + "peerDependenciesMeta": { + "github-slugger": { + "optional": true + }, + "hast-util-to-jsx-runtime": { + "optional": true + } + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/pretty-time": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/pretty-time/-/pretty-time-1.1.5.tgz", + "integrity": "sha512-5yl+BYwmnRWZb783W8YYoHXvPY8q/rp7ctHBVaGBB9RxlzGpHNJ72tGQMK7TrUSnxzl1dbDcBDuBCSbtfnSQGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-tgVP2H469x9zq34Z0m/fgPewGhg/MLClalNOiPIzQlXrSS2YrKu/xCdSCKnEDwkFha51VKEKB6A9wW26/ZNwzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "^0.6.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansi-truncate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-truncate/-/ansi-truncate-1.4.0.tgz", + "integrity": "sha512-p6d2MrNs/mbpdXFT08fGabIg4pbgnUbbhrsoFfxWV5L3zFKw7tUkYUxGY3xCGJUPohENM80Q4sWkl/VDEN3pZg==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.1" + } + }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "license": "MIT" + }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "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/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clean-git-ref": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", + "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==", + "license": "Apache-2.0" + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "license": "MIT", + "peer": true + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff3": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", + "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/esbuild-sass-plugin": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-3.6.0.tgz", + "integrity": "sha512-lzPJQSEXcnj5amBPPib5lBjsDNPzvdMnX+1Rf7eha9BIpLSM5Ad2pi+Rqg5CAlWMduCgLntS2hLAqG7v1fxWGw==", + "license": "MIT", + "dependencies": { + "resolve": "^1.22.11", + "sass": "^1.97.2" + }, + "peerDependencies": { + "esbuild": ">=0.27.2", + "sass-embedded": "^1.97.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.1.tgz", + "integrity": "sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "is-path-inside": "^4.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "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": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isomorphic-git": { + "version": "1.37.2", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.37.2.tgz", + "integrity": "sha512-HCQBBKmXIMPdHgYGstSBNp6MNmVcMQBbUqJF8xfywFmlpNseO4KKex59YlXqNxhRxmv3fUZwvNWvMyOdc1VvhA==", + "license": "MIT", + "dependencies": { + "async-lock": "^1.4.1", + "clean-git-ref": "^2.0.1", + "crc-32": "^1.2.0", + "diff3": "0.0.3", + "ignore": "^5.1.4", + "minimisted": "^2.0.0", + "pako": "^1.0.10", + "pify": "^4.0.1", + "readable-stream": "^4.0.0", + "sha.js": "^2.4.12", + "simple-get": "^4.0.1" + }, + "bin": { + "isogit": "cli.cjs" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/isomorphic-git/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromorph": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/micromorph/-/micromorph-0.4.5.tgz", + "integrity": "sha512-Erasr0xiDvDeEhh7B/k7RFTwwfaAX10D7BMorNpokkwDh6XsRLYWDPaWF1m5JQeMSkGdqlEtQ8s68NcdDWuGgw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimisted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz", + "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "license": "(WTFPL OR MIT)" + }, + "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==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/preact": { + "version": "10.28.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.4.tgz", + "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.6.6.tgz", + "integrity": "sha512-EfqZJytnjJldV+YaaqhthU2oXsEf5e+6rDv957p+zxAvNfFLQOPfvBOTncscQ+akzu6Wrl7s3Pa0LjUQmWJsGQ==", + "license": "MIT", + "peerDependencies": { + "preact": ">=10 || >= 11.0.0-0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-bytes": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-7.1.0.tgz", + "integrity": "sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "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-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "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": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/sass": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.97.3.tgz", + "integrity": "sha512-eKzFy13Nk+IRHhlAwP3sfuv+PzOrvzUkwJK2hdoCKYcWGSdmwFpeGpWmyewdw8EgBnsKaSBtgf/0b2K635ecSA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@bufbuild/protobuf": "^2.5.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-all-unknown": "1.97.3", + "sass-embedded-android-arm": "1.97.3", + "sass-embedded-android-arm64": "1.97.3", + "sass-embedded-android-riscv64": "1.97.3", + "sass-embedded-android-x64": "1.97.3", + "sass-embedded-darwin-arm64": "1.97.3", + "sass-embedded-darwin-x64": "1.97.3", + "sass-embedded-linux-arm": "1.97.3", + "sass-embedded-linux-arm64": "1.97.3", + "sass-embedded-linux-musl-arm": "1.97.3", + "sass-embedded-linux-musl-arm64": "1.97.3", + "sass-embedded-linux-musl-riscv64": "1.97.3", + "sass-embedded-linux-musl-x64": "1.97.3", + "sass-embedded-linux-riscv64": "1.97.3", + "sass-embedded-linux-x64": "1.97.3", + "sass-embedded-unknown-all": "1.97.3", + "sass-embedded-win32-arm64": "1.97.3", + "sass-embedded-win32-x64": "1.97.3" + } + }, + "node_modules/sass-embedded-all-unknown": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.97.3.tgz", + "integrity": "sha512-t6N46NlPuXiY3rlmG6/+1nwebOBOaLFOOVqNQOC2cJhghOD4hh2kHNQQTorCsbY9S1Kir2la1/XLBwOJfui0xg==", + "cpu": [ + "!arm", + "!arm64", + "!riscv64", + "!x64" + ], + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "sass": "1.97.3" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.97.3.tgz", + "integrity": "sha512-cRTtf/KV/q0nzGZoUzVkeIVVFv3L/tS1w4WnlHapphsjTXF/duTxI8JOU1c/9GhRPiMdfeXH7vYNcMmtjwX7jg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.97.3.tgz", + "integrity": "sha512-aiZ6iqiHsUsaDx0EFbbmmA0QgxicSxVVN3lnJJ0f1RStY0DthUkquGT5RJ4TPdaZ6ebeJWkboV4bra+CP766eA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.97.3.tgz", + "integrity": "sha512-zVEDgl9JJodofGHobaM/q6pNETG69uuBIGQHRo789jloESxxZe82lI3AWJQuPmYCOG5ElfRthqgv89h3gTeLYA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.97.3.tgz", + "integrity": "sha512-3ke0le7ZKepyXn/dKKspYkpBC0zUk/BMciyP5ajQUDy4qJwobd8zXdAq6kOkdiMB+d9UFJOmEkvgFJHl3lqwcw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.97.3.tgz", + "integrity": "sha512-fuqMTqO4gbOmA/kC5b9y9xxNYw6zDEyfOtMgabS7Mz93wimSk2M1quQaTJnL98Mkcsl2j+7shNHxIS/qpcIDDA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.97.3.tgz", + "integrity": "sha512-b/2RBs/2bZpP8lMkyZ0Px0vkVkT8uBd0YXpOwK7iOwYkAT8SsO4+WdVwErsqC65vI5e1e5p1bb20tuwsoQBMVA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.97.3.tgz", + "integrity": "sha512-2lPQ7HQQg4CKsH18FTsj2hbw5GJa6sBQgDsls+cV7buXlHjqF8iTKhAQViT6nrpLK/e8nFCoaRgSqEC8xMnXuA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.97.3.tgz", + "integrity": "sha512-IP1+2otCT3DuV46ooxPaOKV1oL5rLjteRzf8ldZtfIEcwhSgSsHgA71CbjYgLEwMY9h4jeal8Jfv3QnedPvSjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.97.3.tgz", + "integrity": "sha512-cBTMU68X2opBpoYsSZnI321gnoaiMBEtc+60CKCclN6PCL3W3uXm8g4TLoil1hDD6mqU9YYNlVG6sJ+ZNef6Lg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.97.3.tgz", + "integrity": "sha512-Lij0SdZCsr+mNRSyDZ7XtJpXEITrYsaGbOTz5e6uFLJ9bmzUbV7M8BXz2/cA7bhfpRPT7/lwRKPdV4+aR9Ozcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.97.3.tgz", + "integrity": "sha512-sBeLFIzMGshR4WmHAD4oIM7WJVkSoCIEwutzptFtGlSlwfNiijULp+J5hA2KteGvI6Gji35apR5aWj66wEn/iA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.97.3.tgz", + "integrity": "sha512-/oWJ+OVrDg7ADDQxRLC/4g1+Nsz1g4mkYS2t6XmyMJKFTFK50FVI2t5sOdFH+zmMp+nXHKM036W94y9m4jjEcw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.97.3.tgz", + "integrity": "sha512-l3IfySApLVYdNx0Kjm7Zehte1CDPZVcldma3dZt+TfzvlAEerM6YDgsk5XEj3L8eHBCgHgF4A0MJspHEo2WNfA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.97.3.tgz", + "integrity": "sha512-Kwqwc/jSSlcpRjULAOVbndqEy2GBzo6OBmmuBVINWUaJLJ8Kczz3vIsDUWLfWz/kTEw9FHBSiL0WCtYLVAXSLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-unknown-all": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.97.3.tgz", + "integrity": "sha512-/GHajyYJmvb0IABUQHbVHf1nuHPtIDo/ClMZ81IDr59wT5CNcMe7/dMNujXwWugtQVGI5UGmqXWZQCeoGnct8Q==", + "license": "MIT", + "optional": true, + "os": [ + "!android", + "!darwin", + "!linux", + "!win32" + ], + "peer": true, + "dependencies": { + "sass": "1.97.3" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.97.3.tgz", + "integrity": "sha512-RDGtRS1GVvQfMGAmVXNxYiUOvPzn9oO1zYB/XUM9fudDRnieYTcUytpNTQZLs6Y1KfJxgt5Y+giRceC92fT8Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.97.3.tgz", + "integrity": "sha512-SFRa2lED9UEwV6vIGeBXeBOLKF+rowF3WmNfb/BzhxmdAsKofCXrJ8ePW7OcDVrvNEbTOGwhsReIsF5sH8fVaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serve-handler": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/serve-handler/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/serve-handler/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "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/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "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": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.2.0.tgz", + "integrity": "sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-vfile": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-8.0.0.tgz", + "integrity": "sha512-IcmH1xB5576MJc9qcfEC/m/nQCFt3fzMHz45sSlgJyTWjRbKW1HAkJpuf3DgE57YzIlZcwcBZA5ENQbBo4aLkg==", + "license": "MIT", + "dependencies": { + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "license": "MIT", + "peer": true + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/workerpool": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-10.0.1.tgz", + "integrity": "sha512-NAnKwZJxWlj/U1cp6ZkEtPE+GQY1S6KtOS3AlCiPfPFLxV3m64giSp7g2LsNJxzYCocDT7TSl+7T0sgrDp3KoQ==", + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..d59980fd0 --- /dev/null +++ b/package.json @@ -0,0 +1,87 @@ +{ + "name": "canwdev.github.io", + "description": "Canwdev's Tech Blog — Obsidian vault published with Quartz", + "private": true, + "version": "5.0.0", + "type": "module", + "license": "MIT", + "homepage": "https://canwdev.github.io", + "repository": { + "type": "git", + "url": "https://github.com/canwdev/canwdev.github.io.git" + }, + "scripts": { + "quartz": "./quartz/bootstrap-cli.mjs", + "dev": "npx quartz build --serve -d docs", + "build": "npx quartz build -d docs && node scripts/restore-html-ext.mjs", + "check": "tsc --noEmit && npx prettier . --check", + "format": "npx prettier . --write", + "test": "tsx --test", + "profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1", + "install-plugins": "npx quartz plugin install --from-config" + }, + "engines": { + "npm": ">=10.9.2", + "node": ">=22" + }, + "keywords": [ + "site generator", + "ssg", + "digital-garden", + "markdown", + "blog", + "quartz" + ], + "bin": { + "quartz": "./quartz/bootstrap-cli.mjs" + }, + "dependencies": { + "@clack/prompts": "^0.11.0", + "@floating-ui/dom": "^1.7.4", + "@myriaddreamin/rehype-typst": "^0.6.0", + "@napi-rs/simple-git": "0.1.22", + "ansi-truncate": "^1.4.0", + "async-mutex": "^0.5.0", + "chokidar": "^5.0.0", + "esbuild-sass-plugin": "^3.6.0", + "github-slugger": "^2.0.0", + "globby": "^16.1.0", + "hast-util-to-jsx-runtime": "^2.3.6", + "isomorphic-git": "^1.36.3", + "lightningcss": "^1.31.1", + "micromorph": "^0.4.5", + "minimatch": "^10.1.1", + "preact": "^10.28.2", + "preact-render-to-string": "^6.6.5", + "pretty-bytes": "^7.1.0", + "pretty-time": "^1.1.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "rfdc": "^1.4.1", + "serve-handler": "^6.1.6", + "sharp": "^0.34.5", + "source-map-support": "^0.5.21", + "to-vfile": "^8.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3", + "workerpool": "^10.0.1", + "ws": "^8.19.0", + "yaml": "^2.8.2", + "yargs": "^18.0.0" + }, + "devDependencies": { + "@quartz-community/types": "github:quartz-community/types", + "@quartz-community/utils": "github:quartz-community/utils", + "@types/hast": "^3.0.4", + "@types/node": "^25.0.10", + "@types/pretty-time": "^1.1.5", + "@types/source-map-support": "^0.5.10", + "@types/ws": "^8.18.1", + "@types/yargs": "^17.0.35", + "esbuild": "^0.27.2", + "prettier": "^3.8.1", + "tsx": "^4.21.0", + "typescript": "^5.9.3" + } +} diff --git a/quartz.config.yaml b/quartz.config.yaml new file mode 100644 index 000000000..57d3b1f97 --- /dev/null +++ b/quartz.config.yaml @@ -0,0 +1,304 @@ +# yaml-language-server: $schema=./quartz/plugins/quartz-plugins.schema.json +# Template: obsidian +# Content lives in docs/ (Obsidian vault). Build with: npx quartz build -d docs +configuration: + pageTitle: Canwdev's Tech Blog + pageTitleSuffix: "" + enableSPA: true + enablePopovers: true + analytics: null + locale: zh-CN + baseUrl: canwdev.github.io + ignorePatterns: + - private + - templates + - .obsidian + - .trash + theme: + fontOrigin: local + cdnCaching: false + typography: + header: Schibsted Grotesk + body: Source Sans Pro + code: IBM Plex Mono + colors: + lightMode: + light: "#faf8f8" + lightgray: "#e5e5e5" + gray: "#b8b8b8" + darkgray: "#4e4e4e" + dark: "#2b2b2b" + secondary: "#284b63" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#fff23688" + darkMode: + light: "#161618" + lightgray: "#393639" + gray: "#646464" + darkgray: "#d4d4d4" + dark: "#ebebec" + secondary: "#7b97aa" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#b3aa0288" +plugins: + - source: github:quartz-community/created-modified-date + enabled: true + options: + defaultDateType: modified + priority: + - frontmatter + - git + - filesystem + order: 10 + - source: github:quartz-community/syntax-highlighting + enabled: true + options: + theme: + light: github-light + dark: github-dark + keepBackground: false + order: 20 + - source: github:quartz-community/obsidian-flavored-markdown + enabled: true + options: + comments: true + highlight: true + wikilinks: true + callouts: true + mermaid: true + parseTags: true + parseArrows: true + parseBlockReferences: true + enableInHtmlEmbed: false + enableYouTubeEmbed: true + enableVideoEmbed: true + enableCheckbox: true + order: 30 + - source: github:quartz-community/github-flavored-markdown + enabled: true + order: 40 + - source: github:quartz-community/table-of-contents + enabled: true + order: 50 + layout: + position: right + priority: 30 + - source: github:quartz-community/crawl-links + enabled: true + options: + markdownLinkResolution: shortest + # disableBrokenWikilinks: false # Set true to add a "broken" CSS class to internal links whose target is not in ctx.allSlugs. + order: 60 + - source: github:quartz-community/description + enabled: true + order: 70 + - source: github:quartz-community/latex + enabled: true + options: + renderEngine: katex + order: 80 + - source: github:quartz-community/citations + enabled: false + order: 85 + - source: github:quartz-community/hard-line-breaks + enabled: true + order: 90 + - source: github:quartz-community/ox-hugo + enabled: false + order: 91 + - source: github:quartz-community/roam + enabled: false + order: 92 + - source: github:quartz-community/fonts + enabled: true + options: + fontOrigin: local + - source: github:quartz-community/remove-draft + enabled: true + - source: github:quartz-community/explicit-publish + enabled: false + - source: github:quartz-community/unlisted-pages + enabled: true + options: {} + order: 45 + - source: github:quartz-community/encrypted-pages + enabled: true + options: + iterations: 600000 + passwordField: password + unlistWhenEncrypted: false + outputPath: static/encryptedContentIndex.json + - source: github:quartz-community/stacked-pages + enabled: false + layout: + position: afterBody + priority: 50 + display: all + - source: github:quartz-community/alias-redirects + enabled: true + - source: github:quartz-community/content-index + enabled: true + options: + enableSiteMap: true + enableRSS: true + - source: github:quartz-community/favicon + enabled: true + - source: github:quartz-community/og-image + enabled: false + - source: github:quartz-community/cname + enabled: true + - source: github:quartz-community/canvas-page + enabled: true + - source: github:quartz-community/content-page + enabled: true + - source: github:quartz-community/folder-page + enabled: true + - source: github:quartz-community/tag-page + enabled: true + - source: github:quartz-community/explorer + enabled: true + layout: + position: left + priority: 50 + - source: github:quartz-community/graph + enabled: true + layout: + position: right + priority: 10 + - source: github:quartz-community/search + enabled: true + layout: + position: left + priority: 20 + group: toolbar + groupOptions: + grow: true + - source: github:quartz-community/backlinks + enabled: true + layout: + position: right + priority: 50 + - source: github:quartz-community/article-title + enabled: true + layout: + position: beforeBody + priority: 10 + - source: github:quartz-community/content-meta + enabled: true + layout: + position: beforeBody + priority: 20 + - source: github:quartz-community/tag-list + enabled: false + layout: + position: beforeBody + priority: 30 + - source: github:quartz-community/page-title + enabled: true + layout: + position: left + priority: 10 + - source: github:quartz-community/darkmode + enabled: true + layout: + position: left + priority: 30 + group: toolbar + - source: github:quartz-community/reader-mode + enabled: true + layout: + position: left + priority: 35 + group: toolbar + - source: github:quartz-community/breadcrumbs + enabled: true + layout: + position: beforeBody + priority: 5 + condition: not-index + - source: github:quartz-community/comments + enabled: false + options: + provider: giscus + options: {} + layout: + position: afterBody + priority: 10 + - source: github:quartz-community/footer + enabled: true + options: + links: + GitHub: https://github.com/jackyzha0/quartz + Discord Community: https://discord.gg/cRFFHYye7t + - source: github:quartz-community/recent-notes + enabled: false + - source: github:quartz-community/spacer + enabled: true + options: {} + order: 25 + layout: + position: left + priority: 25 + display: mobile-only + - source: github:quartz-community/bases-page + enabled: true + options: {} + order: 50 + - source: github:quartz-community/note-properties + enabled: true + options: + includeAll: false + includedProperties: + - description + - tags + - aliases + excludedProperties: [] + hidePropertiesView: false + delimiters: "---" + language: yaml + order: 5 + layout: + position: beforeBody + priority: 15 + display: all + - source: + name: quartz-themes + repo: github:saberzero1/quartz-themes + subdir: plugin + enabled: true + options: + theme: default + - source: github:quartz-community/obsidian-plugin-excalidraw + enabled: true + options: + enableInteraction: true + darkMode: auto + exportPadding: 20 + order: 50 +layout: + groups: + toolbar: + priority: 35 + direction: row + gap: 0.5rem + byPageType: + "404": + positions: + beforeBody: [] + left: [] + right: [] + content: {} + folder: + exclude: + - reader-mode + positions: + right: [] + tag: + exclude: + - reader-mode + positions: + right: [] + canvas: {} + bases: {} diff --git a/quartz.lock.json b/quartz.lock.json new file mode 100644 index 000000000..a75c59f73 --- /dev/null +++ b/quartz.lock.json @@ -0,0 +1,286 @@ +{ + "version": "1.0.0", + "plugins": { + "alias-redirects": { + "source": "github:quartz-community/alias-redirects", + "resolved": "https://github.com/quartz-community/alias-redirects.git", + "commit": "73a98dda7e4f55239310833299d91daf8611349f", + "installedAt": "2026-07-22T05:50:30.714Z" + }, + "article-title": { + "source": "github:quartz-community/article-title", + "resolved": "https://github.com/quartz-community/article-title.git", + "commit": "e608ca815e137e22b598094f735bcd8a481dafaa", + "installedAt": "2026-07-22T05:50:32.165Z" + }, + "backlinks": { + "source": "github:quartz-community/backlinks", + "resolved": "https://github.com/quartz-community/backlinks.git", + "commit": "7490f921b7bd974c3f2f985ad3744b06160827d6", + "installedAt": "2026-07-22T05:50:32.505Z" + }, + "bases-page": { + "source": "github:quartz-community/bases-page", + "resolved": "https://github.com/quartz-community/bases-page.git", + "commit": "f8fc7da3515a4d4bbe0bd4be0fd9188823866a01", + "installedAt": "2026-07-22T05:50:34.976Z" + }, + "breadcrumbs": { + "source": "github:quartz-community/breadcrumbs", + "resolved": "https://github.com/quartz-community/breadcrumbs.git", + "commit": "cf2e161425165e1ac713f1feb7250b07fe0250ae", + "installedAt": "2026-07-22T05:50:33.127Z" + }, + "canvas-page": { + "source": "github:quartz-community/canvas-page", + "resolved": "https://github.com/quartz-community/canvas-page.git", + "commit": "84fc96799671a65f76b66e30217dbefdd43a2905", + "installedAt": "2026-07-22T05:50:31.403Z" + }, + "citations": { + "source": "github:quartz-community/citations", + "resolved": "https://github.com/quartz-community/citations.git", + "commit": "2ad133a123c5b15b7c41d4fb684d422622113081", + "installedAt": "2026-07-22T05:50:29.822Z" + }, + "cname": { + "source": "github:quartz-community/cname", + "resolved": "https://github.com/quartz-community/cname.git", + "commit": "ec4c81c91da13a6f6c2cbe1656daf5c14575dab3", + "installedAt": "2026-07-22T05:50:30.858Z" + }, + "comments": { + "source": "github:quartz-community/comments", + "resolved": "https://github.com/quartz-community/comments.git", + "commit": "42c5023e42cf62495219095a862b2ea144b65600", + "installedAt": "2026-07-22T05:50:33.311Z" + }, + "content-index": { + "source": "github:quartz-community/content-index", + "resolved": "https://github.com/quartz-community/content-index.git", + "commit": "c3d4f5c85311712c3355cd71da46b28e2d8eba71", + "installedAt": "2026-07-22T05:50:31.271Z" + }, + "content-meta": { + "source": "github:quartz-community/content-meta", + "resolved": "https://github.com/quartz-community/content-meta.git", + "commit": "dd6e94b5ca1cb195104a2b5e624a43ee6aa0a324", + "installedAt": "2026-07-22T05:50:32.585Z" + }, + "content-page": { + "source": "github:quartz-community/content-page", + "resolved": "https://github.com/quartz-community/content-page.git", + "commit": "d22fae357ae74a3e97a2f450862f23f5227842c4", + "installedAt": "2026-07-22T05:50:31.123Z" + }, + "crawl-links": { + "source": "github:quartz-community/crawl-links", + "resolved": "https://github.com/quartz-community/crawl-links.git", + "commit": "43edc6d5182e79bf1b63fed7eb3ba0c7624a1526", + "installedAt": "2026-07-22T05:50:28.758Z" + }, + "created-modified-date": { + "source": "github:quartz-community/created-modified-date", + "resolved": "https://github.com/quartz-community/created-modified-date.git", + "commit": "c003199fb842969d43ee9e0f54120a85e588260e", + "installedAt": "2026-07-22T05:50:28.597Z" + }, + "darkmode": { + "source": "github:quartz-community/darkmode", + "resolved": "https://github.com/quartz-community/darkmode.git", + "commit": "c6484f72ebc6ea89339be7cf86ad14b40c47dcc7", + "installedAt": "2026-07-22T05:50:32.887Z" + }, + "description": { + "source": "github:quartz-community/description", + "resolved": "https://github.com/quartz-community/description.git", + "commit": "56dc546614d905ad07dd0da8dd5820e25e5ea97b", + "installedAt": "2026-07-22T05:50:28.673Z" + }, + "encrypted-pages": { + "source": "github:quartz-community/encrypted-pages", + "resolved": "https://github.com/quartz-community/encrypted-pages.git", + "commit": "89e88af565dfaa9ab2d9c99677b82394ea7be356", + "installedAt": "2026-07-22T05:50:30.674Z" + }, + "explicit-publish": { + "source": "github:quartz-community/explicit-publish", + "resolved": "https://github.com/quartz-community/explicit-publish.git", + "commit": "4afb630a1cc9034267beb93c928e81d0fa2f4d4b", + "installedAt": "2026-07-22T05:50:28.982Z" + }, + "explorer": { + "source": "github:quartz-community/explorer", + "resolved": "https://github.com/quartz-community/explorer.git", + "commit": "a2dfd1373abe58ace461ebea0b4e94cb287f894e", + "installedAt": "2026-07-22T05:50:31.505Z" + }, + "favicon": { + "source": "github:quartz-community/favicon", + "resolved": "https://github.com/quartz-community/favicon.git", + "commit": "85842d5c15f937a3d1a02c45accee27118146d73", + "installedAt": "2026-07-22T05:50:31.054Z" + }, + "folder-page": { + "source": "github:quartz-community/folder-page", + "resolved": "https://github.com/quartz-community/folder-page.git", + "commit": "93304d22e1d7f09f93a33658ec273f7cb8d17793", + "installedAt": "2026-07-22T05:50:31.288Z" + }, + "fonts": { + "source": "github:quartz-community/fonts", + "resolved": "https://github.com/quartz-community/fonts.git", + "commit": "8be5be706d19ca34fa8e342327b78e11aff3d4f0", + "installedAt": "2026-07-22T05:50:28.987Z" + }, + "footer": { + "source": "github:quartz-community/footer", + "resolved": "https://github.com/quartz-community/footer.git", + "commit": "6ed61928d3c0178d7cef972ebcbca6a206a2f065", + "installedAt": "2026-07-22T05:50:33.320Z" + }, + "github-flavored-markdown": { + "source": "github:quartz-community/github-flavored-markdown", + "resolved": "https://github.com/quartz-community/github-flavored-markdown.git", + "commit": "3eabbaa252ce175665ab3f62e1af25948a83e8b6", + "installedAt": "2026-07-22T05:50:29.017Z" + }, + "graph": { + "source": "github:quartz-community/graph", + "resolved": "https://github.com/quartz-community/graph.git", + "commit": "46f0ba1c3c0cc484697572e7bcf315fa384d80d2", + "installedAt": "2026-07-22T05:50:31.489Z" + }, + "hard-line-breaks": { + "source": "github:quartz-community/hard-line-breaks", + "resolved": "https://github.com/quartz-community/hard-line-breaks.git", + "commit": "0d448f38a24c568c5539ff2cd57d813293430ccd", + "installedAt": "2026-07-22T05:50:28.945Z" + }, + "latex": { + "source": "github:quartz-community/latex", + "resolved": "https://github.com/quartz-community/latex.git", + "commit": "3dcfedcb5ae5e28b71248a72665ccb881e82c185", + "installedAt": "2026-07-22T05:50:30.363Z" + }, + "note-properties": { + "source": "github:quartz-community/note-properties", + "resolved": "https://github.com/quartz-community/note-properties.git", + "commit": "3cb40141e792a8a9ba9f99553cd436f36411bf8d", + "installedAt": "2026-07-22T05:50:34.779Z" + }, + "obsidian-flavored-markdown": { + "source": "github:quartz-community/obsidian-flavored-markdown", + "resolved": "https://github.com/quartz-community/obsidian-flavored-markdown.git", + "commit": "07eaca7b31a537c7c4a0fd2848b1f00014c940af", + "installedAt": "2026-07-22T05:50:29.454Z" + }, + "obsidian-plugin-excalidraw": { + "source": "github:quartz-community/obsidian-plugin-excalidraw", + "resolved": "https://github.com/quartz-community/obsidian-plugin-excalidraw.git", + "commit": "dad0a4c0abdc8b3c2786277a2cbd67dc039c754c", + "installedAt": "2026-07-22T05:50:34.178Z" + }, + "og-image": { + "source": "github:quartz-community/og-image", + "resolved": "https://github.com/quartz-community/og-image.git", + "commit": "31343c612d02c5fd22ff27a1e6035b2486be75f5", + "installedAt": "2026-07-22T05:50:31.600Z" + }, + "ox-hugo": { + "source": "github:quartz-community/ox-hugo", + "resolved": "https://github.com/quartz-community/ox-hugo.git", + "commit": "832871da9ab33d52f904456c1a5e14f1b2057d83", + "installedAt": "2026-07-22T05:50:29.262Z" + }, + "page-title": { + "source": "github:quartz-community/page-title", + "resolved": "https://github.com/quartz-community/page-title.git", + "commit": "a1c1fe0a9c6a5ce1acf6efa01d473a7d9850e2a3", + "installedAt": "2026-07-22T05:50:32.779Z" + }, + "quartz-themes": { + "source": { + "name": "quartz-themes", + "repo": "github:saberzero1/quartz-themes", + "subdir": "plugin" + }, + "resolved": "https://github.com/saberzero1/quartz-themes.git", + "commit": "945135e8b70576753d0591e1713fe56553100b3d", + "subdir": "plugin", + "installedAt": "2026-07-22T05:52:08.611Z" + }, + "reader-mode": { + "source": "github:quartz-community/reader-mode", + "resolved": "https://github.com/quartz-community/reader-mode.git", + "commit": "73c8bce66df8835510ffac47e53b1ca0f7efd51e", + "installedAt": "2026-07-22T05:50:33.041Z" + }, + "recent-notes": { + "source": "github:quartz-community/recent-notes", + "resolved": "https://github.com/quartz-community/recent-notes.git", + "commit": "3c3d104335e2fc5ff62371ead157667adb79b046", + "installedAt": "2026-07-22T05:50:33.413Z" + }, + "remove-draft": { + "source": "github:quartz-community/remove-draft", + "resolved": "https://github.com/quartz-community/remove-draft.git", + "commit": "0a51a4c6abb3c86961a9bbf46f4538da87fcf9f8", + "installedAt": "2026-07-22T05:50:28.881Z" + }, + "roam": { + "source": "github:quartz-community/roam", + "resolved": "https://github.com/quartz-community/roam.git", + "commit": "b2aad70fdf548d07b8fd28df9edbdba7f256f166", + "installedAt": "2026-07-22T05:50:28.950Z" + }, + "search": { + "source": "github:quartz-community/search", + "resolved": "https://github.com/quartz-community/search.git", + "commit": "0f4c1a233cd03a0f562e13636b89b7708f8e2698", + "installedAt": "2026-07-22T05:50:31.959Z" + }, + "spacer": { + "source": "github:quartz-community/spacer", + "resolved": "https://github.com/quartz-community/spacer.git", + "commit": "64b135dcfb78c4911f8c42d942872d645cd2a456", + "installedAt": "2026-07-22T05:50:33.426Z" + }, + "stacked-pages": { + "source": "github:quartz-community/stacked-pages", + "resolved": "https://github.com/quartz-community/stacked-pages.git", + "commit": "e88fe063c68f74e6ba2dc7c54ddb353bb10c7982", + "installedAt": "2026-07-22T05:50:30.912Z" + }, + "syntax-highlighting": { + "source": "github:quartz-community/syntax-highlighting", + "resolved": "https://github.com/quartz-community/syntax-highlighting.git", + "commit": "5bfdc2c3f42d3d0326c4e777eb575f3fb68d51fb", + "installedAt": "2026-07-22T05:50:30.247Z" + }, + "table-of-contents": { + "source": "github:quartz-community/table-of-contents", + "resolved": "https://github.com/quartz-community/table-of-contents.git", + "commit": "6984305e5dae0830c025450e160f12610406f7a4", + "installedAt": "2026-07-22T05:50:28.844Z" + }, + "tag-list": { + "source": "github:quartz-community/tag-list", + "resolved": "https://github.com/quartz-community/tag-list.git", + "commit": "c48324e2ba768e2a0a5c4c17cd0661b190d075c5", + "installedAt": "2026-07-22T05:50:32.709Z" + }, + "tag-page": { + "source": "github:quartz-community/tag-page", + "resolved": "https://github.com/quartz-community/tag-page.git", + "commit": "eabf3eec55c0bab7f49e69957341f34d2ae3c5a1", + "installedAt": "2026-07-22T05:50:31.381Z" + }, + "unlisted-pages": { + "source": "github:quartz-community/unlisted-pages", + "resolved": "https://github.com/quartz-community/unlisted-pages.git", + "commit": "f512d8672862c5891e63d755ba04b66277e6d760", + "installedAt": "2026-07-22T05:50:29.157Z" + } + } +} diff --git a/quartz.ts b/quartz.ts new file mode 100644 index 000000000..7a257d16b --- /dev/null +++ b/quartz.ts @@ -0,0 +1,5 @@ +import { loadQuartzConfig, loadQuartzLayout } from "./quartz/plugins/loader/config-loader" + +const config = await loadQuartzConfig() +export default config +export const layout = await loadQuartzLayout() diff --git a/quartz/bootstrap-cli.mjs b/quartz/bootstrap-cli.mjs new file mode 100644 index 000000000..34288c7d6 --- /dev/null +++ b/quartz/bootstrap-cli.mjs @@ -0,0 +1,284 @@ +#!/usr/bin/env -S node --no-deprecation +const [major] = process.versions.node.split(".").map(Number) +if (major < 22) { + console.error( + `\nQuartz requires Node.js >= 22, but you are running Node.js ${process.version}.\n` + + `Please upgrade: https://nodejs.org/\n`, + ) + process.exit(1) +} +import yargs from "yargs" +import { hideBin } from "yargs/helpers" +import { + handleBuild, + handleCreate, + handleUpgrade, + handleRestore, + handleSync, +} from "./cli/handlers.js" + +import { + handlePluginInstallUnified, + handlePluginAdd, + handlePluginRemove, + handlePluginList, + handlePluginStatus, + handlePluginEnable, + handlePluginDisable, + handlePluginConfig, + handlePluginPrune, +} from "./cli/plugin-git-handlers.js" +import { CommonArgv, BuildArgv, CreateArgv, SyncArgv } from "./cli/args.js" +import { version } from "./cli/constants.js" + +async function launchTui() { + const { join } = await import("path") + const { existsSync } = await import("fs") + const { spawn } = await import("child_process") + const tuiPath = join(process.cwd(), ".quartz", "plugins", "tui", "dist", "App.mjs") + + if (!existsSync(tuiPath)) { + console.error( + "TUI plugin not installed. Install with:\n" + + " npx quartz plugin add github:quartz-community/tui\n", + ) + process.exit(1) + } + + // OpenTUI requires Bun runtime (uses bun:ffi for Zig renderer) + return new Promise((resolve, reject) => { + const child = spawn("bun", ["run", tuiPath], { + stdio: "inherit", + cwd: process.cwd(), + }) + + child.on("error", (err) => { + if (err.code === "ENOENT") { + console.error( + "Error: Bun runtime not found. The TUI requires Bun to run.\n" + + "Install Bun: https://bun.sh/docs/installation", + ) + } + reject(err) + }) + + child.on("close", (code) => { + if (code === 0) { + resolve() + } else { + reject(new Error(`TUI exited with code ${code}`)) + } + }) + }) +} + +yargs(hideBin(process.argv)) + .scriptName("quartz") + .version(version) + .usage("$0 [args]") + .command("create", "Initialize Quartz", CreateArgv, async (argv) => { + await handleCreate(argv) + }) + .command( + ["upgrade", "update"], + "Upgrade Quartz to the latest version", + CommonArgv, + async (argv) => { + await handleUpgrade(argv) + }, + ) + .command( + "restore", + "Try to restore your content folder from the cache", + CommonArgv, + async (argv) => { + await handleRestore(argv) + }, + ) + .command("sync", "Sync your Quartz to and from GitHub.", SyncArgv, async (argv) => { + await handleSync(argv) + }) + .command("build", "Build Quartz into a bundle of static HTML files", BuildArgv, async (argv) => { + await handleBuild(argv) + }) + .command("tui", "Launch interactive plugin manager", CommonArgv, async () => { + await launchTui() + }) + .command( + "plugin [subcommand]", + "Manage Quartz plugins", + (yargs) => { + return ( + yargs + .command( + "install [names..]", + "Install plugins from lockfile or config", + { + ...CommonArgv, + "from-config": { + boolean: true, + default: false, + describe: "install plugins referenced in quartz.config.yaml instead of lockfile", + }, + latest: { + boolean: true, + default: false, + describe: "fetch latest version from remote instead of pinned lockfile commit", + }, + clean: { + boolean: true, + default: false, + describe: "skip plugins whose directory already exists", + }, + "dry-run": { + boolean: true, + default: false, + describe: "show what would happen without making changes", + }, + }, + async (argv) => { + await handlePluginInstallUnified({ + names: argv.names?.length ? argv.names : undefined, + fromConfig: argv.fromConfig, + latest: argv.latest, + clean: argv.clean, + dryRun: argv.dryRun, + concurrency: argv.concurrency, + }) + }, + ) + .command( + "add ", + "Add plugins from Git repositories", + { + ...CommonArgv, + name: { + string: true, + alias: ["as"], + describe: "Override the plugin name (for resolving conflicts with duplicate names)", + }, + subdir: { + string: true, + describe: "Subdirectory within the repository containing the plugin", + }, + }, + async (argv) => { + await handlePluginAdd(argv.repos, { + name: argv.name, + subdir: argv.subdir, + concurrency: argv.concurrency, + }) + }, + ) + .command("remove ", "Remove installed plugins", CommonArgv, async (argv) => { + await handlePluginRemove(argv.names) + }) + .command("list", "List all installed plugins", CommonArgv, async () => { + await handlePluginList() + }) + .command( + "enable ", + "Enable plugins in quartz.config.yaml", + CommonArgv, + async (argv) => { + await handlePluginEnable(argv.names) + }, + ) + .command( + "disable ", + "Disable plugins in quartz.config.yaml", + CommonArgv, + async (argv) => { + await handlePluginDisable(argv.names) + }, + ) + .command( + "config ", + "View or set plugin configuration", + { + ...CommonArgv, + set: { + string: true, + describe: "Set a config value (key=value)", + }, + }, + async (argv) => { + await handlePluginConfig(argv.name, { set: argv.set }) + }, + ) + .command( + "prune", + "Remove installed plugins no longer referenced in config", + { + ...CommonArgv, + "dry-run": { + boolean: true, + default: false, + describe: "show what would be pruned without making changes", + }, + }, + async (argv) => { + await handlePluginPrune({ dryRun: argv.dryRun }) + }, + ) + // Hidden deprecated aliases + .command("restore", false, CommonArgv, async (argv) => { + console.log( + "\x1b[33m⚠ 'plugin restore' is deprecated. Use 'plugin install --clean' instead.\x1b[0m", + ) + await handlePluginInstallUnified({ clean: true, concurrency: argv.concurrency }) + }) + .command("update [names..]", false, CommonArgv, async (argv) => { + console.log( + "\x1b[33m⚠ 'plugin update' is deprecated. Use 'plugin install --latest' instead.\x1b[0m", + ) + await handlePluginInstallUnified({ + names: argv.names?.length ? argv.names : undefined, + latest: true, + concurrency: argv.concurrency, + }) + }) + .command("check", false, CommonArgv, async (argv) => { + console.log( + "\x1b[33m⚠ 'plugin check' is deprecated. Use 'plugin install --latest --dry-run' instead.\x1b[0m", + ) + await handlePluginInstallUnified({ + latest: true, + dryRun: true, + concurrency: argv.concurrency, + }) + }) + .command( + "resolve", + false, + { + ...CommonArgv, + "dry-run": { + boolean: true, + default: false, + describe: "show what would be resolved without making changes", + }, + }, + async (argv) => { + console.log( + "\x1b[33m⚠ 'plugin resolve' is deprecated. Use 'plugin install --from-config' instead.\x1b[0m", + ) + await handlePluginInstallUnified({ + fromConfig: true, + dryRun: argv.dryRun, + concurrency: argv.concurrency, + }) + }, + ) + .demandCommand(0, "") + ) + }, + async (argv) => { + if (!argv._.includes("plugin") || argv._.length > 1) return + await handlePluginStatus() + }, + ) + .showHelpOnFail(true) + .help() + .strict() + .demandCommand().argv diff --git a/quartz/bootstrap-worker.mjs b/quartz/bootstrap-worker.mjs new file mode 100644 index 000000000..c4c4949b9 --- /dev/null +++ b/quartz/bootstrap-worker.mjs @@ -0,0 +1,8 @@ +#!/usr/bin/env node +import workerpool from "workerpool" +const cacheFile = "./.quartz-cache/transpiled-worker.mjs" +const { parseMarkdown, processHtml } = await import(cacheFile) +workerpool.worker({ + parseMarkdown, + processHtml, +}) diff --git a/quartz/build.ts b/quartz/build.ts new file mode 100644 index 000000000..3d7364174 --- /dev/null +++ b/quartz/build.ts @@ -0,0 +1,369 @@ +import sourceMapSupport from "source-map-support" +sourceMapSupport.install(options) +import path from "path" +import { PerfTimer } from "./util/perf" +import { rm } from "fs/promises" +import { GlobbyFilterFunction, isGitIgnored } from "globby" +import { styleText } from "util" +import { parseMarkdown } from "./processors/parse" +import { filterContent } from "./processors/filter" +import { emitContent } from "./processors/emit" +import cfg from "../quartz" +import { FilePath, joinSegments, slugifyFilePath } from "./util/path" +import { detectSlugCollisions, formatCollisionWarning } from "./util/slugCollisions" +import chokidar from "chokidar" +import { ProcessedContent } from "./plugins/vfile" +import { Argv, BuildCtx } from "./util/ctx" +import { glob, toPosixPath } from "./util/glob" +import { trace } from "./util/trace" +import { options } from "./util/sourcemap" +import { Mutex } from "async-mutex" +import { getStaticResourcesFromPlugins } from "./plugins" +import { randomIdNonSecure } from "./util/random" +import { ChangeEvent } from "./plugins/types" +import { minimatch } from "minimatch" + +function reportSlugCollisions(content: ProcessedContent[]): void { + const collisions = detectSlugCollisions(content) + if (collisions.length === 0) return + console.warn(styleText("yellow", formatCollisionWarning(collisions))) +} + +type ContentMap = Map< + FilePath, + | { + type: "markdown" + content: ProcessedContent + } + | { + type: "other" + } +> + +type BuildData = { + ctx: BuildCtx + ignored: GlobbyFilterFunction + mut: Mutex + contentMap: ContentMap + changesSinceLastBuild: Record + lastBuildMs: number +} + +async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) { + const ctx: BuildCtx = { + buildId: randomIdNonSecure(), + argv, + cfg, + allSlugs: [], + allFiles: [], + incremental: false, + virtualPages: [], + } + + const perf = new PerfTimer() + const output = argv.output + + const pluginCount = Object.values(cfg.plugins).flat().length + const pluginNames = (key: "transformers" | "filters" | "emitters" | "pageTypes") => + (cfg.plugins[key] ?? []).map((plugin) => plugin.name) + if (argv.verbose) { + console.log(`Loaded ${pluginCount} plugins`) + console.log(` Transformers: ${pluginNames("transformers").join(", ")}`) + console.log(` Filters: ${pluginNames("filters").join(", ")}`) + console.log(` Emitters: ${pluginNames("emitters").join(", ")}`) + console.log(` PageTypes: ${pluginNames("pageTypes").join(", ")}`) + } + + const release = await mut.acquire() + perf.addEvent("clean") + await rm(output, { recursive: true, force: true }) + console.log(`Cleaned output directory \`${output}\` in ${perf.timeSince("clean")}`) + + perf.addEvent("glob") + const allFiles = await glob("**/*.*", argv.directory, cfg.configuration.ignorePatterns) + const markdownPaths = allFiles.filter((fp) => fp.endsWith(".md")).sort() + console.log( + `Found ${markdownPaths.length} input files from \`${argv.directory}\` in ${perf.timeSince("glob")}`, + ) + + const filePaths = markdownPaths.map((fp) => joinSegments(argv.directory, fp) as FilePath) + ctx.allFiles = allFiles + ctx.allSlugs = allFiles.map((fp) => slugifyFilePath(fp as FilePath)) + + const parsedFiles = await parseMarkdown(ctx, filePaths) + reportSlugCollisions(parsedFiles) + const filteredContent = filterContent(ctx, parsedFiles) + + await emitContent(ctx, filteredContent) + console.log( + styleText("green", `Done processing ${markdownPaths.length} files in ${perf.timeSince()}`), + ) + release() + + if (argv.watch) { + ctx.incremental = true + return startWatching(ctx, mut, parsedFiles, clientRefresh) + } +} + +// setup watcher for rebuilds +async function startWatching( + ctx: BuildCtx, + mut: Mutex, + initialContent: ProcessedContent[], + clientRefresh: () => void, +) { + const { argv, allFiles } = ctx + + const contentMap: ContentMap = new Map() + for (const filePath of allFiles) { + contentMap.set(filePath, { + type: "other", + }) + } + + for (const content of initialContent) { + const [_tree, vfile] = content + const relPath = vfile.data.relativePath + if (!relPath) { + console.warn(`Skipping file with no relativePath: ${vfile.path}`) + continue + } + contentMap.set(relPath, { + type: "markdown", + content, + }) + } + + const gitIgnoredMatcher = await isGitIgnored() + const buildData: BuildData = { + ctx, + mut, + contentMap, + ignored: (fp) => { + const pathStr = toPosixPath(fp.toString()) + if (pathStr.startsWith(".git/")) return true + if (gitIgnoredMatcher(pathStr)) return true + for (const pattern of cfg.configuration.ignorePatterns) { + if (minimatch(pathStr, pattern)) { + return true + } + } + + return false + }, + + changesSinceLastBuild: {}, + lastBuildMs: 0, + } + + const watcher = chokidar.watch(".", { + awaitWriteFinish: { stabilityThreshold: 250 }, + persistent: true, + cwd: argv.directory, + ignoreInitial: true, + }) + + const changes: ChangeEvent[] = [] + let rebuildTimeout: ReturnType | null = null + const scheduleRebuild = () => { + if (rebuildTimeout) clearTimeout(rebuildTimeout) + rebuildTimeout = setTimeout(() => { + rebuildTimeout = null + rebuild(changes, clientRefresh, buildData).catch((err) => { + console.error(styleText("red", "Rebuild failed:"), err.message ?? err) + }) + }, 100) + } + watcher + .on("add", (fp) => { + fp = toPosixPath(fp) + if (buildData.ignored(fp)) return + changes.push({ path: fp as FilePath, type: "add" }) + scheduleRebuild() + }) + .on("change", (fp) => { + fp = toPosixPath(fp) + if (buildData.ignored(fp)) return + changes.push({ path: fp as FilePath, type: "change" }) + scheduleRebuild() + }) + .on("unlink", (fp) => { + fp = toPosixPath(fp) + if (buildData.ignored(fp)) return + changes.push({ path: fp as FilePath, type: "delete" }) + scheduleRebuild() + }) + + return async () => { + await watcher.close() + } +} + +async function rebuild(changes: ChangeEvent[], clientRefresh: () => void, buildData: BuildData) { + const { ctx, contentMap, mut, changesSinceLastBuild } = buildData + const { argv, cfg } = ctx + + const buildId = randomIdNonSecure() + ctx.buildId = buildId + buildData.lastBuildMs = new Date().getTime() + const numChangesInBuild = changes.length + const release = await mut.acquire() + try { + // if there's another build after us, release and let them do it + if (ctx.buildId !== buildId) { + return + } + + const perf = new PerfTimer() + perf.addEvent("rebuild") + console.log(styleText("yellow", "Detected change, rebuilding...")) + + // update changesSinceLastBuild + for (const change of changes) { + changesSinceLastBuild[change.path] = change.type + } + + const staticResources = getStaticResourcesFromPlugins(ctx) + const pathsToParse: FilePath[] = [] + for (const [fp, type] of Object.entries(changesSinceLastBuild)) { + if (type === "delete" || path.extname(fp) !== ".md") continue + const fullPath = joinSegments(argv.directory, toPosixPath(fp)) as FilePath + pathsToParse.push(fullPath) + } + + const parsed = await parseMarkdown(ctx, pathsToParse) + for (const content of parsed) { + const relPath = content[1].data.relativePath + if (!relPath) { + console.warn(`Skipping file with no relativePath: ${content[1].path}`) + continue + } + contentMap.set(relPath, { + type: "markdown", + content, + }) + } + + // update state using changesSinceLastBuild + // we do this weird play of add => compute change events => remove + // so that partialEmitters can do appropriate cleanup based on the content of deleted files + for (const [file, change] of Object.entries(changesSinceLastBuild)) { + if (change === "delete") { + // universal delete case + contentMap.delete(file as FilePath) + } + + // manually track non-markdown files as processed files only + // contains markdown files + if (change === "add" && path.extname(file) !== ".md") { + contentMap.set(file as FilePath, { + type: "other", + }) + } + } + + const changeEvents: ChangeEvent[] = Object.entries(changesSinceLastBuild).map(([fp, type]) => { + const path = fp as FilePath + const processedContent = contentMap.get(path) + if (processedContent?.type === "markdown") { + const [_tree, file] = processedContent.content + return { + type, + path, + file, + } + } + + return { + type, + path, + } + }) + + // update allFiles and then allSlugs with the consistent view of content map + ctx.allFiles = Array.from(contentMap.keys()) + ctx.allSlugs = ctx.allFiles.map((fp) => slugifyFilePath(fp as FilePath)) + + const markdownContent = Array.from(contentMap.values()) + .filter((file) => file.type === "markdown") + .map((file) => file.content) + reportSlugCollisions(markdownContent) + let processedFiles = filterContent(ctx, markdownContent) + + let emittedFiles = 0 + + // Phase 1: Run PageTypeDispatcher first so it populates ctx.virtualPages + const dispatcher = cfg.plugins.emitters.find((e) => e.name === "PageTypeDispatcher") + if (dispatcher) { + ctx.virtualPages = [] + const emitFn = dispatcher.partialEmit ?? dispatcher.emit + const emitted = await emitFn(ctx, processedFiles, staticResources, changeEvents) + if (emitted !== null) { + if (Symbol.asyncIterator in emitted) { + for await (const file of emitted) { + emittedFiles++ + if (ctx.argv.verbose) { + console.log(`[emit:${dispatcher.name}] ${file}`) + } + } + } else { + emittedFiles += emitted.length + if (ctx.argv.verbose) { + for (const file of emitted) { + console.log(`[emit:${dispatcher.name}] ${file}`) + } + } + } + } + } + + // Phase 2: Run all other emitters with content extended by virtual pages + const contentWithVirtual = + ctx.virtualPages.length > 0 ? [...processedFiles, ...ctx.virtualPages] : processedFiles + for (const emitter of cfg.plugins.emitters) { + if (emitter.name === "PageTypeDispatcher") continue + // Try to use partialEmit if available, otherwise assume the output is static + const emitFn = emitter.partialEmit ?? emitter.emit + const emitted = await emitFn(ctx, contentWithVirtual, staticResources, changeEvents) + if (emitted === null) { + continue + } + + if (Symbol.asyncIterator in emitted) { + // Async generator case + for await (const file of emitted) { + emittedFiles++ + if (ctx.argv.verbose) { + console.log(`[emit:${emitter.name}] ${file}`) + } + } + } else { + // Array case + emittedFiles += emitted.length + if (ctx.argv.verbose) { + for (const file of emitted) { + console.log(`[emit:${emitter.name}] ${file}`) + } + } + } + } + + console.log( + `Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`, + ) + console.log(styleText("green", `Done rebuilding in ${perf.timeSince()}`)) + changes.splice(0, numChangesInBuild) + clientRefresh() + } finally { + release() + } +} + +export default async (argv: Argv, mut: Mutex, clientRefresh: () => void) => { + try { + return await buildQuartz(argv, mut, clientRefresh) + } catch (err) { + trace("\nExiting Quartz due to a fatal error", err as Error) + } +} diff --git a/quartz/cfg.ts b/quartz/cfg.ts new file mode 100644 index 000000000..9027c461f --- /dev/null +++ b/quartz/cfg.ts @@ -0,0 +1,106 @@ +import { QuartzComponent } from "./components/types" +import { ValidLocale } from "./i18n" +import { PluginSpecifier } from "./plugins/loader/types" +import { PluginTypes } from "./plugins/types" +import { Theme } from "./util/theme" + +export type Analytics = + | null + | { + provider: "plausible" + host?: string + } + | { + provider: "google" + tagId: string + } + | { + provider: "umami" + websiteId: string + host?: string + } + | { + provider: "goatcounter" + websiteId: string + host?: string + scriptSrc?: string + } + | { + provider: "posthog" + apiKey: string + host?: string + } + | { + provider: "tinylytics" + siteId: string + } + | { + provider: "cabin" + host?: string + } + | { + provider: "clarity" + projectId?: string + } + | { + provider: "matomo" + host: string + siteId: string + } + | { + provider: "vercel" + } + | { + provider: "rybbit" + siteId: string + host?: string + } + +export interface GlobalConfiguration { + pageTitle: string + pageTitleSuffix?: string + /** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */ + enableSPA: boolean + /** Whether to display Wikipedia-style popovers when hovering over links */ + enablePopovers: boolean + /** Analytics mode */ + analytics: Analytics + /** Glob patterns to not search */ + ignorePatterns: string[] + /** Base URL to use for CNAME files, sitemaps, and RSS feeds that require an absolute URL. + * Quartz will avoid using this as much as possible and use relative URLs most of the time + */ + baseUrl?: string + theme: Theme + /** + * Allow to translate the date in the language of your choice. + * Also used for UI translation (default: en-US) + * Need to be formatted following BCP 47: https://en.wikipedia.org/wiki/IETF_language_tag + * The first part is the language (en) and the second part is the script/region (US) + * Language Codes: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes + * Region Codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + */ + locale: ValidLocale +} + +export interface QuartzConfig { + configuration: GlobalConfiguration + plugins: PluginTypes + externalPlugins?: PluginSpecifier[] +} + +export interface FullPageLayout { + head: QuartzComponent + header: QuartzComponent[] + beforeBody: QuartzComponent[] + pageBody: QuartzComponent + afterBody: QuartzComponent[] + left: QuartzComponent[] + right: QuartzComponent[] + footer: QuartzComponent + /** Page frame name (e.g. "default", "full-width", "minimal"). Defaults to "default". */ + frame?: string +} + +export type PageLayout = Pick +export type SharedLayout = Pick diff --git a/quartz/cli/args.js b/quartz/cli/args.js new file mode 100644 index 000000000..7a6c1556d --- /dev/null +++ b/quartz/cli/args.js @@ -0,0 +1,120 @@ +export const CommonArgv = { + directory: { + string: true, + alias: ["d"], + default: "content", + describe: "directory to look for content files", + }, + verbose: { + boolean: true, + alias: ["v"], + default: false, + describe: "print out extra logging information", + }, + concurrency: { + number: true, + alias: ["c"], + describe: "max parallel operations (default: number of CPU cores)", + }, +} + +export const CreateArgv = { + ...CommonArgv, + template: { + string: true, + alias: ["t"], + choices: ["default", "obsidian", "ttrpg", "blog"], + describe: "template to use for initial configuration", + }, + source: { + string: true, + alias: ["s"], + describe: "source directory to copy/create symlink from", + }, + strategy: { + string: true, + alias: ["X"], + choices: ["new", "copy", "symlink"], + describe: "strategy for content folder setup", + }, + baseUrl: { + string: true, + alias: ["b"], + describe: "base URL for your Quartz site (e.g. mysite.github.io/quartz)", + }, + links: { + string: true, + alias: ["l"], + choices: ["absolute", "shortest", "relative"], + describe: "strategy to resolve links", + }, +} + +export const SyncArgv = { + ...CommonArgv, + commit: { + boolean: true, + default: true, + describe: "create a git commit for your unsaved changes", + }, + message: { + string: true, + alias: ["m"], + describe: "option to override the default Quartz commit message", + }, + push: { + boolean: true, + default: true, + describe: "push updates to your Quartz fork", + }, + pull: { + boolean: true, + default: true, + describe: "pull updates from your Quartz fork", + }, +} + +export const BuildArgv = { + ...CommonArgv, + output: { + string: true, + alias: ["o"], + default: "public", + describe: "output folder for files", + }, + serve: { + boolean: true, + default: false, + describe: "run a local server to live-preview your Quartz", + }, + watch: { + boolean: true, + default: false, + describe: "watch for changes and rebuild automatically", + }, + baseDir: { + string: true, + default: "", + describe: "base path to serve your local server on", + }, + port: { + number: true, + default: 8080, + describe: "port to serve Quartz on", + }, + wsPort: { + number: true, + default: 3001, + describe: "port to use for WebSocket-based hot-reload notifications", + }, + remoteDevHost: { + string: true, + default: "", + describe: "A URL override for the websocket connection if you are not developing on localhost", + }, + bundleInfo: { + boolean: true, + default: false, + describe: "show detailed bundle information", + }, +} diff --git a/quartz/cli/constants.js b/quartz/cli/constants.js new file mode 100644 index 000000000..83c702af9 --- /dev/null +++ b/quartz/cli/constants.js @@ -0,0 +1,16 @@ +import path from "path" +import { readFileSync } from "fs" + +/** + * All constants relating to helpers or handlers + */ +export const ORIGIN_NAME = "origin" +export const UPSTREAM_NAME = "upstream" +export const QUARTZ_SOURCE_BRANCH = "v5" +export const QUARTZ_SOURCE_REPO = "https://github.com/jackyzha0/quartz.git" +export const cwd = process.cwd() +export const cacheDir = path.join(cwd, ".quartz-cache") +export const cacheFile = "./quartz/.quartz-cache/transpiled-build.mjs" +export const fp = "./quartz/build.ts" +export const { version } = JSON.parse(readFileSync("./package.json").toString()) +export const contentCacheFolder = path.join(cacheDir, "content-cache") diff --git a/quartz/cli/handlers.js b/quartz/cli/handlers.js new file mode 100644 index 000000000..6d4119c57 --- /dev/null +++ b/quartz/cli/handlers.js @@ -0,0 +1,801 @@ +import { promises } from "fs" +import path from "path" +import esbuild from "esbuild" +import { styleText } from "util" +import { sassPlugin } from "esbuild-sass-plugin" +import fs from "fs" +import { intro, outro, select, text } from "@clack/prompts" +import { rm } from "fs/promises" +import chokidar from "chokidar" +import prettyBytes from "pretty-bytes" +import { execSync, spawnSync } from "child_process" +import http from "http" +import serveHandler from "serve-handler" +import { WebSocketServer } from "ws" +import { randomUUID } from "crypto" +import { Mutex } from "async-mutex" +import { CreateArgv } from "./args.js" +import { globby } from "globby" +import { + exitIfCancel, + escapePath, + gitPull, + popContentFolder, + stashContentFolder, + symlinkOrCopy, +} from "./helpers.js" +import { + handlePluginRestore, + handlePluginCheck, + handlePluginResolve, +} from "./plugin-git-handlers.js" +import { + configExists, + createConfigFromDefault, + createConfigFromTemplate, + readPluginsJson, + writePluginsJson, + extractPluginName, + updateGlobalConfig, + LOCKFILE_PATH, +} from "./plugin-data.js" +import { + UPSTREAM_NAME, + QUARTZ_SOURCE_BRANCH, + QUARTZ_SOURCE_REPO, + ORIGIN_NAME, + version, + fp, + cacheFile, + cwd, +} from "./constants.js" + +/** + * Resolve content directory path + * @param contentPath path to resolve + */ +function resolveContentPath(contentPath) { + if (path.isAbsolute(contentPath)) return path.relative(cwd, contentPath) + return path.join(cwd, contentPath) +} + +/** + * Handles `npx quartz create` + * @param {*} argv arguments for `create` + */ +export async function handleCreate(argv) { + console.log() + intro(styleText(["bgGreen", "black"], ` Quartz v${version} `)) + const contentFolder = resolveContentPath(argv.directory) + let setupStrategy = argv.strategy?.toLowerCase() + let linkResolutionStrategy = argv.links?.toLowerCase() + const sourceDirectory = argv.source + let template = argv.template?.toLowerCase() + let baseUrl = argv.baseUrl + + // If all cmd arguments were provided, check if they're valid + if (setupStrategy && linkResolutionStrategy) { + // If setup isn't, "new", source argument is required + if (setupStrategy !== "new") { + // Error handling + if (!sourceDirectory) { + outro( + styleText( + "red", + `Setup strategies (arg '${styleText( + "yellow", + `-${CreateArgv.strategy.alias[0]}`, + )}') other than '${styleText( + "yellow", + "new", + )}' require content folder argument ('${styleText( + "yellow", + `-${CreateArgv.source.alias[0]}`, + )}') to be set`, + ), + ) + process.exit(1) + } else { + if (!fs.existsSync(sourceDirectory)) { + outro( + styleText( + "red", + `Input directory to copy/symlink 'content' from not found ('${styleText( + "yellow", + sourceDirectory, + )}', invalid argument "${styleText("yellow", `-${CreateArgv.source.alias[0]}`)})`, + ), + ) + process.exit(1) + } else if (!fs.lstatSync(sourceDirectory).isDirectory()) { + outro( + styleText( + "red", + `Source directory to copy/symlink 'content' from is not a directory (found file at '${styleText( + "yellow", + sourceDirectory, + )}', invalid argument ${styleText("yellow", `-${CreateArgv.source.alias[0]}`)}")`, + ), + ) + process.exit(1) + } + } + } + } + + // Template selection + if (!template) { + template = exitIfCancel( + await select({ + message: "Choose a template for your Quartz configuration", + options: [ + { value: "default", label: "Default", hint: "clean Quartz setup with sensible defaults" }, + { + value: "obsidian", + label: "Obsidian", + hint: "optimized for Obsidian vaults with full OFM support", + }, + { + value: "ttrpg", + label: "TTRPG", + hint: "Obsidian + map plugin + ITS Theme for D&D/TTRPG wikis", + }, + { + value: "blog", + label: "Blog", + hint: "recent notes and comments enabled for blogging", + }, + ], + }), + ) + } + // Use cli process if cmd args werent provided + if (!setupStrategy) { + setupStrategy = exitIfCancel( + await select({ + message: `Choose how to initialize the content in \`${contentFolder}\``, + options: [ + { value: "new", label: "Empty Quartz" }, + { value: "copy", label: "Copy an existing folder", hint: "overwrites `content`" }, + { + value: "symlink", + label: "Symlink an existing folder", + hint: "don't select this unless you know what you are doing!", + }, + ], + }), + ) + } + + async function rmContentFolder() { + const contentStat = await fs.promises.lstat(contentFolder) + if (contentStat.isSymbolicLink()) { + await fs.promises.unlink(contentFolder) + } else { + await rm(contentFolder, { recursive: true, force: true }) + } + } + + const gitkeepPath = path.join(contentFolder, ".gitkeep") + if (fs.existsSync(gitkeepPath)) { + await fs.promises.unlink(gitkeepPath) + } + if (setupStrategy === "copy" || setupStrategy === "symlink") { + let originalFolder = sourceDirectory + + // If input directory was not passed, use cli + if (!sourceDirectory) { + originalFolder = escapePath( + exitIfCancel( + await text({ + message: "Enter the full path to existing content folder", + placeholder: + "On most terminal emulators, you can drag and drop a folder into the window and it will paste the full path", + validate(fp) { + const fullPath = escapePath(fp) + if (!fs.existsSync(fullPath)) { + return "The given path doesn't exist" + } else if (!fs.lstatSync(fullPath).isDirectory()) { + return "The given path is not a folder" + } + }, + }), + ), + ) + } + + await rmContentFolder() + if (setupStrategy === "copy") { + await fs.promises.cp(originalFolder, contentFolder, { + recursive: true, + preserveTimestamps: true, + }) + } else if (setupStrategy === "symlink") { + await symlinkOrCopy(originalFolder, contentFolder) + } + } else if (setupStrategy === "new") { + await fs.promises.writeFile( + path.join(contentFolder, "index.md"), + `--- +title: Welcome to Quartz +--- + +This is a blank Quartz installation. +See the [documentation](https://quartz.jzhao.xyz) for how to get started. +`, + ) + } + + // Obsidian and TTRPG templates auto-set link resolution to "shortest" + const skipLinkPrompt = template === "obsidian" || template === "ttrpg" + if (skipLinkPrompt) { + linkResolutionStrategy = "shortest" + } + + // Use cli process if cmd args werent provided + if (!linkResolutionStrategy) { + // get a preferred link resolution strategy + linkResolutionStrategy = exitIfCancel( + await select({ + message: `Choose how Quartz should resolve links in your content. This should match Obsidian's link format. You can change this later in \`quartz.config.yaml\`.`, + options: [ + { + value: "shortest", + label: "Treat links as shortest path", + hint: "(default)", + }, + { + value: "absolute", + label: "Treat links as absolute path", + }, + { + value: "relative", + label: "Treat links as relative paths", + }, + ], + }), + ) + } + + // Base URL prompt + if (!baseUrl) { + baseUrl = exitIfCancel( + await text({ + message: "Enter the base URL for your Quartz site (e.g. mysite.github.io/quartz)", + placeholder: "mysite.github.io", + validate(value) { + if (!value || value.trim().length === 0) { + return "Base URL cannot be empty" + } + }, + }), + ) + } + + // Strip protocol prefix if user included it + baseUrl = baseUrl.replace(/^https?:\/\//, "").replace(/\/+$/, "") + + if (template && template !== "default") { + createConfigFromTemplate(template) + console.log(styleText("green", `Created quartz.config.yaml from '${template}' template`)) + } else { + createConfigFromTemplate("default") + console.log(styleText("green", "Created quartz.config.yaml from defaults")) + } + + // Update markdownLinkResolution in the crawl-links plugin options via YAML config + const json = readPluginsJson() + if (json?.plugins) { + const crawlLinksIndex = json.plugins.findIndex( + (p) => extractPluginName(p.source) === "crawl-links", + ) + if (crawlLinksIndex !== -1) { + json.plugins[crawlLinksIndex].options = { + ...json.plugins[crawlLinksIndex].options, + markdownLinkResolution: linkResolutionStrategy, + } + writePluginsJson(json) + } + } + + // Update baseUrl in configuration + updateGlobalConfig({ baseUrl }) + + // install plugins referenced in the template config + await handlePluginResolve() + + // setup remote + execSync(`git remote show upstream || git remote add upstream ${QUARTZ_SOURCE_REPO}`, { + stdio: "ignore", + }) + + outro(`You're all set! Not sure what to do next? Try: + • Customizing Quartz a bit more by editing \`quartz.config.yaml\` + • Running \`npx quartz build --serve\` to preview your Quartz locally + • Hosting your Quartz online (see: https://quartz.jzhao.xyz/hosting) +`) +} + +/** + * Handles `npx quartz build` + * @param {*} argv arguments for `build` + */ +export async function handleBuild(argv) { + if (argv.concurrency !== undefined && argv.concurrency < 1) { + console.error("Concurrency must be at least 1") + process.exit(1) + } + + if (argv.serve) { + argv.watch = true + } + + console.log(`\n${styleText(["bgGreen", "black"], ` Quartz v${version} `)} \n`) + const ctx = await esbuild.context({ + entryPoints: [fp], + outfile: cacheFile, + bundle: true, + keepNames: true, + minifyWhitespace: true, + minifySyntax: true, + platform: "node", + format: "esm", + jsx: "automatic", + jsxImportSource: "preact", + packages: "external", + metafile: true, + sourcemap: true, + sourcesContent: false, + logOverride: { + "direct-eval": "silent", + "equals-negative-zero": "silent", + "duplicate-object-key": "silent", + }, + plugins: [ + sassPlugin({ + type: "css-text", + cssImports: true, + }), + sassPlugin({ + filter: /\.inline\.scss$/, + type: "css", + cssImports: true, + }), + { + name: "inline-script-loader", + setup(build) { + build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => { + let text = await promises.readFile(args.path, "utf8") + + // remove default exports that we manually inserted + text = text.replace("export default", "") + text = text.replace("export", "") + + const sourcefile = path.relative(path.resolve("."), args.path) + const resolveDir = path.dirname(sourcefile) + const transpiled = await esbuild.build({ + stdin: { + contents: text, + loader: "ts", + resolveDir, + sourcefile, + }, + write: false, + bundle: true, + minify: true, + platform: "browser", + format: "esm", + }) + const rawMod = transpiled.outputFiles[0].text + return { + contents: rawMod, + loader: "text", + } + }) + }, + }, + ], + }) + + const buildMutex = new Mutex() + let lastBuildMs = 0 + let cleanupBuild = null + const build = async (clientRefresh) => { + const buildStart = new Date().getTime() + lastBuildMs = buildStart + const release = await buildMutex.acquire() + if (lastBuildMs > buildStart) { + release() + return + } + + if (cleanupBuild) { + console.log(styleText("yellow", "Detected a source code change, doing a hard rebuild...")) + await cleanupBuild() + } + + const result = await ctx.rebuild().catch((err) => { + console.error( + `${styleText("red", "Failed to build Quartz.")} Check for syntax errors in your configuration or plugins.`, + ) + console.log(`Reason: ${styleText("gray", err.message ?? String(err))}`) + process.exit(1) + }) + release() + + if (argv.bundleInfo) { + const outputFileName = "quartz/.quartz-cache/transpiled-build.mjs" + const meta = result.metafile.outputs[outputFileName] + console.log( + `Successfully transpiled ${Object.keys(meta.inputs).length} files (${prettyBytes( + meta.bytes, + )})`, + ) + console.log(await esbuild.analyzeMetafile(result.metafile, { color: true })) + } + + // bypass module cache + // https://github.com/nodejs/modules/issues/307 + const { default: buildQuartz } = await import(`../../${cacheFile}?update=${randomUUID()}`) + // ^ this import is relative, so base "cacheFile" path can't be used + + cleanupBuild = await buildQuartz(argv, buildMutex, clientRefresh) + clientRefresh() + } + + let clientRefresh = () => {} + if (argv.serve) { + const connections = [] + clientRefresh = () => connections.forEach((conn) => conn.send("rebuild")) + + if (argv.baseDir !== "" && !argv.baseDir.startsWith("/")) { + argv.baseDir = "/" + argv.baseDir + } + + await build(clientRefresh) + const server = http.createServer(async (req, res) => { + if (argv.baseDir && !req.url?.startsWith(argv.baseDir)) { + console.log( + styleText( + "red", + `[404] ${req.url} (warning: link outside of site, this is likely a Quartz bug)`, + ), + ) + res.writeHead(404) + res.end() + return + } + + // strip baseDir prefix + req.url = req.url?.slice(argv.baseDir.length) + + const serve = async () => { + const release = await buildMutex.acquire() + await serveHandler(req, res, { + public: argv.output, + directoryListing: false, + headers: [ + { + source: "**/*.*", + headers: [{ key: "Content-Disposition", value: "inline" }], + }, + { + source: "**/*.webp", + headers: [{ key: "Content-Type", value: "image/webp" }], + }, + // fixes bug where avif images are displayed as text instead of images (future proof) + { + source: "**/*.avif", + headers: [{ key: "Content-Type", value: "image/avif" }], + }, + ], + }) + const status = res.statusCode + const statusString = + status >= 200 && status < 300 + ? styleText("green", `[${status}]`) + : styleText("red", `[${status}]`) + console.log(statusString + styleText("gray", ` ${argv.baseDir}${req.url}`)) + release() + } + + const redirect = (newFp) => { + newFp = argv.baseDir + newFp + res.writeHead(302, { + Location: newFp, + }) + console.log( + styleText("yellow", "[302]") + + styleText("gray", ` ${argv.baseDir}${req.url} -> ${newFp}`), + ) + res.end() + } + + let fp = req.url?.split("?")[0] ?? "/" + + // handle redirects + if (fp.endsWith("/")) { + // /trailing/ + // does /trailing/index.html exist? if so, serve it + const indexFp = path.posix.join(fp, "index.html") + if (fs.existsSync(path.posix.join(argv.output, indexFp))) { + req.url = fp + return serve() + } + + // does /trailing.html exist? if so, redirect to /trailing + let base = fp.slice(0, -1) + if (path.extname(base) === "") { + base += ".html" + } + if (fs.existsSync(path.posix.join(argv.output, base))) { + return redirect(fp.slice(0, -1)) + } + } else { + // /regular + // does /regular.html exist? if so, serve it + let base = fp + if (path.extname(base) === "") { + base += ".html" + } + if (fs.existsSync(path.posix.join(argv.output, base))) { + req.url = fp + return serve() + } + + // does /regular/index.html exist? if so, redirect to /regular/ + let indexFp = path.posix.join(fp, "index.html") + if (fs.existsSync(path.posix.join(argv.output, indexFp))) { + return redirect(fp + "/") + } + } + + return serve() + }) + + server.on("error", (err) => { + if (err.code === "EADDRINUSE") { + console.error( + `Port ${argv.port} is already in use. Try a different port with --port `, + ) + process.exit(1) + } + throw err + }) + server.listen(argv.port) + const wss = new WebSocketServer({ port: argv.wsPort }) + wss.on("error", (err) => { + if (err.code === "EADDRINUSE") { + console.error( + `WebSocket port ${argv.wsPort} is already in use. Try a different port with --wsPort `, + ) + process.exit(1) + } + throw err + }) + wss.on("connection", (ws) => connections.push(ws)) + console.log( + styleText( + "cyan", + `Started a Quartz server listening at http://localhost:${argv.port}${argv.baseDir}`, + ), + ) + } else { + await build(clientRefresh) + ctx.dispose() + } + + if (argv.watch) { + const paths = await globby([ + "**/*.ts", + "quartz/cli/*.js", + "quartz/static/**/*", + "**/*.tsx", + "**/*.scss", + "package.json", + "quartz.config.yaml", + "quartz.config.default.yaml", + ]) + chokidar + .watch(paths, { ignoreInitial: true }) + .on("add", () => build(clientRefresh)) + .on("change", () => build(clientRefresh)) + .on("unlink", () => build(clientRefresh)) + + console.log(styleText("gray", "hint: exit with ctrl+c")) + } +} + +/** + * Handles `npx quartz upgrade` + * Upgrades the Quartz framework itself by pulling latest changes from upstream. + * @param {*} argv arguments for `upgrade` + */ +export async function handleUpgrade(argv) { + const contentFolder = resolveContentPath(argv.directory) + console.log(`\n${styleText(["bgGreen", "black"], ` Quartz v${version} `)} \n`) + console.log("Backing up your content") + execSync(`git remote show upstream || git remote add upstream ${QUARTZ_SOURCE_REPO}`) + await stashContentFolder(contentFolder) + + const lockfileBackup = LOCKFILE_PATH + ".bak" + const hasLockfile = fs.existsSync(LOCKFILE_PATH) + if (hasLockfile) { + fs.copyFileSync(LOCKFILE_PATH, lockfileBackup) + } + + console.log( + "Pulling updates... you may need to resolve some `git` conflicts if you've made changes to components or plugins.", + ) + + let pullOk = false + try { + gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH) + pullOk = true + } catch { + if (hasLockfile) { + try { + fs.copyFileSync(lockfileBackup, LOCKFILE_PATH) + execSync(`git add ${LOCKFILE_PATH}`) + const remaining = execSync("git diff --name-only --diff-filter=U", { + encoding: "utf-8", + }).trim() + if (remaining.length === 0) { + execSync("git commit --no-edit") + pullOk = true + console.log(styleText("cyan", "Resolved quartz.lock.json merge conflict automatically.")) + } + } catch { + // Could not auto-resolve, fall through to manual resolution + } + } + + if (!pullOk) { + console.log( + styleText("red", "An error occurred while pulling updates.") + + "\nCheck your network connection and git credentials. If you see merge conflicts, resolve them manually and run `npx quartz sync --no-pull`.", + ) + await popContentFolder(contentFolder) + if (fs.existsSync(lockfileBackup)) fs.unlinkSync(lockfileBackup) + return + } + } + + if (hasLockfile && fs.existsSync(lockfileBackup)) { + fs.copyFileSync(lockfileBackup, LOCKFILE_PATH) + fs.unlinkSync(lockfileBackup) + } + + await popContentFolder(contentFolder) + + // Read the new version after pulling + const newPkg = JSON.parse(fs.readFileSync("./package.json").toString()) + const newVersion = newPkg.version + if (newVersion !== version) { + console.log(styleText("cyan", `Upgraded Quartz: v${version} → v${newVersion}`)) + } else { + console.log(styleText("gray", `Quartz is already up to date (v${version})`)) + } + + console.log("Ensuring dependencies are up to date") + + /* + On Windows, if the command `npm` is really `npm.cmd', this call fails + as it will be unable to find `npm`. This is often the case on systems + where `npm` is installed via a package manager. + + This means `npx quartz upgrade` will not actually update dependencies + on Windows, without a manual `npm i` from the caller. + + However, by spawning a shell, we are able to call `npm.cmd`. + See: https://nodejs.org/api/child_process.html#spawning-bat-and-cmd-files-on-windows + */ + + const opts = { stdio: "inherit" } + if (process.platform === "win32") { + opts.shell = true + } + + const res = spawnSync("npm", ["i"], opts) + if (res.status === 0) { + console.log(styleText("green", "Dependencies updated!")) + } else { + console.log( + styleText("red", "An error occurred while installing dependencies.") + + "\nTry running `npm install` manually to see detailed errors.", + ) + } + + console.log("Restoring plugins from lockfile...") + await handlePluginRestore() + + console.log("Checking plugin compatibility...") + await handlePluginCheck() + + console.log(styleText("green", "Done!")) +} + +/** + * Handles `npx quartz restore` + * @param {*} argv arguments for `restore` + */ +export async function handleRestore(argv) { + const contentFolder = resolveContentPath(argv.directory) + await popContentFolder(contentFolder) +} + +/** + * Handles `npx quartz sync` + * @param {*} argv arguments for `sync` + */ +export async function handleSync(argv) { + const contentFolder = resolveContentPath(argv.directory) + console.log(`\n${styleText(["bgGreen", "black"], ` Quartz v${version} `)}\n`) + console.log("Backing up your content") + + if (argv.commit) { + const contentStat = await fs.promises.lstat(contentFolder) + if (contentStat.isSymbolicLink()) { + const linkTarg = await fs.promises.readlink(contentFolder) + console.log(styleText("yellow", "Detected symlink, trying to dereference before committing")) + + // stash symlink file + await stashContentFolder(contentFolder) + + // follow symlink and copy content + await fs.promises.cp(linkTarg, contentFolder, { + recursive: true, + preserveTimestamps: true, + }) + } + + const currentTimestamp = new Date().toLocaleString("en-US", { + dateStyle: "medium", + timeStyle: "short", + }) + const commitMessage = argv.message ?? `Quartz sync: ${currentTimestamp}` + spawnSync("git", ["add", "."], { stdio: "inherit" }) + spawnSync("git", ["commit", "-m", commitMessage], { stdio: "inherit" }) + + if (contentStat.isSymbolicLink()) { + // put symlink back + await popContentFolder(contentFolder) + } + } + + await stashContentFolder(contentFolder) + + if (argv.pull) { + console.log( + "Pulling updates from your repository. You may need to resolve some `git` conflicts if you've made changes to components or plugins.", + ) + try { + gitPull(ORIGIN_NAME, QUARTZ_SOURCE_BRANCH) + } catch { + console.log( + styleText("red", "An error occurred while pulling updates from your repository.") + + "\nCheck your network connection and git credentials.", + ) + await popContentFolder(contentFolder) + return + } + } + + await popContentFolder(contentFolder) + if (argv.push) { + console.log("Pushing your changes") + const currentBranch = execSync("git rev-parse --abbrev-ref HEAD").toString().trim() + const res = spawnSync("git", ["push", "-uf", ORIGIN_NAME, currentBranch], { + stdio: "inherit", + }) + if (res.status !== 0) { + console.log( + styleText("red", `An error occurred while pushing to remote ${ORIGIN_NAME}.`) + + "\nCheck that you have push access to the remote repository.", + ) + return + } + } + + console.log(styleText("green", "Done!")) +} diff --git a/quartz/cli/helpers.js b/quartz/cli/helpers.js new file mode 100644 index 000000000..75334c748 --- /dev/null +++ b/quartz/cli/helpers.js @@ -0,0 +1,109 @@ +import { isCancel, outro } from "@clack/prompts" +import { styleText } from "util" +import { contentCacheFolder } from "./constants.js" +import { spawnSync } from "child_process" +import fs from "fs" +import path from "path" + +export function escapePath(fp) { + return fp + .replace(/\\ /g, " ") // unescape spaces + .replace(/^"(.*)"$/, "$1") + .replace(/^'(.*)'$/, "$1") + .trim() +} + +export function exitIfCancel(val) { + if (isCancel(val)) { + outro(styleText("red", "Exiting")) + process.exit(0) + } else { + return val + } +} + +export async function stashContentFolder(contentFolder) { + await fs.promises.rm(contentCacheFolder, { force: true, recursive: true }) + await fs.promises.cp(contentFolder, contentCacheFolder, { + force: true, + recursive: true, + verbatimSymlinks: true, + preserveTimestamps: true, + }) + await fs.promises.rm(contentFolder, { force: true, recursive: true }) +} + +export function gitPull(origin, branch) { + const flags = ["--no-rebase", "--autostash", "--no-edit", "--allow-unrelated-histories"] + const out = spawnSync("git", ["pull", ...flags, origin, branch], { stdio: "inherit" }) + if (out.stderr) { + throw new Error(styleText("red", `Error while pulling updates: ${out.stderr}`)) + } else if (out.status !== 0) { + throw new Error(styleText("red", "Error while pulling updates")) + } +} + +export async function popContentFolder(contentFolder) { + await fs.promises.rm(contentFolder, { force: true, recursive: true }) + await fs.promises.cp(contentCacheFolder, contentFolder, { + force: true, + recursive: true, + verbatimSymlinks: true, + preserveTimestamps: true, + }) + await fs.promises.rm(contentCacheFolder, { force: true, recursive: true }) +} + +/** + * Create a directory symlink with Windows fallback. + * + * On Windows, creating symlinks requires Developer Mode or admin privileges. + * When that fails (EPERM), we try a junction first (no elevation needed), + * then fall back to a recursive copy as a last resort. + * + * @param {string} target Symlink target (may be relative) + * @param {string} linkPath Path where the link is created + */ +export function symlinkOrCopySync(target, linkPath) { + try { + fs.symlinkSync(target, linkPath, "dir") + } catch (err) { + if (err.code === "EEXIST") return + if (err.code === "EPERM" && process.platform === "win32") { + try { + fs.symlinkSync(target, linkPath, "junction") + return + } catch { + const resolvedTarget = path.resolve(path.dirname(linkPath), target) + fs.cpSync(resolvedTarget, linkPath, { recursive: true }) + return + } + } + throw err + } +} + +/** + * Async version of {@link symlinkOrCopySync}. + * + * @param {string} target Symlink target (may be relative) + * @param {string} linkPath Path where the link is created + */ +export async function symlinkOrCopy(target, linkPath) { + try { + await fs.promises.symlink(target, linkPath, "dir") + } catch (err) { + if (err.code === "EEXIST") return + if (err.code === "EPERM" && process.platform === "win32") { + try { + await fs.promises.symlink(target, linkPath, "junction") + return + } catch { + const resolvedTarget = path.resolve(path.dirname(linkPath), target) + await fs.promises.cp(resolvedTarget, linkPath, { recursive: true }) + return + } + } + throw err + } +} diff --git a/quartz/cli/helpers.test.js b/quartz/cli/helpers.test.js new file mode 100644 index 000000000..b3890bd62 --- /dev/null +++ b/quartz/cli/helpers.test.js @@ -0,0 +1,237 @@ +import test, { describe, beforeEach, afterEach, mock } from "node:test" +import assert from "node:assert" +import fs from "fs" +import path from "path" +import os from "os" +import { symlinkOrCopySync, symlinkOrCopy } from "./helpers.js" + +function makeTmpDir() { + return fs.mkdtempSync(path.join(os.tmpdir(), "quartz-symlink-test-")) +} + +function makeTarget(tmpDir) { + const target = path.join(tmpDir, "target") + fs.mkdirSync(target) + fs.writeFileSync(path.join(target, "marker.txt"), "hello") + return target +} + +describe("symlinkOrCopySync", () => { + let tmpDir + + beforeEach(() => { + tmpDir = makeTmpDir() + }) + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }) + }) + + test("creates a symlink on success", () => { + const target = makeTarget(tmpDir) + const linkPath = path.join(tmpDir, "link") + + symlinkOrCopySync(target, linkPath) + + const stat = fs.lstatSync(linkPath) + assert.ok(stat.isSymbolicLink()) + assert.ok(fs.existsSync(path.join(linkPath, "marker.txt"))) + }) + + test("silently succeeds when link already exists (EEXIST)", () => { + const target = makeTarget(tmpDir) + const linkPath = path.join(tmpDir, "link") + + symlinkOrCopySync(target, linkPath) + assert.doesNotThrow(() => symlinkOrCopySync(target, linkPath)) + }) + + test("re-throws non-EPERM errors", () => { + const target = makeTarget(tmpDir) + const linkPath = path.join(tmpDir, "nonexistent-parent", "link") + + assert.throws( + () => symlinkOrCopySync(target, linkPath), + (err) => { + return err.code === "ENOENT" + }, + ) + }) + + test("falls back to junction on Windows EPERM", () => { + const target = makeTarget(tmpDir) + const linkPath = path.join(tmpDir, "link") + + const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform") + Object.defineProperty(process, "platform", { value: "win32", configurable: true }) + + let callCount = 0 + const originalSymlinkSync = fs.symlinkSync + mock.method(fs, "symlinkSync", (t, lp, type) => { + callCount++ + if (callCount === 1 && type === "dir") { + const err = new Error("EPERM: operation not permitted, symlink") + err.code = "EPERM" + err.errno = -4048 + err.syscall = "symlink" + throw err + } + return originalSymlinkSync(t, lp, type) + }) + + try { + symlinkOrCopySync(target, linkPath) + assert.ok(fs.existsSync(path.join(linkPath, "marker.txt"))) + assert.strictEqual(callCount, 2) + } finally { + fs.symlinkSync.mock.restore() + if (originalPlatform) { + Object.defineProperty(process, "platform", originalPlatform) + } + } + }) + + test("falls back to copy when both symlink and junction fail on Windows", () => { + const target = makeTarget(tmpDir) + const linkPath = path.join(tmpDir, "link") + + const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform") + Object.defineProperty(process, "platform", { value: "win32", configurable: true }) + + const originalSymlinkSync = fs.symlinkSync + mock.method(fs, "symlinkSync", (_t, _lp, _type) => { + const err = new Error("EPERM: operation not permitted, symlink") + err.code = "EPERM" + err.errno = -4048 + err.syscall = "symlink" + throw err + }) + + try { + symlinkOrCopySync(target, linkPath) + + const stat = fs.lstatSync(linkPath) + assert.ok(stat.isDirectory(), "fallback should produce a real directory, not a symlink") + assert.strictEqual(fs.readFileSync(path.join(linkPath, "marker.txt"), "utf-8"), "hello") + } finally { + fs.symlinkSync.mock.restore() + if (originalPlatform) { + Object.defineProperty(process, "platform", originalPlatform) + } + } + }) + + test("does not fall back on EPERM when not on Windows", () => { + const target = makeTarget(tmpDir) + const linkPath = path.join(tmpDir, "link") + + const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform") + Object.defineProperty(process, "platform", { value: "linux", configurable: true }) + + const originalSymlinkSync = fs.symlinkSync + mock.method(fs, "symlinkSync", (_t, _lp, _type) => { + const err = new Error("EPERM: operation not permitted, symlink") + err.code = "EPERM" + throw err + }) + + try { + assert.throws( + () => symlinkOrCopySync(target, linkPath), + (err) => err.code === "EPERM", + ) + } finally { + fs.symlinkSync.mock.restore() + if (originalPlatform) { + Object.defineProperty(process, "platform", originalPlatform) + } + } + }) +}) + +describe("symlinkOrCopy", () => { + let tmpDir + + beforeEach(() => { + tmpDir = makeTmpDir() + }) + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }) + }) + + test("creates a symlink on success", async () => { + const target = makeTarget(tmpDir) + const linkPath = path.join(tmpDir, "link") + + await symlinkOrCopy(target, linkPath) + + const stat = fs.lstatSync(linkPath) + assert.ok(stat.isSymbolicLink()) + assert.ok(fs.existsSync(path.join(linkPath, "marker.txt"))) + }) + + test("silently succeeds when link already exists (EEXIST)", async () => { + const target = makeTarget(tmpDir) + const linkPath = path.join(tmpDir, "link") + + await symlinkOrCopy(target, linkPath) + await assert.doesNotReject(() => symlinkOrCopy(target, linkPath)) + }) + + test("falls back to copy when both symlink and junction fail on Windows", async () => { + const target = makeTarget(tmpDir) + const linkPath = path.join(tmpDir, "link") + + const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform") + Object.defineProperty(process, "platform", { value: "win32", configurable: true }) + + const originalSymlink = fs.promises.symlink + mock.method(fs.promises, "symlink", async (_t, _lp, _type) => { + const err = new Error("EPERM: operation not permitted, symlink") + err.code = "EPERM" + err.errno = -4048 + err.syscall = "symlink" + throw err + }) + + try { + await symlinkOrCopy(target, linkPath) + + const stat = fs.lstatSync(linkPath) + assert.ok(stat.isDirectory(), "fallback should produce a real directory, not a symlink") + assert.strictEqual(fs.readFileSync(path.join(linkPath, "marker.txt"), "utf-8"), "hello") + } finally { + fs.promises.symlink.mock.restore() + if (originalPlatform) { + Object.defineProperty(process, "platform", originalPlatform) + } + } + }) + + test("does not fall back on EPERM when not on Windows", async () => { + const target = makeTarget(tmpDir) + const linkPath = path.join(tmpDir, "link") + + const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform") + Object.defineProperty(process, "platform", { value: "linux", configurable: true }) + + mock.method(fs.promises, "symlink", async (_t, _lp, _type) => { + const err = new Error("EPERM: operation not permitted, symlink") + err.code = "EPERM" + throw err + }) + + try { + await assert.rejects( + () => symlinkOrCopy(target, linkPath), + (err) => err.code === "EPERM", + ) + } finally { + fs.promises.symlink.mock.restore() + if (originalPlatform) { + Object.defineProperty(process, "platform", originalPlatform) + } + } + }) +}) diff --git a/quartz/cli/plugin-data.js b/quartz/cli/plugin-data.js new file mode 100644 index 000000000..c8cd6b457 --- /dev/null +++ b/quartz/cli/plugin-data.js @@ -0,0 +1,367 @@ +import fs from "fs" +import path from "path" +import { execSync } from "child_process" +import YAML from "yaml" + +const LOCKFILE_PATH = path.join(process.cwd(), "quartz.lock.json") +const PLUGINS_DIR = path.join(process.cwd(), ".quartz", "plugins") +const CONFIG_YAML_PATH = path.join(process.cwd(), "quartz.config.yaml") +const DEFAULT_CONFIG_YAML_PATH = path.join(process.cwd(), "quartz.config.default.yaml") +const TEMPLATES_DIR = path.join(process.cwd(), "quartz", "cli", "templates") + +const LEGACY_PLUGINS_JSON_PATH = path.join(process.cwd(), "quartz.plugins.json") +const LEGACY_DEFAULT_PLUGINS_JSON_PATH = path.join(process.cwd(), "quartz.plugins.default.json") + +function resolveConfigPath() { + if (fs.existsSync(CONFIG_YAML_PATH)) return CONFIG_YAML_PATH + if (fs.existsSync(LEGACY_PLUGINS_JSON_PATH)) return LEGACY_PLUGINS_JSON_PATH + if (fs.existsSync(DEFAULT_CONFIG_YAML_PATH)) return DEFAULT_CONFIG_YAML_PATH + if (fs.existsSync(LEGACY_DEFAULT_PLUGINS_JSON_PATH)) return LEGACY_DEFAULT_PLUGINS_JSON_PATH + return CONFIG_YAML_PATH +} + +function resolveDefaultConfigPath() { + if (fs.existsSync(DEFAULT_CONFIG_YAML_PATH)) return DEFAULT_CONFIG_YAML_PATH + if (fs.existsSync(LEGACY_DEFAULT_PLUGINS_JSON_PATH)) return LEGACY_DEFAULT_PLUGINS_JSON_PATH + return DEFAULT_CONFIG_YAML_PATH +} + +function readFileAsData(filePath) { + if (!fs.existsSync(filePath)) return null + try { + const raw = fs.readFileSync(filePath, "utf-8") + if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) { + return YAML.parse(raw) + } + return JSON.parse(raw) + } catch { + return null + } +} + +function writeDataToFile(filePath, data) { + if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) { + const header = "# yaml-language-server: $schema=./quartz/plugins/quartz-plugins.schema.json\n" + fs.writeFileSync(filePath, header + YAML.stringify(data, { lineWidth: 120 })) + } else { + fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n") + } +} + +export function readPluginsJson() { + const configPath = resolveConfigPath() + return readFileAsData(configPath) +} + +export function writePluginsJson(data) { + const { $schema, ...rest } = data + writeDataToFile(CONFIG_YAML_PATH, rest) +} + +function readDefaultPluginsJson() { + const defaultPath = resolveDefaultConfigPath() + return readFileAsData(defaultPath) +} + +export function readLockfile() { + if (!fs.existsSync(LOCKFILE_PATH)) return null + try { + return JSON.parse(fs.readFileSync(LOCKFILE_PATH, "utf-8")) + } catch { + return null + } +} + +export function writeLockfile(lockfile) { + if (lockfile.plugins) { + const sorted = {} + for (const key of Object.keys(lockfile.plugins).sort()) { + sorted[key] = lockfile.plugins[key] + } + lockfile = { ...lockfile, plugins: sorted } + } + fs.writeFileSync(LOCKFILE_PATH, JSON.stringify(lockfile, null, 2) + "\n") +} + +/** + * Normalizes a source value to a URL string. + * Source can be a plain string (e.g. "github:owner/repo") or an object + * with { name?, repo, subdir? } for installing from a subdirectory of a repo. + */ +export function getSourceUrl(source) { + if (typeof source === "string") return source + if (typeof source === "object" && source !== null && typeof source.repo === "string") { + return source.repo + } + throw new Error(`Invalid plugin source: ${JSON.stringify(source)}`) +} + +/** + * Returns the subdir from an object source, or undefined for string sources. + */ +function getSourceSubdir(source) { + if (typeof source === "object" && source !== null && typeof source.subdir === "string") { + return source.subdir + } + return undefined +} + +/** + * Returns a display-friendly string for a source value. + */ +export function formatSource(source) { + if (typeof source === "string") return source + if (typeof source === "object" && source !== null) { + const parts = [source.repo] + if (source.subdir) parts.push(`(subdir: ${source.subdir})`) + return parts.join(" ") + } + return String(source) +} + +export function isLocalSource(source) { + const url = getSourceUrl(source) + if (url.startsWith("./") || url.startsWith("../") || url.startsWith("/")) { + return true + } + // Windows absolute paths (e.g. C:\ or D:/) + if (/^[A-Za-z]:[\\/]/.test(url)) { + return true + } + return false +} +export function extractPluginName(source) { + if (typeof source === "object" && source !== null && typeof source.name === "string") { + return source.name + } + const url = getSourceUrl(source) + if (isLocalSource(url)) { + return path.basename(url.replace(/[\/]+$/, "")) + } + if (url.startsWith("github:")) { + const withoutPrefix = url.replace("github:", "") + const [repoPath] = withoutPrefix.split("#") + const parts = repoPath.split("/") + return parts[parts.length - 1] + } + if (url.startsWith("git+") || url.startsWith("https://")) { + const cleaned = url.replace("git+", "") + const match = cleaned.match(/\/([^/]+?)(?:\.git)?(?:#|$)/) + return match?.[1] ?? url + } + return url +} + +export function readManifestFromPackageJson(pluginDir) { + const pkgPath = path.join(pluginDir, "package.json") + if (!fs.existsSync(pkgPath)) return null + try { + const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")) + return pkg.quartz ?? null + } catch { + return null + } +} + +export function parseGitSource(source) { + const url = getSourceUrl(source) + const subdir = getSourceSubdir(source) + if (isLocalSource(url)) { + const resolved = path.resolve(url) + const name = typeof source === "object" && source.name ? source.name : path.basename(resolved) + return { name, url: resolved, ref: undefined, local: true, subdir } + } + if (url.startsWith("github:")) { + const [repoPath, ref] = url.replace("github:", "").split("#") + const [owner, repo] = repoPath.split("/") + const name = typeof source === "object" && source.name ? source.name : repo + return { name, url: `https://github.com/${owner}/${repo}.git`, ref, subdir } + } + if (url.startsWith("git+")) { + const raw = url.replace("git+", "") + const [parsed, ref] = raw.split("#") + const name = + typeof source === "object" && source.name ? source.name : path.basename(parsed, ".git") + return { name, url: parsed, ref, subdir } + } + if (url.startsWith("https://")) { + const [parsed, ref] = url.split("#") + const name = + typeof source === "object" && source.name ? source.name : path.basename(parsed, ".git") + return { name, url: parsed, ref, subdir } + } + throw new Error(`Cannot parse plugin source: ${formatSource(source)}`) +} + +export function getGitCommit(pluginDir) { + try { + return execSync("git rev-parse HEAD", { cwd: pluginDir, encoding: "utf-8" }).trim() + } catch { + return "unknown" + } +} + +export function updateGlobalConfig(updates) { + const json = readPluginsJson() + if (!json) return false + json.configuration = { ...json.configuration, ...updates } + writePluginsJson(json) + return true +} + +export function configExists() { + return fs.existsSync(CONFIG_YAML_PATH) || fs.existsSync(LEGACY_PLUGINS_JSON_PATH) +} + +export function createConfigFromDefault() { + const defaultData = readDefaultPluginsJson() + if (!defaultData) { + // No default available — create minimal config + const minimal = { + configuration: { + pageTitle: "Quartz", + enableSPA: true, + enablePopovers: true, + analytics: { provider: "plausible" }, + locale: "en-US", + baseUrl: "quartz.jzhao.xyz", + ignorePatterns: ["private", "templates", ".obsidian"], + theme: { + cdnCaching: true, + typography: { + header: "Schibsted Grotesk", + body: "Source Sans Pro", + code: "IBM Plex Mono", + }, + colors: { + lightMode: { + light: "#faf8f8", + lightgray: "#e5e5e5", + gray: "#b8b8b8", + darkgray: "#4e4e4e", + dark: "#2b2b2b", + secondary: "#284b63", + tertiary: "#84a59d", + highlight: "rgba(143, 159, 169, 0.15)", + textHighlight: "#fff23688", + }, + darkMode: { + light: "#161618", + lightgray: "#393639", + gray: "#646464", + darkgray: "#d4d4d4", + dark: "#ebebec", + secondary: "#7b97aa", + tertiary: "#84a59d", + highlight: "rgba(143, 159, 169, 0.15)", + textHighlight: "#fff23688", + }, + }, + }, + }, + plugins: [], + layout: { groups: {}, byPageType: {} }, + } + writePluginsJson(minimal) + return minimal + } + + const { $schema, ...rest } = defaultData + writePluginsJson(rest) + return rest +} + +const VALID_TEMPLATES = ["default", "obsidian", "ttrpg", "blog"] + +export function createConfigFromTemplate(templateName) { + if (!VALID_TEMPLATES.includes(templateName)) { + throw new Error( + `Unknown template: ${templateName}. Valid templates: ${VALID_TEMPLATES.join(", ")}`, + ) + } + + const templatePath = path.join(TEMPLATES_DIR, `${templateName}.yaml`) + const templateData = readFileAsData(templatePath) + if (!templateData) { + // Template file missing — fall back to default config creation + return createConfigFromDefault() + } + + const { $schema, ...rest } = templateData + writePluginsJson(rest) + return rest +} + +/** + * Resolves a user-facing plugin name (which may be an overridden name from config) + * to the corresponding lockfile key (the original name at install time). + * + * This bridges the naming identity split between config YAML (which supports + * source.name overrides) and the lockfile/disk (which are keyed by the original name). + * + * @param {string} name - The name the user provided (may be overridden or original) + * @param {object|null} lockfile - The parsed lockfile + * @param {object|null} pluginsJson - The parsed config YAML + * @returns {string} The lockfile key that corresponds to this plugin + */ +export function resolveLockfileName(name, lockfile, pluginsJson) { + // Direct match — no resolution needed + if (lockfile?.plugins?.[name]) return name + + // Check if any config entry with this overridden name maps to a different lockfile key + if (pluginsJson?.plugins) { + const configEntry = pluginsJson.plugins.find( + (e) => extractPluginName(e.source) === name || formatSource(e.source) === name, + ) + if (configEntry) { + const url = getSourceUrl(configEntry.source) + for (const [key, lock] of Object.entries(lockfile?.plugins ?? {})) { + if ( + lock.source === url || + lock.source === formatSource(configEntry.source) || + lock.resolved === url + ) { + return key + } + } + } + } + + return name +} + +/** + * Builds a map from lockfile keys to their overridden display names from config. + * Returns entries only where the overridden name differs from the lockfile key. + * + * @param {object|null} lockfile - The parsed lockfile + * @param {object|null} pluginsJson - The parsed config YAML + * @returns {Map} Map of lockfileKey → overriddenName + */ +export function getNameOverrides(lockfile, pluginsJson) { + const overrides = new Map() + if (!lockfile?.plugins || !pluginsJson?.plugins) return overrides + + for (const entry of pluginsJson.plugins) { + const configName = extractPluginName(entry.source) + const url = getSourceUrl(entry.source) + + for (const [lockKey, lock] of Object.entries(lockfile.plugins)) { + if (lockKey === configName) break // no override, names match + if ( + lock.source === url || + lock.source === formatSource(entry.source) || + lock.resolved === url + ) { + overrides.set(lockKey, configName) + break + } + } + } + + return overrides +} + +export const PLUGINS_JSON_PATH = CONFIG_YAML_PATH +export const DEFAULT_PLUGINS_JSON_PATH = DEFAULT_CONFIG_YAML_PATH +export { LOCKFILE_PATH, PLUGINS_DIR } diff --git a/quartz/cli/plugin-git-handlers.js b/quartz/cli/plugin-git-handlers.js new file mode 100644 index 000000000..fb475cd9f --- /dev/null +++ b/quartz/cli/plugin-git-handlers.js @@ -0,0 +1,1759 @@ +import fs from "fs" +import path from "path" +import os from "os" +import { exec as execCb } from "child_process" +import { styleText, promisify } from "util" +import { + readPluginsJson, + writePluginsJson, + readLockfile, + writeLockfile, + extractPluginName, + readManifestFromPackageJson, + parseGitSource, + getGitCommit, + PLUGINS_DIR, + LOCKFILE_PATH, + isLocalSource, + getSourceUrl, + formatSource, + resolveLockfileName, + getNameOverrides, +} from "./plugin-data.js" +import { symlinkOrCopySync } from "./helpers.js" + +const INTERNAL_EXPORTS = new Set(["manifest", "default"]) + +const execAsync = promisify(execCb) + +async function cloneWithSubdirAsync({ url, ref, subdir, pluginDir }) { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "quartz-plugin-")) + try { + if (ref) { + await execAsync(`git clone --depth 1 --branch ${ref} "${url}" "${tmpDir}"`) + } else { + await execAsync(`git clone --depth 1 "${url}" "${tmpDir}"`) + } + const subdirPath = path.join(tmpDir, subdir) + if (!fs.existsSync(subdirPath)) { + throw new Error(`Subdirectory "${subdir}" not found in cloned repository`) + } + fs.cpSync(subdirPath, pluginDir, { recursive: true }) + const { stdout } = await execAsync("git rev-parse HEAD", { cwd: tmpDir }) + return stdout.trim() + } finally { + fs.rmSync(tmpDir, { recursive: true, force: true }) + } +} + +async function buildPluginAsync(pluginDir, name) { + if (hasPrebuiltDist(pluginDir)) { + console.log(styleText("green", ` ✓ ${name}: using pre-built dist/`)) + linkPeerPlugins(pluginDir) + return true + } + + try { + const skipBuild = !needsBuild(pluginDir) + console.log(styleText("cyan", ` → ${name}: installing dependencies...`)) + await execAsync("npm install --ignore-scripts", { cwd: pluginDir }) + if (!skipBuild) { + console.log(styleText("cyan", ` → ${name}: building...`)) + await execAsync("npm run build", { cwd: pluginDir }) + } + await execAsync("npm prune --omit=dev", { cwd: pluginDir }) + linkPeerPlugins(pluginDir) + return true + } catch (error) { + console.log(styleText("red", ` ✗ ${name}: build failed`)) + return false + } +} + +/** + * Run async tasks with bounded concurrency. + * @param {Array} items - Items to process + * @param {number} concurrency - Max parallel tasks + * @param {Function} fn - Async function to run per item + * @returns {Promise} Results in order + */ +async function runParallel(items, concurrency, fn) { + const results = new Array(items.length) + let nextIndex = 0 + + async function worker() { + while (nextIndex < items.length) { + const i = nextIndex++ + results[i] = await fn(items[i], i) + } + } + + const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker()) + await Promise.all(workers) + return results +} + +/** + * Check whether a plugin's .gitignore excludes dist/. + * When dist/ is gitignored, the plugin cannot ship pre-built output in version + * control (e.g. because it uses tree-shaking) and must always be built locally. + */ +function isDistGitignored(pluginDir) { + const gitignorePath = path.join(pluginDir, ".gitignore") + if (!fs.existsSync(gitignorePath)) return false + + const lines = fs.readFileSync(gitignorePath, "utf-8").split("\n") + return lines.some((line) => { + const trimmed = line.trim() + return trimmed === "dist" || trimmed === "dist/" || trimmed === "/dist" || trimmed === "/dist/" + }) +} + +function hasPrebuiltDist(pluginDir) { + const distDir = path.join(pluginDir, "dist") + return fs.existsSync(distDir) && !isDistGitignored(pluginDir) +} + +function needsBuild(pluginDir) { + if (isDistGitignored(pluginDir)) return true + const distDir = path.join(pluginDir, "dist") + return !fs.existsSync(distDir) +} + +/** + * After pruning devDependencies, peerDependencies may no longer be installed + * in the plugin's own node_modules. This function resolves them: + * + * 1. @quartz-community/* peers → symlink to the co-installed sibling plugin + * 2. All other peers → symlink to the host Quartz node_modules so plugins + * share a single copy of packages like unified, vfile, rehype-raw, etc. + */ +function trySymlink(target, linkPath) { + symlinkOrCopySync(target, linkPath) +} + +function linkPeerPlugins(pluginDir) { + const pkgPath = path.join(pluginDir, "package.json") + if (!fs.existsSync(pkgPath)) return + + const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")) + const peers = pkg.peerDependencies ?? {} + + const quartzRoot = path.resolve(pluginDir, "..", "..", "..") + const hostNodeModules = path.join(quartzRoot, "node_modules") + + for (const peerName of Object.keys(peers)) { + const peerNodeModulesPath = path.join(pluginDir, "node_modules", ...peerName.split("/")) + if (fs.existsSync(peerNodeModulesPath)) continue + + if (peerName.startsWith("@quartz-community/")) { + const siblingPlugin = findPluginByPackageName(peerName) + if (!siblingPlugin) continue + + const scopeDir = path.join(pluginDir, "node_modules", peerName.split("/")[0]) + fs.mkdirSync(scopeDir, { recursive: true }) + + const target = path.relative(scopeDir, siblingPlugin) + trySymlink(target, peerNodeModulesPath) + continue + } + + const hostPeerPath = path.join(hostNodeModules, ...peerName.split("/")) + if (!fs.existsSync(hostPeerPath)) continue + + const parts = peerName.split("/") + if (parts.length > 1) { + const scopeDir = path.join(pluginDir, "node_modules", parts[0]) + fs.mkdirSync(scopeDir, { recursive: true }) + } else { + fs.mkdirSync(path.join(pluginDir, "node_modules"), { recursive: true }) + } + + const target = path.relative(path.dirname(peerNodeModulesPath), hostPeerPath) + trySymlink(target, peerNodeModulesPath) + } +} + +/** + * Search installed plugins for one whose package.json "name" matches the given + * npm package name (e.g. "@quartz-community/bases-page"). + */ +function findPluginByPackageName(packageName) { + if (!fs.existsSync(PLUGINS_DIR)) return null + + const plugins = fs.readdirSync(PLUGINS_DIR).filter((entry) => { + const entryPath = path.join(PLUGINS_DIR, entry) + return fs.statSync(entryPath).isDirectory() + }) + + for (const pluginDirName of plugins) { + const pkgPath = path.join(PLUGINS_DIR, pluginDirName, "package.json") + if (!fs.existsSync(pkgPath)) continue + try { + const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")) + if (pkg.name === packageName) { + return path.join(PLUGINS_DIR, pluginDirName) + } + } catch {} + } + return null +} + +const PLUGIN_TYPE_PATTERN = + /Quartz(?:Emitter|Transformer|Filter|PageType)Plugin|QuartzComponentConstructor|\(.*\)\s*=>\s*QuartzComponent\b/ + +function resolveOriginalName(exportName, dtsContent) { + const aliasPattern = new RegExp(`(\\w+)\\s+as\\s+${exportName}\\b`) + const match = dtsContent.match(aliasPattern) + return match ? match[1] : exportName +} + +function isOverridableExport(name, dtsContent) { + const declName = resolveOriginalName(name, dtsContent) + const declPattern = new RegExp(`declare\\s+const\\s+${declName}\\s*:\\s*(.+?)(?:;|$)`, "m") + const match = dtsContent.match(declPattern) + if (!match) return false + return PLUGIN_TYPE_PATTERN.test(match[1]) +} + +function parseExportsFromDts(content) { + const exports = [] + const exportMatches = content.matchAll(/export\s*{\s*([^}]+)\s*}(?:\s*from\s*['"]([^'"]+)['"])?/g) + for (const match of exportMatches) { + const fromModule = match[2] + if (fromModule?.startsWith("@")) continue + + const names = match[1] + .split(",") + .map((n) => n.trim()) + .filter(Boolean) + for (const name of names) { + const cleanName = name.split(" as ").pop()?.trim() || name.trim() + if (cleanName && !cleanName.startsWith("_") && !INTERNAL_EXPORTS.has(cleanName)) { + const finalName = cleanName.replace(/^type\s+/, "") + if (name.includes("type ")) { + exports.push(`type ${finalName}`) + } else { + exports.push(finalName) + } + } + } + } + return exports +} + +async function regeneratePluginIndex() { + if (!fs.existsSync(PLUGINS_DIR)) return + + const pluginDirs = fs.readdirSync(PLUGINS_DIR).filter((name) => { + const pluginPath = path.join(PLUGINS_DIR, name) + return fs.statSync(pluginPath).isDirectory() + }) + + // Phase 1: Collect all exports per plugin, detect conflicts + const pluginExports = new Map() + const nameCount = new Map() + + for (const pluginName of pluginDirs) { + const pluginDir = path.join(PLUGINS_DIR, pluginName) + const distIndex = path.join(pluginDir, "dist", "index.d.ts") + + if (!fs.existsSync(distIndex)) continue + + const dtsContent = fs.readFileSync(distIndex, "utf-8") + const exportedNames = parseExportsFromDts(dtsContent) + const named = exportedNames.filter((e) => !e.startsWith("type ")) + const types = exportedNames.filter((e) => e.startsWith("type ")).map((e) => e.slice(5)) + + const overridable = named.filter((n) => isOverridableExport(n, dtsContent)) + const passthrough = named.filter((n) => !isOverridableExport(n, dtsContent)) + + if (overridable.length > 0 || passthrough.length > 0 || types.length > 0) { + pluginExports.set(pluginName, { overridable, passthrough, types }) + for (const n of [...overridable, ...passthrough]) { + nameCount.set(n, (nameCount.get(n) ?? 0) + 1) + } + } + } + + // Phase 2: Generate index with registry import, plugin map, and conditional top-level exports + const lines = [] + + lines.push(`import { componentRegistry } from "../../quartz/components/registry"`) + lines.push("") + + // Type re-exports + for (const [pluginName, { types }] of pluginExports) { + if (types.length > 0) { + lines.push(`export type { ${types.join(", ")} } from "./${pluginName}"`) + } + } + + // Direct re-exports for non-overridable values (constants, utility functions, etc.) + for (const [pluginName, { passthrough }] of pluginExports) { + if (passthrough.length === 0) continue + const unique = passthrough.filter((n) => (nameCount.get(n) ?? 0) === 1) + if (unique.length > 0) { + lines.push(`export { ${unique.join(", ")} } from "./${pluginName}"`) + } + } + lines.push("") + + // Generate the plugins map with override wrappers (overridable exports only) + lines.push( + `export const plugins: Record void>> = {`, + ) + for (const [pluginName, { overridable }] of pluginExports) { + if (overridable.length === 0) continue + const escapedName = pluginName.replace(/"/g, '\\"') + lines.push(` "${escapedName}": {`) + for (const n of overridable) { + lines.push( + ` ${n}: (...args: unknown[]) => { componentRegistry.setOptionOverrides("${escapedName}", args[0] as Record); },`, + ) + } + lines.push(` },`) + } + lines.push(`}`) + lines.push("") + + // Top-level exports for overridable names: alias to the plugins map wrapper + for (const [pluginName, { overridable }] of pluginExports) { + if (overridable.length === 0) continue + + const unique = overridable.filter((n) => (nameCount.get(n) ?? 0) === 1) + const conflicting = overridable.filter((n) => (nameCount.get(n) ?? 0) > 1) + + if (unique.length > 0) { + const escapedName = pluginName.replace(/"/g, '\\"') + for (const n of unique) { + lines.push(`export const ${n} = plugins["${escapedName}"].${n}`) + } + } + + if (conflicting.length > 0) { + for (const n of conflicting) { + console.warn( + styleText("yellow", `⚠`), + `Export "${n}" conflicts across plugins — use plugins["${pluginName}"].${n} in quartz.ts`, + ) + } + } + } + + lines.push("") + + const indexContent = lines.join("\n") + const indexPath = path.join(PLUGINS_DIR, "index.ts") + fs.writeFileSync(indexPath, indexContent) +} + +export async function handlePluginInstallUnified({ + names, + fromConfig = false, + latest = false, + clean = false, + dryRun = false, + concurrency: concurrencyOption, +} = {}) { + if (clean && latest) { + console.log(styleText("red", "✗ --clean and --latest cannot be used together")) + return + } + + const resolvedConcurrency = Math.max(1, concurrencyOption ?? os.cpus().length) + + const pluginsJson = readPluginsJson() + let lockfile = readLockfile() + + if (!fromConfig && !lockfile) { + console.log( + styleText("yellow", "⚠ No quartz.lock.json found. Run 'npx quartz plugin add ' first."), + ) + return + } + + const resolvedNames = names + ? names.map((name) => + resolveLockfileName(name, lockfile ?? { version: "1.0.0", plugins: {} }, pluginsJson), + ) + : null + const nameFilter = resolvedNames ? new Set(resolvedNames) : null + + if (dryRun && latest) { + if (!lockfile || Object.keys(lockfile.plugins).length === 0) { + console.log(styleText("gray", "No plugins installed")) + return + } + + const nameOverrides = getNameOverrides(lockfile, pluginsJson) + + const rows = Object.entries(lockfile.plugins) + .filter(([name]) => !nameFilter || nameFilter.has(name)) + .map(([name, entry]) => ({ + name, + entry, + displayName: nameOverrides.get(name) ?? name, + })) + + const isTTY = process.stdout.isTTY + const nameWidth = Math.max(6, ...rows.map((row) => row.displayName.length)) + 2 + const header = `${"Plugin".padEnd(nameWidth)}${"Installed".padEnd(12)}${"Latest".padEnd(12)}Status` + + const renderRow = ({ displayName }, installed, latest, statusLabel) => + `${displayName.padEnd(nameWidth)}${installed.padEnd(12)}${latest.padEnd(12)}${statusLabel}` + + const updateRow = (index, installed, latest, statusLabel) => { + if (!isTTY) return + const offset = rows.length - index + process.stdout.write( + `\x1b[${offset}A\x1b[2K\r${renderRow(rows[index], installed, latest, statusLabel)}\x1b[${offset}B`, + ) + } + + if (isTTY) { + console.log(styleText("bold", "Checking for plugin updates...\n")) + console.log(styleText("bold", header)) + console.log("─".repeat(header.length)) + for (const row of rows) { + if (row.entry.commit === "local") { + console.log(renderRow(row, "local", "—", styleText("green", "local"))) + continue + } + console.log(renderRow(row, row.entry.commit.slice(0, 7), "—", styleText("cyan", "⋯"))) + } + } + + const promises = rows.map((row, index) => { + if (row.entry.commit === "local") { + return Promise.resolve({ + index, + installed: "local", + latest: "—", + status: "local", + }) + } + + const lsRemoteRef = row.entry.ref ? `refs/heads/${row.entry.ref}` : "HEAD" + return execAsync(`git ls-remote "${row.entry.resolved}" ${lsRemoteRef}`) + .then(({ stdout }) => { + const latestCommit = stdout.split("\t")[0].trim() + const isCurrent = latestCommit === row.entry.commit + const installed = row.entry.commit.slice(0, 7) + const latest = latestCommit.slice(0, 7) + const statusLabel = isCurrent + ? styleText("green", "up to date") + : styleText("yellow", "update available") + updateRow(index, installed, latest, statusLabel) + return { + index, + installed, + latest, + status: isCurrent ? "up to date" : "update available", + } + }) + .catch(() => { + const installed = row.entry.commit.slice(0, 7) + const latest = "?" + const statusLabel = styleText("red", "check failed") + updateRow(index, installed, latest, statusLabel) + return { + index, + installed, + latest, + status: "check failed", + } + }) + }) + + const results = await Promise.all(promises) + + if (!isTTY) { + console.log(styleText("bold", "Checking for plugin updates...\n")) + console.log(styleText("bold", header)) + console.log("─".repeat(header.length)) + for (const { index, installed, latest, status } of results) { + const color = + status === "up to date" || status === "local" + ? "green" + : status === "check failed" + ? "red" + : "yellow" + console.log(renderRow(rows[index], installed, latest, styleText(color, status))) + } + } + return + } + + if (fromConfig) { + if (!pluginsJson?.plugins || pluginsJson.plugins.length === 0) { + console.log(styleText("gray", "No plugins configured")) + return + } + + if (!lockfile) { + lockfile = { version: "1.0.0", plugins: {} } + } + + if (!fs.existsSync(PLUGINS_DIR)) { + fs.mkdirSync(PLUGINS_DIR, { recursive: true }) + } + + const configNames = new Set(pluginsJson.plugins.map((entry) => extractPluginName(entry.source))) + const orphans = Object.keys(lockfile.plugins).filter((name) => !configNames.has(name)) + + const missing = pluginsJson.plugins + .filter((entry) => { + const name = extractPluginName(entry.source) + const pluginDir = path.join(PLUGINS_DIR, name) + if (lockfile.plugins[name] && fs.existsSync(pluginDir)) return false + const src = getSourceUrl(entry.source) + return ( + src.startsWith("github:") || + src.startsWith("git+") || + src.startsWith("https://") || + isLocalSource(src) + ) + }) + .filter((entry) => { + if (!nameFilter) return true + const name = extractPluginName(entry.source) + return nameFilter.has(name) + }) + + if (missing.length === 0) { + console.log(styleText("green", "✓ All configured plugins are already installed")) + if (dryRun) { + if (orphans.length > 0) { + console.log() + console.log(`Found ${orphans.length} orphaned plugin(s) in lockfile:\n`) + for (const name of orphans) { + console.log(` ${styleText("yellow", name)} — in lockfile but not in config`) + } + console.log() + console.log( + styleText("cyan", "Dry run — no changes made. Re-run without --dry-run to resolve."), + ) + } + return + } + if (orphans.length === 0) { + return + } + } + + if (missing.length > 0) { + console.log(`Found ${missing.length} uninstalled plugin(s) in config:\n`) + for (const entry of missing) { + const name = extractPluginName(entry.source) + console.log(` ${styleText("yellow", name)} — ${formatSource(entry.source)}`) + } + console.log() + + if (dryRun) { + if (orphans.length > 0) { + console.log(`Found ${orphans.length} orphaned plugin(s) in lockfile:\n`) + for (const name of orphans) { + console.log(` ${styleText("yellow", name)} — in lockfile but not in config`) + } + console.log() + } + console.log( + styleText("cyan", "Dry run — no changes made. Re-run without --dry-run to resolve."), + ) + return + } + } + + const installed = [] + let failed = 0 + let lockfileChanged = false + + // Handle existing dirs and local symlinks (fast), collect remote clones + const remoteEntries = [] + for (const entry of missing) { + try { + const { name, url, ref, local, subdir } = parseGitSource(entry.source) + const pluginDir = path.join(PLUGINS_DIR, name) + + if (fs.existsSync(pluginDir)) { + if (local) { + console.log( + styleText("yellow", `⚠ ${name} directory already exists, updating lockfile`), + ) + lockfile.plugins[name] = { + source: entry.source, + resolved: url, + commit: "local", + ...(subdir && { subdir }), + installedAt: new Date().toISOString(), + } + installed.push({ name, pluginDir }) + lockfileChanged = true + continue + } + console.log(styleText("yellow", `⚠ ${name} directory already exists, updating lockfile`)) + const commit = getGitCommit(pluginDir) + lockfile.plugins[name] = { + source: entry.source, + resolved: url, + commit, + ...(ref && { ref }), + ...(subdir && { subdir }), + installedAt: new Date().toISOString(), + } + installed.push({ name, pluginDir }) + lockfileChanged = true + continue + } + + if (local) { + let resolvedPath = path.resolve(url) + if (subdir) resolvedPath = path.join(resolvedPath, subdir) + if (!fs.existsSync(resolvedPath)) { + console.log(styleText("red", `✗ Local path does not exist: ${resolvedPath}`)) + failed++ + continue + } + console.log(styleText("cyan", `→ Linking ${name} from ${resolvedPath}...`)) + fs.mkdirSync(path.dirname(pluginDir), { recursive: true }) + symlinkOrCopySync(resolvedPath, pluginDir) + lockfile.plugins[name] = { + source: entry.source, + resolved: resolvedPath, + commit: "local", + ...(subdir && { subdir }), + installedAt: new Date().toISOString(), + } + installed.push({ name, pluginDir }) + lockfileChanged = true + console.log(styleText("green", `✓ Linked ${name} (local)`)) + } else { + remoteEntries.push({ entry, name, url, ref, subdir, pluginDir }) + } + } catch (error) { + console.log(styleText("red", `✗ Failed to resolve ${formatSource(entry.source)}: ${error}`)) + failed++ + } + } + + // Clone remote plugins in parallel + if (remoteEntries.length > 0) { + const concurrency = resolvedConcurrency + await runParallel( + remoteEntries, + concurrency, + async ({ entry, name, url, ref, subdir, pluginDir }) => { + try { + if (subdir) { + console.log(styleText("cyan", `→ Cloning ${name} from ${url} (subdir: ${subdir})...`)) + fs.mkdirSync(path.dirname(pluginDir), { recursive: true }) + const commit = await cloneWithSubdirAsync({ url, ref, subdir, pluginDir }) + lockfile.plugins[name] = { + source: entry.source, + resolved: url, + commit, + ...(ref && { ref }), + subdir, + installedAt: new Date().toISOString(), + } + installed.push({ name, pluginDir }) + lockfileChanged = true + console.log( + styleText("green", `✓ Cloned ${name}@${commit.slice(0, 7)} (subdir: ${subdir})`), + ) + } else { + console.log(styleText("cyan", `→ Cloning ${name} from ${url}...`)) + + const branchArg = ref ? ` --branch ${ref}` : "" + await execAsync(`git clone --depth 1${branchArg} "${url}" "${pluginDir}"`) + + const { stdout } = await execAsync("git rev-parse HEAD", { cwd: pluginDir }) + const commit = stdout.trim() + lockfile.plugins[name] = { + source: entry.source, + resolved: url, + commit, + ...(ref && { ref }), + installedAt: new Date().toISOString(), + } + + installed.push({ name, pluginDir }) + lockfileChanged = true + console.log(styleText("green", `✓ Cloned ${name}@${commit.slice(0, 7)}`)) + } + } catch (error) { + console.log( + styleText("red", `✗ Failed to resolve ${formatSource(entry.source)}: ${error}`), + ) + failed++ + } + }, + ) + } + + if (installed.length > 0) { + console.log() + console.log(styleText("cyan", "→ Building plugins...")) + const concurrency = resolvedConcurrency + const results = await runParallel(installed, concurrency, async ({ name, pluginDir }) => { + const ok = await buildPluginAsync(pluginDir, name) + if (ok) console.log(styleText("green", ` ✓ ${name} built`)) + return ok + }) + for (const ok of results) { + if (!ok) failed++ + } + await regeneratePluginIndex() + } + + if (orphans.length > 0) { + console.log() + let removedOrphans = false + for (const name of orphans) { + const entry = lockfile.plugins[name] + if (entry?.commit === "local") { + console.log( + styleText( + "yellow", + `⚠ ${name} is a local plugin not in config — skipping (remove manually with 'plugin remove')`, + ), + ) + continue + } + const pluginDir = path.join(PLUGINS_DIR, name) + if (fs.existsSync(pluginDir)) { + fs.rmSync(pluginDir, { recursive: true }) + } + delete lockfile.plugins[name] + lockfileChanged = true + removedOrphans = true + console.log(styleText("yellow", `✗ Removed ${name} (not in config)`)) + } + if (removedOrphans) { + await regeneratePluginIndex() + } + } + + if (lockfileChanged) { + writeLockfile(lockfile) + console.log() + if (failed === 0) { + console.log(styleText("green", `✓ Resolved ${installed.length} plugin(s)`)) + } else { + console.log( + styleText("yellow", `⚠ Resolved ${installed.length} plugin(s), ${failed} failed`), + ) + } + console.log(styleText("gray", "Updated quartz.lock.json")) + } else if (failed > 0) { + console.log() + console.log(styleText("yellow", `⚠ Resolved ${installed.length} plugin(s), ${failed} failed`)) + } + + return + } + + if (dryRun) { + const entries = Object.entries(lockfile.plugins).filter(([name]) => + nameFilter ? nameFilter.has(name) : true, + ) + if (entries.length === 0) { + console.log(styleText("gray", "No plugins installed")) + return + } + + console.log(styleText("cyan", "→ Dry run: plugins to install from lockfile...")) + for (const [name, entry] of entries) { + const sourceLabel = entry.source ? formatSource(entry.source) : entry.resolved + const commitLabel = entry.commit === "local" ? "local" : entry.commit.slice(0, 7) + console.log(` ${styleText("yellow", name)} — ${sourceLabel} (${commitLabel})`) + } + return + } + + if (clean) { + console.log(styleText("cyan", "→ Restoring plugins from lockfile...")) + console.log() + + if (!fs.existsSync(PLUGINS_DIR)) { + fs.mkdirSync(PLUGINS_DIR, { recursive: true }) + } + + let installed = 0 + let failed = 0 + const restoredPlugins = [] + + const entries = Object.entries(lockfile.plugins).filter(([name]) => + nameFilter ? nameFilter.has(name) : true, + ) + + // Handle local symlinks and collect remote plugins to clone + const remotePlugins = [] + for (const [name, entry] of entries) { + const pluginDir = path.join(PLUGINS_DIR, name) + + if (fs.existsSync(pluginDir)) { + console.log(styleText("yellow", `⚠ ${name}: directory exists, skipping`)) + continue + } + + if (entry.commit === "local") { + try { + if (!fs.existsSync(entry.resolved)) { + console.log(styleText("red", ` ✗ ${name}: local path missing: ${entry.resolved}`)) + failed++ + continue + } + fs.mkdirSync(path.dirname(pluginDir), { recursive: true }) + symlinkOrCopySync(entry.resolved, pluginDir) + console.log(styleText("green", `✓ ${name} restored (local symlink)`)) + restoredPlugins.push({ name, pluginDir }) + installed++ + } catch { + console.log(styleText("red", `✗ ${name}: failed to restore local symlink`)) + failed++ + } + continue + } + + remotePlugins.push({ name, entry, pluginDir }) + } + + // Clone remote plugins in parallel + if (remotePlugins.length > 0) { + const concurrency = resolvedConcurrency + await runParallel(remotePlugins, concurrency, async ({ name, entry, pluginDir }) => { + try { + if (entry.subdir) { + console.log( + styleText( + "cyan", + `→ ${name}: cloning ${entry.resolved}@${entry.commit.slice(0, 7)} (subdir: ${entry.subdir})...`, + ), + ) + fs.mkdirSync(path.dirname(pluginDir), { recursive: true }) + await cloneWithSubdirAsync({ + url: entry.resolved, + ref: entry.ref, + subdir: entry.subdir, + pluginDir, + }) + } else { + console.log( + styleText( + "cyan", + `→ ${name}: cloning ${entry.resolved}@${entry.commit.slice(0, 7)}...`, + ), + ) + const branchArg = entry.ref ? ` --branch ${entry.ref}` : "" + await execAsync(`git clone --depth 1${branchArg} "${entry.resolved}" "${pluginDir}"`) + await execAsync(`git checkout ${entry.commit}`, { cwd: pluginDir }) + } + console.log(styleText("green", `✓ ${name} restored`)) + restoredPlugins.push({ name, pluginDir }) + installed++ + } catch { + console.log(styleText("red", `✗ ${name}: failed to restore`)) + failed++ + } + }) + } + + if (restoredPlugins.length > 0) { + console.log() + console.log(styleText("cyan", "→ Building restored plugins...")) + const concurrency = resolvedConcurrency + const results = await runParallel( + restoredPlugins, + concurrency, + async ({ name, pluginDir }) => { + const ok = await buildPluginAsync(pluginDir, name) + if (ok) console.log(styleText("green", ` ✓ ${name} built`)) + return ok + }, + ) + for (const ok of results) { + if (!ok) { + failed++ + installed-- + } + } + await regeneratePluginIndex() + } + + console.log() + if (failed === 0) { + console.log(styleText("green", `✓ Restored ${installed} plugin(s)`)) + } else { + console.log(styleText("yellow", `⚠ Restored ${installed} plugin(s), ${failed} failed`)) + } + return + } + + if (latest) { + const pluginsToUpdate = nameFilter ? Array.from(nameFilter) : Object.keys(lockfile.plugins) + const updatedPlugins = [] + let lockfileChanged = false + + // Phase 1: Validate and categorize plugins (fast, sequential) + const validPlugins = [] + for (const name of pluginsToUpdate) { + const entry = lockfile.plugins[name] + if (!entry) { + console.log(styleText("yellow", `⚠ ${name} is not installed`)) + continue + } + + const pluginDir = path.join(PLUGINS_DIR, name) + if (!fs.existsSync(pluginDir)) { + console.log( + styleText("yellow", `⚠ ${name} directory missing. Run 'npx quartz plugin install'.`), + ) + continue + } + + if (entry.commit === "local") { + console.log(styleText("cyan", `→ Rebuilding local plugin ${name}...`)) + updatedPlugins.push({ name, pluginDir }) + continue + } + + validPlugins.push({ name, pluginDir, entry }) + } + + // Phase 2: Fetch/update plugins in parallel + if (validPlugins.length > 0) { + const concurrency = resolvedConcurrency + await runParallel(validPlugins, concurrency, async ({ name, pluginDir, entry }) => { + try { + console.log(styleText("cyan", `→ Updating ${name}...`)) + + if (entry.subdir) { + fs.rmSync(pluginDir, { recursive: true }) + fs.mkdirSync(path.dirname(pluginDir), { recursive: true }) + const newCommit = await cloneWithSubdirAsync({ + url: entry.resolved, + ref: entry.ref, + subdir: entry.subdir, + pluginDir, + }) + if (needsBuild(pluginDir)) { + updatedPlugins.push({ name, pluginDir }) + } + if (newCommit !== entry.commit) { + entry.commit = newCommit + entry.installedAt = new Date().toISOString() + lockfileChanged = true + console.log( + styleText( + "green", + `✓ Updated ${name} to ${newCommit.slice(0, 7)} (subdir: ${entry.subdir})`, + ), + ) + } else { + console.log(styleText("gray", `✓ ${name} rebuilt (subdir: ${entry.subdir})`)) + } + } else { + const fetchRef = entry.ref || "" + const resetTarget = entry.ref ? `origin/${entry.ref}` : "origin/HEAD" + await execAsync(`git fetch --depth 1 origin${fetchRef ? " " + fetchRef : ""}`, { + cwd: pluginDir, + }) + await execAsync(`git reset --hard ${resetTarget}`, { cwd: pluginDir }) + + const { stdout } = await execAsync("git rev-parse HEAD", { cwd: pluginDir }) + const newCommit = stdout.trim() + if (newCommit !== entry.commit) { + entry.commit = newCommit + entry.installedAt = new Date().toISOString() + updatedPlugins.push({ name, pluginDir }) + lockfileChanged = true + console.log(styleText("green", `✓ Updated ${name} to ${newCommit.slice(0, 7)}`)) + } else { + console.log(styleText("gray", `✓ ${name} already up to date`)) + } + } + } catch (error) { + console.log(styleText("red", `✗ Failed to update ${name}: ${error}`)) + } + }) + } + + // Phase 3: Build updated plugins in parallel + if (updatedPlugins.length > 0) { + console.log() + console.log(styleText("cyan", "→ Rebuilding updated plugins...")) + const concurrency = resolvedConcurrency + await runParallel(updatedPlugins, concurrency, async ({ name, pluginDir }) => { + const ok = await buildPluginAsync(pluginDir, name) + if (ok) console.log(styleText("green", ` ✓ ${name} rebuilt`)) + return ok + }) + await regeneratePluginIndex() + } + + if (lockfileChanged) { + writeLockfile(lockfile) + console.log() + console.log(styleText("gray", "Updated quartz.lock.json")) + } + return + } + + if (!fs.existsSync(PLUGINS_DIR)) { + fs.mkdirSync(PLUGINS_DIR, { recursive: true }) + } + + const entries = Object.entries(lockfile.plugins).filter(([name]) => + nameFilter ? nameFilter.has(name) : true, + ) + if (entries.length === 0) { + console.log(styleText("gray", "No plugins installed")) + return + } + + console.log(styleText("cyan", "→ Installing plugins from lockfile...")) + let installed = 0 + let failed = 0 + const pluginsToBuild = [] + + // Handle local plugins and collect entries needing git operations + const gitEntries = [] + for (const [name, entry] of entries) { + const pluginDir = path.join(PLUGINS_DIR, name) + + if (entry.commit === "local") { + try { + if (fs.existsSync(pluginDir)) { + const stat = fs.lstatSync(pluginDir) + if (stat.isSymbolicLink() && fs.readlinkSync(pluginDir) === entry.resolved) { + console.log(styleText("gray", ` ✓ ${name} (local) already linked`)) + installed++ + continue + } + if (stat.isSymbolicLink()) fs.unlinkSync(pluginDir) + else fs.rmSync(pluginDir, { recursive: true }) + } + if (!fs.existsSync(entry.resolved)) { + console.log(styleText("red", ` ✗ ${name}: local path missing: ${entry.resolved}`)) + failed++ + continue + } + fs.mkdirSync(path.dirname(pluginDir), { recursive: true }) + symlinkOrCopySync(entry.resolved, pluginDir) + console.log(styleText("green", ` ✓ ${name} (local) linked`)) + pluginsToBuild.push({ name, pluginDir }) + installed++ + } catch { + console.log(styleText("red", ` ✗ ${name}: failed to link local path`)) + failed++ + } + continue + } + + if (fs.existsSync(pluginDir)) { + if (entry.subdir) { + if (!needsBuild(pluginDir)) { + console.log( + styleText("gray", ` ✓ ${name}@${entry.commit.slice(0, 7)} already installed (subdir)`), + ) + installed++ + continue + } + pluginsToBuild.push({ name, pluginDir }) + installed++ + } else { + const currentCommit = getGitCommit(pluginDir) + if (currentCommit === entry.commit && !needsBuild(pluginDir)) { + console.log( + styleText("gray", ` ✓ ${name}@${entry.commit.slice(0, 7)} already installed`), + ) + installed++ + continue + } + if (currentCommit !== entry.commit) { + gitEntries.push({ name, entry, pluginDir, action: "update" }) + } else { + pluginsToBuild.push({ name, pluginDir }) + installed++ + } + } + } else { + gitEntries.push({ name, entry, pluginDir, action: "clone" }) + } + } + + // Run git fetch/clone operations in parallel + if (gitEntries.length > 0) { + const concurrency = resolvedConcurrency + await runParallel(gitEntries, concurrency, async ({ name, entry, pluginDir, action }) => { + try { + if (action === "update") { + console.log(styleText("cyan", ` → ${name}: updating to ${entry.commit.slice(0, 7)}...`)) + const fetchRef = entry.ref ? ` ${entry.ref}` : "" + await execAsync(`git fetch --depth 1 origin${fetchRef}`, { cwd: pluginDir }) + await execAsync(`git reset --hard ${entry.commit}`, { cwd: pluginDir }) + pluginsToBuild.push({ name, pluginDir }) + installed++ + } else { + if (entry.subdir) { + console.log(styleText("cyan", ` → ${name}: cloning (subdir: ${entry.subdir})...`)) + fs.mkdirSync(path.dirname(pluginDir), { recursive: true }) + await cloneWithSubdirAsync({ + url: entry.resolved, + ref: entry.ref, + subdir: entry.subdir, + pluginDir, + }) + } else { + console.log(styleText("cyan", ` → ${name}: cloning...`)) + const branchArg = entry.ref ? ` --branch ${entry.ref}` : "" + await execAsync(`git clone --depth 1${branchArg} "${entry.resolved}" "${pluginDir}"`) + if (entry.commit !== "unknown") { + await execAsync(`git fetch --depth 1 origin ${entry.commit}`, { cwd: pluginDir }) + await execAsync(`git checkout ${entry.commit}`, { cwd: pluginDir }) + } + } + console.log(styleText("green", ` ✓ ${name}@${entry.commit.slice(0, 7)}`)) + pluginsToBuild.push({ name, pluginDir }) + installed++ + } + } catch { + console.log( + styleText("red", ` ✗ ${name}: failed to ${action === "update" ? "update" : "clone"}`), + ) + failed++ + } + }) + } + + if (pluginsToBuild.length > 0) { + console.log() + console.log(styleText("cyan", "→ Building plugins...")) + const concurrency = resolvedConcurrency + const results = await runParallel(pluginsToBuild, concurrency, async ({ name, pluginDir }) => { + const ok = await buildPluginAsync(pluginDir, name) + if (ok) console.log(styleText("green", ` ✓ ${name} built`)) + return ok + }) + for (const ok of results) { + if (!ok) { + failed++ + installed-- + } + } + } + + await regeneratePluginIndex() + + console.log() + if (failed === 0) { + console.log(styleText("green", `✓ Installed ${installed} plugin(s)`)) + } else { + console.log(styleText("yellow", `⚠ Installed ${installed} plugin(s), ${failed} failed`)) + } +} + +export async function handlePluginInstall() { + return handlePluginInstallUnified() +} + +export async function handlePluginAdd( + sources, + { name: nameOverride, subdir: subdirOverride, concurrency: concurrencyOption } = {}, +) { + if (nameOverride && sources.length > 1) { + console.log(styleText("red", "✗ --name/--as can only be used when adding a single plugin")) + return + } + if (subdirOverride && sources.length > 1) { + console.log(styleText("red", "✗ --subdir can only be used when adding a single plugin")) + return + } + + const resolvedConcurrency = Math.max(1, concurrencyOption ?? os.cpus().length) + + let lockfile = readLockfile() + if (!lockfile) { + lockfile = { version: "1.0.0", plugins: {} } + } + + if (!fs.existsSync(PLUGINS_DIR)) { + fs.mkdirSync(PLUGINS_DIR, { recursive: true }) + } + + const addedPlugins = [] + + // Handle local plugins and collect remote sources to clone + const remoteSources = [] + for (const source of sources) { + try { + const parsed = parseGitSource(source) + const name = nameOverride ?? parsed.name + const url = parsed.url + const ref = parsed.ref + const local = parsed.local + const subdir = subdirOverride ?? parsed.subdir + const pluginDir = path.join(PLUGINS_DIR, name) + + let configSource = undefined + if (nameOverride || subdirOverride) { + configSource = { repo: source } + if (nameOverride) configSource.name = nameOverride + if (subdirOverride) configSource.subdir = subdirOverride + } + + if (fs.existsSync(pluginDir)) { + console.log(styleText("yellow", `⚠ ${name} already exists. Use 'update' to refresh.`)) + continue + } + + if (local) { + let resolvedPath = path.resolve(url) + if (subdir) resolvedPath = path.join(resolvedPath, subdir) + if (!fs.existsSync(resolvedPath)) { + console.log(styleText("red", `✗ Local path does not exist: ${resolvedPath}`)) + continue + } + console.log(styleText("cyan", `→ Adding ${name} from local path ${resolvedPath}...`)) + fs.mkdirSync(path.dirname(pluginDir), { recursive: true }) + symlinkOrCopySync(resolvedPath, pluginDir) + lockfile.plugins[name] = { + source, + resolved: resolvedPath, + commit: "local", + ...(subdir && { subdir }), + installedAt: new Date().toISOString(), + } + addedPlugins.push({ name, pluginDir, source, configSource }) + console.log(styleText("green", `✓ Added ${name} (local symlink)`)) + } else { + remoteSources.push({ source, name, url, ref, subdir, pluginDir, configSource }) + } + } catch (error) { + console.log(styleText("red", `✗ Failed to add ${formatSource(source)}: ${error}`)) + } + } + + // Clone remote plugins in parallel + if (remoteSources.length > 0) { + const concurrency = resolvedConcurrency + await runParallel( + remoteSources, + concurrency, + async ({ source, name, url, ref, subdir, pluginDir, configSource }) => { + try { + if (subdir) { + console.log(styleText("cyan", `→ Adding ${name} from ${url} (subdir: ${subdir})...`)) + fs.mkdirSync(path.dirname(pluginDir), { recursive: true }) + const commit = await cloneWithSubdirAsync({ url, ref, subdir, pluginDir }) + lockfile.plugins[name] = { + source, + resolved: url, + commit, + ...(ref && { ref }), + subdir, + installedAt: new Date().toISOString(), + } + addedPlugins.push({ name, pluginDir, source, configSource }) + console.log( + styleText("green", `✓ Added ${name}@${commit.slice(0, 7)} (subdir: ${subdir})`), + ) + } else { + console.log(styleText("cyan", `→ Adding ${name} from ${url}...`)) + + const branchArg = ref ? ` --branch ${ref}` : "" + await execAsync(`git clone --depth 1${branchArg} "${url}" "${pluginDir}"`) + + const { stdout } = await execAsync("git rev-parse HEAD", { cwd: pluginDir }) + const commit = stdout.trim() + lockfile.plugins[name] = { + source, + resolved: url, + commit, + ...(ref && { ref }), + installedAt: new Date().toISOString(), + } + + addedPlugins.push({ name, pluginDir, source, configSource }) + console.log(styleText("green", `✓ Added ${name}@${commit.slice(0, 7)}`)) + } + } catch (error) { + console.log(styleText("red", `✗ Failed to add ${formatSource(source)}: ${error}`)) + } + }, + ) + } + + if (addedPlugins.length > 0) { + console.log() + console.log(styleText("cyan", "→ Building plugins...")) + const concurrency = resolvedConcurrency + await runParallel(addedPlugins, concurrency, async ({ name, pluginDir }) => { + const ok = await buildPluginAsync(pluginDir, name) + if (ok) console.log(styleText("green", ` ✓ ${name} built`)) + return ok + }) + await regeneratePluginIndex() + } + + writeLockfile(lockfile) + const pluginsJson = readPluginsJson() + if (pluginsJson?.plugins) { + for (const { pluginDir, source, configSource } of addedPlugins) { + const manifest = readManifestFromPackageJson(pluginDir) + const newEntry = { + source: configSource ?? source, + enabled: manifest?.defaultEnabled ?? true, + options: manifest?.defaultOptions ?? {}, + order: manifest?.defaultOrder ?? 50, + } + + if (manifest?.components) { + const layoutPositions = new Set(["left", "right", "beforeBody", "afterBody"]) + const firstComponentKey = Object.keys(manifest.components)[0] + const comp = manifest.components[firstComponentKey] + if (comp?.defaultPosition && layoutPositions.has(comp.defaultPosition)) { + newEntry.layout = { + position: comp.defaultPosition, + priority: comp.defaultPriority ?? 50, + } + } + } + + pluginsJson.plugins.push(newEntry) + } + writePluginsJson(pluginsJson) + } + console.log() + console.log(styleText("gray", "Updated quartz.lock.json")) +} + +export async function handlePluginRemove(names) { + const lockfile = readLockfile() + if (!lockfile) { + console.log(styleText("yellow", "⚠ No plugins installed")) + return + } + + const pluginsJson = readPluginsJson() + let removed = false + const resolvedNames = [] + for (const name of names) { + const lockKey = resolveLockfileName(name, lockfile, pluginsJson) + resolvedNames.push(lockKey) + const pluginDir = path.join(PLUGINS_DIR, lockKey) + + if (!lockfile.plugins[lockKey] && !fs.existsSync(pluginDir)) { + console.log(styleText("yellow", `⚠ ${name} is not installed`)) + continue + } + + const displayName = lockKey !== name ? `${name} (${lockKey})` : name + console.log(styleText("cyan", `→ Removing ${displayName}...`)) + + if (fs.existsSync(pluginDir)) { + fs.rmSync(pluginDir, { recursive: true }) + } + + delete lockfile.plugins[lockKey] + console.log(styleText("green", `✓ Removed ${displayName}`)) + removed = true + } + + if (removed) { + await regeneratePluginIndex() + } + + writeLockfile(lockfile) + if (pluginsJson?.plugins) { + pluginsJson.plugins = pluginsJson.plugins.filter( + (plugin) => + !names.includes(extractPluginName(plugin.source)) && + !names.includes(formatSource(plugin.source)) && + !resolvedNames.includes(extractPluginName(plugin.source)), + ) + writePluginsJson(pluginsJson) + } + console.log() + console.log(styleText("gray", "Updated quartz.lock.json")) +} + +export async function handlePluginEnable(names) { + const json = readPluginsJson() + if (!json) { + console.log(styleText("red", "✗ No quartz.config.yaml found. Cannot enable plugins.")) + return + } + + for (const name of names) { + const entry = json.plugins.find( + (e) => extractPluginName(e.source) === name || formatSource(e.source) === name, + ) + if (!entry) { + console.log(styleText("yellow", `⚠ Plugin "${name}" not found in quartz.config.yaml`)) + continue + } + if (entry.enabled) { + console.log(styleText("gray", `✓ ${name} is already enabled`)) + continue + } + entry.enabled = true + console.log(styleText("green", `✓ Enabled ${name}`)) + } + + writePluginsJson(json) +} + +export async function handlePluginDisable(names) { + const json = readPluginsJson() + if (!json) { + console.log(styleText("red", "✗ No quartz.config.yaml found. Cannot disable plugins.")) + return + } + + for (const name of names) { + const entry = json.plugins.find( + (e) => extractPluginName(e.source) === name || formatSource(e.source) === name, + ) + if (!entry) { + console.log(styleText("yellow", `⚠ Plugin "${name}" not found in quartz.config.yaml`)) + continue + } + if (!entry.enabled) { + console.log(styleText("gray", `✓ ${name} is already disabled`)) + continue + } + entry.enabled = false + console.log(styleText("green", `✓ Disabled ${name}`)) + } + + writePluginsJson(json) +} + +export async function handlePluginConfig(name, options = {}) { + const json = readPluginsJson() + if (!json) { + console.log(styleText("red", "✗ No quartz.config.yaml found.")) + return + } + + const entry = json.plugins.find( + (e) => extractPluginName(e.source) === name || formatSource(e.source) === name, + ) + if (!entry) { + console.log(styleText("red", `✗ Plugin "${name}" not found in quartz.config.yaml`)) + return + } + + if (options.set) { + const eqIndex = options.set.indexOf("=") + if (eqIndex === -1) { + console.log(styleText("red", "✗ Invalid format. Use: --set key=value")) + return + } + const key = options.set.slice(0, eqIndex) + let value = options.set.slice(eqIndex + 1) + + try { + value = JSON.parse(value) + } catch {} + + if (!entry.options) entry.options = {} + entry.options[key] = value + writePluginsJson(json) + console.log(styleText("green", `✓ Set ${name}.${key} = ${JSON.stringify(value)}`)) + } else { + console.log(styleText("bold", `Plugin: ${name}`)) + console.log(` Source: ${formatSource(entry.source)}`) + console.log(` Enabled: ${entry.enabled}`) + console.log(` Order: ${entry.order ?? 50}`) + if (entry.options && Object.keys(entry.options).length > 0) { + console.log(` Options:`) + for (const [k, v] of Object.entries(entry.options)) { + console.log(` ${k}: ${JSON.stringify(v)}`) + } + } else { + console.log(` Options: (none)`) + } + if (entry.layout) { + console.log(` Layout:`) + for (const [k, v] of Object.entries(entry.layout)) { + console.log(` ${k}: ${JSON.stringify(v)}`) + } + } + } +} + +export async function handlePluginCheck() { + return handlePluginInstallUnified({ latest: true, dryRun: true }) +} + +export async function handlePluginUpdate(names) { + return handlePluginInstallUnified({ names, latest: true }) +} + +export async function handlePluginList() { + const lockfile = readLockfile() + if (!lockfile || Object.keys(lockfile.plugins).length === 0) { + console.log(styleText("gray", "No plugins installed")) + return + } + + const pluginsJson = readPluginsJson() + const nameOverrides = getNameOverrides(lockfile, pluginsJson) + + console.log(styleText("bold", "Installed Plugins:")) + console.log() + + for (const [name, entry] of Object.entries(lockfile.plugins)) { + const pluginDir = path.join(PLUGINS_DIR, name) + const exists = fs.existsSync(pluginDir) + const overriddenName = nameOverrides.get(name) + const displayLabel = overriddenName + ? `${overriddenName} ${styleText("gray", `(dir: ${name})`)}` + : name + + if (entry.commit === "local") { + const isLinked = exists && fs.lstatSync(pluginDir).isSymbolicLink() + const status = isLinked ? styleText("green", "✓") : styleText("red", "✗") + console.log(` ${status} ${styleText("bold", displayLabel)}`) + console.log(` Source: ${formatSource(entry.source)}`) + console.log(` Type: local symlink`) + console.log(` Target: ${entry.resolved}`) + console.log(` Installed: ${new Date(entry.installedAt).toLocaleDateString()}`) + console.log() + continue + } + + let currentCommit = entry.commit + + if (exists) { + currentCommit = getGitCommit(pluginDir) + } + + const status = exists + ? currentCommit === entry.commit + ? styleText("green", "✓") + : styleText("yellow", "⚡") + : styleText("red", "✗") + + console.log(` ${status} ${styleText("bold", displayLabel)}`) + console.log(` Source: ${formatSource(entry.source)}`) + console.log(` Commit: ${entry.commit.slice(0, 7)}`) + if (currentCommit !== entry.commit && exists) { + console.log(` Current: ${currentCommit.slice(0, 7)} (modified)`) + } + console.log(` Installed: ${new Date(entry.installedAt).toLocaleDateString()}`) + console.log() + } +} + +export async function handlePluginStatus() { + const lockfile = readLockfile() + if (!lockfile || Object.keys(lockfile.plugins).length === 0) { + console.log(styleText("gray", "No plugins installed")) + return + } + + const pluginsJson = readPluginsJson() + const nameOverrides = getNameOverrides(lockfile, pluginsJson) + const enabledByName = new Map( + (pluginsJson?.plugins ?? []).map((entry) => [ + extractPluginName(entry.source), + entry.enabled !== false, + ]), + ) + + const rows = Object.entries(lockfile.plugins).map(([name, entry]) => { + const pluginDir = path.join(PLUGINS_DIR, name) + const exists = fs.existsSync(pluginDir) + const displayName = nameOverrides.get(name) ?? name + const sourceLabel = formatSource(entry.source) + const commitLabel = entry.commit === "local" ? "local" : `@${entry.commit.slice(0, 7)}` + const enabled = enabledByName.get(name) ?? false + return { name, entry, exists, displayName, sourceLabel, commitLabel, enabled } + }) + + const nameWidth = Math.max(8, ...rows.map((row) => row.displayName.length)) + 2 + const sourceWidth = Math.max(8, ...rows.map((row) => row.sourceLabel.length)) + 2 + const commitWidth = Math.max(6, ...rows.map((row) => row.commitLabel.length)) + 2 + const enabledWidth = Math.max("enabled".length, "disabled".length) + 2 + const updateWidth = + Math.max( + "— local".length, + "⋯".length, + "✓ up to date".length, + "↑ update available".length, + "✗ check failed".length, + ) + 2 + + const formatRow = (row, updateLabel, updateText) => { + const statusIcon = row.exists ? styleText("green", "✓") : styleText("red", "✗") + const enabledText = row.enabled ? "enabled" : "disabled" + const enabledLabel = row.enabled + ? styleText("green", enabledText) + : styleText("gray", enabledText) + const enabledColumn = `${enabledLabel}${" ".repeat(enabledWidth - enabledText.length)}` + const updateColumn = `${updateLabel}${" ".repeat(Math.max(0, updateWidth - updateText.length))}` + return ` ${statusIcon} ${row.displayName.padEnd(nameWidth)}${row.sourceLabel.padEnd( + sourceWidth, + )}${row.commitLabel.padEnd(commitWidth)}${enabledColumn}${updateColumn}` + } + + const updateDisplay = (status) => { + switch (status) { + case "local": + return { text: "— local", label: styleText("gray", "— local") } + case "up_to_date": + return { text: "✓ up to date", label: styleText("green", "✓ up to date") } + case "update_available": + return { text: "↑ update available", label: styleText("yellow", "↑ update available") } + case "failed": + return { text: "✗ check failed", label: styleText("red", "✗ check failed") } + default: + return { text: "⋯", label: styleText("cyan", "⋯") } + } + } + + const isTTY = process.stdout.isTTY + + const updateLine = (index, updateLabel, updateText) => { + if (!isTTY) return + const offset = rows.length - index + process.stdout.write( + `\x1b[${offset}A\x1b[2K\r${formatRow(rows[index], updateLabel, updateText)}\x1b[${offset}B`, + ) + } + + if (isTTY) { + console.log(styleText("bold", "Installed Plugins:")) + console.log() + for (const row of rows) { + const display = + row.entry.commit === "local" ? updateDisplay("local") : updateDisplay("checking") + console.log(formatRow(row, display.label, display.text)) + } + } + + const promises = rows.map((row, index) => { + if (row.entry.commit === "local") { + return Promise.resolve({ + index, + status: "local", + name: row.displayName, + }) + } + + const lsRemoteRef = row.entry.ref ? `refs/heads/${row.entry.ref}` : "HEAD" + return execAsync(`git ls-remote "${row.entry.resolved}" ${lsRemoteRef}`) + .then(({ stdout }) => { + const latestCommit = stdout.split("\t")[0].trim() + const status = latestCommit === row.entry.commit ? "up_to_date" : "update_available" + const display = updateDisplay(status) + updateLine(index, display.label, display.text) + return { index, status, name: row.displayName } + }) + .catch(() => { + const display = updateDisplay("failed") + updateLine(index, display.label, display.text) + return { index, status: "failed", name: row.displayName } + }) + }) + + const results = await Promise.all(promises) + const updatesAvailable = results + .filter((result) => result.status === "update_available") + .map((result) => result.name) + const failedChecks = results + .filter((result) => result.status === "failed") + .map((result) => result.name) + + if (!isTTY) { + console.log(styleText("bold", "Installed Plugins:")) + console.log() + for (const result of results) { + const row = rows[result.index] + const display = updateDisplay(result.status) + console.log(formatRow(row, display.label, display.text)) + } + } + + if (updatesAvailable.length === 0 && failedChecks.length === 0) { + console.log(styleText("green", "\n✓ All plugins up to date")) + return + } + + if (updatesAvailable.length > 0) { + console.log(styleText("yellow", `\nUpdates available: ${updatesAvailable.join(", ")}`)) + console.log(styleText("gray", "Run 'npx quartz plugin install --latest' to update.")) + } + + if (failedChecks.length > 0) { + console.log(styleText("red", `\nChecks failed: ${failedChecks.join(", ")}`)) + } +} + +export async function handlePluginRestore() { + return handlePluginInstallUnified({ clean: true }) +} + +export async function handlePluginPrune({ dryRun = false } = {}) { + const lockfile = readLockfile() + if (!lockfile || Object.keys(lockfile.plugins).length === 0) { + console.log(styleText("gray", "No plugins installed")) + return + } + + const pluginsJson = readPluginsJson() + const configuredNames = new Set( + (pluginsJson?.plugins ?? []).map((entry) => extractPluginName(entry.source)), + ) + + const orphans = Object.keys(lockfile.plugins).filter((name) => !configuredNames.has(name)) + + if (orphans.length === 0) { + console.log(styleText("green", "✓ No orphaned plugins found — nothing to prune")) + return + } + + console.log(`Found ${orphans.length} orphaned plugin(s):\n`) + for (const name of orphans) { + console.log(` ${styleText("yellow", name)} — in lockfile but not in config`) + } + console.log() + + if (dryRun) { + console.log(styleText("cyan", "Dry run — no changes made. Re-run without --dry-run to prune.")) + return + } + + let removed = 0 + for (const name of orphans) { + const pluginDir = path.join(PLUGINS_DIR, name) + + console.log(styleText("cyan", `→ Removing ${name}...`)) + + if (fs.existsSync(pluginDir)) { + fs.rmSync(pluginDir, { recursive: true }) + } + + delete lockfile.plugins[name] + console.log(styleText("green", `✓ Removed ${name}`)) + removed++ + } + + if (removed > 0) { + await regeneratePluginIndex() + } + + writeLockfile(lockfile) + console.log() + console.log(styleText("green", `✓ Pruned ${removed} plugin(s)`)) + console.log(styleText("gray", "Updated quartz.lock.json")) +} + +export async function handlePluginResolve({ dryRun = false } = {}) { + return handlePluginInstallUnified({ fromConfig: true, dryRun }) +} diff --git a/quartz/cli/templates/blog.yaml b/quartz/cli/templates/blog.yaml new file mode 100644 index 000000000..5ca9c0e56 --- /dev/null +++ b/quartz/cli/templates/blog.yaml @@ -0,0 +1,291 @@ +# yaml-language-server: $schema=../../plugins/quartz-plugins.schema.json +# Template: blog +# A blog-focused setup with recent notes and comments enabled. +configuration: + pageTitle: Quartz 5 + pageTitleSuffix: "" + enableSPA: true + enablePopovers: true + analytics: + provider: plausible + locale: en-US + baseUrl: quartz.jzhao.xyz + ignorePatterns: + - private + - templates + - .obsidian + theme: + fontOrigin: googleFonts + cdnCaching: true + typography: + header: Schibsted Grotesk + body: Source Sans Pro + code: IBM Plex Mono + colors: + lightMode: + light: "#faf8f8" + lightgray: "#e5e5e5" + gray: "#b8b8b8" + darkgray: "#4e4e4e" + dark: "#2b2b2b" + secondary: "#284b63" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#fff23688" + darkMode: + light: "#161618" + lightgray: "#393639" + gray: "#646464" + darkgray: "#d4d4d4" + dark: "#ebebec" + secondary: "#7b97aa" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#b3aa0288" +plugins: + - source: github:quartz-community/created-modified-date + enabled: true + options: + defaultDateType: modified + priority: + - frontmatter + - git + - filesystem + order: 10 + - source: github:quartz-community/syntax-highlighting + enabled: true + options: + theme: + light: github-light + dark: github-dark + keepBackground: false + order: 20 + - source: github:quartz-community/obsidian-flavored-markdown + enabled: true + options: + enableInHtmlEmbed: false + enableCheckbox: true + order: 30 + - source: github:quartz-community/github-flavored-markdown + enabled: true + order: 40 + - source: github:quartz-community/table-of-contents + enabled: true + order: 50 + layout: + position: right + priority: 30 + - source: github:quartz-community/crawl-links + enabled: true + options: + markdownLinkResolution: shortest + order: 60 + - source: github:quartz-community/description + enabled: true + order: 70 + - source: github:quartz-community/latex + enabled: true + options: + renderEngine: katex + order: 80 + - source: github:quartz-community/citations + enabled: false + order: 85 + - source: github:quartz-community/hard-line-breaks + enabled: false + order: 90 + - source: github:quartz-community/ox-hugo + enabled: false + order: 91 + - source: github:quartz-community/roam + enabled: false + order: 92 + - source: github:quartz-community/fonts + enabled: true + - source: github:quartz-community/remove-draft + enabled: true + - source: github:quartz-community/explicit-publish + enabled: false + - source: github:quartz-community/unlisted-pages + enabled: true + options: {} + order: 45 + - source: github:quartz-community/encrypted-pages + enabled: false + - source: github:quartz-community/stacked-pages + enabled: false + layout: + position: afterBody + priority: 50 + display: all + - source: github:quartz-community/alias-redirects + enabled: true + - source: github:quartz-community/content-index + enabled: true + options: + enableSiteMap: true + enableRSS: true + - source: github:quartz-community/favicon + enabled: true + - source: github:quartz-community/og-image + enabled: true + - source: github:quartz-community/cname + enabled: true + - source: github:quartz-community/canvas-page + enabled: true + - source: github:quartz-community/content-page + enabled: true + - source: github:quartz-community/folder-page + enabled: true + - source: github:quartz-community/tag-page + enabled: true + - source: github:quartz-community/explorer + enabled: true + layout: + position: left + priority: 50 + - source: github:quartz-community/graph + enabled: true + layout: + position: right + priority: 10 + - source: github:quartz-community/search + enabled: true + layout: + position: left + priority: 20 + group: toolbar + groupOptions: + grow: true + - source: github:quartz-community/backlinks + enabled: true + layout: + position: right + priority: 50 + - source: github:quartz-community/article-title + enabled: true + layout: + position: beforeBody + priority: 10 + - source: github:quartz-community/content-meta + enabled: true + layout: + position: beforeBody + priority: 20 + - source: github:quartz-community/tag-list + enabled: false + layout: + position: beforeBody + priority: 30 + - source: github:quartz-community/page-title + enabled: true + layout: + position: left + priority: 10 + - source: github:quartz-community/darkmode + enabled: true + layout: + position: left + priority: 30 + group: toolbar + - source: github:quartz-community/reader-mode + enabled: true + layout: + position: left + priority: 35 + group: toolbar + - source: github:quartz-community/breadcrumbs + enabled: true + layout: + position: beforeBody + priority: 5 + condition: not-index + - source: github:quartz-community/comments + enabled: true + options: + provider: giscus + options: + repo: "TODO:username/repo-name" + repoId: "TODO:your-repo-id" + category: Announcements + categoryId: "TODO:your-category-id" + mapping: url + strict: true + reactionsEnabled: true + inputPosition: bottom + lightTheme: light + darkTheme: dark + lang: en + layout: + position: afterBody + priority: 10 + - source: github:quartz-community/footer + enabled: true + options: + links: + GitHub: https://github.com/jackyzha0/quartz + Discord Community: https://discord.gg/cRFFHYye7t + - source: github:quartz-community/recent-notes + enabled: true + options: + title: Recent Notes + limit: 5 + linkToMore: false + showTags: true + layout: + position: left + priority: 25 + - source: github:quartz-community/spacer + enabled: true + options: {} + order: 25 + layout: + position: left + priority: 25 + display: mobile-only + - source: github:quartz-community/bases-page + enabled: true + options: {} + order: 50 + - source: github:quartz-community/note-properties + enabled: true + options: + includeAll: false + includedProperties: + - description + - tags + - aliases + excludedProperties: [] + hidePropertiesView: false + delimiters: "---" + language: yaml + order: 5 + layout: + position: beforeBody + priority: 15 + display: all +layout: + groups: + toolbar: + priority: 35 + direction: row + gap: 0.5rem + byPageType: + "404": + positions: + beforeBody: [] + left: [] + right: [] + content: {} + folder: + exclude: + - reader-mode + positions: + right: [] + tag: + exclude: + - reader-mode + positions: + right: [] + canvas: {} + bases: {} diff --git a/quartz/cli/templates/default.yaml b/quartz/cli/templates/default.yaml new file mode 100644 index 000000000..111654b3b --- /dev/null +++ b/quartz/cli/templates/default.yaml @@ -0,0 +1,277 @@ +# yaml-language-server: $schema=../../plugins/quartz-plugins.schema.json +# Template: default +# A clean Quartz setup with sensible defaults. +configuration: + pageTitle: Quartz 5 + pageTitleSuffix: "" + enableSPA: true + enablePopovers: true + analytics: + provider: plausible + locale: en-US + baseUrl: quartz.jzhao.xyz + ignorePatterns: + - private + - templates + - .obsidian + theme: + fontOrigin: googleFonts + cdnCaching: true + typography: + header: Schibsted Grotesk + body: Source Sans Pro + code: IBM Plex Mono + colors: + lightMode: + light: "#faf8f8" + lightgray: "#e5e5e5" + gray: "#b8b8b8" + darkgray: "#4e4e4e" + dark: "#2b2b2b" + secondary: "#284b63" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#fff23688" + darkMode: + light: "#161618" + lightgray: "#393639" + gray: "#646464" + darkgray: "#d4d4d4" + dark: "#ebebec" + secondary: "#7b97aa" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#b3aa0288" +plugins: + - source: github:quartz-community/created-modified-date + enabled: true + options: + defaultDateType: modified + priority: + - frontmatter + - git + - filesystem + order: 10 + - source: github:quartz-community/syntax-highlighting + enabled: true + options: + theme: + light: github-light + dark: github-dark + keepBackground: false + order: 20 + - source: github:quartz-community/obsidian-flavored-markdown + enabled: true + options: + enableInHtmlEmbed: false + enableCheckbox: true + order: 30 + - source: github:quartz-community/github-flavored-markdown + enabled: true + order: 40 + - source: github:quartz-community/table-of-contents + enabled: true + order: 50 + layout: + position: right + priority: 30 + - source: github:quartz-community/crawl-links + enabled: true + options: + markdownLinkResolution: shortest + order: 60 + - source: github:quartz-community/description + enabled: true + order: 70 + - source: github:quartz-community/latex + enabled: true + options: + renderEngine: katex + order: 80 + - source: github:quartz-community/citations + enabled: false + order: 85 + - source: github:quartz-community/hard-line-breaks + enabled: false + order: 90 + - source: github:quartz-community/ox-hugo + enabled: false + order: 91 + - source: github:quartz-community/roam + enabled: false + order: 92 + - source: github:quartz-community/fonts + enabled: true + - source: github:quartz-community/remove-draft + enabled: true + - source: github:quartz-community/explicit-publish + enabled: false + - source: github:quartz-community/unlisted-pages + enabled: true + options: {} + order: 45 + - source: github:quartz-community/encrypted-pages + enabled: true + options: + iterations: 600000 + passwordField: password + unlistWhenEncrypted: false + outputPath: static/encryptedContentIndex.json + - source: github:quartz-community/stacked-pages + enabled: false + layout: + position: afterBody + priority: 50 + display: all + - source: github:quartz-community/alias-redirects + enabled: true + - source: github:quartz-community/content-index + enabled: true + options: + enableSiteMap: true + enableRSS: true + - source: github:quartz-community/favicon + enabled: true + - source: github:quartz-community/og-image + enabled: true + - source: github:quartz-community/cname + enabled: true + - source: github:quartz-community/canvas-page + enabled: true + - source: github:quartz-community/content-page + enabled: true + - source: github:quartz-community/folder-page + enabled: true + - source: github:quartz-community/tag-page + enabled: true + - source: github:quartz-community/explorer + enabled: true + layout: + position: left + priority: 50 + - source: github:quartz-community/graph + enabled: true + layout: + position: right + priority: 10 + - source: github:quartz-community/search + enabled: true + layout: + position: left + priority: 20 + group: toolbar + groupOptions: + grow: true + - source: github:quartz-community/backlinks + enabled: true + layout: + position: right + priority: 50 + - source: github:quartz-community/article-title + enabled: true + layout: + position: beforeBody + priority: 10 + - source: github:quartz-community/content-meta + enabled: true + layout: + position: beforeBody + priority: 20 + - source: github:quartz-community/tag-list + enabled: false + layout: + position: beforeBody + priority: 30 + - source: github:quartz-community/page-title + enabled: true + layout: + position: left + priority: 10 + - source: github:quartz-community/darkmode + enabled: true + layout: + position: left + priority: 30 + group: toolbar + - source: github:quartz-community/reader-mode + enabled: true + layout: + position: left + priority: 35 + group: toolbar + - source: github:quartz-community/breadcrumbs + enabled: true + layout: + position: beforeBody + priority: 5 + condition: not-index + - source: github:quartz-community/comments + enabled: false + options: + provider: giscus + options: {} + layout: + position: afterBody + priority: 10 + - source: github:quartz-community/footer + enabled: true + options: + links: + GitHub: https://github.com/jackyzha0/quartz + Discord Community: https://discord.gg/cRFFHYye7t + - source: github:quartz-community/recent-notes + enabled: false + - source: github:quartz-community/spacer + enabled: true + options: {} + order: 25 + layout: + position: left + priority: 25 + display: mobile-only + - source: github:quartz-community/bases-page + enabled: true + options: {} + order: 50 + - source: github:quartz-community/note-properties + enabled: true + options: + includeAll: false + includedProperties: + - description + - tags + - aliases + excludedProperties: [] + hidePropertiesView: false + delimiters: "---" + language: yaml + order: 5 + layout: + position: beforeBody + priority: 15 + display: all +layout: + groups: + toolbar: + priority: 35 + direction: row + gap: 0.5rem + byPageType: + "404": + positions: + beforeBody: [] + left: [] + right: [] + content: {} + folder: + exclude: + - reader-mode + positions: + right: [] + tag: + exclude: + - reader-mode + positions: + right: [] + canvas: {} + bases: {} diff --git a/quartz/cli/templates/obsidian.yaml b/quartz/cli/templates/obsidian.yaml new file mode 100644 index 000000000..b45dfb3f6 --- /dev/null +++ b/quartz/cli/templates/obsidian.yaml @@ -0,0 +1,302 @@ +# yaml-language-server: $schema=../../plugins/quartz-plugins.schema.json +# Template: obsidian +# Optimized for Obsidian vaults with full OFM support and shortest link resolution. +configuration: + pageTitle: Quartz 5 + pageTitleSuffix: "" + enableSPA: true + enablePopovers: true + analytics: + provider: plausible + locale: en-US + baseUrl: quartz.jzhao.xyz + ignorePatterns: + - private + - templates + - .obsidian + theme: + fontOrigin: googleFonts + cdnCaching: true + typography: + header: Schibsted Grotesk + body: Source Sans Pro + code: IBM Plex Mono + colors: + lightMode: + light: "#faf8f8" + lightgray: "#e5e5e5" + gray: "#b8b8b8" + darkgray: "#4e4e4e" + dark: "#2b2b2b" + secondary: "#284b63" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#fff23688" + darkMode: + light: "#161618" + lightgray: "#393639" + gray: "#646464" + darkgray: "#d4d4d4" + dark: "#ebebec" + secondary: "#7b97aa" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#b3aa0288" +plugins: + - source: github:quartz-community/created-modified-date + enabled: true + options: + defaultDateType: modified + priority: + - frontmatter + - git + - filesystem + order: 10 + - source: github:quartz-community/syntax-highlighting + enabled: true + options: + theme: + light: github-light + dark: github-dark + keepBackground: false + order: 20 + - source: github:quartz-community/obsidian-flavored-markdown + enabled: true + options: + comments: true + highlight: true + wikilinks: true + callouts: true + mermaid: true + parseTags: true + parseArrows: true + parseBlockReferences: true + enableInHtmlEmbed: false + enableYouTubeEmbed: true + enableVideoEmbed: true + enableCheckbox: true + order: 30 + - source: github:quartz-community/github-flavored-markdown + enabled: true + order: 40 + - source: github:quartz-community/table-of-contents + enabled: true + order: 50 + layout: + position: right + priority: 30 + - source: github:quartz-community/crawl-links + enabled: true + options: + markdownLinkResolution: shortest + # disableBrokenWikilinks: false # Set true to add a "broken" CSS class to internal links whose target is not in ctx.allSlugs. + order: 60 + - source: github:quartz-community/description + enabled: true + order: 70 + - source: github:quartz-community/latex + enabled: true + options: + renderEngine: katex + order: 80 + - source: github:quartz-community/citations + enabled: false + order: 85 + - source: github:quartz-community/hard-line-breaks + enabled: true + order: 90 + - source: github:quartz-community/ox-hugo + enabled: false + order: 91 + - source: github:quartz-community/roam + enabled: false + order: 92 + - source: github:quartz-community/fonts + enabled: true + - source: github:quartz-community/remove-draft + enabled: true + - source: github:quartz-community/explicit-publish + enabled: false + - source: github:quartz-community/unlisted-pages + enabled: true + options: {} + order: 45 + - source: github:quartz-community/encrypted-pages + enabled: true + options: + iterations: 600000 + passwordField: password + unlistWhenEncrypted: false + outputPath: static/encryptedContentIndex.json + - source: github:quartz-community/stacked-pages + enabled: false + layout: + position: afterBody + priority: 50 + display: all + - source: github:quartz-community/alias-redirects + enabled: true + - source: github:quartz-community/content-index + enabled: true + options: + enableSiteMap: true + enableRSS: true + - source: github:quartz-community/favicon + enabled: true + - source: github:quartz-community/og-image + enabled: true + - source: github:quartz-community/cname + enabled: true + - source: github:quartz-community/canvas-page + enabled: true + - source: github:quartz-community/content-page + enabled: true + - source: github:quartz-community/folder-page + enabled: true + - source: github:quartz-community/tag-page + enabled: true + - source: github:quartz-community/explorer + enabled: true + layout: + position: left + priority: 50 + - source: github:quartz-community/graph + enabled: true + layout: + position: right + priority: 10 + - source: github:quartz-community/search + enabled: true + layout: + position: left + priority: 20 + group: toolbar + groupOptions: + grow: true + - source: github:quartz-community/backlinks + enabled: true + layout: + position: right + priority: 50 + - source: github:quartz-community/article-title + enabled: true + layout: + position: beforeBody + priority: 10 + - source: github:quartz-community/content-meta + enabled: true + layout: + position: beforeBody + priority: 20 + - source: github:quartz-community/tag-list + enabled: false + layout: + position: beforeBody + priority: 30 + - source: github:quartz-community/page-title + enabled: true + layout: + position: left + priority: 10 + - source: github:quartz-community/darkmode + enabled: true + layout: + position: left + priority: 30 + group: toolbar + - source: github:quartz-community/reader-mode + enabled: true + layout: + position: left + priority: 35 + group: toolbar + - source: github:quartz-community/breadcrumbs + enabled: true + layout: + position: beforeBody + priority: 5 + condition: not-index + - source: github:quartz-community/comments + enabled: false + options: + provider: giscus + options: {} + layout: + position: afterBody + priority: 10 + - source: github:quartz-community/footer + enabled: true + options: + links: + GitHub: https://github.com/jackyzha0/quartz + Discord Community: https://discord.gg/cRFFHYye7t + - source: github:quartz-community/recent-notes + enabled: false + - source: github:quartz-community/spacer + enabled: true + options: {} + order: 25 + layout: + position: left + priority: 25 + display: mobile-only + - source: github:quartz-community/bases-page + enabled: true + options: {} + order: 50 + - source: github:quartz-community/note-properties + enabled: true + options: + includeAll: false + includedProperties: + - description + - tags + - aliases + excludedProperties: [] + hidePropertiesView: false + delimiters: "---" + language: yaml + order: 5 + layout: + position: beforeBody + priority: 15 + display: all + - source: + name: quartz-themes + repo: github:saberzero1/quartz-themes + subdir: plugin + enabled: true + options: + theme: default + - source: github:quartz-community/obsidian-plugin-excalidraw + enabled: true + options: + enableInteraction: true + darkMode: auto + exportPadding: 20 + order: 50 +layout: + groups: + toolbar: + priority: 35 + direction: row + gap: 0.5rem + byPageType: + "404": + positions: + beforeBody: [] + left: [] + right: [] + content: {} + folder: + exclude: + - reader-mode + positions: + right: [] + tag: + exclude: + - reader-mode + positions: + right: [] + canvas: {} + bases: {} diff --git a/quartz/cli/templates/ttrpg.yaml b/quartz/cli/templates/ttrpg.yaml new file mode 100644 index 000000000..2c06b76dc --- /dev/null +++ b/quartz/cli/templates/ttrpg.yaml @@ -0,0 +1,308 @@ +# yaml-language-server: $schema=../../plugins/quartz-plugins.schema.json +# Template: ttrpg +# Obsidian-based setup with map plugin and ITS Theme for TTRPG/D&D wikis. +configuration: + pageTitle: Quartz 5 + pageTitleSuffix: "" + enableSPA: true + enablePopovers: true + analytics: + provider: plausible + locale: en-US + baseUrl: quartz.jzhao.xyz + ignorePatterns: + - private + - templates + - .obsidian + theme: + fontOrigin: googleFonts + cdnCaching: true + typography: + header: Schibsted Grotesk + body: Source Sans Pro + code: IBM Plex Mono + colors: + lightMode: + light: "#faf8f8" + lightgray: "#e5e5e5" + gray: "#b8b8b8" + darkgray: "#4e4e4e" + dark: "#2b2b2b" + secondary: "#284b63" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#fff23688" + darkMode: + light: "#161618" + lightgray: "#393639" + gray: "#646464" + darkgray: "#d4d4d4" + dark: "#ebebec" + secondary: "#7b97aa" + tertiary: "#84a59d" + highlight: rgba(143, 159, 169, 0.15) + textHighlight: "#b3aa0288" +plugins: + - source: github:quartz-community/created-modified-date + enabled: true + options: + defaultDateType: modified + priority: + - frontmatter + - git + - filesystem + order: 10 + - source: github:quartz-community/syntax-highlighting + enabled: true + options: + theme: + light: github-light + dark: github-dark + keepBackground: false + order: 20 + - source: github:quartz-community/obsidian-flavored-markdown + enabled: true + options: + comments: true + highlight: true + wikilinks: true + callouts: true + mermaid: true + parseTags: true + parseArrows: true + parseBlockReferences: true + enableInHtmlEmbed: false + enableYouTubeEmbed: true + enableVideoEmbed: true + enableCheckbox: true + order: 30 + - source: github:quartz-community/github-flavored-markdown + enabled: true + order: 40 + - source: github:quartz-community/table-of-contents + enabled: true + order: 50 + layout: + position: right + priority: 30 + - source: github:quartz-community/crawl-links + enabled: true + options: + markdownLinkResolution: shortest + # disableBrokenWikilinks: false # Set true to add a "broken" CSS class to internal links whose target is not in ctx.allSlugs. + order: 60 + - source: github:quartz-community/description + enabled: true + order: 70 + - source: github:quartz-community/latex + enabled: true + options: + renderEngine: katex + order: 80 + - source: github:quartz-community/citations + enabled: false + order: 85 + - source: github:quartz-community/hard-line-breaks + enabled: true + order: 90 + - source: github:quartz-community/ox-hugo + enabled: false + order: 91 + - source: github:quartz-community/roam + enabled: false + order: 92 + - source: github:quartz-community/fonts + enabled: true + - source: github:quartz-community/remove-draft + enabled: true + - source: github:quartz-community/explicit-publish + enabled: false + - source: github:quartz-community/unlisted-pages + enabled: true + options: {} + order: 45 + - source: github:quartz-community/encrypted-pages + enabled: true + options: + iterations: 600000 + passwordField: password + unlistWhenEncrypted: false + outputPath: static/encryptedContentIndex.json + - source: github:quartz-community/stacked-pages + enabled: false + layout: + position: afterBody + priority: 50 + display: all + - source: github:quartz-community/alias-redirects + enabled: true + - source: github:quartz-community/content-index + enabled: true + options: + enableSiteMap: true + enableRSS: true + - source: github:quartz-community/favicon + enabled: true + - source: github:quartz-community/og-image + enabled: true + - source: github:quartz-community/cname + enabled: true + - source: github:quartz-community/canvas-page + enabled: true + - source: github:quartz-community/content-page + enabled: true + - source: github:quartz-community/folder-page + enabled: true + - source: github:quartz-community/tag-page + enabled: true + - source: github:quartz-community/explorer + enabled: true + layout: + position: left + priority: 50 + - source: github:quartz-community/graph + enabled: true + layout: + position: right + priority: 10 + - source: github:quartz-community/search + enabled: true + layout: + position: left + priority: 20 + group: toolbar + groupOptions: + grow: true + - source: github:quartz-community/backlinks + enabled: true + layout: + position: right + priority: 50 + - source: github:quartz-community/article-title + enabled: true + layout: + position: beforeBody + priority: 10 + - source: github:quartz-community/content-meta + enabled: true + layout: + position: beforeBody + priority: 20 + - source: github:quartz-community/tag-list + enabled: false + layout: + position: beforeBody + priority: 30 + - source: github:quartz-community/page-title + enabled: true + layout: + position: left + priority: 10 + - source: github:quartz-community/darkmode + enabled: true + layout: + position: left + priority: 30 + group: toolbar + - source: github:quartz-community/reader-mode + enabled: true + layout: + position: left + priority: 35 + group: toolbar + - source: github:quartz-community/breadcrumbs + enabled: true + layout: + position: beforeBody + priority: 5 + condition: not-index + - source: github:quartz-community/comments + enabled: false + options: + provider: giscus + options: {} + layout: + position: afterBody + priority: 10 + - source: github:quartz-community/footer + enabled: true + options: + links: + GitHub: https://github.com/jackyzha0/quartz + Discord Community: https://discord.gg/cRFFHYye7t + - source: github:quartz-community/recent-notes + enabled: false + - source: github:quartz-community/spacer + enabled: true + options: {} + order: 25 + layout: + position: left + priority: 25 + display: mobile-only + - source: github:quartz-community/bases-page + enabled: true + options: {} + order: 50 + - source: github:quartz-community/note-properties + enabled: true + options: + includeAll: false + includedProperties: + - description + - tags + - aliases + excludedProperties: [] + hidePropertiesView: false + delimiters: "---" + language: yaml + order: 5 + layout: + position: beforeBody + priority: 15 + display: all + # TTRPG-specific plugins + - source: github:Requiae/quartz-leaflet-bases-plugin + enabled: true + options: + enableCopyTool: false + - source: + name: quartz-themes + repo: github:saberzero1/quartz-themes + subdir: plugin + enabled: true + options: + theme: its-theme + variation: ttrpg-dnd + - source: github:quartz-community/obsidian-plugin-excalidraw + enabled: true + options: + enableInteraction: true + darkMode: auto + exportPadding: 20 + order: 50 +layout: + groups: + toolbar: + priority: 35 + direction: row + gap: 0.5rem + byPageType: + "404": + positions: + beforeBody: [] + left: [] + right: [] + content: {} + folder: + exclude: + - reader-mode + positions: + right: [] + tag: + exclude: + - reader-mode + positions: + right: [] + canvas: {} + bases: {} diff --git a/quartz/components/Body.tsx b/quartz/components/Body.tsx new file mode 100644 index 000000000..d396f4d5d --- /dev/null +++ b/quartz/components/Body.tsx @@ -0,0 +1,7 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" + +const Body: QuartzComponent = ({ children }: QuartzComponentProps) => { + return
{children}
+} + +export default (() => Body) satisfies QuartzComponentConstructor diff --git a/quartz/components/ConditionalRender.tsx b/quartz/components/ConditionalRender.tsx new file mode 100644 index 000000000..74a4db07f --- /dev/null +++ b/quartz/components/ConditionalRender.tsx @@ -0,0 +1,22 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" + +type ConditionalRenderConfig = { + component: QuartzComponent + condition: (props: QuartzComponentProps) => boolean +} + +export default ((config: ConditionalRenderConfig) => { + const ConditionalRender: QuartzComponent = (props: QuartzComponentProps) => { + if (config.condition(props)) { + return + } + + return null + } + + ConditionalRender.afterDOMLoaded = config.component.afterDOMLoaded + ConditionalRender.beforeDOMLoaded = config.component.beforeDOMLoaded + ConditionalRender.css = config.component.css + + return ConditionalRender +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/Date.tsx b/quartz/components/Date.tsx new file mode 100644 index 000000000..096cdc69b --- /dev/null +++ b/quartz/components/Date.tsx @@ -0,0 +1,30 @@ +import { ValidLocale } from "../i18n" +import { QuartzPluginData } from "../plugins/vfile" + +interface Props { + date: Date + locale?: ValidLocale +} + +export type ValidDateType = keyof Required["dates"] + +export function getDate(data: QuartzPluginData): Date | undefined { + if (!data.defaultDateType) { + throw new Error( + `Field 'defaultDateType' was not set. Ensure the CreatedModifiedDate plugin is configured with a 'defaultDateType' option. See https://quartz.jzhao.xyz/plugins/CreatedModifiedDate for more details.`, + ) + } + return data.dates?.[data.defaultDateType] +} + +export function formatDate(d: Date, locale: ValidLocale = "en-US"): string { + return d.toLocaleDateString(locale, { + year: "numeric", + month: "short", + day: "2-digit", + }) +} + +export function Date({ date, locale }: Props) { + return +} diff --git a/quartz/components/DesktopOnly.tsx b/quartz/components/DesktopOnly.tsx new file mode 100644 index 000000000..b163eb9d5 --- /dev/null +++ b/quartz/components/DesktopOnly.tsx @@ -0,0 +1,18 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" + +export default ((component: QuartzComponent) => { + const Component = component + const DesktopOnly: QuartzComponent = (props: QuartzComponentProps) => { + return ( +
+ +
+ ) + } + + DesktopOnly.displayName = component.displayName + DesktopOnly.afterDOMLoaded = component?.afterDOMLoaded + DesktopOnly.beforeDOMLoaded = component?.beforeDOMLoaded + DesktopOnly.css = component?.css + return DesktopOnly +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/Flex.tsx b/quartz/components/Flex.tsx new file mode 100644 index 000000000..70d214965 --- /dev/null +++ b/quartz/components/Flex.tsx @@ -0,0 +1,59 @@ +import { concatenateResources } from "../util/resources" +import { classNames } from "../util/lang" +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" + +type FlexConfig = { + components: { + Component: QuartzComponent + grow?: boolean + shrink?: boolean + basis?: string + order?: number + align?: "start" | "end" | "center" | "stretch" + justify?: "start" | "end" | "center" | "between" | "around" + }[] + direction?: "row" | "row-reverse" | "column" | "column-reverse" + wrap?: "nowrap" | "wrap" | "wrap-reverse" + gap?: string +} + +export default ((config: FlexConfig) => { + const Flex: QuartzComponent = (props: QuartzComponentProps) => { + const direction = config.direction ?? "row" + const wrap = config.wrap ?? "nowrap" + const gap = config.gap ?? "1rem" + + return ( +
+ {config.components.map((c) => { + const grow = c.grow ? 1 : 0 + const shrink = (c.shrink ?? true) ? 1 : 0 + const basis = c.basis ?? "auto" + const order = c.order ?? 0 + const align = c.align ?? "center" + const justify = c.justify ?? "center" + + return ( +
+ +
+ ) + })} +
+ ) + } + + Flex.afterDOMLoaded = concatenateResources( + ...config.components.map((c) => c.Component.afterDOMLoaded), + ) + Flex.beforeDOMLoaded = concatenateResources( + ...config.components.map((c) => c.Component.beforeDOMLoaded), + ) + Flex.css = concatenateResources(...config.components.map((c) => c.Component.css)) + return Flex +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx new file mode 100644 index 000000000..0cb31fa1b --- /dev/null +++ b/quartz/components/Head.tsx @@ -0,0 +1,114 @@ +import { i18n } from "../i18n" +import { FullSlug, getFileExtension, joinSegments, pathToRoot } from "../util/path" +import { CSSResourceToStyleElement, JSResourceToScriptElement } from "../util/resources" +import { googleFontHref, googleFontSubsetHref } from "../util/theme" +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { unescapeHTML } from "../util/escape" +import { CustomOgImagesEmitterName } from "../../.quartz/plugins" +export default (() => { + const Head: QuartzComponent = ({ + cfg, + fileData, + externalResources, + ctx, + }: QuartzComponentProps) => { + const titleSuffix = cfg.pageTitleSuffix ?? "" + const title = + (fileData.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title) + titleSuffix + const description = + fileData.frontmatter?.socialDescription ?? + fileData.frontmatter?.description ?? + unescapeHTML(fileData.description?.trim() ?? i18n(cfg.locale).propertyDefaults.description) + + const { css, js, additionalHead } = externalResources + + const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`) + const path = url.pathname as FullSlug + const baseDir = fileData.slug === "404" ? path : pathToRoot(fileData.slug!) + const iconPath = joinSegments(baseDir, "static/icon.png") + + // Url of current page + const socialUrl = + fileData.slug === "404" ? url.toString() : joinSegments(url.toString(), fileData.slug!) + + const usesCustomOgImage = ctx.cfg.plugins.emitters.some( + (e) => e.name === CustomOgImagesEmitterName, + ) + const ogImageDefaultPath = `https://${cfg.baseUrl}/static/og-image.png` + + const coreStylesheet = css[0]?.content + const coreScript = js.find( + (r) => r.loadTime === "beforeDOMReady" && r.contentType === "external", + ) + + return ( + + {title} + + {coreStylesheet && } + {coreScript && coreScript.contentType === "external" && ( + + )} + {cfg.theme.cdnCaching && cfg.theme.fontOrigin === "googleFonts" && ( + <> + + + + {cfg.theme.typography.title && ( + + )} + + )} + + + + + + + + + + + + + {!usesCustomOgImage && ( + <> + + + + + + )} + + {cfg.baseUrl && ( + <> + + + + + )} + + + + + + {css.map((resource) => CSSResourceToStyleElement(resource, true))} + {js + .filter((resource) => resource.loadTime === "beforeDOMReady") + .map((res) => JSResourceToScriptElement(res, true))} + {additionalHead.map((resource) => { + if (typeof resource === "function") { + return resource(fileData) + } else { + return resource + } + })} + + ) + } + + return Head +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/Header.tsx b/quartz/components/Header.tsx new file mode 100644 index 000000000..eba17ae09 --- /dev/null +++ b/quartz/components/Header.tsx @@ -0,0 +1,22 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" + +const Header: QuartzComponent = ({ children }: QuartzComponentProps) => { + return children.length > 0 ?
{children}
: null +} + +Header.css = ` +header { + display: flex; + flex-direction: row; + align-items: center; + margin: 2rem 0; + gap: 1.5rem; +} + +header h1 { + margin: 0; + flex: auto; +} +` + +export default (() => Header) satisfies QuartzComponentConstructor diff --git a/quartz/components/MobileOnly.tsx b/quartz/components/MobileOnly.tsx new file mode 100644 index 000000000..2c108358e --- /dev/null +++ b/quartz/components/MobileOnly.tsx @@ -0,0 +1,18 @@ +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" + +export default ((component: QuartzComponent) => { + const Component = component + const MobileOnly: QuartzComponent = (props: QuartzComponentProps) => { + return ( +
+ +
+ ) + } + + MobileOnly.displayName = component.displayName + MobileOnly.afterDOMLoaded = component?.afterDOMLoaded + MobileOnly.beforeDOMLoaded = component?.beforeDOMLoaded + MobileOnly.css = component?.css + return MobileOnly +}) satisfies QuartzComponentConstructor diff --git a/quartz/components/PageList.tsx b/quartz/components/PageList.tsx new file mode 100644 index 000000000..6993b7908 --- /dev/null +++ b/quartz/components/PageList.tsx @@ -0,0 +1,114 @@ +import { FullSlug, isFolderPath, resolveRelative } from "../util/path" +import { QuartzPluginData } from "../plugins/vfile" +import { Date, getDate } from "./Date" +import { QuartzComponent, QuartzComponentProps } from "./types" + +export type SortFn = (f1: QuartzPluginData, f2: QuartzPluginData) => number + +export function byDateAndAlphabetical(): SortFn { + return (f1, f2) => { + // Sort by date/alphabetical + if (f1.dates && f2.dates) { + // sort descending + return getDate(f2)!.getTime() - getDate(f1)!.getTime() + } else if (f1.dates && !f2.dates) { + // prioritize files with dates + return -1 + } else if (!f1.dates && f2.dates) { + return 1 + } + + // otherwise, sort lexographically by title + const f1Title = f1.frontmatter?.title.toLowerCase() ?? "" + const f2Title = f2.frontmatter?.title.toLowerCase() ?? "" + return f1Title.localeCompare(f2Title) + } +} + +export function byDateAndAlphabeticalFolderFirst(): SortFn { + return (f1, f2) => { + // Sort folders first + const f1IsFolder = isFolderPath(f1.slug ?? "") + const f2IsFolder = isFolderPath(f2.slug ?? "") + if (f1IsFolder && !f2IsFolder) return -1 + if (!f1IsFolder && f2IsFolder) return 1 + + // If both are folders or both are files, sort by date/alphabetical + if (f1.dates && f2.dates) { + // sort descending + return getDate(f2)!.getTime() - getDate(f1)!.getTime() + } else if (f1.dates && !f2.dates) { + // prioritize files with dates + return -1 + } else if (!f1.dates && f2.dates) { + return 1 + } + + // otherwise, sort lexographically by title + const f1Title = f1.frontmatter?.title.toLowerCase() ?? "" + const f2Title = f2.frontmatter?.title.toLowerCase() ?? "" + return f1Title.localeCompare(f2Title) + } +} + +type Props = { + limit?: number + sort?: SortFn +} & QuartzComponentProps + +export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit, sort }: Props) => { + const sorter = sort ?? byDateAndAlphabeticalFolderFirst() + let list = allFiles.sort(sorter) + if (limit) { + list = list.slice(0, limit) + } + + return ( +
    + {list.map((page) => { + const title = page.frontmatter?.title + const tags = page.frontmatter?.tags ?? [] + + return ( +
  • +
    +

    {page.dates && }

    + + +
    +
  • + ) + })} +
+ ) +} + +PageList.css = ` +.section h3 { + margin: 0; +} + +.section > .tags { + margin: 0; +} +` diff --git a/quartz/components/Spacer.tsx b/quartz/components/Spacer.tsx new file mode 100644 index 000000000..5288752f7 --- /dev/null +++ b/quartz/components/Spacer.tsx @@ -0,0 +1,8 @@ +import { QuartzComponentConstructor, QuartzComponentProps } from "./types" +import { classNames } from "../util/lang" + +function Spacer({ displayClass }: QuartzComponentProps) { + return
+} + +export default (() => Spacer) satisfies QuartzComponentConstructor diff --git a/quartz/components/external.ts b/quartz/components/external.ts new file mode 100644 index 000000000..0113445a3 --- /dev/null +++ b/quartz/components/external.ts @@ -0,0 +1,23 @@ +import { componentRegistry } from "./registry" +import { QuartzComponent, QuartzComponentConstructor } from "./types" + +export function External( + name: string, + options?: Options, +): QuartzComponent { + const registered = componentRegistry.get(name) + if (!registered) { + throw new Error( + `External component "${name}" not found. ` + + `Make sure the plugin is installed and components are loaded before layouts are evaluated.`, + ) + } + + const { component } = registered + + if (typeof component === "function") { + return (component as QuartzComponentConstructor)(options as Options) + } + + return component as QuartzComponent +} diff --git a/quartz/components/frames/DefaultFrame.tsx b/quartz/components/frames/DefaultFrame.tsx new file mode 100644 index 000000000..d46c120d4 --- /dev/null +++ b/quartz/components/frames/DefaultFrame.tsx @@ -0,0 +1,61 @@ +import { PageFrame, PageFrameProps } from "./types" +import HeaderConstructor from "../Header" + +const Header = HeaderConstructor() + +/** + * The default page frame — three-column layout with left sidebar, center + * content (header + body + afterBody), and right sidebar, followed by a footer. + * + * This is the original Quartz layout, extracted from renderPage.tsx. + */ +export const DefaultFrame: PageFrame = { + name: "default", + render({ + componentData, + header, + beforeBody, + pageBody: Content, + afterBody, + left, + right, + footer: Footer, + }: PageFrameProps) { + return ( + <> + +
+ + +
+ +
+ +