Full Code of hexojs/hexo for AI

master bc395f7fa1aa cached
297 files
912.2 KB
253.4k tokens
646 symbols
1 requests
Download .txt
Showing preview only (984K chars total). Download the full file or copy to clipboard to get everything.
Repository: hexojs/hexo
Branch: master
Commit: bc395f7fa1aa
Files: 297
Total size: 912.2 KB

Directory structure:
gitextract_47fk84wq/

├── .editorconfig
├── .github/
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature-request-improvement.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── benchmark.yml
│       ├── commenter.yml
│       ├── dependencies-review.yml
│       ├── linter.yml
│       └── tester.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .lintstagedrc.json
├── .mocharc.yml
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── bin/
│   └── hexo
├── eslint.config.js
├── lib/
│   ├── box/
│   │   ├── file.ts
│   │   └── index.ts
│   ├── extend/
│   │   ├── console.ts
│   │   ├── deployer.ts
│   │   ├── filter.ts
│   │   ├── generator.ts
│   │   ├── helper.ts
│   │   ├── index.ts
│   │   ├── injector.ts
│   │   ├── migrator.ts
│   │   ├── processor.ts
│   │   ├── renderer.ts
│   │   ├── syntax_highlight.ts
│   │   └── tag.ts
│   ├── hexo/
│   │   ├── default_config.ts
│   │   ├── index.ts
│   │   ├── load_config.ts
│   │   ├── load_database.ts
│   │   ├── load_plugins.ts
│   │   ├── load_theme_config.ts
│   │   ├── locals.ts
│   │   ├── multi_config_path.ts
│   │   ├── post.ts
│   │   ├── register_models.ts
│   │   ├── render.ts
│   │   ├── router.ts
│   │   ├── scaffold.ts
│   │   ├── source.ts
│   │   ├── update_package.ts
│   │   └── validate_config.ts
│   ├── models/
│   │   ├── asset.ts
│   │   ├── binary_relation_index.ts
│   │   ├── cache.ts
│   │   ├── category.ts
│   │   ├── data.ts
│   │   ├── index.ts
│   │   ├── page.ts
│   │   ├── post.ts
│   │   ├── post_asset.ts
│   │   ├── post_category.ts
│   │   ├── post_tag.ts
│   │   ├── tag.ts
│   │   └── types/
│   │       └── moment.ts
│   ├── plugins/
│   │   ├── console/
│   │   │   ├── clean.ts
│   │   │   ├── config.ts
│   │   │   ├── deploy.ts
│   │   │   ├── generate.ts
│   │   │   ├── index.ts
│   │   │   ├── list/
│   │   │   │   ├── category.ts
│   │   │   │   ├── common.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── page.ts
│   │   │   │   ├── post.ts
│   │   │   │   ├── route.ts
│   │   │   │   └── tag.ts
│   │   │   ├── migrate.ts
│   │   │   ├── new.ts
│   │   │   ├── publish.ts
│   │   │   └── render.ts
│   │   ├── filter/
│   │   │   ├── after_post_render/
│   │   │   │   ├── excerpt.ts
│   │   │   │   ├── external_link.ts
│   │   │   │   └── index.ts
│   │   │   ├── after_render/
│   │   │   │   ├── external_link.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── meta_generator.ts
│   │   │   ├── before_exit/
│   │   │   │   ├── index.ts
│   │   │   │   └── save_database.ts
│   │   │   ├── before_generate/
│   │   │   │   ├── index.ts
│   │   │   │   └── render_post.ts
│   │   │   ├── before_post_render/
│   │   │   │   ├── backtick_code_block.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── titlecase.ts
│   │   │   ├── index.ts
│   │   │   ├── new_post_path.ts
│   │   │   ├── post_permalink.ts
│   │   │   └── template_locals/
│   │   │       ├── i18n.ts
│   │   │       └── index.ts
│   │   ├── generator/
│   │   │   ├── asset.ts
│   │   │   ├── index.ts
│   │   │   ├── page.ts
│   │   │   └── post.ts
│   │   ├── helper/
│   │   │   ├── css.ts
│   │   │   ├── date.ts
│   │   │   ├── debug.ts
│   │   │   ├── favicon_tag.ts
│   │   │   ├── feed_tag.ts
│   │   │   ├── format.ts
│   │   │   ├── fragment_cache.ts
│   │   │   ├── full_url_for.ts
│   │   │   ├── gravatar.ts
│   │   │   ├── image_tag.ts
│   │   │   ├── index.ts
│   │   │   ├── is.ts
│   │   │   ├── js.ts
│   │   │   ├── link_to.ts
│   │   │   ├── list_archives.ts
│   │   │   ├── list_categories.ts
│   │   │   ├── list_posts.ts
│   │   │   ├── list_tags.ts
│   │   │   ├── mail_to.ts
│   │   │   ├── markdown.ts
│   │   │   ├── meta_generator.ts
│   │   │   ├── number_format.ts
│   │   │   ├── open_graph.ts
│   │   │   ├── paginator.ts
│   │   │   ├── partial.ts
│   │   │   ├── relative_url.ts
│   │   │   ├── render.ts
│   │   │   ├── search_form.ts
│   │   │   ├── tagcloud.ts
│   │   │   ├── toc.ts
│   │   │   └── url_for.ts
│   │   ├── highlight/
│   │   │   ├── highlight.ts
│   │   │   ├── index.ts
│   │   │   └── prism.ts
│   │   ├── injector/
│   │   │   └── index.ts
│   │   ├── processor/
│   │   │   ├── asset.ts
│   │   │   ├── common.ts
│   │   │   ├── data.ts
│   │   │   ├── index.ts
│   │   │   └── post.ts
│   │   ├── renderer/
│   │   │   ├── index.ts
│   │   │   ├── json.ts
│   │   │   ├── nunjucks.ts
│   │   │   ├── plain.ts
│   │   │   └── yaml.ts
│   │   └── tag/
│   │       ├── asset_img.ts
│   │       ├── asset_link.ts
│   │       ├── asset_path.ts
│   │       ├── blockquote.ts
│   │       ├── code.ts
│   │       ├── full_url_for.ts
│   │       ├── iframe.ts
│   │       ├── img.ts
│   │       ├── include_code.ts
│   │       ├── index.ts
│   │       ├── link.ts
│   │       ├── post_link.ts
│   │       ├── post_path.ts
│   │       ├── pullquote.ts
│   │       └── url_for.ts
│   ├── theme/
│   │   ├── index.ts
│   │   ├── processors/
│   │   │   ├── config.ts
│   │   │   ├── i18n.ts
│   │   │   ├── source.ts
│   │   │   └── view.ts
│   │   └── view.ts
│   └── types.ts
├── package.json
├── test/
│   ├── benchmark.js
│   ├── fixtures/
│   │   ├── _config.json
│   │   ├── hello.njk
│   │   └── post_render.ts
│   ├── scripts/
│   │   ├── box/
│   │   │   ├── box.ts
│   │   │   └── file.ts
│   │   ├── console/
│   │   │   ├── clean.ts
│   │   │   ├── config.ts
│   │   │   ├── deploy.ts
│   │   │   ├── generate.ts
│   │   │   ├── list.ts
│   │   │   ├── list_categories.ts
│   │   │   ├── list_page.ts
│   │   │   ├── list_post.ts
│   │   │   ├── list_route.ts
│   │   │   ├── list_tags.ts
│   │   │   ├── migrate.ts
│   │   │   ├── new.ts
│   │   │   ├── publish.ts
│   │   │   └── render.ts
│   │   ├── extend/
│   │   │   ├── console.ts
│   │   │   ├── deployer.ts
│   │   │   ├── filter.ts
│   │   │   ├── generator.ts
│   │   │   ├── helper.ts
│   │   │   ├── injector.ts
│   │   │   ├── migrator.ts
│   │   │   ├── processor.ts
│   │   │   ├── renderer.ts
│   │   │   ├── tag.ts
│   │   │   └── tag_errors.ts
│   │   ├── filters/
│   │   │   ├── backtick_code_block.ts
│   │   │   ├── excerpt.ts
│   │   │   ├── external_link.ts
│   │   │   ├── i18n_locals.ts
│   │   │   ├── meta_generator.ts
│   │   │   ├── new_post_path.ts
│   │   │   ├── post_permalink.ts
│   │   │   ├── render_post.ts
│   │   │   ├── save_database.ts
│   │   │   └── titlecase.ts
│   │   ├── generators/
│   │   │   ├── asset.ts
│   │   │   ├── page.ts
│   │   │   └── post.ts
│   │   ├── helpers/
│   │   │   ├── css.ts
│   │   │   ├── date.ts
│   │   │   ├── debug.ts
│   │   │   ├── escape_html.ts
│   │   │   ├── favicon_tag.ts
│   │   │   ├── feed_tag.ts
│   │   │   ├── fragment_cache.ts
│   │   │   ├── full_url_for.ts
│   │   │   ├── gravatar.ts
│   │   │   ├── image_tag.ts
│   │   │   ├── is.ts
│   │   │   ├── js.ts
│   │   │   ├── link_to.ts
│   │   │   ├── list_archives.ts
│   │   │   ├── list_categories.ts
│   │   │   ├── list_posts.ts
│   │   │   ├── list_tags.ts
│   │   │   ├── mail_to.ts
│   │   │   ├── markdown.ts
│   │   │   ├── meta_generator.ts
│   │   │   ├── number_format.ts
│   │   │   ├── open_graph.ts
│   │   │   ├── paginator.ts
│   │   │   ├── partial.ts
│   │   │   ├── relative_url.ts
│   │   │   ├── render.ts
│   │   │   ├── search_form.ts
│   │   │   ├── tagcloud.ts
│   │   │   ├── toc.ts
│   │   │   └── url_for.ts
│   │   ├── hexo/
│   │   │   ├── hexo.ts
│   │   │   ├── load_config.ts
│   │   │   ├── load_database.ts
│   │   │   ├── load_plugins.ts
│   │   │   ├── load_theme_config.ts
│   │   │   ├── locals.ts
│   │   │   ├── multi_config_path.ts
│   │   │   ├── post.ts
│   │   │   ├── render.ts
│   │   │   ├── router.ts
│   │   │   ├── scaffold.ts
│   │   │   ├── update_package.ts
│   │   │   └── validate_config.ts
│   │   ├── models/
│   │   │   ├── asset.ts
│   │   │   ├── cache.ts
│   │   │   ├── category.ts
│   │   │   ├── moment.ts
│   │   │   ├── page.ts
│   │   │   ├── post.ts
│   │   │   ├── post_asset.ts
│   │   │   └── tag.ts
│   │   ├── processors/
│   │   │   ├── asset.ts
│   │   │   ├── common.ts
│   │   │   ├── data.ts
│   │   │   └── post.ts
│   │   ├── renderers/
│   │   │   ├── json.ts
│   │   │   ├── nunjucks.ts
│   │   │   ├── plain.ts
│   │   │   └── yaml.ts
│   │   ├── tags/
│   │   │   ├── asset_img.ts
│   │   │   ├── asset_link.ts
│   │   │   ├── asset_path.ts
│   │   │   ├── blockquote.ts
│   │   │   ├── code.ts
│   │   │   ├── full_url_for.ts
│   │   │   ├── iframe.ts
│   │   │   ├── img.ts
│   │   │   ├── include_code.ts
│   │   │   ├── link.ts
│   │   │   ├── post_link.ts
│   │   │   ├── post_path.ts
│   │   │   ├── pullquote.ts
│   │   │   └── url_for.ts
│   │   ├── theme/
│   │   │   ├── theme.ts
│   │   │   └── view.ts
│   │   └── theme_processors/
│   │       ├── config.ts
│   │       ├── i18n.ts
│   │       ├── source.ts
│   │       └── view.ts
│   └── util/
│       ├── index.ts
│       └── stream.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true

[vcbuild.bat]
end_of_line = crlf

[*.{md,markdown}]
trim_trailing_whitespace = false

[{lib,src,test}/**.js]
indent_style = space
indent_size = 2

[src/**.{h,cc}]
indent_style = space
indent_size = 2

[test/*.py]
indent_style = space
indent_size = 2

[configure]
indent_style = space
indent_size = 2

[Makefile]
indent_style = tab
indent_size = 8

[{deps,tools}/**]
indent_style = ignore
indent_size = ignore
end_of_line = ignore
trim_trailing_whitespace = ignore
charset = ignore

================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing

## Style Guide

We use [ESLint] to maintain the code style. You can install linter plugins on your editor or check the status with the following commands:

``` bash
$ npm run eslint

# You can append `--fix` option to these commands to fix the code style automatically
$ npm run eslint -- --fix
```

## Pull Requests

1. Fork [hexojs/hexo](https://github.com/hexojs/hexo).
2. Clone the repository to your computer and install dependencies.

    ``` bash
    $ git clone https://github.com/<username>/hexo.git
    $ cd hexo
    $ npm install
    ```
    
3. Create a feature branch.

    ``` bash
    $ git checkout -b new_feature
    ```
    
4. Start hacking.
5. Push the branch.

    ``` bash
    $ git push origin new_feature
    ```
    
6. Create a pull request and describe the change.

## Testing

Before you submitting the pull request. Please make sure your code is coveraged and passes the tests. Otherwise your pull request won't be merged.

``` bash
$ npm test
```

## Updating Documentation

The Hexo documentation is open source and you can find the source code on [hexojs/site]. 

### Workflow

1. Fork [hexojs/site](https://github.com/hexojs/site).
2. Clone the repository to your computer and install dependencies.

    ``` bash
    $ git clone https://github.com/<username>/site.git
    $ cd site
    $ npm install
    ```
    
3. Start editing the documentation. You can start the server for live previewing.

    ``` bash
    $ hexo server
    ```
    
4. Push the branch.
5. Create a pull request and describe the change.

### Translating

1. Add a new language folder in `source` folder. (all in lower case)
2. Copy Markdown and template files in `source` folder to the new language folder.
3. Add the new language to `source/_data/language.yml`.
4. Copy `en.yml` in `themes/navy/languages` and rename to the language name (all in lower case).

## Reporting Issues

When you encounter some problems when using Hexo, you can find the solutions in [Troubleshooting](https://hexo.io/docs/troubleshooting.html) or ask me on [GitHub](https://github.com/hexojs/hexo/issues) or [Google Group](https://groups.google.com/group/hexo). If you can't find the answer, please report it on GitHub.

1. Represent the problem in [debug mode](https://hexo.io/docs/commands.html#Debug_mode).
2. Run `hexo version` and check the version info.    
3. Post both debug message and version info on GitHub.

[ESLint]: https://eslint.org/

================================================
FILE: .github/FUNDING.yml
================================================
open_collective: hexo


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
description: Something isn't working as expected.
# title: ""
# labels: []
# assignees: []

body:
  - type: markdown
    attributes:
      value: |
        ## Tips

        - 给简体中文用户的提示:

          - 在提交 issue 时请按照下面的模板提供相关信息,这将有助于我们发现问题。
          - 请尽量使用英语描述你遇到的问题,这可以让更多的人帮助到你。

        - A good bug report should have your configuration and build environment information, which are essential for us to investigate the problem. We've provided the following steps on how to attach the necessary information.

        - If you find that markdown files are not rendered as expected, please go to https://marked.js.org/demo/ to see if it can be reproduced there. If it can be reproduced, please file a bug to https://github.com/markedjs/marked.

        - If you want help on your bug, please also send us the git repository (GitHub, GitLab, Bitbucket, etc.) where your hexo code is stored. It would greatly help. If you prefer not to have your hexo code out in public, please upload to a private GitHub repository and grant read-only access to `hexojs/core`.

        - Please take extra precaution not to attach any secret or personal information. (likes personal privacy, password, GitHub Personal Access Token, etc.)

        ------

  - type: checkboxes
    validations:
      required: true
    attributes:
      label: Check List
      description: Please check followings before submitting a new issue.
      options:
        - label: I have already read [Docs page](https://hexo.io/docs/) & [Troubleshooting page](https://hexo.io/docs/troubleshooting).
        - label: I have already searched existing issues and they are not help to me.
        - label: I examined error or warning messages and it's difficult to solve.
        - label: I am using the [latest](https://github.com/hexojs/hexo/releases) version of Hexo. (run `hexo version` to check)
        - label: My Node.js is matched [the required version](https://hexo.io/docs/#Required-Node-js-version).

  - type: textarea
    validations:
      required: true
    attributes:
      label: Expected behavior
      # description:
      placeholder: Descripe what you expected to happen.
      # value:
      # render:

  - type: textarea
    validations:
      required: true
    attributes:
      label: Actual behavior
      # description:
      placeholder: Descripe what actually happen.
      # value:
      # render:

  - type: textarea
    validations:
      required: true
    attributes:
      label: How to reproduce?
      description: How do you trigger this bug? Please walk us through it step by step.
      placeholder: |
        1. Step1
        2. Step2
        3. etc.
        ...
      # value:
      # render:

  - type: input
    validations:
      required: true
    attributes:
      label: Is the problem still there under `Safe mode`?
      description: |
        https://hexo.io/docs/commands#Safe-mode

        "Safe mode" will disable all the plugins and scripts.
        If your problem disappear under "Safe mode" means the problem is probably at your newly installed plugins, not at hexo.
      # placeholder:
      # value: |
      # render:

  - type: markdown
    attributes:
      value: |
        ------

        ## Environment & Settings

  - type: textarea
    validations:
      required: false
    attributes:
      label: Your Node.js & npm version
      description: |
        Please run `node -v && npm -v` 
        and paste the output here.
      placeholder: node -v && npm -v
      # value: |
      render: text

  - type: textarea
    validations:
      required: false
    attributes:
      label: Your Hexo and Plugin version
      description: |
        Please run `npm ls --depth 0` 
        and paste the output here.
      placeholder: npm ls --depth 0
      # value:
      render: text

  - type: textarea
    validations:
      required: false
    attributes:
      label: Your `package.json`
      description: Please paste the content of `package.json` here.
      placeholder: package.json
      # value:
      render: json

  - type: textarea
    validations:
      required: false
    attributes:
      label: Your site's `_config.yml` (Optional)
      description: Please paste the content of your `_config.yml` here.
      placeholder: _config.yml
      # value: |
      render: yaml

  - type: textarea
    validations:
      required: false
    attributes:
      label: Others
      description: If you have other information. Please write here.
      # placeholder:
      # value:
      # render:


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Ask a Question, Help, Discuss
    url: https://github.com/hexojs/hexo/discussions
    about: I have a question, help for hexo (e.g. Customize)


================================================
FILE: .github/ISSUE_TEMPLATE/feature-request-improvement.yml
================================================
name: Feature request / Improvement
description: I have a feature request, suggestion, improvement etc...
# title: ""
# labels: []
# assignees: []

body:
  - type: checkboxes
    validations:
      required: true
    attributes:
      label: Check List
      description: Please check followings before submitting a new feature request.
      options:
        - label: I have already read [Docs page](https://hexo.io/docs/).
        - label: I have already searched existing issues.

  - type: textarea
    validations:
      required: true
    attributes:
      label: Feature Request
      description: Descripe the feature and why it is needed.
      # placeholder:
      # value:
      # render:

  - type: textarea
    validations:
      required: false
    attributes:
      label: Others
      description: If you have other information. Please write here.
      # placeholder:
      # value:
      # render:


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
Thank you for creating a pull request to contribute to Hexo code! Before you open the request please answer the following questions to help it be more easily integrated. Please check the boxes "[ ]" with "[x]" when done too.
-->

## What does it do?



## Screenshots



## Pull request tasks

- [ ] Add test cases for the changes.
- [ ] Passed the CI test.


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
  directory: "/"
  schedule:
    interval: daily
- package-ecosystem: github-actions
  directory: "/"
  schedule:
    interval: daily


================================================
FILE: .github/workflows/benchmark.yml
================================================
name: Benchmark

on:
  workflow_dispatch:
  push:
    branches:
      - "master"
    paths:
      - "lib/**"
      - ".github/workflows/benchmark.yml"
  pull_request:
    branches:
      - "master"
    paths:
      - "lib/**"
      - ".github/workflows/benchmark.yml"

jobs:
  benchmark:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest]
        node-version: ["20", "22", "24"]
      fail-fast: false
    steps:
      - uses: actions/checkout@v6
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
      - name: Install dependencies
        run: npm install --silent
      - name: Running benchmark
        run: node test/benchmark.js --benchmark

  profiling:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest]
        node-version: ["20", "22", "24"]
      fail-fast: false
    env:
      comment_file: ".tmp-comment-flamegraph-node${{ matrix.node-version }}.md"
    steps:
      - uses: actions/checkout@v6
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
      - name: Install dependencies
        run: npm install --silent
      - name: Running profiling
        run: node test/benchmark.js --profiling
      - name: Publish flamegraph to https://${{ github.sha }}-${{ matrix.node-version }}-hexo.surge.sh/flamegraph.html
        uses: dswistowski/surge-sh-action@v1
        with:
          domain: ${{ github.sha }}-${{ matrix.node-version }}-hexo.surge.sh
          project: ./.tmp-hexo-theme-unit-test/0x/
          login: ${{ secrets.SURGE_LOGIN }}
          token: ${{ secrets.SURGE_TOKEN }}

      - name: save comment to file
        if: ${{github.event_name == 'pull_request' }}
        run: |
          echo "https://${{ github.sha }}-${{ matrix.node-version }}-hexo.surge.sh/flamegraph.html" > ${{env.comment_file}}

      - uses: actions/upload-artifact@v6
        if: ${{github.event_name == 'pull_request' }}
        with:
          retention-days: 1
          name: comment-node${{ matrix.node-version }}
          path: ${{env.comment_file}}

  number:
    runs-on: ubuntu-latest
    if: ${{github.event_name == 'pull_request' }}
    env:
      pr_number_file: .tmp-comment-pr_number
    steps:
      - name: save PR number to file
        run: |
          echo -n "${{ github.event.number }}" > ${{env.pr_number_file}}
      - uses: actions/upload-artifact@v6
        with:
          retention-days: 1
          name: comment-pr_number
          path: ${{env.pr_number_file}}


================================================
FILE: .github/workflows/commenter.yml
================================================
name: Commenter

on:
  pull_request_target:

  workflow_run:
    workflows: ["Benchmark"]
    types:
      - completed

permissions:
  contents: read

jobs:
  comment-test:
    name: How to test
    permissions:
      pull-requests: write # for marocchino/sticky-pull-request-comment to create or update PR comment
    runs-on: ubuntu-latest
    if: ${{github.event_name == 'pull_request_target'}}
    steps:
      - name: Comment PR - How to test
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          header: How to test
          message: |
            ## How to test

            ```sh
            git clone -b ${{ github.head_ref }} https://github.com/${{ github.event.pull_request.head.repo.full_name }}.git
            cd hexo
            npm install
            npm test
            ```

  comment-flamegraph:
    name: Flamegraph
    permissions:
      pull-requests: write # for marocchino/sticky-pull-request-comment to create or update PR comment
      actions: read # get artifact
    runs-on: ubuntu-latest
    if: ${{github.event_name == 'workflow_run' && github.event.workflow_run.conclusion=='success'}}
    env:
      comment_result: ".tmp-comment-flamegraph.md"
    steps:
      - name: download artifact
        uses: actions/download-artifact@v7
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          run-id: ${{toJSON(github.event.workflow_run.id)}}
          pattern: "comment-*"
          merge-multiple: true

      - name: get PR number
        run: |
          echo "pr_number=$(cat .tmp-comment-pr_number)" >> "$GITHUB_ENV"

      - name: combime comment
        if: ${{env.pr_number!=''}}
        run: |
          echo "## Flamegraph" > ${{env.comment_result}}
          echo "" >> ${{env.comment_result}}
          cat .tmp-comment-flamegraph-*.md >> ${{env.comment_result}}

      - name: Comment PR - flamegraph
        if: ${{env.pr_number!=''}}
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
          number: ${{env.pr_number}}
          header: Flamegraph
          path: ${{env.comment_result}}


================================================
FILE: .github/workflows/dependencies-review.yml
================================================
name: 'Dependencies Review'
on:
  pull_request:
    paths:
      - 'package.json'
      - 'package-lock.json'
  workflow_dispatch:

permissions:
  contents: read
  pull-requests: write

jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout Repository'
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #6.0.1
      - name: 'Dependencies Review'
        uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 #4.8.3
        with:
          vulnerability-check: true
          fail-on-severity: high
          comment-summary-in-pr: always


================================================
FILE: .github/workflows/linter.yml
================================================
name: Linter

on:
  push:
    branches:
      - "master"
    paths:
      - "lib/**"
      - "test/**"
      - ".github/workflows/linter.yml"
  pull_request:
    paths:
      - "lib/**"
      - "test/**"
      - ".github/workflows/linter.yml"

permissions:
  contents: read

jobs:
  linter:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Use Node.js 22
        uses: actions/setup-node@v6
        with:
          node-version: "22"
      - name: Install Dependencies
        run: npm install
      - name: Lint
        run: |
          npm run eslint
        env:
          CI: true


================================================
FILE: .github/workflows/tester.yml
================================================
name: Tester

on:
  push:
    branches:
      - "master"
    paths:
      - "lib/**"
      - "test/**"
      - "package.json"
      - "tsconfig.json"
      - ".github/workflows/tester.yml"
  pull_request:
    paths:
      - "lib/**"
      - "test/**"
      - "package.json"
      - "tsconfig.json"
      - ".github/workflows/tester.yml"

permissions:
  contents: read

jobs:
  tester:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: ["20", "22", "24"]
      fail-fast: false
    steps:
      - uses: actions/checkout@v6
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
      - name: Install Dependencies
        run: npm install
      - name: Test
        run: npm test -- --no-parallel
        env:
          CI: true
  coverage:
    permissions:
      checks: write # for coverallsapp/github-action to create new checks
      contents: read # for actions/checkout to fetch code
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest]
        node-version: ["22"]
    steps:
      - uses: actions/checkout@v6
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
      - name: Install Dependencies
        run: npm install
      - name: Coverage
        run: npm run test-cov
        env:
          CI: true
      - name: Coveralls
        uses: coverallsapp/github-action@master
        with:
          github-token: ${{ secrets.github_token }}


================================================
FILE: .gitignore
================================================
.DS_Store
node_modules/
tmp/
*.log
.idea/
yarn.lock
package-lock.json
pnpm-lock.yaml
.nyc_output/
coverage/
.tmp*
.vscode
dist/


================================================
FILE: .husky/pre-commit
================================================
npx lint-staged


================================================
FILE: .lintstagedrc.json
================================================
{
  "*.js": "eslint --fix",
  "*.ts": "eslint --fix"
}


================================================
FILE: .mocharc.yml
================================================
color: true
reporter: spec
ui: bdd
full-trace: true
exit: true


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
  advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
  address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.

Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at report@hexo.io. All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series of actions.

**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior,  harassment of an individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within the project community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.



================================================
FILE: LICENSE
================================================
Copyright (c) 2012-present Tommy Chen

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
<img src="https://raw.githubusercontent.com/hexojs/logo/master/hexo-logo-avatar.png" alt="Hexo logo" width="100" height="100" align="right" />

# Hexo

> A fast, simple & powerful blog framework, powered by [Node.js](https://nodejs.org).

[Website](https://hexo.io) |
[Documentation](https://hexo.io/docs/) |
[Installation Guide](https://hexo.io/docs/#Installation) |
[Contribution Guide](https://hexo.io/docs/contributing) |
[Code of Conduct](CODE_OF_CONDUCT.md) |
[API](https://hexo.io/api/) |
[Twitter](https://twitter.com/hexojs)

[![NPM version](https://badge.fury.io/js/hexo.svg)](https://www.npmjs.com/package/hexo)
![Required Node version](https://img.shields.io/node/v/hexo)
[![Build Status](https://github.com/hexojs/hexo/workflows/Tester/badge.svg)](https://github.com/hexojs/hexo/actions?query=workflow%3ATester)
[![dependencies Status](https://img.shields.io/librariesio/release/npm/hexo)](https://libraries.io/npm/hexo)
[![Coverage Status](https://coveralls.io/repos/hexojs/hexo/badge.svg?branch=master)](https://coveralls.io/r/hexojs/hexo?branch=master)
[![Gitter](https://badges.gitter.im/hexojs/hexo.svg)](https://gitter.im/hexojs/hexo)
[![Discord Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/teM2Anj)
[![Telegram Chat](https://img.shields.io/badge/chat-on%20telegram-32afed.svg)](https://t.me/hexojs)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fhexojs%2Fhexo.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fhexojs%2Fhexo?ref=badge_shield)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md)

## Features

- Blazing fast generating
- Support for GitHub Flavored Markdown and most Octopress plugins
- One-command deploy to GitHub Pages, Heroku, etc.
- Powerful API for limitless extensibility
- Hundreds of [themes](https://hexo.io/themes/) & [plugins](https://hexo.io/plugins/)

## Quick Start

**Install Hexo**

``` bash
$ npm install hexo-cli -g
```

Install with [brew](https://brew.sh/) on macOS and Linux:

```bash
$ brew install hexo
```

**Setup your blog**

``` bash
$ hexo init blog
$ cd blog
```

**Start the server**

``` bash
$ hexo server
```

**Create a new post**

``` bash
$ hexo new "Hello Hexo"
```

**Generate static files**

``` bash
$ hexo generate
```

## More Information

- Read the [documentation](https://hexo.io/)
- Visit the [Awesome Hexo](https://github.com/hexojs/awesome-hexo) list
- Find solutions in [troubleshooting](https://hexo.io/docs/troubleshooting.html)
- Join discussion on [Google Group](https://groups.google.com/group/hexo), [Discord](https://discord.gg/teM2Anj), [Gitter](https://gitter.im/hexojs/hexo) or [Telegram](https://t.me/hexojs)
- See the [plugin list](https://hexo.io/plugins/) and the [theme list](https://hexo.io/themes/) on wiki
- Follow [@hexojs](https://twitter.com/hexojs) for latest news

## Contributing

We welcome you to join the development of Hexo. Please see [contributing document](https://hexo.io/docs/contributing). 🤗

Also, we welcome PR or issue to [official-plugins](https://github.com/hexojs).

## Contributors

[![](https://opencollective.com/Hexo/contributors.svg?width=890)](https://github.com/hexojs/hexo/graphs/contributors)

## Backers

[![Backers](https://opencollective.com/hexo/tiers/backers.svg?avatarHeight=36&width=600)](https://opencollective.com/hexo)

## Sponsors

[![Sponsors](https://opencollective.com/hexo/tiers/sponsors.svg?width=600)](https://opencollective.com/hexo)

## License

[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fhexojs%2Fhexo.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fhexojs%2Fhexo?ref=badge_large)


================================================
FILE: bin/hexo
================================================
#!/usr/bin/env node

'use strict';

require('hexo-cli')();


================================================
FILE: eslint.config.js
================================================
const config = require('eslint-config-hexo/ts');
const testConfig = require('eslint-config-hexo/test');

module.exports = [
  // Configurations applied globally
  ...config,
  {
    rules: {
      '@typescript-eslint/no-explicit-any': 0,
      '@typescript-eslint/no-var-requires': 0,
      '@typescript-eslint/no-require-imports': 0,
      'n/no-missing-require': 0,
      'n/no-missing-import': 0,
      '@typescript-eslint/no-unused-vars': [
        'error', {
          'argsIgnorePattern': '^_'
        }
      ]
    }
  },
  // Configurations applied only to test files
  {
    files: [
      'test/**/*.ts'
    ],
    languageOptions: {
      ...testConfig.languageOptions
    },
    rules: {
      ...testConfig.rules,
      '@typescript-eslint/ban-ts-comment': 0,
      '@typescript-eslint/no-unused-expressions': 0,
      '@typescript-eslint/no-unused-vars': [
        'error', {
          'varsIgnorePattern': '^_',
          'argsIgnorePattern': '^_',
          'caughtErrorsIgnorePattern': '^_'
        }
      ]
    }
  }
];


================================================
FILE: lib/box/file.ts
================================================
import type Promise from 'bluebird';
import { readFile, readFileSync, stat, statSync, type ReadFileOptions } from 'hexo-fs';
import type fs from 'fs';

class File {

  /**
   * Full path of the file
   */
  public source: string;

  /**
   * Relative path to the box of the file
   */
  public path: string;

  /**
   * The information from path matching.
   */
  public params: any;

  /**
   * File type. The value can be create, update, skip, delete.
   */
  // eslint-disable-next-line no-use-before-define
  public type: typeof File.TYPE_CREATE | typeof File.TYPE_UPDATE | typeof File.TYPE_SKIP | typeof File.TYPE_DELETE;
  static TYPE_CREATE: 'create';
  static TYPE_UPDATE: 'update';
  static TYPE_SKIP: 'skip';
  static TYPE_DELETE: 'delete';

  constructor({ source, path, params, type }: {
    source: string;
    path: string;
    params: any;
    type: typeof File.TYPE_CREATE | typeof File.TYPE_UPDATE | typeof File.TYPE_SKIP | typeof File.TYPE_DELETE;
  }) {
    this.source = source;
    this.path = path;
    this.params = params;
    this.type = type;
  }

  read(options?: ReadFileOptions): Promise<string> {
    return readFile(this.source, options) as Promise<string>;
  }

  readSync(options?: ReadFileOptions): string {
    return readFileSync(this.source, options) as string;
  }

  stat(): Promise<fs.Stats> {
    return stat(this.source);
  }

  statSync(): fs.Stats {
    return statSync(this.source);
  }
}

File.TYPE_CREATE = 'create';
File.TYPE_UPDATE = 'update';
File.TYPE_SKIP = 'skip';
File.TYPE_DELETE = 'delete';

export = File;


================================================
FILE: lib/box/index.ts
================================================
import { join, sep } from 'path';
import BlueBirdPromise from 'bluebird';
import File from './file';
import { Pattern, createSha1Hash } from 'hexo-util';
import { createReadStream, readdir, stat, watch } from 'hexo-fs';
import { magenta } from 'picocolors';
import { EventEmitter } from 'events';
import { isMatch, makeRe } from 'micromatch';
import type Hexo from '../hexo';
import type { NodeJSLikeCallback } from '../types';
import type fs from 'fs';

const defaultPattern = new Pattern(() => ({}));

interface Processor {
  pattern: Pattern;
  process: (file?: File) => any;
}

interface BoxOptions {
  persistent: boolean;
  awaitWriteFinish: { stabilityThreshold: number };
  ignored: RegExp[];
  [key: string]: any;
}

class Box extends EventEmitter {
  public options: BoxOptions;
  public context: Hexo;
  public base: string;
  public processors: Processor[];
  public _processingFiles: Record<string, boolean>;
  public watcher: Awaited<ReturnType<typeof watch>> | null;
  public Cache: any;
  // TODO: replace runtime class _File
  public File: any;
  public ignore: string[];

  constructor(ctx: Hexo, base: string, options?: any) {
    super();

    this.options = Object.assign({
      persistent: true,
      awaitWriteFinish: {
        stabilityThreshold: 200
      }
    }, options);

    if (!base.endsWith(sep)) {
      base += sep;
    }

    this.context = ctx;
    this.base = base;
    this.processors = [];
    this._processingFiles = {};
    this.watcher = null;
    this.Cache = ctx.model('Cache');
    this.File = this._createFileClass();
    let targets = this.options.ignored as unknown as string[] || [];
    if (ctx.config.ignore && ctx.config.ignore.length) {
      targets = targets.concat(ctx.config.ignore);
    }
    this.ignore = targets;
    this.options.ignored = targets.map(s => toRegExp(ctx, s)).filter(x => x);
  }

  _createFileClass() {
    const ctx = this.context;

    class _File extends File {
      public box: Box;

      render(options?: any) {
        return ctx.render.render({
          path: this.source
        }, options);
      }

      renderSync(options?: any) {
        return ctx.render.renderSync({
          path: this.source
        }, options);
      }
    }

    _File.prototype.box = this;

    return _File;
  }

  addProcessor(pattern: (...args: any[]) => any): void;
  addProcessor(pattern: string | RegExp | Pattern | ((str: string) => any), fn: (...args: any[]) => any): void;
  addProcessor(pattern: string | RegExp | Pattern | ((str: string) => any), fn?: (...args: any[]) => any): void {
    if (!fn && typeof pattern === 'function') {
      fn = pattern;
      pattern = defaultPattern;
    }

    if (typeof fn !== 'function') throw new TypeError('fn must be a function');
    if (!(pattern instanceof Pattern)) pattern = new Pattern(pattern);

    this.processors.push({
      pattern,
      process: fn
    });
  }

  _readDir(base: string, prefix = ''): BlueBirdPromise<string[]> {
    const { context: ctx } = this;
    const results: string[] = [];
    return readDirWalker(ctx, base, results, this.ignore, prefix)
      .return(results)
      .map(path => this._checkFileStatus(path))
      .map(file => this._processFile(file.type, file.path).return(file.path));
  }

  _checkFileStatus(path: string): { type: string; path: string } {
    const { Cache, context: ctx } = this;
    const src = join(this.base, path);

    return Cache.compareFile(
      escapeBackslash(src.substring(ctx.base_dir.length)),
      () => getHash(src),
      () => stat(src)
    ).then(result => ({
      type: result.type,
      path
    }));
  }

  process(callback?: NodeJSLikeCallback<any>): BlueBirdPromise<void | (string | void)[]> {
    const { base, Cache, context: ctx } = this;

    return stat(base).then(stats => {
      if (!stats.isDirectory()) return;

      // Check existing files in cache
      const relativeBase = escapeBackslash(base.substring(ctx.base_dir.length));
      const cacheFiles: string[] = Cache.filter(item => item._id.startsWith(relativeBase)).map(item => item._id.substring(relativeBase.length));

      // Handle deleted files
      return this._readDir(base)
        .then(files => cacheFiles.filter(path => !files.includes(path)))
        .map(path => this._processFile(File.TYPE_DELETE, path));
    }).catch(err => {
      if (err && err.code !== 'ENOENT') throw err;
    }).asCallback(callback);
  }

  _processFile(type: string, path: string): BlueBirdPromise<void | string> {
    if (this._processingFiles[path]) {
      return BlueBirdPromise.resolve();
    }

    this._processingFiles[path] = true;

    const { base, File, context: ctx } = this;

    this.emit('processBefore', {
      type,
      path
    });

    return BlueBirdPromise.reduce(this.processors, (count, processor) => {
      const params = processor.pattern.match(path);
      if (!params) return count;

      const file: File = new File({
        // source is used for filesystem path, keep backslashes on Windows
        source: join(base, path),
        // path is used for URL path, replace backslashes on Windows
        path: escapeBackslash(path),
        params,
        type
      });

      return Reflect.apply(BlueBirdPromise.method(processor.process), ctx, [file])
        .thenReturn(count + 1);
    }, 0).then(count => {
      if (count) {
        ctx.log.debug('Processed: %s', magenta(path));
      }

      this.emit('processAfter', {
        type,
        path
      });
    }).catch(err => {
      ctx.log.error({ err }, 'Process failed: %s', magenta(path));
    }).finally(() => {
      this._processingFiles[path] = false;
    }).thenReturn(path);
  }

