@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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +13 -0
- package/README.md +88 -3
- package/dist/bin.js +361 -19
- package/dist/index.d.ts +14 -1
- package/dist/index.js +422 -88
- package/package.json +9 -9
- package/src/bin.ts +18 -1
- package/src/commands/plugin.ts +372 -0
- package/src/index.ts +2 -0
- package/src/utils/plugin-commands.ts +163 -0
- package/test/commands.test.ts +6 -0
- package/test/plugin-commands.test.ts +162 -0
- package/test/plugin.test.ts +173 -0
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
|
|
65
|
+
const path12 = issue.path?.join(".") || "";
|
|
66
66
|
const code = issue.code || "";
|
|
67
67
|
const msg = issue.message || "";
|
|
68
|
-
console.log(chalk.red(` \u2717 ${
|
|
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/
|
|
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
|
-
|
|
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 =
|
|
1250
|
-
if (packageName === "all" &&
|
|
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 =
|
|
1263
|
-
const isWorkspaceRoot =
|
|
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(
|
|
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(
|
|
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
|
|
1287
|
-
import
|
|
1288
|
-
import
|
|
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
|
|
1535
|
+
import chalk11 from "chalk";
|
|
1291
1536
|
import { bundleRequire as bundleRequire2 } from "bundle-require";
|
|
1292
1537
|
|
|
1293
1538
|
// src/utils/studio.ts
|
|
1294
|
-
import
|
|
1295
|
-
import
|
|
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
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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 =
|
|
1308
|
-
if (
|
|
1552
|
+
const pkgPath = path8.join(candidate, "package.json");
|
|
1553
|
+
if (fs8.existsSync(pkgPath)) {
|
|
1309
1554
|
try {
|
|
1310
|
-
const pkg = JSON.parse(
|
|
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(
|
|
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
|
|
1571
|
+
return path8.dirname(resolved);
|
|
1327
1572
|
} catch {
|
|
1328
1573
|
}
|
|
1329
1574
|
}
|
|
1330
|
-
const directPath =
|
|
1331
|
-
if (
|
|
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
|
|
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 =
|
|
1352
|
-
const indexPath =
|
|
1353
|
-
if (!
|
|
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 =
|
|
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 =
|
|
1613
|
+
const filePath = path8.join(absoluteDist, reqPath);
|
|
1369
1614
|
if (!filePath.startsWith(absoluteDist)) {
|
|
1370
1615
|
return c.text("Forbidden", 403);
|
|
1371
1616
|
}
|
|
1372
|
-
if (
|
|
1373
|
-
const content =
|
|
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 =
|
|
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
|
|
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 =
|
|
1441
|
-
const relativeConfig =
|
|
1442
|
-
if (!
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1827
|
+
console.warn(chalk11.yellow(` \u26A0 @objectstack/studio not found \u2014 skipping UI`));
|
|
1583
1828
|
} else if (hasStudioDist(studioPath)) {
|
|
1584
|
-
const distPath =
|
|
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(
|
|
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(
|
|
1849
|
+
console.warn(chalk11.yellow(`
|
|
1605
1850
|
|
|
1606
1851
|
\u23F9 Stopping server...`));
|
|
1607
1852
|
await runtime.getKernel().shutdown();
|
|
1608
|
-
console.log(
|
|
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(
|
|
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
|
|
1622
|
-
import
|
|
1623
|
-
import
|
|
1624
|
-
import
|
|
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
|
|
1873
|
+
return fs10.existsSync(pattern) ? [pattern] : [];
|
|
1629
1874
|
}
|
|
1630
|
-
const parts = pattern.split(
|
|
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] :
|
|
1884
|
+
baseDir = i === 0 ? segments[i] : path10.join(baseDir, segments[i]);
|
|
1640
1885
|
}
|
|
1641
|
-
if (!
|
|
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 =
|
|
1646
|
-
return entries.filter((entry) => regex.test(entry.replace(/\\/g, "/"))).map((entry) =>
|
|
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
|
|
1649
|
-
console.log(
|
|
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(
|
|
1652
|
-
console.log(`Target: ${
|
|
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(
|
|
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: ${
|
|
1910
|
+
\u{1F4C4} Running suite: ${chalk12.bold(path10.basename(file))}`);
|
|
1666
1911
|
try {
|
|
1667
|
-
const content =
|
|
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(
|
|
1919
|
+
console.error(chalk12.red(` Error: ${result.error}`));
|
|
1675
1920
|
result.steps.forEach((step) => {
|
|
1676
1921
|
if (!step.passed) {
|
|
1677
|
-
console.error(
|
|
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(
|
|
1933
|
+
console.error(chalk12.red(`Failed to load or run suite ${file}: ${e}`));
|
|
1689
1934
|
totalFailed++;
|
|
1690
1935
|
}
|
|
1691
1936
|
}
|
|
1692
|
-
console.log(
|
|
1937
|
+
console.log(chalk12.dim(`
|
|
1693
1938
|
-------------------------------------`));
|
|
1694
1939
|
if (totalFailed > 0) {
|
|
1695
|
-
console.log(
|
|
1940
|
+
console.log(chalk12.red(`FAILED: ${totalFailed} scenarios failed. ${totalPassed} passed.`));
|
|
1696
1941
|
process.exit(1);
|
|
1697
1942
|
} else {
|
|
1698
|
-
console.log(
|
|
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
|
|
1705
|
-
import
|
|
1949
|
+
import { Command as Command11 } from "commander";
|
|
1950
|
+
import chalk13 from "chalk";
|
|
1706
1951
|
import { execSync as execSync2 } from "child_process";
|
|
1707
|
-
import
|
|
1708
|
-
import
|
|
1709
|
-
var doctorCommand = new
|
|
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 =
|
|
1769
|
-
if (
|
|
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 =
|
|
1784
|
-
if (
|
|
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(
|
|
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(
|
|
1834
|
-
results.filter((r) => r.status === "error" && r.fix).forEach((r) => console.log(
|
|
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(
|
|
1838
|
-
console.log(
|
|
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(
|
|
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,
|