@docmd/plugin-installer 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/index.js +229 -0
- package/package.json +37 -0
- package/registry/plugins.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 docmd.io
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @docmd/plugin-installer
|
|
2
|
+
|
|
3
|
+
The cross-platform engine powering `docmd add` and `docmd remove`.
|
|
4
|
+
|
|
5
|
+
Automatically downloads, resolves, and injects plugin schemas directly into your `docmd.config.js` with built-in AST-like regex parsing and elegant structural fallbacks.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
You do not need to configure this package manually. It is bundled directly into `@docmd/core` and runs natively through the CLI.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
docmd add search
|
|
13
|
+
docmd add analytics --verbose
|
|
14
|
+
docmd remove search
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## The `docmd` Ecosystem
|
|
18
|
+
|
|
19
|
+
`docmd` is a modular system. Here are the official packages:
|
|
20
|
+
|
|
21
|
+
**The Engine**
|
|
22
|
+
* [**@docmd/core**](https://www.npmjs.com/package/@docmd/core) - The CLI runner and build orchestrator.
|
|
23
|
+
* [**@docmd/parser**](https://www.npmjs.com/package/@docmd/parser) - The pure Markdown-to-HTML logic.
|
|
24
|
+
* [**@docmd/live**](https://www.npmjs.com/package/@docmd/live) - The browser-based Live Editor bundle.
|
|
25
|
+
|
|
26
|
+
**Interface & Design**
|
|
27
|
+
* [**@docmd/ui**](https://www.npmjs.com/package/@docmd/ui) - Base EJS templates and assets.
|
|
28
|
+
* [**@docmd/themes**](https://www.npmjs.com/package/@docmd/themes) - Official themes (Sky, Ruby, Retro).
|
|
29
|
+
|
|
30
|
+
**Plugins**
|
|
31
|
+
* [**@docmd/plugin-search**](https://www.npmjs.com/package/@docmd/plugin-search) - Offline full-text search.
|
|
32
|
+
* [**@docmd/plugin-pwa**](https://www.npmjs.com/package/@docmd/plugin-pwa) - Progressive Web App support.
|
|
33
|
+
* [**@docmd/plugin-mermaid**](https://www.npmjs.com/package/@docmd/plugin-mermaid) - Diagrams and flowcharts.
|
|
34
|
+
* [**@docmd/plugin-seo**](https://www.npmjs.com/package/@docmd/plugin-seo) - Meta tags and Open Graph data.
|
|
35
|
+
* [**@docmd/plugin-sitemap**](https://www.npmjs.com/package/@docmd/plugin-sitemap) - Automatic sitemap generation.
|
|
36
|
+
* [**@docmd/plugin-llms**](https://www.npmjs.com/package/@docmd/plugin-llms) - AI context generation.
|
|
37
|
+
* [**@docmd/plugin-analytics**](https://www.npmjs.com/package/@docmd/plugin-analytics) - Google Analytics integration.
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
package/index.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
// Load the official plugins registry
|
|
7
|
+
const pluginsRegistry = require('./registry/plugins.json');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Detects the package manager used in the current project by looking for lockfiles upwards.
|
|
11
|
+
* Defaults to 'npm' if no lockfile is found.
|
|
12
|
+
*/
|
|
13
|
+
function getPackageManager(cwd) {
|
|
14
|
+
let dir = cwd;
|
|
15
|
+
while (dir !== path.parse(dir).root) {
|
|
16
|
+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
17
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn';
|
|
18
|
+
if (fs.existsSync(path.join(dir, 'bun.lockb'))) return 'bun';
|
|
19
|
+
if (fs.existsSync(path.join(dir, 'package-lock.json'))) return 'npm';
|
|
20
|
+
dir = path.dirname(dir);
|
|
21
|
+
}
|
|
22
|
+
return 'npm';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolves plugin metadata from the registry, or builds a fallback object.
|
|
27
|
+
*/
|
|
28
|
+
function resolvePluginMeta(name) {
|
|
29
|
+
if (pluginsRegistry[name]) {
|
|
30
|
+
return pluginsRegistry[name];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Fallback for unofficial/custom plugins
|
|
34
|
+
return {
|
|
35
|
+
package: name,
|
|
36
|
+
description: "Custom community plugin",
|
|
37
|
+
configKey: name.replace('@docmd/plugin-', ''), // Best effort guess
|
|
38
|
+
defaultConfig: "{}"
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reads config and safely injects the plugin to the `plugins` object.
|
|
44
|
+
*/
|
|
45
|
+
function injectPluginToConfig(configPath, meta) {
|
|
46
|
+
let content = '';
|
|
47
|
+
if (fs.existsSync(configPath)) {
|
|
48
|
+
content = fs.readFileSync(configPath, 'utf8');
|
|
49
|
+
} else {
|
|
50
|
+
// Scaffold minimal config if missing
|
|
51
|
+
content = `module.exports = {\n plugins: {}\n};\n`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Strip comments to avoid false positives (e.g., `// analytics: {}`)
|
|
55
|
+
const strippedContent = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
56
|
+
|
|
57
|
+
const configKey = meta.configKey;
|
|
58
|
+
|
|
59
|
+
// Check if plugin is already inside
|
|
60
|
+
if (strippedContent.includes(`'${configKey}'`) || strippedContent.includes(`"${configKey}"`) || strippedContent.includes(`\`${configKey}\``) || strippedContent.includes(`${configKey}:`)) {
|
|
61
|
+
return false; // Already installed
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Look for `plugins: {`
|
|
65
|
+
const pluginsRegex = /plugins\s*:\s*\{/;
|
|
66
|
+
const match = content.match(pluginsRegex);
|
|
67
|
+
|
|
68
|
+
// Dynamically format multi-line configs to match indentation
|
|
69
|
+
const formattedConfig = meta.defaultConfig.replace(/\n/g, '\n ');
|
|
70
|
+
const injectStr = `'${configKey}': ${formattedConfig}`;
|
|
71
|
+
|
|
72
|
+
if (match) {
|
|
73
|
+
content = content.replace(pluginsRegex, `plugins: {\n ${injectStr},`);
|
|
74
|
+
} else {
|
|
75
|
+
// If there's no plugins object, we can try to inject it before the last closing brace
|
|
76
|
+
const moduleExportsRegex = /module\.exports\s*=\s*(?:defineConfig\()?\{([\s\S]*?)\}(?:\))?;?/g;
|
|
77
|
+
let matchE;
|
|
78
|
+
let lastMatch;
|
|
79
|
+
while ((matchE = moduleExportsRegex.exec(content)) !== null) {
|
|
80
|
+
lastMatch = matchE;
|
|
81
|
+
}
|
|
82
|
+
if (lastMatch) {
|
|
83
|
+
const closingBraceIndex = lastMatch.index + lastMatch[0].lastIndexOf('}');
|
|
84
|
+
const prefix = content.substring(0, closingBraceIndex);
|
|
85
|
+
const suffix = content.substring(closingBraceIndex);
|
|
86
|
+
|
|
87
|
+
const insert = prefix.trim().endsWith(',') || prefix.trim().endsWith('{') ?
|
|
88
|
+
`\n plugins: {\n ${injectStr}\n }\n` :
|
|
89
|
+
`,\n plugins: {\n ${injectStr}\n }\n`;
|
|
90
|
+
|
|
91
|
+
content = prefix + insert + suffix;
|
|
92
|
+
} else {
|
|
93
|
+
console.warn(chalk.yellow(`Could not automatically inject plugin into config file. Please add '${configKey}': ${meta.defaultConfig} manually to the plugins object.`));
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fs.writeFileSync(configPath, content, 'utf8');
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Removes the plugin from the config file.
|
|
104
|
+
*/
|
|
105
|
+
function removePluginFromConfig(configPath, meta) {
|
|
106
|
+
if (!fs.existsSync(configPath)) return false;
|
|
107
|
+
|
|
108
|
+
let content = fs.readFileSync(configPath, 'utf8');
|
|
109
|
+
const configKey = meta.configKey;
|
|
110
|
+
|
|
111
|
+
if (!content.includes(configKey)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// This regex handles both multiline well-formatted configs, and poorly formatted tight inline configs
|
|
116
|
+
// generated by the scaffolding fallback e.g. `'search': {},}`
|
|
117
|
+
const re1 = new RegExp(`\\s*['"\`]?${configKey}['"\`]?\\s*:\\s*\\{[^}]*\\}\\s*,?\\s*`, 'gm');
|
|
118
|
+
|
|
119
|
+
// Replace active entries with empty string
|
|
120
|
+
const newContent = content.replace(re1, '');
|
|
121
|
+
|
|
122
|
+
if (content === newContent) {
|
|
123
|
+
return false; // Regex didn't match (e.g. config differs from expected format)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fs.writeFileSync(configPath, newContent, 'utf8');
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async function installPlugin(pluginInput, opts = {}) {
|
|
132
|
+
const cwd = process.cwd();
|
|
133
|
+
const pkgManager = getPackageManager(cwd);
|
|
134
|
+
const meta = resolvePluginMeta(pluginInput);
|
|
135
|
+
const packageName = meta.package;
|
|
136
|
+
|
|
137
|
+
if (opts.verbose) {
|
|
138
|
+
console.log(chalk.cyan(`Installing ${packageName} using ${pkgManager}...`));
|
|
139
|
+
} else {
|
|
140
|
+
process.stdout.write(chalk.cyan(`Installing ${packageName}... `));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let installCmd = '';
|
|
144
|
+
if (pkgManager === 'npm') installCmd = `npm install ${packageName}`;
|
|
145
|
+
else if (pkgManager === 'yarn') installCmd = `yarn add ${packageName}`;
|
|
146
|
+
else if (pkgManager === 'pnpm') installCmd = `pnpm add ${packageName}`;
|
|
147
|
+
else if (pkgManager === 'bun') installCmd = `bun add ${packageName}`;
|
|
148
|
+
|
|
149
|
+
// Use `--no-save` fallback for global / raw directories gracefully if it's npm
|
|
150
|
+
if (pkgManager === 'npm' && !fs.existsSync(path.join(cwd, 'package.json'))) {
|
|
151
|
+
installCmd += ' --no-save';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const stdioMode = opts.verbose ? 'inherit' : 'pipe';
|
|
156
|
+
execSync(installCmd, { stdio: stdioMode, cwd });
|
|
157
|
+
|
|
158
|
+
if (!opts.verbose) process.stdout.write(chalk.green(`Done\n`));
|
|
159
|
+
else console.log(chalk.green(`Successfully installed ${packageName}.`));
|
|
160
|
+
|
|
161
|
+
const configPath = path.join(cwd, 'docmd.config.js');
|
|
162
|
+
console.log(chalk.cyan(`Injecting '${meta.configKey}' into docmd.config.js...`));
|
|
163
|
+
|
|
164
|
+
const injected = injectPluginToConfig(configPath, meta);
|
|
165
|
+
if (injected) {
|
|
166
|
+
console.log(chalk.green(`Successfully activated '${meta.configKey}' in config.`));
|
|
167
|
+
} else {
|
|
168
|
+
console.log(chalk.yellow(`Plugin '${meta.configKey}' was already in config or could not be injected.`));
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (!opts.verbose) process.stdout.write(chalk.red(`Failed\n`));
|
|
172
|
+
console.error(chalk.red(`❌ Could not install plugin '${packageName}'.`));
|
|
173
|
+
if (opts.verbose) {
|
|
174
|
+
console.error(chalk.dim(err.message));
|
|
175
|
+
if (err.stdout) console.error(err.stdout.toString());
|
|
176
|
+
if (err.stderr) console.error(err.stderr.toString());
|
|
177
|
+
} else {
|
|
178
|
+
console.error(chalk.yellow(`Run with --verbose to see detailed logs.`));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function removePlugin(pluginInput, opts = {}) {
|
|
184
|
+
const cwd = process.cwd();
|
|
185
|
+
const pkgManager = getPackageManager(cwd);
|
|
186
|
+
const meta = resolvePluginMeta(pluginInput);
|
|
187
|
+
const packageName = meta.package;
|
|
188
|
+
|
|
189
|
+
if (opts.verbose) {
|
|
190
|
+
console.log(chalk.cyan(`Removing ${packageName} using ${pkgManager}...`));
|
|
191
|
+
} else {
|
|
192
|
+
process.stdout.write(chalk.cyan(`Removing ${packageName}... `));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let uninstallCmd = '';
|
|
196
|
+
if (pkgManager === 'npm') uninstallCmd = `npm uninstall ${packageName}`;
|
|
197
|
+
else if (pkgManager === 'yarn') uninstallCmd = `yarn remove ${packageName}`;
|
|
198
|
+
else if (pkgManager === 'pnpm') uninstallCmd = `pnpm remove ${packageName}`;
|
|
199
|
+
else if (pkgManager === 'bun') uninstallCmd = `bun remove ${packageName}`;
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const stdioMode = opts.verbose ? 'inherit' : 'pipe';
|
|
203
|
+
execSync(uninstallCmd, { stdio: stdioMode, cwd });
|
|
204
|
+
|
|
205
|
+
if (!opts.verbose) process.stdout.write(chalk.green(`Done\n`));
|
|
206
|
+
else console.log(chalk.green(`Successfully uninstalled ${packageName}.`));
|
|
207
|
+
|
|
208
|
+
const configPath = path.join(cwd, 'docmd.config.js');
|
|
209
|
+
console.log(chalk.cyan(`Removing '${meta.configKey}' from docmd.config.js...`));
|
|
210
|
+
|
|
211
|
+
const removed = removePluginFromConfig(configPath, meta);
|
|
212
|
+
if (removed) {
|
|
213
|
+
console.log(chalk.green(`Successfully deactivated '${meta.configKey}' in config.`));
|
|
214
|
+
} else {
|
|
215
|
+
console.log(chalk.yellow(`Plugin '${meta.configKey}' was not found in config or could not be removed automatically.`));
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
if (!opts.verbose) process.stdout.write(chalk.red(`Failed\n`));
|
|
219
|
+
console.error(chalk.red(`❌ Could not remove plugin '${packageName}'.`));
|
|
220
|
+
if (opts.verbose) {
|
|
221
|
+
console.error(chalk.dim(err.message));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
installPlugin,
|
|
228
|
+
removePlugin
|
|
229
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@docmd/plugin-installer",
|
|
3
|
+
"version": "0.5.3",
|
|
4
|
+
"description": "Installer utility to add and remove plugins for docmd.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"chalk": "^4.1.2"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"docmd",
|
|
11
|
+
"plugin",
|
|
12
|
+
"installer",
|
|
13
|
+
"cli",
|
|
14
|
+
"add",
|
|
15
|
+
"remove",
|
|
16
|
+
"markdown",
|
|
17
|
+
"minimalist",
|
|
18
|
+
"zero-config",
|
|
19
|
+
"site-generator",
|
|
20
|
+
"static-site-generator",
|
|
21
|
+
"docs"
|
|
22
|
+
],
|
|
23
|
+
"author": {
|
|
24
|
+
"name": "Ghazi",
|
|
25
|
+
"url": "https://mgks.dev"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/docmd-io/docmd.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/docmd-io/docmd/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://docmd.io",
|
|
35
|
+
"funding": "https://github.com/sponsors/mgks",
|
|
36
|
+
"license": "MIT"
|
|
37
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"analytics": {
|
|
3
|
+
"package": "@docmd/plugin-analytics",
|
|
4
|
+
"description": "Analytics injection plugin for docmd",
|
|
5
|
+
"configKey": "analytics",
|
|
6
|
+
"defaultConfig": "{}"
|
|
7
|
+
},
|
|
8
|
+
"search": {
|
|
9
|
+
"package": "@docmd/plugin-search",
|
|
10
|
+
"description": "Local search implementation for docmd",
|
|
11
|
+
"configKey": "search",
|
|
12
|
+
"defaultConfig": "{}"
|
|
13
|
+
},
|
|
14
|
+
"seo": {
|
|
15
|
+
"package": "@docmd/plugin-seo",
|
|
16
|
+
"description": "SEO meta tag generator for docmd",
|
|
17
|
+
"configKey": "seo",
|
|
18
|
+
"defaultConfig": "{}"
|
|
19
|
+
},
|
|
20
|
+
"sitemap": {
|
|
21
|
+
"package": "@docmd/plugin-sitemap",
|
|
22
|
+
"description": "Sitemap generator for docmd",
|
|
23
|
+
"configKey": "sitemap",
|
|
24
|
+
"defaultConfig": "{}"
|
|
25
|
+
},
|
|
26
|
+
"mermaid": {
|
|
27
|
+
"package": "@docmd/plugin-mermaid",
|
|
28
|
+
"description": "Mermaid.js diagram support for docmd",
|
|
29
|
+
"configKey": "mermaid",
|
|
30
|
+
"defaultConfig": "{}"
|
|
31
|
+
},
|
|
32
|
+
"llms": {
|
|
33
|
+
"package": "@docmd/plugin-llms",
|
|
34
|
+
"description": "LLM integration plugin for docmd",
|
|
35
|
+
"configKey": "llms",
|
|
36
|
+
"defaultConfig": "{}"
|
|
37
|
+
},
|
|
38
|
+
"pwa": {
|
|
39
|
+
"package": "@docmd/plugin-pwa",
|
|
40
|
+
"description": "Progressive Web App support for docmd",
|
|
41
|
+
"configKey": "pwa",
|
|
42
|
+
"defaultConfig": "{}"
|
|
43
|
+
}
|
|
44
|
+
}
|