  watch(callback?: NodeJSLikeCallback<never>): BlueBirdPromise<void> {
    if (this.isWatching()) {
      return BlueBirdPromise.reject(new Error('Watcher has already started.')).asCallback(callback);
    }

    const { base } = this;

    function getPath(path) {
      return escapeBackslash(path.substring(base.length));
    }

    return this.process().then(() => watch(base, this.options)).then(watcher => {
      this.watcher = watcher;

      watcher.on('add', path => {
        this._processFile(File.TYPE_CREATE, getPath(path));
      });

      watcher.on('change', path => {
        this._processFile(File.TYPE_UPDATE, getPath(path));
      });

      watcher.on('unlink', path => {
        this._processFile(File.TYPE_DELETE, getPath(path));
      });

      watcher.on('addDir', path => {
        let prefix = getPath(path);
        if (prefix) prefix += '/';

        this._readDir(path, prefix);
      });
    }).asCallback(callback);
  }

  unwatch(): void {
    if (!this.isWatching()) return;

    this.watcher.close();
    this.watcher = null;
  }

  isWatching(): boolean {
    return Boolean(this.watcher);
  }
}

function escapeBackslash(path: string): string {
  // Replace backslashes on Windows
  return path.replace(/\\/g, '/');
}

function getHash(path: string): BlueBirdPromise<string> {
  const src = createReadStream(path);
  const hasher = createSha1Hash();

  const finishedPromise = new BlueBirdPromise((resolve, reject) => {
    src.once('error', reject);
    src.once('end', resolve);
  });

  src.on('data', chunk => { hasher.update(chunk); });

  return finishedPromise.then(() => hasher.digest('hex'));
}

function toRegExp(ctx: Hexo, arg: string): RegExp | null {
  if (!arg) return null;
  if (typeof arg !== 'string') {
    ctx.log.warn('A value of "ignore:" section in "_config.yml" is not invalid (not a string)');
    return null;
  }
  const result = makeRe(arg);
  if (!result) {
    ctx.log.warn('A value of "ignore:" section in "_config.yml" can not be converted to RegExp:' + arg);
    return null;
  }
  return result;
}

function isIgnoreMatch(path: string, ignore: string | string[]): boolean {
  return path && ignore && ignore.length && isMatch(path, ignore);
}

function readDirWalker(ctx: Hexo, base: string, results: string[], ignore: string | string[], prefix: string): BlueBirdPromise<any> {
  if (isIgnoreMatch(base, ignore)) return BlueBirdPromise.resolve();

  return BlueBirdPromise.map(readdir(base).catch(err => {
    ctx.log.error({ err }, 'Failed to read directory: %s', base);
    if (err && err.code === 'ENOENT') return [];
    throw err;
  }), async (path: string) => {
    const fullPath = join(base, path);
    const stats: fs.Stats | null = await stat(fullPath).catch(err => {
      ctx.log.error({ err }, 'Failed to stat file: %s', fullPath);
      if (err && err.code === 'ENOENT') return null;
      throw err;
    });
    const prefixPath = `${prefix}${path}`;
    if (stats) {
      if (stats.isDirectory()) {
        return readDirWalker(ctx, fullPath, results, ignore, `${prefixPath}/`);
      }
      if (!isIgnoreMatch(fullPath, ignore)) {
        results.push(prefixPath);
      }
    }
  });
}

export interface _File extends File {
  box: Box;
  render(options?: any): any;
  renderSync(options?: any): any;
}

export default Box;


================================================
FILE: lib/extend/console.ts
================================================
import Promise from 'bluebird';
import abbrev from 'abbrev';
import type { NodeJSLikeCallback } from '../types';
import type Hexo from '../hexo';

type Option = Partial<{
  usage: string;
  desc: string;
  init: boolean;
  arguments: {
      name: string;
      desc: string;
    }[];
  options: {
    name: string;
    desc: string;
  }[];
}>

interface Args {
  _: string[];
  [key: string]: string | boolean | string[];
}
type AnyFn = (this: Hexo, args: Args, callback?: NodeJSLikeCallback<any>) => any;
interface StoreFunction {
  (this: Hexo, args: Args): Promise<any>;
  desc?: string;
  options?: Option;
}

interface Store {
  [key: string]: StoreFunction
}
interface Alias {
  [abbreviation: string]: string
}

/**
 * The console forms the bridge between Hexo and its users. It registers and describes the available console commands.
 */
class Console {
  public store: Store;
  public alias: Alias;

  constructor() {
    this.store = {};
    this.alias = {};
  }

  /**
   * Get a console plugin function by name
   * @param {String} name - The name of the console plugin
   * @returns {StoreFunction} - The console plugin function
   */
  get(name: string): StoreFunction {
    name = name.toLowerCase();
    return this.store[this.alias[name]];
  }

  list(): Store {
    return this.store;
  }

  /**
   * Register a console plugin
   * @param {String} name - The name of console plugin to be registered
   * @param {String} desc - More detailed information about a console command
   * @param {Option} options - The description of each option of a console command
   * @param {AnyFn} fn - The console plugin to be registered
   */
  register(name: string, fn: AnyFn): void
  register(name: string, desc: string, fn: AnyFn): void
  register(name: string, options: Option, fn: AnyFn): void
  register(name: string, desc: string, options: Option, fn: AnyFn): void
  register(name: string, desc: string | Option | AnyFn, options?: Option | AnyFn, fn?: AnyFn): void {
    if (!name) throw new TypeError('name is required');

    if (!fn) {
      if (options) {
        if (typeof options === 'function') {
          fn = options;

          if (typeof desc === 'object') { // name, options, fn
            options = desc;
            desc = '';
          } else { // name, desc, fn
            options = {};
          }
        } else {
          throw new TypeError('fn must be a function');
        }
      } else {
        // name, fn
        if (typeof desc === 'function') {
          fn = desc;
          options = {};
          desc = '';
        } else {
          throw new TypeError('fn must be a function');
        }
      }
    }

    if (fn.length > 1) {
      fn = Promise.promisify(fn);
    } else {
      fn = Promise.method(fn);
    }

    const c = fn as StoreFunction;
    this.store[name.toLowerCase()] = c;
    c.options = options as Option;
    c.desc = desc as string;

    this.alias = abbrev(Object.keys(this.store));
  }
}

export = Console;


================================================
FILE: lib/extend/deployer.ts
================================================
import Promise from 'bluebird';
import type { NodeJSLikeCallback } from '../types';
import type Hexo from '../hexo';

interface StoreFunction {
  (this: Hexo, deployArg: { type: string; [key: string]: any }): Promise<any>;
}
interface Store {
  [key: string]: StoreFunction;
}

/**
 * A deployer helps users quickly deploy their site to a remote server without complicated commands.
 */
class Deployer {
  public store: Store;

  constructor() {
    this.store = {};
  }

  list(): Store {
    return this.store;
  }

  get(name: string): StoreFunction {
    return this.store[name];
  }

  register(
    name: string,
    fn: (
      this: Hexo,
      deployArg: {
        type: string;
        [key: string]: any;
      },
      callback?: NodeJSLikeCallback<any>
    ) => any
  ): void {
    if (!name) throw new TypeError('name is required');
    if (typeof fn !== 'function') throw new TypeError('fn must be a function');

    if (fn.length > 1) {
      fn = Promise.promisify(fn);
    } else {
      fn = Promise.method(fn);
    }

    this.store[name] = fn;
  }
}

export = Deployer;


================================================
FILE: lib/extend/filter.ts
================================================
import Promise from 'bluebird';
import { FilterOptions } from '../types';

const typeAlias = {
  pre: 'before_post_render',
  post: 'after_post_render',
  'after_render:html': '_after_html_render'
};

interface StoreFunction {
  (data?: any, ...args: any[]): any;
  priority?: number;
}

interface Store {
  [key: string]: StoreFunction[]
}

/**
 * A filter is used to modify some specified data. Hexo passes data to filters in sequence and the filters then modify the data one after the other.
 * This concept was borrowed from WordPress.
 */
class Filter {
  public store: Store;

  constructor() {
    this.store = {};
  }

  list(): Store;
  list(type: string): StoreFunction[];
  list(type?: string) {
    if (!type) return this.store;
    return this.store[type] || [];
  }

  register(fn: StoreFunction): void
  register(fn: StoreFunction, priority: number): void
  register(type: string, fn: StoreFunction): void
  register(type: string, fn: StoreFunction, priority: number): void
  register(type: string | StoreFunction, fn?: StoreFunction | number, priority?: number): void {
    if (!priority) {
      if (typeof type === 'function') {
        priority = fn as number;
        fn = type;
        type = 'after_post_render';
      }
    }

    if (typeof fn !== 'function') throw new TypeError('fn must be a function');

    type = typeAlias[type as string] || type;
    priority = priority == null ? 10 : priority;

    const store = this.store[type as string] || [];
    this.store[type as string] = store;

    fn.priority = priority;
    store.push(fn);

    store.sort((a, b) => a.priority - b.priority);
  }

  unregister(type: string, fn: StoreFunction): void {
    if (!type) throw new TypeError('type is required');
    if (typeof fn !== 'function') throw new TypeError('fn must be a function');

    type = typeAlias[type] || type;

    const list = this.list(type);
    if (!list || !list.length) return;

    const index = list.indexOf(fn);

    if (index !== -1) list.splice(index, 1);
  }

  exec(type: string, data: any, options: FilterOptions = {}): Promise<any> {
    const filters = this.list(type);
    if (filters.length === 0) return Promise.resolve(data);

    const ctx = options.context;
    const args = options.args || [];

    args.unshift(data);

    return Promise.each(filters, filter => Reflect.apply(Promise.method(filter), ctx, args).then(result => {
      args[0] = result == null ? args[0] : result;
      return args[0];
    })).then(() => args[0]);
  }

  execSync(type: string, data: any, options: FilterOptions = {}) {
    const filters = this.list(type);
    const filtersLen = filters.length;
    if (filtersLen === 0) return data;

    const ctx = options.context;
    const args = options.args || [];

    args.unshift(data);

    for (let i = 0, len = filtersLen; i < len; i++) {
      const result = Reflect.apply(filters[i], ctx, args);
      args[0] = result == null ? args[0] : result;
    }

    return args[0];
  }
}

export = Filter;


================================================
FILE: lib/extend/generator.ts
================================================
import Promise from 'bluebird';
import type { BaseGeneratorReturn, NodeJSLikeCallback, SiteLocals } from '../types';

type ReturnType = BaseGeneratorReturn | BaseGeneratorReturn[];
type GeneratorReturnType = ReturnType | Promise<ReturnType>;

interface GeneratorFunction {
  (locals: SiteLocals, callback?: NodeJSLikeCallback<any>): GeneratorReturnType;
}

type StoreFunctionReturn = Promise<ReturnType>;

interface StoreFunction {
  (locals: SiteLocals): StoreFunctionReturn;
}

interface Store {
  [key: string]: StoreFunction
}

/**
 * A generator builds routes based on processed files.
 */
class Generator {
  public id: number;
  public store: Store;

  constructor() {
    this.id = 0;
    this.store = {};
  }

  list(): Store {
    return this.store;
  }

  get(name: string): StoreFunction {
    return this.store[name];
  }

  register(fn: GeneratorFunction): void
  register(name: string, fn: GeneratorFunction): void
  register(name: string | GeneratorFunction, fn?: GeneratorFunction): void {
    if (!fn) {
      if (typeof name === 'function') { // fn
        fn = name;
        name = `generator-${this.id++}`;
      } else {
        throw new TypeError('fn must be a function');
      }
    }

    if (fn.length > 1) fn = Promise.promisify(fn);
    this.store[name as string] = Promise.method(fn);
  }
}

export = Generator;


================================================
FILE: lib/extend/helper.ts
================================================
import Hexo from '../hexo';
import { PageSchema } from '../types';
import * as hutil from 'hexo-util';

interface HexoContext extends Hexo {
  // get current page information
  // https://github.com/dimaslanjaka/hexo-renderers/blob/147340f6d03a8d3103e9589ddf86778ed7f9019b/src/helper/related-posts.ts#L106-L113
  page?: PageSchema;

  // hexo-util shims
  url_for: typeof hutil.url_for;
  full_url_for: typeof hutil.full_url_for;
  relative_url: typeof hutil.relative_url;
  slugize: typeof hutil.slugize;
  escapeDiacritic: typeof hutil.escapeDiacritic;
  escapeHTML: typeof hutil.escapeHTML;
  unescapeHTML: typeof hutil.unescapeHTML;
  encodeURL: typeof hutil.encodeURL;
  decodeURL: typeof hutil.decodeURL;
  escapeRegExp: typeof hutil.escapeRegExp;
  stripHTML: typeof hutil.stripHTML;
  stripIndent: typeof hutil.stripIndent;
  hash: typeof hutil.hash;
  createSha1Hash: typeof hutil.createSha1Hash;
  highlight: typeof hutil.highlight;
  prismHighlight: typeof hutil.prismHighlight;
  tocObj: typeof hutil.tocObj;
  wordWrap: typeof hutil.wordWrap;
  prettyUrls: typeof hutil.prettyUrls;
  isExternalLink: typeof hutil.isExternalLink;
  gravatar: typeof hutil.gravatar;
  htmlTag: typeof hutil.htmlTag;
  truncate: typeof hutil.truncate;
  spawn: typeof hutil.spawn;
  camelCaseKeys: typeof hutil.camelCaseKeys;
  deepMerge: typeof hutil.deepMerge;
}

interface StoreFunction {
  (this: HexoContext, ...args: any[]): any;
}

interface Store {
  [key: string]: StoreFunction;
}

/**
 * A helper makes it easy to quickly add snippets to your templates. We recommend using helpers instead of templates when you’re dealing with more complicated code.
 */
class Helper {
  public store: Store;

  constructor() {
    this.store = {};
  }

  /**
   * @returns {Store} - The plugin store
   */
  list(): Store {
    return this.store;
  }

  /**
   * Get helper plugin function by name
   * @param {String} name - The name of the helper plugin
   * @returns {StoreFunction}
   */
  get(name: string): StoreFunction {
    return this.store[name];
  }

  /**
   * Register a helper plugin
   * @param {String} name - The name of the helper plugin
   * @param {StoreFunction} fn - The helper plugin function
   */
  register(name: string, fn: StoreFunction): void {
    if (!name) throw new TypeError('name is required');
    if (typeof fn !== 'function') throw new TypeError('fn must be a function');

    this.store[name] = fn;
  }
}

export = Helper;


================================================
FILE: lib/extend/index.ts
================================================
export { default as Console } from './console';
export { default as Deployer } from './deployer';
export { default as Filter } from './filter';
export { default as Generator } from './generator';
export { default as Helper } from './helper';
export { default as Highlight } from './syntax_highlight';
export { default as Injector } from './injector';
export { default as Migrator } from './migrator';
export { default as Processor } from './processor';
export { default as Renderer } from './renderer';
export { default as Tag } from './tag';


================================================
FILE: lib/extend/injector.ts
================================================
import { Cache } from 'hexo-util';

type Entry = 'head_begin' | 'head_end' | 'body_begin' | 'body_end';

type Store = {
  [key in Entry]: {
    [key: string]: Set<string>;
  };
};

/**
 * An injector is used to add static code snippet to the `<head>` or/and `<body>` of generated HTML files.
 * Hexo run injector before `after_render:html` filter is executed.
 */
class Injector {
  public store: Store;
  public cache: InstanceType<typeof Cache>;
  public page: any;

  constructor() {
    this.store = {
      head_begin: {},
      head_end: {},
      body_begin: {},
      body_end: {}
    };

    this.cache = new Cache();
  }

  list(): Store {
    return this.store;
  }

  get(entry: Entry, to = 'default'): any[] {
    return Array.from(this.store[entry][to] || []);
  }

  getText(entry: Entry, to = 'default'): string {
    const arr = this.get(entry, to);
    if (!arr || !arr.length) return '';
    return arr.join('');
  }

  getSize(entry: Entry): number {
    return this.cache.apply(`${entry}-size`, () => Object.keys(this.store[entry]).length) as number;
  }

  register(entry: Entry, value: string | (() => string), to = 'default'): void {
    if (!entry) throw new TypeError('entry is required');
    if (typeof value === 'function') value = value();

    const entryMap = this.store[entry] || this.store.head_end;
    const valueSet = entryMap[to] || new Set();
    valueSet.add(value);
    entryMap[to] = valueSet;
  }

  _getPageType(pageLocals): string {
    let currentType = 'default';
    if (pageLocals.__index) currentType = 'home';
    if (pageLocals.__post) currentType = 'post';
    if (pageLocals.__page) currentType = 'page';
    if (pageLocals.archive) currentType = 'archive';
    if (pageLocals.category) currentType = 'category';
    if (pageLocals.tag) currentType = 'tag';
    if (pageLocals.layout) currentType = pageLocals.layout;

    return currentType;
  }

  _injector(input: string, pattern: string | RegExp, flag: Entry, isBegin = true, currentType: string): string {
    if (input.includes(`<!-- hexo injector ${flag}`)) return input;

    const code = this.cache.apply(`${flag}-${currentType}-code`, () => {
      const content = currentType === 'default' ? this.getText(flag, 'default') : this.getText(flag, currentType) + this.getText(flag, 'default');

      if (!content.length) return '';
      return '<!-- hexo injector ' + flag + ' start -->' + content + '<!-- hexo injector ' + flag + ' end -->';
    }) as string;

    // avoid unnecessary replace() for better performance
    if (!code.length) return input;

    return input.replace(pattern, str => { return isBegin ? str + code : code + str; });
  }

  exec(data: string, locals = { page: {} }): string {
    const { page } = locals;
    const currentType = this._getPageType(page);

    if (this.getSize('head_begin') !== 0) {
      // Inject head_begin
      data = this._injector(data, /<head.*?>/, 'head_begin', true, currentType);
    }

    if (this.getSize('head_end') !== 0) {
      // Inject head_end
      data = this._injector(data, '</head>', 'head_end', false, currentType);
    }

    if (this.getSize('body_begin') !== 0) {
      // Inject body_begin
      data = this._injector(data, /<body.*?>/, 'body_begin', true, currentType);
    }

    if (this.getSize('body_end') !== 0) {
      // Inject body_end
      data = this._injector(data, '</body>', 'body_end', false, currentType);
    }

    return data;
  }
}

export = Injector;


================================================
FILE: lib/extend/migrator.ts
================================================
import Promise from 'bluebird';
import type { NodeJSLikeCallback } from '../types';
import type Hexo from '../hexo';

interface StoreFunction {
  (this: Hexo, args: any): Promise<any>;
}

interface Store {
  [key: string]: StoreFunction
}

/**
 * A migrator helps users migrate from other systems to Hexo.
 */
class Migrator {
  public store: Store;

  constructor() {
    this.store = {};
  }

  list(): Store {
    return this.store;
  }

  get(name: string): StoreFunction {
    return this.store[name];
  }

  register(name: string, fn: (this: Hexo, args: any, callback?: NodeJSLikeCallback<any>) => any): void {
    if (!name) throw new TypeError('name is required');
    if (typeof fn !== 'function') throw new TypeError('fn must be a function');

    if (fn.length > 1) {
      fn = Promise.promisify(fn);
    } else {
      fn = Promise.method(fn);
    }

    this.store[name] = fn;
  }
}

export = Migrator;


================================================
FILE: lib/extend/processor.ts
================================================
import Promise from 'bluebird';
import { Pattern } from 'hexo-util';
import type File from '../box/file';

interface StoreFunction {
  (file: File | string): any;
}

type Store = {
  pattern: Pattern;
  process: StoreFunction;
}[];

type patternType = Exclude<ConstructorParameters<typeof Pattern>[0], (str: string) => string>;

/**
 * A processor is used to process source files in the `source` folder.
 */
class Processor {
  public store: Store;

  constructor() {
    this.store = [];
  }

  list(): Store {
    return this.store;
  }

  register(fn: StoreFunction): void;
  register(pattern: patternType, fn: StoreFunction): void;
  register(pattern: patternType | StoreFunction, fn?: StoreFunction): void {
    if (!fn) {
      if (typeof pattern === 'function') {
        fn = pattern;
        pattern = /(.*)/;
      } else {
        throw new TypeError('fn must be a function');
      }
    }

    if (fn.length > 1) {
      fn = Promise.promisify(fn);
    } else {
      fn = Promise.method(fn);
    }

    this.store.push({
      pattern: new Pattern(pattern as patternType),
      process: fn
    });
  }
}

export = Processor;


================================================
FILE: lib/extend/renderer.ts
================================================
import { extname } from 'path';
import Promise from 'bluebird';
import type { NodeJSLikeCallback } from '../types';

const getExtname = (str: string) => {
  if (typeof str !== 'string') return '';

  const ext = extname(str) || str;
  return ext.startsWith('.') ? ext.slice(1) : ext;
};

export interface StoreFunctionData {
  path?: any;
  text?: string;
  engine?: string;
  toString?: any;
  onRenderEnd?: (data: string) => any;
}

export interface StoreSyncFunction {
  (
    data: StoreFunctionData,
    options?: object
  ): any;
  output?: string;
  compile?: (data: StoreFunctionData) => (local: any) => any;
  disableNunjucks?: boolean;
  [key: string]: any;
}

export interface StoreFunction {
  (
    data: StoreFunctionData,
    options?: object
  ): Promise<any>;
  output?: string;
  compile?: (data: StoreFunctionData) => (local: any) => any;
  disableNunjucks?: boolean;
  [key: string]: any;
}

interface StoreFunctionWithCallback {
  (
    data: StoreFunctionData,
    options: object,
    callback?: NodeJSLikeCallback<any>
  ): Promise<any>;
  output?: string;
  compile?: (data: StoreFunctionData) => (local: any) => any;
  disableNunjucks?: boolean;
  [key: string]: any;
}

interface SyncStore {
  [key: string]: StoreSyncFunction;
}
interface Store {
  [key: string]: StoreFunction;
}

/**
 * A renderer is used to render content.
 */
class Renderer {
  public store: Store;
  public storeSync: SyncStore;

  constructor() {
    this.store = {};
    this.storeSync = {};
  }

  list(sync = false): Store | SyncStore {
    return sync ? this.storeSync : this.store;
  }

  get(name: string, sync?: boolean): StoreSyncFunction | StoreFunction {
    const store = this[sync ? 'storeSync' : 'store'];

    return store[getExtname(name)] || store[name];
  }

  isRenderable(path: string): boolean {
    return Boolean(this.get(path));
  }

  isRenderableSync(path: string): boolean {
    return Boolean(this.get(path, true));
  }

  getOutput(path: string): string {
    const renderer = this.get(path);
    return renderer ? renderer.output : '';
  }

  register(name: string, output: string, fn: StoreFunctionWithCallback): void;
  register(name: string, output: string, fn: StoreFunctionWithCallback, sync: false): void;
  register(name: string, output: string, fn: StoreSyncFunction, sync: true): void;
  register(name: string, output: string, fn: StoreFunctionWithCallback | StoreSyncFunction, sync: boolean): void;
  register(name: string, output: string, fn: StoreFunctionWithCallback | StoreSyncFunction, sync?: boolean) {
    if (!name) throw new TypeError('name is required');
    if (!output) throw new TypeError('output is required');
    if (typeof fn !== 'function') throw new TypeError('fn must be a function');

    name = getExtname(name);
    output = getExtname(output);

    if (sync) {
      this.storeSync[name] = fn;
      this.storeSync[name].output = output;

      this.store[name] = Promise.method(fn);
      this.store[name].disableNunjucks = (fn as StoreFunction).disableNunjucks;
    } else {
      if (fn.length > 2) fn = Promise.promisify(fn);
      this.store[name] = fn;
    }

    this.store[name].output = output;
    this.store[name].compile = fn.compile;
  }
}

export default Renderer;


================================================
FILE: lib/extend/syntax_highlight.ts
================================================
import type Hexo from '../hexo';

export interface HighlightOptions {
  lang: string | undefined,
  caption: string | undefined,
  lines_length?: number | undefined,

  // plugins/filter/before_post_render/backtick_code_block
  firstLineNumber?: string | number

  // plugins/tag/code.ts
  language_attr?: boolean | undefined;
  firstLine?: string | number;
  line_number?: boolean | undefined;
  line_threshold?: number | undefined;
  mark?: number[] | string;
  wrap?: boolean | undefined;

}

interface HighlightExecArgs {
  context?: Hexo;
  args?: [string, HighlightOptions];
}

interface StoreFunction {
  (content: string, options: HighlightOptions): string;
  priority?: number;
}

interface Store {
  [key: string]: StoreFunction
}

class SyntaxHighlight {
  public store: Store;

  constructor() {
    this.store = {};
  }

  register(name: string, fn: StoreFunction): void {
    if (typeof fn !== 'function') throw new TypeError('fn must be a function');

    this.store[name] = fn;
  }

  query(name: string): StoreFunction {
    return name && this.store[name];
  }

  exec(name: string, options: HighlightExecArgs): string {
    const fn = this.store[name];

    if (!fn) throw new TypeError(`syntax highlighter ${name} is not registered`);
    const ctx = options.context;
    const args = options.args || [];

    return Reflect.apply(fn, ctx, args);
  }
}

export default SyntaxHighlight;


================================================
FILE: lib/extend/tag.ts
================================================
import { stripIndent } from 'hexo-util';
import { cyan, magenta, red, bold } from 'picocolors';
import { Environment } from 'nunjucks';
import Promise from 'bluebird';
import type { NodeJSLikeCallback } from '../types';

const rSwigRawFullBlock = /{% *raw *%}/;
const rCodeTag = /<code[^<>]*>[\s\S]+?<\/code>/g;
const escapeSwigTag = (str: string) => str.replace(/{/g, '&#123;').replace(/}/g, '&#125;');

interface TagFunction {
  (args: any[], content: string, callback?: NodeJSLikeCallback<any>): string | PromiseLike<string>;
}
interface AsyncTagFunction {
  (args: any[], content: string): Promise<string>;
}

class NunjucksTag {
  public tags: string[];
  public fn: TagFunction | AsyncTagFunction;

  constructor(name: string, fn: TagFunction | AsyncTagFunction) {
    this.tags = [name];
    this.fn = fn;
  }

  parse(parser, nodes, lexer) {
    const node = this._parseArgs(parser, nodes, lexer);

    return new nodes.CallExtension(this, 'run', node, []);
  }

  _parseArgs(parser, nodes, lexer) {
    const tag = parser.nextToken();
    const node = new nodes.NodeList(tag.lineno, tag.colno);
    const argarray = new nodes.Array(tag.lineno, tag.colno);

    let token;
    let argitem = '';

    while ((token = parser.nextToken(true))) {
      if (token.type === lexer.TOKEN_WHITESPACE || token.type === lexer.TOKEN_BLOCK_END) {
        if (argitem !== '') {
          const argnode = new nodes.Literal(tag.lineno, tag.colno, argitem.trim());
          argarray.addChild(argnode);
          argitem = '';
        }

        if (token.type === lexer.TOKEN_BLOCK_END) {
          break;
        }
      } else {
        argitem += token.value;
      }
    }

    node.addChild(argarray);

    return node;
  }

  run(context, args, _body, _callback) {
    return this._run(context, args, '');
  }

  _run(context, args, body): any {
    return Reflect.apply(this.fn, context.ctx, [args, body]);
  }
}

const trimBody = (body: () => any) => {
  return stripIndent(body()).replace(/^\n?|\n?$/g, '');
};

class NunjucksBlock extends NunjucksTag {
  parse(parser, nodes, lexer) {
    const node = this._parseArgs(parser, nodes, lexer);
    const body = this._parseBody(parser, nodes, lexer);

    return new nodes.CallExtension(this, 'run', node, [body]);
  }

  _parseBody(parser, _nodes, _lexer) {
    const body = parser.parseUntilBlocks(`end${this.tags[0]}`);

    parser.advanceAfterBlockEnd();
    return body;
  }

  run(context, args, body, _callback) {
    return this._run(context, args, trimBody(body));
  }
}

class NunjucksAsyncTag extends NunjucksTag {
  parse(parser, nodes, lexer) {
    const node = this._parseArgs(parser, nodes, lexer);

    return new nodes.CallExtensionAsync(this, 'run', node, []);
  }

  run(context, args, callback) {
    return this._run(context, args, '').then(result => {
      callback(null, result);
    }, callback);
  }
}

class NunjucksAsyncBlock extends NunjucksBlock {
  parse(parser, nodes, lexer) {
    const node = this._parseArgs(parser, nodes, lexer);
    const body = this._parseBody(parser, nodes, lexer);

    return new nodes.CallExtensionAsync(this, 'run', node, [body]);
  }

  run(context, args, body, callback) {
    // enable async tag nesting
    body((err, result) => {
      // wrapper for trimBody expecting
      // body to be a function
      body = () => result || '';

      this._run(context, args, trimBody(body)).then(result => {
        callback(err, result);
      });
    });
  }
}

const getContextLineNums = (min: number, max: number, center: number, amplitude: number) => {
  const result = [];
  let lbound = Math.max(min, center - amplitude);
  const hbound = Math.min(max, center + amplitude);
  while (lbound <= hbound) result.push(lbound++);
  return result;
};

const LINES_OF_CONTEXT = 5;

const getContext = (lines: string[], errLine: number, location: string, type: string) => {
  const message = [
    location + ' ' + red(type),
    cyan('    =====               Context Dump               ====='),
    cyan('    === (line number probably different from source) ===')
  ];

  message.push(
    // get LINES_OF_CONTEXT lines surrounding `errLine`
    ...getContextLineNums(1, lines.length, errLine, LINES_OF_CONTEXT)
      .map(lnNum => {
        const line = '  ' + lnNum + ' | ' + lines[lnNum - 1];
        if (lnNum === errLine) {
          return cyan(bold(line));
        }

        return cyan(line);
      })
  );
  message.push(cyan(
    '    =====             Context Dump Ends            ====='));

  return message;
};

class NunjucksError extends Error {
  line?: number;
  location?: string;
  type?: string;
}

/**
 * Provide context for Nunjucks error
 * @param  {Error}    err Nunjucks error
 * @param  {string}   str string input for Nunjucks
 * @return {Error}    New error object with embedded context
 */
const formatNunjucksError = (err: Error, input: string, source = ''): Error => {
  err.message = err.message.replace('(unknown path)', source ? magenta(source) : '');

  const match = err.message.match(/Line (\d+), Column \d+/);
  if (!match) return err;
  const errLine = parseInt(match[1], 10);
  if (isNaN(errLine)) return err;

  // trim useless info from Nunjucks Error
  const splitted = err.message.split('\n');

  const e = new NunjucksError();
  e.name = 'Nunjucks Error';
  e.line = errLine;
  e.location = splitted[0];
  e.type = splitted[1].trim();
  e.message = getContext(input.split(/\r?\n/), errLine, e.location, e.type).join('\n');
  return e;
};

type RegisterOptions = {
  async?: boolean;
  ends?: boolean;
}

/**
 * A tag allows users to quickly and easily insert snippets into their posts.
 */
class Tag {
  public env: Environment;
  public source: string;

  constructor() {
    this.env = new Environment(null, {
      autoescape: false
    });
  }

  register(name: string, fn: TagFunction): void
  register(name: string, fn: TagFunction, ends: boolean): void
  register(name: string, fn: TagFunction, options: RegisterOptions): void
  register(name: string, fn: TagFunction, options?: RegisterOptions | boolean):void {
    if (!name) throw new TypeError('name is required');
    if (typeof fn !== 'function') throw new TypeError('fn must be a function');

    if (options == null || typeof options === 'boolean') {
      options = { ends: options as boolean };
    }

    let tag: NunjucksTag;

    if (options.async) {
      let asyncFn: AsyncTagFunction;
      if (fn.length > 2) {
        asyncFn = Promise.promisify(fn);
      } else {
        asyncFn = Promise.method(fn);
      }

      if (options.ends) {
        tag = new NunjucksAsyncBlock(name, asyncFn);
      } else {
        tag = new NunjucksAsyncTag(name, asyncFn);
      }
    } else if (options.ends) {
      tag = new NunjucksBlock(name, fn);
    } else {
      tag = new NunjucksTag(name, fn);
    }

    this.env.addExtension(name, tag);
  }

  unregister(name: string): void {
    if (!name) throw new TypeError('name is required');

    const { env } = this;

    if (env.hasExtension(name)) env.removeExtension(name);
  }

  render(str: string): Promise<any>;
  render(str: string, callback: NodeJSLikeCallback<any>): Promise<any>;
  render(str: string, options: { source?: string, [key: string]: any }, callback?: NodeJSLikeCallback<any>): Promise<any>;
  render(str: string, options: { source?: string, [key: string]: any } | NodeJSLikeCallback<any> = {}, callback?: NodeJSLikeCallback<any>): Promise<any> {
    if (!callback && typeof options === 'function') {
      callback = options;
      options = {};
    }

    // Get path of post from source
    const { source = '' } = options as { source?: string };

    return Promise.fromCallback(cb => {
      this.env.renderString(
        str.replace(rCodeTag, s => {
          // https://hexo.io/docs/tag-plugins#Raw
          // https://mozilla.github.io/nunjucks/templating.html#raw
          // Only escape code block when there is no raw tag included
          return s.match(rSwigRawFullBlock) ? s : escapeSwigTag(s);
        }),
        options,
        cb
      );
    }).catch(err => {
      return Promise.reject(formatNunjucksError(err, str, source));
    })
      .asCallback(callback);
  }
}

export = Tag;


================================================
FILE: lib/hexo/default_config.ts
================================================
export = {
  // Site
  title: 'Hexo',
  subtitle: '',
  description: '',
  author: 'John Doe',
  language: 'en',
  timezone: '',
  // URL
  url: 'http://example.com',
  root: '/',
  permalink: ':year/:month/:day/:title/',
  permalink_defaults: {} as Record<string, string>,
  pretty_urls: {
    trailing_index: true,
    trailing_html: true
  },
  // Directory
  source_dir: 'source',
  public_dir: 'public',
  tag_dir: 'tags',
  archive_dir: 'archives',
  category_dir: 'categories',
  code_dir: 'downloads/code',
  i18n_dir: ':lang',
  skip_render: [] as string[],
  // Writing
  new_post_name: ':title.md',
  default_layout: 'post',
  titlecase: false,
  external_link: {
    enable: true,
    field: 'site',
    exclude: ''
  },
  filename_case: 0,
  render_drafts: false,
  post_asset_folder: false,
  relative_link: false,
  future: true,
  syntax_highlighter: 'highlight.js',
  highlight: {
    auto_detect: false,
    line_number: true,
    tab_replace: '',
    wrap: true,
    exclude_languages: [] as string[],
    language_attr: false,
    hljs: false,
    line_threshold: 0,
    first_line_number: 'always1',
    strip_indent: true
  },
  prismjs: {
    preprocess: true,
    line_number: true,
    tab_replace: '',
    exclude_languages: [] as string[],
    strip_indent: true
  },
  use_filename_as_post_title: false,

  // Category & Tag
  default_category: 'uncategorized',
  category_map: {} as Record<string, string>,
  tag_map: {} as Record<string, string>,
  // Date / Time format
  date_format: 'YYYY-MM-DD',
  time_format: 'HH:mm:ss',
  updated_option: 'mtime',
  // * mtime: file modification date (default)
  // * empty: no more update
  // Pagination
  per_page: 10,
  pagination_dir: 'page',
  // Extensions
  theme: 'landscape',
  server: {
    cache: false
  },
  // Deployment
  deploy: {} as { type: string; [keys: string]: any } | { type: string; [keys: string]: any }[],

  // ignore files from processing
  ignore: [] as string[],

  // Category & Tag
  meta_generator: true
};


================================================
FILE: lib/hexo/index.ts
================================================
import Promise from 'bluebird';
import { sep, join, dirname } from 'path';
import tildify from 'tildify';
import Database from 'warehouse';
import { magenta, underline } from 'picocolors';
import { EventEmitter } from 'events';
import { readFile } from 'hexo-fs';
import Module from 'module';
import { runInThisContext } from 'vm';
const { version } = require('../../package.json');
import logger from 'hexo-log';

import {
  Console,
  Deployer,
  Filter,
  Generator,
  Helper,
  Highlight,
  Injector,
  Migrator,
  Processor,
  Renderer,
  Tag
} from '../extend';

import Render from './render';
import registerModels from './register_models';
import Post from './post';
import Scaffold from './scaffold';
import Source from './source';
import Router from './router';
import Theme from '../theme';
import Locals from './locals';
import defaultConfig from './default_config';
import loadDatabase from './load_database';
import multiConfigPath from './multi_config_path';
import { deepMerge, full_url_for } from 'hexo-util';
import type Box from '../box';
import type { BaseGeneratorReturn, FilterOptions, LocalsType, NodeJSLikeCallback, SiteLocals } from '../types';
import type { AddSchemaTypeOptions } from 'warehouse/dist/types';
import type Schema from 'warehouse/dist/schema';
import BinaryRelationIndex from '../models/binary_relation_index';

const libDir = dirname(__dirname);
const dbVersion = 1;

const stopWatcher = (box: Box) => { if (box.isWatching()) box.unwatch(); };

const routeCache = new WeakMap();

const castArray = (obj: any) => { return Array.isArray(obj) ? obj : [obj]; };

// eslint-disable-next-line no-use-before-define
const mergeCtxThemeConfig = (ctx: Hexo) => {
  // Merge hexo.config.theme_config into hexo.theme.config before post rendering & generating
  // config.theme_config has "_config.[theme].yml" merged in load_theme_config.js
  if (ctx.config.theme_config) {
    ctx.theme.config = deepMerge(ctx.theme.config, ctx.config.theme_config);
  }
};

// eslint-disable-next-line no-use-before-define
const createLoadThemeRoute = function(generatorResult: BaseGeneratorReturn, locals: LocalsType, ctx: Hexo) {
  const { log, theme } = ctx;
  const { path, cache: useCache } = locals;

  const layout = [...new Set<string>(castArray(generatorResult.layout))];
  const layoutLength = layout.length;

  // always use cache in fragment_cache
  locals.cache = true;
  return () => {
    if (useCache && routeCache.has(generatorResult)) return routeCache.get(generatorResult);

    for (let i = 0; i < layoutLength; i++) {
      const name = layout[i];
      const view = theme.getView(name);

      if (view) {
        log.debug(`Rendering HTML ${name}: ${magenta(path)}`);
        return view.render(locals)
          .then(result => ctx.extend.injector.exec(result, locals))
          .then(result => ctx.execFilter('_after_html_render', result, {
            context: ctx,
            args: [locals]
          }))
          .tap(result => {
            if (useCache) {
              routeCache.set(generatorResult, result);
            }
          }).tapCatch(err => {
            log.error({ err }, `Render HTML failed: ${magenta(path)}`);
          });
      }
    }

    log.warn(`No layout: ${magenta(path)}`);
  };
};

