@objectstack/cli 0.8.1 โ 0.9.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/CHANGELOG.md +9 -0
- package/dist/bin.js +75 -2
- package/package.json +5 -5
- package/src/bin.ts +2 -0
- package/src/commands/test.ts +92 -0
package/CHANGELOG.md
CHANGED
package/dist/bin.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-2YXVEYO7.js";
|
|
5
5
|
|
|
6
6
|
// src/bin.ts
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command6 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/dev.ts
|
|
10
10
|
import { Command } from "commander";
|
|
@@ -529,12 +529,85 @@ var serveCommand = new Command4("serve").description("Start ObjectStack server w
|
|
|
529
529
|
}
|
|
530
530
|
});
|
|
531
531
|
|
|
532
|
+
// src/commands/test.ts
|
|
533
|
+
import { Command as Command5 } from "commander";
|
|
534
|
+
import chalk5 from "chalk";
|
|
535
|
+
import path4 from "path";
|
|
536
|
+
import fs4 from "fs";
|
|
537
|
+
import { QA as CoreQA } from "@objectstack/core";
|
|
538
|
+
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) => {
|
|
539
|
+
console.log(chalk5.bold(`
|
|
540
|
+
\u{1F9EA} ObjectStack Quality Protocol Runner`));
|
|
541
|
+
console.log(chalk5.dim(`-------------------------------------`));
|
|
542
|
+
console.log(`Target: ${chalk5.blue(options.url)}`);
|
|
543
|
+
const adapter = new CoreQA.HttpTestAdapter(options.url, options.token);
|
|
544
|
+
const runner = new CoreQA.TestRunner(adapter);
|
|
545
|
+
const cwd = process.cwd();
|
|
546
|
+
const testFiles = [];
|
|
547
|
+
if (fs4.existsSync(filesPattern)) {
|
|
548
|
+
testFiles.push(filesPattern);
|
|
549
|
+
} else {
|
|
550
|
+
const dir = path4.dirname(filesPattern);
|
|
551
|
+
const ext = path4.extname(filesPattern);
|
|
552
|
+
if (fs4.existsSync(dir)) {
|
|
553
|
+
const files = fs4.readdirSync(dir).filter((f) => f.endsWith(ext) || f.endsWith(".json"));
|
|
554
|
+
files.forEach((f) => testFiles.push(path4.join(dir, f)));
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (testFiles.length === 0) {
|
|
558
|
+
console.warn(chalk5.yellow(`No test files found matching: ${filesPattern}`));
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
console.log(`Found ${testFiles.length} test suites.`);
|
|
562
|
+
let totalPassed = 0;
|
|
563
|
+
let totalFailed = 0;
|
|
564
|
+
for (const file of testFiles) {
|
|
565
|
+
console.log(`
|
|
566
|
+
\u{1F4C4} Running suite: ${chalk5.bold(path4.basename(file))}`);
|
|
567
|
+
try {
|
|
568
|
+
const content = fs4.readFileSync(file, "utf-8");
|
|
569
|
+
const suite = JSON.parse(content);
|
|
570
|
+
const results = await runner.runSuite(suite);
|
|
571
|
+
for (const result of results) {
|
|
572
|
+
const icon = result.passed ? "\u2705" : "\u274C";
|
|
573
|
+
console.log(` ${icon} Scenario: ${result.scenarioId} (${result.duration}ms)`);
|
|
574
|
+
if (!result.passed) {
|
|
575
|
+
console.error(chalk5.red(` Error: ${result.error}`));
|
|
576
|
+
result.steps.forEach((step) => {
|
|
577
|
+
if (!step.passed) {
|
|
578
|
+
console.error(chalk5.red(` Step Failed: ${step.stepName}`));
|
|
579
|
+
if (step.output) console.error(` Output:`, step.output);
|
|
580
|
+
if (step.error) console.error(` Error:`, step.error);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
totalFailed++;
|
|
584
|
+
} else {
|
|
585
|
+
totalPassed++;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
} catch (e) {
|
|
589
|
+
console.error(chalk5.red(`Failed to load or run suite ${file}: ${e}`));
|
|
590
|
+
totalFailed++;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
console.log(chalk5.dim(`
|
|
594
|
+
-------------------------------------`));
|
|
595
|
+
if (totalFailed > 0) {
|
|
596
|
+
console.log(chalk5.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
|
|
597
|
+
process.exit(1);
|
|
598
|
+
} else {
|
|
599
|
+
console.log(chalk5.green(`SUCCESS: All ${totalPassed} scenarios passed.`));
|
|
600
|
+
process.exit(0);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
532
604
|
// src/bin.ts
|
|
533
|
-
var program = new
|
|
605
|
+
var program = new Command6();
|
|
534
606
|
program.name("objectstack").description("CLI for ObjectStack Protocol - Development Tools for Microkernel Architecture").version("0.8.0");
|
|
535
607
|
program.addCommand(compileCommand);
|
|
536
608
|
program.addCommand(serveCommand);
|
|
537
609
|
program.addCommand(devCommand);
|
|
538
610
|
program.addCommand(doctorCommand);
|
|
539
611
|
program.addCommand(createCommand);
|
|
612
|
+
program.addCommand(testCommand);
|
|
540
613
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Command Line Interface for ObjectStack Protocol",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"commander": "^11.1.0",
|
|
23
23
|
"tsx": "^4.7.1",
|
|
24
24
|
"zod": "^4.3.6",
|
|
25
|
-
"@objectstack/spec": "0.
|
|
26
|
-
"@objectstack/core": "0.
|
|
27
|
-
"@objectstack/plugin-hono-server": "0.
|
|
25
|
+
"@objectstack/spec": "0.9.0",
|
|
26
|
+
"@objectstack/core": "0.9.0",
|
|
27
|
+
"@objectstack/plugin-hono-server": "0.9.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@objectstack/core": "^0.
|
|
30
|
+
"@objectstack/core": "^0.9.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^25.1.0",
|
package/src/bin.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { devCommand } from './commands/dev.js';
|
|
|
4
4
|
import { doctorCommand } from './commands/doctor.js';
|
|
5
5
|
import { createCommand } from './commands/create.js';
|
|
6
6
|
import { serveCommand } from './commands/serve.js';
|
|
7
|
+
import { testCommand } from './commands/test.js';
|
|
7
8
|
|
|
8
9
|
const program = new Command();
|
|
9
10
|
|
|
@@ -18,5 +19,6 @@ program.addCommand(serveCommand);
|
|
|
18
19
|
program.addCommand(devCommand);
|
|
19
20
|
program.addCommand(doctorCommand);
|
|
20
21
|
program.addCommand(createCommand);
|
|
22
|
+
program.addCommand(testCommand);
|
|
21
23
|
|
|
22
24
|
program.parse(process.argv);
|
|
@@ -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
|
+
});
|