@objectstack/cli 0.8.2 โ†’ 0.9.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @objectstack/cli
2
2
 
3
+ ## 0.9.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Patch release for maintenance and stability improvements. All packages updated with unified versioning.
8
+ - Updated dependencies
9
+ - @objectstack/spec@0.9.1
10
+ - @objectstack/core@0.9.1
11
+ - @objectstack/objectql@0.9.1
12
+ - @objectstack/runtime@0.9.1
13
+ - @objectstack/driver-memory@0.9.1
14
+ - @objectstack/plugin-hono-server@0.9.1
15
+
3
16
  ## 0.8.2
4
17
 
5
18
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,487 @@
1
+ # @objectstack/cli
2
+
3
+ Command Line Interface for ObjectStack Protocol - Development tools for building, serving, and managing ObjectStack applications.
4
+
5
+ ## Features
6
+
7
+ - ๐Ÿš€ **Development Server** - Hot-reload development server with auto-discovery
8
+ - ๐Ÿ”จ **Build & Compile** - Validate and compile ObjectStack configurations to JSON
9
+ - ๐Ÿฅ **Environment Check** - Health check for development environment
10
+ - ๐Ÿ“ฆ **Project Scaffolding** - Generate plugins and examples from templates
11
+ - ๐Ÿงช **Test Runner** - Execute Quality Protocol test scenarios
12
+ - โšก **Auto-Configuration** - Smart defaults with minimal configuration
13
+
14
+ ## Installation
15
+
16
+ ### Global Installation (Recommended)
17
+
18
+ ```bash
19
+ npm install -g @objectstack/cli
20
+ # or
21
+ pnpm add -g @objectstack/cli
22
+ ```
23
+
24
+ ### Local Development
25
+
26
+ ```bash
27
+ pnpm add -D @objectstack/cli
28
+ ```
29
+
30
+ ## Commands
31
+
32
+ ### `objectstack serve`
33
+
34
+ Start an ObjectStack server with automatic plugin loading and configuration.
35
+
36
+ ```bash
37
+ # Start server with default config (objectstack.config.ts)
38
+ objectstack serve
39
+
40
+ # Specify custom config file
41
+ objectstack serve my-config.ts
42
+
43
+ # Custom port
44
+ objectstack serve --port 8080
45
+
46
+ # Development mode (loads devPlugins)
47
+ objectstack serve --dev
48
+
49
+ # Skip HTTP server plugin
50
+ objectstack serve --no-server
51
+ ```
52
+
53
+ **Features:**
54
+ - Auto-detects and loads `objectstack.config.ts`
55
+ - Auto-injects ObjectQL Engine if objects are defined
56
+ - Auto-injects Memory Driver in development mode
57
+ - Auto-registers AppPlugin for app configurations
58
+ - Finds available port if requested port is in use
59
+ - Pretty logging in development mode
60
+ - Graceful shutdown on SIGINT (Ctrl+C)
61
+
62
+ **Example:**
63
+
64
+ ```typescript
65
+ // objectstack.config.ts
66
+ import { defineStack } from '@objectstack/spec';
67
+
68
+ export default defineStack({
69
+ manifest: {
70
+ name: 'my-app',
71
+ version: '1.0.0'
72
+ },
73
+ objects: [
74
+ {
75
+ name: 'todo_task',
76
+ fields: {
77
+ subject: { type: 'text', label: 'Subject' },
78
+ completed: { type: 'boolean', default: false }
79
+ }
80
+ }
81
+ ]
82
+ });
83
+ ```
84
+
85
+ ```bash
86
+ $ objectstack serve --dev
87
+
88
+ ๐Ÿš€ ObjectStack Server
89
+ ------------------------
90
+ ๐Ÿ“‚ Config: objectstack.config.ts
91
+ ๐ŸŒ Port: 3000
92
+
93
+ ๐Ÿ“ฆ Loading configuration...
94
+ โœ“ Configuration loaded
95
+ ๐Ÿ”ง Initializing ObjectStack kernel...
96
+ Auto-injecting ObjectQL Engine...
97
+ โœ“ Registered ObjectQL Plugin (auto-detected)
98
+ Auto-injecting Memory Driver (Dev Mode)...
99
+ โœ“ Registered Memory Driver (auto-detected)
100
+ โœ“ Registered App Plugin (auto-detected)
101
+ โœ“ Registered HTTP server plugin (port: 3000)
102
+
103
+ ๐Ÿš€ Starting ObjectStack...
104
+
105
+ โœ… ObjectStack server is running!
106
+ Press Ctrl+C to stop
107
+ ```
108
+
109
+ ### `objectstack dev`
110
+
111
+ Start development mode with watch and hot-reload capabilities.
112
+
113
+ ```bash
114
+ # Single package mode (if objectstack.config.ts exists in CWD)
115
+ objectstack dev
116
+
117
+ # Monorepo mode - run dev for all packages
118
+ objectstack dev
119
+
120
+ # Monorepo mode - filter specific package
121
+ objectstack dev @objectstack/core
122
+ ```
123
+
124
+ **Behavior:**
125
+ - **Single Package Mode**: If `objectstack.config.ts` exists in current directory, delegates to `objectstack serve --dev` to start a development server with hot reload
126
+ - **Monorepo Mode**: If `pnpm-workspace.yaml` exists in current directory (workspace root), executes `pnpm dev` with optional package filter to run development builds across multiple packages in the workspace. The command uses pnpm's workspace filtering to target specific packages or all packages.
127
+
128
+ ### `objectstack compile`
129
+
130
+ Compile and validate ObjectStack configuration files.
131
+
132
+ ```bash
133
+ # Compile default config
134
+ objectstack compile
135
+
136
+ # Custom source and output
137
+ objectstack compile src/config.ts build/app.json
138
+ ```
139
+
140
+ **Features:**
141
+ - Bundles TypeScript configuration files
142
+ - Validates against ObjectStack Protocol using Zod schemas
143
+ - Outputs optimized JSON artifact
144
+ - Shows validation errors with exact field paths
145
+ - Build time and artifact size reporting
146
+
147
+ **Example:**
148
+
149
+ ```bash
150
+ $ objectstack compile
151
+
152
+ ๐Ÿ”น ObjectStack Compiler v0.1
153
+ ------------------------------
154
+ ๐Ÿ“‚ Source: objectstack.config.ts
155
+ ๐Ÿ“ฆ Bundling Configuration...
156
+ ๐Ÿ” Validating Protocol Compliance...
157
+
158
+ โœ… Build Success (234ms)
159
+ ๐Ÿ“ฆ Artifact: dist/objectstack.json (12.45 KB)
160
+ โœจ Ready for Deployment
161
+ ```
162
+
163
+ ### `objectstack doctor`
164
+
165
+ Check development environment health and dependencies.
166
+
167
+ ```bash
168
+ # Quick health check
169
+ objectstack doctor
170
+
171
+ # Detailed information with fix suggestions
172
+ objectstack doctor --verbose
173
+ ```
174
+
175
+ **Checks:**
176
+ - โœ“ Node.js version (>= 18.0.0 required)
177
+ - โœ“ pnpm package manager
178
+ - โœ“ TypeScript compiler
179
+ - โœ“ Dependencies installation status
180
+ - โœ“ @objectstack/spec build status
181
+ - โœ“ Git installation
182
+
183
+ **Example Output:**
184
+
185
+ ```bash
186
+ $ objectstack doctor
187
+
188
+ ๐Ÿฅ ObjectStack Environment Health Check
189
+ -----------------------------------------
190
+
191
+ โœ“ Node.js Version v20.10.0
192
+ โœ“ pnpm Version 10.28.1
193
+ โœ“ TypeScript Version 5.3.3
194
+ โœ“ Dependencies Installed
195
+ โœ“ @objectstack/spec Built
196
+ โœ“ Git git version 2.39.0
197
+
198
+ โœ… Environment is healthy and ready for development!
199
+ ```
200
+
201
+ ### `objectstack create`
202
+
203
+ Create new plugins or examples from templates.
204
+
205
+ ```bash
206
+ # Create a plugin
207
+ objectstack create plugin my-feature
208
+
209
+ # Create an example
210
+ objectstack create example todo-app
211
+
212
+ # Custom directory
213
+ objectstack create plugin auth --dir custom/path
214
+ ```
215
+
216
+ **Templates:**
217
+
218
+ #### Plugin Template
219
+ Creates a fully-configured ObjectStack plugin with:
220
+ - `package.json` with dependencies
221
+ - TypeScript configuration
222
+ - Plugin implementation boilerplate
223
+ - README with usage examples
224
+ - Test setup with Vitest
225
+
226
+ #### Example Template
227
+ Creates an ObjectStack example application with:
228
+ - `objectstack.config.ts` configuration
229
+ - Build and dev scripts
230
+ - TypeScript configuration
231
+ - Example README
232
+
233
+ **Example:**
234
+
235
+ ```bash
236
+ $ objectstack create plugin authentication
237
+
238
+ ๐Ÿ“ฆ ObjectStack Project Creator
239
+ -------------------------------
240
+ ๐Ÿ“ Creating plugin: authentication
241
+ ๐Ÿ“‚ Location: packages/plugins/plugin-authentication
242
+
243
+ โœ“ Created package.json
244
+ โœ“ Created tsconfig.json
245
+ โœ“ Created src/index.ts
246
+ โœ“ Created README.md
247
+
248
+ โœ… Project created successfully!
249
+
250
+ Next steps:
251
+ cd packages/plugins/plugin-authentication
252
+ pnpm install
253
+ pnpm build
254
+ ```
255
+
256
+ ### `objectstack test:run`
257
+
258
+ Run Quality Protocol test scenarios against your ObjectStack server.
259
+
260
+ ```bash
261
+ # Run all tests in qa/ directory
262
+ objectstack test:run
263
+
264
+ # Specific test file or pattern
265
+ objectstack test:run qa/api-tests.json
266
+
267
+ # Custom target URL
268
+ objectstack test:run --url http://staging.example.com
269
+
270
+ # With authentication
271
+ objectstack test:run --token "Bearer xyz..."
272
+ ```
273
+
274
+ **Features:**
275
+ - Executes test scenarios from JSON files
276
+ - HTTP-based test adapter
277
+ - Detailed step-by-step output
278
+ - Pass/fail reporting with timing
279
+ - Authentication support
280
+
281
+ ## Configuration File
282
+
283
+ The CLI expects an `objectstack.config.ts` file in your project root:
284
+
285
+ ```typescript
286
+ import { defineStack } from '@objectstack/spec';
287
+ import type { ObjectStackDefinition } from '@objectstack/spec';
288
+
289
+ export default defineStack({
290
+ // App metadata
291
+ manifest: {
292
+ name: 'my-app',
293
+ version: '1.0.0',
294
+ description: 'My ObjectStack Application'
295
+ },
296
+
297
+ // Data objects
298
+ objects: [
299
+ {
300
+ name: 'customer',
301
+ label: 'Customer',
302
+ fields: {
303
+ name: { type: 'text', label: 'Name', required: true },
304
+ email: { type: 'email', label: 'Email' },
305
+ phone: { type: 'phone', label: 'Phone' }
306
+ }
307
+ }
308
+ ],
309
+
310
+ // UI applications
311
+ apps: [
312
+ {
313
+ id: 'crm',
314
+ name: 'CRM',
315
+ objects: ['customer', 'opportunity']
316
+ }
317
+ ],
318
+
319
+ // Runtime plugins
320
+ plugins: [
321
+ // Your production plugins
322
+ ],
323
+
324
+ // Development-only plugins
325
+ devPlugins: [
326
+ // Loaded only with --dev flag
327
+ ]
328
+ });
329
+ ```
330
+
331
+ ## Usage in Monorepo
332
+
333
+ The CLI is monorepo-aware and works seamlessly in pnpm workspaces:
334
+
335
+ ```bash
336
+ # From workspace root
337
+ pnpm --filter @objectstack/spec build
338
+ pnpm --filter my-app dev
339
+
340
+ # Or use the CLI shorthand
341
+ objectstack dev @objectstack/spec
342
+ ```
343
+
344
+ ## Environment Variables
345
+
346
+ ```bash
347
+ # Server port (overridden by --port flag)
348
+ PORT=3000
349
+
350
+ # Node environment
351
+ NODE_ENV=development|production
352
+ ```
353
+
354
+ ## Aliases
355
+
356
+ The CLI binary is available as both `objectstack` and `os`:
357
+
358
+ ```bash
359
+ objectstack serve
360
+ # or
361
+ os serve
362
+ ```
363
+
364
+ ## Development Workflow
365
+
366
+ ### New Project Setup
367
+
368
+ ```bash
369
+ # 1. Create a new example
370
+ objectstack create example my-project
371
+
372
+ # 2. Navigate to project
373
+ cd examples/my-project
374
+
375
+ # 3. Install dependencies
376
+ pnpm install
377
+
378
+ # 4. Start development server
379
+ objectstack dev
380
+ ```
381
+
382
+ ### Plugin Development
383
+
384
+ ```bash
385
+ # 1. Create plugin
386
+ objectstack create plugin my-feature
387
+
388
+ # 2. Implement plugin logic
389
+ # Edit packages/plugins/plugin-my-feature/src/index.ts
390
+
391
+ # 3. Build and test
392
+ cd packages/plugins/plugin-my-feature
393
+ pnpm build
394
+ pnpm test
395
+ ```
396
+
397
+ ### Production Build
398
+
399
+ ```bash
400
+ # 1. Compile configuration
401
+ objectstack compile
402
+
403
+ # 2. Run production server
404
+ NODE_ENV=production objectstack serve --port 8080
405
+ ```
406
+
407
+ ## Integration with Package Scripts
408
+
409
+ Add CLI commands to your `package.json`:
410
+
411
+ ```json
412
+ {
413
+ "scripts": {
414
+ "build": "objectstack compile",
415
+ "dev": "objectstack dev",
416
+ "serve": "objectstack serve",
417
+ "test": "objectstack test:run",
418
+ "doctor": "objectstack doctor"
419
+ }
420
+ }
421
+ ```
422
+
423
+ ## Troubleshooting
424
+
425
+ ### Port Already in Use
426
+
427
+ The CLI automatically finds an available port if the requested port is in use:
428
+
429
+ ```bash
430
+ ๐ŸŒ Port: 3001 (requested: 3000 in use)
431
+ ```
432
+
433
+ ### Configuration Not Found
434
+
435
+ Ensure your config file exists and is properly named:
436
+
437
+ ```bash
438
+ โŒ Configuration file not found: /path/to/objectstack.config.ts
439
+ ```
440
+
441
+ Default file name: `objectstack.config.ts`
442
+
443
+ ### Validation Errors
444
+
445
+ The compiler will show detailed validation errors:
446
+
447
+ ```bash
448
+ โŒ Validation Failed!
449
+ - [objects.0.name] Expected snake_case, received "MyObject"
450
+ - [objects.0.fields.name.type] Invalid enum value. Expected 'text' | 'number' | ...
451
+ ```
452
+
453
+ ### Environment Issues
454
+
455
+ Run the doctor command to diagnose:
456
+
457
+ ```bash
458
+ objectstack doctor --verbose
459
+ ```
460
+
461
+ ## API Reference
462
+
463
+ ### Command Options
464
+
465
+ All commands support `--help` for detailed usage:
466
+
467
+ ```bash
468
+ objectstack --help
469
+ objectstack serve --help
470
+ objectstack compile --help
471
+ ```
472
+
473
+ ### Exit Codes
474
+
475
+ - `0` - Success
476
+ - `1` - Error (validation failed, file not found, etc.)
477
+
478
+ ## Related Packages
479
+
480
+ - [@objectstack/spec](../spec) - Protocol definitions and schemas
481
+ - [@objectstack/core](../core) - Microkernel runtime
482
+ - [@objectstack/objectql](../objectql) - Data query engine
483
+ - [@objectstack/runtime](../runtime) - Runtime utilities and plugins
484
+
485
+ ## License
486
+
487
+ MIT
package/dist/bin.js CHANGED
@@ -4,20 +4,44 @@ import {
4
4
  } from "./chunk-2YXVEYO7.js";
