Skip to content

Commit 5964652

Browse files
committed
feat: add commands to remove services and plugins with options for confirmation and file retention
1 parent 8fe2d77 commit 5964652

File tree

5 files changed

+609
-20
lines changed

5 files changed

+609
-20
lines changed

README.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ create-polyglot dev --docker
167167
| `create-polyglot init <name>` | Scaffold a new workspace (root invocation without `init` is deprecated). |
168168
| `create-polyglot add service <name>` | Add a service after init (`--type`, `--port`, `--yes`). |
169169
| `create-polyglot add plugin <name>` | Create plugin skeleton under `plugins/<name>`. |
170+
| `create-polyglot remove service <name>` | Remove a service from the workspace (`--keep-files`, `--yes`). |
171+
| `create-polyglot remove plugin <name>` | Remove a plugin from the workspace (`--keep-files`, `--yes`). |
170172
| `create-polyglot dev [--docker]` | Run Node & frontend services locally or all via compose. |
171173

172174
## Init Options
@@ -227,6 +229,50 @@ my-org/
227229
4. Edit services under `services/<name>`.
228230
5. Extend infra / databases inside `compose.yaml`.
229231

232+
## Managing Services & Plugins
233+
234+
### Adding Components
235+
Add services after initial scaffolding:
236+
```bash
237+
create-polyglot add service payments --type node --port 4100
238+
create-polyglot add plugin analytics
239+
```
240+
241+
### Removing Components
242+
Clean removal of services and plugins:
243+
```bash
244+
# Remove a service (including files and configuration)
245+
create-polyglot remove service payments --yes
246+
247+
# Remove a plugin
248+
create-polyglot remove plugin analytics --yes
249+
250+
# Keep files but remove from configuration
251+
create-polyglot remove service payments --keep-files --yes
252+
```
253+
254+
The remove commands:
255+
- **Remove from configuration**: Updates `polyglot.json`, `compose.yaml`, and root `package.json` scripts
256+
- **Clean up files**: Removes service/plugin directories unless `--keep-files` is specified
257+
- **Update dependencies**: Cleans up related logs and docker configurations
258+
- **Interactive confirmation**: Prompts for confirmation unless `--yes` is used
259+
- **Plugin management**: Also available via `create-polyglot plugin remove <name>`
260+
261+
### Service Removal Details
262+
When removing a service:
263+
- Service entry removed from `polyglot.json`
264+
- Service definition removed from `compose.yaml`
265+
- Service directory deleted from `services/<name>` (unless `--keep-files`)
266+
- Related scripts removed from root `package.json`
267+
- Log files cleaned up
268+
269+
### Plugin Removal Details
270+
When removing a plugin:
271+
- Plugin configuration removed from `polyglot.json`
272+
- Plugin directory deleted from `plugins/<name>` (unless `--keep-files`)
273+
- Plugin unloaded from the plugin system
274+
- External plugin references removed
275+
230276
### Basic Dev Runner
231277
When no preset is chosen, `npm run dev` uses `npx create-polyglot dev`:
232278
1. Detects package manager (pnpm > yarn > bun > npm fallback)
@@ -291,7 +337,6 @@ Generates ESLint + Prettier base configs at the root. Extend rules per service i
291337
- Automatic test harness & CI workflow template
292338
- Language-specific shared libs (Python package, Go module)
293339
- Hot reload integration aggregator
294-
- Remove service / remove plugin commands
295340

296341
## Contributing
297342
Contributions welcome! See `CONTRIBUTING.md` for guidelines. Please run tests before submitting a PR:
@@ -489,7 +534,6 @@ See [`docs/automated-release-notes.md`](docs/automated-release-notes.md) for det
489534
- Automatic test harness & CI workflow template
490535
- Language-specific shared libs (Python package, Go module)
491536
- Hot reload integration aggregator
492-
- Remove service / remove plugin commands
493537

494538
## License
495539
MIT

bin/index.js

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { Command } from 'commander';
33
import chalk from 'chalk';
44