function debounce(func: () => void, wait: number): () => void {
  let timeout: NodeJS.Timeout;
  return function() {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(this);
    }, wait);
  };
}

interface Args {

  /**
   * Enable debug mode. Display debug messages in the terminal and save debug.log in the root directory.
   */
  debug?: boolean;

  /**
   * Enable safe mode. Don’t load any plugins.
   */
  safe?: boolean;

  /**
   * Enable silent mode. Don’t display any messages in the terminal.
   */
  silent?: boolean;

  /**
   * Enable to add drafts to the posts list.
   */
  draft?: boolean;

    /**
   * Enable to add drafts to the posts list.
   */
  drafts?: boolean;
  _?: string[];
  output?: string;

  /**
   * Specify the path of the configuration file.
   */
  config?: string;
  [key: string]: any;
}

interface Query {
  date?: any;
  published?: boolean;
}

interface Extend {
  console: Console,
  deployer: Deployer,
  filter: Filter,
  generator: Generator,
  helper: Helper,
  highlight: Highlight,
  injector: Injector,
  migrator: Migrator,
  processor: Processor,
  renderer: Renderer,
  tag: Tag
}

interface Env {
  args: Args;
  debug: boolean;
  safe: boolean;
  silent: boolean;
  env: string;
  version: string;
  cmd: string;
  init: boolean;
}

type DefaultConfigType = typeof defaultConfig;
interface Config extends DefaultConfigType {
  [key: string]: any;
}

// Node.js internal APIs
declare module 'module' {
  function _nodeModulePaths(path: string): string[];
  function _resolveFilename(request: string, parent: Module, isMain?: any, options?: any): string;
  const _extensions: NodeJS.RequireExtensions,
    _cache: any;
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
interface Hexo {

  /**
   * Emitted before deployment begins.
   * @param event
   * @param listener
   * @link https://hexo.io/api/events.html#deployBefore
   */
  on(event: 'deployBefore', listener: (...args: any[]) => any): this;

  /**
   * Emitted after deployment begins.
   * @param event
   * @param listener
   * @link https://hexo.io/api/events.html#deployAfter
   */
  on(event: 'deployAfter', listener: (...args: any[]) => any): this;

  /**
   * Emitted before Hexo exits.
   * @param event
   * @param listener
   * @link https://hexo.io/api/events.html#exit
   */
  on(event: 'exit', listener: (...args: any[]) => any): this;

  /**
   * Emitted before generation begins.
   * @param event
   * @param listener
   * @link https://hexo.io/api/events.html#generateBefore
   */
  on(event: 'generateBefore', listener: (...args: any[]) => any): this;

  /**
   * Emitted after generation finishes.
   * @param event
   * @param listener
   * @link https://hexo.io/api/events.html#generateAfter
   */
  on(event: 'generateAfter', listener: (...args: any[]) => any): this;

  /**
   * Emitted after a new post has been created. This event returns the post data:
   * @param event
   * @param listener
   * @link https://hexo.io/api/events.html#new
   */
  on(event: 'new', listener: (post: { path: string; content: string; }) => any): this;

  /**
   * Emitted before processing begins. This event returns a path representing the root directory of the box.
   * @param event
   * @param listener
   * @link https://hexo.io/api/events.html#processBefore
   */
  on(event: 'processBefore', listener: (...args: any[]) => any): this;

  /**
   * Emitted after processing finishes. This event returns a path representing the root directory of the box.
   * @param event
   * @param listener
   * @link https://hexo.io/api/events.html#processAfter
   */
  on(event: 'processAfter', listener: (...args: any[]) => any): this;

  /**
   * Emitted after initialization finishes.
   * @param event
   * @param listener
   */
  on(event: 'ready', listener: (...args: any[]) => any): this;

  /**
   * undescripted on emit
   * @param event
   * @param listener
   */
  on(event: string, listener: (...args: any[]) => any): any;
  emit(event: string, ...args: any[]): any;
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
class Hexo extends EventEmitter {
  public base_dir: string;
  public public_dir: string;
  public source_dir: string;
  public plugin_dir: string;
  public script_dir: string;
  public scaffold_dir: string;
  public theme_dir: string;
  public theme_script_dir: string;
  public env: Env;
  public extend: Extend;
  public config: Config;
  public log: ReturnType<typeof logger>;
  public render: Render;
  public route: Router;
  public post: Post;
  public scaffold: Scaffold;
  public _dbLoaded: boolean;
  public _isGenerating: boolean;
  public database: Database;
  public config_path: string;
  public source: Source;
  public theme: Theme;
  public locals: Locals;
  public version: string;
  public _watchBox: () => void;
  public lib_dir: string;
  public core_dir: string;
  static lib_dir: string;
  static core_dir: string;
  static version: string;
  public _binaryRelationIndex: {
    post_tag: BinaryRelationIndex<'post_id', 'tag_id'>;
    post_category: BinaryRelationIndex<'post_id', 'category_id'>;
  };

  constructor(base = process.cwd(), args: Args = {}) {
    super();

    this.base_dir = base + sep;
    this.public_dir = join(base, 'public') + sep;
    this.source_dir = join(base, 'source') + sep;
    this.plugin_dir = join(base, 'node_modules') + sep;
    this.script_dir = join(base, 'scripts') + sep;
    this.scaffold_dir = join(base, 'scaffolds') + sep;
    this.theme_dir = join(base, 'themes', defaultConfig.theme) + sep;
    this.theme_script_dir = join(this.theme_dir, 'scripts') + sep;

    this.env = {
      args,
      debug: Boolean(args.debug),
      safe: Boolean(args.safe),
      silent: Boolean(args.silent),
      env: process.env.NODE_ENV || 'development',
      version,
      cmd: args._ ? args._[0] : '',
      init: false
    };

    this.extend = {
      console: new Console(),
      deployer: new Deployer(),
      filter: new Filter(),
      generator: new Generator(),
      helper: new Helper(),
      highlight: new Highlight(),
      injector: new Injector(),
      migrator: new Migrator(),
      processor: new Processor(),
      renderer: new Renderer(),
      tag: new Tag()
    };

    this.config = { ...defaultConfig };

    this.log = logger(this.env);

    this.render = new Render(this);

    this.route = new Router();

    this.post = new Post(this);

    this.scaffold = new Scaffold(this);

    this._dbLoaded = false;

    this._isGenerating = false;

    // If `output` is provided, use that as the
    // root for saving the db. Otherwise default to `base`.
    const dbPath = args.output || base;

    if (/^(init|new|g|publish|s|deploy|render|migrate)/.test(this.env.cmd)) {
      this.log.d(`Writing database to ${join(dbPath, 'db.json')}`);
    }

    this.database = new Database({
      version: dbVersion,
      path: join(dbPath, 'db.json')
    });

    const mcp = multiConfigPath(this);

    this.config_path = args.config ? mcp(base, args.config, args.output)
      : join(base, '_config.yml');

    registerModels(this);

    this.source = new Source(this);
    this.theme = new Theme(this);
    this.locals = new Locals();
    this._bindLocals();
    this._binaryRelationIndex = {
      post_tag: new BinaryRelationIndex<'post_id', 'tag_id'>('post_id', 'tag_id', 'PostTag', this),
      post_category: new BinaryRelationIndex<'post_id', 'category_id'>('post_id', 'category_id', 'PostCategory', this)
    };
  }

  _bindLocals(): void {
    const db = this.database;
    const { locals } = this;

    locals.set('posts', () => {
      const query: Query = {};

      if (!this.config.future) {
        query.date = { $lte: Date.now() };
      }

      if (!this._showDrafts()) {
        query.published = true;
      }

      return db.model('Post').find(query);
    });

    locals.set('pages', () => {
      const query: Query = {};

      if (!this.config.future) {
        query.date = { $lte: Date.now() };
      }

      return db.model('Page').find(query);
    });

    locals.set('categories', () => {
      // Ignore categories with zero posts
      return db.model('Category').filter(category => category.length);
    });

    locals.set('tags', () => {
      // Ignore tags with zero posts
      return db.model('Tag').filter(tag => tag.length);
    });

    locals.set('data', () => {
      const obj = {};

      db.model('Data').forEach(data => {
        obj[data._id] = data.data;
      });

      return obj;
    });
  }

  /**
   * Load configuration and plugins.
   * @returns {Promise}
   * @link https://hexo.io/api#Initialize
   */
  init(): Promise<void> {
    this.log.debug('Hexo version: %s', magenta(this.version));
    this.log.debug('Working directory: %s', magenta(tildify(this.base_dir)));

    // Load internal plugins
    require('../plugins/console')(this);
    require('../plugins/filter')(this);
    require('../plugins/generator')(this);
    require('../plugins/helper')(this);
    require('../plugins/highlight')(this);
    require('../plugins/injector')(this);
    require('../plugins/processor')(this);
    require('../plugins/renderer')(this);
    require('../plugins/tag').default(this);

    // Load config
    return Promise.each([
      'update_package', // Update package.json
      'load_config', // Load config
      'load_theme_config', // Load alternate theme config
      'load_plugins' // Load external plugins & scripts
    ], name => require(`./${name}`)(this)).then(() => this.execFilter('after_init', null, { context: this })).then(() => {
      // Ready to go!
      this.emit('ready');
    });
  }

  /**
   * Call any console command explicitly.
   * @param name
   * @param args
   * @param callback
   * @returns {Promise}
   * @link https://hexo.io/api#Execute-Commands
   */
  call(name: string, callback?: NodeJSLikeCallback<any>): Promise<any>;
  call(name: string, args: object, callback?: NodeJSLikeCallback<any>): Promise<any>;
  call(name: string, args?: object | NodeJSLikeCallback<any>, callback?: NodeJSLikeCallback<any>): Promise<any> {
    if (!callback && typeof args === 'function') {
      callback = args as NodeJSLikeCallback<any>;
      args = {};
    }

    const c = this.extend.console.get(name);

    if (c) return (Reflect.apply(c, this, [args]) as any).asCallback(callback);
    return Promise.reject(new Error(`Console \`${name}\` has not been registered yet!`));
  }

  model(name: string, schema?: Schema | Record<string, AddSchemaTypeOptions>) {
    return this.database.model(name, schema);
  }

  resolvePlugin(name: string, basedir: string): string {
    try {
      // Try to resolve the plugin with the Node.js's built-in require.resolve.
      return require.resolve(name, { paths: [basedir] });
    } catch {
      // There was an error (likely the node_modules is corrupt or from early version of npm),
      // so return a possibly non-existing path that a later part of the resolution process will check.
      return join(basedir, 'node_modules', name);
    }
  }

  loadPlugin(path: string, callback?: NodeJSLikeCallback<any>): Promise<any> {
    return readFile(path).then(script => {
      // Based on: https://github.com/nodejs/node-v0.x-archive/blob/v0.10.33/src/node.js#L516
      const module = new Module(path);
      module.filename = path;
      module.paths = Module._nodeModulePaths(path);

      function req(path: string) {
        return module.require(path);
      }

      req.resolve = (request: string) => Module._resolveFilename(request, module);

      req.main = require.main;
      req.extensions = Module._extensions;
      req.cache = Module._cache;

      script = `(async function(exports, require, module, __filename, __dirname, hexo){${script}\n});`;

      const fn = runInThisContext(script, path);

      return fn(module.exports, req, module, path, dirname(path), this);
    }).asCallback(callback);
  }

  _showDrafts(): boolean {
    const { args } = this.env;
    return args.draft || args.drafts || this.config.render_drafts;
  }

  /**
   * Load all files in the source folder as well as the theme data.
   * @param callback
   * @returns {Promise}
   * @link https://hexo.io/api#Load-Files
   */
  load(callback?: NodeJSLikeCallback<any>): Promise<any> {
    return loadDatabase(this).then(() => {
      this._binaryRelationIndex.post_tag.load();
      this._binaryRelationIndex.post_category.load();
      this.log.info('Start processing');

      return Promise.all([
        this.source.process(),
        this.theme.process()
      ]);
    }).then(() => {
      mergeCtxThemeConfig(this);
      return this._generate({ cache: false });
    }).asCallback(callback);
  }

  /**
   * Load all files in the source folder as well as the theme data.
   * Start watching for file changes continuously.
   * @param callback
   * @returns {Promise}
   * @link https://hexo.io/api#Load-Files
   */
  watch(callback?: NodeJSLikeCallback<any>): Promise<any> {
    let useCache = false;
    const { cache } = Object.assign({
      cache: false
    }, this.config.server);
    const { alias } = this.extend.console;

    if (alias[this.env.cmd] === 'server' && cache) {
      // enable cache when run hexo server
      useCache = true;
    }
    this._watchBox = debounce(() => this._generate({ cache: useCache }), 100);

    return loadDatabase(this).then(() => {
      this._binaryRelationIndex.post_tag.load();
      this._binaryRelationIndex.post_category.load();
      this.log.info('Start processing');

      return Promise.all([
        this.source.watch(),
        this.theme.watch()
      ]);
    }).then(() => {
      mergeCtxThemeConfig(this);

      this.source.on('processAfter', this._watchBox);
      this.theme.on('processAfter', () => {
        this._watchBox();
        mergeCtxThemeConfig(this);
      });

      return this._generate({ cache: useCache });
    }).asCallback(callback);
  }

  unwatch(): void {
    if (this._watchBox != null) {
      this.source.removeListener('processAfter', this._watchBox);
      this.theme.removeListener('processAfter', this._watchBox);

      this._watchBox = null;
    }

    stopWatcher(this.source);
    stopWatcher(this.theme);
  }

  _generateLocals() {
    const { config, env, theme, theme_dir } = this;
    const ctx = { config: { url: this.config.url } };
    const localsObj = this.locals.toObject() as SiteLocals;

    class Locals {
      page: any;
      path: string;
      url: string;
      config: Config;
      theme: any;
      layout: string;
      env: Env;
      view_dir: string;
      site: SiteLocals;
      cache?: boolean;

      constructor(path: string, locals: any) {
        this.page = { ...locals };
        if (this.page.path == null) this.page.path = path;
        this.path = path;
        this.url = full_url_for.call(ctx, path);
        this.config = config;
        this.theme = theme.config;
        this.layout = 'layout';
        this.env = env;
        this.view_dir = join(theme_dir, 'layout') + sep;
        this.site = localsObj;
      }
    }

    return Locals;
  }

  _runGenerators(): Promise<BaseGeneratorReturn[]> {
    this.locals.invalidate();
    const siteLocals = this.locals.toObject() as SiteLocals;
    const generators = this.extend.generator.list();
    const { log } = this;

    // Run generators
    return Promise.map(Object.keys(generators), key => {
      const generator = generators[key];

      log.debug('Generator: %s', magenta(key));
      return Reflect.apply(generator, this, [siteLocals]);
    }).reduce((result, data) => {
      return data ? result.concat(data) : result;
    }, []);
  }

  _routerRefresh(runningGenerators: Promise<BaseGeneratorReturn[]>, useCache: boolean): Promise<void> {
    const { route } = this;
    const routeList = route.list();
    const Locals = this._generateLocals();
    Locals.prototype.cache = useCache;

    return runningGenerators.map(generatorResult => {
      if (typeof generatorResult !== 'object' || generatorResult.path == null) return undefined;

      // add Route
      const path = route.format(generatorResult.path);
      const { data, layout } = generatorResult;

      if (!layout) {
        route.set(path, data);
        return path;
      }

      return this.execFilter('template_locals', new Locals(path, data), { context: this })
        .then((locals: LocalsType) => { route.set(path, createLoadThemeRoute(generatorResult, locals, this)); })
        .thenReturn(path);
    }).then(newRouteList => {
      // Remove old routes
      for (let i = 0, len = routeList.length; i < len; i++) {
        const item = routeList[i];

        if (!newRouteList.includes(item)) {
          route.remove(item);
        }
      }
    });
  }

  _generate(options: { cache?: boolean } = {}): Promise<any> {
    if (this._isGenerating) return;

    const useCache = options.cache;

    this._isGenerating = true;

    this.emit('generateBefore');

    // Run before_generate filters
    // https://github.com/hexojs/hexo/issues/5287
    // locals should be invalidated before before_generate filters because tags may use locals
    this.locals.invalidate();
    return this.execFilter('before_generate', null, { context: this })
      .then(() => this._routerRefresh(this._runGenerators(), useCache)).then(() => {
        this.emit('generateAfter');

        // Run after_generate filters
        return this.execFilter('after_generate', null, { context: this });
      }).finally(() => {
        this._isGenerating = false;
      });
  }

  /**
   * Exit gracefully and finish up important things such as saving the database.
   * @param err
   * @returns {Promise}
   * @link https://hexo.io/api/#Exit
   */
  exit(err?: any): Promise<void> {
    if (err) {
      this.log.fatal(
        { err },
        'Something\'s wrong. Maybe you can find the solution here: %s',
        underline('https://hexo.io/docs/troubleshooting.html')
      );
    }

    return this.execFilter('before_exit', null, { context: this }).then(() => {
      this.emit('exit', err);
    });
  }

  execFilter(type: string, data: any, options?: FilterOptions) {
    return this.extend.filter.exec(type, data, options);
  }