5
5
 
6
6
  // src/bin.ts
7
- import { Command as Command5 } from "commander";
7
+ import { createRequire } from "module";
8
+ import { Command as Command6 } from "commander";
8
9
 
9
10
  // src/commands/dev.ts
10
11
  import { Command } from "commander";
11
12
  import chalk from "chalk";
12
- import { execSync } from "child_process";
13
- var devCommand = new Command("dev").description("Start development mode for a package").argument("[package]", "Package name (without @objectstack/ prefix)", "all").option("-w, --watch", "Enable watch mode (default)", true).option("-v, --verbose", "Verbose output").action(async (packageName, options) => {
13
+ import { execSync, spawn } from "child_process";
14
+ import fs from "fs";
15
+ import path from "path";
16
+ var devCommand = new Command("dev").description("Start development mode for a package").argument("[package]", "Package name or filter pattern", "all").option("-w, --watch", "Enable watch mode (default)", true).option("-v, --verbose", "Verbose output").action(async (packageName, options) => {
14
17
  console.log(chalk.bold(`
15
18
  \u{1F680} ObjectStack Development Mode`));
16
19
  console.log(chalk.dim(`-------------------------------`));
20
+ const configPath = path.resolve(process.cwd(), "objectstack.config.ts");
21
+ if (packageName === "all" && fs.existsSync(configPath)) {
22
+ console.log(chalk.blue(`\u{1F4C2} Detected package config: ${configPath}`));
23
+ console.log(chalk.green(`\u26A1 Starting Dev Server...`));
24
+ console.log("");
25
+ const binPath = process.argv[1];
26
+ const child = spawn(process.execPath, [binPath, "serve", "--dev", ...options.verbose ? ["--verbose"] : []], {
27
+ stdio: "inherit",
28
+ env: { ...process.env, NODE_ENV: "development" }
29
+ });
30
+ return;
31
+ }
17
32
  try {
18
33
  const cwd = process.cwd();
19
- const filter = packageName === "all" ? "" : `--filter @objectstack/${packageName}`;
20
- console.log(`\u{1F4E6} Package: ${chalk.blue(packageName === "all" ? "All packages" : `@objectstack/${packageName}`)}`);
34
+ const workspaceConfigPath = path.resolve(cwd, "pnpm-workspace.yaml");
35
+ const isWorkspaceRoot = fs.existsSync(workspaceConfigPath);
36
+ if (packageName === "all" && !isWorkspaceRoot) {
37
+ console.error(chalk.red(`
38
+ \u274C Configuration file (objectstack.config.ts) not found in ${cwd}`));
39
+ console.error(chalk.yellow(` To start development mode, run this command in a directory with objectstack.config.ts`));
40
+ console.error(chalk.yellow(` OR run from the monorepo root to start all packages.`));
41
+ process.exit(1);
42
+ }
43
+ const filter = packageName === "all" ? "" : `--filter ${packageName}`;
44
+ console.log(`\u{1F4E6} Package: ${chalk.blue(packageName === "all" ? "All packages" : packageName)}`);
21
45
  console.log(`\u{1F504} Watch mode: ${chalk.green("enabled")}`);
22
46
  console.log("");
23
47
  const command = `pnpm ${filter} dev`.trim();
@@ -39,8 +63,8 @@ var devCommand = new Command("dev").description("Start development mode for a pa
39
63
  import { Command as Command2 } from "commander";
40
64
  import chalk2 from "chalk";
41
65
  import { execSync as execSync2 } from "child_process";
42
- import fs from "fs";
43
- import path from "path";
66
+ import fs2 from "fs";
67
+ import path2 from "path";
44
68
  var doctorCommand = new Command2("doctor").description("Check development environment health").option("-v, --verbose", "Show detailed information").action(async (options) => {
45
69
  console.log(chalk2.bold(`
46
70
  \u{1F3E5} ObjectStack Environment Health Check`));
@@ -103,8 +127,8 @@ var doctorCommand = new Command2("doctor").description("Check development enviro
103
127
  });
104
128
  }
105
129
  const cwd = process.cwd();
106
- const nodeModulesPath = path.join(cwd, "node_modules");
107
- if (fs.existsSync(nodeModulesPath)) {
130
+ const nodeModulesPath = path2.join(cwd, "node_modules");
131
+ if (fs2.existsSync(nodeModulesPath)) {
108
132
  results.push({
109
133
  name: "Dependencies",
110
134
  status: "ok",
@@ -118,8 +142,8 @@ var doctorCommand = new Command2("doctor").description("Check development enviro
118
142
  fix: "Run: pnpm install"
119
143
  });
120
144
  }
121
- const specDistPath = path.join(cwd, "packages/spec/dist");
122
- if (fs.existsSync(specDistPath)) {
145
+ const specDistPath = path2.join(cwd, "packages/spec/dist");
146
+ if (fs2.existsSync(specDistPath)) {
123
147
  results.push({
124
148
  name: "@objectstack/spec",
125
149
  status: "ok",
@@ -177,8 +201,8 @@ var doctorCommand = new Command2("doctor").description("Check development enviro
177
201
  // src/commands/create.ts
178
202
  import { Command as Command3 } from "commander";
179
203
  import chalk3 from "chalk";
180
- import fs2 from "fs";
181
- import path2 from "path";
204
+ import fs3 from "fs";
205
+ import path3 from "path";
182
206
  var templates = {
183
207
  plugin: {
184
208
  description: "Create a new ObjectStack plugin",
@@ -270,13 +294,13 @@ MIT
270
294
  description: "Create a new ObjectStack example application",
271
295
  files: {
272
296
  "package.json": (name) => ({
273
- name: `@objectstack/example-${name}`,
297
+ name: `@example/${name}`,
274
298
  version: "0.1.0",
275
299
  private: true,
276
300
  description: `ObjectStack Example: ${name}`,
277
301
  scripts: {
278
302
  build: "objectstack compile",
279
- dev: "tsx watch objectstack.config.ts",
303
+ dev: "objectstack dev",
280
304
  test: "vitest"
281
305
  },
282
306
  dependencies: {
@@ -294,20 +318,20 @@ MIT
294
318
  "objectstack.config.ts": (name) => `import { defineStack } from '@objectstack/spec';
295
319
 
296
320
  export default defineStack({
297
- metadata: {
321
+ manifest: {
298
322
  name: '${name}',
299
323
  version: '0.1.0',
300
324
  description: '${name} example application',
301
325
  },
302
326
 
303
- objects: {
327
+ objects: [
304
328
  // Define your data objects here
305
- },
329
+ // { name: 'my_object', fields: { ... } }
330
+ ],
306
331
 
307
- ui: {
308
- apps: [],
309
- views: [],
310
- },
332
+ apps: [
333
+ // Define your apps here
334
+ ],
311
335
  });
312
336
  `,
313
337
  "README.md": (name) => `# ${name} Example
@@ -367,13 +391,13 @@ var createCommand = new Command3("create").description("Create a new package, pl
367
391
  const cwd = process.cwd();
368
392
  let targetDir;
369
393
  if (options?.dir) {
370
- targetDir = path2.resolve(cwd, options.dir);
394
+ targetDir = path3.resolve(cwd, options.dir);
371
395
  } else {
372
396
  const baseDir = type === "plugin" ? "packages/plugins" : "examples";
373
397
  const projectName = type === "plugin" ? `plugin-${name}` : name;
374
- targetDir = path2.join(cwd, baseDir, projectName);
398
+ targetDir = path3.join(cwd, baseDir, projectName);
375
399
  }
376
- if (fs2.existsSync(targetDir)) {
400
+ if (fs3.existsSync(targetDir)) {
377
401
  console.error(chalk3.red(`
378
402
  \u274C Directory already exists: ${targetDir}`));
379
403
  process.exit(1);
@@ -382,31 +406,31 @@ var createCommand = new Command3("create").description("Create a new package, pl
382
406
  console.log(`\u{1F4C2} Location: ${chalk3.dim(targetDir)}`);
383
407
  console.log("");
384
408
  try {
385
- fs2.mkdirSync(targetDir, { recursive: true });
409
+ fs3.mkdirSync(targetDir, { recursive: true });
386
410
  for (const [filePath, contentFn] of Object.entries(template.files)) {
387
- const fullPath = path2.join(targetDir, filePath);
388
- const dir = path2.dirname(fullPath);
389
- if (!fs2.existsSync(dir)) {
390
- fs2.mkdirSync(dir, { recursive: true });
411
+ const fullPath = path3.join(targetDir, filePath);
412
+ const dir = path3.dirname(fullPath);
413
+ if (!fs3.existsSync(dir)) {
414
+ fs3.mkdirSync(dir, { recursive: true });
391
415
  }
392
416
  const content = contentFn(name);
393
417
  const fileContent = typeof content === "string" ? content : JSON.stringify(content, null, 2);
394
- fs2.writeFileSync(fullPath, fileContent);
418
+ fs3.writeFileSync(fullPath, fileContent);
395
419
  console.log(chalk3.green(`\u2713 Created ${filePath}`));
396
420
  }
397
421
  console.log("");
398
422
  console.log(chalk3.green("\u2705 Project created successfully!"));
399
423
  console.log("");
400
424
  console.log(chalk3.bold("Next steps:"));
401
- console.log(chalk3.dim(` cd ${path2.relative(cwd, targetDir)}`));
425
+ console.log(chalk3.dim(` cd ${path3.relative(cwd, targetDir)}`));
402
426
  console.log(chalk3.dim(" pnpm install"));
403
427
  console.log(chalk3.dim(" pnpm build"));
404
428
  console.log("");
405
429
  } catch (error) {
406
430
  console.error(chalk3.red("\n\u274C Failed to create project:"));
407
431
  console.error(error.message || error);
408
- if (fs2.existsSync(targetDir)) {
409
- fs2.rmSync(targetDir, { recursive: true });
432
+ if (fs3.existsSync(targetDir)) {
433
+ fs3.rmSync(targetDir, { recursive: true });
410
434
  }
411
435
  process.exit(1);
412
436
  }
@@ -414,8 +438,8 @@ var createCommand = new Command3("create").description("Create a new package, pl
414
438
 
415
439
  // src/commands/serve.ts
416
440
  import { Command as Command4 } from "commander";
417
- import path3 from "path";
418
- import fs3 from "fs";
441
+ import path4 from "path";
442
+ import fs4 from "fs";
419
443
  import net from "net";
420
444
  import chalk4 from "chalk";
421
445
  import { bundleRequire } from "bundle-require";
@@ -441,7 +465,7 @@ var getAvailablePort = async (startPort) => {
441
465
  }
442
466
  return port;
443
467
  };
444
- var serveCommand = new Command4("serve").description("Start ObjectStack server with plugins from configuration").argument("[config]", "Configuration file path", "objectstack.config.ts").option("-p, --port <port>", "Server port", "3000").option("--no-server", "Skip starting HTTP server plugin").action(async (configPath, options) => {
468
+ var serveCommand = new Command4("serve").description("Start ObjectStack server with plugins from configuration").argument("[config]", "Configuration file path", "objectstack.config.ts").option("-p, --port <port>", "Server port", "3000").option("--dev", "Run in development mode (load devPlugins)").option("--no-server", "Skip starting HTTP server plugin").action(async (configPath, options) => {
445
469
  let port = parseInt(options.port);
446
470
  try {
447
471
  const availablePort = await getAvailablePort(port);
@@ -460,8 +484,8 @@ var serveCommand = new Command4("serve").description("Start ObjectStack server w
460
484
  console.log(`\u{1F310} Port: ${chalk4.blue(port)}`);
461
485
  }
462
486
  console.log("");
463
- const absolutePath = path3.resolve(process.cwd(), configPath);
464
- if (!fs3.existsSync(absolutePath)) {
487
+ const absolutePath = path4.resolve(process.cwd(), configPath);
488
+ if (!fs4.existsSync(absolutePath)) {
465
489
  console.error(chalk4.red(`
466
490
  \u274C Configuration file not found: ${absolutePath}`));
467
491
  process.exit(1);
@@ -478,16 +502,65 @@ var serveCommand = new Command4("serve").description("Start ObjectStack server w
478
502
  console.log(chalk4.green(`\u2713 Configuration loaded`));
479
503
  const { ObjectKernel } = await import("@objectstack/core");
480
504
  console.log(chalk4.yellow(`\u{1F527} Initializing ObjectStack kernel...`));
505
+ const isDev = options.dev || process.env.NODE_ENV === "development";
506
+ const loggerConfig = isDev ? { format: "pretty" } : void 0;
481
507
  const kernel = new ObjectKernel({
482
508
  metadata: config.metadata || {},
483
- objects: config.objects || {}
509
+ objects: config.objects || {},
510
+ logger: loggerConfig
484
511
  });
485
- const plugins = config.plugins || [];
512
+ let plugins = config.plugins || [];
513
+ if (options.dev && config.devPlugins) {
514
+ console.log(chalk4.blue(`\u{1F4E6} Loading development plugins...`));
515
+ plugins = [...plugins, ...config.devPlugins];
516
+ }
517
+ const hasObjectQL = plugins.some((p) => p.name?.includes("objectql") || p.constructor?.name?.includes("ObjectQL"));
518
+ if (config.objects && !hasObjectQL) {
519
+ try {
520
+ console.log(chalk4.dim(` Auto-injecting ObjectQL Engine...`));
521
+ const { ObjectQLPlugin } = await import("@objectstack/objectql");
522
+ kernel.use(new ObjectQLPlugin());
523
+ console.log(chalk4.green(` \u2713 Registered ObjectQL Plugin (auto-detected)`));
524
+ } catch (e) {
525
+ console.warn(chalk4.yellow(` \u26A0 Could not auto-load ObjectQL: ${e.message}`));
526
+ }
527
+ }
528
+ const hasDriver = plugins.some((p) => p.name?.includes("driver") || p.constructor?.name?.includes("Driver"));
529
+ if (isDev && !hasDriver && config.objects) {
530
+ try {
531
+ console.log(chalk4.dim(` Auto-injecting Memory Driver (Dev Mode)...`));
532
+ const { DriverPlugin } = await import("@objectstack/runtime");
533
+ const { InMemoryDriver } = await import("@objectstack/driver-memory");
534
+ kernel.use(new DriverPlugin(new InMemoryDriver()));
535
+ console.log(chalk4.green(` \u2713 Registered Memory Driver (auto-detected)`));
536
+ } catch (e) {
537
+ console.log(chalk4.dim(` \u2139 No default driver loaded: ${e.message}`));
538
+ }
539
+ }
540
+ if (config.objects || config.manifest || config.apps) {
541
+ try {
542
+ const { AppPlugin } = await import("@objectstack/runtime");
543
+ kernel.use(new AppPlugin(config));
544
+ console.log(chalk4.green(` \u2713 Registered App Plugin (auto-detected)`));
545
+ } catch (e) {
546
+ console.warn(chalk4.yellow(` \u26A0 Could not auto-load AppPlugin: ${e.message}`));
547
+ }
548
+ }
486
549
  if (plugins.length > 0) {
487
550
  console.log(chalk4.yellow(`\u{1F4E6} Loading ${plugins.length} plugin(s)...`));
488
551
  for (const plugin of plugins) {
489
552
  try {
490
- kernel.use(plugin);
553
+ let pluginToLoad = plugin;
554
+ if (typeof plugin === "string") {
555
+ console.log(chalk4.dim(` Trying to resolve plugin: ${plugin}`));
556
+ try {
557
+ const imported = await import(plugin);
558
+ pluginToLoad = imported.default || imported;
559
+ } catch (importError) {
560
+ throw new Error(`Failed to import plugin '${plugin}': ${importError.message}`);
561
+ }
562
+ }
563
+ kernel.use(pluginToLoad);
491
564
  const pluginName = plugin.name || plugin.constructor?.name || "unnamed";
492
565
  console.log(chalk4.green(` \u2713 Registered plugin: ${pluginName}`));
493
566
  } catch (e) {
@@ -529,12 +602,87 @@ var serveCommand = new Command4("serve").description("Start ObjectStack server w
529
602
  }
530
603
  });
531
604
 
605
+ // src/commands/test.ts
606
+ import { Command as Command5 } from "commander";
607
+ import chalk5 from "chalk";
608
+ import path5 from "path";
609
+ import fs5 from "fs";
610
+ import { QA as CoreQA } from "@objectstack/core";
611
+ var testCommand = new Command5("test:run").description("Run Quality Protocol test scenarios").argument("[files]", 'Glob pattern for test files (e.g. "qa/*.test.json")', "qa/*.test.json").option("--url <url>", "Target base URL", "http://localhost:3000").option("--token <token>", "Authentication token").action(async (filesPattern, options) => {
612
+ console.log(chalk5.bold(`
613
+ \u{1F9EA} ObjectStack Quality Protocol Runner`));
614
+ console.log(chalk5.dim(`-------------------------------------`));
615
+ console.log(`Target: ${chalk5.blue(options.url)}`);
616
+ const adapter = new CoreQA.HttpTestAdapter(options.url, options.token);
617
+ const runner = new CoreQA.TestRunner(adapter);
618
+ const cwd = process.cwd();
619
+ const testFiles = [];
620
+ if (fs5.existsSync(filesPattern)) {
621
+ testFiles.push(filesPattern);
622
+ } else {
623
+ const dir = path5.dirname(filesPattern);
624
+ const ext = path5.extname(filesPattern);
625
+ if (fs5.existsSync(dir)) {
626
+ const files = fs5.readdirSync(dir).filter((f) => f.endsWith(ext) || f.endsWith(".json"));
627
+ files.forEach((f) => testFiles.push(path5.join(dir, f)));
628
+ }
629
+ }
630
+ if (testFiles.length === 0) {
631
+ console.warn(chalk5.yellow(`No test files found matching: ${filesPattern}`));
632
+ return;
633
+ }
634
+ console.log(`Found ${testFiles.length} test suites.`);
635
+ let totalPassed = 0;
636
+ let totalFailed = 0;
637
+ for (const file of testFiles) {
638
+ console.log(`
639
+ \u{1F4C4} Running suite: ${chalk5.bold(path5.basename(file))}`);
640
+ try {
641
+ const content = fs5.readFileSync(file, "utf-8");
642
+ const suite = JSON.parse(content);
643
+ const results = await runner.runSuite(suite);
644
+ for (const result of results) {
645
+ const icon = result.passed ? "\u2705" : "\u274C";
646
+ console.log(` ${icon} Scenario: ${result.scenarioId} (${result.duration}ms)`);
647
+ if (!result.passed) {
648
+ console.error(chalk5.red(` Error: ${result.error}`));
649
+ result.steps.forEach((step) => {
650
+ if (!step.passed) {
651
+ console.error(chalk5.red(` Step Failed: ${step.stepName}`));
652
+ if (step.output) console.error(` Output:`, step.output);
653
+ if (step.error) console.error(` Error:`, step.error);
654
+ }
655
+ });
656
+ totalFailed++;
657
+ } else {
658
+ totalPassed++;
659
+ }
660
+ }
661
+ } catch (e) {
662
+ console.error(chalk5.red(`Failed to load or run suite ${file}: ${e}`));
663
+ totalFailed++;
664
+ }
665
+ }
666
+ console.log(chalk5.dim(`
667
+ -------------------------------------`));
668
+ if (totalFailed > 0) {
669
+ console.log(chalk5.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
670
+ process.exit(1);
671
+ } else {
672
+ console.log(chalk5.green(`SUCCESS: All ${totalPassed} scenarios passed.`));
673
+ process.exit(0);
674
+ }
675
+ });
676
+
532
677
  // src/bin.ts
533
- var program = new Command5();
534
- program.name("objectstack").description("CLI for ObjectStack Protocol - Development Tools for Microkernel Architecture").version("0.8.0");
678
+ var require2 = createRequire(import.meta.url);
679
+ var pkg = require2("../package.json");
680
+ var program = new Command6();
681
+ program.name("objectstack").description("CLI for ObjectStack Protocol - Development Tools for Microkernel Architecture").version(pkg.version);
535
682
  program.addCommand(compileCommand);
536
683
  program.addCommand(serveCommand);
537
684
  program.addCommand(devCommand);
538
685
  program.addCommand(doctorCommand);
539
686
  program.addCommand(createCommand);
687
+ program.addCommand(testCommand);
540
688
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/cli",
3
- "version": "0.8.2",
3
+ "version": "0.9.1",
4
4
  "description": "Command Line Interface for ObjectStack Protocol",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -22,21 +22,26 @@
22
22
  "commander": "^11.1.0",
23
23
  "tsx": "^4.7.1",
24
24
  "zod": "^4.3.6",
25
- "@objectstack/spec": "0.8.2",
26
- "@objectstack/core": "0.8.2",
27
- "@objectstack/plugin-hono-server": "0.8.2"
25
+ "@objectstack/core": "0.9.1",
26
+ "@objectstack/driver-memory": "^0.9.1",
27
+ "@objectstack/objectql": "^0.9.1",
28
+ "@objectstack/plugin-hono-server": "0.9.1",
29
+ "@objectstack/runtime": "^0.9.1",
30
+ "@objectstack/spec": "0.9.1"
28
31
  },
29
32
  "peerDependencies": {
30
- "@objectstack/core": "^0.8.2"
33
+ "@objectstack/core": "^0.9.1"
31
34
  },
32
35
  "devDependencies": {
33
36
  "@types/node": "^25.1.0",
34
37
  "tsup": "^8.0.2",
35
- "typescript": "^5.3.3"
38
+ "typescript": "^5.3.3",
39
+ "vitest": "^4.0.18"
36
40
  },
37
41
  "scripts": {
38
42
  "build": "tsup",
39
43
  "dev": "tsup --watch",
44
+ "test": "vitest run",
40
45
  "lint": "eslint src"
41
46
  }
42
47
  }
package/src/bin.ts CHANGED
@@ -1,16 +1,21 @@
1
+ import { createRequire } from 'module';
1
2
  import { Command } from 'commander';
2
3
  import { compileCommand } from './commands/compile.js';
3
4
  import { devCommand } from './commands/dev.js';
4
5
  import { doctorCommand } from './commands/doctor.js';
5
6
  import { createCommand } from './commands/create.js';
6
7
  import { serveCommand } from './commands/serve.js';
8
+ import { testCommand } from './commands/test.js';
9
+
10
+ const require = createRequire(import.meta.url);
11
+ const pkg = require('../package.json');
7
12
 
8
13
  const program = new Command();
9
14
 
10
15
  program
11
16
  .name('objectstack')
12
17
  .description('CLI for ObjectStack Protocol - Development Tools for Microkernel Architecture')
13
- .version('0.8.0');
18
+ .version(pkg.version);
14
19
 
15
20
  // Add all commands
16
21
  program.addCommand(compileCommand);
@@ -18,5 +23,6 @@ program.addCommand(serveCommand);
18
23
  program.addCommand(devCommand);
19
24
  program.addCommand(doctorCommand);
20
25
  program.addCommand(createCommand);
26
+ program.addCommand(testCommand);
21
27
 
22
28
  program.parse(process.argv);
@@ -4,7 +4,7 @@ import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { execSync } from 'child_process';
6
6
 
7
- const templates = {
7
+ export const templates = {
8
8
  plugin: {
9
9
  description: 'Create a new ObjectStack plugin',
10
10
  files: {
@@ -96,13 +96,13 @@ MIT
96
96
  description: 'Create a new ObjectStack example application',
97
97
  files: {
98
98
  'package.json': (name: string) => ({
99
- name: `@objectstack/example-${name}`,
99
+ name: `@example/${name}`,
100
100
  version: '0.1.0',
101
101
  private: true,
102
102
  description: `ObjectStack Example: ${name}`,
103
103
  scripts: {
104
104
  build: 'objectstack compile',
105
- dev: 'tsx watch objectstack.config.ts',
105
+ dev: 'objectstack dev',
106
106
  test: 'vitest',
107
107
  },
108
108
  dependencies: {
@@ -120,20 +120,20 @@ MIT
120
120
  'objectstack.config.ts': (name: string) => `import { defineStack } from '@objectstack/spec';
121
121
 
122
122
  export default defineStack({
123
- metadata: {
123
+ manifest: {
124
124
  name: '${name}',
125
125
  version: '0.1.0',
126
126
  description: '${name} example application',
127
127
  },
128
128
 
129
- objects: {
129
+ objects: [
130
130
  // Define your data objects here
131
- },
131
+ // { name: 'my_object', fields: { ... } }
132
+ ],
132
133
 
133
- ui: {
134
- apps: [],
135
- views: [],
136
- },
134
+ apps: [
135
+ // Define your apps here
136
+ ],
137
137
  });
138
138
  `,
139
139
  'README.md': (name: string) => `# ${name} Example
@@ -1,24 +1,58 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import { execSync } from 'child_process';
3
+ import { execSync, spawn } from 'child_process';
4
4
  import os from 'os';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
 
8
8
  export const devCommand = new Command('dev')
9
9
  .description('Start development mode for a package')
10
- .argument('[package]', 'Package name (without @objectstack/ prefix)', 'all')
10
+ .argument('[package]', 'Package name or filter pattern', 'all')
11
11
  .option('-w, --watch', 'Enable watch mode (default)', true)
12
12
  .option('-v, --verbose', 'Verbose output')
13
13
  .action(async (packageName, options) => {
14
14
  console.log(chalk.bold(`\n๐Ÿš€ ObjectStack Development Mode`));
15
15
  console.log(chalk.dim(`-------------------------------`));
16
16
 
17
+ // Check if we are running inside a package (Single Package Mode)
18
+ // If "package" argument is 'all' (default) AND objectstack.config.ts exists in CWD
19
+ const configPath = path.resolve(process.cwd(), 'objectstack.config.ts');
20
+ if (packageName === 'all' && fs.existsSync(configPath)) {
21
+ console.log(chalk.blue(`๐Ÿ“‚ Detected package config: ${configPath}`));
22
+ console.log(chalk.green(`โšก Starting Dev Server...`));
23
+ console.log('');
24
+
25
+ // Delegate to 'serve --dev'
26
+ // We spawn a new process to ensure clean environment and watch capabilities (plugin-loader etc)
27
+ // usage: objectstack serve --dev
28
+ const binPath = process.argv[1]; // path to objectstack bin
29
+
30
+ const child = spawn(process.execPath, [binPath, 'serve', '--dev', ...(options.verbose ? ['--verbose'] : [])], {
31
+ stdio: 'inherit',
32
+ env: { ...process.env, NODE_ENV: 'development' }
33
+ });
34
+
35
+ return;
36
+ }
37
+
38
+ // Monorepo Orchestration Mode
17
39
  try {
18
40
  const cwd = process.cwd();
19
- const filter = packageName === 'all' ? '' : `--filter @objectstack/${packageName}`;
20
41
 
21
- console.log(`๐Ÿ“ฆ Package: ${chalk.blue(packageName === 'all' ? 'All packages' : `@objectstack/${packageName}`)}`);
42
+ // Only attempt monorepo orchestration if we are in a workspace root
43
+ const workspaceConfigPath = path.resolve(cwd, 'pnpm-workspace.yaml');
44
+ const isWorkspaceRoot = fs.existsSync(workspaceConfigPath);
45
+
46
+ if (packageName === 'all' && !isWorkspaceRoot) {
47
+ console.error(chalk.red(`\nโŒ Configuration file (objectstack.config.ts) not found in ${cwd}`));
48
+ console.error(chalk.yellow(` To start development mode, run this command in a directory with objectstack.config.ts`));
49
+ console.error(chalk.yellow(` OR run from the monorepo root to start all packages.`));
50
+ process.exit(1);
51
+ }
52
+
53
+ const filter = packageName === 'all' ? '' : `--filter ${packageName}`;
54
+
55
+ console.log(`๐Ÿ“ฆ Package: ${chalk.blue(packageName === 'all' ? 'All packages' : packageName)}`);
22
56
  console.log(`๐Ÿ”„ Watch mode: ${chalk.green('enabled')}`);
23
57
  console.log('');
24
58
 
@@ -34,6 +34,7 @@ export const serveCommand = new Command('serve')
34
34
  .description('Start ObjectStack server with plugins from configuration')
35
35
  .argument('[config]', 'Configuration file path', 'objectstack.config.ts')
36
36
  .option('-p, --port <port>', 'Server port', '3000')
37
+ .option('--dev', 'Run in development mode (load devPlugins)')
37
38
  .option('--no-server', 'Skip starting HTTP server plugin')
38
39
  .action(async (configPath, options) => {
39
40
  let port = parseInt(options.port);
@@ -84,20 +85,87 @@ export const serveCommand = new Command('serve')
84
85
 
85
86
  // Create kernel instance
86
87
  console.log(chalk.yellow(`๐Ÿ”ง Initializing ObjectStack kernel...`));
88
+
89
+ // Auto-configure pretty logging in development mode
90
+ const isDev = options.dev || process.env.NODE_ENV === 'development';
91
+ const loggerConfig = isDev ? { format: 'pretty' } : undefined;
92
+
87
93
  const kernel = new ObjectKernel({
88
94
  metadata: config.metadata || {},
89
95
  objects: config.objects || {},
96
+ logger: loggerConfig
90
97
  });
91
98
 
92
99
  // Load plugins from configuration
93
- const plugins = config.plugins || [];
100
+ let plugins = config.plugins || [];
101
+
102
+ // Merge devPlugins if in dev mode
103
+ if (options.dev && config.devPlugins) {
104
+ console.log(chalk.blue(`๐Ÿ“ฆ Loading development plugins...`));
105
+ plugins = [...plugins, ...config.devPlugins];
106
+ }
107
+
108
+ // 1. Auto-register ObjectQL Plugin if objects define but plugins missing
109
+ const hasObjectQL = plugins.some((p: any) => p.name?.includes('objectql') || p.constructor?.name?.includes('ObjectQL'));
110
+ if (config.objects && !hasObjectQL) {
111
+ try {
112
+ console.log(chalk.dim(` Auto-injecting ObjectQL Engine...`));
113
+ const { ObjectQLPlugin } = await import('@objectstack/objectql');
114
+ kernel.use(new ObjectQLPlugin());
115
+ console.log(chalk.green(` โœ“ Registered ObjectQL Plugin (auto-detected)`));
116
+ } catch (e: any) {
117
+ console.warn(chalk.yellow(` โš  Could not auto-load ObjectQL: ${e.message}`));
118
+ }
119
+ }
120
+
121
+ // 2. Auto-register Memory Driver if in Dev and no driver configured
122
+ const hasDriver = plugins.some((p: any) => p.name?.includes('driver') || p.constructor?.name?.includes('Driver'));
123
+ if (isDev && !hasDriver && config.objects) {
124
+ try {
125
+ console.log(chalk.dim(` Auto-injecting Memory Driver (Dev Mode)...`));
126
+ const { DriverPlugin } = await import('@objectstack/runtime');
127
+ const { InMemoryDriver } = await import('@objectstack/driver-memory');
128
+ kernel.use(new DriverPlugin(new InMemoryDriver()));
129
+ console.log(chalk.green(` โœ“ Registered Memory Driver (auto-detected)`));
130
+ } catch (e: any) {
131
+ // Silent fail - maybe they don't want a driver or don't have the package
132
+ console.log(chalk.dim(` โ„น No default driver loaded: ${e.message}`));
133
+ }
134
+ }
135
+
136
+ // 3. Auto-register AppPlugin if config contains app definitions
137
+ if (config.objects || config.manifest || config.apps) {
138
+ try {
139
+ const { AppPlugin } = await import('@objectstack/runtime');
140
+ kernel.use(new AppPlugin(config));
141
+ console.log(chalk.green(` โœ“ Registered App Plugin (auto-detected)`));
142
+ } catch (e: any) {
143
+ console.warn(chalk.yellow(` โš  Could not auto-load AppPlugin: ${e.message}`));
144
+ }
145
+ }
146
+
94
147
 
95
148
  if (plugins.length > 0) {
96
149
  console.log(chalk.yellow(`๐Ÿ“ฆ Loading ${plugins.length} plugin(s)...`));
97
150
 
98
151
  for (const plugin of plugins) {
99
152
  try {
100
- kernel.use(plugin);
153
+ let pluginToLoad = plugin;
154
+
155
+ // Resolve string references (package names)
156
+ if (typeof plugin === 'string') {
157
+ console.log(chalk.dim(` Trying to resolve plugin: ${plugin}`));
158
+ try {
159
+ // Try dynamic import for packages
160
+ const imported = await import(plugin);
161
+ pluginToLoad = imported.default || imported;
162
+ } catch (importError: any) {
163
+ // Fallback: try bundleRequire for local paths if needed, otherwise throw
164
+ throw new Error(`Failed to import plugin '${plugin}': ${importError.message}`);
165
+ }
166
+ }
167
+
168
+ kernel.use(pluginToLoad);
101
169
  const pluginName = plugin.name || plugin.constructor?.name || 'unnamed';
102
170
  console.log(chalk.green(` โœ“ Registered plugin: ${pluginName}`));
103
171
  } catch (e: any) {
@@ -0,0 +1,92 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import { QA as CoreQA } from '@objectstack/core';
6
+ import { QA } from '@objectstack/spec';
7
+
8
+ export const testCommand = new Command('test:run')
9
+ .description('Run Quality Protocol test scenarios')
10
+ .argument('[files]', 'Glob pattern for test files (e.g. "qa/*.test.json")', 'qa/*.test.json')
11
+ .option('--url <url>', 'Target base URL', 'http://localhost:3000')
12
+ .option('--token <token>', 'Authentication token')
13
+ .action(async (filesPattern, options) => {
14
+ console.log(chalk.bold(`\n๐Ÿงช ObjectStack Quality Protocol Runner`));
15
+ console.log(chalk.dim(`-------------------------------------`));
16
+ console.log(`Target: ${chalk.blue(options.url)}`);
17
+
18
+ // 1. Setup Runner
19
+ const adapter = new CoreQA.HttpTestAdapter(options.url, options.token);
20
+ const runner = new CoreQA.TestRunner(adapter);
21
+
22
+ // 2. Find Files (Simple implementation for now)
23
+ // TODO: Use glob
24
+ const cwd = process.cwd();
25
+ const testFiles: string[] = [];
26
+
27
+ // Very basic file finding for demo - assume explicit path or check local dir
28
+ if (fs.existsSync(filesPattern)) {
29
+ testFiles.push(filesPattern);
30
+ } else {
31
+ // Simple directory scan
32
+ const dir = path.dirname(filesPattern);
33
+ const ext = path.extname(filesPattern);
34
+ if (fs.existsSync(dir)) {
35
+ const files = fs.readdirSync(dir).filter(f => f.endsWith(ext) || f.endsWith('.json'));
36
+ files.forEach(f => testFiles.push(path.join(dir, f)));
37
+ }
38
+ }
39
+
40
+ if (testFiles.length === 0) {
41
+ console.warn(chalk.yellow(`No test files found matching: ${filesPattern}`));
42
+ // Create a demo test file if none exist?
43
+ return;
44
+ }
45
+
46
+ console.log(`Found ${testFiles.length} test suites.`);
47
+
48
+ // 3. Run Tests
49
+ let totalPassed = 0;
50
+ let totalFailed = 0;
51
+
52
+ for (const file of testFiles) {
53
+ console.log(`\n๐Ÿ“„ Running suite: ${chalk.bold(path.basename(file))}`);
54
+ try {
55
+ const content = fs.readFileSync(file, 'utf-8');
56
+ const suite = JSON.parse(content) as QA.TestSuite; // Should validate with Zod
57
+
58
+ const results = await runner.runSuite(suite);
59
+
60
+ for (const result of results) {
61
+ const icon = result.passed ? 'โœ…' : 'โŒ';
62
+ console.log(` ${icon} Scenario: ${result.scenarioId} (${result.duration}ms)`);
63
+ if (!result.passed) {
64
+ console.error(chalk.red(` Error: ${result.error}`));
65
+ result.steps.forEach(step => {
66
+ if (!step.passed) {
67
+ console.error(chalk.red(` Step Failed: ${step.stepName}`));
68
+ if (step.output) console.error(` Output:`, step.output);
69
+ if (step.error) console.error(` Error:`, step.error);
70
+ }
71
+ });
72
+ totalFailed++;
73
+ } else {
74
+ totalPassed++;
75
+ }
76
+ }
77
+ } catch (e) {
78
+ console.error(chalk.red(`Failed to load or run suite ${file}: ${e}`));
79
+ totalFailed++; // Count suite failure
80
+ }
81
+ }
82
+
83
+ // 4. Summary
84
+ console.log(chalk.dim(`\n-------------------------------------`));
85
+ if (totalFailed > 0) {
86
+ console.log(chalk.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
87
+ process.exit(1);
88
+ } else {
89
+ console.log(chalk.green(`SUCCESS: All ${totalPassed} scenarios passed.`));
90
+ process.exit(0);
91
+ }
92
+ });
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { compileCommand } from '../src/commands/compile';
3
+ import { serveCommand } from '../src/commands/serve';
4
+ import { devCommand } from '../src/commands/dev';
5
+ import { doctorCommand } from '../src/commands/doctor';
6
+ import { createCommand } from '../src/commands/create';
7
+ import { testCommand } from '../src/commands/test';
8
+
9
+ describe('CLI Commands', () => {
10
+ it('should have compile command', () => {
11
+ expect(compileCommand.name()).toBe('compile');
12
+ expect(compileCommand.description()).toContain('Compile');
13
+ });
14
+
15
+ it('should have serve command', () => {
16
+ expect(serveCommand.name()).toBe('serve');
17
+ expect(serveCommand.description()).toContain('server');
18
+ });
19
+
20
+ it('should have dev command', () => {
21
+ expect(devCommand.name()).toBe('dev');
22
+ expect(devCommand.description()).toContain('development mode');
23
+ });
24
+
25
+ it('should have doctor command', () => {
26
+ expect(doctorCommand.name()).toBe('doctor');
27
+ expect(doctorCommand.description()).toContain('health');
28
+ });
29
+
30
+ it('should have create command', () => {
31
+ expect(createCommand.name()).toBe('create');
32
+ expect(createCommand.description()).toContain('Create');
33
+ });
34
+
35
+ it('should have test command', () => {
36
+ expect(testCommand.name()).toBe('test:run');
37
+ expect(testCommand.description()).toContain('Quality Protocol');
38
+ });
39
+ });
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { templates } from '../src/commands/create';
3
+
4
+ describe('Create Command Templates', () => {
5
+ describe('Example Template', () => {
6
+ it('should generate correct package.json scripts', () => {
7
+ const packageJsonFn = templates.example.files['package.json'];
8
+ const packageJson = packageJsonFn('test-app');
9
+
10
+ expect(packageJson.scripts.dev).toBe('objectstack dev');
11
+ expect(packageJson.scripts.build).toBe('objectstack compile');
12
+ expect(packageJson.dependencies['@objectstack/cli']).toBe('workspace:*');
13
+ });
14
+ });
15
+
16
+ describe('Plugin Template', () => {
17
+ it('should generate correct dependencies', () => {
18
+ const packageJsonFn = templates.plugin.files['package.json'];
19
+ const packageJson = packageJsonFn('test-plugin');
20
+
21
+ expect(packageJson.dependencies).toHaveProperty('@objectstack/spec');
22
+ expect(packageJson.keywords).toContain('test-plugin');
23
+ });
24
+ });
25
+ });