@objectstack/cli 2.0.6 → 2.0.7

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/dist/index.js CHANGED
@@ -62,10 +62,10 @@ function formatZodErrors(error) {
62
62
  console.log(chalk.bold.red(`
63
63
  ${section}:`));
64
64
  for (const issue of sectionIssues) {
65
- const path11 = issue.path?.join(".") || "";
65
+ const path12 = issue.path?.join(".") || "";
66
66
  const code = issue.code || "";
67
67
  const msg = issue.message || "";
68
- console.log(chalk.red(` \u2717 ${path11}`));
68
+ console.log(chalk.red(` \u2717 ${path12}`));
69
69
  console.log(chalk.dim(` ${code}: ${msg}`));
70
70
  if (issue.expected) {
71
71
  console.log(chalk.dim(` expected: ${chalk.green(issue.expected)}`));
@@ -1238,16 +1238,261 @@ var createCommand = new Command6("create").description("Create a new package, pl
1238
1238
  }
1239
1239
  });
1240
1240
 
1241
- // src/commands/dev.ts
1241
+ // src/commands/plugin.ts
1242
1242
  import { Command as Command7 } from "commander";
1243
1243
  import chalk9 from "chalk";
1244
- import { execSync, spawn } from "child_process";
1245
1244
  import fs6 from "fs";
1246
1245
  import path6 from "path";
1247
- var devCommand = new Command7("dev").description("Start development mode with hot-reload").argument("[package]", "Package name or filter pattern", "all").option("-w, --watch", "Enable watch mode (default)", true).option("--ui", "Enable Studio UI at /_studio/").option("-v, --verbose", "Verbose output").action(async (packageName, options) => {
1246
+ function resolvePluginName(plugin) {
1247
+ if (typeof plugin === "string") return plugin;
1248
+ if (plugin && typeof plugin === "object") {
1249
+ const p = plugin;
1250
+ if (typeof p.name === "string") return p.name;
1251
+ if (p.constructor && p.constructor.name !== "Object") return p.constructor.name;
1252
+ }
1253
+ return "unknown";
1254
+ }
1255
+ function resolvePluginVersion(plugin) {
1256
+ if (plugin && typeof plugin === "object") {
1257
+ const p = plugin;
1258
+ if (typeof p.version === "string") return p.version;
1259
+ }
1260
+ return "-";
1261
+ }
1262
+ function resolvePluginType(plugin) {
1263
+ if (plugin && typeof plugin === "object") {
1264
+ const p = plugin;
1265
+ if (typeof p.type === "string") return p.type;
1266
+ }
1267
+ return "standard";
1268
+ }
1269
+ function readConfigText(configPath) {
1270
+ return fs6.readFileSync(configPath, "utf-8");
1271
+ }
1272
+ function addPluginToConfig(configPath, packageName) {
1273
+ let content = readConfigText(configPath);
1274
+ const shortName = packageName.replace(/^@[^/]+\//, "").replace(/^plugin-/, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
1275
+ const varName = shortName.replace(/-([a-z])/g, (_, c) => c.toUpperCase()) + "Plugin";
1276
+ const importLine = `import ${varName} from '${packageName}';
1277
+ `;
1278
+ if (content.includes(packageName)) {
1279
+ throw new Error(`Plugin '${packageName}' is already referenced in the config`);
1280
+ }
1281
+ const importRegex = /^import .+$/gm;
1282
+ let lastImportEnd = 0;
1283
+ let match;
1284
+ while ((match = importRegex.exec(content)) !== null) {
1285
+ lastImportEnd = match.index + match[0].length;
1286
+ }
1287
+ if (lastImportEnd > 0) {
1288
+ content = content.slice(0, lastImportEnd) + "\n" + importLine + content.slice(lastImportEnd);
1289
+ } else {
1290
+ content = importLine + "\n" + content;
1291
+ }
1292
+ if (/plugins\s*:\s*\[/.test(content)) {
1293
+ let replaced = false;
1294
+ content = content.replace(
1295
+ /(plugins\s*:\s*\[)/,
1296
+ (match2) => {
1297
+ if (replaced) return match2;
1298
+ replaced = true;
1299
+ return `${match2}
1300
+ ${varName},`;
1301
+ }
1302
+ );
1303
+ } else {
1304
+ content = content.replace(
1305
+ /(defineStack\(\{[\s\S]*?)(}\s*\))/,
1306
+ `$1 plugins: [
1307
+ ${varName},
1308
+ ],
1309
+ $2`
1310
+ );
1311
+ }
1312
+ fs6.writeFileSync(configPath, content);
1313
+ }
1314
+ function removePluginFromConfig(configPath, pluginName) {
1315
+ let content = readConfigText(configPath);
1316
+ const importRegex = new RegExp(`^import .+['"]${escapeRegex(pluginName)}['"]\\s*;?\\s*$\\n?`, "gm");
1317
+ const hadImport = importRegex.test(content);
1318
+ importRegex.lastIndex = 0;
1319
+ content = content.replace(importRegex, "");
1320
+ const shortName = pluginName.replace(/^@[^/]+\//, "").replace(/^plugin-/, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
1321
+ const varName = shortName.replace(/-([a-z])/g, (_, c) => c.toUpperCase()) + "Plugin";
1322
+ if (!hadImport) {
1323
+ const varImportRegex = new RegExp(`^import .* ${escapeRegex(varName)} .+$\\n?`, "gm");
1324
+ content = content.replace(varImportRegex, "");
1325
+ }
1326
+ const entryPatterns = [
1327
+ new RegExp(`\\s*${escapeRegex(varName)},?\\n?`, "g"),
1328
+ new RegExp(`\\s*['"]${escapeRegex(pluginName)}['"],?\\n?`, "g")
1329
+ ];
1330
+ for (const pattern of entryPatterns) {
1331
+ content = content.replace(pattern, "\n");
1332
+ }
1333
+ content = content.replace(/plugins\s*:\s*\[\s*\],?\n?/g, "");
1334
+ fs6.writeFileSync(configPath, content);
1335
+ }
1336
+ function escapeRegex(str) {
1337
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1338
+ }
1339
+ var listCommand = new Command7("list").alias("ls").description("List plugins defined in the configuration").argument("[config]", "Configuration file path").option("--json", "Output as JSON").action(async (configSource, options) => {
1340
+ try {
1341
+ const { config } = await loadConfig(configSource);
1342
+ const plugins = config.plugins || [];
1343
+ const devPlugins = config.devPlugins || [];
1344
+ if (options?.json) {
1345
+ const data = {
1346
+ plugins: plugins.map((p) => ({
1347
+ name: resolvePluginName(p),
1348
+ version: resolvePluginVersion(p),
1349
+ type: resolvePluginType(p),
1350
+ dev: false
1351
+ })),
1352
+ devPlugins: devPlugins.map((p) => ({
1353
+ name: resolvePluginName(p),
1354
+ version: resolvePluginVersion(p),
1355
+ type: resolvePluginType(p),
1356
+ dev: true
1357
+ }))
1358
+ };
1359
+ console.log(JSON.stringify(data, null, 2));
1360
+ return;
1361
+ }
1362
+ printHeader("Plugins");
1363
+ if (plugins.length === 0 && devPlugins.length === 0) {
1364
+ printInfo("No plugins configured");
1365
+ console.log("");
1366
+ console.log(chalk9.dim(" Hint: Add plugins to your objectstack.config.ts"));
1367
+ console.log(chalk9.dim(" Or run: os plugin add <package-name>"));
1368
+ console.log("");
1369
+ return;
1370
+ }
1371
+ if (plugins.length > 0) {
1372
+ console.log(chalk9.bold(`
1373
+ Plugins (${plugins.length}):`));
1374
+ for (const plugin of plugins) {
1375
+ const name = resolvePluginName(plugin);
1376
+ const version = resolvePluginVersion(plugin);
1377
+ const type = resolvePluginType(plugin);
1378
+ console.log(
1379
+ ` ${chalk9.cyan("\u25CF")} ${chalk9.white(name)}` + (version !== "-" ? chalk9.dim(` v${version}`) : "") + (type !== "standard" ? chalk9.dim(` [${type}]`) : "")
1380
+ );
1381
+ }
1382
+ }
1383
+ if (devPlugins.length > 0) {
1384
+ console.log(chalk9.bold(`
1385
+ Dev Plugins (${devPlugins.length}):`));
1386
+ for (const plugin of devPlugins) {
1387
+ const name = resolvePluginName(plugin);
1388
+ const version = resolvePluginVersion(plugin);
1389
+ console.log(
1390
+ ` ${chalk9.yellow("\u25CF")} ${chalk9.white(name)}` + (version !== "-" ? chalk9.dim(` v${version}`) : "") + chalk9.dim(" [dev]")
1391
+ );
1392
+ }
1393
+ }
1394
+ console.log("");
1395
+ } catch (error) {
1396
+ printError(error.message || String(error));
1397
+ process.exit(1);
1398
+ }
1399
+ });
1400
+ var infoSubCommand = new Command7("info").description("Show detailed information about a plugin").argument("<name>", "Plugin name or package name").argument("[config]", "Configuration file path").action(async (name, configSource) => {
1401
+ try {
1402
+ const { config } = await loadConfig(configSource);
1403
+ const allPlugins = [
1404
+ ...config.plugins || [],
1405
+ ...config.devPlugins || []
1406
+ ];
1407
+ const found = allPlugins.find((p) => {
1408
+ const pName = resolvePluginName(p);
1409
+ return pName === name || pName.includes(name);
1410
+ });
1411
+ if (!found) {
1412
+ printError(`Plugin '${name}' not found in configuration`);
1413
+ console.log("");
1414
+ console.log(chalk9.dim(" Available plugins:"));
1415
+ for (const p of allPlugins) {
1416
+ console.log(chalk9.dim(` - ${resolvePluginName(p)}`));
1417
+ }
1418
+ console.log("");
1419
+ process.exit(1);
1420
+ }
1421
+ printHeader(`Plugin: ${resolvePluginName(found)}`);
1422
+ printKV("Name", resolvePluginName(found));
1423
+ printKV("Version", resolvePluginVersion(found));
1424
+ printKV("Type", resolvePluginType(found));
1425
+ const isDev = (config.devPlugins || []).includes(found);
1426
+ printKV("Environment", isDev ? "development" : "production");
1427
+ if (found && typeof found === "object") {
1428
+ const p = found;
1429
+ if (typeof p.description === "string") {
1430
+ printKV("Description", p.description);
1431
+ }
1432
+ if (Array.isArray(p.dependencies) && p.dependencies.length > 0) {
1433
+ printKV("Dependencies", p.dependencies.join(", "));
1434
+ }
1435
+ if (typeof p.init === "function") {
1436
+ printInfo("This is a runtime plugin instance (has init function)");
1437
+ }
1438
+ }
1439
+ if (typeof found === "string") {
1440
+ printInfo("This is a string reference (will be imported at runtime)");
1441
+ }
1442
+ console.log("");
1443
+ } catch (error) {
1444
+ printError(error.message || String(error));
1445
+ process.exit(1);
1446
+ }
1447
+ });
1448
+ var addCommand = new Command7("add").description("Add a plugin to objectstack.config.ts").argument("<package>", "Plugin package name (e.g. @objectstack/plugin-auth)").option("-d, --dev", "Add as a dev-only plugin").option("-c, --config <path>", "Configuration file path").action(async (packageName, options) => {
1449
+ try {
1450
+ const configPath = resolveConfigPath(options?.config);
1451
+ printHeader("Add Plugin");
1452
+ console.log(` ${chalk9.dim("Package:")} ${chalk9.white(packageName)}`);
1453
+ console.log(` ${chalk9.dim("Config:")} ${chalk9.white(path6.relative(process.cwd(), configPath))}`);
1454
+ console.log("");
1455
+ addPluginToConfig(configPath, packageName);
1456
+ printSuccess(`Added ${chalk9.cyan(packageName)} to config`);
1457
+ console.log("");
1458
+ console.log(chalk9.dim(" Next steps:"));
1459
+ console.log(chalk9.dim(` 1. Install the package: pnpm add ${packageName}`));
1460
+ console.log(chalk9.dim(" 2. Run: os validate"));
1461
+ console.log("");
1462
+ } catch (error) {
1463
+ printError(error.message || String(error));
1464
+ process.exit(1);
1465
+ }
1466
+ });
1467
+ var removeCommand = new Command7("remove").alias("rm").description("Remove a plugin from objectstack.config.ts").argument("<name>", "Plugin name or package name to remove").option("-c, --config <path>", "Configuration file path").action(async (pluginName, options) => {
1468
+ try {
1469
+ const configPath = resolveConfigPath(options?.config);
1470
+ printHeader("Remove Plugin");
1471
+ console.log(` ${chalk9.dim("Plugin:")} ${chalk9.white(pluginName)}`);
1472
+ console.log(` ${chalk9.dim("Config:")} ${chalk9.white(path6.relative(process.cwd(), configPath))}`);
1473
+ console.log("");
1474
+ removePluginFromConfig(configPath, pluginName);
1475
+ printSuccess(`Removed ${chalk9.cyan(pluginName)} from config`);
1476
+ console.log("");
1477
+ console.log(chalk9.dim(" Tip: Run `pnpm remove " + pluginName + "` to uninstall the package"));
1478
+ console.log("");
1479
+ } catch (error) {
1480
+ printError(error.message || String(error));
1481
+ process.exit(1);
1482
+ }
1483
+ });
1484
+ var pluginCommand = new Command7("plugin").description("Manage plugins (list, info, add, remove)").addCommand(listCommand).addCommand(infoSubCommand).addCommand(addCommand).addCommand(removeCommand);
1485
+
1486
+ // src/commands/dev.ts
1487
+ import { Command as Command8 } from "commander";
1488
+ import chalk10 from "chalk";
1489
+ import { execSync, spawn } from "child_process";
1490
+ import fs7 from "fs";
1491
+ import path7 from "path";
1492
+ var devCommand = new Command8("dev").description("Start development mode with hot-reload").argument("[package]", "Package name or filter pattern", "all").option("-w, --watch", "Enable watch mode (default)", true).option("--ui", "Enable Studio UI at /_studio/").option("-v, --verbose", "Verbose output").action(async (packageName, options) => {
1248
1493
  printHeader("Development Mode");
1249
- const configPath = path6.resolve(process.cwd(), "objectstack.config.ts");
1250
- if (packageName === "all" && fs6.existsSync(configPath)) {
1494
+ const configPath = path7.resolve(process.cwd(), "objectstack.config.ts");
1495
+ if (packageName === "all" && fs7.existsSync(configPath)) {
1251
1496
  printKV("Config", configPath, "\u{1F4C2}");
1252
1497
  printStep("Starting dev server...");
1253
1498
  const binPath = process.argv[1];
@@ -1259,18 +1504,18 @@ var devCommand = new Command7("dev").description("Start development mode with ho
1259
1504
  }
1260
1505
  try {
1261
1506
  const cwd = process.cwd();
1262
- const workspaceConfigPath = path6.resolve(cwd, "pnpm-workspace.yaml");
1263
- const isWorkspaceRoot = fs6.existsSync(workspaceConfigPath);
1507
+ const workspaceConfigPath = path7.resolve(cwd, "pnpm-workspace.yaml");
1508
+ const isWorkspaceRoot = fs7.existsSync(workspaceConfigPath);
1264
1509
  if (packageName === "all" && !isWorkspaceRoot) {
1265
1510
  printError(`Config file not found in ${cwd}`);
1266
- console.error(chalk9.yellow(" Run in a directory with objectstack.config.ts, or from the monorepo root."));
1511
+ console.error(chalk10.yellow(" Run in a directory with objectstack.config.ts, or from the monorepo root."));
1267
1512
  process.exit(1);
1268
1513
  }
1269
1514
  const filter = packageName === "all" ? "" : `--filter ${packageName}`;
1270
1515
  printKV("Package", packageName === "all" ? "All packages" : packageName, "\u{1F4E6}");
1271
1516
  printKV("Watch", "enabled", "\u{1F504}");
1272
1517
  const command = `pnpm ${filter} dev`.trim();
1273
- console.log(chalk9.dim(`$ ${command}`));
1518
+ console.log(chalk10.dim(`$ ${command}`));
1274
1519
  console.log("");
1275
1520
  execSync(command, {
1276
1521
  stdio: "inherit",
@@ -1283,38 +1528,38 @@ var devCommand = new Command7("dev").description("Start development mode with ho
1283
1528
  });
1284
1529
 
1285
1530
  // src/commands/serve.ts
1286
- import { Command as Command8 } from "commander";
1287
- import path8 from "path";
1288
- import fs8 from "fs";
1531
+ import { Command as Command9 } from "commander";
1532
+ import path9 from "path";
1533
+ import fs9 from "fs";
1289
1534
  import net from "net";
1290
- import chalk10 from "chalk";
1535
+ import chalk11 from "chalk";
1291
1536
  import { bundleRequire as bundleRequire2 } from "bundle-require";
1292
1537
 
1293
1538
  // src/utils/studio.ts
1294
- import path7 from "path";
1295
- import fs7 from "fs";
1539
+ import path8 from "path";
1540
+ import fs8 from "fs";
1296
1541
  import { createRequire } from "module";
1297
1542
  import { pathToFileURL } from "url";
1298
1543
  var STUDIO_PATH = "/_studio";
1299
1544
  function resolveStudioPath() {
1300
1545
  const cwd = process.cwd();
1301
1546
  const candidates = [
1302
- path7.resolve(cwd, "apps/studio"),
1303
- path7.resolve(cwd, "../../apps/studio"),
1304
- path7.resolve(cwd, "../apps/studio")
1547
+ path8.resolve(cwd, "apps/studio"),
1548
+ path8.resolve(cwd, "../../apps/studio"),
1549
+ path8.resolve(cwd, "../apps/studio")
1305
1550
  ];
1306
1551
  for (const candidate of candidates) {
1307
- const pkgPath = path7.join(candidate, "package.json");
1308
- if (fs7.existsSync(pkgPath)) {
1552
+ const pkgPath = path8.join(candidate, "package.json");
1553
+ if (fs8.existsSync(pkgPath)) {
1309
1554
  try {
1310
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
1555
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
1311
1556
  if (pkg.name === "@objectstack/studio") return candidate;
1312
1557
  } catch {
1313
1558
  }
1314
1559
  }
1315
1560
  }
1316
1561
  const resolutionBases = [
1317
- pathToFileURL(path7.join(cwd, "package.json")).href,
1562
+ pathToFileURL(path8.join(cwd, "package.json")).href,
1318
1563
  // consumer workspace
1319
1564
  import.meta.url
1320
1565
  // CLI package itself
@@ -1323,18 +1568,18 @@ function resolveStudioPath() {
1323
1568
  try {
1324
1569
  const req = createRequire(base);
1325
1570
  const resolved = req.resolve("@objectstack/studio/package.json");
1326
- return path7.dirname(resolved);
1571
+ return path8.dirname(resolved);
1327
1572
  } catch {
1328
1573
  }
1329
1574
  }
1330
- const directPath = path7.join(cwd, "node_modules", "@objectstack", "studio");
1331
- if (fs7.existsSync(path7.join(directPath, "package.json"))) {
1575
+ const directPath = path8.join(cwd, "node_modules", "@objectstack", "studio");
1576
+ if (fs8.existsSync(path8.join(directPath, "package.json"))) {
1332
1577
  return directPath;
1333
1578
  }
1334
1579
  return null;
1335
1580
  }
1336
1581
  function hasStudioDist(studioPath) {
1337
- return fs7.existsSync(path7.join(studioPath, "dist", "index.html"));
1582
+ return fs8.existsSync(path8.join(studioPath, "dist", "index.html"));
1338
1583
  }
1339
1584
  function createStudioStaticPlugin(distPath, options) {
1340
1585
  return {
@@ -1348,13 +1593,13 @@ function createStudioStaticPlugin(distPath, options) {
1348
1593
  return;
1349
1594
  }
1350
1595
  const app = httpServer.getRawApp();
1351
- const absoluteDist = path7.resolve(distPath);
1352
- const indexPath = path7.join(absoluteDist, "index.html");
1353
- if (!fs7.existsSync(indexPath)) {
1596
+ const absoluteDist = path8.resolve(distPath);
1597
+ const indexPath = path8.join(absoluteDist, "index.html");
1598
+ if (!fs8.existsSync(indexPath)) {
1354
1599
  ctx.logger?.warn?.(`Studio static: dist not found at ${absoluteDist}`);
1355
1600
  return;
1356
1601
  }
1357
- const rawHtml = fs7.readFileSync(indexPath, "utf-8");
1602
+ const rawHtml = fs8.readFileSync(indexPath, "utf-8");
1358
1603
  const rewrittenHtml = rawHtml.replace(
1359
1604
  /(\s(?:href|src))="\/(?!\/)/g,
1360
1605
  `$1="${STUDIO_PATH}/`
@@ -1365,12 +1610,12 @@ function createStudioStaticPlugin(distPath, options) {
1365
1610
  app.get(STUDIO_PATH, (c) => c.redirect(`${STUDIO_PATH}/`));
1366
1611
  app.get(`${STUDIO_PATH}/*`, async (c) => {
1367
1612
  const reqPath = c.req.path.substring(STUDIO_PATH.length) || "/";
1368
- const filePath = path7.join(absoluteDist, reqPath);
1613
+ const filePath = path8.join(absoluteDist, reqPath);
1369
1614
  if (!filePath.startsWith(absoluteDist)) {
1370
1615
  return c.text("Forbidden", 403);
1371
1616
  }
1372
- if (fs7.existsSync(filePath) && fs7.statSync(filePath).isFile()) {
1373
- const content = fs7.readFileSync(filePath);
1617
+ if (fs8.existsSync(filePath) && fs8.statSync(filePath).isFile()) {
1618
+ const content = fs8.readFileSync(filePath);
1374
1619
  return new Response(content, {
1375
1620
  headers: { "content-type": mimeType(filePath) }
1376
1621
  });
@@ -1400,7 +1645,7 @@ var MIME_TYPES = {
1400
1645
  ".map": "application/json"
1401
1646
  };
1402
1647
  function mimeType(filePath) {
1403
- const ext = path7.extname(filePath).toLowerCase();
1648
+ const ext = path8.extname(filePath).toLowerCase();
1404
1649
  return MIME_TYPES[ext] || "application/octet-stream";
1405
1650
  }
1406
1651
 
@@ -1427,7 +1672,7 @@ var getAvailablePort = async (startPort) => {
1427
1672
  }
1428
1673
  return port;
1429
1674
  };
1430
- var serveCommand = new Command8("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("--ui", "Enable Studio UI at /_studio/ (default: true in dev mode)").option("--no-server", "Skip starting HTTP server plugin").action(async (configPath, options) => {
1675
+ var serveCommand = new Command9("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("--ui", "Enable Studio UI at /_studio/ (default: true in dev mode)").option("--no-server", "Skip starting HTTP server plugin").action(async (configPath, options) => {
1431
1676
  let port = parseInt(options.port);
1432
1677
  try {
1433
1678
  const availablePort = await getAvailablePort(port);
@@ -1437,15 +1682,15 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
1437
1682
  } catch (e) {
1438
1683
  }
1439
1684
  const isDev = options.dev || process.env.NODE_ENV === "development";
1440
- const absolutePath = path8.resolve(process.cwd(), configPath);
1441
- const relativeConfig = path8.relative(process.cwd(), absolutePath);
1442
- if (!fs8.existsSync(absolutePath)) {
1685
+ const absolutePath = path9.resolve(process.cwd(), configPath);
1686
+ const relativeConfig = path9.relative(process.cwd(), absolutePath);
1687
+ if (!fs9.existsSync(absolutePath)) {
1443
1688
  printError(`Configuration file not found: ${absolutePath}`);
1444
- console.log(chalk10.dim(" Hint: Run `objectstack init` to create a new project"));
1689
+ console.log(chalk11.dim(" Hint: Run `objectstack init` to create a new project"));
1445
1690
  process.exit(1);
1446
1691
  }
1447
1692
  console.log("");
1448
- console.log(chalk10.dim(` Loading ${relativeConfig}...`));
1693
+ console.log(chalk11.dim(` Loading ${relativeConfig}...`));
1449
1694
  const loadedPlugins = [];
1450
1695
  const shortPluginName = (raw) => {
1451
1696
  if (raw.includes("objectql")) return "ObjectQL";
@@ -1549,7 +1794,7 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
1549
1794
  const pluginName = plugin.name || plugin.constructor?.name || "unnamed";
1550
1795
  trackPlugin(pluginName);
1551
1796
  } catch (e) {
1552
- console.error(chalk10.red(` \u2717 Failed to load plugin: ${e.message}`));
1797
+ console.error(chalk11.red(` \u2717 Failed to load plugin: ${e.message}`));
1553
1798
  }
1554
1799
  }
1555
1800
  }
@@ -1560,7 +1805,7 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
1560
1805
  await kernel.use(serverPlugin);
1561
1806
  trackPlugin("HonoServer");
1562
1807
  } catch (e) {
1563
- console.warn(chalk10.yellow(` \u26A0 HTTP server plugin not available: ${e.message}`));
1808
+ console.warn(chalk11.yellow(` \u26A0 HTTP server plugin not available: ${e.message}`));
1564
1809
  }
1565
1810
  try {
1566
1811
  const { createRestApiPlugin } = await import("@objectstack/rest");
@@ -1579,13 +1824,13 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
1579
1824
  if (enableUI) {
1580
1825
  const studioPath = resolveStudioPath();
1581
1826
  if (!studioPath) {
1582
- console.warn(chalk10.yellow(` \u26A0 @objectstack/studio not found \u2014 skipping UI`));
1827
+ console.warn(chalk11.yellow(` \u26A0 @objectstack/studio not found \u2014 skipping UI`));
1583
1828
  } else if (hasStudioDist(studioPath)) {
1584
- const distPath = path8.join(studioPath, "dist");
1829
+ const distPath = path9.join(studioPath, "dist");
1585
1830
  await kernel.use(createStudioStaticPlugin(distPath, { isDev }));
1586
1831
  trackPlugin("StudioUI");
1587
1832
  } else {
1588
- console.warn(chalk10.yellow(` \u26A0 Studio dist not found \u2014 run "pnpm --filter @objectstack/studio build" first`));
1833
+ console.warn(chalk11.yellow(` \u26A0 Studio dist not found \u2014 run "pnpm --filter @objectstack/studio build" first`));
1589
1834
  }
1590
1835
  }
1591
1836
  await runtime.start();
@@ -1601,33 +1846,33 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
1601
1846
  studioPath: STUDIO_PATH
1602
1847
  });
1603
1848
  process.on("SIGINT", async () => {
1604
- console.warn(chalk10.yellow(`
1849
+ console.warn(chalk11.yellow(`
1605
1850
 
1606
1851
  \u23F9 Stopping server...`));
1607
1852
  await runtime.getKernel().shutdown();
1608
- console.log(chalk10.green(`\u2705 Server stopped`));
1853
+ console.log(chalk11.green(`\u2705 Server stopped`));
1609
1854
  process.exit(0);
1610
1855
  });
1611
1856
  } catch (error) {
1612
1857
  restoreOutput();
1613
1858
  console.log("");
1614
1859
  printError(error.message || String(error));
1615
- if (process.env.DEBUG) console.error(chalk10.dim(error.stack));
1860
+ if (process.env.DEBUG) console.error(chalk11.dim(error.stack));
1616
1861
  process.exit(1);
1617
1862
  }
1618
1863
  });
1619
1864
 
1620
1865
  // src/commands/test.ts
1621
- import { Command as Command9 } from "commander";
1622
- import chalk11 from "chalk";
1623
- import path9 from "path";
1624
- import fs9 from "fs";
1866
+ import { Command as Command10 } from "commander";
1867
+ import chalk12 from "chalk";
1868
+ import path10 from "path";
1869
+ import fs10 from "fs";
1625
1870
  import { QA as CoreQA } from "@objectstack/core";
1626
1871
  function resolveGlob(pattern) {
1627
1872
  if (!pattern.includes("*")) {
1628
- return fs9.existsSync(pattern) ? [pattern] : [];
1873
+ return fs10.existsSync(pattern) ? [pattern] : [];
1629
1874
  }
1630
- const parts = pattern.split(path9.sep.replace("\\", "/"));
1875
+ const parts = pattern.split(path10.sep.replace("\\", "/"));
1631
1876
  const segments = pattern.includes("/") ? pattern.split("/") : parts;
1632
1877
  let baseDir = ".";
1633
1878
  let globStart = 0;
@@ -1636,25 +1881,25 @@ function resolveGlob(pattern) {
1636
1881
  globStart = i;
1637
1882
  break;
1638
1883
  }
1639
- baseDir = i === 0 ? segments[i] : path9.join(baseDir, segments[i]);
1884
+ baseDir = i === 0 ? segments[i] : path10.join(baseDir, segments[i]);
1640
1885
  }
1641
- if (!fs9.existsSync(baseDir)) return [];
1886
+ if (!fs10.existsSync(baseDir)) return [];
1642
1887
  const globPortion = segments.slice(globStart).join("/");
1643
1888
  const regexStr = globPortion.replace(/\./g, "\\.").replace(/\*\*\//g, "(.+/)?").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
1644
1889
  const regex = new RegExp(`^${regexStr}$`);
1645
- const entries = fs9.readdirSync(baseDir, { recursive: true, encoding: "utf-8" });
1646
- return entries.filter((entry) => regex.test(entry.replace(/\\/g, "/"))).map((entry) => path9.join(baseDir, entry)).filter((fullPath) => fs9.statSync(fullPath).isFile());
1890
+ const entries = fs10.readdirSync(baseDir, { recursive: true, encoding: "utf-8" });
1891
+ return entries.filter((entry) => regex.test(entry.replace(/\\/g, "/"))).map((entry) => path10.join(baseDir, entry)).filter((fullPath) => fs10.statSync(fullPath).isFile());
1647
1892
  }
1648
- var testCommand = new Command9("test").description("Run Quality Protocol test scenarios against a running server").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) => {
1649
- console.log(chalk11.bold(`
1893
+ var testCommand = new Command10("test").description("Run Quality Protocol test scenarios against a running server").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) => {
1894
+ console.log(chalk12.bold(`
1650
1895
  \u{1F9EA} ObjectStack Quality Protocol Runner`));
1651
- console.log(chalk11.dim(`-------------------------------------`));
1652
- console.log(`Target: ${chalk11.blue(options.url)}`);
1896
+ console.log(chalk12.dim(`-------------------------------------`));
1897
+ console.log(`Target: ${chalk12.blue(options.url)}`);
1653
1898
  const adapter = new CoreQA.HttpTestAdapter(options.url, options.token);
1654
1899
  const runner = new CoreQA.TestRunner(adapter);
1655
1900
  const testFiles = resolveGlob(filesPattern);
1656
1901
  if (testFiles.length === 0) {
1657
- console.warn(chalk11.yellow(`No test files found matching: ${filesPattern}`));
1902
+ console.warn(chalk12.yellow(`No test files found matching: ${filesPattern}`));
1658
1903
  return;
1659
1904
  }
1660
1905
  console.log(`Found ${testFiles.length} test suites.`);
@@ -1662,19 +1907,19 @@ var testCommand = new Command9("test").description("Run Quality Protocol test sc
1662
1907
  let totalFailed = 0;
1663
1908
  for (const file of testFiles) {
1664
1909
  console.log(`
1665
- \u{1F4C4} Running suite: ${chalk11.bold(path9.basename(file))}`);
1910
+ \u{1F4C4} Running suite: ${chalk12.bold(path10.basename(file))}`);
1666
1911
  try {
1667
- const content = fs9.readFileSync(file, "utf-8");
1912
+ const content = fs10.readFileSync(file, "utf-8");
1668
1913
  const suite = JSON.parse(content);
1669
1914
  const results = await runner.runSuite(suite);
1670
1915
  for (const result of results) {
1671
1916
  const icon = result.passed ? "\u2705" : "\u274C";
1672
1917
  console.log(` ${icon} Scenario: ${result.scenarioId} (${result.duration}ms)`);
1673
1918
  if (!result.passed) {
1674
- console.error(chalk11.red(` Error: ${result.error}`));
1919
+ console.error(chalk12.red(` Error: ${result.error}`));
1675
1920
  result.steps.forEach((step) => {
1676
1921
  if (!step.passed) {
1677
- console.error(chalk11.red(` Step Failed: ${step.stepName}`));
1922
+ console.error(chalk12.red(` Step Failed: ${step.stepName}`));
1678
1923
  if (step.output) console.error(` Output:`, step.output);
1679
1924
  if (step.error) console.error(` Error:`, step.error);
1680
1925
  }
@@ -1685,28 +1930,28 @@ var testCommand = new Command9("test").description("Run Quality Protocol test sc
1685
1930
  }
1686
1931
  }
1687
1932
  } catch (e) {
1688
- console.error(chalk11.red(`Failed to load or run suite ${file}: ${e}`));
1933
+ console.error(chalk12.red(`Failed to load or run suite ${file}: ${e}`));
1689
1934
  totalFailed++;
1690
1935
  }
1691
1936
  }
1692
- console.log(chalk11.dim(`
1937
+ console.log(chalk12.dim(`
1693
1938
  -------------------------------------`));
1694
1939
  if (totalFailed > 0) {
1695
- console.log(chalk11.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
1940
+ console.log(chalk12.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
1696
1941
  process.exit(1);
1697
1942
  } else {
1698
- console.log(chalk11.green(`SUCCESS: All ${totalPassed} scenarios passed.`));
1943
+ console.log(chalk12.green(`SUCCESS: All ${totalPassed} scenarios passed.`));
1699
1944
  process.exit(0);
1700
1945
  }
1701
1946
  });
1702
1947
 
1703
1948
  // src/commands/doctor.ts
1704
- import { Command as Command10 } from "commander";
1705
- import chalk12 from "chalk";
1949
+ import { Command as Command11 } from "commander";
1950
+ import chalk13 from "chalk";
1706
1951
  import { execSync as execSync2 } from "child_process";
1707
- import fs10 from "fs";
1708
- import path10 from "path";
1709
- var doctorCommand = new Command10("doctor").description("Check development environment health").option("-v, --verbose", "Show detailed information").action(async (options) => {
1952
+ import fs11 from "fs";
1953
+ import path11 from "path";
1954
+ var doctorCommand = new Command11("doctor").description("Check development environment health").option("-v, --verbose", "Show detailed information").action(async (options) => {
1710
1955
  printHeader("Environment Health Check");
1711
1956
  const results = [];
1712
1957
  try {
@@ -1765,8 +2010,8 @@ var doctorCommand = new Command10("doctor").description("Check development envir
1765
2010
  });
1766
2011
  }
1767
2012
  const cwd = process.cwd();
1768
- const nodeModulesPath = path10.join(cwd, "node_modules");
1769
- if (fs10.existsSync(nodeModulesPath)) {
2013
+ const nodeModulesPath = path11.join(cwd, "node_modules");
2014
+ if (fs11.existsSync(nodeModulesPath)) {
1770
2015
  results.push({
1771
2016
  name: "Dependencies",
1772
2017
  status: "ok",
@@ -1780,8 +2025,8 @@ var doctorCommand = new Command10("doctor").description("Check development envir
1780
2025
  fix: "Run: pnpm install"
1781
2026
  });
1782
2027
  }
1783
- const specDistPath = path10.join(cwd, "packages/spec/dist");
1784
- if (fs10.existsSync(specDistPath)) {
2028
+ const specDistPath = path11.join(cwd, "packages/spec/dist");
2029
+ if (fs11.existsSync(specDistPath)) {
1785
2030
  results.push({
1786
2031
  name: "@objectstack/spec",
1787
2032
  status: "ok",
@@ -1823,24 +2068,111 @@ var doctorCommand = new Command10("doctor").description("Check development envir
1823
2068
  printError(`${padded} ${result.message}`);
1824
2069
  }
1825
2070
  if (result.fix && (options.verbose || result.status === "error")) {
1826
- console.log(chalk12.dim(` \u2192 ${result.fix}`));
2071
+ console.log(chalk13.dim(` \u2192 ${result.fix}`));
1827
2072
  }
1828
2073
  if (result.status === "error") hasErrors = true;
1829
2074
  if (result.status === "warning") hasWarnings = true;
1830
2075
  });
1831
2076
  console.log("");
1832
2077
  if (hasErrors) {
1833
- console.log(chalk12.red("\u274C Some critical issues found. Please fix them before continuing."));
1834
- results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(chalk12.dim(` ${r.fix}`)));
2078
+ console.log(chalk13.red("\u274C Some critical issues found. Please fix them before continuing."));
2079
+ results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(chalk13.dim(` ${r.fix}`)));
1835
2080
  process.exit(1);
1836
2081
  } else if (hasWarnings) {
1837
- console.log(chalk12.yellow("\u26A0\uFE0F Environment is functional but has some warnings."));
1838
- console.log(chalk12.dim(" Run with --verbose to see fix suggestions."));
2082
+ console.log(chalk13.yellow("\u26A0\uFE0F Environment is functional but has some warnings."));
2083
+ console.log(chalk13.dim(" Run with --verbose to see fix suggestions."));
1839
2084
  } else {
1840
- console.log(chalk12.green("\u2705 Environment is healthy and ready for development!"));
2085
+ console.log(chalk13.green("\u2705 Environment is healthy and ready for development!"));
1841
2086
  }
1842
2087
  console.log("");
1843
2088
  });
2089
+
2090
+ // src/utils/plugin-commands.ts
2091
+ import chalk14 from "chalk";
2092
+ async function loadPluginCommands(program) {
2093
+ let config;
2094
+ try {
2095
+ const loaded = await loadConfig();
2096
+ config = loaded.config;
2097
+ } catch {
2098
+ return;
2099
+ }
2100
+ const plugins = [
2101
+ ...config.plugins || [],
2102
+ ...config.devPlugins || []
2103
+ ];
2104
+ const contributions = [];
2105
+ for (const plugin of plugins) {
2106
+ if (!plugin || typeof plugin !== "object") continue;
2107
+ const p = plugin;
2108
+ const manifest = p.manifest;
2109
+ const contributes = manifest?.contributes ?? p.contributes;
2110
+ if (!contributes) continue;
2111
+ const commands = contributes.commands;
2112
+ if (!Array.isArray(commands)) continue;
2113
+ const pluginName = resolvePluginName2(p);
2114
+ for (const cmd of commands) {
2115
+ if (!cmd || typeof cmd.name !== "string") continue;
2116
+ contributions.push({
2117
+ name: cmd.name,
2118
+ description: typeof cmd.description === "string" ? cmd.description : void 0,
2119
+ module: typeof cmd.module === "string" ? cmd.module : void 0,
2120
+ pluginName
2121
+ });
2122
+ }
2123
+ }
2124
+ if (contributions.length === 0) return;
2125
+ for (const contribution of contributions) {
2126
+ try {
2127
+ const commands = await importPluginCommands(contribution);
2128
+ for (const cmd of commands) {
2129
+ program.addCommand(cmd);
2130
+ }
2131
+ } catch (error) {
2132
+ if (process.env.DEBUG) {
2133
+ const message = error instanceof Error ? error.message : String(error);
2134
+ console.error(
2135
+ chalk14.yellow(` \u26A0 Failed to load CLI command '${contribution.name}' from plugin '${contribution.pluginName}': ${message}`)
2136
+ );
2137
+ }
2138
+ }
2139
+ }
2140
+ }
2141
+ async function importPluginCommands(contribution) {
2142
+ const moduleId = contribution.module ? `${contribution.pluginName}/${contribution.module.replace(/^\.\//, "")}` : contribution.pluginName;
2143
+ const mod = await import(moduleId);
2144
+ if (Array.isArray(mod.commands)) {
2145
+ return mod.commands.filter(isCommandInstance);
2146
+ }
2147
+ const defaultExport = mod.default;
2148
+ if (defaultExport) {
2149
+ if (Array.isArray(defaultExport)) {
2150
+ return defaultExport.filter(isCommandInstance);
2151
+ }
2152
+ if (isCommandInstance(defaultExport)) {
2153
+ return [defaultExport];
2154
+ }
2155
+ }
2156
+ const commands = [];
2157
+ for (const key of Object.keys(mod)) {
2158
+ if (isCommandInstance(mod[key])) {
2159
+ commands.push(mod[key]);
2160
+ }
2161
+ }
2162
+ return commands;
2163
+ }
2164
+ function isCommandInstance(value) {
2165
+ if (value === null || typeof value !== "object") return false;
2166
+ const obj = value;
2167
+ return typeof obj.name === "function" && typeof obj.description === "function" && typeof obj.action === "function" && typeof obj.parse === "function";
2168
+ }
2169
+ function resolvePluginName2(plugin) {
2170
+ if (typeof plugin.name === "string") return plugin.name;
2171
+ const manifest = plugin.manifest;
2172
+ if (manifest && typeof manifest.name === "string") return manifest.name;
2173
+ if (plugin.constructor && plugin.constructor.name !== "Object") return plugin.constructor.name;
2174
+ return "unknown";
2175
+ }
1844
2176
  export {
1845
2177
  compileCommand,
1846
2178
  createCommand,
@@ -1849,6 +2181,8 @@ export {
1849
2181
  generateCommand,
1850
2182
  infoCommand,
1851
2183
  initCommand,
2184
+ loadPluginCommands,
2185
+ pluginCommand,
1852
2186
  serveCommand,
1853
2187
  templates,
1854
2188
  testCommand,