Skip to content

Commit 6e47b65

Browse files
authored
Merge pull request #704 from objectstack-ai/copilot/migrate-cli-architecture-to-oclif
2 parents b3d5378 + b3a8a81 commit 6e47b65

37 files changed

Lines changed: 1642 additions & 1354 deletions

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
"description": "ObjectStack Protocol & Specification - Monorepo for TypeScript Interfaces, JSON Schemas, and Convention Configurations",
66
"scripts": {
77
"build": "turbo run build --filter=!@objectstack/docs",
8-
"dev": "pnpm --filter @objectstack/cli build && node packages/cli/bin/objectstack.js serve --dev",
8+
"dev": "pnpm --filter @objectstack/cli build && node packages/cli/bin/run.js serve --dev",
99
"dev:studio": "pnpm --filter @objectstack/studio dev",
10-
"studio": "pnpm --filter @objectstack/cli build && node packages/cli/bin/objectstack.js studio",
10+
"studio": "pnpm --filter @objectstack/cli build && node packages/cli/bin/run.js studio",
1111
"test": "turbo run test --filter=@objectstack/spec",
1212
"clean": "turbo run clean && rm -rf dist",
13-
"doctor": "pnpm --filter @objectstack/cli build && node packages/cli/bin/objectstack.js doctor",
13+
"doctor": "pnpm --filter @objectstack/cli build && node packages/cli/bin/run.js doctor",
1414
"setup": "pnpm install && pnpm --filter @objectstack/spec build",
1515
"version": "changeset version",
1616
"release": "pnpm run build && changeset publish",

packages/cli/README.md

Lines changed: 98 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Command Line Interface for building metadata-driven applications with the ObjectStack Protocol.
44

5+
Built on [oclif](https://oclif.io/) — commands are auto-discovered, and plugins can extend the CLI without modifying the main package.
6+
57
## Installation
68

79
```bash
@@ -40,6 +42,7 @@ os compile
4042
| `os init [name]` | Initialize a new ObjectStack project in the current directory |
4143
| `os dev [package]` | Start development mode with hot reload |
4244
| `os serve [config]` | Start the ObjectStack server with plugin auto-detection |
45+
| `os studio [config]` | Launch Studio UI with development server |
4346

4447
### Build & Validate
4548

@@ -73,6 +76,20 @@ Available generate types: `object`, `view`, `action`, `flow`, `agent`, `dashboar
7376
|---------|-------------|
7477
| `os test [files]` | Run Quality Protocol test scenarios against a running server |
7578
| `os doctor` | Check development environment health |
79+
| `os lint [config]` | Check configuration for style and convention issues |
80+
| `os diff [before] [after]` | Compare two configurations and detect breaking changes |
81+
82+
### Reference
83+
84+
| Command | Description |
85+
|---------|-------------|
86+
| `os explain [schema]` | Display human-readable explanation of an ObjectStack schema |
87+
88+
### Code Transforms
89+
90+
| Command | Description |
91+
|---------|-------------|
92+
| `os codemod v2-to-v3` | Migrate ObjectStack v2 config to v3 format |
7693

7794
## Configuration
7895

@@ -120,12 +137,12 @@ export default defineStack({
120137

121138
- `-p, --port <port>` — Server port (default: `3000`)
122139
- `--dev` — Run in development mode (load devPlugins, pretty logging)
140+
- `--ui` — Enable Studio UI
123141
- `--no-server` — Skip starting HTTP server plugin
124142

125143
### `os generate`
126144

127145
- `-d, --dir <directory>` — Override target directory
128-
- `--dry-run` — Preview without writing files
129146

130147
### `os plugin list`
131148

@@ -140,75 +157,78 @@ export default defineStack({
140157

141158
- `-c, --config <path>` — Configuration file path
142159

143-
## Plugin CLI Extensions
160+
### `os info`
161+
162+
- `--json` — Output as JSON
144163

145-
Plugins can extend the CLI with custom commands via the `contributes.commands` manifest field. The CLI automatically discovers and loads these commands at startup.
164+
### `os doctor`
146165

147-
### How to Create a CLI Plugin
166+
- `-v, --verbose` — Show fix suggestions for warnings
167+
- `--scan-deprecations` — Scan for deprecated patterns
148168

149-
**1. Declare commands in the plugin manifest:**
169+
## oclif Plugin System
150170

151-
```typescript
152-
export default defineStack({
153-
manifest: {
154-
id: 'com.acme.marketplace',
155-
version: '1.0.0',
156-
type: 'plugin',
157-
name: 'Marketplace Plugin',
158-
contributes: {
159-
commands: [
160-
{
161-
name: 'marketplace',
162-
description: 'Manage marketplace applications',
163-
module: './dist/cli.js',
164-
},
165-
],
166-
},
167-
},
168-
});
171+
The CLI uses oclif's built-in plugin system for extensibility. Third-party plugins (e.g., cloud commands, marketplace tools) can extend the CLI without modifying the main package.
172+
173+
### How Plugin Extension Works
174+
175+
1. **Create an oclif plugin package** with its own `oclif` config in `package.json`
176+
2. **Export oclif Command classes** from the plugin's `src/commands/` directory
177+
3. **Install the plugin** via `os plugins install <package>` or declare it in the main CLI's `oclif.plugins`
178+
179+
### Creating a CLI Plugin
180+
181+
**1. Configure the plugin's `package.json`:**
182+
183+
```json
184+
{
185+
"name": "@acme/plugin-marketplace",
186+
"oclif": {
187+
"commands": {
188+
"strategy": "pattern",
189+
"target": "./dist/commands",
190+
"glob": "**/*.js"
191+
}
192+
}
193+
}
169194
```
170195

171-
**2. Export Commander.js commands from the module:**
196+
**2. Create oclif Command classes:**
172197

173198
```typescript
174-
// src/cli.ts
175-
import { Command } from 'commander';
176-
177-
const marketplaceCommand = new Command('marketplace')
178-
.description('Manage marketplace applications')
179-
.addCommand(
180-
new Command('search')
181-
.argument('<query>')
182-
.action(async (query) => { /* ... */ })
183-
)
184-
.addCommand(
185-
new Command('install')
186-
.argument('<app>')
187-
.action(async (app) => { /* ... */ })
188-
);
189-
190-
// Named export (recommended)
191-
export const commands = [marketplaceCommand];
192-
// Also supports: export default Command | Command[]
199+
// src/commands/marketplace/search.ts
200+
import { Args, Command, Flags } from '@oclif/core';
201+
202+
export default class MarketplaceSearch extends Command {
203+
static override description = 'Search marketplace applications';
204+
205+
static override args = {
206+
query: Args.string({ description: 'Search query', required: true }),
207+
};
208+
209+
async run() {
210+
const { args } = await this.parse(MarketplaceSearch);
211+
// Implementation...
212+
}
213+
}
193214
```
194215

195-
**3. Register the plugin in the host project and use:**
216+
**3. Install and use:**
196217

197218
```bash
198-
os plugin add @acme/plugin-marketplace
219+
os plugins install @acme/plugin-marketplace
199220
os marketplace search "crm"
200-
os marketplace install com.acme.crm
201221
```
202222

203-
For a complete guide, see the [Plugin CLI Extensions](/docs/guides/plugins#cli-command-extensions) section in the Plugins guide.
223+
### Key Differences from Previous Plugin Model
204224

205-
### `os info`
206-
207-
- `--json` — Output as JSON
208-
209-
### `os doctor`
210-
211-
- `-v, --verbose` — Show fix suggestions for warnings
225+
| Before (Commander.js) | After (oclif) |
226+
|---|---|
227+
| Plugins declared in `objectstack.config.ts` | Plugins installed via `os plugins install` or `oclif.plugins` |
228+
| Custom `loadPluginCommands` mechanism | oclif's built-in plugin discovery |
229+
| `contributes.commands` in manifest | `oclif.commands` in `package.json` |
230+
| Commander.js `new Command(...)` exports | oclif `class extends Command` exports |
231+
| Project config determines CLI commands | CLI commands available without project init |
212232

213233
## Typical Workflow
214234

@@ -223,6 +243,30 @@ os dev # 7. Start dev server
223243
os compile # 8. Build for production
224244
```
225245

246+
## Architecture
247+
248+
```
249+
@objectstack/cli (oclif)
250+
├── bin/run.js # Entry point (os / objectstack)
251+
├── src/commands/ # Auto-discovered command classes
252+
│ ├── init.ts # os init
253+
│ ├── dev.ts # os dev
254+
│ ├── serve.ts # os serve
255+
│ ├── compile.ts # os compile
256+
│ ├── validate.ts # os validate
257+
│ ├── generate.ts # os generate (alias: g)
258+
│ ├── plugin/ # os plugin <subcommand>
259+
│ │ ├── list.ts
260+
│ │ ├── info.ts
261+
│ │ ├── add.ts
262+
│ │ └── remove.ts
263+
│ ├── codemod/ # os codemod <subcommand>
264+
│ │ └── v2-to-v3.ts
265+
│ └── ...
266+
├── src/utils/ # Shared utilities
267+
└── package.json # oclif config under "oclif" key
268+
```
269+
226270
## License
227271

228272
Apache-2.0

packages/cli/bin/objectstack.js

Lines changed: 0 additions & 2 deletions
This file was deleted.

packages/cli/bin/run-dev.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env tsx
2+
3+
import { execute } from '@oclif/core';
4+
5+
await execute({ type: 'esm', development: true, dir: import.meta.url });

packages/cli/bin/run.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env node
2+
3+
import { execute } from '@oclif/core';
4+
5+
await execute({ type: 'esm', dir: import.meta.url });

packages/cli/package.json

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,41 @@
33
"version": "3.0.6",
44
"description": "Command Line Interface for ObjectStack Protocol",
55
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
67
"bin": {
7-
"objectstack": "./bin/objectstack.js",
8-
"os": "./bin/objectstack.js"
8+
"objectstack": "./bin/run.js",
9+
"os": "./bin/run.js"
910
},
1011
"scripts": {
11-
"build": "tsup",
12-
"dev": "tsup --watch",
12+
"build": "tsc -p tsconfig.build.json",
13+
"dev": "tsc -p tsconfig.build.json --watch",
1314
"test": "vitest run",
1415
"lint": "eslint src"
1516
},
1617
"keywords": [
1718
"objectstack",
1819
"cli",
20+
"oclif",
1921
"compiler",
2022
"scaffold"
2123
],
2224
"type": "module",
2325
"author": "Steedos",
2426
"license": "Apache-2.0",
27+
"oclif": {
28+
"bin": "os",
29+
"dirname": "objectstack",
30+
"commands": {
31+
"strategy": "pattern",
32+
"target": "./dist/commands",
33+
"glob": "**/*.js"
34+
},
35+
"plugins": [
36+
"@oclif/plugin-help",
37+
"@oclif/plugin-plugins"
38+
],
39+
"topicSeparator": " "
40+
},
2541
"dependencies": {
2642
"@objectstack/core": "workspace:*",
2743
"@objectstack/driver-memory": "workspace:^",
@@ -30,16 +46,18 @@
3046
"@objectstack/rest": "workspace:*",
3147
"@objectstack/runtime": "workspace:^",
3248
"@objectstack/spec": "workspace:*",
49+
"@oclif/core": "^4.8.0",
3350
"bundle-require": "^5.1.0",
3451
"chalk": "^5.3.0",
35-
"commander": "^14.0.3",
3652
"tsx": "^4.7.1",
3753
"zod": "^4.3.6"
3854
},
3955
"peerDependencies": {
4056
"@objectstack/core": "workspace:*"
4157
},
4258
"devDependencies": {
59+
"@oclif/plugin-help": "^6.2.37",
60+
"@oclif/plugin-plugins": "^5.4.56",
4361
"@types/node": "^25.2.2",
4462
"tsup": "^8.0.2",
4563
"typescript": "^5.3.3",

0 commit comments

Comments
 (0)