  execFilterSync(type: string, data: any, options?: FilterOptions) {
    return this.extend.filter.execSync(type, data, options);
  }
}

Hexo.lib_dir = libDir + sep;
Hexo.prototype.lib_dir = Hexo.lib_dir;

Hexo.core_dir = dirname(libDir) + sep;
Hexo.prototype.core_dir = Hexo.core_dir;

Hexo.version = version;
Hexo.prototype.version = Hexo.version;

// define global variable
// this useful for plugin written in typescript
declare global {
  // eslint-disable-next-line one-var
  const hexo: Hexo;
}

export = Hexo;


================================================
FILE: lib/hexo/load_config.ts
================================================
import { sep, resolve, join, parse, basename, extname } from 'path';
import tildify from 'tildify';
import Theme from '../theme';
import Source from './source';
import { exists, readdir } from 'hexo-fs';
import { magenta } from 'picocolors';
import { deepMerge } from 'hexo-util';
import validateConfig from './validate_config';
import type Hexo from './index';

export = async (ctx: Hexo): Promise<void> => {
  if (!ctx.env.init) return;

  const baseDir = ctx.base_dir;
  let configPath = ctx.config_path;

  const path = await exists(configPath) ? configPath : await findConfigPath(configPath);
  if (!path) return;
  configPath = path;

  let config = await ctx.render.render({ path });
  if (!config || typeof config !== 'object') return;

  ctx.log.debug('Config loaded: %s', magenta(tildify(configPath)));

  ctx.config = deepMerge(ctx.config, config);
  // If root is not exist, create it by config.url
  if (!config.root) {
    let { pathname } = new URL(ctx.config.url);
    if (!pathname.endsWith('/')) pathname += '/';
    ctx.config.root = pathname;
  }
  config = ctx.config;

  validateConfig(ctx);

  ctx.config_path = configPath;
  // Trim multiple trailing '/'
  config.root = config.root.replace(/\/*$/, '/');
  // Remove any trailing '/'
  config.url = config.url.replace(/\/+$/, '');

  ctx.public_dir = resolve(baseDir, config.public_dir) + sep;
  ctx.source_dir = resolve(baseDir, config.source_dir) + sep;
  ctx.source = new Source(ctx);

  if (!config.theme) return;

  const theme = config.theme.toString();
  config.theme = theme;

  const themeDirFromThemes = join(baseDir, 'themes', theme) + sep; // base_dir/themes/[config.theme]/
  const themeDirFromNodeModules = join(ctx.plugin_dir, 'hexo-theme-' + theme) + sep; // base_dir/node_modules/hexo-theme-[config.theme]/

  // themeDirFromThemes has higher priority than themeDirFromNodeModules
  let ignored: string[] = [];
  if (await exists(themeDirFromThemes)) {
    ctx.theme_dir = themeDirFromThemes;
    ignored = ['**/themes/*/node_modules/**', '**/themes/*/.git/**'];
  } else if (await exists(themeDirFromNodeModules)) {
    ctx.theme_dir = themeDirFromNodeModules;
    ignored = ['**/node_modules/hexo-theme-*/node_modules/**', '**/node_modules/hexo-theme-*/.git/**'];
  }
  ctx.theme_script_dir = join(ctx.theme_dir, 'scripts') + sep;
  ctx.theme = new Theme(ctx, { ignored });
};

async function findConfigPath(path: string): Promise<string> {
  const { dir, name } = parse(path);

  const files = await readdir(dir);
  const item = files.find(item => basename(item, extname(item)) === name);
  if (item != null) return join(dir, item);
}


================================================
FILE: lib/hexo/load_database.ts
================================================
import { exists, unlink } from 'hexo-fs';
import Promise from 'bluebird';
import type Hexo from './index';

export = (ctx: Hexo): Promise<void> => {
  if (ctx._dbLoaded) return Promise.resolve();

  const db = ctx.database;
  const { path } = db.options;
  const { log } = ctx;

  return exists(path).then(exist => {
    if (!exist) return;

    log.debug('Loading database.');
    return db.load();
  }).then(() => {
    ctx._dbLoaded = true;
  }).catch(() => {
    log.error('Database load failed. Deleting database.');
    return unlink(path);
  });
};


================================================
FILE: lib/hexo/load_plugins.ts
================================================
import { join } from 'path';
import { exists, readFile, listDir } from 'hexo-fs';
import Promise from 'bluebird';
import { magenta } from 'picocolors';
import type Hexo from './index';

export = (ctx: Hexo): Promise<void[][]> => {
  if (!ctx.env.init || ctx.env.safe) return;

  return loadModules(ctx).then(() => loadScripts(ctx));
};

function loadModuleList(ctx: Hexo, basedir: string): Promise<Record<string, string>> {
  const packagePath = join(basedir, 'package.json');

  // Make sure package.json exists
  return exists(packagePath).then(exist => {
    if (!exist) return [];

    // Read package.json and find dependencies
    return readFile(packagePath).then(content => {
      const json = JSON.parse(content);
      const deps = Object.keys(json.dependencies || {});
      const devDeps = Object.keys(json.devDependencies || {});

      return basedir === ctx.base_dir ? deps.concat(devDeps) : deps;
    });
  }).filter((name: string) => {
    // Ignore plugins whose name is not started with "hexo-"
    if (!/^hexo-|^@[^/]+\/hexo-/.test(name)) return false;

    // Ignore plugin whose name is started with "hexo-theme"
    if (/^hexo-theme-|^@[^/]+\/hexo-theme-/.test(name)) return false;

    // Ignore typescript definition file that is started with "@types/"
    if (name.startsWith('@types/')) return false;

    // Make sure the plugin exists
    const path = ctx.resolvePlugin(name, basedir);
    return exists(path);
  }).then((modules: string[]) => {
    return Object.fromEntries(modules.map(name => [name, ctx.resolvePlugin(name, basedir)]));
  });
}

function loadModules(ctx: Hexo): Promise<void[]> {
  return Promise.map([ctx.base_dir, ctx.theme_dir], basedir => loadModuleList(ctx, basedir))
    .then(([hexoModuleList, themeModuleList]) => {
      return Object.entries(Object.assign(themeModuleList, hexoModuleList));
    })
    .map(([name, path]) => {
      // Load plugins
      return ctx.loadPlugin(path as string).then(() => {
        ctx.log.debug('Plugin loaded: %s', magenta(name));
      }).catch(err => {
        ctx.log.error({err}, 'Plugin load failed: %s', magenta(name));
      });
    });
}

function loadScripts(ctx: Hexo): Promise<void[][]> {
  const baseDirLength = ctx.base_dir.length;

  return Promise.filter([
    ctx.theme_script_dir,
    ctx.script_dir
  ], scriptDir => { // Ignore the directory if it does not exist
    return scriptDir ? exists(scriptDir) : false;
  }).map(scriptDir => listDir(scriptDir).map(name => {
    const path = join(scriptDir, name);

    return ctx.loadPlugin(path).then(() => {
      ctx.log.debug('Script loaded: %s', displayPath(path, baseDirLength));
    }).catch(err => {
      ctx.log.error({err}, 'Script load failed: %s', displayPath(path, baseDirLength));
    });
  }));
}

function displayPath(path: string, baseDirLength: number): string {
  return magenta(path.substring(baseDirLength));
}


================================================
FILE: lib/hexo/load_theme_config.ts
================================================
import { join, parse, basename, extname } from 'path';
import tildify from 'tildify';
import { exists, readdir } from 'hexo-fs';
import { magenta } from 'picocolors';
import { deepMerge } from 'hexo-util';
import type Hexo from './index';
import type Promise from 'bluebird';

export = (ctx: Hexo): Promise<void> => {
  if (!ctx.env.init) return;
  if (!ctx.config.theme) return;

  let configPath = join(ctx.base_dir, `_config.${String(ctx.config.theme)}.yml`);

  return exists(configPath).then(exist => {
    return exist ? configPath : findConfigPath(configPath);
  }).then(path => {
    if (!path) return;

    configPath = path;
    return ctx.render.render({ path });
  }).then(config => {
    if (!config || typeof config !== 'object') return;

    ctx.log.debug('Second Theme Config loaded: %s', magenta(tildify(configPath)));

    // ctx.config.theme_config should have highest priority
    // If ctx.config.theme_config exists, then merge it with _config.[theme].yml
    // If ctx.config.theme_config doesn't exist, set it to _config.[theme].yml
    ctx.config.theme_config = ctx.config.theme_config
      ? deepMerge(config, ctx.config.theme_config) : config;
  });
};

function findConfigPath(path: string): Promise<string> {
  const { dir, name } = parse(path);

  return readdir(dir).then(files => {
    const item = files.find(item => basename(item, extname(item)) === name);
    if (item != null) return join(dir, item);
  });
}


================================================
FILE: lib/hexo/locals.ts
================================================
import { Cache } from 'hexo-util';

class Locals {
  public cache: InstanceType<typeof Cache>;
  public getters: Record<string, () => any>;

  constructor() {
    this.cache = new Cache();
    this.getters = {};
  }

  get(name: string): any {
    if (typeof name !== 'string') throw new TypeError('name must be a string!');

    return this.cache.apply(name, () => {
      const getter = this.getters[name];
      if (!getter) return;

      return getter();
    });
  }

  set(name: string, value: any): this {
    if (typeof name !== 'string') throw new TypeError('name must be a string!');
    if (value == null) throw new TypeError('value is required!');

    const getter = typeof value === 'function' ? value : () => value;

    this.getters[name] = getter;
    this.cache.del(name);

    return this;
  }

  remove(name: string): this {
    if (typeof name !== 'string') throw new TypeError('name must be a string!');

    this.getters[name] = null;
    this.cache.del(name);

    return this;
  }

  invalidate(): this {
    this.cache.flush();

    return this;
  }

  toObject(): Record<string, any> {
    const result = {};
    const keys = Object.keys(this.getters);

    for (let i = 0, len = keys.length; i < len; i++) {
      const key = keys[i];
      const item = this.get(key);

      if (item != null) result[key] = item;
    }

    return result;
  }
}

export = Locals;


================================================
FILE: lib/hexo/multi_config_path.ts
================================================
import { isAbsolute, resolve, join, extname } from 'path';
import { existsSync, readFileSync, writeFileSync } from 'hexo-fs';
import yml from 'js-yaml';
import { deepMerge } from 'hexo-util';
import type Hexo from './index';

export = (ctx: Hexo) => function multiConfigPath(base: string, configPaths?: string, outputDir?: string): string {
  const { log } = ctx;
  const defaultPath = join(base, '_config.yml');

  if (!configPaths) {
    log.w('No config file entered.');
    return join(base, '_config.yml');
  }

  let paths: string[];
  // determine if comma or space separated
  if (configPaths.includes(',')) {
    paths = configPaths.replace(' ', '').split(',');
  } else {
    // only one config
    let configPath = isAbsolute(configPaths) ? configPaths : resolve(base, configPaths);

    if (!existsSync(configPath)) {
      log.w(`Config file ${configPaths} not found, using default.`);
      configPath = defaultPath;
    }

    return configPath;
  }

  const numPaths = paths.length;

  // combine files
  let combinedConfig = {};
  let count = 0;
  for (let i = 0; i < numPaths; i++) {
    const configPath = isAbsolute(paths[i]) ? paths[i] : join(base, paths[i]);

    if (!existsSync(configPath)) {
      log.w(`Config file ${paths[i]} not found.`);
      continue;
    }

    // files read synchronously to ensure proper overwrite order
    const file = readFileSync(configPath);
    const ext = extname(paths[i]).toLowerCase();

    if (ext === '.yml') {
      combinedConfig = deepMerge(combinedConfig, yml.load(file));
      count++;
    } else if (ext === '.json') {
      combinedConfig = deepMerge(combinedConfig, yml.load(file, {json: true}));
      count++;
    } else {
      log.w(`Config file ${paths[i]} not supported type.`);
    }
  }

  if (count === 0) {
    log.e('No config files found. Using _config.yml.');
    return defaultPath;
  }

  log.i('Config based on', count.toString(), 'files');

  const multiconfigRoot = outputDir || base;
  const outputPath = join(multiconfigRoot, '_multiconfig.yml');

  log.d(`Writing _multiconfig.yml to ${outputPath}`);

  writeFileSync(outputPath, yml.dump(combinedConfig));

  // write file and return path
  return outputPath;
};


================================================
FILE: lib/hexo/post.ts
================================================
import assert from 'assert';
import moment from 'moment';
import Promise from 'bluebird';
import { join, extname, basename } from 'path';
import { magenta } from 'picocolors';
import { load } from 'js-yaml';
import { slugize, escapeRegExp, deepMerge} from 'hexo-util';
import { copyDir, exists, listDir, mkdirs, readFile, rmdir, unlink, writeFile } from 'hexo-fs';
import { parse as yfmParse, split as yfmSplit, stringify as yfmStringify } from 'hexo-front-matter';
import type Hexo from './index';
import type { NodeJSLikeCallback, RenderData } from '../types';

const preservedKeys = ['title', 'slug', 'path', 'layout', 'date', 'content'];

const rHexoPostRenderEscape = /<hexoPostRenderCodeBlock>([\s\S]+?)<\/hexoPostRenderCodeBlock>/g;
const rCommentEscape = /(<!--[\s\S]*?-->)/g;
const rSwigTag = /(\{\{.+?\}\})|(\{#.+?#\})|(\{%.+?%\})/s;

const rSwigPlaceHolder = /(?:<|&lt;)!--swig\uFFFC(\d+)--(?:>|&gt;)/g;
const rCodeBlockPlaceHolder = /(?:<|&lt;)!--code\uFFFC(\d+)--(?:>|&gt;)/g;
const rCommentHolder = /(?:<|&lt;)!--comment\uFFFC(\d+)--(?:>|&gt;)/g;

const STATE_PLAINTEXT = 0;
const STATE_SWIG_VAR = 1;
const STATE_SWIG_COMMENT = 2;
const STATE_SWIG_TAG = 3;
const STATE_SWIG_FULL_TAG = 4;
const STATE_PLAINTEXT_COMMENT = 5;

const isNonWhiteSpaceChar = (char: string) => char !== '\r'
  && char !== '\n'
  && char !== '\t'
  && char !== '\f'
  && char !== '\v'
  && char !== ' ';

class PostRenderEscape {
  public stored: string[];
  public length: number;

  constructor() {
    this.stored = [];
  }

  static escapeContent(cache: string[], flag: string, str: string) {
    return `<!--${flag}\uFFFC${cache.push(str) - 1}-->`;
  }

  static restoreContent(cache: string[]) {
    return (_: string, index: number) => {
      assert(cache[index]);
      const value = cache[index];
      cache[index] = null;
      return value;
    };
  }

  restoreAllSwigTags(str: string) {
    const restored = str.replace(rSwigPlaceHolder, PostRenderEscape.restoreContent(this.stored));
    return restored;
  }

  restoreCodeBlocks(str: string) {
    return str.replace(rCodeBlockPlaceHolder, PostRenderEscape.restoreContent(this.stored));
  }

  restoreComments(str: string) {
    return str.replace(rCommentHolder, PostRenderEscape.restoreContent(this.stored));
  }

  escapeComments(str: string) {
    return str.replace(rCommentEscape, (_, content) => PostRenderEscape.escapeContent(this.stored, 'comment', content));
  }

  escapeCodeBlocks(str: string) {
    return str.replace(rHexoPostRenderEscape, (_, content) => PostRenderEscape.escapeContent(this.stored, 'code', content));
  }

  /**
   * @param {string} str
   * @returns string
   */
  escapeAllSwigTags(str: string) {
    let state = STATE_PLAINTEXT;
    let buffer_start = -1;
    let plaintext_comment_start = -1;
    let plain_text_start = 0;
    let output = '';

    let swig_tag_name_begin = false;
    let swig_tag_name_end = false;
    let swig_tag_name = '';

    let swig_full_tag_start_start = -1;
    let swig_full_tag_start_end = -1;
    // current we just consider one level of string quote
    let swig_string_quote = '';

    const { length } = str;

    let idx = 0;

    // for backtracking
    const swig_start_idx = [0, 0, 0, 0, 0];

    const flushPlainText = (end: number) => {
      if (plain_text_start !== -1 && end > plain_text_start) {
        output += str.slice(plain_text_start, end);
      }
      plain_text_start = -1;
    };

    const ensurePlainTextStart = (position: number) => {
      if (plain_text_start === -1) {
        plain_text_start = position;
      }
    };

    const pushAndReset = (value: string) => {
      output += value;
      plain_text_start = -1;
    };

    while (idx < length) {
      while (idx < length) {
        const char = str[idx];
        const next_char = str[idx + 1];

        if (state === STATE_PLAINTEXT) { // From plain text to swig
          ensurePlainTextStart(idx);
          if (char === '{') {
            // check if it is a complete tag {{ }}
            if (next_char === '{') {
              flushPlainText(idx);
              state = STATE_SWIG_VAR;
              idx++;
              buffer_start = idx + 1;
              swig_start_idx[state] = idx;
            } else if (next_char === '#') {
              flushPlainText(idx);
              state = STATE_SWIG_COMMENT;
              idx++;
              buffer_start = idx + 1;
              swig_start_idx[state] = idx;
            } else if (next_char === '%') {
              flushPlainText(idx);
              state = STATE_SWIG_TAG;
              idx++;
              buffer_start = idx + 1;
              swig_full_tag_start_start = idx + 1;
              swig_full_tag_start_end = idx + 1;
              swig_tag_name = '';
              swig_tag_name_begin = false; // Mark if it is the first non white space char in the swig tag
              swig_tag_name_end = false;
              swig_start_idx[state] = idx;
            }
          }
          if (char === '<' && next_char === '!' && str[idx + 2] === '-' && str[idx + 3] === '-') {
            flushPlainText(idx);
            state = STATE_PLAINTEXT_COMMENT;
            plaintext_comment_start = idx;
            idx += 3;
          }
        } else if (state === STATE_SWIG_TAG) {
          if (char === '"' || char === '\'') {
            if (swig_string_quote === '') {
              swig_string_quote = char;
            } else if (swig_string_quote === char) {
              swig_string_quote = '';
            }
          }
          if (char === '%' && next_char === '}' && swig_string_quote === '') { // From swig back to plain text
            idx++;
            if (swig_tag_name !== '' && str.includes(`end${swig_tag_name}`)) {
              state = STATE_SWIG_FULL_TAG;
              buffer_start = idx + 1;
              // since we have already move idx to next char of '}', so here is idx -1
              swig_full_tag_start_end = idx - 1;
              swig_start_idx[state] = idx;
            } else {
              swig_tag_name = '';
              state = STATE_PLAINTEXT;
              // since we have already move idx to next char of '}', so here is idx -1
              pushAndReset(PostRenderEscape.escapeContent(this.stored, 'swig', `{%${str.slice(buffer_start, idx - 1)}%}`));
            }

          } else {
            if (isNonWhiteSpaceChar(char)) {
              if (!swig_tag_name_begin && !swig_tag_name_end) {
                swig_tag_name_begin = true;
              }

              if (swig_tag_name_begin) {
                swig_tag_name += char;
              }
            } else {
              if (swig_tag_name_begin === true) {
                swig_tag_name_begin = false;
                swig_tag_name_end = true;
              }
            }
          }
        } else if (state === STATE_SWIG_VAR) {
          if (char === '"' || char === '\'') {
            if (swig_string_quote === '') {
              swig_string_quote = char;
            } else if (swig_string_quote === char) {
              swig_string_quote = '';
            }
          }
          // {{ }
          if (char === '}' && next_char !== '}' && swig_string_quote === '') {
            // From swig back to plain text
            state = STATE_PLAINTEXT;
            pushAndReset(`{{${str.slice(buffer_start, idx)}${char}`);
          } else if (char === '}' && next_char === '}' && swig_string_quote === '') {
            pushAndReset(PostRenderEscape.escapeContent(this.stored, 'swig', `{{${str.slice(buffer_start, idx)}}}`));
            idx++;
            state = STATE_PLAINTEXT;
          }
        } else if (state === STATE_SWIG_COMMENT) { // From swig back to plain text
          if (char === '#' && next_char === '}') {
            idx++;
            state = STATE_PLAINTEXT;
            plain_text_start = -1;
          }
        } else if (state === STATE_SWIG_FULL_TAG) {
          if (char === '{' && next_char === '%') {
            let swig_full_tag_end_buffer = '';
            let swig_full_tag_found = false;

            let _idx = idx + 2;
            for (; _idx < length; _idx++) {
              const _char = str[_idx];
              const _next_char = str[_idx + 1];

              if (_char === '%' && _next_char === '}') {
                _idx++;
                swig_full_tag_found = true;
                break;
              }

              swig_full_tag_end_buffer = swig_full_tag_end_buffer + _char;
            }

            if (swig_full_tag_found && swig_full_tag_end_buffer.includes(`end${swig_tag_name}`)) {
              state = STATE_PLAINTEXT;
              pushAndReset(PostRenderEscape.escapeContent(this.stored, 'swig', `{%${str.slice(swig_full_tag_start_start, swig_full_tag_start_end)}%}${str.slice(buffer_start, idx)}{%${swig_full_tag_end_buffer}%}`));
              idx = _idx;
              swig_full_tag_end_buffer = '';
            }
          }
        } else if (state === STATE_PLAINTEXT_COMMENT) {
          if (char === '-' && next_char === '-' && str[idx + 2] === '>') {
            state = STATE_PLAINTEXT;
            const comment = str.slice(plaintext_comment_start, idx + 3);
            pushAndReset(PostRenderEscape.escapeContent(this.stored, 'comment', comment));
            idx += 2;
          }
        }
        idx++;
      }
      if (state === STATE_PLAINTEXT) {
        break;
      }
      if (state === STATE_PLAINTEXT_COMMENT) {
        // Unterminated comment, just push the rest as comment
        const comment = str.slice(plaintext_comment_start, length);
        pushAndReset(PostRenderEscape.escapeContent(this.stored, 'comment', comment));
        break;
      }
      // If the swig tag is not closed, then it is a plain text, we need to backtrack
      if (state === STATE_SWIG_FULL_TAG) {
        pushAndReset(`{%${str.slice(swig_full_tag_start_start, swig_full_tag_start_end)}%`);
      } else {
        pushAndReset('{');
      }
      idx = swig_start_idx[state];
      swig_string_quote = '';
      state = STATE_PLAINTEXT;
    }

    if (plain_text_start !== -1 && plain_text_start < length) {
      output += str.slice(plain_text_start);
    }

    return output;
  }
}

const prepareFrontMatter = (data: any, jsonMode: boolean): Record<string, string> => {
  for (const [key, item] of Object.entries(data)) {
    if (moment.isMoment(item)) {
      data[key] = item.utc().format('YYYY-MM-DD HH:mm:ss');
    } else if (moment.isDate(item)) {
      data[key] = moment.utc(item).format('YYYY-MM-DD HH:mm:ss');
    } else if (typeof item === 'string') {
      if (jsonMode || item.includes(':') || item.startsWith('#') || item.startsWith('!!')
      || item.includes('{') || item.includes('}') || item.includes('[') || item.includes(']')
      || item.includes('\'') || item.includes('"')) data[key] = `"${item.replace(/"/g, '\\"')}"`;
    }
  }

  return data;
};


const removeExtname = (str: string) => {
  return str.substring(0, str.length - extname(str).length);
};

const createAssetFolder = (path: string, assetFolder: boolean) => {
  if (!assetFolder) return Promise.resolve();

  const target = removeExtname(path);

  if (basename(target) === 'index') return Promise.resolve();

  return exists(target).then(exist => {
    if (!exist) return mkdirs(target);
  });
};

interface Result {
  path: string;
  content: string;
}

interface PostData {
  title?: string | number;
  layout?: string;
  slug?: string | number;
  path?: string;
  date?: moment.Moment;
  [prop: string]: any;
}

class Post {
  public context: Hexo;

  constructor(context: Hexo) {
    this.context = context;
  }

  create(data: PostData, callback?: NodeJSLikeCallback<any>): Promise<Result>;
  create(data: PostData, replace: boolean, callback?: NodeJSLikeCallback<any>): Promise<Result>;
  create(data: PostData, replace: boolean | (NodeJSLikeCallback<any>), callback?: NodeJSLikeCallback<any>): Promise<Result> {
    if (!callback && typeof replace === 'function') {
      callback = replace;
      replace = false;
    }

    const ctx = this.context;
    const { config } = ctx;

    data.slug = slugize((data.slug || data.title).toString(), { transform: config.filename_case });
    data.layout = (data.layout || config.default_layout).toLowerCase();
    data.date = data.date ? moment(data.date) : moment();

    return Promise.all([
      // Get the post path
      ctx.execFilter('new_post_path', data, {
        args: [replace],
        context: ctx
      }),
      this._renderScaffold(data)
    ]).spread((path: string, content: string) => {
      const result = { path, content };

      return Promise.all<void, void | string>([
        // Write content to file
        writeFile(path, content),
        // Create asset folder
        createAssetFolder(path, config.post_asset_folder)
      ]).then(() => {
        ctx.emit('new', result);
        return result;
      });
    }).asCallback(callback);
  }

  _getScaffold(layout: string) {
    const ctx = this.context;

    return ctx.scaffold.get(layout).then(result => {
      if (result != null) return result;
      return ctx.scaffold.get('normal');
    });
  }

  _renderScaffold(data: PostData) {
    const { tag } = this.context.extend;
    let splitted: ReturnType<typeof yfmSplit>;

    return this._getScaffold(data.layout).then(scaffold => {
      splitted = yfmSplit(scaffold);
      const jsonMode = splitted.separator.startsWith(';');
      const frontMatter = prepareFrontMatter({ ...data }, jsonMode);

      return tag.render(splitted.data, frontMatter);
    }).then(frontMatter => {
      const { separator } = splitted;
      const jsonMode = separator.startsWith(';');

      // Parse front-matter
      let obj = jsonMode ? JSON.parse(`{${frontMatter}}`) : load(frontMatter);

      obj = deepMerge(obj, Object.fromEntries(Object.entries(data).filter(([key, value]) => !preservedKeys.includes(key) && value != null)));

      let content = '';
      // Prepend the separator
      if (splitted.prefixSeparator) content += `${separator}\n`;

      content += yfmStringify(obj, {
        mode: jsonMode ? 'json' : ''
      });

      // Concat content
      content += splitted.content;

      if (data.content) {
        content += `\n${data.content}`;
      }

      return content;
    });
  }

  publish(data: PostData, replace?: boolean): Promise<Result>;
  publish(data: PostData, callback?: NodeJSLikeCallback<Result>): Promise<Result>;
  publish(data: PostData, replace: boolean, callback?: NodeJSLikeCallback<Result>): Promise<Result>;
  publish(data: PostData, replace?: boolean | NodeJSLikeCallback<Result>, callback?: NodeJSLikeCallback<Result>): Promise<Result> {
    if (!callback && typeof replace === 'function') {
      callback = replace;
      replace = false;
    }

    if (data.layout === 'draft') data.layout = 'post';

    const ctx = this.context;
    const { config } = ctx;
    const draftDir = join(ctx.source_dir, '_drafts');
    const slug = slugize(data.slug.toString(), { transform: config.filename_case });
    data.slug = slug;
    const regex = new RegExp(`^${escapeRegExp(slug)}(?:[^\\/\\\\]+)`);
    let src = '';
    const result: Result = {} as any;

    data.layout = (data.layout || config.default_layout).toLowerCase();

    // Find the draft
    return listDir(draftDir).then(list => {
      const item = list.find(item => regex.test(item));
      if (!item) throw new Error(`Draft "${slug}" does not exist.`);

      // Read the content
      src = join(draftDir, item);
      return readFile(src);
    }).then(content => {
      // Create post
      Object.assign(data, yfmParse(content));
      data.content = data._content;
      data._content = undefined;

      return this.create(data, replace as boolean);
    }).then(post => {
      result.path = post.path;
      result.content = post.content;
      return unlink(src);
    }).then(() => { // Remove the original draft file
      if (!config.post_asset_folder) return;

      // Copy assets
      const assetSrc = removeExtname(src);
      const assetDest = removeExtname(result.path);

      return exists(assetSrc).then(exist => {
        if (!exist) return;

        return copyDir(assetSrc, assetDest).then(() => rmdir(assetSrc));
      });
    }).thenReturn(result).asCallback(callback);
  }

  render(source: string, data: RenderData = {}, callback?: NodeJSLikeCallback<never>) {
    const ctx = this.context;
    const { config } = ctx;
    const { tag } = ctx.extend;
    const ext = data.engine || (source ? extname(source) : '');

    let promise;

    if (data.content != null) {
      promise = Promise.resolve(data.content);
    } else if (source) {
      // Read content from files
      promise = readFile(source);
    } else {
      return Promise.reject(new Error('No input file or string!')).asCallback(callback);
    }

    // Files like js and css are also processed by this function, but they do not require preprocessing like markdown
    // data.source does not exist when tag plugins call the markdown renderer
    const isPost = !data.source || ['html', 'htm'].includes(ctx.render.getOutput(data.source));

    if (!isPost) {
      return promise.then(content => {
        data.content = content;
        ctx.log.debug('Rendering file: %s', magenta(source));

        return ctx.render.render({
          text: data.content,
          path: source,
          engine: data.engine,
          toString: true
        });
      }).then(content => {
        data.content = content;
        return data;
      }).asCallback(callback);
    }

    // disable Nunjucks when the renderer specify that.
    let disableNunjucks = ext && ctx.render.renderer.get(ext) && !!ctx.render.renderer.get(ext).disableNunjucks;

    // front-matter overrides renderer's option
    if (typeof data.disableNunjucks === 'boolean') disableNunjucks = data.disableNunjucks;

    const cacheObj = new PostRenderEscape();

    return promise.then(content => {
      data.content = content;
      // Run "before_post_render" filters
      return ctx.execFilter('before_post_render', data, { context: ctx });
    }).then(() => {
      // Escape all comments to avoid conflict with Nunjucks and code block
      data.content = cacheObj.escapeCodeBlocks(data.content);
      // Escape all Nunjucks/Swig tags
      let hasSwigTag = true;
      if (disableNunjucks === false) {
        hasSwigTag = rSwigTag.test(data.content);
        if (hasSwigTag) {
          data.content = cacheObj.escapeAllSwigTags(data.content);
        }
      }

      const options: { highlight?: boolean; } = data.markdown || {};
      if (!config.syntax_highlighter) options.highlight = null;

      ctx.log.debug('Rendering post: %s', magenta(source));
      // Render with markdown or other renderer
      return ctx.render.render({
        text: data.content,
        path: source,
        engine: data.engine,
        toString: true,
        onRenderEnd(content) {
          // Replace cache data with real contents
          data.content = cacheObj.restoreAllSwigTags(content);

          // Return content after replace the placeholders
          if (disableNunjucks || !hasSwigTag) return data.content;

          // Render with Nunjucks if there are Swig tags
          return tag.render(data.content, data);
        }
      }, options);
    }).then(content => {
      data.content = cacheObj.restoreComments(content);
      data.content = cacheObj.restoreCodeBlocks(data.content);

      // Run "after_post_render" filters
      return ctx.execFilter('after_post_render', data, { context: ctx });
    }).asCallback(callback);
  }
}

export = Post;


================================================
FILE: lib/hexo/register_models.ts
================================================
import * as models from '../models';
import type Hexo from './index';

export = (ctx: Hexo): void => {
  const db = ctx.database;

  const keys = Object.keys(models);

  for (let i = 0, len = keys.length; i < len; i++) {
    const key = keys[i];
    db.model(key, models[key](ctx));
  }
};


================================================
FILE: lib/hexo/render.ts
================================================
import { extname } from 'path';
import Promise from 'bluebird';
import { readFile, readFileSync } from 'hexo-fs';
import type Hexo from './index';
import type { Renderer } from '../extend';
import type { StoreFunction, StoreFunctionData, StoreSyncFunction } from '../extend/renderer';
import { NodeJSLikeCallback } from '../types';

const getExtname = (str: string): string => {
  if (typeof str !== 'string') return '';

  const ext = extname(str);
  return ext.startsWith('.') ? ext.slice(1) : ext;
};

const toString = (result: any, options: StoreFunctionData): string => {
  if (!Object.prototype.hasOwnProperty.call(options, 'toString') || typeof result === 'string') return result;

  if (typeof options.toString === 'function') {
    return options.toString(result);
  } else if (typeof result === 'object') {
    return JSON.stringify(result);
  } else if (result.toString) {
    return result.toString();
  }

  return result;
};

class Render {
  public context: Hexo;
  public renderer: Renderer;

  constructor(ctx: Hexo) {
    this.context = ctx;
    this.renderer = ctx.extend.renderer;
  }

  isRenderable(path: string): boolean {
    return this.renderer.isRenderable(path);
  }

  isRenderableSync(path: string): boolean {
    return this.renderer.isRenderableSync(path);
  }

  getOutput(path: string): string {
    return this.renderer.getOutput(path);
  }

  getRenderer(ext: string, sync?: boolean): StoreSyncFunction | StoreFunction {
    return this.renderer.get(ext, sync);
  }

  getRendererSync(ext: string): StoreSyncFunction | StoreFunction {
    return this.getRenderer(ext, true);
  }

  render(data: StoreFunctionData, callback?: NodeJSLikeCallback<any>): Promise<any>;
  render(data: StoreFunctionData, options: any, callback?: NodeJSLikeCallback<any>): Promise<any>;
  render(data: StoreFunctionData, options?: any | NodeJSLikeCallback<any>, callback?: NodeJSLikeCallback<any>): Promise<any> {
    if (!callback && typeof options === 'function') {
      callback = options;
      options = {};
    }

    const ctx = this.context;
    let ext = '';

    let promise: Promise<string>;

    if (!data) return Promise.reject(new TypeError('No input file or string!'));

    if (data.text != null) {
      promise = Promise.resolve(data.text);
    } else if (!data.path) {
      return Promise.reject(new TypeError('No input file or string!'));
    } else {
      promise = readFile(data.path);
    }

    return promise.then(text => {
      data.text = text;
      ext = data.engine || getExtname(data.path);
      if (!ext || !this.isRenderable(ext)) return text;

      const renderer = this.getRenderer(ext);
      return Reflect.apply(renderer, ctx, [data, options]);
    }).then(result => {
      result = toString(result, data);
      if (data.onRenderEnd) {
        return data.onRenderEnd(result);
      }

      return result;
    }).then(result => {
      const output = this.getOutput(ext) || ext;
      return ctx.execFilter(`after_render:${output}`, result, {
        context: ctx,
        args: [data]
      });
    }).asCallback(callback);
  }

  renderSync(data: StoreFunctionData, options = {}): any {
    if (!data) throw new TypeError('No input file or string!');

    const ctx = this.context;

    if (data.text == null) {
      if (!data.path) throw new TypeError('No input file or string!');
      data.text = readFileSync(data.path);
    }

    if (data.text == null) throw new TypeError('No input file or string!');

    const ext = data.engine || getExtname(data.path);
    let result;

    if (ext && this.isRenderableSync(ext)) {
      const renderer = this.getRendererSync(ext);
      result = Reflect.apply(renderer, ctx, [data, options]);
    } else {
      result = data.text;
    }

    const output = this.getOutput(ext) || ext;
    result = toString(result, data);

    if (data.onRenderEnd) {
      result = data.onRenderEnd(result);
    }

    return ctx.execFilterSync(`after_render:${output}`, result, {
      context: ctx,
      args: [data]
    });
  }
}

export = Render;


================================================
FILE: lib/hexo/router.ts
================================================
import { EventEmitter } from 'events';
import Promise from 'bluebird';
import Stream from 'stream';
const { Readable } = Stream;

interface Data {
  data: any;
  modified: boolean;
}

class RouteStream extends Readable {
  public _data: any;
  public _ended: boolean;
  public modified: boolean;

  constructor(data: Data) {
    super({ objectMode: true });

    this._data = data.data;
    this._ended = false;
    this.modified = data.modified;
  }

  // Assume we only accept Buffer, plain object, or string
  _toBuffer(data: Buffer | object | string): Buffer | null {
    if (data instanceof Buffer) {
      return data;
    }
    if (typeof data === 'object') {
      data = JSON.stringify(data);
    }
    if (typeof data === 'string') {
      return Buffer.from(data); // Assume string is UTF-8 encoded string
    }
    return null;
  }

  _read(): boolean {
    const data = this._data;

    if (typeof data !== 'function') {
      const bufferData = this._toBuffer(data);
      if (bufferData) {
        this.push(bufferData);
      }
      this.push(null);
      return;
    }

    // Don't read it twice!
    if (this._ended) return false;
    this._ended = true;

    data().then(data => {
      if (data instanceof Stream && (data as Stream.Readable).readable) {
        data.on('data', d => {
          this.push(d);
        });

        data.on('end', () => {
          this.push(null);
        });

        data.on('error', err => {
          this.emit('error', err);
        });
      } else {
        const bufferData = this._toBuffer(data);
        if (bufferData) {
          this.push(bufferData);
        }
        this.push(null);
      }
    }).catch(err => {
      this.emit('error', err);
      this.push(null);
    });
  }
}

const _format = (path?: string): string => {
  path = path || '';
  if (typeof path !== 'string') throw new TypeError('path must be a string!');

  path = path
    .replace(/^\/+/, '') // Remove prefixed slashes
    .replace(/\\/g, '/') // Replaces all backslashes
    .replace(/\?.*$/, ''); // Remove query string

  // Appends `index.html` to the path with trailing slash
  if (!path || path.endsWith('/')) {
    path += 'index.html';
  }

  return path;
};

class Router extends EventEmitter {
  public routes: {
    [key: string]: Data | null;
  };

  constructor() {
    super();

    this.routes = {};
  }

  list(): string[] {
    const { routes } = this;
    return Object.keys(routes).filter(key => routes[key]);
  }

  format(path?: string): string {
    return _format(path);
  }

  get(path: string): RouteStream {
    if (typeof path !== 'string') throw new TypeError('path must be a string!');

    const data = this.routes[this.format(path)];
    if (data == null) return;

    return new RouteStream(data);
  }

  isModified(path: string): boolean {
    if (typeof path !== 'string') throw new TypeError('path must be a string!');

    const data = this.routes[this.format(path)];
    return data ? data.modified : false;
  }

  set(path: string, data: any): this {
    if (typeof path !== 'string') throw new TypeError('path must be a string!');
    if (data == null) throw new TypeError('data is required!');

    let obj: Data;

    if (typeof data === 'object' && data.data != null) {
      obj = data;
    } else {
      obj = {
        data,
        modified: true
      };
    }

    if (typeof obj.data === 'function') {
      if (obj.data.length) {
        obj.data = Promise.promisify(obj.data);
      } else {
        obj.data = Promise.method(obj.data);
      }
    }

    path = this.format(path);

    this.routes[path] = {
      data: obj.data,
      modified: obj.modified == null ? true : obj.modified
    };

    this.emit('update', path);

    return this;
  }

  remove(path: string): this {
    if (typeof path !== 'string') throw new TypeError('path must be a string!');
    path = this.format(path);

    this.routes[path] = null;
    this.emit('remove', path);

    return this;
  }
}

export = Router;


================================================
FILE: lib/hexo/scaffold.ts
================================================
import { extname, join } from 'path';
import { exists, listDir, readFile, unlink, writeFile } from 'hexo-fs';
import type Hexo from './index';
import type { NodeJSLikeCallback } from '../types';
import type Promise from 'bluebird';

class Scaffold {
  public context: Hexo;
  public scaffoldDir: string;
  public defaults: {
    normal: string
  };

  constructor(context: Hexo) {
    this.context = context;
    this.scaffoldDir = context.scaffold_dir;
    this.defaults = {
      normal: [
        '---',
        'layout: {{ layout }}',
        'title: {{ title }}',
        'date: {{ date }}',
        'tags:',
        '---'
      ].join('\n')
    };
  }

  _listDir(): Promise<{
    name: string;
    path: string;
  }[]> {
    const { scaffoldDir } = this;

    return exists(scaffoldDir).then(exist => {
      if (!exist) return [];

      return listDir(scaffoldDir, {
        ignorePattern: /^_|\/_/
      });
    }).map(item => ({
      name: item.substring(0, item.length - extname(item).length),
      path: join(scaffoldDir, item)
    }));
  }

  _getScaffold(name: string): Promise<{
    name: string;
    path: string;
  }> {
    return this._listDir().then(list => list.find(item => item.name === name));
  }

  get(name: string, callback?: NodeJSLikeCallback<any>): Promise<string> {
    return this._getScaffold(name).then(item => {
      if (item) {
        return readFile(item.path);
      }

      return this.defaults[name];
    }).asCallback(callback);
  }

  set(name: string, content: any, callback?: NodeJSLikeCallback<void>): Promise<void> {
    const { scaffoldDir } = this;

    return this._getScaffold(name).then(item => {
      let path = item ? item.path : join(scaffoldDir, name);
      if (!extname(path)) path += '.md';

      return writeFile(path, content);
    }).asCallback(callback);
  }

  remove(name: string, callback?: NodeJSLikeCallback<void>): Promise<void> {
    return this._getScaffold(name).then(item => {
      if (!item) return;

      return unlink(item.path);
    }).asCallback(callback);
  }
}

export = Scaffold;


================================================
FILE: lib/hexo/source.ts
================================================
import Box from '../box';
import type Hexo from './index';

class Source extends Box {
  constructor(ctx: Hexo) {
    super(ctx, ctx.source_dir);

    this.processors = ctx.extend.processor.list();
  }
}

export = Source;


================================================
FILE: lib/hexo/update_package.ts
================================================
import { join } from 'path';
import { writeFile, exists, readFile } from 'hexo-fs';
import type Hexo from './index';
import type Promise from 'bluebird';

export = (ctx: Hexo): Promise<void> => {
  const pkgPath = join(ctx.base_dir, 'package.json');

  return readPkg(pkgPath).then(pkg => {
    if (!pkg) return;

    ctx.env.init = true;

    if (pkg.hexo.version === ctx.version) return;

    pkg.hexo.version = ctx.version;

    ctx.log.debug('Updating package.json');
    return writeFile(pkgPath, JSON.stringify(pkg, null, '  '));
  });
};

function readPkg(path: string): Promise<any> {
  return exists(path).then(exist => {
    if (!exist) return;

    return readFile(path).then(content => {
      const pkg = JSON.parse(content);
      if (typeof pkg.hexo !== 'object') return;

      return pkg;
    });
  });
}


================================================
FILE: lib/hexo/validate_config.ts
================================================
import assert from 'assert';
import type Hexo from './index';

export = (ctx: Hexo): void => {
  const { config, log } = ctx;

  log.info('Validating config');

  // Validation for config.url && config.root
  if (typeof config.url !== 'string') {
    throw new TypeError(`Invalid config detected: "url" should be string, not ${typeof config.url}!`);
  }
  try {
    // eslint-disable-next-line no-new
    new URL(config.url);
    assert(new URL(config.url).protocol.startsWith('http'));
  } catch {
    throw new TypeError('Invalid config detected: "url" should be a valid URL!');
  }

  if (typeof config.root !== 'string') {
    throw new TypeError(`Invalid config detected: "root" should be string, not ${typeof config.root}!`);
  }
  if (config.root.trim().length <= 0) {
    throw new TypeError('Invalid config detected: "root" should not be empty!');
  }
};



================================================
FILE: lib/models/asset.ts
================================================
import warehouse from 'warehouse';
import { join } from 'path';
import type Hexo from '../hexo';
import type { AssetSchema } from '../types';

export = (ctx: Hexo) => {
  const Asset = new warehouse.Schema<AssetSchema>({
    _id: {type: String, required: true},
    path: {type: String, required: true},
    modified: {type: Boolean, default: true},
    renderable: {type: Boolean, default: true}
  });

  Asset.virtual('source').get(function() {
    return join(ctx.base_dir, this._id);
  });

  return Asset;
};


================================================
FILE: lib/models/binary_relation_index.ts
================================================
import type Hexo from '../hexo';

type BinaryRelationType<K extends PropertyKey, V extends PropertyKey> = {
  [key in K]: PropertyKey;
} & {
  [key in V]: PropertyKey;
};

class BinaryRelationIndex<K extends PropertyKey, V extends PropertyKey> {
  keyIndex: Map<PropertyKey, Set<PropertyKey>> = new Map();
  valueIndex: Map<PropertyKey, Set<PropertyKey>> = new Map();
  key: K;
  value: V;
  ctx: Hexo;
  schemaName: string;

  constructor(key: K, value: V, schemaName: string, ctx: Hexo) {
    this.key = key;
    this.value = value;
    this.schemaName = schemaName;
    this.ctx = ctx;
  }

  load() {
    this.keyIndex.clear();
    this.valueIndex.clear();
    const raw = this.ctx.model(this.schemaName).data;
    for (const _id in raw) {
      this.saveHook(raw[_id]);
    }
  }

  saveHook(data: BinaryRelationType<K, V> & { _id: PropertyKey }) {
    if (!data) return;
    const _id = data._id;
    const key = data[this.key];
    const value = data[this.value];
    if (!this.keyIndex.has(key)) {
      this.keyIndex.set(key, new Set());
    }
    this.keyIndex.get(key).add(_id);

    if (!this.valueIndex.has(value)) {
      this.valueIndex.set(value, new Set());
    }
    this.valueIndex.get(value).add(_id);
  }

  removeHook(data: BinaryRelationType<K, V> & { _id: PropertyKey }) {
    const _id = data._id;
    const key = data[this.key];
    const value = data[this.value];
    this.keyIndex.get(key)?.delete(_id);
    if (this.keyIndex.get(key)?.size === 0) {
      this.keyIndex.delete(key);
    }
    this.valueIndex.get(value)?.delete(_id);
    if (this.valueIndex.get(value)?.size === 0) {
      this.valueIndex.delete(value);
    }
  }

  findById(_id: PropertyKey) {
    const raw = this.ctx.model(this.schemaName).findById(_id, { lean: true });
    if (!raw) return;
    return { ...raw };
  }

  find(query: Partial<BinaryRelationType<K, V>>) {
    const key = query[this.key];
    const value = query[this.value];

    if (key && value) {
      const ids = this.keyIndex.get(key);
      if (!ids) return [];
      return Array.from(ids)
        .map(_id => this.findById(_id))
        .filter(record => record?.[this.value] === value);
    }

    if (key) {
      const ids = this.keyIndex.get(key);
      if (!ids) return [];
      return Array.from(ids).map(_id => this.findById(_id));
    }

    if (value) {
      const ids = this.valueIndex.get(value);
      if (!ids) return [];
      return Array.from(ids).map(_id => this.findById(_id));
    }

    return [];
  }

  findOne(query: Partial<BinaryRelationType<K, V>>) {
    return this.find(query)[0];
  }
}

export default BinaryRelationIndex;


================================================
FILE: lib/models/cache.ts
================================================
import warehouse from 'warehouse';
import Promise from 'bluebird';
import type Hexo from '../hexo';
import type fs from 'fs';
import type Document from 'warehouse/dist/document';
import type { CacheSchema } from '../types';

export = (_ctx: Hexo) => {
  const Cache = new warehouse.Schema<CacheSchema>({
    _id: {type: String, required: true},
    hash: {type: String, default: ''},
    modified: {type: Number, default: Date.now() } // UnixTime
  });

  Cache.static('compareFile', function(id: string,
    hashFn: (id: string) => Promise<string>,
    statFn: (id: string) => Promise<fs.Stats>): Promise<{ type: string }> {
    const cache = this.findById(id) as Document<CacheSchema>;

    // If cache does not exist, then it must be a new file. We have to get both
    // file hash and stats.
    if (!cache) {
      return Promise.all([hashFn(id), statFn(id)]).spread((hash: string, stats: fs.Stats) => this.insert({
        _id: id,
        hash,
        modified: stats.mtime.getTime()
      })).thenReturn({
        type: 'create'
      });
    }

    let mtime: number;

    // Get file stats
    return statFn(id).then<any>(stats => {
      mtime = stats.mtime.getTime();

      // Skip the file if the modified time is unchanged
      if (cache.modified === mtime) {
        return {
          type: 'skip'
        };
      }

      // Get file hash
      return hashFn(id);
    }).then((result: string | { type: string }) => {
      // If the result is an object, skip the following steps because it's an
      // unchanged file
      if (typeof result === 'object') return result;

      const hash = result;

      // Skip the file if the hash is unchanged
      if (cache.hash === hash) {
        return {
          type: 'skip'
        };
      }

      // Update cache info
      cache.hash = hash;
      cache.modified = mtime;

      return cache.save().thenReturn({
        type: 'update'
      });
    });
  });

  return Cache;
};


================================================
FILE: lib/models/category.ts
================================================
import warehouse from 'warehouse';
import { slugize, full_url_for } from 'hexo-util';
import type Hexo from '../hexo';
import type { CategorySchema } from '../types';

export = (ctx: Hexo) => {
  const Category = new warehouse.Schema<CategorySchema>({
    name: {type: String, required: true},
    parent: { type: warehouse.Schema.Types.CUID, ref: 'Category'}
  });

  Category.virtual('slug').get(function() {
    let name = this.name;

    if (!name) return;

    let str = '';

    if (this.parent) {
      const parent = ctx.model('Category').findById(this.parent);
      str += `${parent.slug}/`;
    }

    const map = ctx.config.category_map || {};

    name = map[name] || name;
    str += slugize(name, {transform: ctx.config.filename_case});

    return str;
  });

  Category.virtual('path').get(function() {
    let catDir = ctx.config.category_dir;
    if (catDir === '/') catDir = '';
    if (!catDir.endsWith('/')) catDir += '/';

    return `${catDir + this.slug}/`;
  });

  Category.virtual('permalink').get(function() {
    return full_url_for.call(ctx, this.path);
  });

  Category.virtual('posts').get(function() {
    const ReadOnlyPostCategory = ctx._binaryRelationIndex.post_category;

    const ids = ReadOnlyPostCategory.find({category_id: this._id}).map(item => item.post_id);

    return ctx.locals.get('posts').find({
      _id: {$in: ids}
    });
  });

  Category.virtual('length').get(function() {
    const ReadOnlyPostCategory = ctx._binaryRelationIndex.post_category;

    return ReadOnlyPostCategory.find({category_id: this._id}).length;
  });

  // Check whether a category exists
  Category.pre('save', (data: CategorySchema) => {
    const { name, parent } = data;
    if (!name) return;

    const Category = ctx.model('Category');
    const cat = Category.findOne({
      name,
      parent: parent || {$exists: false}
    }, {lean: true});

    if (cat) {
      throw new Error(`Category \`${name}\` has already existed!`);
    }
  });

  // Remove PostCategory references
  Category.pre('remove', (data: CategorySchema) => {
    const PostCategory = ctx.model('PostCategory');
    return PostCategory.remove({category_id: data._id});
  });

  return Category;
};


================================================
FILE: lib/models/data.ts
================================================
import warehouse from 'warehouse';
import type Hexo from '../hexo';
import { DataSchema } from '../types';

export = (_ctx: Hexo) => {
  const Data = new warehouse.Schema<DataSchema>({
    _id: {type: String, required: true},
    data: Object
  });

  return Data;
};


================================================
FILE: lib/models/index.ts
================================================
export { default as Asset } from './asset';
export { default as Cache } from './cache';
export { default as Category } from './category';
export { default as Data } from './data';
export { default as Page } from './page';
export { default as Post } from './post';
export { default as PostAsset } from './post_asset';
export { default as PostCategory } from './post_category';
export { default as PostTag } from './post_tag';
export { default as Tag } from './tag';


================================================
FILE: lib/models/page.ts
================================================
import warehouse from 'warehouse';
import { join } from 'path';
import Moment from './types/moment';
import moment from 'moment';
import { full_url_for } from 'hexo-util';
import type Hexo from '../hexo';
import type { PageSchema } from '../types';

export = (ctx: Hexo) => {
  const Page = new warehouse.Schema<PageSchema>({
    title: {type: String, default: ''},
    date: {
      type: Moment,
      default: moment
    },
    updated: {
      type: Moment
    },
    comments: {type: Boolean, default: true},
    layout: {type: String, default: 'page'},
    _content: {type: String, default: ''},
    source: {type: String, required: true},
    path: {type: String, required: true},
    raw: {type: String, default: ''},
    content: {type: String},
    excerpt: {type: String},
    more: {type: String}
  });

  Page.virtual('permalink').get(function() {
    return full_url_for.call(ctx, this.path);
  });

  Page.virtual('full_source').get(function() {
    return join(ctx.source_dir, this.source || '');
  });

  return Page;
};


================================================
FILE: lib/models/post.ts
================================================
import warehouse from 'warehouse';
import moment from 'moment';
import { extname, join, sep } from 'path';
import Promise from 'bluebird';
import Moment from './types/moment';
import { full_url_for, Cache } from 'hexo-util';
import type Hexo from '../hexo';
import type { CategorySchema, PostCategorySchema, PostSchema } from '../types';

function pickID(data: PostSchema | PostCategorySchema) {
  return data._id;
}

function removeEmptyTag(tags: string[]) {
  return tags.filter(tag => tag != null && tag !== '').map(tag => `${tag}`);
}

const tagsGetterCache = new Cache();

export = (ctx: Hexo) => {
  const Post = new warehouse.Schema<PostSchema>({
    id: String,
    title: {type: String, default: ''},
    date: {
      type: Moment,
      default: moment
    },
    updated: {
      type: Moment
    },
    comments: {type: Boolean, default: true},
    layout: {type: String, default: 'post'},
    _content: {type: String, default: ''},
    source: {type: String, required: true},
    slug: {type: String, required: true},
    photos: [String],
    raw: {type: String, default: ''},
    published: {type: Boolean, default: true},
    content: {type: String},
    excerpt: {type: String},
    more: {type: String}
  });

  Post.virtual('path').get(function() {
    const path = ctx.execFilterSync('post_permalink', this, {context: ctx});
    return typeof path === 'string' ? path : '';
  });

  Post.virtual('permalink').get(function() {
    return full_url_for.call(ctx, this.path);
  });

  Post.virtual('full_source').get(function() {
    return join(ctx.source_dir, this.source || '');
  });

  Post.virtual('asset_dir').get(function() {
    const src = this.full_source;
    return src.substring(0, src.length - extname(src).length) + sep;
  });

  Post.virtual('tags').get(function() {
    return tagsGetterCache.apply(this._id, () => {
      const ReadOnlyPostTag = ctx._binaryRelationIndex.post_tag;
      const Tag = ctx.model('Tag');

      const ids = ReadOnlyPostTag.find({post_id: this._id}).map(item => item.tag_id);

      return Tag.find({_id: {$in: ids}});
    });
  });

  Post.method('notPublished', function() {
    // The same condition as ctx._bindLocals
    return (!ctx.config.future && this.date.valueOf() > Date.now()) || (!ctx._showDrafts() && this.published === false);
  });

  Post.method('setTags', function(tags: string[]) {
    if (this.notPublished()) {
      // Ignore tags of draft posts
      // If the post is unpublished then the tag needs to be removed, thus the function cannot be returned early here
      tags = [];
    }
    tagsGetterCache.flush();
    tags = removeEmptyTag(tags);

    const ReadOnlyPostTag = ctx._binaryRelationIndex.post_tag;
    const PostTag = ctx.model('PostTag');
    const Tag = ctx.model('Tag');
    const id = this._id;
    const existed = ReadOnlyPostTag.find({post_id: id}).map(pickID);

    return Promise.map(tags, tag => {
      // Find the tag by name
      const data = Tag.findOne({name: tag}, {lean: true});
      if (data) return data;

      // Insert the tag if not exist
      return Tag.insert({name: tag}).catch(err => {
        // Try to find the tag again. Throw the error if not found
        const data = Tag.findOne({name: tag}, {lean: true});

        if (data) return data;
        throw err;
      });
    }).map(tag => {
      // Find the reference
      const ref = ReadOnlyPostTag.findOne({post_id: id, tag_id: tag._id});
      if (ref) return ref;

      // Insert the reference if not exist
      return PostTag.insert({
        post_id: id,
        tag_id: tag._id
      });
    }).then(tags => {
      // Remove old tags
      const deleted = existed.filter(item => !tags.map(pickID).includes(item));
      return deleted;
    }).map(tag => PostTag.removeById(tag));
  });

  Post.virtual('categories').get(function() {
    const ReadOnlyPostCategory = ctx._binaryRelationIndex.post_category;
    const Category = ctx.model('Category');

    const ids = ReadOnlyPostCategory.find({post_id: this._id}).map(item => item.category_id);

    return Category.find({_id: {$in: ids}});
  });

  Post.method('setCategories', function(cats: (string | string[])[]) {
    if (this.notPublished()) {
      cats = [];
    }
    // Remove empty categories, preserving hierarchies
    cats = cats.filter(cat => {
      return Array.isArray(cat) || (cat != null && cat !== '');
    }).map(cat => {
      return Array.isArray(cat) ? removeEmptyTag(cat) : `${cat}`;
    });

    const ReadOnlyPostCategory = ctx._binaryRelationIndex.post_category;
    const PostCategory = ctx.model('PostCategory');
    const Category = ctx.model('Category');
    const id = this._id;
    const allIds: string[] = [];
    const existed = ReadOnlyPostCategory.find({post_id: id}).map(pickID);
    const hasHierarchy = cats.filter(Array.isArray).length > 0;

    // Add a hierarchy of categories
    const addHierarchy = (catHierarchy: string | string[]) => {
      const parentIds = [];
      if (!Array.isArray(catHierarchy)) catHierarchy = [catHierarchy];
      // Don't use "Promise.map". It doesn't run in series.
      // MUST USE "Promise.each".
      return Promise.each(catHierarchy, (cat, i) => {
        // Find the category by name
        const data: CategorySchema = Category.findOne({
          name: cat,
          parent: i ? parentIds[i - 1] : {$exists: false}
        }, {lean: true});

        if (data) {
          allIds.push(data._id);
          parentIds.push(data._id);
          return data;
        }

        // Insert the category if not exist
        const obj: {name: string, parent?: string} = {name: cat};
        if (i) obj.parent = parentIds[i - 1];

        return Category.insert(obj).catch(err => {
          // Try to find the category again. Throw the error if not found
          const data: CategorySchema = Category.findOne({
            name: cat,
            parent: i ? parentIds[i - 1] : {$exists: false}
          }, {lean: true});

          if (data) return data;
          throw err;
        }).then((data: CategorySchema) => {
          allIds.push(data._id);
          parentIds.push(data._id);
          return data;
        });
      });
    };

    return (hasHierarchy ? Promise.each(cats, addHierarchy) : Promise.resolve(addHierarchy(cats as string[]))
    ).then(() => allIds).map(catId => {
      // Find the reference
      const ref: PostCategorySchema = ReadOnlyPostCategory.findOne({post_id: id, category_id: catId});
      if (ref) return ref;

      // Insert the reference if not exist
      return PostCategory.insert({
        post_id: id,
        category_id: catId
      });
    }).then((postCats: PostCategorySchema[]) => // Remove old categories
      existed.filter(item => !postCats.map(pickID).includes(item))).map(cat => PostCategory.removeById(cat));
  });

  // Remove PostTag references
  Post.pre('remove', (data: PostSchema) => {
    const PostTag = ctx.model('PostTag');
    return PostTag.remove({post_id: data._id});
  });

  // Remove PostCategory references
  Post.pre('remove', (data: PostSchema) => {
    const PostCategory = ctx.model('PostCategory');
    return PostCategory.remove({post_id: data._id});
  });

  // Remove assets
  Post.pre('remove', (data: PostSchema) => {
    const PostAsset = ctx.model('PostAsset');
    return PostAsset.remove({post: data._id});
  });

  return Post;
};


================================================
FILE: lib/models/post_asset.ts
================================================
import warehouse from 'warehouse';
import { join, posix } from 'path';
import type Hexo from '../hexo';
import type { PostAssetSchema } from '../types';

export = (ctx: Hexo) => {
  const PostAsset = new warehouse.Schema<PostAssetSchema>({
    _id: {type: String, required: true},
    slug: {type: String, required: true},
    modified: {type: Boolean, default: true},
    post: {type: warehouse.Schema.Types.CUID, ref: 'Post'},
    renderable: {type: Boolean, default: true}
  });

  PostAsset.virtual('path').get(function() {
    const Post = ctx.model('Post');
    const post = Post.findById(this.post);
    if (!post) return;

    // PostAsset.path is file path relative to `public_dir`
    // no need to urlescape, #1562
    // strip /\.html?$/ extensions on permalink, #2134
    // Use path.posix.join to avoid path.join introducing unwanted backslashes on Windows.
    return posix.join(post.path.replace(/\.html?$/, ''), this.slug);
  });

  PostAsset.virtual('source').get(function() {
    return join(ctx.base_dir, this._id);
  });

  return PostAsset;
};


================================================
FILE: lib/models/post_category.ts
================================================
import warehouse from 'warehouse';
import type Hexo from '../hexo';
import { PostCategorySchema } from '../types';

export = (ctx: Hexo) => {
  const PostCategory = new warehouse.Schema<PostCategorySchema>({
    post_id: {type: warehouse.Schema.Types.CUID, ref: 'Post'},
    category_id: {type: warehouse.Schema.Types.CUID, ref: 'Category'}
  });

  PostCategory.pre('save', data => {
    ctx._binaryRelationIndex.post_category.removeHook(data);
    return data;
  });

  PostCategory.post('save', data => {
    ctx._binaryRelationIndex.post_category.saveHook(data);
    return data;
  });

  PostCategory.pre('remove', data => {
    ctx._binaryRelationIndex.post_category.removeHook(data);
    return data;
  });

  return PostCategory;
};


================================================
FILE: lib/models/post_tag.ts
================================================
import warehouse from 'warehouse';
import type Hexo from '../hexo';
import { PostTagSchema } from '../types';

export = (ctx: Hexo) => {
  const PostTag = new warehouse.Schema<PostTagSchema>({
    post_id: {type: warehouse.Schema.Types.CUID, ref: 'Post'},
    tag_id: {type: warehouse.Schema.Types.CUID, ref: 'Tag'}
  });

  PostTag.pre('save', data => {
    ctx._binaryRelationIndex.post_tag.removeHook(data);
    return data;
  });

  PostTag.post('save', data => {
    ctx._binaryRelationIndex.post_tag.saveHook(data);
    return data;
  });

  PostTag.pre('remove', data => {
    ctx._binaryRelationIndex.post_tag.removeHook(data);
    return data;
  });

  return PostTag;
};


================================================
FILE: lib/models/tag.ts
================================================
import warehouse from 'warehouse';
import { slugize, full_url_for } from 'hexo-util';
const { hasOwnProperty: hasOwn } = Object.prototype;
import type Hexo from '../hexo';
import type { TagSchema } from '../types';

export = (ctx: Hexo) => {
  const Tag = new warehouse.Schema<TagSchema>({
    name: {type: String, required: true}
  });

  Tag.virtual('slug').get(function() {
    const map = ctx.config.tag_map || {};
    let name = this.name;
    if (!name) return;

    if (Reflect.apply(hasOwn, map, [name])) {
      name = map[name] || name;
    }

    return slugize(name, {transform: ctx.config.filename_case});
  });

  Tag.virtual('path').get(function() {
    let tagDir = ctx.config.tag_dir;
    if (!tagDir.endsWith('/')) tagDir += '/';

    return `${tagDir + this.slug}/`;
  });

  Tag.virtual('permalink').get(function() {
    return full_url_for.call(ctx, this.path);
  });

  Tag.virtual('posts').get(function() {
    const ReadOnlyPostTag = ctx._binaryRelationIndex.post_tag;

    const ids = ReadOnlyPostTag.find({tag_id: this._id}).map(item => item.post_id);

    return ctx.locals.get('posts').find({
      _id: {$in: ids}
    });
  });

  Tag.virtual('length').get(function() {
    // Note: this.posts.length is also working
    // But it's slow because `find` has to iterate over all posts
    const ReadOnlyPostTag = ctx._binaryRelationIndex.post_tag;

    return ReadOnlyPostTag.find({tag_id: this._id}).length;
  });

  // Check whether a tag exists
  Tag.pre('save', (data: TagSchema) => {
    const { name } = data;
    if (!name) return;

    const Tag = ctx.model('Tag');
    const tag = Tag.findOne({name}, {lean: true});

    if (tag) {
      throw new Error(`Tag \`${name}\` has already existed!`);
    }
  });

  // Remove PostTag references
  Tag.pre('remove', (data: TagSchema) => {
    const PostTag = ctx.model('PostTag');
    return PostTag.remove({tag_id: data._id});
  });

  return Tag;
};


================================================
FILE: lib/models/types/moment.ts
================================================
import warehouse from 'warehouse';
import { moment } from '../../plugins/helper/date';

// It'll pollute the moment module.
// declare module 'moment' {
//   export default interface Moment extends moment.Moment {
//     _d: Date;
//   // eslint-disable-next-line semi
//   }
// }

class SchemaTypeMoment extends warehouse.SchemaType<moment.Moment> {
  public options: any;

  constructor(name, options = {}) {
    super(name, options);
  }

  cast(value?, data?) {
    value = super.cast(value, data);
    if (value == null) return value;

    return toMoment(value);
  }

  validate(value, data?) {
    value = super.validate(value, data);
    if (value == null) return value;

    value = toMoment(value);

    if (!value.isValid()) {
      throw new Error('`' + value + '` is not a valid date!');
    }

    return value;
  }

  match(value, query, _data?) {
    return value ? value.valueOf() === query.valueOf() : false;
  }

  compare(a?, b?) {
    if (a) {
      if (b) return a - b;
      return 1;
    }

    if (b) return -1;
    return 0;
  }

  parse(value?) {
    if (value) return toMoment(value);
  }

  value(value?, _data?) {
    // FIXME: Same as above. Also a dirty hack.
    return value ? value._d.toISOString() : value;
  }

  q$day(value, query, _data?) {
    return value ? value.date() === query : false;
  }

  q$month(value, query, _data?) {
    return value ? value.month() === query : false;
  }

  q$year(value, query, _data?) {
    return value ? value.year() === query : false;
  }

  u$inc(value, update, _data?) {
    if (!value) return value;
    return value.add(update);
  }

  u$dec(value, update, _data?) {
    if (!value) return value;
    return value.subtract(update);
  }
}

function toMoment(value) {
  // FIXME: Something is wrong when using a moment instance. I try to get the
  // original date object and create a new moment object again.
  if (moment.isMoment(value)) return moment((value as any)._d);
  return moment(value);
}

export = SchemaTypeMoment;


================================================
FILE: lib/plugins/console/clean.ts
================================================
import Promise from 'bluebird';
import { exists, unlink, rmdir } from 'hexo-fs';
import type Hexo from '../../hexo';

function cleanConsole(this: Hexo): Promise<[void, void, any]> {
  return Promise.all([
    deleteDatabase(this),
    deletePublicDir(this),
    this.execFilter('after_clean', null, {context: this})
  ]);
}

function deleteDatabase(ctx: Hexo): Promise<void> {
  const dbPath = ctx.database.options.path;

  return exists(dbPath).then(exist => {
    if (!exist) return;

    return unlink(dbPath).then(() => {
      ctx.log.info('Deleted database.');
    });
  });
}

function deletePublicDir(ctx: Hexo): Promise<void> {
  const publicDir = ctx.public_dir;

  return exists(publicDir).then(exist => {
    if (!exist) return;

    return rmdir(publicDir).then(() => {
      ctx.log.info('Deleted public folder.');
    });
  });
}

export = cleanConsole;


================================================
FILE: lib/plugins/console/config.ts
================================================
import yaml from 'js-yaml';
import { exists, writeFile } from 'hexo-fs';
import { extname } from 'path';
import Promise from 'bluebird';
import type Hexo from '../../hexo';

interface ConfigArgs {
  _: string[]
  [key: string]: any
}

function configConsole(this: Hexo, args: ConfigArgs): Promise<void> {
  const key = args._[0];
  let value = args._[1];

  if (!key) {
    console.log(this.config);
    return Promise.resolve();
  }

  if (!value) {
    value = getProperty(this.config, key);
    if (value) console.log(value);
    return Promise.resolve();
  }

  const configPath = this.config_path;
  const ext = extname(configPath);

  return exists(configPath).then(exist => {
    if (!exist) return {};
    return this.render.render({path: configPath});
  }).then(config => {
    if (!config) config = {};

    setProperty(config, key, castValue(value));

    const result = ext === '.json' ? JSON.stringify(config) : yaml.dump(config);

    return writeFile(configPath, result);
  });
}

function getProperty(obj: object, key: string): any {
  const split = key.split('.');
  let result = obj[split[0]];

  for (let i = 1, len = split.length; i < len; i++) {
    result = result[split[i]];
  }

  return result;
}

function setProperty(obj: object, key: string, value: any): void {
  const split = key.split('.');
  let cursor = obj;
  const lastKey = split.pop();

  for (let i = 0, len = split.length; i < len; i++) {
    const name = split[i];
    cursor[name] = cursor[name] || {};
    cursor = cursor[name];
  }

  cursor[lastKey] = value;
}

function castValue(value: string): any {
  switch (value) {
    case 'true':
      return true;

    case 'false':
      return false;

    case 'null':
      return null;

    case 'undefined':
      return undefined;
  }

  const num = Number(value);
  if (!isNaN(num)) return num;

  return value;
}

export = configConsole;


================================================
FILE: lib/plugins/console/deploy.ts
================================================
import { exists } from 'hexo-fs';
import { underline, magenta } from 'picocolors';
import type Hexo from '../../hexo';
import type Promise from 'bluebird';

interface DeployArgs {
  _?: string[]
  g?: boolean
  generate?: boolean
  [key: string]: any
}

function deployConsole(this: Hexo, args: DeployArgs): Promise<any> {
  let config = this.config.deploy;
  const deployers = this.extend.deployer.list();

  if (!config) {
    let help = '';

    help += 'You should configure deployment settings in _config.yml first!\n\n';
    help += 'Available deployer plugins:\n';
    help += `  ${Object.keys(deployers).join(', ')}\n\n`;
    help += `For more help, you can check the online docs: ${underline('https://hexo.io/')}`;

    console.log(help);
    return;
  }

  let promise: Promise<void>;

  if (args.g || args.generate) {
    promise = this.call('generate', args);
  } else {
    promise = exists(this.public_dir).then(exist => {
      if (!exist) return this.call('generate', args);
    });
  }

  return promise.then(() => {
    this.emit('deployBefore');

    if (!Array.isArray(config)) config = [config];
    return config;
  }).each(item => {
    if (!item.type) return;

    const { type } = item;

    if (!deployers[type]) {
      this.log.error('Deployer not found: %s', magenta(type));
      return;
    }

    this.log.info('Deploying: %s', magenta(type));

    return (Reflect.apply(deployers[type], this, [{ ...item, ...args }]) as any).then(() => {
      this.log.info('Deploy done: %s', magenta(type));
    });
  }).then(() => {
    this.emit('deployAfter');
  });
}

export = deployConsole;


================================================
FILE: lib/plugins/console/generate.ts
================================================
import { exists, writeFile, unlink, stat, mkdirs } from 'hexo-fs';
import { join } from 'path';
import Promise from 'bluebird';
import prettyHrtime from 'pretty-hrtime';
import { cyan, magenta } from 'picocolors';
import tildify from 'tildify';
import { PassThrough, type Readable } from 'stream';
import { createSha1Hash } from 'hexo-util';
import type Hexo from '../../hexo';
import type Router from '../../hexo/router';

interface GenerateArgs {
  f?: boolean
  force?: boolean
  b?: boolean
  bail?: boolean
  c?: string
  concurrency?: string
  w?: boolean
  watch?: boolean
  d?: boolean
  deploy?: boolean
  [key: string]: any
}

class Generator {
  public context: Hexo;
  public force: boolean;
  public bail: boolean;
  public concurrency: string;
  public watch: boolean;
  public deploy: boolean;
  public generatingFiles: Set<string>;
  public start: [number, number];
  public args: GenerateArgs;

  constructor(ctx: Hexo, args: GenerateArgs) {
    this.context = ctx;
    this.force = args.f || args.force;
    this.bail = args.b || args.bail;
    this.concurrency = args.c || args.concurrency;
    this.watch = args.w || args.watch;
    this.deploy = args.d || args.deploy;
    this.generatingFiles = new Set();
    this.start = process.hrtime();
    this.args = args;
  }
  generateFile(path: string): Promise<void | boolean> {
    const publicDir = this.context.public_dir;
    const { generatingFiles } = this;
    const { route } = this.context;
    // Skip if the file is generating
    if (generatingFiles.has(path)) return Promise.resolve();

    // Lock the file
    generatingFiles.add(path);

    let promise: Promise<boolean>;

    if (this.force) {
      promise = this.writeFile(path, true);
    } else {
      const dest = join(publicDir, path);
      promise = exists(dest).then(exist => {
        if (!exist) return this.writeFile(path, true);
        if (route.isModified(path)) return this.writeFile(path);
      });
    }

    return promise.finally(() => {
      // Unlock the file
      generatingFiles.delete(path);
    });
  }
  writeFile(path: string, force?: boolean): Promise<boolean> {
    const { route, log } = this.context;
    const publicDir = this.context.public_dir;
    const Cache = this.context.model('Cache');
    const dataStream = this.wrapDataStream(route.get(path));
    const buffers = [];
    const hasher = createSha1Hash();

    const finishedPromise = new Promise<void>((resolve, reject) => {
      dataStream.once('error', reject);
      dataStream.once('end', resolve);
    });

    // Get data => Cache data => Calculate hash
    dataStream.on('data', chunk => {
      buffers.push(chunk);
      hasher.update(chunk);
    });

    return finishedPromise.then(() => {
      const dest = join(publicDir, path);
      const cacheId = `public/${path}`;
      const cache = Cache.findById(cacheId);
      const hash = hasher.digest('hex');

      // Skip generating if hash is unchanged
      if (!force && cache && cache.hash === hash) {
        return;
      }

      // Save new hash to cache
      return Cache.save({
        _id: cacheId,
        hash
      }).then(() => // Write cache data to public folder
        writeFile(dest, Buffer.concat(buffers))).then(() => {
        log.info('Generated: %s', magenta(path));
        return true;
      });
    });
  }
  deleteFile(path: string): Promise<void> {
    const { log } = this.context;
    const publicDir = this.context.public_dir;
    const dest = join(publicDir, path);

    return unlink(dest).then(() => {
      log.info('Deleted: %s', magenta(path));
    }, err => {
      // Skip ENOENT errors (file was deleted)
      if (err && err.code === 'ENOENT') return;
      throw err;
    });
  }
  wrapDataStream(dataStream: ReturnType<Router['get']>): Readable {
    const { log } = this.context;
    // Pass original stream with all data and errors
    if (this.bail) {
      return dataStream;
    }

    // Pass all data, but don't populate errors
    dataStream.on('error', err => {
      log.error(err);
    });

    return dataStream.pipe(new PassThrough());
  }
  firstGenerate(): Promise<void> {
    const { concurrency } = this;
    const { route, log } = this.context;
    const publicDir = this.context.public_dir;
    const Cache = this.context.model('Cache');

    // Show the loading time
    const interval = prettyHrtime(process.hrtime(this.start));
    log.info('Files loaded in %s', cyan(interval));

    // Reset the timer for later usage
    this.start = process.hrtime();


    // Check the public folder
    return stat(publicDir).then(stats => {
      if (!stats.isDirectory()) {
        throw new Error(`${magenta(tildify(publicDir))} is not a directory`);
      }
    }).catch(err => {
      // Create public folder if not exists
      if (err && err.code === 'ENOENT') {
        return mkdirs(publicDir);
      }

      throw err;
    }).then(() => {
      const task = (fn, path) => () => fn.call(this, path);
      const doTask = fn => fn();
      const routeList = route.list();
      const publicFiles = Cache.filter(item => item._id.startsWith('public/')).map(item => item._id.substring(7));
      const tasks = publicFiles.filter(path => !routeList.includes(path))
        // Clean files
        .map(path => task(this.deleteFile, path))
        // Generate files
        .concat(routeList.map(path => task(this.generateFile, path)));

      return Promise.all(Promise.map(tasks, doTask, { concurrency: parseFloat(concurrency || 'Infinity') }));
    }).then(result => {
      const interval = prettyHrtime(process.hrtime(this.start));
      const count = result.filter(Boolean).length;

      log.info('%d files generated in %s', count.toString(), cyan(interval));
    });
  }
  execWatch(): Promise<void> {
    const { route, log } = this.context;
    return this.context.watch().then(() => this.firstGenerate()).then(() => {
      log.info('Hexo is watching for file changes. Press Ctrl+C to exit.');

      // Watch changes of the route
      route.on('update', path => {
        const modified = route.isModified(path);
        if (!modified) return;

        this.generateFile(path);
      }).on('remove', path => {
        this.deleteFile(path);
      });
    });
  }
  execDeploy() {
    return this.context.call('deploy', this.args);
  }
}

function generateConsole(this: Hexo, args: GenerateArgs = {}): Promise<any> {
  const generator = new Generator(this, args);

  if (generator.watch) {
    return generator.execWatch();
  }

  return this.load().then(() => generator.firstGenerate()).then(() => {
    if (generator.deploy) {
      return generator.execDeploy();
    }
  });
}

export = generateConsole;


================================================
FILE: lib/plugins/console/index.ts
================================================
import type Hexo from '../../hexo';

export = function(ctx: Hexo) {
  const { console } = ctx.extend;

  console.register('clean', 'Remove generated files and cache.', require('./clean'));

  console.register('config', 'Get or set configurations.', {
    usage: '[name] [value]',
    arguments: [
      {name: 'name', desc: 'Setting name. Leave it blank if you want to show all configurations.'},
      {name: 'value', desc: 'New value of a setting. Leave it blank if you just want to show a single configuration.'}
    ]
  }, require('./config'));

  console.register('deploy', 'Deploy your website.', {
    options: [
      {name: '--setup', desc: 'Setup without deployment'},
      {name: '-g, --generate', desc: 'Generate before deployment'}
    ]
  }, require('./deploy'));

  console.register('generate', 'Generate static files.', {
    options: [
      {name: '-d, --deploy', desc: 'Deploy after generated'},
      {name: '-f, --force', desc: 'Force regenerate'},
      {name: '-w, --watch', desc: 'Watch file changes'},
      {name: '-b, --bail', desc: 'Raise an error if any unhandled exception is thrown during generation'},
      {name: '-c, --concurrency', desc: 'Maximum number of files to be generated in parallel. Default is infinity'}
    ]
  }, require('./generate'));

  console.register('list', 'List the information of the site', {
    desc: 'List the information of the site.',
    usage: '<type>',
    arguments: [
      {name: 'type', desc: 'Available types: page, post, route, tag, category'}
    ]
  }, require('./list'));

  console.register('migrate', 'Migrate your site from other system to Hexo.', {
    init: true,
    usage: '<type>',
    arguments: [
      {name: 'type', desc: 'Migrator type.'}
    ]
  }, require('./migrate'));

  console.register('new', 'Create a new post.', {
    usage: '[layout] <title>',
    arguments: [
      {name: 'layout', desc: 'Post layout. Use post, page, draft or whatever you want.'},
      {name: 'title', desc: 'Post title. Wrap it with quotations to escape.'}
    ],
    options: [
      {name: '-r, --replace', desc: 'Replace the current post if existed.'},
      {name: '-s, --slug', desc: 'Post slug. Customize the URL of the post.'},
      {name: '-p, --path', desc: 'Post path. Customize the path of the post.'}
    ]
  }, require('./new'));

  console.register('publish', 'Moves a draft post from _drafts to _posts folder.', {
    usage: '[layout] <filename>',
    arguments: [
      {name: 'layout', desc: 'Post layout. Use post, page, draft or whatever you want.'},
      {name: 'filename', desc: 'Draft filename. "hello-world" for example.'}
    ]
  }, require('./publish'));

  console.register('render', 'Render files with renderer plugins.', {
    init: true,
    desc: 'Render files with renderer plugins (e.g. Markdown) and save them at the specified path.',
    usage: '<file1> [file2] ...',
    options: [
      {name: '--output', desc: 'Output destination. Result will be printed in the terminal if the output destination is not set.'},
      {name: '--engine', desc: 'Specify render engine'},
      {name: '--pretty', desc: 'Prettify JSON output'}
    ]
  }, require('./render'));
}


================================================
FILE: lib/plugins/console/list/category.ts
================================================
import { underline } from 'picocolors';
import table from 'fast-text-table';
import { stringLength } from './common';
import type Hexo from '../../../hexo';
import type { CategorySchema } from '../../../types';
import type Model from 'warehouse/dist/model';
import type Document from 'warehouse/dist/document';

function listCategory(this: Hexo): void {
  const categories: Model<CategorySchema> = this.model('Category');

  const data = categories.sort({name: 1}).map((cate: Document<CategorySchema> & CategorySchema) => [cate.name, String(cate.length)]);

  // Table header
  const header = ['Name', 'Posts'].map(str => underline(str));

  data.unshift(header);

  const t = table(data, {
    align: ['l', 'r'],
    stringLength
  });

  console.log(t);
  if (data.length === 1) console.log('No categories.');
}

export = listCategory;


================================================
FILE: lib/plugins/console/list/common.ts
================================================
import strip from 'strip-ansi';

export function stringLength(str: string): number {
  str = strip(str);

  const len = str.length;
  let result = len;

  // Detect double-byte characters
  for (let i = 0; i < len; i++) {
    if (str.charCodeAt(i) > 255) {
      result++;
    }
  }

  return result;
}


================================================
FILE: lib/plugins/console/list/index.ts
================================================
import abbrev from 'abbrev';
import page from './page';
import post from './post';
import route from './route';
import tag from './tag';
import category from './category';
import type Hexo from '../../../hexo';
import type Promise from 'bluebird';

interface ListArgs {
  _: string[]
}

const store = {
  page, post, route, tag, category
};

const alias = abbrev(Object.keys(store));

function listConsole(this: Hexo, args: ListArgs): Promise<void> {
  const type = args._.shift();

  // Display help message if user didn't input any arguments
  if (!type || !alias[type]) {
    return this.call('help', {_: ['list']});
  }

  return this.load().then(() => Reflect.apply(store[alias[type]], this, [args]));
}

export = listConsole;


================================================
FILE: lib/plugins/console/list/page.ts
================================================
import { magenta, underline, gray } from 'picocolors';
import table from 'fast-text-table';
import { stringLength } from './common';
import type Hexo from '../../../hexo';
import type { PageSchema } from '../../../types';
import type Model from 'warehouse/dist/model';
import type Document from 'warehouse/dist/document';

function listPage(this: Hexo): void {
  const Page: Model<PageSchema> = this.model('Page');

  const data = Page.sort({date: 1}).map((page: Document<PageSchema> & PageSchema) => {
    const date = page.date.format('YYYY-MM-DD');
    return [gray(date), page.title, magenta(page.source)];
  });

  // Table header
  const header = ['Date', 'Title', 'Path'].map(str => underline(str));

  data.unshift(header);

  const t = table(data, {
    stringLength
  });

  console.log(t);
  if (data.length === 1) console.log('No pages.');
}

export = listPage;


================================================
FILE: lib/plugins/console/list/post.ts
================================================
import { gray, magenta, underline } from 'picocolors';
import table from 'fast-text-table';
import { stringLength } from './common';
import type Hexo from '../../../hexo';
import type { PostSchema } from '../../../types';
import type Model from 'warehouse/dist/model';
import type Document from 'warehouse/dist/document';

function mapName(item: any): string {
  return item.name;
}

function listPost(this: Hexo): void {
  const Post: Model<PostSchema> = this.model('Post');

  const data = Post.sort({published: -1, date: 1}).map((post: Document<PostSchema> & PostSchema) => {
    const date = post.published ? post.date.format('YYYY-MM-DD') : 'Draft';
    const tags = post.tags.map(mapName);
    const categories = post.categories.map(mapName);

    return [
      gray(date),
      post.title,
      magenta(post.source),
      categories.join(', '),
      tags.join(', ')
    ];
  });

  // Table header
  const header = ['Date', 'Title', 'Path', 'Category', 'Tags'].map(str => underline(str));

  data.unshift(header);

  const t = table(data, {
    stringLength
  });

  console.log(t);
  if (data.length === 1) console.log('No posts.');
}

export = listPost;


================================================
FILE: lib/plugins/console/list/route.ts
================================================
import archy from 'fast-archy';
import type Hexo from '../../../hexo';

function listRoute(this: Hexo): void {
  const routes = this.route.list().sort();
  const tree = buildTree(routes);
  const nodes = buildNodes(tree);

  const s = archy({
    label: `Total: ${routes.length}`,
    nodes
  });

  console.log(s);
}

function buildTree(routes: string[]) {
  const obj: Record<string, any> = {};
  let cursor: typeof obj;

  for (let i = 0, len = routes.length; i < len; i++) {
    const item = routes[i].split('/');
    cursor = obj;

    for (let j = 0, lenj = item.length; j < lenj; j++) {
      const seg = item[j];
      cursor[seg] = cursor[seg] || {};
      cursor = cursor[seg];
    }
  }

  return obj;
}

function buildNodes(tree: Record<string, any>) {
  const nodes = [];

  for (const [key, item] of Object.entries(tree)) {
    if (Object.keys(item).length) {
      nodes.push({
        label: key,
        nodes: buildNodes(item)
      });
    } else {
      nodes.push(key);
    }
  }

  return nodes;
}

export = listRoute;


================================================
FILE: lib/plugins/console/list/tag.ts
================================================
import { magenta, underline } from 'picocolors';
import table from 'fast-text-table';
import { stringLength } from './common';
import type Hexo from '../../../hexo';
import type { TagSchema } from '../../../types';
import type Model from 'warehouse/dist/model';
import type Document from 'warehouse/dist/document';

function listTag(this: Hexo): void {
  const Tag: Model<TagSchema> = this.model('Tag');

  const data = Tag.sort({name: 1}).map((tag: Document<TagSchema> & TagSchema) => [tag.name, String(tag.length), magenta(tag.path)]);

  // Table header
  const header = ['Name', 'Posts', 'Path'].map(str => underline(str));

  data.unshift(header);

  const t = table(data, {
    align: ['l', 'r', 'l'],
    stringLength
  });

  console.log(t);
  if (data.length === 1) console.log('No tags.');
}

export = listTag;


================================================
FILE: lib/plugins/console/migrate.ts
================================================
import { underline, magenta } from 'picocolors';
import type Hexo from '../../hexo';

interface MigrateArgs {
  _: string[]
  [key: string]: any
}

function migrateConsole(this: Hexo, args: MigrateArgs): Promise<any> {
  // Display help message if user didn't input any arguments
  if (!args._.length) {
    return this.call('help', {_: ['migrate']});
  }

  const type = args._.shift();
  const migrators = this.extend.migrator.list();

  if (!migrators[type]) {
    let help = '';

    help += `${magenta(type)} migrator plugin is not installed.\n\n`;
    help += 'Installed migrator plugins:\n';
    help += `  ${Object.keys(migrators).join(', ')}\n\n`;
    help += `For more help, you can check the online docs: ${underline('https://hexo.io/')}`;

    console.log(help);
    return;
  }

  return Reflect.apply(migrators[type], this, [args]);
}

export = migrateConsole;


================================================
FILE: lib/plugins/console/new.ts
================================================
import tildify from 'tildify';
import { magenta } from 'picocolors';
import { basename } from 'path';
import Hexo from '../../hexo';
import type Promise from 'bluebird';

const reservedKeys = {
  _: true,
  title: true,
  layout: true,
  slug: true,
  s: true,
  path: true,
  p: true,
  replace: true,
  r: true,
  // Global options
  config: true,
  debug: true,
  safe: true,
  silent: true
};

interface NewArgs {
  _?: string[]
  p?: string
  path?: string
  s?: string
  slug?: string
  r?: boolean
  replace?: boolean
  [key: string]: any
}

function newConsole(this: Hexo, args: NewArgs): Promise<void> {
  const path = args.p || args.path;
  let title: string;
  if (args._.length) {
    title = args._.pop();
  } else if (path) {
    // Default title
    title = basename(path);
  } else {
    // Display help message if user didn't input any arguments
    return this.call('help', { _: ['new'] });
  }

  const data = {
    title,
    layout: args._.length ? args._[0] : this.config.default_layout,
    slug: args.s || args.slug,
    path
  };

  const keys = Object.keys(args);

  for (let i = 0, len = keys.length; i < len; i++) {
    const key = keys[i];
    if (!reservedKeys[key]) data[key] = args[key];
  }

  return this.post.create(data, args.r || args.replace).then(post => {
    this.log.info('Created: %s', magenta(tildify(post.path)));
  });
}

export = newConsole;


================================================
FILE: lib/plugins/console/publish.ts
================================================
import tildify from 'tildify';
import { magenta } from 'picocolors';
import type Hexo from '../../hexo';
import type Promise from 'bluebird';

interface PublishArgs {
  _: string[]
  r?: boolean
  replace?: boolean
  [key: string]: any
}

function publishConsole(this: Hexo, args: PublishArgs): Promise<void> {
  // Display help message if user didn't input any arguments
  if (!args._.length) {
    return this.call('help', {_: ['publish']});
  }

  return this.post.publish({
    slug: args._.pop(),
    layout: args._.length ? args._[0] : this.config.default_layout
  }, args.r || args.replace).then(post => {
    this.log.info('Published: %s', magenta(tildify(post.path)));
  });
}

export = publishConsole;


================================================
FILE: lib/plugins/console/render.ts
================================================
import { resolve } from 'path';
import tildify from 'tildify';
import prettyHrtime from 'pretty-hrtime';
import { writeFile } from 'hexo-fs';
import { cyan, magenta } from 'picocolors';
import type Hexo from '../../hexo';
import type Promise from 'bluebird';

interface RenderArgs {
  _: string[]
  o?: string
  output?: string
  pretty?: boolean
  engine?: string
  [key: string]: any
}

function renderConsole(this: Hexo, args: RenderArgs): Promise<void> {
  // Display help message if user didn't input any arguments
  if (!args._.length) {
    return this.call('help', {_: 'render'});
  }

  const baseDir = this.base_dir;
  const src = resolve(baseDir, args._[0]);
  const output = args.o || args.output;
  const start = process.hrtime();
  const { log } = this;

  return this.render.render({
    path: src,
    engine: args.engine
  }).then(result => {
    if (typeof result === 'object') {
      if (args.pretty) {
        result = JSON.stringify(result, null, '  ');
      } else {
        result = JSON.stringify(result);
      }
    }

    if (!output) return console.log(result);

    const dest = resolve(baseDir, output);
    const interval = prettyHrtime(process.hrtime(start));

    log.info('Rendered in %s: %s -> %s', cyan(interval), magenta(tildify(src)), magenta(tildify(dest)));
    return writeFile(dest, result);
  });
}

export = renderConsole;


================================================
FILE: lib/plugins/filter/after_post_render/excerpt.ts
================================================
import type { RenderData } from '../../../types';

const rExcerpt = /<!-- ?more ?-->/i;

function excerptFilter(data: RenderData): void {
  const { content } = data;

  if (typeof data.excerpt !== 'undefined') {
    data.more = content;
  } else if (rExcerpt.test(content)) {
    data.content = content.replace(rExcerpt, (match, index) => {
      data.excerpt = content.substring(0, index).trim();
      data.more = content.substring(index + match.length).trim();

      return '<span id="more"></span>';
    });
  } else {
    data.excerpt = '';
    data.more = content;
  }
}

export = excerptFilter;


================================================
FILE: lib/plugins/filter/after_post_render/external_link.ts
================================================
import { isExternalLink } from 'hexo-util';
import type Hexo from '../../../hexo';
import type { RenderData } from '../../../types';

let EXTERNAL_LINK_POST_ENABLED = true;
const rATag = /<a(?:\s+?|\s+?[^<>]+?\s+?)href=["']((?:https?:|\/\/)[^<>"']+)["'][^<>]*>/gi;
const rTargetAttr = /target=/i;
const rRelAttr = /rel=/i;
const rRelStrAttr = /rel=["']([^<>"']*)["']/i;

function externalLinkFilter(this: Hexo, data: RenderData): void {
  if (!EXTERNAL_LINK_POST_ENABLED) return;

  const { external_link, url } = this.config;

  if (!external_link.enable || external_link.field !== 'post') {
    EXTERNAL_LINK_POST_ENABLED = false;
    return;
  }

  data.content = data.content.replace(rATag, (str, href) => {
    if (!isExternalLink(href, url, external_link.exclude as any) || rTargetAttr.test(str)) return str;

    if (rRelAttr.test(str)) {
      str = str.replace(rRelStrAttr, (relStr, rel) => {
        return rel.includes('noopener') ? relStr : `rel="${rel} noopener"`;
      });
      return str.replace('href=', 'target="_blank" href=');
    }

    return str.replace('href=', 'target="_blank" rel="noopener" href=');
  });
}

export = externalLinkFilter;


================================================
FILE: lib/plugins/filter/after_post_render/index.ts
================================================
import type Hexo from '../../../hexo';

export = (ctx: Hexo) => {
  const { filter } = ctx.extend;

  filter.register('after_post_render', require('./external_link'));
  filter.register('after_post_render', require('./excerpt'));
};


================================================
FILE: lib/plugins/filter/after_render/external_link.ts
================================================
import { isExternalLink } from 'hexo-util';
import type Hexo from '../../../hexo';

let EXTERNAL_LINK_SITE_ENABLED = true;
const rATag = /<a(?:\s+?|\s+?[^<>]+?\s+?)href=["']((?:https?:|\/\/)[^<>"']+)["'][^<>]*>/gi;
const rTargetAttr = /target=/i;
const rRelAttr = /rel=/i;
const rRelStrAttr = /rel=["']([^<>"']*)["']/i;

const addNoopener = (relStr: string, rel: string) => {
  return rel.includes('noopener') ? relStr : `rel="${rel} noopener"`;
};

function externalLinkFilter(this: Hexo, data: string): string {
  if (!EXTERNAL_LINK_SITE_ENABLED) return;

  const { external_link, url } = this.config;

  if (!external_link.enable || external_link.field !== 'site') {
    EXTERNAL_LINK_SITE_ENABLED = false;
    return;
  }

  let result = '';
  let lastIndex = 0;
  let match;

  while ((match = rATag.exec(data)) !== null) {
    result += data.slice(lastIndex, match.index);

    const str = match[0];
    const href = match[1];

    if (!isExternalLink(href, url, external_link.exclude as any) || rTargetAttr.test(str)) {
      result += str;
    } else {
      if (rRelAttr.test(str)) {
        result += str.replace(rRelStrAttr, addNoopener).replace('href=', 'target="_blank" href=');
      } else {
        result += str.replace('href=', 'target="_blank" rel="noopener" href=');
      }
    }
    lastIndex = rATag.lastIndex;
  }
  result += data.slice(lastIndex);

  return result;
}

export = externalLinkFilter;


================================================
FILE: lib/plugins/filter/after_render/index.ts
================================================
import type Hexo from '../../../hexo';

export = (ctx: Hexo) => {
  const { filter } = ctx.extend;

  filter.register('after_render:html', require('./external_link'));
  filter.register('after_render:html', require('./meta_generator'));
};


================================================
FILE: lib/plugins/filter/after_render/meta_generator.ts
================================================
import type Hexo from '../../../hexo';

let NEED_INJECT = true;
let HAS_CHECKED = false;
let META_GENERATOR_TAG;

function hexoMetaGeneratorInject(this: Hexo, data: string): string {
  if (!NEED_INJECT) return;

  if (!HAS_CHECKED) {
    HAS_CHECKED = true;
    if (!this.config.meta_generator
    || data.match(/<meta\s+(?:[^<>/]+\s)?name=['"]generator['"]/i)) {
      NEED_INJECT = false;
      return;
    }
  }

  META_GENERATOR_TAG = META_GENERATOR_TAG || `<meta name="generator" content="Hexo ${this.version}">`;

  return data.replace('</head>', `${META_GENERATOR_TAG}</head>`);
}

export = hexoMetaGeneratorInject;


================================================
FILE: lib/plugins/filter/before_exit/index.ts
================================================
import type Hexo from '../../../hexo';

export = (ctx: Hexo) => {
  const { filter } = ctx.extend;

  filter.register('before_exit', require('./save_database'));
};


================================================
FILE: lib/plugins/filter/before_exit/save_database.ts
================================================
import type Hexo from '../../../hexo';

function saveDatabaseFilter(this: Hexo): Promise<void> {
  if (!this.env.init || !this._dbLoaded) return;

  return this.database.save().then(() => {
    this.log.debug('Database saved');
  });
}

export = saveDatabaseFilter;


================================================
FILE: lib/plugins/filter/before_generate/index.ts
================================================
import type Hexo from '../../../hexo';

export = (ctx: Hexo) => {
  const { filter } = ctx.extend;

  filter.register('before_generate', require('./render_post'));
};


================================================
FILE: lib/plugins/filter/before_generate/render_post.ts
================================================
import Promise from 'bluebird';
import type Hexo from '../../../hexo';
import type Model from 'warehou
Download .txt
gitextract_47fk84wq/

├── .editorconfig
├── .github/
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature-request-improvement.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── benchmark.yml
│       ├── commenter.yml
│       ├── dependencies-review.yml
│       ├── linter.yml
│       └── tester.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .lintstagedrc.json
├── .mocharc.yml
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── bin/
│   └── hexo
├── eslint.config.js
├── lib/
│   ├── box/
│   │   ├── file.ts
│   │   └── index.ts
│   ├── extend/
│   │   ├── console.ts
│   │   ├── deployer.ts
│   │   ├── filter.ts
│   │   ├── generator.ts
│   │   ├── helper.ts
│   │   ├── index.ts
│   │   ├── injector.ts
│   │   ├── migrator.ts
│   │   ├── processor.ts
│   │   ├── renderer.ts
│   │   ├── syntax_highlight.ts
│   │   └── tag.ts
│   ├── hexo/
│   │   ├── default_config.ts
│   │   ├── index.ts
│   │   ├── load_config.ts
│   │   ├── load_database.ts
│   │   ├── load_plugins.ts
│   │   ├── load_theme_config.ts
│   │   ├── locals.ts
│   │   ├── multi_config_path.ts
│   │   ├── post.ts
│   │   ├── register_models.ts
│   │   ├── render.ts
│   │   ├── router.ts
│   │   ├── scaffold.ts
│   │   ├── source.ts
│   │   ├── update_package.ts
│   │   └── validate_config.ts
│   ├── models/
│   │   ├── asset.ts
│   │   ├── binary_relation_index.ts
│   │   ├── cache.ts
│   │   ├── category.ts
│   │   ├── data.ts
│   │   ├── index.ts
│   │   ├── page.ts
│   │   ├── post.ts
│   │   ├── post_asset.ts
│   │   ├── post_category.ts
│   │   ├── post_tag.ts
│   │   ├── tag.ts
│   │   └── types/
│   │       └── moment.ts
│   ├── plugins/
│   │   ├── console/
│   │   │   ├── clean.ts
│   │   │   ├── config.ts
│   │   │   ├── deploy.ts
│   │   │   ├── generate.ts
│   │   │   ├── index.ts
│   │   │   ├── list/
│   │   │   │   ├── category.ts
│   │   │   │   ├── common.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── page.ts
│   │   │   │   ├── post.ts
│   │   │   │   ├── route.ts
│   │   │   │   └── tag.ts
│   │   │   ├── migrate.ts
│   │   │   ├── new.ts
│   │   │   ├── publish.ts
│   │   │   └── render.ts
│   │   ├── filter/
│   │   │   ├── after_post_render/
│   │   │   │   ├── excerpt.ts
│   │   │   │   ├── external_link.ts
│   │   │   │   └── index.ts
│   │   │   ├── after_render/
│   │   │   │   ├── external_link.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── meta_generator.ts
│   │   │   ├── before_exit/
│   │   │   │   ├── index.ts
│   │   │   │   └── save_database.ts
│   │   │   ├── before_generate/
│   │   │   │   ├── index.ts
│   │   │   │   └── render_post.ts
│   │   │   ├── before_post_render/
│   │   │   │   ├── backtick_code_block.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── titlecase.ts
│   │   │   ├── index.ts
│   │   │   ├── new_post_path.ts
│   │   │   ├── post_permalink.ts
│   │   │   └── template_locals/
│   │   │       ├── i18n.ts
│   │   │       └── index.ts
│   │   ├── generator/
│   │   │   ├── asset.ts
│   │   │   ├── index.ts
│   │   │   ├── page.ts
│   │   │   └── post.ts
│   │   ├── helper/
│   │   │   ├── css.ts
│   │   │   ├── date.ts
│   │   │   ├── debug.ts
│   │   │   ├── favicon_tag.ts
│   │   │   ├── feed_tag.ts
│   │   │   ├── format.ts
│   │   │   ├── fragment_cache.ts
│   │   │   ├── full_url_for.ts
│   │   │   ├── gravatar.ts
│   │   │   ├── image_tag.ts
│   │   │   ├── index.ts
│   │   │   ├── is.ts
│   │   │   ├── js.ts
│   │   │   ├── link_to.ts
│   │   │   ├── list_archives.ts
│   │   │   ├── list_categories.ts
│   │   │   ├── list_posts.ts
│   │   │   ├── list_tags.ts
│   │   │   ├── mail_to.ts
│   │   │   ├── markdown.ts
│   │   │   ├── meta_generator.ts
│   │   │   ├── number_format.ts
│   │   │   ├── open_graph.ts
│   │   │   ├── paginator.ts
│   │   │   ├── partial.ts
│   │   │   ├── relative_url.ts
│   │   │   ├── render.ts
│   │   │   ├── search_form.ts
│   │   │   ├── tagcloud.ts
│   │   │   ├── toc.ts
│   │   │   └── url_for.ts
│   │   ├── highlight/
│   │   │   ├── highlight.ts
│   │   │   ├── index.ts
│   │   │   └── prism.ts
│   │   ├── injector/
│   │   │   └── index.ts
│   │   ├── processor/
│   │   │   ├── asset.ts
│   │   │   ├── common.ts
│   │   │   ├── data.ts
│   │   │   ├── index.ts
│   │   │   └── post.ts
│   │   ├── renderer/
│   │   │   ├── index.ts
│   │   │   ├── json.ts
│   │   │   ├── nunjucks.ts
│   │   │   ├── plain.ts
│   │   │   └── yaml.ts
│   │   └── tag/
│   │       ├── asset_img.ts
│   │       ├── asset_link.ts
│   │       ├── asset_path.ts
│   │       ├── blockquote.ts
│   │       ├── code.ts
│   │       ├── full_url_for.ts
│   │       ├── iframe.ts
│   │       ├── img.ts
│   │       ├── include_code.ts
│   │       ├── index.ts
│   │       ├── link.ts
│   │       ├── post_link.ts
│   │       ├── post_path.ts
│   │       ├── pullquote.ts
│   │       └── url_for.ts
│   ├── theme/
│   │   ├── index.ts
│   │   ├── processors/
│   │   │   ├── config.ts
│   │   │   ├── i18n.ts
│   │   │   ├── source.ts
│   │   │   └── view.ts
│   │   └── view.ts
│   └── types.ts
├── package.json
├── test/
│   ├── benchmark.js
│   ├── fixtures/
│   │   ├── _config.json
│   │   ├── hello.njk
│   │   └── post_render.ts
│   ├── scripts/
│   │   ├── box/
│   │   │   ├── box.ts
│   │   │   └── file.ts
│   │   ├── console/
│   │   │   ├── clean.ts
│   │   │   ├── config.ts
│   │   │   ├── deploy.ts
│   │   │   ├── generate.ts
│   │   │   ├── list.ts
│   │   │   ├── list_categories.ts
│   │   │   ├── list_page.ts
│   │   │   ├── list_post.ts
│   │   │   ├── list_route.ts
│   │   │   ├── list_tags.ts
│   │   │   ├── migrate.ts
│   │   │   ├── new.ts
│   │   │   ├── publish.ts
│   │   │   └── render.ts
│   │   ├── extend/
│   │   │   ├── console.ts
│   │   │   ├── deployer.ts
│   │   │   ├── filter.ts
│   │   │   ├── generator.ts
│   │   │   ├── helper.ts
│   │   │   ├── injector.ts
│   │   │   ├── migrator.ts
│   │   │   ├── processor.ts
│   │   │   ├── renderer.ts
│   │   │   ├── tag.ts
│   │   │   └── tag_errors.ts
│   │   ├── filters/
│   │   │   ├── backtick_code_block.ts
│   │   │   ├── excerpt.ts
│   │   │   ├── external_link.ts
│   │   │   ├── i18n_locals.ts
│   │   │   ├── meta_generator.ts
│   │   │   ├── new_post_path.ts
│   │   │   ├── post_permalink.ts
│   │   │   ├── render_post.ts
│   │   │   ├── save_database.ts
│   │   │   └── titlecase.ts
│   │   ├── generators/
│   │   │   ├── asset.ts
│   │   │   ├── page.ts
│   │   │   └── post.ts
│   │   ├── helpers/
│   │   │   ├── css.ts
│   │   │   ├── date.ts
│   │   │   ├── debug.ts
│   │   │   ├── escape_html.ts
│   │   │   ├── favicon_tag.ts
│   │   │   ├── feed_tag.ts
│   │   │   ├── fragment_cache.ts
│   │   │   ├── full_url_for.ts
│   │   │   ├── gravatar.ts
│   │   │   ├── image_tag.ts
│   │   │   ├── is.ts
│   │   │   ├── js.ts
│   │   │   ├── link_to.ts
│   │   │   ├── list_archives.ts
│   │   │   ├── list_categories.ts
│   │   │   ├── list_posts.ts
│   │   │   ├── list_tags.ts
│   │   │   ├── mail_to.ts
│   │   │   ├── markdown.ts
│   │   │   ├── meta_generator.ts
│   │   │   ├── number_format.ts
│   │   │   ├── open_graph.ts
│   │   │   ├── paginator.ts
│   │   │   ├── partial.ts
│   │   │   ├── relative_url.ts
│   │   │   ├── render.ts
│   │   │   ├── search_form.ts
│   │   │   ├── tagcloud.ts
│   │   │   ├── toc.ts
│   │   │   └── url_for.ts
│   │   ├── hexo/
│   │   │   ├── hexo.ts
│   │   │   ├── load_config.ts
│   │   │   ├── load_database.ts
│   │   │   ├── load_plugins.ts
│   │   │   ├── load_theme_config.ts
│   │   │   ├── locals.ts
│   │   │   ├── multi_config_path.ts
│   │   │   ├── post.ts
│   │   │   ├── render.ts
│   │   │   ├── router.ts
│   │   │   ├── scaffold.ts
│   │   │   ├── update_package.ts
│   │   │   └── validate_config.ts
│   │   ├── models/
│   │   │   ├── asset.ts
│   │   │   ├── cache.ts
│   │   │   ├── category.ts
│   │   │   ├── moment.ts
│   │   │   ├── page.ts
│   │   │   ├── post.ts
│   │   │   ├── post_asset.ts
│   │   │   └── tag.ts
│   │   ├── processors/
│   │   │   ├── asset.ts
│   │   │   ├── common.ts
│   │   │   ├── data.ts
│   │   │   └── post.ts
│   │   ├── renderers/
│   │   │   ├── json.ts
│   │   │   ├── nunjucks.ts
│   │   │   ├── plain.ts
│   │   │   └── yaml.ts
│   │   ├── tags/
│   │   │   ├── asset_img.ts
│   │   │   ├── asset_link.ts
│   │   │   ├── asset_path.ts
│   │   │   ├── blockquote.ts
│   │   │   ├── code.ts
│   │   │   ├── full_url_for.ts
│   │   │   ├── iframe.ts
│   │   │   ├── img.ts
│   │   │   ├── include_code.ts
│   │   │   ├── link.ts
│   │   │   ├── post_link.ts
│   │   │   ├── post_path.ts
│   │   │   ├── pullquote.ts
│   │   │   └── url_for.ts
│   │   ├── theme/
│   │   │   ├── theme.ts
│   │   │   └── view.ts
│   │   └── theme_processors/
│   │       ├── config.ts
│   │       ├── i18n.ts
│   │       ├── source.ts
│   │       └── view.ts
│   └── util/
│       ├── index.ts
│       └── stream.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (646 symbols across 174 files)

FILE: lib/box/file.ts
  class File (line 5) | class File {
    method constructor (line 32) | constructor({ source, path, params, type }: {
    method read (line 44) | read(options?: ReadFileOptions): Promise<string> {
    method readSync (line 48) | readSync(options?: ReadFileOptions): string {
    method stat (line 52) | stat(): Promise<fs.Stats> {
    method statSync (line 56) | statSync(): fs.Stats {

FILE: lib/box/index.ts
  type Processor (line 15) | interface Processor {
  type BoxOptions (line 20) | interface BoxOptions {
  class Box (line 27) | class Box extends EventEmitter {
    method constructor (line 39) | constructor(ctx: Hexo, base: string, options?: any) {
    method _createFileClass (line 68) | _createFileClass() {
    method addProcessor (line 94) | addProcessor(pattern: string | RegExp | Pattern | ((str: string) => an...
    method _readDir (line 109) | _readDir(base: string, prefix = ''): BlueBirdPromise<string[]> {
    method _checkFileStatus (line 118) | _checkFileStatus(path: string): { type: string; path: string } {
    method process (line 132) | process(callback?: NodeJSLikeCallback<any>): BlueBirdPromise<void | (s...
    method _processFile (line 151) | _processFile(type: string, path: string): BlueBirdPromise<void | strin...
    method watch (line 196) | watch(callback?: NodeJSLikeCallback<never>): BlueBirdPromise<void> {
    method unwatch (line 231) | unwatch(): void {
    method isWatching (line 238) | isWatching(): boolean {
  function escapeBackslash (line 243) | function escapeBackslash(path: string): string {
  function getHash (line 248) | function getHash(path: string): BlueBirdPromise<string> {
  function toRegExp (line 262) | function toRegExp(ctx: Hexo, arg: string): RegExp | null {
  function isIgnoreMatch (line 276) | function isIgnoreMatch(path: string, ignore: string | string[]): boolean {
  function readDirWalker (line 280) | function readDirWalker(ctx: Hexo, base: string, results: string[], ignor...
  type _File (line 306) | interface _File extends File {

FILE: lib/extend/console.ts
  type Option (line 6) | type Option = Partial<{
  type Args (line 20) | interface Args {
  type AnyFn (line 24) | type AnyFn = (this: Hexo, args: Args, callback?: NodeJSLikeCallback<any>...
  type StoreFunction (line 25) | interface StoreFunction {
  type Store (line 31) | interface Store {
  type Alias (line 34) | interface Alias {
  class Console (line 41) | class Console {
    method constructor (line 45) | constructor() {
    method get (line 55) | get(name: string): StoreFunction {
    method list (line 60) | list(): Store {
    method register (line 75) | register(name: string, desc: string | Option | AnyFn, options?: Option...

FILE: lib/extend/deployer.ts
  type StoreFunction (line 5) | interface StoreFunction {
  type Store (line 8) | interface Store {
  class Deployer (line 15) | class Deployer {
    method constructor (line 18) | constructor() {
    method list (line 22) | list(): Store {
    method get (line 26) | get(name: string): StoreFunction {
    method register (line 30) | register(

FILE: lib/extend/filter.ts
  type StoreFunction (line 10) | interface StoreFunction {
  type Store (line 15) | interface Store {
  class Filter (line 23) | class Filter {
    method constructor (line 26) | constructor() {
    method list (line 32) | list(type?: string) {
    method register (line 41) | register(type: string | StoreFunction, fn?: StoreFunction | number, pr...
    method unregister (line 64) | unregister(type: string, fn: StoreFunction): void {
    method exec (line 78) | exec(type: string, data: any, options: FilterOptions = {}): Promise<an...
    method execSync (line 93) | execSync(type: string, data: any, options: FilterOptions = {}) {

FILE: lib/extend/generator.ts
  type ReturnType (line 4) | type ReturnType = BaseGeneratorReturn | BaseGeneratorReturn[];
  type GeneratorReturnType (line 5) | type GeneratorReturnType = ReturnType | Promise<ReturnType>;
  type GeneratorFunction (line 7) | interface GeneratorFunction {
  type StoreFunctionReturn (line 11) | type StoreFunctionReturn = Promise<ReturnType>;
  type StoreFunction (line 13) | interface StoreFunction {
  type Store (line 17) | interface Store {
  class Generator (line 24) | class Generator {
    method constructor (line 28) | constructor() {
    method list (line 33) | list(): Store {
    method get (line 37) | get(name: string): StoreFunction {
    method register (line 43) | register(name: string | GeneratorFunction, fn?: GeneratorFunction): vo...

FILE: lib/extend/helper.ts
  type HexoContext (line 5) | interface HexoContext extends Hexo {
  type StoreFunction (line 39) | interface StoreFunction {
  type Store (line 43) | interface Store {
  class Helper (line 50) | class Helper {
    method constructor (line 53) | constructor() {
    method list (line 60) | list(): Store {
    method get (line 69) | get(name: string): StoreFunction {
    method register (line 78) | register(name: string, fn: StoreFunction): void {

FILE: lib/extend/injector.ts
  type Entry (line 3) | type Entry = 'head_begin' | 'head_end' | 'body_begin' | 'body_end';
  type Store (line 5) | type Store = {
  class Injector (line 15) | class Injector {
    method constructor (line 20) | constructor() {
    method list (line 31) | list(): Store {
    method get (line 35) | get(entry: Entry, to = 'default'): any[] {
    method getText (line 39) | getText(entry: Entry, to = 'default'): string {
    method getSize (line 45) | getSize(entry: Entry): number {
    method register (line 49) | register(entry: Entry, value: string | (() => string), to = 'default')...
    method _getPageType (line 59) | _getPageType(pageLocals): string {
    method _injector (line 72) | _injector(input: string, pattern: string | RegExp, flag: Entry, isBegi...
    method exec (line 88) | exec(data: string, locals = { page: {} }): string {

FILE: lib/extend/migrator.ts
  type StoreFunction (line 5) | interface StoreFunction {
  type Store (line 9) | interface Store {
  class Migrator (line 16) | class Migrator {
    method constructor (line 19) | constructor() {
    method list (line 23) | list(): Store {
    method get (line 27) | get(name: string): StoreFunction {
    method register (line 31) | register(name: string, fn: (this: Hexo, args: any, callback?: NodeJSLi...

FILE: lib/extend/processor.ts
  type StoreFunction (line 5) | interface StoreFunction {
  type Store (line 9) | type Store = {
  type patternType (line 14) | type patternType = Exclude<ConstructorParameters<typeof Pattern>[0], (st...
  class Processor (line 19) | class Processor {
    method constructor (line 22) | constructor() {
    method list (line 26) | list(): Store {
    method register (line 32) | register(pattern: patternType | StoreFunction, fn?: StoreFunction): vo...

FILE: lib/extend/renderer.ts
  type StoreFunctionData (line 12) | interface StoreFunctionData {
  type StoreSyncFunction (line 20) | interface StoreSyncFunction {
  type StoreFunction (line 31) | interface StoreFunction {
  type StoreFunctionWithCallback (line 42) | interface StoreFunctionWithCallback {
  type SyncStore (line 54) | interface SyncStore {
  type Store (line 57) | interface Store {
  class Renderer (line 64) | class Renderer {
    method constructor (line 68) | constructor() {
    method list (line 73) | list(sync = false): Store | SyncStore {
    method get (line 77) | get(name: string, sync?: boolean): StoreSyncFunction | StoreFunction {
    method isRenderable (line 83) | isRenderable(path: string): boolean {
    method isRenderableSync (line 87) | isRenderableSync(path: string): boolean {
    method getOutput (line 91) | getOutput(path: string): string {
    method register (line 100) | register(name: string, output: string, fn: StoreFunctionWithCallback |...

FILE: lib/extend/syntax_highlight.ts
  type HighlightOptions (line 3) | interface HighlightOptions {
  type HighlightExecArgs (line 21) | interface HighlightExecArgs {
  type StoreFunction (line 26) | interface StoreFunction {
  type Store (line 31) | interface Store {
  class SyntaxHighlight (line 35) | class SyntaxHighlight {
    method constructor (line 38) | constructor() {
    method register (line 42) | register(name: string, fn: StoreFunction): void {
    method query (line 48) | query(name: string): StoreFunction {
    method exec (line 52) | exec(name: string, options: HighlightExecArgs): string {

FILE: lib/extend/tag.ts
  type TagFunction (line 11) | interface TagFunction {
  type AsyncTagFunction (line 14) | interface AsyncTagFunction {
  class NunjucksTag (line 18) | class NunjucksTag {
    method constructor (line 22) | constructor(name: string, fn: TagFunction | AsyncTagFunction) {
    method parse (line 27) | parse(parser, nodes, lexer) {
    method _parseArgs (line 33) | _parseArgs(parser, nodes, lexer) {
    method run (line 62) | run(context, args, _body, _callback) {
    method _run (line 66) | _run(context, args, body): any {
  class NunjucksBlock (line 75) | class NunjucksBlock extends NunjucksTag {
    method parse (line 76) | parse(parser, nodes, lexer) {
    method _parseBody (line 83) | _parseBody(parser, _nodes, _lexer) {
    method run (line 90) | run(context, args, body, _callback) {
  class NunjucksAsyncTag (line 95) | class NunjucksAsyncTag extends NunjucksTag {
    method parse (line 96) | parse(parser, nodes, lexer) {
    method run (line 102) | run(context, args, callback) {
  class NunjucksAsyncBlock (line 109) | class NunjucksAsyncBlock extends NunjucksBlock {
    method parse (line 110) | parse(parser, nodes, lexer) {
    method run (line 117) | run(context, args, body, callback) {
  constant LINES_OF_CONTEXT (line 139) | const LINES_OF_CONTEXT = 5;
  class NunjucksError (line 166) | class NunjucksError extends Error {
  type RegisterOptions (line 198) | type RegisterOptions = {
  class Tag (line 206) | class Tag {
    method constructor (line 210) | constructor() {
    method register (line 219) | register(name: string, fn: TagFunction, options?: RegisterOptions | bo...
    method unregister (line 251) | unregister(name: string): void {
    method render (line 262) | render(str: string, options: { source?: string, [key: string]: any } |...

FILE: lib/hexo/index.ts
  function debounce (line 102) | function debounce(func: () => void, wait: number): () => void {
  type Args (line 112) | interface Args {
  type Query (line 148) | interface Query {
  type Extend (line 153) | interface Extend {
  type Env (line 167) | interface Env {
  type DefaultConfigType (line 178) | type DefaultConfigType = typeof defaultConfig;
  type Config (line 179) | interface Config extends DefaultConfigType {
  type Hexo (line 192) | interface Hexo {
    method constructor (line 311) | constructor(base = process.cwd(), args: Args = {}) {
    method _bindLocals (line 394) | _bindLocals(): void {
    method init (line 448) | init(): Promise<void> {
    method call (line 485) | call(name: string, args?: object | NodeJSLikeCallback<any>, callback?:...
    method model (line 497) | model(name: string, schema?: Schema | Record<string, AddSchemaTypeOpti...
    method resolvePlugin (line 501) | resolvePlugin(name: string, basedir: string): string {
    method loadPlugin (line 512) | loadPlugin(path: string, callback?: NodeJSLikeCallback<any>): Promise<...
    method _showDrafts (line 537) | _showDrafts(): boolean {
    method load (line 548) | load(callback?: NodeJSLikeCallback<any>): Promise<any> {
    method watch (line 571) | watch(callback?: NodeJSLikeCallback<any>): Promise<any> {
    method unwatch (line 606) | unwatch(): void {
    method _generateLocals (line 618) | _generateLocals() {
    method _runGenerators (line 652) | _runGenerators(): Promise<BaseGeneratorReturn[]> {
    method _routerRefresh (line 669) | _routerRefresh(runningGenerators: Promise<BaseGeneratorReturn[]>, useC...
    method _generate (line 702) | _generate(options: { cache?: boolean } = {}): Promise<any> {
    method exit (line 732) | exit(err?: any): Promise<void> {
    method execFilter (line 746) | execFilter(type: string, data: any, options?: FilterOptions) {
    method execFilterSync (line 750) | execFilterSync(type: string, data: any, options?: FilterOptions) {
  class Hexo (line 275) | class Hexo extends EventEmitter {
    method constructor (line 311) | constructor(base = process.cwd(), args: Args = {}) {
    method _bindLocals (line 394) | _bindLocals(): void {
    method init (line 448) | init(): Promise<void> {
    method call (line 485) | call(name: string, args?: object | NodeJSLikeCallback<any>, callback?:...
    method model (line 497) | model(name: string, schema?: Schema | Record<string, AddSchemaTypeOpti...
    method resolvePlugin (line 501) | resolvePlugin(name: string, basedir: string): string {
    method loadPlugin (line 512) | loadPlugin(path: string, callback?: NodeJSLikeCallback<any>): Promise<...
    method _showDrafts (line 537) | _showDrafts(): boolean {
    method load (line 548) | load(callback?: NodeJSLikeCallback<any>): Promise<any> {
    method watch (line 571) | watch(callback?: NodeJSLikeCallback<any>): Promise<any> {
    method unwatch (line 606) | unwatch(): void {
    method _generateLocals (line 618) | _generateLocals() {
    method _runGenerators (line 652) | _runGenerators(): Promise<BaseGeneratorReturn[]> {
    method _routerRefresh (line 669) | _routerRefresh(runningGenerators: Promise<BaseGeneratorReturn[]>, useC...
    method _generate (line 702) | _generate(options: { cache?: boolean } = {}): Promise<any> {
    method exit (line 732) | exit(err?: any): Promise<void> {
    method execFilter (line 746) | execFilter(type: string, data: any, options?: FilterOptions) {
    method execFilterSync (line 750) | execFilterSync(type: string, data: any, options?: FilterOptions) {

FILE: lib/hexo/load_config.ts
  function findConfigPath (line 68) | async function findConfigPath(path: string): Promise<string> {

FILE: lib/hexo/load_plugins.ts
  function loadModuleList (line 13) | function loadModuleList(ctx: Hexo, basedir: string): Promise<Record<stri...
  function loadModules (line 46) | function loadModules(ctx: Hexo): Promise<void[]> {
  function loadScripts (line 61) | function loadScripts(ctx: Hexo): Promise<void[][]> {
  function displayPath (line 80) | function displayPath(path: string, baseDirLength: number): string {

FILE: lib/hexo/load_theme_config.ts
  function findConfigPath (line 35) | function findConfigPath(path: string): Promise<string> {

FILE: lib/hexo/locals.ts
  class Locals (line 3) | class Locals {
    method constructor (line 7) | constructor() {
    method get (line 12) | get(name: string): any {
    method set (line 23) | set(name: string, value: any): this {
    method remove (line 35) | remove(name: string): this {
    method invalidate (line 44) | invalidate(): this {
    method toObject (line 50) | toObject(): Record<string, any> {

FILE: lib/hexo/post.ts
  constant STATE_PLAINTEXT (line 23) | const STATE_PLAINTEXT = 0;
  constant STATE_SWIG_VAR (line 24) | const STATE_SWIG_VAR = 1;
  constant STATE_SWIG_COMMENT (line 25) | const STATE_SWIG_COMMENT = 2;
  constant STATE_SWIG_TAG (line 26) | const STATE_SWIG_TAG = 3;
  constant STATE_SWIG_FULL_TAG (line 27) | const STATE_SWIG_FULL_TAG = 4;
  constant STATE_PLAINTEXT_COMMENT (line 28) | const STATE_PLAINTEXT_COMMENT = 5;
  class PostRenderEscape (line 37) | class PostRenderEscape {
    method constructor (line 41) | constructor() {
    method escapeContent (line 45) | static escapeContent(cache: string[], flag: string, str: string) {
    method restoreContent (line 49) | static restoreContent(cache: string[]) {
    method restoreAllSwigTags (line 58) | restoreAllSwigTags(str: string) {
    method restoreCodeBlocks (line 63) | restoreCodeBlocks(str: string) {
    method restoreComments (line 67) | restoreComments(str: string) {
    method escapeComments (line 71) | escapeComments(str: string) {
    method escapeCodeBlocks (line 75) | escapeCodeBlocks(str: string) {
    method escapeAllSwigTags (line 83) | escapeAllSwigTags(str: string) {
  type Result (line 324) | interface Result {
  type PostData (line 329) | interface PostData {
  class Post (line 338) | class Post {
    method constructor (line 341) | constructor(context: Hexo) {
    method create (line 347) | create(data: PostData, replace: boolean | (NodeJSLikeCallback<any>), c...
    method _getScaffold (line 382) | _getScaffold(layout: string) {
    method _renderScaffold (line 391) | _renderScaffold(data: PostData) {
    method publish (line 432) | publish(data: PostData, replace?: boolean | NodeJSLikeCallback<Result>...
    method render (line 485) | render(source: string, data: RenderData = {}, callback?: NodeJSLikeCal...

FILE: lib/hexo/render.ts
  class Render (line 30) | class Render {
    method constructor (line 34) | constructor(ctx: Hexo) {
    method isRenderable (line 39) | isRenderable(path: string): boolean {
    method isRenderableSync (line 43) | isRenderableSync(path: string): boolean {
    method getOutput (line 47) | getOutput(path: string): string {
    method getRenderer (line 51) | getRenderer(ext: string, sync?: boolean): StoreSyncFunction | StoreFun...
    method getRendererSync (line 55) | getRendererSync(ext: string): StoreSyncFunction | StoreFunction {
    method render (line 61) | render(data: StoreFunctionData, options?: any | NodeJSLikeCallback<any...
    method renderSync (line 105) | renderSync(data: StoreFunctionData, options = {}): any {

FILE: lib/hexo/router.ts
  type Data (line 6) | interface Data {
  class RouteStream (line 11) | class RouteStream extends Readable {
    method constructor (line 16) | constructor(data: Data) {
    method _toBuffer (line 25) | _toBuffer(data: Buffer | object | string): Buffer | null {
    method _read (line 38) | _read(): boolean {
  class Router (line 98) | class Router extends EventEmitter {
    method constructor (line 103) | constructor() {
    method list (line 109) | list(): string[] {
    method format (line 114) | format(path?: string): string {
    method get (line 118) | get(path: string): RouteStream {
    method isModified (line 127) | isModified(path: string): boolean {
    method set (line 134) | set(path: string, data: any): this {
    method remove (line 169) | remove(path: string): this {

FILE: lib/hexo/scaffold.ts
  class Scaffold (line 7) | class Scaffold {
    method constructor (line 14) | constructor(context: Hexo) {
    method _listDir (line 29) | _listDir(): Promise<{
    method _getScaffold (line 47) | _getScaffold(name: string): Promise<{
    method get (line 54) | get(name: string, callback?: NodeJSLikeCallback<any>): Promise<string> {
    method set (line 64) | set(name: string, content: any, callback?: NodeJSLikeCallback<void>): ...
    method remove (line 75) | remove(name: string, callback?: NodeJSLikeCallback<void>): Promise<voi...

FILE: lib/hexo/source.ts
  class Source (line 4) | class Source extends Box {
    method constructor (line 5) | constructor(ctx: Hexo) {

FILE: lib/hexo/update_package.ts
  function readPkg (line 23) | function readPkg(path: string): Promise<any> {

FILE: lib/models/binary_relation_index.ts
  type BinaryRelationType (line 3) | type BinaryRelationType<K extends PropertyKey, V extends PropertyKey> = {
  class BinaryRelationIndex (line 9) | class BinaryRelationIndex<K extends PropertyKey, V extends PropertyKey> {
    method constructor (line 17) | constructor(key: K, value: V, schemaName: string, ctx: Hexo) {
    method load (line 24) | load() {
    method saveHook (line 33) | saveHook(data: BinaryRelationType<K, V> & { _id: PropertyKey }) {
    method removeHook (line 49) | removeHook(data: BinaryRelationType<K, V> & { _id: PropertyKey }) {
    method findById (line 63) | findById(_id: PropertyKey) {
    method find (line 69) | find(query: Partial<BinaryRelationType<K, V>>) {
    method findOne (line 96) | findOne(query: Partial<BinaryRelationType<K, V>>) {

FILE: lib/models/post.ts
  function pickID (line 10) | function pickID(data: PostSchema | PostCategorySchema) {
  function removeEmptyTag (line 14) | function removeEmptyTag(tags: string[]) {

FILE: lib/models/types/moment.ts
  class SchemaTypeMoment (line 12) | class SchemaTypeMoment extends warehouse.SchemaType<moment.Moment> {
    method constructor (line 15) | constructor(name, options = {}) {
    method cast (line 19) | cast(value?, data?) {
    method validate (line 26) | validate(value, data?) {
    method match (line 39) | match(value, query, _data?) {
    method compare (line 43) | compare(a?, b?) {
    method parse (line 53) | parse(value?) {
    method value (line 57) | value(value?, _data?) {
    method q$day (line 62) | q$day(value, query, _data?) {
    method q$month (line 66) | q$month(value, query, _data?) {
    method q$year (line 70) | q$year(value, query, _data?) {
    method u$inc (line 74) | u$inc(value, update, _data?) {
    method u$dec (line 79) | u$dec(value, update, _data?) {
  function toMoment (line 85) | function toMoment(value) {

FILE: lib/plugins/console/clean.ts
  function cleanConsole (line 5) | function cleanConsole(this: Hexo): Promise<[void, void, any]> {
  function deleteDatabase (line 13) | function deleteDatabase(ctx: Hexo): Promise<void> {
  function deletePublicDir (line 25) | function deletePublicDir(ctx: Hexo): Promise<void> {

FILE: lib/plugins/console/config.ts
  type ConfigArgs (line 7) | interface ConfigArgs {
  function configConsole (line 12) | function configConsole(this: Hexo, args: ConfigArgs): Promise<void> {
  function getProperty (line 44) | function getProperty(obj: object, key: string): any {
  function setProperty (line 55) | function setProperty(obj: object, key: string, value: any): void {
  function castValue (line 69) | function castValue(value: string): any {

FILE: lib/plugins/console/deploy.ts
  type DeployArgs (line 6) | interface DeployArgs {
  function deployConsole (line 13) | function deployConsole(this: Hexo, args: DeployArgs): Promise<any> {

FILE: lib/plugins/console/generate.ts
  type GenerateArgs (line 12) | interface GenerateArgs {
  class Generator (line 26) | class Generator {
    method constructor (line 37) | constructor(ctx: Hexo, args: GenerateArgs) {
    method generateFile (line 48) | generateFile(path: string): Promise<void | boolean> {
    method writeFile (line 75) | writeFile(path: string, force?: boolean): Promise<boolean> {
    method deleteFile (line 116) | deleteFile(path: string): Promise<void> {
    method wrapDataStream (line 129) | wrapDataStream(dataStream: ReturnType<Router['get']>): Readable {
    method firstGenerate (line 143) | firstGenerate(): Promise<void> {
    method execWatch (line 188) | execWatch(): Promise<void> {
    method execDeploy (line 204) | execDeploy() {
  function generateConsole (line 209) | function generateConsole(this: Hexo, args: GenerateArgs = {}): Promise<a...

FILE: lib/plugins/console/list/category.ts
  function listCategory (line 9) | function listCategory(this: Hexo): void {

FILE: lib/plugins/console/list/common.ts
  function stringLength (line 3) | function stringLength(str: string): number {

FILE: lib/plugins/console/list/index.ts
  type ListArgs (line 10) | interface ListArgs {
  function listConsole (line 20) | function listConsole(this: Hexo, args: ListArgs): Promise<void> {

FILE: lib/plugins/console/list/page.ts
  function listPage (line 9) | function listPage(this: Hexo): void {

FILE: lib/plugins/console/list/post.ts
  function mapName (line 9) | function mapName(item: any): string {
  function listPost (line 13) | function listPost(this: Hexo): void {

FILE: lib/plugins/console/list/route.ts
  function listRoute (line 4) | function listRoute(this: Hexo): void {
  function buildTree (line 17) | function buildTree(routes: string[]) {
  function buildNodes (line 35) | function buildNodes(tree: Record<string, any>) {

FILE: lib/plugins/console/list/tag.ts
  function listTag (line 9) | function listTag(this: Hexo): void {

FILE: lib/plugins/console/migrate.ts
  type MigrateArgs (line 4) | interface MigrateArgs {
  function migrateConsole (line 9) | function migrateConsole(this: Hexo, args: MigrateArgs): Promise<any> {

FILE: lib/plugins/console/new.ts
  type NewArgs (line 24) | interface NewArgs {
  function newConsole (line 35) | function newConsole(this: Hexo, args: NewArgs): Promise<void> {

FILE: lib/plugins/console/publish.ts
  type PublishArgs (line 6) | interface PublishArgs {
  function publishConsole (line 13) | function publishConsole(this: Hexo, args: PublishArgs): Promise<void> {

FILE: lib/plugins/console/render.ts
  type RenderArgs (line 9) | interface RenderArgs {
  function renderConsole (line 18) | function renderConsole(this: Hexo, args: RenderArgs): Promise<void> {

FILE: lib/plugins/filter/after_post_render/excerpt.ts
  function excerptFilter (line 5) | function excerptFilter(data: RenderData): void {

FILE: lib/plugins/filter/after_post_render/external_link.ts
  constant EXTERNAL_LINK_POST_ENABLED (line 5) | let EXTERNAL_LINK_POST_ENABLED = true;
  function externalLinkFilter (line 11) | function externalLinkFilter(this: Hexo, data: RenderData): void {

FILE: lib/plugins/filter/after_render/external_link.ts
  constant EXTERNAL_LINK_SITE_ENABLED (line 4) | let EXTERNAL_LINK_SITE_ENABLED = true;
  function externalLinkFilter (line 14) | function externalLinkFilter(this: Hexo, data: string): string {

FILE: lib/plugins/filter/after_render/meta_generator.ts
  constant NEED_INJECT (line 3) | let NEED_INJECT = true;
  constant HAS_CHECKED (line 4) | let HAS_CHECKED = false;
  constant META_GENERATOR_TAG (line 5) | let META_GENERATOR_TAG;
  function hexoMetaGeneratorInject (line 7) | function hexoMetaGeneratorInject(this: Hexo, data: string): string {

FILE: lib/plugins/filter/before_exit/save_database.ts
  function saveDatabaseFilter (line 3) | function saveDatabaseFilter(this: Hexo): Promise<void> {

FILE: lib/plugins/filter/before_generate/render_post.ts
  function renderPostFilter (line 5) | function renderPostFilter(this: Hexo): Promise<[any[], any[]]> {

FILE: lib/plugins/filter/before_post_render/backtick_code_block.ts
  function parseArgs (line 13) | function parseArgs(args: string) {

FILE: lib/plugins/filter/before_post_render/titlecase.ts
  function titlecaseFilter (line 5) | function titlecaseFilter(data: RenderData): void {

FILE: lib/plugins/filter/new_post_path.ts
  function newPostPathFilter (line 21) | function newPostPathFilter(this: Hexo, data: Partial<PostSchema> = {}, r...

FILE: lib/plugins/filter/post_permalink.ts
  function postPermalinkFilter (line 8) | function postPermalinkFilter(this: Hexo, data: PostSchema): string {

FILE: lib/plugins/filter/template_locals/i18n.ts
  function i18nLocalsFilter (line 5) | function i18nLocalsFilter(this: Hexo, locals: LocalsType): void {

FILE: lib/plugins/generator/asset.ts
  type AssetData (line 9) | interface AssetData {
  type AssetGenerator (line 14) | interface AssetGenerator extends BaseGeneratorReturn {
  function assetGenerator (line 51) | function assetGenerator(this: Hexo): Promise<AssetGenerator[]> {

FILE: lib/plugins/generator/page.ts
  type SimplePageGenerator (line 4) | type SimplePageGenerator = Omit<BaseGeneratorReturn, 'layout'> & { data:...
  type NormalPageGenerator (line 5) | interface NormalPageGenerator extends BaseGeneratorReturn {
  type PageGenerator (line 9) | type PageGenerator = SimplePageGenerator | NormalPageGenerator;
  function pageGenerator (line 11) | function pageGenerator(locals: SiteLocals): PageGenerator[] {

FILE: lib/plugins/generator/post.ts
  type SimplePostGenerator (line 4) | type SimplePostGenerator = Omit<BaseGeneratorReturn, 'layout'> & { data:...
  type NormalPostGenerator (line 5) | interface NormalPostGenerator extends BaseGeneratorReturn {
  type PostGenerator (line 9) | type PostGenerator = SimplePostGenerator | NormalPostGenerator;
  function postGenerator (line 11) | function postGenerator(locals: SiteLocals): PostGenerator[] {

FILE: lib/plugins/helper/css.ts
  function cssHelper (line 6) | function cssHelper(this: LocalsType, ...args: any[]) {
  method updateCacheForKey (line 35) | updateCacheForKey() {

FILE: lib/plugins/helper/date.ts
  function getMoment (line 9) | function getMoment(date: moment.MomentInput | moment.Moment, lang: strin...
  function toISOString (line 20) | function toISOString(date?: string | number | Date | moment.Moment) {
  function dateHelper (line 32) | function dateHelper(this: LocalsType, date?: moment.Moment | moment.Mome...
  function timeHelper (line 38) | function timeHelper(this: LocalsType, date?: moment.Moment | moment.Mome...
  function fullDateHelper (line 44) | function fullDateHelper(this: LocalsType, date?: moment.Moment | moment....
  function relativeDateHelper (line 53) | function relativeDateHelper(this: LocalsType, date?: moment.Moment | mom...
  function timeTagHelper (line 59) | function timeTagHelper(this: LocalsType, date?: string | number | Date |...
  function getLanguage (line 63) | function getLanguage(ctx: LocalsType) {
  function _toMomentLocale (line 75) | function _toMomentLocale(lang?: string) {

FILE: lib/plugins/helper/debug.ts
  function inspectObject (line 4) | function inspectObject(object: any, options?: any) {
  function log (line 9) | function log(...args: any[]) {

FILE: lib/plugins/helper/favicon_tag.ts
  function faviconTagHelper (line 4) | function faviconTagHelper(this: LocalsType, path: string) {

FILE: lib/plugins/helper/feed_tag.ts
  type Options (line 10) | interface Options {
  function makeFeedTag (line 15) | function makeFeedTag(this: LocalsType, path?: string, options: Options =...
  function feedTagHelper (line 50) | function feedTagHelper(this: LocalsType, path?: string, options: Options...

FILE: lib/plugins/helper/format.ts
  function trim (line 6) | function trim(str: string) {

FILE: lib/plugins/helper/image_tag.ts
  type Options (line 4) | interface Options {
  type Attrs (line 10) | interface Attrs {
  function imageTagHelper (line 16) | function imageTagHelper(this: LocalsType, path: string, options: Options...

FILE: lib/plugins/helper/is.ts
  function isCurrentHelper (line 3) | function isCurrentHelper(this: LocalsType, path = '/', strict: boolean) {
  function isHomeHelper (line 22) | function isHomeHelper() {
  function isHomeFirstPageHelper (line 26) | function isHomeFirstPageHelper() {
  function isPostHelper (line 30) | function isPostHelper() {
  function isPageHelper (line 34) | function isPageHelper() {
  function isArchiveHelper (line 38) | function isArchiveHelper() {
  function isYearHelper (line 42) | function isYearHelper(year?) {
  function isMonthHelper (line 53) | function isMonthHelper(year?, month?) {
  function isCategoryHelper (line 68) | function isCategoryHelper(category?) {
  function isTagHelper (line 76) | function isTagHelper(tag?) {

FILE: lib/plugins/helper/js.ts
  function jsHelper (line 6) | function jsHelper(this: LocalsType, ...args: any[]) {
  method updateCacheForKey (line 32) | updateCacheForKey() {

FILE: lib/plugins/helper/link_to.ts
  type Options (line 4) | interface Options {
  type Attrs (line 14) | interface Attrs {
  function linkToHelper (line 24) | function linkToHelper(this: LocalsType, path: string, text?: string, opt...

FILE: lib/plugins/helper/list_archives.ts
  type Options (line 6) | interface Options {
  type Data (line 17) | interface Data {
  function listArchivesHelper (line 26) | function listArchivesHelper(this: LocalsType, options: Options = {}) {

FILE: lib/plugins/helper/list_categories.ts
  type Options (line 6) | interface Options {
  function listCategoriesHelper (line 20) | function listCategoriesHelper(this: LocalsType, categories?: Query<Categ...

FILE: lib/plugins/helper/list_posts.ts
  type Options (line 5) | interface Options {
  function listPostsHelper (line 15) | function listPostsHelper(this: LocalsType, posts?: Query<PostSchema> | O...

FILE: lib/plugins/helper/list_tags.ts
  type Options (line 6) | interface Options {
  function listTagsHelper (line 18) | function listTagsHelper(this: LocalsType, tags?: Query<TagSchema> | Opti...
  function listTagsHelperFactory (line 106) | function listTagsHelperFactory(tags?: Query<TagSchema> | Options, option...

FILE: lib/plugins/helper/mail_to.ts
  type Options (line 4) | interface Options {
  type Attrs (line 15) | interface Attrs {
  function mailToHelper (line 27) | function mailToHelper(path: string | string[], text?: string, options: O...

FILE: lib/plugins/helper/markdown.ts
  function markdownHelper (line 3) | function markdownHelper(this: LocalsType, text: string, options?: any) {

FILE: lib/plugins/helper/meta_generator.ts
  function metaGeneratorHelper (line 3) | function metaGeneratorHelper(this: LocalsType) {

FILE: lib/plugins/helper/number_format.ts
  type Options (line 1) | interface Options {
  function numberFormatHelper (line 7) | function numberFormatHelper(num: number, options: Options = {}) {

FILE: lib/plugins/helper/open_graph.ts
  type Options (line 56) | interface Options {
  function openGraphHelper (line 76) | function openGraphHelper(this: LocalsType, options: Options = {}) {

FILE: lib/plugins/helper/paginator.ts
  type Options (line 4) | interface Options {
  function paginatorHelper (line 112) | function paginatorHelper(this: LocalsType, options: Options = {}) {

FILE: lib/plugins/helper/partial.ts
  type Options (line 5) | interface Options {

FILE: lib/plugins/helper/search_form.ts
  type Options (line 4) | interface Options {
  function searchFormHelper (line 10) | function searchFormHelper(this: LocalsType, options: Options = {}) {

FILE: lib/plugins/helper/tagcloud.ts
  type Options (line 6) | interface Options {
  function tagcloudHelper (line 24) | function tagcloudHelper(this: LocalsType, tags?: Query<TagSchema> | Opti...
  function tagcloudHelperFactory (line 99) | function tagcloudHelperFactory(this: LocalsType, tags?: Query<TagSchema>...

FILE: lib/plugins/helper/toc.ts
  type Options (line 3) | interface Options {
  function tocHelper (line 22) | function tocHelper(str, options: Options = {}) {
  function getAndTruncateTocObj (line 87) | function getAndTruncateTocObj(str, { min_depth, max_depth }, max_items) {
  function buildTree (line 107) | function buildTree(headings) {
  function assignNumbers (line 128) | function assignNumbers(nodes) {

FILE: lib/plugins/helper/url_for.ts
  type Options (line 4) | interface Options {

FILE: lib/plugins/processor/asset.ts
  function processPage (line 32) | function processPage(ctx: Hexo, file: _File) {
  function processAsset (line 112) | function processAsset(ctx: Hexo, file: _File) {

FILE: lib/plugins/processor/common.ts
  constant DURATION_MINUTE (line 4) | const DURATION_MINUTE = 1000 * 60;
  function isMatch (line 6) | function isMatch(path: string, patterns?: string| string[]) {
  function isTmpFile (line 12) | function isTmpFile(path: string) {
  function isHiddenFile (line 16) | function isHiddenFile(path: string) {
  function isExcludedFile (line 20) | function isExcludedFile(path: string, config) {
  function toDate (line 31) | function toDate(date?: string | number | Date | moment.Moment): Date | u...
  function adjustDateForTimezone (line 43) | function adjustDateForTimezone(date: Date | moment.Moment, timezone: str...

FILE: lib/plugins/processor/index.ts
  function register (line 6) | function register(name: string) {

FILE: lib/plugins/processor/post.ts
  function processPost (line 70) | function processPost(ctx: Hexo, file: _File) {
  function parseFilename (line 194) | function parseFilename(config: string, path: string) {
  function scanAssetDir (line 227) | function scanAssetDir(ctx: Hexo, post: PostSchema) {
  function shouldSkipAsset (line 261) | function shouldSkipAsset(ctx: Hexo, post: PostSchema, asset: Document<Po...
  function processAsset (line 276) | function processAsset(ctx: Hexo, file: _File) {

FILE: lib/plugins/renderer/json.ts
  function jsonRenderer (line 3) | function jsonRenderer(data: StoreFunctionData): any {

FILE: lib/plugins/renderer/nunjucks.ts
  function toArray (line 6) | function toArray(value) {
  function safeJsonStringify (line 25) | function safeJsonStringify(json: any, spacer = undefined): string {
  function njkCompile (line 45) | function njkCompile(data: StoreFunctionData): nunjucks.Template {
  function njkRenderer (line 59) | function njkRenderer(data: StoreFunctionData, locals?: any): string {

FILE: lib/plugins/renderer/plain.ts
  function plainRenderer (line 3) | function plainRenderer(data: StoreFunctionData): string {

FILE: lib/plugins/renderer/yaml.ts
  function yamlHelper (line 18) | function yamlHelper(data: StoreFunctionData): any {

FILE: lib/plugins/tag/code.ts
  function parseArgs (line 30) | function parseArgs(args: string[]): HighlightOptions {

FILE: lib/plugins/tag/iframe.ts
  function iframeTag (line 10) | function iframeTag(args: string[]) {

FILE: lib/plugins/tag/index.ts
  function postFindOneFactory (line 55) | function postFindOneFactory(ctx: Hexo) {
  function createPostFindOne (line 69) | function createPostFindOne(ctx: Hexo) {

FILE: lib/plugins/tag/link.ts
  function linkTag (line 12) | function linkTag(args: string[]) {

FILE: lib/theme/index.ts
  class Theme (line 11) | class Theme extends Box {
    method constructor (line 17) | constructor(ctx: Hexo, options?: any) {
    method getView (line 50) | getView(path: string): View {
    method setView (line 67) | setView(path: string, data: string): void {
    method removeView (line 76) | removeView(path: string): void {

FILE: lib/theme/processors/config.ts
  function process (line 5) | function process(file: _File) {

FILE: lib/theme/processors/i18n.ts
  function process (line 6) | function process(file: _File) {

FILE: lib/theme/processors/source.ts
  function process (line 5) | function process(file: _File) {

FILE: lib/theme/processors/view.ts
  function process (line 5) | function process(file: _File): Promise<void> {

FILE: lib/theme/view.ts
  class Options (line 23) | class Options {
  class View (line 28) | class View {
    method constructor (line 38) | constructor(path: string, data: string) {
    method render (line 48) | render(options: Options | NodeJSLikeCallback<any> = {}, callback?: Nod...
    method renderSync (line 73) | renderSync(options: Options = {}) {
    method _buildLocals (line 93) | _buildLocals(locals: Options) {
    method _bindHelpers (line 101) | _bindHelpers(locals) {
    method _resolveLayout (line 112) | _resolveLayout(name: string): View {
    method _precompile (line 124) | _precompile(): void {

FILE: lib/types.ts
  type NodeJSLikeCallback (line 35) | type NodeJSLikeCallback<R, E = any> = (err: E, result?: R) => void
  type RenderData (line 37) | interface RenderData {
  type TagSchema (line 50) | interface TagSchema {
  type DataSchema (line 61) | interface DataSchema {
  type CategorySchema (line 66) | interface CategorySchema {
  type PostCategorySchema (line 78) | interface PostCategorySchema {
  type PostTagSchema (line 84) | interface PostTagSchema {
  type PostAssetSchema (line 90) | interface PostAssetSchema {
  type BasePagePostSchema (line 100) | interface BasePagePostSchema {
  type PostSchema (line 224) | interface PostSchema extends BasePagePostSchema {
  type PageSchema (line 276) | interface PageSchema extends BasePagePostSchema {
  type AssetSchema (line 349) | interface AssetSchema {
  type CacheSchema (line 357) | interface CacheSchema {
  type BaseGeneratorReturn (line 364) | interface BaseGeneratorReturn {
  type SiteLocals (line 383) | interface SiteLocals {
  type LocalsType (line 407) | interface LocalsType {
  type FilterOptions (line 518) | interface FilterOptions {

FILE: test/benchmark.js
  function run_benchmark (line 62) | async function run_benchmark(name) {
  function cleanUp (line 152) | async function cleanUp() {
  function gitClone (line 156) | async function gitClone(repo, dir, depth = 1) {
  function init (line 160) | async function init() {
  function profiling (line 204) | async function profiling() {

FILE: test/scripts/console/clean.ts
  type OriginalParams (line 4) | type OriginalParams = Parameters<typeof cleanConsole>;
  type OriginalReturn (line 5) | type OriginalReturn = ReturnType<typeof cleanConsole>;

FILE: test/scripts/console/config.ts
  type OriginalParams (line 7) | type OriginalParams = Parameters<typeof configConsole>;
  type OriginalReturn (line 8) | type OriginalReturn = ReturnType<typeof configConsole>;
  function writeConfig (line 65) | async function writeConfig(...args) {

FILE: test/scripts/console/deploy.ts
  type OriginalParams (line 8) | type OriginalParams = Parameters<typeof deployConsole>;
  type OriginalReturn (line 9) | type OriginalReturn = ReturnType<typeof deployConsole>;

FILE: test/scripts/console/generate.ts
  type OriginalParams (line 9) | type OriginalParams = Parameters<typeof generateConsole>;
  type OriginalReturn (line 10) | type OriginalReturn = ReturnType<typeof generateConsole>;

FILE: test/scripts/console/list.ts
  type OriginalParams (line 5) | type OriginalParams = Parameters<typeof listConsole>;
  type OriginalReturn (line 6) | type OriginalReturn = ReturnType<typeof listConsole>;

FILE: test/scripts/console/list_categories.ts
  type OriginalParams (line 5) | type OriginalParams = Parameters<typeof listCategory>;
  type OriginalReturn (line 6) | type OriginalReturn = ReturnType<typeof listCategory>;

FILE: test/scripts/console/list_page.ts
  type OriginalParams (line 4) | type OriginalParams = Parameters<typeof listPage>;
  type OriginalReturn (line 5) | type OriginalReturn = ReturnType<typeof listPage>;

FILE: test/scripts/console/list_post.ts
  type OriginalParams (line 5) | type OriginalParams = Parameters<typeof listPost>;
  type OriginalReturn (line 6) | type OriginalReturn = ReturnType<typeof listPost>;

FILE: test/scripts/console/list_route.ts
  type OriginalParams (line 4) | type OriginalParams = Parameters<typeof listRoute>;
  type OriginalReturn (line 5) | type OriginalReturn = ReturnType<typeof listRoute>;

FILE: test/scripts/console/list_tags.ts
  type OriginalParams (line 5) | type OriginalParams = Parameters<typeof listTag>;
  type OriginalReturn (line 6) | type OriginalReturn = ReturnType<typeof listTag>;

FILE: test/scripts/console/migrate.ts
  type OriginalParams (line 4) | type OriginalParams = Parameters<typeof migrateConsole>;
  type OriginalReturn (line 5) | type OriginalReturn = ReturnType<typeof migrateConsole>;

FILE: test/scripts/console/new.ts
  type OriginalParams (line 8) | type OriginalParams = Parameters<typeof newConsole>;
  type OriginalReturn (line 9) | type OriginalReturn = ReturnType<typeof newConsole>;

FILE: test/scripts/console/publish.ts
  type OriginalParams (line 8) | type OriginalParams = Parameters<typeof publishConsole>;
  type OriginalReturn (line 9) | type OriginalReturn = ReturnType<typeof publishConsole>;

FILE: test/scripts/console/render.ts
  type OriginalParams (line 7) | type OriginalParams = Parameters<typeof renderConsole>;
  type OriginalReturn (line 8) | type OriginalReturn = ReturnType<typeof renderConsole>;

FILE: test/scripts/extend/renderer.ts
  function renderer (line 60) | function renderer(_data, _locals) {

FILE: test/scripts/extend/tag.ts
  type PostParams (line 14) | type PostParams = Parameters<ReturnType<typeof posts>['process']>
  type PostReturn (line 15) | type PostReturn = ReturnType<ReturnType<typeof posts>['process']>
  function newFile (line 27) | function newFile(options) {

FILE: test/scripts/filters/backtick_code_block.ts
  function highlight (line 21) | function highlight(code: string, options?) {
  function prism (line 27) | function prism(code: string, options?) {
  function createCodeWithOptions (line 33) | function createCodeWithOptions(options: string, source = code) {

FILE: test/scripts/filters/excerpt.ts
  type ExcerptFilterParams (line 3) | type ExcerptFilterParams = Parameters<typeof excerptFilter>;
  type ExcerptFilterReturn (line 4) | type ExcerptFilterReturn = ReturnType<typeof excerptFilter>;
  function _test (line 41) | function _test(more) {

FILE: test/scripts/filters/external_link.ts
  type ExternalLinkParams (line 7) | type ExternalLinkParams = Parameters<typeof externalLinkFilter>;
  type ExternalLinkReturn (line 8) | type ExternalLinkReturn = ReturnType<typeof externalLinkFilter>;
  type ExternalLinkPostParams (line 9) | type ExternalLinkPostParams = Parameters<typeof externalLinkPostFilter>;
  type ExternalLinkPostReturn (line 10) | type ExternalLinkPostReturn = ReturnType<typeof externalLinkPostFilter>;

FILE: test/scripts/filters/i18n_locals.ts
  type I18nLocalsFilterParams (line 3) | type I18nLocalsFilterParams = Parameters<typeof i18nLocalsFilter>;
  type I18nLocalsFilterReturn (line 4) | type I18nLocalsFilterReturn = ReturnType<typeof i18nLocalsFilter>;

FILE: test/scripts/filters/meta_generator.ts
  type hexoMetaGeneratorInjectParams (line 7) | type hexoMetaGeneratorInjectParams = Parameters<typeof hexoMetaGenerator...
  type hexoMetaGeneratorInjectReturn (line 8) | type hexoMetaGeneratorInjectReturn = ReturnType<typeof hexoMetaGenerator...

FILE: test/scripts/filters/new_post_path.ts
  type NewPostPathFilterParams (line 7) | type NewPostPathFilterParams = Parameters<typeof newPostPathFilter>;
  type NewPostPathFilterReturn (line 8) | type NewPostPathFilterReturn = ReturnType<typeof newPostPathFilter>;

FILE: test/scripts/filters/post_permalink.ts
  type PostPermalinkFilterParams (line 4) | type PostPermalinkFilterParams = Parameters<typeof postPermalinkFilter>;
  type PostPermalinkFilterReturn (line 5) | type PostPermalinkFilterReturn = ReturnType<typeof postPermalinkFilter>;

FILE: test/scripts/filters/render_post.ts
  type RenderPostFilterParams (line 4) | type RenderPostFilterParams = Parameters<typeof renderPostFilter>;
  type RenderPostFilterReturn (line 5) | type RenderPostFilterReturn = ReturnType<typeof renderPostFilter>;

FILE: test/scripts/filters/save_database.ts
  type SaveDatabaseFilterParams (line 5) | type SaveDatabaseFilterParams = Parameters<typeof saveDatabaseFilter>
  type SaveDatabaseFilterReturn (line 6) | type SaveDatabaseFilterReturn = ReturnType<typeof saveDatabaseFilter>

FILE: test/scripts/filters/titlecase.ts
  type titlecaseFilterParams (line 3) | type titlecaseFilterParams = Parameters<typeof titlecaseFilter>;
  type titlecaseFilterReturn (line 4) | type titlecaseFilterReturn = ReturnType<typeof titlecaseFilter>;

FILE: test/scripts/generators/asset.ts
  type AssetParams (line 9) | type AssetParams = Parameters<typeof assetGenerator>
  type AssetReturn (line 10) | type AssetReturn = ReturnType<typeof assetGenerator>

FILE: test/scripts/generators/page.ts
  type PageGeneratorParams (line 7) | type PageGeneratorParams = Parameters<typeof pageGenerator>;
  type PageGeneratorReturn (line 8) | type PageGeneratorReturn = ReturnType<typeof pageGenerator>;

FILE: test/scripts/generators/post.ts
  type PostGeneratorParams (line 7) | type PostGeneratorParams = Parameters<typeof postGenerator>;
  type PostGeneratorReturn (line 8) | type PostGeneratorReturn = ReturnType<typeof postGenerator>;

FILE: test/scripts/helpers/css.ts
  type CssHelperParams (line 4) | type CssHelperParams = Parameters<typeof cssHelper>;
  type CssHelperReturn (line 5) | type CssHelperReturn = ReturnType<typeof cssHelper>;
  function assertResult (line 16) | function assertResult(result, expected) {

FILE: test/scripts/helpers/date.ts
  type DateHelperParams (line 5) | type DateHelperParams = Parameters<typeof dateHelper>;
  type DateHelperReturn (line 6) | type DateHelperReturn = ReturnType<typeof dateHelper>;
  type TimeHelperParams (line 7) | type TimeHelperParams = Parameters<typeof timeHelper>;
  type TimeHelperReturn (line 8) | type TimeHelperReturn = ReturnType<typeof timeHelper>;
  type FullDateHelperParams (line 9) | type FullDateHelperParams = Parameters<typeof full_date>;
  type FullDateHelperReturn (line 10) | type FullDateHelperReturn = ReturnType<typeof full_date>;
  type TimeTagHelperParams (line 11) | type TimeTagHelperParams = Parameters<typeof time_tag>;
  type TimeTagHelperReturn (line 12) | type TimeTagHelperReturn = ReturnType<typeof time_tag>;
  type RelativeDateHelperParams (line 13) | type RelativeDateHelperParams = Parameters<typeof relative_date>;
  type RelativeDateHelperReturn (line 14) | type RelativeDateHelperReturn = ReturnType<typeof relative_date>;
  function result (line 194) | function result(date?, format?) {
  function check (line 200) | function check(date, format?) {

FILE: test/scripts/helpers/favicon_tag.ts
  type faviconTagParams (line 3) | type faviconTagParams = Parameters<typeof faviconTag>;
  type faviconTagReturn (line 4) | type faviconTagReturn = ReturnType<typeof faviconTag>;

FILE: test/scripts/helpers/feed_tag.ts
  type FeedTagParams (line 4) | type FeedTagParams = Parameters<typeof feedTag>;
  type FeedTagReturn (line 5) | type FeedTagReturn = ReturnType<typeof feedTag>;

FILE: test/scripts/helpers/full_url_for.ts
  type FullUrlForHelperParams (line 2) | type FullUrlForHelperParams = Parameters<typeof fullUrlForHelper>;
  type FullUrlForHelperReturn (line 3) | type FullUrlForHelperReturn = ReturnType<typeof fullUrlForHelper>;

FILE: test/scripts/helpers/gravatar.ts
  function md5 (line 5) | function md5(str) {

FILE: test/scripts/helpers/image_tag.ts
  type imageTagParams (line 3) | type imageTagParams = Parameters<typeof imageTag>;
  type imageTagReturn (line 4) | type imageTagReturn = ReturnType<typeof imageTag>;

FILE: test/scripts/helpers/js.ts
  type JsHelperParams (line 4) | type JsHelperParams = Parameters<typeof jsHelper>;
  type JsHelperReturn (line 5) | type JsHelperReturn = ReturnType<typeof jsHelper>;
  function assertResult (line 16) | function assertResult(result, expected) {

FILE: test/scripts/helpers/link_to.ts
  type LinkToHelperParams (line 3) | type LinkToHelperParams = Parameters<typeof linkToHelper>;
  type LinkToHelperReturn (line 4) | type LinkToHelperReturn = ReturnType<typeof linkToHelper>;

FILE: test/scripts/helpers/list_archives.ts
  type ListArchivesHelperParams (line 3) | type ListArchivesHelperParams = Parameters<typeof listArchivesHelper>;
  type ListArchivesHelperReturn (line 4) | type ListArchivesHelperReturn = ReturnType<typeof listArchivesHelper>;
  function resetLocals (line 17) | function resetLocals() {
  method transform (line 127) | transform(str) {
  method transform (line 144) | transform(str) {

FILE: test/scripts/helpers/list_categories.ts
  type ListCategoriesHelperParams (line 3) | type ListCategoriesHelperParams = Parameters<typeof listCategoriesHelper>;
  type ListCategoriesHelperReturn (line 4) | type ListCategoriesHelperReturn = ReturnType<typeof listCategoriesHelper>;
  method transform (line 217) | transform(name) {

FILE: test/scripts/helpers/list_posts.ts
  type ListPostsHelperParams (line 3) | type ListPostsHelperParams = Parameters<typeof listPostsHelper>;
  type ListPostsHelperReturn (line 4) | type ListPostsHelperReturn = ReturnType<typeof listPostsHelper>;
  method transform (line 110) | transform(str) {

FILE: test/scripts/helpers/list_tags.ts
  type ListTagsHelperParams (line 3) | type ListTagsHelperParams = Parameters<typeof listTagsHelper>;
  type ListTagsHelperReturn (line 4) | type ListTagsHelperReturn = ReturnType<typeof listTagsHelper>;
  method transform (line 170) | transform(name) {

FILE: test/scripts/helpers/mail_to.ts
  type MailToHelperParams (line 3) | type MailToHelperParams = Parameters<typeof mailToHelper>;
  type MailToHelperReturn (line 4) | type MailToHelperReturn = ReturnType<typeof mailToHelper>;

FILE: test/scripts/helpers/markdown.ts
  type MarkdownHelperParams (line 4) | type MarkdownHelperParams = Parameters<typeof markdownHelper>;
  type MarkdownHelperReturn (line 5) | type MarkdownHelperReturn = ReturnType<typeof markdownHelper>;

FILE: test/scripts/helpers/meta_generator.ts
  type MetaGeneratorHelperParams (line 5) | type MetaGeneratorHelperParams = Parameters<typeof metaGeneratorHelper>;
  type MetaGeneratorHelperReturn (line 6) | type MetaGeneratorHelperReturn = ReturnType<typeof metaGeneratorHelper>;

FILE: test/scripts/helpers/open_graph.ts
  function meta (line 13) | function meta(options) {
  method is_post (line 97) | is_post() {

FILE: test/scripts/helpers/paginator.ts
  type PaginatorHelperParams (line 4) | type PaginatorHelperParams = Parameters<typeof paginatorHelper>;
  type PaginatorHelperReturn (line 5) | type PaginatorHelperReturn = ReturnType<typeof paginatorHelper>;
  function link (line 21) | function link(i) {
  function checkResult (line 25) | function checkResult(result, data) {
  method transform (line 248) | transform(page) {

FILE: test/scripts/helpers/partial.ts
  type PartialHelperParams (line 9) | type PartialHelperParams = Parameters<ReturnType<typeof partialHelper>>;
  type PartialHelperReturn (line 10) | type PartialHelperReturn = ReturnType<ReturnType<typeof partialHelper>>;

FILE: test/scripts/helpers/search_form.ts
  type SearchFormHelperParams (line 2) | type SearchFormHelperParams = Parameters<typeof searchFormHelper>;
  type SearchFormHelperReturn (line 3) | type SearchFormHelperReturn = ReturnType<typeof searchFormHelper>;

FILE: test/scripts/helpers/tagcloud.ts
  type TagcloudHelperParams (line 6) | type TagcloudHelperParams = Parameters<typeof tagcloudHelper>;
  type TagcloudHelperReturn (line 7) | type TagcloudHelperReturn = ReturnType<typeof tagcloudHelper>;
  method transform (line 173) | transform(name) {

FILE: test/scripts/helpers/url_for.ts
  type UrlForHelperParams (line 3) | type UrlForHelperParams = Parameters<typeof urlForHelper>;
  type UrlForHelperReturn (line 4) | type UrlForHelperReturn = ReturnType<typeof urlForHelper>;

FILE: test/scripts/hexo/hexo.ts
  function checkStream (line 21) | async function checkStream(stream, expected) {
  function loadAssetGenerator (line 26) | function loadAssetGenerator() {
  function testLoad (line 140) | async function testLoad(path) {
  function testWatch (line 259) | async function testWatch(path) {
  function mapper (line 377) | function mapper(post) {
  function mapper (line 399) | function mapper(page) {

FILE: test/scripts/hexo/load_plugins.ts
  function validate (line 34) | function validate(path) {
  function createPackageFile (line 45) | function createPackageFile(name, path?) {
  function createPackageFileWithDevDeps (line 59) | function createPackageFileWithDevDeps(name) {

FILE: test/scripts/hexo/multi_config_path.ts
  function ConsoleReader (line 14) | function ConsoleReader() {

FILE: test/scripts/hexo/render.ts
  method toString (line 148) | toString(data) {
  method toString (line 280) | toString(data) {

FILE: test/scripts/hexo/router.ts
  function checkStream (line 15) | function checkStream(stream, expected) {
  function checksum (line 21) | function checksum(stream) {

FILE: test/scripts/models/category.ts
  function mapper (line 158) | function mapper(post) {
  function mapper (line 181) | function mapper(post) {
  function mapper (line 219) | function mapper(post) {

FILE: test/scripts/models/moment.ts
  function shouldThrowError (line 21) | function shouldThrowError(value) {

FILE: test/scripts/models/tag.ts
  function mapper (line 140) | function mapper(post) {
  function mapper (line 162) | function mapper(post) {
  function mapper (line 197) | function mapper(post) {

FILE: test/scripts/processors/asset.ts
  function newFile (line 23) | function newFile(options) {

FILE: test/scripts/processors/data.ts
  function newFile (line 18) | function newFile(options) {

FILE: test/scripts/processors/post.ts
  type PostParams (line 10) | type PostParams = Parameters<ReturnType<typeof posts>['process']>
  type PostReturn (line 11) | type PostReturn = ReturnType<ReturnType<typeof posts>['process']>
  function newFile (line 26) | function newFile(options) {

FILE: test/scripts/renderers/nunjucks.ts
  method toArray (line 60) | toArray() {

FILE: test/scripts/tags/asset_img.ts
  function assetImg (line 16) | function assetImg(args) {

FILE: test/scripts/tags/asset_link.ts
  function assetLink (line 16) | function assetLink(args) {

FILE: test/scripts/tags/asset_path.ts
  function assetPath (line 16) | function assetPath(args) {

FILE: test/scripts/tags/code.ts
  function code (line 17) | function code(args, content) {
  function highlight (line 21) | function highlight(code, options?) {
  function prism (line 27) | function prism(code, options?) {

FILE: test/scripts/theme/view.ts
  function newView (line 16) | function newView(path, data) {

FILE: test/scripts/theme_processors/config.ts
  type ConfigParams (line 9) | type ConfigParams = Parameters<typeof config['process']>
  type ConfigReturn (line 10) | type ConfigReturn = ReturnType<typeof config['process']>
  function newFile (line 17) | function newFile(options) {

FILE: test/scripts/theme_processors/i18n.ts
  type I18nParams (line 8) | type I18nParams = Parameters<typeof i18n['process']>
  type I18nReturn (line 9) | type I18nReturn = ReturnType<typeof i18n['process']>
  function newFile (line 16) | function newFile(options) {

FILE: test/scripts/theme_processors/source.ts
  type SourceParams (line 8) | type SourceParams = Parameters<typeof source['process']>
  type SourceReturn (line 9) | type SourceReturn = ReturnType<typeof source['process']>
  function newFile (line 17) | function newFile(options) {

FILE: test/scripts/theme_processors/view.ts
  type ViewParams (line 8) | type ViewParams = Parameters<typeof view['process']>
  type ViewReturn (line 9) | type ViewReturn = ReturnType<typeof view['process']>
  function newFile (line 19) | function newFile(options) {

FILE: test/util/stream.ts
  function readStream (line 3) | function readStream(stream): Promise<string> {
Condensed preview — 297 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (991K chars).
[
  {
    "path": ".editorconfig",
    "chars": 563,
    "preview": "root = true\n\n[*]\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\n\n[vcbuild.bat]\nend_of_line = crlf\n\n[*."
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 2450,
    "preview": "# Contributing\n\n## Style Guide\n\nWe use [ESLint] to maintain the code style. You can install linter plugins on your edito"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 22,
    "preview": "open_collective: hexo\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 4546,
    "preview": "name: Bug report\ndescription: Something isn't working as expected.\n# title: \"\"\n# labels: []\n# assignees: []\n\nbody:\n  - t"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 196,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Ask a Question, Help, Discuss\n    url: https://github.com/hexojs/he"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request-improvement.yml",
    "chars": 916,
    "preview": "name: Feature request / Improvement\ndescription: I have a feature request, suggestion, improvement etc...\n# title: \"\"\n# "
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 363,
    "preview": "<!--\nThank you for creating a pull request to contribute to Hexo code! Before you open the request please answer the fol"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 179,
    "preview": "version: 2\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: daily\n- package-ecosystem: githu"
  },
  {
    "path": ".github/workflows/benchmark.yml",
    "chars": 2676,
    "preview": "name: Benchmark\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - \"master\"\n    paths:\n      - \"lib/**\"\n      - \".g"
  },
  {
    "path": ".github/workflows/commenter.yml",
    "chars": 2149,
    "preview": "name: Commenter\n\non:\n  pull_request_target:\n\n  workflow_run:\n    workflows: [\"Benchmark\"]\n    types:\n      - completed\n\n"
  },
  {
    "path": ".github/workflows/dependencies-review.yml",
    "chars": 620,
    "preview": "name: 'Dependencies Review'\non:\n  pull_request:\n    paths:\n      - 'package.json'\n      - 'package-lock.json'\n  workflow"
  },
  {
    "path": ".github/workflows/linter.yml",
    "chars": 622,
    "preview": "name: Linter\n\non:\n  push:\n    branches:\n      - \"master\"\n    paths:\n      - \"lib/**\"\n      - \"test/**\"\n      - \".github/"
  },
  {
    "path": ".github/workflows/tester.yml",
    "chars": 1672,
    "preview": "name: Tester\n\non:\n  push:\n    branches:\n      - \"master\"\n    paths:\n      - \"lib/**\"\n      - \"test/**\"\n      - \"package."
  },
  {
    "path": ".gitignore",
    "chars": 128,
    "preview": ".DS_Store\nnode_modules/\ntmp/\n*.log\n.idea/\nyarn.lock\npackage-lock.json\npnpm-lock.yaml\n.nyc_output/\ncoverage/\n.tmp*\n.vscod"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 16,
    "preview": "npx lint-staged\n"
  },
  {
    "path": ".lintstagedrc.json",
    "chars": 55,
    "preview": "{\n  \"*.js\": \"eslint --fix\",\n  \"*.ts\": \"eslint --fix\"\n}\n"
  },
  {
    "path": ".mocharc.yml",
    "chars": 63,
    "preview": "color: true\nreporter: spec\nui: bdd\nfull-trace: true\nexit: true\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5221,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": "LICENSE",
    "chars": 1062,
    "preview": "Copyright (c) 2012-present Tommy Chen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of t"
  },
  {
    "path": "README.md",
    "chars": 3737,
    "preview": "<img src=\"https://raw.githubusercontent.com/hexojs/logo/master/hexo-logo-avatar.png\" alt=\"Hexo logo\" width=\"100\" height="
  },
  {
    "path": "bin/hexo",
    "chars": 59,
    "preview": "#!/usr/bin/env node\n\n'use strict';\n\nrequire('hexo-cli')();\n"
  },
  {
    "path": "eslint.config.js",
    "chars": 1039,
    "preview": "const config = require('eslint-config-hexo/ts');\nconst testConfig = require('eslint-config-hexo/test');\n\nmodule.exports "
  },
  {
    "path": "lib/box/file.ts",
    "chars": 1563,
    "preview": "import type Promise from 'bluebird';\nimport { readFile, readFileSync, stat, statSync, type ReadFileOptions } from 'hexo-"
  },
  {
    "path": "lib/box/index.ts",
    "chars": 8975,
    "preview": "import { join, sep } from 'path';\nimport BlueBirdPromise from 'bluebird';\nimport File from './file';\nimport { Pattern, c"
  },
  {
    "path": "lib/extend/console.ts",
    "chars": 2979,
    "preview": "import Promise from 'bluebird';\nimport abbrev from 'abbrev';\nimport type { NodeJSLikeCallback } from '../types';\nimport "
  },
  {
    "path": "lib/extend/deployer.ts",
    "chars": 1091,
    "preview": "import Promise from 'bluebird';\nimport type { NodeJSLikeCallback } from '../types';\nimport type Hexo from '../hexo';\n\nin"
  },
  {
    "path": "lib/extend/filter.ts",
    "chars": 2995,
    "preview": "import Promise from 'bluebird';\nimport { FilterOptions } from '../types';\n\nconst typeAlias = {\n  pre: 'before_post_rende"
  },
  {
    "path": "lib/extend/generator.ts",
    "chars": 1343,
    "preview": "import Promise from 'bluebird';\nimport type { BaseGeneratorReturn, NodeJSLikeCallback, SiteLocals } from '../types';\n\nty"
  },
  {
    "path": "lib/extend/helper.ts",
    "chars": 2451,
    "preview": "import Hexo from '../hexo';\nimport { PageSchema } from '../types';\nimport * as hutil from 'hexo-util';\n\ninterface HexoCo"
  },
  {
    "path": "lib/extend/index.ts",
    "chars": 543,
    "preview": "export { default as Console } from './console';\nexport { default as Deployer } from './deployer';\nexport { default as Fi"
  },
  {
    "path": "lib/extend/injector.ts",
    "chars": 3461,
    "preview": "import { Cache } from 'hexo-util';\n\ntype Entry = 'head_begin' | 'head_end' | 'body_begin' | 'body_end';\n\ntype Store = {\n"
  },
  {
    "path": "lib/extend/migrator.ts",
    "chars": 917,
    "preview": "import Promise from 'bluebird';\nimport type { NodeJSLikeCallback } from '../types';\nimport type Hexo from '../hexo';\n\nin"
  },
  {
    "path": "lib/extend/processor.ts",
    "chars": 1140,
    "preview": "import Promise from 'bluebird';\nimport { Pattern } from 'hexo-util';\nimport type File from '../box/file';\n\ninterface Sto"
  },
  {
    "path": "lib/extend/renderer.ts",
    "chars": 3244,
    "preview": "import { extname } from 'path';\nimport Promise from 'bluebird';\nimport type { NodeJSLikeCallback } from '../types';\n\ncon"
  },
  {
    "path": "lib/extend/syntax_highlight.ts",
    "chars": 1406,
    "preview": "import type Hexo from '../hexo';\n\nexport interface HighlightOptions {\n  lang: string | undefined,\n  caption: string | un"
  },
  {
    "path": "lib/extend/tag.ts",
    "chars": 8196,
    "preview": "import { stripIndent } from 'hexo-util';\nimport { cyan, magenta, red, bold } from 'picocolors';\nimport { Environment } f"
  },
  {
    "path": "lib/hexo/default_config.ts",
    "chars": 2011,
    "preview": "export = {\n  // Site\n  title: 'Hexo',\n  subtitle: '',\n  description: '',\n  author: 'John Doe',\n  language: 'en',\n  timez"
  },
  {
    "path": "lib/hexo/index.ts",
    "chars": 22008,
    "preview": "import Promise from 'bluebird';\nimport { sep, join, dirname } from 'path';\nimport tildify from 'tildify';\nimport Databas"
  },
  {
    "path": "lib/hexo/load_config.ts",
    "chars": 2629,
    "preview": "import { sep, resolve, join, parse, basename, extname } from 'path';\nimport tildify from 'tildify';\nimport Theme from '."
  },
  {
    "path": "lib/hexo/load_database.ts",
    "chars": 556,
    "preview": "import { exists, unlink } from 'hexo-fs';\nimport Promise from 'bluebird';\nimport type Hexo from './index';\n\nexport = (ct"
  },
  {
    "path": "lib/hexo/load_plugins.ts",
    "chars": 2890,
    "preview": "import { join } from 'path';\nimport { exists, readFile, listDir } from 'hexo-fs';\nimport Promise from 'bluebird';\nimport"
  },
  {
    "path": "lib/hexo/load_theme_config.ts",
    "chars": 1446,
    "preview": "import { join, parse, basename, extname } from 'path';\nimport tildify from 'tildify';\nimport { exists, readdir } from 'h"
  },
  {
    "path": "lib/hexo/locals.ts",
    "chars": 1392,
    "preview": "import { Cache } from 'hexo-util';\n\nclass Locals {\n  public cache: InstanceType<typeof Cache>;\n  public getters: Record<"
  },
  {
    "path": "lib/hexo/multi_config_path.ts",
    "chars": 2208,
    "preview": "import { isAbsolute, resolve, join, extname } from 'path';\nimport { existsSync, readFileSync, writeFileSync } from 'hexo"
  },
  {
    "path": "lib/hexo/post.ts",
    "chars": 19585,
    "preview": "import assert from 'assert';\nimport moment from 'moment';\nimport Promise from 'bluebird';\nimport { join, extname, basena"
  },
  {
    "path": "lib/hexo/register_models.ts",
    "chars": 290,
    "preview": "import * as models from '../models';\nimport type Hexo from './index';\n\nexport = (ctx: Hexo): void => {\n  const db = ctx."
  },
  {
    "path": "lib/hexo/render.ts",
    "chars": 4044,
    "preview": "import { extname } from 'path';\nimport Promise from 'bluebird';\nimport { readFile, readFileSync } from 'hexo-fs';\nimport"
  },
  {
    "path": "lib/hexo/router.ts",
    "chars": 3996,
    "preview": "import { EventEmitter } from 'events';\nimport Promise from 'bluebird';\nimport Stream from 'stream';\nconst { Readable } ="
  },
  {
    "path": "lib/hexo/scaffold.ts",
    "chars": 2070,
    "preview": "import { extname, join } from 'path';\nimport { exists, listDir, readFile, unlink, writeFile } from 'hexo-fs';\nimport typ"
  },
  {
    "path": "lib/hexo/source.ts",
    "chars": 222,
    "preview": "import Box from '../box';\nimport type Hexo from './index';\n\nclass Source extends Box {\n  constructor(ctx: Hexo) {\n    su"
  },
  {
    "path": "lib/hexo/update_package.ts",
    "chars": 822,
    "preview": "import { join } from 'path';\nimport { writeFile, exists, readFile } from 'hexo-fs';\nimport type Hexo from './index';\nimp"
  },
  {
    "path": "lib/hexo/validate_config.ts",
    "chars": 865,
    "preview": "import assert from 'assert';\nimport type Hexo from './index';\n\nexport = (ctx: Hexo): void => {\n  const { config, log } ="
  },
  {
    "path": "lib/models/asset.ts",
    "chars": 514,
    "preview": "import warehouse from 'warehouse';\nimport { join } from 'path';\nimport type Hexo from '../hexo';\nimport type { AssetSche"
  },
  {
    "path": "lib/models/binary_relation_index.ts",
    "chars": 2629,
    "preview": "import type Hexo from '../hexo';\n\ntype BinaryRelationType<K extends PropertyKey, V extends PropertyKey> = {\n  [key in K]"
  },
  {
    "path": "lib/models/cache.ts",
    "chars": 1953,
    "preview": "import warehouse from 'warehouse';\nimport Promise from 'bluebird';\nimport type Hexo from '../hexo';\nimport type fs from "
  },
  {
    "path": "lib/models/category.ts",
    "chars": 2207,
    "preview": "import warehouse from 'warehouse';\nimport { slugize, full_url_for } from 'hexo-util';\nimport type Hexo from '../hexo';\ni"
  },
  {
    "path": "lib/models/data.ts",
    "chars": 268,
    "preview": "import warehouse from 'warehouse';\nimport type Hexo from '../hexo';\nimport { DataSchema } from '../types';\n\nexport = (_c"
  },
  {
    "path": "lib/models/index.ts",
    "chars": 465,
    "preview": "export { default as Asset } from './asset';\nexport { default as Cache } from './cache';\nexport { default as Category } f"
  },
  {
    "path": "lib/models/page.ts",
    "chars": 1038,
    "preview": "import warehouse from 'warehouse';\nimport { join } from 'path';\nimport Moment from './types/moment';\nimport moment from "
  },
  {
    "path": "lib/models/post.ts",
    "chars": 7358,
    "preview": "import warehouse from 'warehouse';\nimport moment from 'moment';\nimport { extname, join, sep } from 'path';\nimport Promis"
  },
  {
    "path": "lib/models/post_asset.ts",
    "chars": 1066,
    "preview": "import warehouse from 'warehouse';\nimport { join, posix } from 'path';\nimport type Hexo from '../hexo';\nimport type { Po"
  },
  {
    "path": "lib/models/post_category.ts",
    "chars": 741,
    "preview": "import warehouse from 'warehouse';\nimport type Hexo from '../hexo';\nimport { PostCategorySchema } from '../types';\n\nexpo"
  },
  {
    "path": "lib/models/post_tag.ts",
    "chars": 681,
    "preview": "import warehouse from 'warehouse';\nimport type Hexo from '../hexo';\nimport { PostTagSchema } from '../types';\n\nexport = "
  },
  {
    "path": "lib/models/tag.ts",
    "chars": 1931,
    "preview": "import warehouse from 'warehouse';\nimport { slugize, full_url_for } from 'hexo-util';\nconst { hasOwnProperty: hasOwn } ="
  },
  {
    "path": "lib/models/types/moment.ts",
    "chars": 2006,
    "preview": "import warehouse from 'warehouse';\nimport { moment } from '../../plugins/helper/date';\n\n// It'll pollute the moment modu"
  },
  {
    "path": "lib/plugins/console/clean.ts",
    "chars": 869,
    "preview": "import Promise from 'bluebird';\nimport { exists, unlink, rmdir } from 'hexo-fs';\nimport type Hexo from '../../hexo';\n\nfu"
  },
  {
    "path": "lib/plugins/console/config.ts",
    "chars": 1884,
    "preview": "import yaml from 'js-yaml';\nimport { exists, writeFile } from 'hexo-fs';\nimport { extname } from 'path';\nimport Promise "
  },
  {
    "path": "lib/plugins/console/deploy.ts",
    "chars": 1615,
    "preview": "import { exists } from 'hexo-fs';\nimport { underline, magenta } from 'picocolors';\nimport type Hexo from '../../hexo';\ni"
  },
  {
    "path": "lib/plugins/console/generate.ts",
    "chars": 6673,
    "preview": "import { exists, writeFile, unlink, stat, mkdirs } from 'hexo-fs';\nimport { join } from 'path';\nimport Promise from 'blu"
  },
  {
    "path": "lib/plugins/console/index.ts",
    "chars": 3170,
    "preview": "import type Hexo from '../../hexo';\n\nexport = function(ctx: Hexo) {\n  const { console } = ctx.extend;\n\n  console.registe"
  },
  {
    "path": "lib/plugins/console/list/category.ts",
    "chars": 838,
    "preview": "import { underline } from 'picocolors';\nimport table from 'fast-text-table';\nimport { stringLength } from './common';\nim"
  },
  {
    "path": "lib/plugins/console/list/common.ts",
    "chars": 303,
    "preview": "import strip from 'strip-ansi';\n\nexport function stringLength(str: string): number {\n  str = strip(str);\n\n  const len = "
  },
  {
    "path": "lib/plugins/console/list/index.ts",
    "chars": 732,
    "preview": "import abbrev from 'abbrev';\nimport page from './page';\nimport post from './post';\nimport route from './route';\nimport t"
  },
  {
    "path": "lib/plugins/console/list/page.ts",
    "chars": 874,
    "preview": "import { magenta, underline, gray } from 'picocolors';\nimport table from 'fast-text-table';\nimport { stringLength } from"
  },
  {
    "path": "lib/plugins/console/list/post.ts",
    "chars": 1168,
    "preview": "import { gray, magenta, underline } from 'picocolors';\nimport table from 'fast-text-table';\nimport { stringLength } from"
  },
  {
    "path": "lib/plugins/console/list/route.ts",
    "chars": 1041,
    "preview": "import archy from 'fast-archy';\nimport type Hexo from '../../../hexo';\n\nfunction listRoute(this: Hexo): void {\n  const r"
  },
  {
    "path": "lib/plugins/console/list/tag.ts",
    "chars": 821,
    "preview": "import { magenta, underline } from 'picocolors';\nimport table from 'fast-text-table';\nimport { stringLength } from './co"
  },
  {
    "path": "lib/plugins/console/migrate.ts",
    "chars": 875,
    "preview": "import { underline, magenta } from 'picocolors';\nimport type Hexo from '../../hexo';\n\ninterface MigrateArgs {\n  _: strin"
  },
  {
    "path": "lib/plugins/console/new.ts",
    "chars": 1389,
    "preview": "import tildify from 'tildify';\nimport { magenta } from 'picocolors';\nimport { basename } from 'path';\nimport Hexo from '"
  },
  {
    "path": "lib/plugins/console/publish.ts",
    "chars": 712,
    "preview": "import tildify from 'tildify';\nimport { magenta } from 'picocolors';\nimport type Hexo from '../../hexo';\nimport type Pro"
  },
  {
    "path": "lib/plugins/console/render.ts",
    "chars": 1369,
    "preview": "import { resolve } from 'path';\nimport tildify from 'tildify';\nimport prettyHrtime from 'pretty-hrtime';\nimport { writeF"
  },
  {
    "path": "lib/plugins/filter/after_post_render/excerpt.ts",
    "chars": 603,
    "preview": "import type { RenderData } from '../../../types';\n\nconst rExcerpt = /<!-- ?more ?-->/i;\n\nfunction excerptFilter(data: Re"
  },
  {
    "path": "lib/plugins/filter/after_post_render/external_link.ts",
    "chars": 1166,
    "preview": "import { isExternalLink } from 'hexo-util';\nimport type Hexo from '../../../hexo';\nimport type { RenderData } from '../."
  },
  {
    "path": "lib/plugins/filter/after_post_render/index.ts",
    "chars": 233,
    "preview": "import type Hexo from '../../../hexo';\n\nexport = (ctx: Hexo) => {\n  const { filter } = ctx.extend;\n\n  filter.register('a"
  },
  {
    "path": "lib/plugins/filter/after_render/external_link.ts",
    "chars": 1423,
    "preview": "import { isExternalLink } from 'hexo-util';\nimport type Hexo from '../../../hexo';\n\nlet EXTERNAL_LINK_SITE_ENABLED = tru"
  },
  {
    "path": "lib/plugins/filter/after_render/index.ts",
    "chars": 240,
    "preview": "import type Hexo from '../../../hexo';\n\nexport = (ctx: Hexo) => {\n  const { filter } = ctx.extend;\n\n  filter.register('a"
  },
  {
    "path": "lib/plugins/filter/after_render/meta_generator.ts",
    "chars": 623,
    "preview": "import type Hexo from '../../../hexo';\n\nlet NEED_INJECT = true;\nlet HAS_CHECKED = false;\nlet META_GENERATOR_TAG;\n\nfuncti"
  },
  {
    "path": "lib/plugins/filter/before_exit/index.ts",
    "chars": 165,
    "preview": "import type Hexo from '../../../hexo';\n\nexport = (ctx: Hexo) => {\n  const { filter } = ctx.extend;\n\n  filter.register('b"
  },
  {
    "path": "lib/plugins/filter/before_exit/save_database.ts",
    "chars": 266,
    "preview": "import type Hexo from '../../../hexo';\n\nfunction saveDatabaseFilter(this: Hexo): Promise<void> {\n  if (!this.env.init ||"
  },
  {
    "path": "lib/plugins/filter/before_generate/index.ts",
    "chars": 167,
    "preview": "import type Hexo from '../../../hexo';\n\nexport = (ctx: Hexo) => {\n  const { filter } = ctx.extend;\n\n  filter.register('b"
  },
  {
    "path": "lib/plugins/filter/before_generate/render_post.ts",
    "chars": 613,
    "preview": "import Promise from 'bluebird';\nimport type Hexo from '../../../hexo';\nimport type Model from 'warehouse/dist/model';\n\nf"
  },
  {
    "path": "lib/plugins/filter/before_post_render/backtick_code_block.ts",
    "chars": 5902,
    "preview": "import type { HighlightOptions } from '../../../extend/syntax_highlight';\nimport type Hexo from '../../../hexo';\nimport "
  },
  {
    "path": "lib/plugins/filter/before_post_render/index.ts",
    "chars": 248,
    "preview": "import type Hexo from '../../../hexo';\n\nexport = (ctx: Hexo) => {\n  const { filter } = ctx.extend;\n\n  filter.register('b"
  },
  {
    "path": "lib/plugins/filter/before_post_render/titlecase.ts",
    "chars": 350,
    "preview": "import type { RenderData } from '../../../types';\n\nlet titlecase;\n\nfunction titlecaseFilter(data: RenderData): void {\n  "
  },
  {
    "path": "lib/plugins/filter/index.ts",
    "chars": 451,
    "preview": "import type Hexo from '../../hexo';\n\nexport = (ctx: Hexo) => {\n  const { filter } = ctx.extend;\n\n  require('./after_rend"
  },
  {
    "path": "lib/plugins/filter/new_post_path.ts",
    "chars": 2499,
    "preview": "import { join, extname } from 'path';\nimport moment from 'moment';\nimport Promise from 'bluebird';\nimport { createSha1Ha"
  },
  {
    "path": "lib/plugins/filter/post_permalink.ts",
    "chars": 2359,
    "preview": "import { createSha1Hash, Permalink, slugize } from 'hexo-util';\nimport { basename } from 'path';\nimport type Hexo from '"
  },
  {
    "path": "lib/plugins/filter/template_locals/i18n.ts",
    "chars": 1120,
    "preview": "import { Pattern } from 'hexo-util';\nimport type Hexo from '../../../hexo';\nimport type { LocalsType } from '../../../ty"
  },
  {
    "path": "lib/plugins/filter/template_locals/index.ts",
    "chars": 160,
    "preview": "import type Hexo from '../../../hexo';\n\nexport = (ctx: Hexo) => {\n  const { filter } = ctx.extend;\n\n  filter.register('t"
  },
  {
    "path": "lib/plugins/generator/asset.ts",
    "chars": 1657,
    "preview": "import { exists, createReadStream } from 'hexo-fs';\nimport Promise from 'bluebird';\nimport { extname } from 'path';\nimpo"
  },
  {
    "path": "lib/plugins/generator/index.ts",
    "chars": 252,
    "preview": "import type Hexo from '../../hexo';\n\nexport = (ctx: Hexo) => {\n  const { generator } = ctx.extend;\n\n  generator.register"
  },
  {
    "path": "lib/plugins/generator/page.ts",
    "chars": 918,
    "preview": "import type { BaseGeneratorReturn, PageSchema, SiteLocals } from '../../types';\nimport type Document from 'warehouse/dis"
  },
  {
    "path": "lib/plugins/generator/post.ts",
    "chars": 1083,
    "preview": "import type { BaseGeneratorReturn, PostSchema, SiteLocals } from '../../types';\nimport type Document from 'warehouse/dis"
  },
  {
    "path": "lib/plugins/helper/css.ts",
    "chars": 983,
    "preview": "import { htmlTag, url_for } from 'hexo-util';\nimport moize from 'moize';\nimport type { LocalsType } from '../../types';\n"
  },
  {
    "path": "lib/plugins/helper/date.ts",
    "chars": 3084,
    "preview": "import moment from 'moment-timezone';\nconst { isMoment } = moment;\nimport moize from 'moize';\nimport type { LocalsType }"
  },
  {
    "path": "lib/plugins/helper/debug.ts",
    "chars": 335,
    "preview": "import { inspect } from 'util';\n\n// this format object as string, resolves circular reference\nfunction inspectObject(obj"
  },
  {
    "path": "lib/plugins/helper/favicon_tag.ts",
    "chars": 249,
    "preview": "import { url_for } from 'hexo-util';\nimport type { LocalsType } from '../../types';\n\nfunction faviconTagHelper(this: Loc"
  },
  {
    "path": "lib/plugins/helper/feed_tag.ts",
    "chars": 1656,
    "preview": "import { url_for } from 'hexo-util';\nimport moize from 'moize';\nimport type { LocalsType } from '../../types';\n\nconst fe"
  },
  {
    "path": "lib/plugins/helper/format.ts",
    "chars": 368,
    "preview": "import { stripHTML, wordWrap, truncate, escapeHTML } from 'hexo-util';\nimport titlecase from 'titlecase';\nexport {stripH"
  },
  {
    "path": "lib/plugins/helper/fragment_cache.ts",
    "chars": 405,
    "preview": "import { Cache } from 'hexo-util';\nimport type Hexo from '../../hexo';\n\nexport = (ctx: Hexo) => {\n  const cache = new Ca"
  },
  {
    "path": "lib/plugins/helper/full_url_for.ts",
    "chars": 186,
    "preview": "\nimport { full_url_for } from 'hexo-util';\nimport type { LocalsType } from '../../types';\n\nexport = function(this: Local"
  },
  {
    "path": "lib/plugins/helper/gravatar.ts",
    "chars": 57,
    "preview": "import { gravatar } from 'hexo-util';\nexport = gravatar;\n"
  },
  {
    "path": "lib/plugins/helper/image_tag.ts",
    "chars": 609,
    "preview": "import { htmlTag, url_for } from 'hexo-util';\nimport type { LocalsType } from '../../types';\n\ninterface Options {\n  src?"
  },
  {
    "path": "lib/plugins/helper/index.ts",
    "chars": 2948,
    "preview": "import type Hexo from '../../hexo';\n\nexport = (ctx: Hexo) => {\n  const { helper } = ctx.extend;\n\n  const date = require("
  },
  {
    "path": "lib/plugins/helper/is.ts",
    "chars": 1954,
    "preview": "import type { LocalsType } from '../../types';\n\nfunction isCurrentHelper(this: LocalsType, path = '/', strict: boolean) "
  },
  {
    "path": "lib/plugins/helper/js.ts",
    "chars": 931,
    "preview": "import { htmlTag, url_for } from 'hexo-util';\nimport moize from 'moize';\nimport type { LocalsType } from '../../types';\n"
  },
  {
    "path": "lib/plugins/helper/link_to.ts",
    "chars": 1073,
    "preview": "import { htmlTag, url_for } from 'hexo-util';\nimport type { LocalsType } from '../../types';\n\ninterface Options {\n  id?:"
  },
  {
    "path": "lib/plugins/helper/list_archives.ts",
    "chars": 3430,
    "preview": "import type Query from 'warehouse/dist/query';\nimport type { LocalsType, PostSchema } from '../../types';\nimport { toMom"
  },
  {
    "path": "lib/plugins/helper/list_categories.ts",
    "chars": 3898,
    "preview": "import { url_for } from 'hexo-util';\nimport type { CategorySchema, LocalsType } from '../../types';\nimport type Query fr"
  },
  {
    "path": "lib/plugins/helper/list_posts.ts",
    "chars": 1825,
    "preview": "import { url_for } from 'hexo-util';\nimport type { LocalsType, PostSchema } from '../../types';\nimport type Query from '"
  },
  {
    "path": "lib/plugins/helper/list_tags.ts",
    "chars": 3820,
    "preview": "import { url_for, escapeHTML } from 'hexo-util';\nimport moize from 'moize';\nimport type { LocalsType, TagSchema } from '"
  },
  {
    "path": "lib/plugins/helper/mail_to.ts",
    "chars": 1229,
    "preview": "import { htmlTag } from 'hexo-util';\nimport moize from 'moize';\n\ninterface Options {\n  href?: string;\n  title?: string;\n"
  },
  {
    "path": "lib/plugins/helper/markdown.ts",
    "chars": 198,
    "preview": "import type { LocalsType } from '../../types';\n\nfunction markdownHelper(this: LocalsType, text: string, options?: any) {"
  },
  {
    "path": "lib/plugins/helper/meta_generator.ts",
    "chars": 201,
    "preview": "import type { LocalsType } from '../../types';\n\nfunction metaGeneratorHelper(this: LocalsType) {\n  return `<meta name=\"g"
  },
  {
    "path": "lib/plugins/helper/number_format.ts",
    "chars": 1398,
    "preview": "interface Options {\n  delimiter?: string;\n  separator?: string;\n  precision?: number | false;\n}\n\nfunction numberFormatHe"
  },
  {
    "path": "lib/plugins/helper/open_graph.ts",
    "chars": 5599,
    "preview": "import { isMoment, isDate, Moment } from 'moment';\nimport { encodeURL, prettyUrls, stripHTML, escapeHTML } from 'hexo-ut"
  },
  {
    "path": "lib/plugins/helper/paginator.ts",
    "chars": 4461,
    "preview": "import { htmlTag, url_for } from 'hexo-util';\nimport type { LocalsType } from '../../types';\n\ninterface Options {\n  base"
  },
  {
    "path": "lib/plugins/helper/partial.ts",
    "chars": 1155,
    "preview": "import { dirname, join } from 'path';\nimport type Hexo from '../../hexo';\nimport type { LocalsType } from '../../types';"
  },
  {
    "path": "lib/plugins/helper/relative_url.ts",
    "chars": 124,
    "preview": "import { relative_url } from 'hexo-util';\n\nexport = function(from: string, to: string) {\n  return relative_url(from, to)"
  },
  {
    "path": "lib/plugins/helper/render.ts",
    "chars": 202,
    "preview": "import type Hexo from '../../hexo';\n\nexport = (ctx: Hexo) => function render(text: string, engine: string, options:objec"
  },
  {
    "path": "lib/plugins/helper/search_form.ts",
    "chars": 799,
    "preview": "import moize from 'moize';\nimport type { LocalsType } from '../../types';\n\ninterface Options {\n  class?: string;\n  text?"
  },
  {
    "path": "lib/plugins/helper/tagcloud.ts",
    "chars": 3346,
    "preview": "import { Color, url_for } from 'hexo-util';\nimport moize from 'moize';\nimport type { LocalsType, TagSchema } from '../.."
  },
  {
    "path": "lib/plugins/helper/toc.ts",
    "chars": 4038,
    "preview": "import { tocObj, escapeHTML } from 'hexo-util';\n\ninterface Options {\n  min_depth?: number;\n  max_depth?: number;\n  max_i"
  },
  {
    "path": "lib/plugins/helper/url_for.ts",
    "chars": 250,
    "preview": "import { url_for } from 'hexo-util';\nimport type { LocalsType } from '../../types';\n\ninterface Options {\n  relative?: bo"
  },
  {
    "path": "lib/plugins/highlight/highlight.ts",
    "chars": 1745,
    "preview": "import type { HighlightOptions } from '../../extend/syntax_highlight';\nimport type Hexo from '../../hexo';\n\n// Lazy requ"
  },
  {
    "path": "lib/plugins/highlight/index.ts",
    "chars": 226,
    "preview": "import type Hexo from '../../hexo';\n\nmodule.exports = (ctx: Hexo) => {\n  const { highlight } = ctx.extend;\n\n  highlight."
  },
  {
    "path": "lib/plugins/highlight/prism.ts",
    "chars": 1435,
    "preview": "import type { HighlightOptions } from '../../extend/syntax_highlight';\nimport type Hexo from '../../hexo';\n\n// Lazy requ"
  },
  {
    "path": "lib/plugins/injector/index.ts",
    "chars": 165,
    "preview": "import type Hexo from '../../hexo';\n\nexport = (ctx: Hexo) => {\n  // eslint-disable-next-line @typescript-eslint/no-unuse"
  },
  {
    "path": "lib/plugins/processor/asset.ts",
    "chars": 3220,
    "preview": "import { adjustDateForTimezone, toDate, isExcludedFile, isMatch } from './common';\nimport Promise from 'bluebird';\nimpor"
  },
  {
    "path": "lib/plugins/processor/common.ts",
    "chars": 1370,
    "preview": "import moment from 'moment-timezone';\nimport micromatch from 'micromatch';\n\nconst DURATION_MINUTE = 1000 * 60;\n\nfunction"
  },
  {
    "path": "lib/plugins/processor/data.ts",
    "chars": 774,
    "preview": "import { Pattern } from 'hexo-util';\nimport { extname } from 'path';\nimport type Hexo from '../../hexo';\nimport type { _"
  },
  {
    "path": "lib/plugins/processor/index.ts",
    "chars": 298,
    "preview": "import type Hexo from '../../hexo';\n\nexport = (ctx: Hexo) => {\n  const { processor } = ctx.extend;\n\n  function register("
  },
  {
    "path": "lib/plugins/processor/post.ts",
    "chars": 9614,
    "preview": "import { toDate, adjustDateForTimezone, isExcludedFile, isTmpFile, isHiddenFile, isMatch } from './common';\nimport Promi"
  },
  {
    "path": "lib/plugins/renderer/index.ts",
    "chars": 675,
    "preview": "import type Hexo from '../../hexo';\n\nexport = (ctx: Hexo) => {\n  const { renderer } = ctx.extend;\n\n  const plain = requi"
  },
  {
    "path": "lib/plugins/renderer/json.ts",
    "chars": 177,
    "preview": "import type { StoreFunctionData } from '../../extend/renderer';\n\nfunction jsonRenderer(data: StoreFunctionData): any {\n "
  },
  {
    "path": "lib/plugins/renderer/nunjucks.ts",
    "chars": 1877,
    "preview": "import nunjucks, { Environment } from 'nunjucks';\nimport { readFileSync } from 'hexo-fs';\nimport { dirname } from 'path'"
  },
  {
    "path": "lib/plugins/renderer/plain.ts",
    "chars": 170,
    "preview": "import type { StoreFunctionData } from '../../extend/renderer';\n\nfunction plainRenderer(data: StoreFunctionData): string"
  },
  {
    "path": "lib/plugins/renderer/yaml.ts",
    "chars": 643,
    "preview": "import yaml from 'js-yaml';\nimport { escape } from 'hexo-front-matter';\nimport logger from 'hexo-log';\nimport type { Sto"
  },
  {
    "path": "lib/plugins/tag/asset_img.ts",
    "chars": 712,
    "preview": "import img from './img';\nimport { encodeURL } from 'hexo-util';\nimport type Hexo from '../../hexo';\n\n/**\n * Asset image "
  },
  {
    "path": "lib/plugins/tag/asset_link.ts",
    "chars": 853,
    "preview": "import { url_for, escapeHTML } from 'hexo-util';\nimport type Hexo from '../../hexo';\n\n/**\n * Asset link tag\n *\n * Syntax"
  },
  {
    "path": "lib/plugins/tag/asset_path.ts",
    "chars": 476,
    "preview": "import { url_for } from 'hexo-util';\nimport type Hexo from '../../hexo';\n\n/**\n * Asset path tag\n *\n * Syntax:\n *   {% as"
  },
  {
    "path": "lib/plugins/tag/blockquote.ts",
    "chars": 1773,
    "preview": "// Based on: https://raw.github.com/imathis/octopress/master/plugins/blockquote.rb\n\nimport titlecase from 'titlecase';\ni"
  },
  {
    "path": "lib/plugins/tag/code.ts",
    "chars": 4471,
    "preview": "// Based on: https://raw.github.com/imathis/octopress/master/plugins/code_block.rb\n\nimport { escapeHTML, htmlTag } from "
  },
  {
    "path": "lib/plugins/tag/full_url_for.ts",
    "chars": 373,
    "preview": "import { full_url_for, htmlTag } from 'hexo-util';\nimport type Hexo from '../../hexo';\n\n/**\n * Full url for tag\n *\n * Sy"
  },
  {
    "path": "lib/plugins/tag/iframe.ts",
    "chars": 495,
    "preview": "import { htmlTag } from 'hexo-util';\n\n/**\n* Iframe tag\n*\n* Syntax:\n*   {% iframe url [width] [height] %}\n*/\n\nfunction if"
  },
  {
    "path": "lib/plugins/tag/img.ts",
    "chars": 1670,
    "preview": "import { htmlTag, url_for } from 'hexo-util';\nimport type Hexo from '../../hexo';\n\nconst rUrl = /((([A-Za-z]{3,9}:(?:\\/\\"
  },
  {
    "path": "lib/plugins/tag/include_code.ts",
    "chars": 2020,
    "preview": "import { basename, extname, join } from 'path';\nimport { htmlTag, url_for } from 'hexo-util';\nimport type Hexo from '../"
  },
  {
    "path": "lib/plugins/tag/index.ts",
    "chars": 1873,
    "preview": "import moize from 'moize';\nimport type Hexo from '../../hexo';\n\nexport default (ctx: Hexo) => {\n  const { tag } = ctx.ex"
  },
  {
    "path": "lib/plugins/tag/link.ts",
    "chars": 1150,
    "preview": "import { htmlTag } from 'hexo-util';\n\nconst rUrl = /((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[-;:&=+$,\\w]+@)?[A-Za-z0-9.-]+|(?:www."
  },
  {
    "path": "lib/plugins/tag/post_link.ts",
    "chars": 1319,
    "preview": "import { url_for, escapeHTML } from 'hexo-util';\nimport { postFindOneFactory } from './';\nimport type Hexo from '../../h"
  },
  {
    "path": "lib/plugins/tag/post_path.ts",
    "chars": 520,
    "preview": "import { url_for } from 'hexo-util';\nimport { postFindOneFactory } from './';\nimport type Hexo from '../../hexo';\n\n/**\n "
  },
  {
    "path": "lib/plugins/tag/pullquote.ts",
    "chars": 405,
    "preview": "import type Hexo from '../../hexo';\n\n/**\n* Pullquote tag\n*\n* Syntax:\n*   {% pullquote [class] %}\n*   Quote string\n*   {%"
  },
  {
    "path": "lib/plugins/tag/url_for.ts",
    "chars": 429,
    "preview": "import { url_for, htmlTag } from 'hexo-util';\nimport type Hexo from '../../hexo';\n\n/**\n * Url for tag\n *\n * Syntax:\n *  "
  },
  {
    "path": "lib/theme/index.ts",
    "chars": 1992,
    "preview": "import { extname } from 'path';\nimport Box from '../box';\nimport View from './view';\nimport I18n from 'hexo-i18n';\nimpor"
  },
  {
    "path": "lib/theme/processors/config.ts",
    "chars": 534,
    "preview": "import { Pattern } from 'hexo-util';\nimport type { _File } from '../../box';\nimport Theme from '..';\n\nfunction process(f"
  },
  {
    "path": "lib/theme/processors/i18n.ts",
    "chars": 615,
    "preview": "import { Pattern } from 'hexo-util';\nimport { extname } from 'path';\nimport type { _File } from '../../box';\nimport type"
  },
  {
    "path": "lib/theme/processors/source.ts",
    "chars": 830,
    "preview": "import { Pattern } from 'hexo-util';\nimport * as common from '../../plugins/processor/common';\nimport type { _File } fro"
  },
  {
    "path": "lib/theme/processors/view.ts",
    "chars": 464,
    "preview": "import { Pattern } from 'hexo-util';\nimport type { _File } from '../../box';\nimport type Theme from '..';\n\nfunction proc"
  },
  {
    "path": "lib/theme/view.ts",
    "chars": 4513,
    "preview": "import { dirname, extname, join } from 'path';\nimport { parse as yfm } from 'hexo-front-matter';\nimport Promise from 'bl"
  },
  {
    "path": "lib/types.ts",
    "chars": 10610,
    "preview": "import moment from 'moment';\nimport type default_config from './hexo/default_config';\nimport type i18n from 'hexo-i18n';"
  },
  {
    "path": "package.json",
    "chars": 2507,
    "preview": "{\n  \"name\": \"hexo\",\n  \"version\": \"8.1.1\",\n  \"description\": \"A fast, simple & powerful blog framework, powered by Node.js"
  },
  {
    "path": "test/benchmark.js",
    "chars": 6486,
    "preview": "const { performance, PerformanceObserver } = require('perf_hooks');\nconst { spawn } = require('child_process');\nconst { "
  },
  {
    "path": "test/fixtures/_config.json",
    "chars": 65,
    "preview": "{\n\t\"author\": \"waldo\",\n\t\"favorites\": {\n\t\t\"food\": \"ice cream\"\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/hello.njk",
    "chars": 18,
    "preview": "Hello {{ name }}!\n"
  },
  {
    "path": "test/fixtures/post_render.ts",
    "chars": 2207,
    "preview": "import { highlight } from 'hexo-util';\n\nconst code = [\n  'if tired && night:',\n  '  sleep()'\n].join('\\n');\n\nexport const"
  },
  {
    "path": "test/scripts/box/box.ts",
    "chars": 16277,
    "preview": "import { join, sep } from 'path';\nimport { appendFile, mkdir, mkdirs, rename, rmdir, stat, unlink, writeFile } from 'hex"
  },
  {
    "path": "test/scripts/box/file.ts",
    "chars": 1738,
    "preview": "import { join } from 'path';\nimport { rmdir, stat, statSync, writeFile } from 'hexo-fs';\nimport { load } from 'js-yaml';"
  },
  {
    "path": "test/scripts/console/clean.ts",
    "chars": 1257,
    "preview": "import { exists, mkdirs, unlink, writeFile } from 'hexo-fs';\nimport Hexo from '../../../lib/hexo';\nimport cleanConsole f"
  },
  {
    "path": "test/scripts/console/config.ts",
    "chars": 3430,
    "preview": "import { mkdirs, readFile, rmdir, unlink, writeFile } from 'hexo-fs';\nimport { join } from 'path';\nimport { load } from "
  },
  {
    "path": "test/scripts/console/deploy.ts",
    "chars": 3535,
    "preview": "import { exists, mkdirs, readFile, rmdir, writeFile } from 'hexo-fs';\nimport { join } from 'path';\nimport { spy, stub, a"
  },
  {
    "path": "test/scripts/console/generate.ts",
    "chars": 10404,
    "preview": "import { join } from 'path';\nimport { emptyDir, exists, mkdirs, readFile, rmdir, stat, unlink, writeFile } from 'hexo-fs"
  },
  {
    "path": "test/scripts/console/list.ts",
    "chars": 1574,
    "preview": "import { spy, stub, assert as sinonAssert, SinonSpy } from 'sinon';\nimport BluebirdPromise from 'bluebird';\nimport Hexo "
  },
  {
    "path": "test/scripts/console/list_categories.ts",
    "chars": 1603,
    "preview": "import BluebirdPromise from 'bluebird';\nimport { stub, assert as sinonAssert } from 'sinon';\nimport Hexo from '../../../"
  },
  {
    "path": "test/scripts/console/list_page.ts",
    "chars": 1703,
    "preview": "import { stub, assert as sinonAssert } from 'sinon';\nimport Hexo from '../../../lib/hexo';\nimport listPage from '../../."
  },
  {
    "path": "test/scripts/console/list_post.ts",
    "chars": 2067,
    "preview": "import BluebirdPromise from 'bluebird';\nimport { stub, assert as sinonAssert } from 'sinon';\nimport Hexo from '../../../"
  },
  {
    "path": "test/scripts/console/list_route.ts",
    "chars": 1137,
    "preview": "import { stub, assert as sinonAssert } from 'sinon';\nimport Hexo from '../../../lib/hexo';\nimport listRoute from '../../"
  },
  {
    "path": "test/scripts/console/list_tags.ts",
    "chars": 1783,
    "preview": "import BluebirdPromise from 'bluebird';\nimport { stub, assert as sinonAssert } from 'sinon';\nimport Hexo from '../../../"
  },
  {
    "path": "test/scripts/console/migrate.ts",
    "chars": 1498,
    "preview": "import { spy, assert as sinonAssert, stub, SinonSpy } from 'sinon';\nimport Hexo from '../../../lib/hexo';\nimport migrate"
  },
  {
    "path": "test/scripts/console/new.ts",
    "chars": 8582,
    "preview": "import { exists, mkdirs, readFile, rmdir, unlink } from 'hexo-fs';\nimport moment from 'moment';\nimport { join } from 'pa"
  },
  {
    "path": "test/scripts/console/publish.ts",
    "chars": 3630,
    "preview": "import { exists, mkdirs, readFile, rmdir, unlink } from 'hexo-fs';\nimport moment from 'moment';\nimport { join } from 'pa"
  },
  {
    "path": "test/scripts/console/render.ts",
    "chars": 3522,
    "preview": "import { mkdirs, readFile, rmdir, unlink, writeFile } from 'hexo-fs';\nimport { join } from 'path';\nimport BluebirdPromis"
  },
  {
    "path": "test/scripts/extend/console.ts",
    "chars": 2312,
    "preview": "import Console from '../../../lib/extend/console';\nimport chai from 'chai';\nconst should = chai.should();\n\ndescribe('Con"
  },
  {
    "path": "test/scripts/extend/deployer.ts",
    "chars": 1446,
    "preview": "import Deployer from '../../../lib/extend/deployer';\nimport chai from 'chai';\nconst should = chai.should();\n\ndescribe('D"
  },
  {
    "path": "test/scripts/extend/filter.ts",
    "chars": 6125,
    "preview": "import Filter from '../../../lib/extend/filter';\nimport { spy } from 'sinon';\nimport chai from 'chai';\nconst should = ch"
  },
  {
    "path": "test/scripts/extend/generator.ts",
    "chars": 1038,
    "preview": "import Generator from '../../../lib/extend/generator';\nimport chai from 'chai';\nconst should = chai.should();\n\ndescribe("
  },
  {
    "path": "test/scripts/extend/helper.ts",
    "chars": 772,
    "preview": "import Helper from '../../../lib/extend/helper';\nimport chai from 'chai';\nconst should = chai.should();\n\ndescribe('Helpe"
  },
  {
    "path": "test/scripts/extend/injector.ts",
    "chars": 9812,
    "preview": "import Injector from '../../../lib/extend/injector';\n\ndescribe('Injector', () => {\n  const content = [\n    '<!DOCTYPE ht"
  },
  {
    "path": "test/scripts/extend/migrator.ts",
    "chars": 1416,
    "preview": "import Migrator from '../../../lib/extend/migrator';\nimport chai from 'chai';\nconst should = chai.should();\n\ndescribe('M"
  },
  {
    "path": "test/scripts/extend/processor.ts",
    "chars": 722,
    "preview": "import Processor from '../../../lib/extend/processor';\nimport chai from 'chai';\nconst should = chai.should();\n\ndescribe("
  },
  {
    "path": "test/scripts/extend/renderer.ts",
    "chars": 3595,
    "preview": "import Renderer from '../../../lib/extend/renderer';\nimport BluebirdPromise from 'bluebird';\nimport chai from 'chai';\nco"
  }
]

// ... and 97 more files (download for full content)

About this extraction

This page contains the full source code of the hexojs/hexo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 297 files (912.2 KB), approximately 253.4k tokens, and a symbol index with 646 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!