@docmd/plugin-installer 0.5.4 → 0.6.0
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 +1 -1
- package/README.md +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +220 -0
- package/package.json +14 -3
- package/index.js +0 -229
- package/registry/plugins.json +0 -44
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ docmd remove search
|
|
|
28
28
|
* [**@docmd/themes**](https://www.npmjs.com/package/@docmd/themes) - Official themes (Sky, Ruby, Retro).
|
|
29
29
|
|
|
30
30
|
**Plugins**
|
|
31
|
+
* [**@docmd/plugin-installer**](https://www.npmjs.com/package/@docmd/plugin-installer) - Plugin installer for docmd.
|
|
31
32
|
* [**@docmd/plugin-search**](https://www.npmjs.com/package/@docmd/plugin-search) - Offline full-text search.
|
|
32
33
|
* [**@docmd/plugin-pwa**](https://www.npmjs.com/package/@docmd/plugin-pwa) - Progressive Web App support.
|
|
33
34
|
* [**@docmd/plugin-mermaid**](https://www.npmjs.com/package/@docmd/plugin-mermaid) - Diagrams and flowcharts.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const pluginsRegistry = require('../registry/plugins.json');
|
|
8
|
+
/**
|
|
9
|
+
* Detects the package manager used in the current project by looking for lockfiles upwards.
|
|
10
|
+
* Defaults to 'npm' if no lockfile is found.
|
|
11
|
+
*/
|
|
12
|
+
function getPackageManager(cwd) {
|
|
13
|
+
let dir = cwd;
|
|
14
|
+
while (dir !== path.parse(dir).root) {
|
|
15
|
+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml')))
|
|
16
|
+
return 'pnpm';
|
|
17
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock')))
|
|
18
|
+
return 'yarn';
|
|
19
|
+
if (fs.existsSync(path.join(dir, 'bun.lockb')))
|
|
20
|
+
return 'bun';
|
|
21
|
+
if (fs.existsSync(path.join(dir, 'package-lock.json')))
|
|
22
|
+
return 'npm';
|
|
23
|
+
dir = path.dirname(dir);
|
|
24
|
+
}
|
|
25
|
+
return 'npm';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolves plugin metadata from the registry, or builds a fallback object.
|
|
29
|
+
*/
|
|
30
|
+
function resolvePluginMeta(name) {
|
|
31
|
+
if (pluginsRegistry[name]) {
|
|
32
|
+
return pluginsRegistry[name];
|
|
33
|
+
}
|
|
34
|
+
// Fallback for unofficial/custom plugins
|
|
35
|
+
return {
|
|
36
|
+
package: name,
|
|
37
|
+
description: "Custom community plugin",
|
|
38
|
+
configKey: name.replace('@docmd/plugin-', ''), // Best effort guess
|
|
39
|
+
defaultConfig: "{}"
|
|
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
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Scaffold minimal config if missing
|
|
52
|
+
content = `module.exports = {\n plugins: {}\n};\n`;
|
|
53
|
+
}
|
|
54
|
+
// Strip comments to avoid false positives (e.g., `// analytics: {}`)
|
|
55
|
+
const strippedContent = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
56
|
+
const configKey = meta.configKey;
|
|
57
|
+
// Check if plugin is already inside
|
|
58
|
+
if (strippedContent.includes(`'${configKey}'`) || strippedContent.includes(`"${configKey}"`) || strippedContent.includes(`\`${configKey}\``) || strippedContent.includes(`${configKey}:`)) {
|
|
59
|
+
return false; // Already installed
|
|
60
|
+
}
|
|
61
|
+
// Look for `plugins: {`
|
|
62
|
+
const pluginsRegex = /plugins\s*:\s*\{/;
|
|
63
|
+
const match = content.match(pluginsRegex);
|
|
64
|
+
// Dynamically format multi-line configs to match indentation
|
|
65
|
+
const formattedConfig = meta.defaultConfig.replace(/\n/g, '\n ');
|
|
66
|
+
const injectStr = `'${configKey}': ${formattedConfig}`;
|
|
67
|
+
if (match) {
|
|
68
|
+
content = content.replace(pluginsRegex, `plugins: {\n ${injectStr},`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// If there's no plugins object, we can try to inject it before the last closing brace
|
|
72
|
+
const moduleExportsRegex = /module\.exports\s*=\s*(?:defineConfig\()?\{([\s\S]*?)\}(?:\))?;?/g;
|
|
73
|
+
let matchE;
|
|
74
|
+
let lastMatch;
|
|
75
|
+
while ((matchE = moduleExportsRegex.exec(content)) !== null) {
|
|
76
|
+
lastMatch = matchE;
|
|
77
|
+
}
|
|
78
|
+
if (lastMatch) {
|
|
79
|
+
const closingBraceIndex = lastMatch.index + lastMatch[0].lastIndexOf('}');
|
|
80
|
+
const prefix = content.substring(0, closingBraceIndex);
|
|
81
|
+
const suffix = content.substring(closingBraceIndex);
|
|
82
|
+
const insert = prefix.trim().endsWith(',') || prefix.trim().endsWith('{') ?
|
|
83
|
+
`\n plugins: {\n ${injectStr}\n }\n` :
|
|
84
|
+
`,\n plugins: {\n ${injectStr}\n }\n`;
|
|
85
|
+
content = prefix + insert + suffix;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.warn(chalk.yellow(`Could not automatically inject plugin into config file. Please add '${configKey}': ${meta.defaultConfig} manually to the plugins object.`));
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
fs.writeFileSync(configPath, content, 'utf8');
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Removes the plugin from the config file.
|
|
97
|
+
*/
|
|
98
|
+
function removePluginFromConfig(configPath, meta) {
|
|
99
|
+
if (!fs.existsSync(configPath))
|
|
100
|
+
return false;
|
|
101
|
+
let content = fs.readFileSync(configPath, 'utf8');
|
|
102
|
+
const configKey = meta.configKey;
|
|
103
|
+
if (!content.includes(configKey)) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
// This regex handles both multiline well-formatted configs, and poorly formatted tight inline configs
|
|
107
|
+
// generated by the scaffolding fallback e.g. `'search': {},}`
|
|
108
|
+
const re1 = new RegExp(`\\s*['"\`]?${configKey}['"\`]?\\s*:\\s*\\{[^}]*\\}\\s*,?\\s*`, 'gm');
|
|
109
|
+
// Replace active entries with empty string
|
|
110
|
+
const newContent = content.replace(re1, '');
|
|
111
|
+
if (content === newContent) {
|
|
112
|
+
return false; // Regex didn't match (e.g. config differs from expected format)
|
|
113
|
+
}
|
|
114
|
+
fs.writeFileSync(configPath, newContent, 'utf8');
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
async function installPlugin(pluginInput, opts = {}) {
|
|
118
|
+
const cwd = process.cwd();
|
|
119
|
+
const pkgManager = getPackageManager(cwd);
|
|
120
|
+
const meta = resolvePluginMeta(pluginInput);
|
|
121
|
+
const packageName = meta.package;
|
|
122
|
+
if (opts.verbose) {
|
|
123
|
+
console.log(chalk.cyan(`Installing ${packageName} using ${pkgManager}...`));
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
process.stdout.write(chalk.cyan(`Installing ${packageName}... `));
|
|
127
|
+
}
|
|
128
|
+
let installCmd = '';
|
|
129
|
+
if (pkgManager === 'npm')
|
|
130
|
+
installCmd = `npm install ${packageName}`;
|
|
131
|
+
else if (pkgManager === 'yarn')
|
|
132
|
+
installCmd = `yarn add ${packageName}`;
|
|
133
|
+
else if (pkgManager === 'pnpm')
|
|
134
|
+
installCmd = `pnpm add ${packageName}`;
|
|
135
|
+
else if (pkgManager === 'bun')
|
|
136
|
+
installCmd = `bun add ${packageName}`;
|
|
137
|
+
// Use `--no-save` fallback for global / raw directories gracefully if it's npm
|
|
138
|
+
if (pkgManager === 'npm' && !fs.existsSync(path.join(cwd, 'package.json'))) {
|
|
139
|
+
installCmd += ' --no-save';
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const stdioMode = opts.verbose ? 'inherit' : 'pipe';
|
|
143
|
+
execSync(installCmd, { stdio: stdioMode, cwd });
|
|
144
|
+
if (!opts.verbose)
|
|
145
|
+
process.stdout.write(chalk.green(`Done\n`));
|
|
146
|
+
else
|
|
147
|
+
console.log(chalk.green(`Successfully installed ${packageName}.`));
|
|
148
|
+
const configPath = path.join(cwd, 'docmd.config.js');
|
|
149
|
+
console.log(chalk.cyan(`Injecting '${meta.configKey}' into docmd.config.js...`));
|
|
150
|
+
const injected = injectPluginToConfig(configPath, meta);
|
|
151
|
+
if (injected) {
|
|
152
|
+
console.log(chalk.green(`Successfully activated '${meta.configKey}' in config.`));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log(chalk.yellow(`Plugin '${meta.configKey}' was already in config or could not be injected.`));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (!opts.verbose)
|
|
160
|
+
process.stdout.write(chalk.red(`Failed\n`));
|
|
161
|
+
console.error(chalk.red(`❌ Could not install plugin '${packageName}'.`));
|
|
162
|
+
if (opts.verbose) {
|
|
163
|
+
console.error(chalk.dim(err.message));
|
|
164
|
+
if (err.stdout)
|
|
165
|
+
console.error(err.stdout.toString());
|
|
166
|
+
if (err.stderr)
|
|
167
|
+
console.error(err.stderr.toString());
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
console.error(chalk.yellow(`Run with --verbose to see detailed logs.`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function removePlugin(pluginInput, opts = {}) {
|
|
175
|
+
const cwd = process.cwd();
|
|
176
|
+
const pkgManager = getPackageManager(cwd);
|
|
177
|
+
const meta = resolvePluginMeta(pluginInput);
|
|
178
|
+
const packageName = meta.package;
|
|
179
|
+
if (opts.verbose) {
|
|
180
|
+
console.log(chalk.cyan(`Removing ${packageName} using ${pkgManager}...`));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
process.stdout.write(chalk.cyan(`Removing ${packageName}... `));
|
|
184
|
+
}
|
|
185
|
+
let uninstallCmd = '';
|
|
186
|
+
if (pkgManager === 'npm')
|
|
187
|
+
uninstallCmd = `npm uninstall ${packageName}`;
|
|
188
|
+
else if (pkgManager === 'yarn')
|
|
189
|
+
uninstallCmd = `yarn remove ${packageName}`;
|
|
190
|
+
else if (pkgManager === 'pnpm')
|
|
191
|
+
uninstallCmd = `pnpm remove ${packageName}`;
|
|
192
|
+
else if (pkgManager === 'bun')
|
|
193
|
+
uninstallCmd = `bun remove ${packageName}`;
|
|
194
|
+
try {
|
|
195
|
+
const stdioMode = opts.verbose ? 'inherit' : 'pipe';
|
|
196
|
+
execSync(uninstallCmd, { stdio: stdioMode, cwd });
|
|
197
|
+
if (!opts.verbose)
|
|
198
|
+
process.stdout.write(chalk.green(`Done\n`));
|
|
199
|
+
else
|
|
200
|
+
console.log(chalk.green(`Successfully uninstalled ${packageName}.`));
|
|
201
|
+
const configPath = path.join(cwd, 'docmd.config.js');
|
|
202
|
+
console.log(chalk.cyan(`Removing '${meta.configKey}' from docmd.config.js...`));
|
|
203
|
+
const removed = removePluginFromConfig(configPath, meta);
|
|
204
|
+
if (removed) {
|
|
205
|
+
console.log(chalk.green(`Successfully deactivated '${meta.configKey}' in config.`));
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
console.log(chalk.yellow(`Plugin '${meta.configKey}' was not found in config or could not be removed automatically.`));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
if (!opts.verbose)
|
|
213
|
+
process.stdout.write(chalk.red(`Failed\n`));
|
|
214
|
+
console.error(chalk.red(`❌ Could not remove plugin '${packageName}'.`));
|
|
215
|
+
if (opts.verbose) {
|
|
216
|
+
console.error(chalk.dim(err.message));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export { installPlugin, removePlugin };
|
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docmd/plugin-installer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Installer utility to add and remove plugins for docmd.",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
6
14
|
"dependencies": {
|
|
7
15
|
"chalk": "^4.1.2"
|
|
8
16
|
},
|
|
@@ -33,5 +41,8 @@
|
|
|
33
41
|
},
|
|
34
42
|
"homepage": "https://docmd.io",
|
|
35
43
|
"funding": "https://github.com/sponsors/mgks",
|
|
36
|
-
"license": "MIT"
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^25.4.0"
|
|
47
|
+
}
|
|
37
48
|
}
|
package/index.js
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
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/registry/plugins.json
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
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
|
-
}
|