feat(plugin-md-power): add support npm-to container (#256)
* feat(plugin-md-power): add support `npm-to` container * chore: tweak * chore: tweak * chore: tweak
This commit is contained in:
parent
08eeac7cb8
commit
b721bf08f9
@ -40,6 +40,7 @@ export const themeGuide = defineNoteConfig({
|
||||
'选项组',
|
||||
'隐秘文本',
|
||||
'示例容器',
|
||||
'npm-to',
|
||||
'caniuse',
|
||||
'导入文件',
|
||||
],
|
||||
|
||||
@ -30,6 +30,7 @@ export const theme: Theme = plumeTheme({
|
||||
replit: true,
|
||||
codeSandbox: true,
|
||||
jsfiddle: true,
|
||||
npmTo: ['pnpm', 'yarn', 'npm'],
|
||||
repl: {
|
||||
go: true,
|
||||
rust: true,
|
||||
|
||||
206
docs/notes/theme/guide/markdown/npm-to.md
Normal file
206
docs/notes/theme/guide/markdown/npm-to.md
Normal file
@ -0,0 +1,206 @@
|
||||
---
|
||||
title: npmTo 容器
|
||||
icon: gg:npm
|
||||
createTime: 2024/10/08 15:54:12
|
||||
permalink: /guide/markdown/npm-to/
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
npmTo 容器用于将 npm 命令行转换为 `pnpm / yarn / deno / bun` 的命令行,并它们作为 代码块组呈现在页面。
|
||||
|
||||
在 `::: npm-to` 容器中,只需要写 一次 npm 命令 代码块即可。
|
||||
|
||||
::: details 为什么需要 npmTo 容器 ?
|
||||
在我编写文档时,常常需要提供 `pnpm / yarn / npm` 等不同运行环境的命令,需要写多个代码块并包装在 `::: code-tabs`
|
||||
容器中。它占据了我不少的工作量,而且还占据了 markdown 内容中的很长一部分空间,体验并不友好。
|
||||
因此我编写了这个 `::: npm-to` 容器以解决这个问题。
|
||||
:::
|
||||
|
||||
## 用法
|
||||
|
||||
````md
|
||||
::: npm-to <!-- [!code hl] -->
|
||||
``` sh
|
||||
npm install -D vuepress vuepress-theme-plume
|
||||
```
|
||||
::: <!-- [!code hl] -->
|
||||
````
|
||||
|
||||
将 包含 `npm` 命令行的代码块,包裹在 `::: npm-to` 容器中即可。
|
||||
|
||||
::: warning npm-to 容器仅支持存在单个代码块,不能包含其他内容
|
||||
:::
|
||||
|
||||
上述代码在内部会被转换为:
|
||||
|
||||
````md
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
``` sh
|
||||
pnpm add -D vuepress vuepress-theme-plume
|
||||
```
|
||||
@tab yarn
|
||||
``` sh
|
||||
yarn add -D vuepress vuepress-theme-plume
|
||||
```
|
||||
@tab npm
|
||||
``` sh
|
||||
npm install -D vuepress vuepress-theme-plume
|
||||
```
|
||||
:::
|
||||
````
|
||||
|
||||
最终会在页面中呈现为:
|
||||
|
||||
::: npm-to
|
||||
|
||||
``` sh
|
||||
npm install -D vuepress vuepress-theme-plume
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
还可以控制 代码块组中的代码块的显示顺序,如下所示:
|
||||
|
||||
**输入:**
|
||||
|
||||
````md
|
||||
::: npm-to tabs="npm,yarn,pnpm,bun,deno" <!-- [!code hl] -->
|
||||
``` sh
|
||||
npm install -D vuepress vuepress-theme-plume
|
||||
```
|
||||
::: <!-- [!code hl] -->
|
||||
````
|
||||
|
||||
**输出:**
|
||||
|
||||
::: npm-to tabs="npm,yarn,pnpm,bun,deno"
|
||||
|
||||
``` sh
|
||||
npm install -D vuepress vuepress-theme-plume
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 配置
|
||||
|
||||
该功能默认不启用,您需要在 `theme` 配置中启用它。
|
||||
|
||||
```ts
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
plugins: {
|
||||
markdownPower: {
|
||||
// npmTo: true, // 启用,并使用默认配置
|
||||
npmTo: {
|
||||
tabs: ['npm', 'yarn', 'pnpm'], // 代码块组默认显示顺序
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
`npm-to` 支持将 `npm` 命令行转换为 `pnpm / yarn / deno / bun` 的命令行。可以根据需求进行配置 `tabs` 。
|
||||
|
||||
## 命令行支持
|
||||
|
||||
`npmTo` 并不对 `npm` 的所有命令行提供支持,以下是支持的列表:
|
||||
|
||||
- `npm install` / `npm i`
|
||||
- `npm run` / `npm run-script`
|
||||
- `npm init`
|
||||
- `npm create`
|
||||
- `npm uninstall` / `npm rm` / `npm remove` / `npm un` / `npm unlink`
|
||||
- `npm ci`
|
||||
- `npx`
|
||||
|
||||
::: info
|
||||
对于不支持的命令行,`npmTo` 不会处理它们,仅将它们复制到其他的代码块中。
|
||||
:::
|
||||
|
||||
## 示例
|
||||
|
||||
**输入:**
|
||||
|
||||
````md
|
||||
::: npm-to
|
||||
```sh
|
||||
npm install && npm run docs:dev
|
||||
```
|
||||
:::
|
||||
````
|
||||
|
||||
**输出:**
|
||||
|
||||
::: npm-to
|
||||
|
||||
```sh
|
||||
npm install && npm run docs:dev
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
**输入:**
|
||||
|
||||
````md
|
||||
::: npm-to
|
||||
```sh
|
||||
npm i -D vue
|
||||
npm i --save-peer vuepress
|
||||
npm i typescript
|
||||
```
|
||||
:::
|
||||
````
|
||||
|
||||
**输出:**
|
||||
::: npm-to
|
||||
|
||||
```sh
|
||||
npm i -D vue
|
||||
npm i --save-peer vuepress
|
||||
npm i typescript
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
**输入:**
|
||||
|
||||
````md
|
||||
::: npm-to
|
||||
```sh
|
||||
npm run docs:dev -- --clean-cache
|
||||
```
|
||||
:::
|
||||
````
|
||||
|
||||
**输出:**
|
||||
|
||||
::: npm-to
|
||||
|
||||
```sh
|
||||
npm run docs:dev -- --clean-cache
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
**输入:**
|
||||
|
||||
````md
|
||||
::: npm-to tabs="pnpm,yarn,npm,bun,deno"
|
||||
```sh
|
||||
npm ci
|
||||
```
|
||||
:::
|
||||
````
|
||||
|
||||
**输出:**
|
||||
|
||||
::: npm-to tabs="pnpm,yarn,npm,bun,deno"
|
||||
|
||||
```sh
|
||||
npm ci
|
||||
```
|
||||
|
||||
:::
|
||||
@ -15,20 +15,7 @@ permalink: /guide/markdown/iconify/
|
||||
为了更好的使用该功能,主题推荐你安装 `@iconify/json` 依赖。主题会自动解析 `@iconify/json` 中的图标数据,
|
||||
将有使用的图标打包为本地资源,以获得更好的访问体验。
|
||||
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
|
||||
```sh
|
||||
pnpm add @iconify/json
|
||||
```
|
||||
|
||||
@tab yarn
|
||||
|
||||
```sh
|
||||
yarn add @iconify/json
|
||||
```
|
||||
|
||||
@tab npm
|
||||
::: npm-to
|
||||
|
||||
```sh
|
||||
npm install @iconify/json
|
||||
|
||||
@ -67,20 +67,7 @@ permalink: /guide/features/icon/
|
||||
|
||||
由于 `@iconify/json` 包比较大,需要手动进行安装:
|
||||
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
|
||||
```sh
|
||||
pnpm add @iconify/json
|
||||
```
|
||||
|
||||
@tab yarn
|
||||
|
||||
```sh
|
||||
yarn add @iconify/json
|
||||
```
|
||||
|
||||
@tab npm
|
||||
::: npm-to
|
||||
|
||||
```sh
|
||||
npm install @iconify/json
|
||||
|
||||
@ -44,24 +44,10 @@ const vuepressVersion = __VUEPRESS_VERSION__
|
||||
|
||||
主题提供了一个 命令行工具,帮助您构建一个基本项目。您可以通过运行以下命令,启动 安装向导。
|
||||
|
||||
::: code-tabs
|
||||
|
||||
@tab pnpm
|
||||
::: npm-to
|
||||
|
||||
```sh
|
||||
pnpm create vuepress-theme-plume@latest
|
||||
```
|
||||
|
||||
@tab yarn
|
||||
|
||||
```sh
|
||||
yarn create vuepress-theme-plume@latest
|
||||
```
|
||||
|
||||
@tab npm
|
||||
|
||||
```sh
|
||||
npm init vuepress-theme-plume@latest
|
||||
npm create vuepress-theme-plume@latest
|
||||
```
|
||||
|
||||
:::
|
||||
@ -110,24 +96,9 @@ cd open-source # 进入 D: 分区下的 open-source 目录
|
||||
|
||||
- ### 初始化项目
|
||||
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
::: npm-to
|
||||
|
||||
``` sh :no-line-numbers
|
||||
git init
|
||||
pnpm init
|
||||
```
|
||||
|
||||
@tab yarn
|
||||
|
||||
``` sh :no-line-numbers
|
||||
git init
|
||||
yarn init
|
||||
```
|
||||
|
||||
@tab npm
|
||||
|
||||
``` sh :no-line-numbers
|
||||
``` sh
|
||||
git init
|
||||
npm init
|
||||
```
|
||||
@ -138,30 +109,11 @@ cd open-source # 进入 D: 分区下的 open-source 目录
|
||||
|
||||
安装 `vuepress@next` 和 `vuepress-theme-plume` 作为本地依赖。
|
||||
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
::: npm-to
|
||||
|
||||
```sh :no-line-numbers
|
||||
```sh
|
||||
# 安装 vuepress
|
||||
pnpm add -D vuepress@next vue
|
||||
# 安装 主题和打包工具
|
||||
pnpm add -D vuepress-theme-plume @vuepress/bundler-vite@next
|
||||
```
|
||||
|
||||
@tab yarn
|
||||
|
||||
``` sh :no-line-numbers
|
||||
# 安装 vuepress
|
||||
yarn add -D vuepress@next
|
||||
# 安装 主题和打包工具
|
||||
yarn add -D vuepress-theme-plume @vuepress/bundler-vite@next
|
||||
```
|
||||
|
||||
@tab npm
|
||||
|
||||
``` sh :no-line-numbers
|
||||
# 安装 vuepress
|
||||
npm i -D vuepress@next
|
||||
npm i -D vuepress@next vue
|
||||
# 安装 主题和打包工具
|
||||
npm i -D vuepress-theme-plume @vuepress/bundler-vite@next
|
||||
```
|
||||
@ -255,22 +207,9 @@ cd open-source # 进入 D: 分区下的 open-source 目录
|
||||
|
||||
- ### 在本地服务器启动你的文档站点
|
||||
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
::: npm-to
|
||||
|
||||
```sh :no-line-numbers
|
||||
pnpm docs:dev
|
||||
```
|
||||
|
||||
@tab yarn
|
||||
|
||||
``` sh :no-line-numbers
|
||||
yarn docs:dev
|
||||
```
|
||||
|
||||
@tab npm
|
||||
|
||||
``` sh :no-line-numbers
|
||||
``` sh
|
||||
npm run docs:dev
|
||||
```
|
||||
|
||||
@ -286,20 +225,7 @@ cd open-source # 进入 D: 分区下的 open-source 目录
|
||||
|
||||
您可以直接在项目中运行以下命令检查是否有可用的更新:
|
||||
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
|
||||
```sh
|
||||
pnpm dlx vp-update
|
||||
```
|
||||
|
||||
@tab yarn
|
||||
|
||||
``` sh
|
||||
yarn dlx vp-update
|
||||
```
|
||||
|
||||
@tab npm
|
||||
::: npm-to
|
||||
|
||||
``` sh
|
||||
npx vp-update
|
||||
|
||||
@ -34,20 +34,7 @@ draft: true
|
||||
|
||||
复制以下命令到你的项目中运行:
|
||||
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
|
||||
```sh
|
||||
pnpm dlx vp-update
|
||||
```
|
||||
|
||||
@tab yarn
|
||||
|
||||
```sh
|
||||
yarn dlx vp-update
|
||||
```
|
||||
|
||||
@tab npm
|
||||
::: npm-to
|
||||
|
||||
```sh
|
||||
npx vp-update
|
||||
@ -58,13 +45,13 @@ npx vp-update
|
||||
## 为什么更新主题版本后新的功能没有生效?
|
||||
|
||||
由于 VuePress 在启动开发服务时,全量编译源目录中的的 `markdown` 文件耗时较长,主题对 `markdown` 的编译进行了
|
||||
缓存,以提高启动速度。主题功能并重启开发服务时,由于源目录中的 `markdown` 文件没有变化,跳过了编译直接使用缓存,
|
||||
缓存,以提高启动速度。主题更新后重启开发服务时,由于源目录中的 `markdown` 文件没有变化,跳过了编译直接使用缓存,
|
||||
这会导致与 markdown 有关的新功能没有生效。
|
||||
|
||||
**只需要删除缓存文件,并重启即可**。
|
||||
**只需要删除缓存文件,并重启即可**:
|
||||
|
||||
1. 直接删除 `.vuepress/.cache` 目录。
|
||||
2. 在启动开发服务命令后面,添加 `--clean-cache` 参数:
|
||||
1. 方法一:直接删除 `.vuepress/.cache` 目录。
|
||||
2. 方法二:在启动开发服务命令后面,添加 `--clean-cache` 参数:
|
||||
|
||||
```sh
|
||||
vuepress dev docs --clean-cache
|
||||
@ -76,10 +63,10 @@ npx vp-update
|
||||
`plugins.markdownMath` 的配置。它与 [为什么更新主题版本后新的功能没有生效?](#为什么更新主题版本后新的功能没有生效)
|
||||
的原因相同。因此
|
||||
|
||||
**只需要删除缓存文件,并重启即可**。
|
||||
**只需要删除缓存文件,并重启即可**:
|
||||
|
||||
1. 直接删除 `.vuepress/.cache` 目录。
|
||||
2. 在启动开发服务命令后面,添加 `--clean-cache` 参数:
|
||||
1. 方法一:直接删除 `.vuepress/.cache` 目录。
|
||||
2. 方法二:在启动开发服务命令后面,添加 `--clean-cache` 参数:
|
||||
|
||||
```sh
|
||||
vuepress dev docs --clean-cache
|
||||
|
||||
@ -6,6 +6,7 @@ import { alignPlugin } from './align.js'
|
||||
import { codeTabs } from './codeTabs.js'
|
||||
import { fileTreePlugin } from './fileTree.js'
|
||||
import { langReplPlugin } from './langRepl.js'
|
||||
import { npmToPlugins } from './npmTo.js'
|
||||
import { tabs } from './tabs.js'
|
||||
|
||||
export async function containerPlugin(
|
||||
@ -20,6 +21,10 @@ export async function containerPlugin(
|
||||
// ::: code-tabs
|
||||
codeTabs(md, options.codeTabs)
|
||||
|
||||
if (options.npmTo) {
|
||||
npmToPlugins(md, typeof options.npmTo === 'boolean' ? {} : options.npmTo)
|
||||
}
|
||||
|
||||
if (options.repl)
|
||||
await langReplPlugin(app, md, options.repl)
|
||||
|
||||
|
||||
373
plugins/plugin-md-power/src/node/container/npmTo.ts
Normal file
373
plugins/plugin-md-power/src/node/container/npmTo.ts
Normal file
@ -0,0 +1,373 @@
|
||||
/**
|
||||
* 只写一个 npm 代码块,自动转为 代码块分组 [npm, pnpm, yarn, bun, deno]
|
||||
*
|
||||
* ::: npm-to
|
||||
* ``` sh
|
||||
* npm i -D vuepress-theme-plume
|
||||
* ```
|
||||
* :::
|
||||
* ↓ ↓ ↓ ↓ ↓
|
||||
* ::: code-tabs
|
||||
* @tab npm
|
||||
* ```sh
|
||||
* npm i -D vuepress-theme-plume
|
||||
* ```
|
||||
* @tab pnpm
|
||||
* ```sh
|
||||
* pnpm add -D vuepress-theme-plume
|
||||
* ```
|
||||
* @tab yarn
|
||||
* ```sh
|
||||
* yarn add -D vuepress-theme-plume
|
||||
* ```
|
||||
* :::
|
||||
*/
|
||||
import type Token from 'markdown-it/lib/token.mjs'
|
||||
import type { Markdown } from 'vuepress/markdown'
|
||||
import type { NpmToOptions, NpmToPackageManager } from '../../shared/index.js'
|
||||
import { isArray } from '@vuepress/helper'
|
||||
import container from 'markdown-it-container'
|
||||
import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
|
||||
type PackageCommand = 'install' | 'add' | 'remove' | 'run' | 'create' | 'init' | 'npx' | 'ci'
|
||||
interface CommandConfigItem {
|
||||
cli: string
|
||||
flags?: Record<string, string>
|
||||
}
|
||||
type CommandConfig = Record<Exclude<NpmToPackageManager, 'npm'>, CommandConfigItem | false>
|
||||
type CommandConfigs = Record<PackageCommand, { pattern: RegExp } & CommandConfig>
|
||||
|
||||
const ALLOW_LIST = ['npm', 'pnpm', 'yarn', 'bun', 'deno'] as const
|
||||
const BOOL_FLAGS: string[] = ['--no-save', '-B', '--save-bundle', '--save-dev', '-D', '--save-prod', '-P', '--save-peer', '-O', '--save-optional', '-E', '--save-exact', '-y', '--yes', '-g', '--global']
|
||||
|
||||
const DEFAULT_TABS: NpmToPackageManager[] = ['npm', 'pnpm', 'yarn']
|
||||
|
||||
const MANAGERS_CONFIG: CommandConfigs = {
|
||||
install: {
|
||||
pattern: /(?:^|\s)npm\s+(?:install|i)$/,
|
||||
pnpm: { cli: 'pnpm install' },
|
||||
yarn: { cli: 'yarn' },
|
||||
bun: { cli: 'bun install' },
|
||||
deno: { cli: 'deno install' },
|
||||
},
|
||||
add: {
|
||||
pattern: /(?:^|\s)npm\s+(?:install|i|add)(?:\s|$)/,
|
||||
pnpm: {
|
||||
cli: 'pnpm add',
|
||||
flags: {
|
||||
'--no-save': '', // unsupported
|
||||
'-B': '', // unsupported
|
||||
'--save-bundle': '', // unsupported
|
||||
},
|
||||
},
|
||||
yarn: {
|
||||
cli: 'yarn add',
|
||||
flags: {
|
||||
'--save-dev': '--dev',
|
||||
'--save-prod': '--prod',
|
||||
'-P': '', // in npm, `-P` same as `--save-prod`. but in yarn, `-P` same as `--peer`
|
||||
'--save-peer': '--peer',
|
||||
'--save-optional': '--optional',
|
||||
'--no-save': '', // unsupported
|
||||
'--save-exact': '--exact',
|
||||
'-B': '', // unsupported
|
||||
'--save-bundle': '', // unsupported
|
||||
},
|
||||
},
|
||||
bun: {
|
||||
cli: 'bun add',
|
||||
flags: {
|
||||
'--save-dev': '--development',
|
||||
'-P': '', // it's default
|
||||
'--save-prod': '', // it's default
|
||||
'--save-peer': '', // unsupported
|
||||
'-O': '--optional',
|
||||
'--save-optional': '--optional',
|
||||
'--no-save': '', // unsupported
|
||||
'--save-exact': '--exact',
|
||||
'-B': '', // unsupported
|
||||
'--save-bundle': '', // unsupported
|
||||
},
|
||||
},
|
||||
deno: {
|
||||
cli: 'deno add',
|
||||
flags: {
|
||||
'-g': '', // unsupported
|
||||
'--global': '', // unsupported
|
||||
'--save-dev': '--dev',
|
||||
'-P': '', // unsupported
|
||||
'--save-prod': '', // unsupported
|
||||
'--save-peer': '', // unsupported
|
||||
'-O': '', // unsupported
|
||||
'--save-optional': '', // unsupported
|
||||
'--no-save': '', // unsupported
|
||||
'-E': '', // unsupported
|
||||
'--save-exact': '', // unsupported
|
||||
'-B': '', // unsupported
|
||||
'--save-bundle': '', // unsupported
|
||||
},
|
||||
},
|
||||
},
|
||||
run: {
|
||||
pattern: /(?:^|\s)npm\s+(?:run|run-script|rum|urn)(?:\s|$)/,
|
||||
pnpm: {
|
||||
cli: 'pnpm',
|
||||
flags: {
|
||||
'-w': '-F', // same as `--workspace`
|
||||
'--workspace': '--filter', // filter by workspaces
|
||||
'--': '', // scripts flags
|
||||
},
|
||||
},
|
||||
yarn: {
|
||||
cli: 'yarn',
|
||||
flags: {
|
||||
'-w': '', // unsupported
|
||||
'--workspace': '', // unsupported
|
||||
},
|
||||
},
|
||||
bun: {
|
||||
cli: 'bun run',
|
||||
flags: {
|
||||
'-w': '--filter', // same as `--workspace`
|
||||
'--workspace': '--filter', // filter by workspaces
|
||||
},
|
||||
},
|
||||
deno: {
|
||||
cli: 'deno run',
|
||||
flags: {
|
||||
'-w': '', // unsupported
|
||||
'--workspace': '', // unsupported
|
||||
},
|
||||
},
|
||||
},
|
||||
create: {
|
||||
pattern: /(?:^|\s)npm\s+create\s/,
|
||||
pnpm: { cli: 'pnpm create', flags: { '-y': '', '--yes': '' } },
|
||||
yarn: { cli: 'yarn create', flags: { '-y': '', '--yes': '' } },
|
||||
bun: { cli: 'bun create', flags: { '-y': '', '--yes': '' } },
|
||||
deno: { cli: 'deno run -A ', flags: { '-y': '', '--yes': '' } },
|
||||
},
|
||||
init: {
|
||||
pattern: /(?:^|\s)npm\s+init/,
|
||||
pnpm: { cli: 'pnpm init', flags: { '-y': '', '--yes': '' } },
|
||||
yarn: { cli: 'yarn init', flags: { '-y': '', '--yes': '' } },
|
||||
bun: { cli: 'bun init', flags: { '-y': '', '--yes': '' } },
|
||||
deno: { cli: 'deno init', flags: { '-y': '', '--yes': '' } },
|
||||
},
|
||||
npx: {
|
||||
pattern: /(?:^|\s)npx\s+/,
|
||||
pnpm: { cli: 'pnpm dlx' },
|
||||
yarn: { cli: 'yarn dlx' },
|
||||
bun: { cli: 'bunx' },
|
||||
deno: { cli: 'deno run -A' },
|
||||
},
|
||||
remove: {
|
||||
pattern: /(?:^|\s)npm\s+(?:uninstall|r|rm|remove|unlink|un)(?:\s|$)/,
|
||||
pnpm: {
|
||||
cli: 'pnpm remove',
|
||||
flags: { '--no-save': '', '--save': '', '-S': '' },
|
||||
},
|
||||
yarn: {
|
||||
cli: 'yarn remove',
|
||||
flags: { '--save-dev': '--dev', '--save': '', '-S': '', '-g': '', '--global': '' },
|
||||
},
|
||||
bun: {
|
||||
cli: 'bun remove',
|
||||
flags: { '--save-dev': '--development', '--save': '', '-S': '', '-g': '', '--global': '' },
|
||||
},
|
||||
deno: {
|
||||
cli: 'deno uninstall',
|
||||
flags: { '--save-dev': '--dev', '--save': '', '-S': '' },
|
||||
},
|
||||
},
|
||||
ci: {
|
||||
pattern: /(?:^|\s)npm\s+ci$/,
|
||||
pnpm: { cli: 'pnpm install --frozen-lockfile' },
|
||||
yarn: { cli: 'yarn install --immutable' },
|
||||
bun: { cli: 'bun install --frozen-lockfile' },
|
||||
deno: { cli: 'deno install --frozen' },
|
||||
},
|
||||
}
|
||||
|
||||
export function npmToPlugins(md: Markdown, options: NpmToOptions): void {
|
||||
const type = 'npm-to'
|
||||
const validate = (info: string): boolean => info.trim().startsWith(type)
|
||||
|
||||
const opt = isArray(options) ? { tabs: options } : options
|
||||
const defaultTabs = opt.tabs?.length ? opt.tabs : DEFAULT_TABS
|
||||
|
||||
const render = (tokens: Token[], idx: number): string => {
|
||||
const { attrs } = resolveAttrs(tokens[idx].info.slice(type.length - 1))
|
||||
const tabs = (attrs.tabs ? attrs.tabs.split(/,\s*/) : defaultTabs) as NpmToPackageManager[]
|
||||
if (tokens[idx].nesting === 1) {
|
||||
const token = tokens[idx + 1]
|
||||
const info = token.info.trim()
|
||||
if (
|
||||
token.type === 'fence'
|
||||
&& (info.startsWith('sh') || info.startsWith('bash') || info.startsWith('shell'))
|
||||
) {
|
||||
const content = token.content
|
||||
token.hidden = true
|
||||
token.type = 'text'
|
||||
token.content = ''
|
||||
const lines = content.split(/(\n|\s*&&\s*)/)
|
||||
return md.render(resolveNpmTo(lines, info, idx, tabs), {})
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
md.use(container, type, { validate, render })
|
||||
}
|
||||
|
||||
function resolveNpmTo(lines: string[], info: string, idx: number, tabs: NpmToPackageManager[]): string {
|
||||
tabs = validateTabs(tabs)
|
||||
const res: string[] = []
|
||||
const map: Record<string, LineParsed | false> = {}
|
||||
for (const tab of tabs) {
|
||||
const newLines: string[] = []
|
||||
for (const line of lines) {
|
||||
const config = findConfig(line)
|
||||
if (config && config[tab]) {
|
||||
const parsed = (map[line] ??= parseLine(line))
|
||||
const { cli, flags } = config[tab] as CommandConfigItem
|
||||
if (parsed) {
|
||||
let newLine = `${parsed.env ? `${parsed.env} ` : ''}${cli}`
|
||||
if (parsed.args && flags) {
|
||||
let args = parsed.args
|
||||
for (const [key, value] of Object.entries(flags)) {
|
||||
args = args.replaceAll(key, value)
|
||||
}
|
||||
newLine += ` ${args.replace(/\s+-/g, ' -').trim()}`
|
||||
}
|
||||
|
||||
if (parsed.cmd)
|
||||
newLine += ` ${parsed.cmd}`
|
||||
|
||||
if (parsed.scriptArgs)
|
||||
newLine += ` ${parsed.scriptArgs}`
|
||||
newLines.push(newLine.trim())
|
||||
}
|
||||
}
|
||||
else {
|
||||
newLines.push(line)
|
||||
}
|
||||
}
|
||||
res.push(`@tab ${tab}\n\`\`\`${info}\n${newLines.join('')}\n\`\`\``)
|
||||
}
|
||||
return `:::code-tabs#npm-to-${idx}\n${res.join('\n')}\n:::`
|
||||
}
|
||||
|
||||
function findConfig(line: string): CommandConfig | undefined {
|
||||
for (const { pattern, ...config } of Object.values(MANAGERS_CONFIG)) {
|
||||
if (pattern.test(line)) {
|
||||
return config
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function validateTabs(tabs: NpmToPackageManager[]): NpmToPackageManager[] {
|
||||
if (tabs.length === 0) {
|
||||
return DEFAULT_TABS
|
||||
}
|
||||
return tabs.filter(tab => ALLOW_LIST.includes(tab))
|
||||
}
|
||||
|
||||
interface LineParsed {
|
||||
env: string
|
||||
cli: string
|
||||
cmd: string
|
||||
args?: string
|
||||
scriptArgs?: string
|
||||
}
|
||||
|
||||
const LINE_REG = /(.*)(npm|npx)\s+(.*)/
|
||||
export function parseLine(line: string): false | LineParsed {
|
||||
const match = line.match(LINE_REG)
|
||||
if (!match)
|
||||
return false
|
||||
|
||||
const [, env, cli, rest] = match
|
||||
if (cli === 'npx')
|
||||
return { env, cli, cmd: '', scriptArgs: rest?.trim() }
|
||||
|
||||
const idx = rest.indexOf(' ')
|
||||
if (idx === -1)
|
||||
return { env, cli: `${cli} ${rest.trim()}`, cmd: '' }
|
||||
|
||||
return { env, cli: `${cli} ${rest.slice(0, idx)}`, ...parseArgs(rest.slice(idx + 1)) }
|
||||
}
|
||||
|
||||
function parseArgs(line: string): { cmd: string, args?: string, scriptArgs?: string } {
|
||||
line = line?.trim()
|
||||
if (!line)
|
||||
return { cmd: '' }
|
||||
|
||||
const [npmArgs, scriptArgs] = line.split(/\s+--\s+/)
|
||||
let cmd = ''
|
||||
let args = ''
|
||||
if (npmArgs[0] !== '-') {
|
||||
if (npmArgs[0] === '"' || npmArgs[0] === '\'') {
|
||||
const idx = npmArgs.slice(1).indexOf(npmArgs[0])
|
||||
cmd = npmArgs.slice(0, idx)
|
||||
args = npmArgs.slice(idx + 1)
|
||||
}
|
||||
else {
|
||||
const idx = npmArgs.indexOf(' ')
|
||||
if (idx === -1) {
|
||||
cmd = npmArgs
|
||||
}
|
||||
else {
|
||||
cmd = npmArgs.slice(0, idx)
|
||||
args = npmArgs.slice(idx + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
let newLine = ''
|
||||
let value = ''
|
||||
let isQuote = false
|
||||
let isBool = false
|
||||
let isNextValue = false
|
||||
let quote = ''
|
||||
for (let i = 0; i < npmArgs.length; i++) {
|
||||
const v = npmArgs[i]
|
||||
if (!isQuote && (v === '"' || v === '\'')) {
|
||||
quote = v
|
||||
isQuote = true
|
||||
value += v
|
||||
}
|
||||
else if (isQuote && v === quote) {
|
||||
isQuote = false
|
||||
value += v
|
||||
}
|
||||
else if ((v === ' ' || v === '=' || i === npmArgs.length - 1) && !isQuote && value) {
|
||||
if (i === npmArgs.length - 1) {
|
||||
value += v
|
||||
}
|
||||
const isKey = value[0] === '-'
|
||||
if (isKey) {
|
||||
isBool = BOOL_FLAGS.includes(value)
|
||||
isNextValue = !isBool
|
||||
}
|
||||
if (!isKey && !isNextValue) {
|
||||
cmd += `${value} `
|
||||
}
|
||||
else {
|
||||
newLine += `${value}${v || ''}`
|
||||
if (!isKey && isNextValue) {
|
||||
isNextValue = false
|
||||
}
|
||||
}
|
||||
value = ''
|
||||
}
|
||||
else {
|
||||
value += v
|
||||
}
|
||||
}
|
||||
args = newLine
|
||||
}
|
||||
|
||||
return { cmd, args: args.trim(), scriptArgs }
|
||||
}
|
||||
@ -5,6 +5,7 @@ export * from './codeTabs.js'
|
||||
export * from './fileTree.js'
|
||||
export * from './icons.js'
|
||||
export * from './jsfiddle.js'
|
||||
export * from './npmTo.js'
|
||||
export * from './pdf.js'
|
||||
export * from './plot.js'
|
||||
export * from './plugin.js'
|
||||
|
||||
5
plugins/plugin-md-power/src/shared/npmTo.ts
Normal file
5
plugins/plugin-md-power/src/shared/npmTo.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export type NpmToPackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'deno'
|
||||
|
||||
export type NpmToOptions = NpmToPackageManager[] | {
|
||||
tabs?: NpmToPackageManager[]
|
||||
}
|
||||
@ -2,6 +2,7 @@ import type { CanIUseOptions } from './caniuse.js'
|
||||
import type { CodeTabsOptions } from './codeTabs.js'
|
||||
import type { FileTreeOptions } from './fileTree.js'
|
||||
import type { IconsOptions } from './icons.js'
|
||||
import type { NpmToOptions } from './npmTo.js'
|
||||
import type { PDFOptions } from './pdf.js'
|
||||
import type { PlotOptions } from './plot.js'
|
||||
import type { ReplOptions } from './repl.js'
|
||||
@ -11,6 +12,12 @@ export interface MarkdownPowerPluginOptions {
|
||||
* 配置代码块分组
|
||||
*/
|
||||
codeTabs?: CodeTabsOptions
|
||||
|
||||
/**
|
||||
* 是否启用 npm-to 容器
|
||||
*/
|
||||
npmTo?: boolean | NpmToOptions
|
||||
|
||||
/**
|
||||
* 是否启用 PDF 嵌入语法
|
||||
*
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user