5-
import { scaffoldMonorepo, addService, scaffoldPlugin } from './lib/scaffold.js';
5+
import { scaffoldMonorepo, addService, scaffoldPlugin, removeService, removePlugin } from './lib/scaffold.js';
66
import fs from 'fs';
77
import path from 'path';
88
import { renderServicesTable } from './lib/ui.js';
@@ -90,14 +90,38 @@ program
9090
} else if (entity === 'plugin') {
9191
await scaffoldPlugin(projectDir, name);
9292
} else {
93-
console.log(chalk.red(`Unknown entity '${entity}'. Use service or plugin.`));
93+
console.error(chalk.red(`Unknown entity '${entity}'. Use service or plugin.`));
9494
process.exit(1);
9595
}
9696
} catch (e) {
9797
console.error(chalk.red('Failed to add:'), e.message);
9898
process.exit(1);
9999
}
100100
});
101+
102+
program
103+
.command('remove')
104+
.description('Remove a service or plugin')
105+
.argument('<entity>', 'service | plugin')
106+
.argument('<name>', 'Name of the service or plugin')
107+
.option('--keep-files', 'Keep service files, only remove from configuration')
108+
.option('--yes', 'Skip confirmation prompt')
109+
.action(async (entity, name, opts) => {
110+
const projectDir = process.cwd();
111+
try {
112+
if (entity === 'service') {
113+
await removeService(projectDir, name, opts);
114+
} else if (entity === 'plugin') {
115+
await removePlugin(projectDir, name, opts);
116+
} else {
117+
console.error(chalk.red(`Unknown entity '${entity}'. Use service or plugin.`));
118+
process.exit(1);
119+
}
120+
} catch (e) {
121+
console.error(chalk.red('Failed to remove:'), e.message);
122+
process.exit(1);
123+
}
124+
});
101125

102126
program
103127
.command('dev')
@@ -407,6 +431,23 @@ pluginCmd
407431
}
408432
});
409433

434+
pluginCmd
435+
.command('remove')
436+
.description('Remove a plugin')
437+
.argument('<name>', 'Plugin name')
438+
.option('--keep-files', 'Keep plugin files, only remove from configuration')
439+
.option('--yes', 'Skip confirmation prompt')
440+
.action(async (name, opts) => {
441+
try {
442+
const { removePlugin } = await import('./lib/scaffold.js');
443+
const cwd = process.cwd();
444+
await removePlugin(cwd, name, opts);
445+
} catch (e) {
446+
console.error(chalk.red('Failed to remove plugin:'), e.message);
447+
process.exit(1);
448+
}
449+
});
450+
410451
pluginCmd
411452
.command('stats')
412453
.description('Show plugin system statistics')

bin/lib/plugin-system.js

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -128,25 +128,35 @@ class PluginSystem {
128128
}
129129

130130
/**
131-
* Discover local plugins in the .polyglot/plugins directory
131+
* Discover local plugins in both .polyglot/plugins and plugins directories
132132
*/
133133
async discoverLocalPlugins() {
134-
const pluginsDir = path.join(this.projectDir, '.polyglot', 'plugins');
135134
const localPlugins = [];
136-
137-
if (await fs.pathExists(pluginsDir)) {
138-
const entries = await fs.readdir(pluginsDir, { withFileTypes: true });
139-
140-
for (const entry of entries) {
141-
if (entry.isDirectory()) {
142-
const pluginPath = path.join(pluginsDir, entry.name, 'index.js');
143-
if (await fs.pathExists(pluginPath)) {
144-
localPlugins.push({
145-
name: entry.name,
146-
type: 'local',
147-
path: pluginPath,
148-
enabled: this.config?.plugins?.[entry.name]?.enabled !== false
149-
});
135+
136+
// Check both .polyglot/plugins and plugins directories for backward compatibility
137+
const pluginDirs = [
138+
path.join(this.projectDir, '.polyglot', 'plugins'),
139+
path.join(this.projectDir, 'plugins')
140+
];
141+
142+
for (const pluginsDir of pluginDirs) {
143+
if (await fs.pathExists(pluginsDir)) {
144+
const entries = await fs.readdir(pluginsDir, { withFileTypes: true });
145+
146+
for (const entry of entries) {
147+
if (entry.isDirectory()) {
148+
const pluginPath = path.join(pluginsDir, entry.name, 'index.js');
149+
if (await fs.pathExists(pluginPath)) {
150+
// Only add if not already added from another directory
151+
if (!localPlugins.find(p => p.name === entry.name)) {
152+
localPlugins.push({
153+
name: entry.name,
154+
type: 'local',
155+
path: pluginPath,
156+
enabled: this.config?.plugins?.[entry.name]?.enabled !== false
157+
});
158+
}
159+
}
150160
}
151161
}
152162
}

0 commit comments

Comments
 (0)