@xnetjs/cli 0.3.1 → 0.4.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/README.md +5 -0
- package/dist/cli.js +215 -142
- package/package.json +14 -12
package/README.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
CLI tools for schema migration workflows, schema diffing, and data integrity diagnostics.
|
|
4
4
|
|
|
5
|
+
> **Alpha software.** xNet is released but early: this package is on npm and
|
|
6
|
+
> usable today, but its API can change between releases, sometimes without a
|
|
7
|
+
> migration path. Pin your version. See the
|
|
8
|
+
> [project README](https://github.com/crs48/xNet#readme) for what alpha means here.
|
|
9
|
+
|
|
5
10
|
## Installation
|
|
6
11
|
|
|
7
12
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -518,7 +518,7 @@ function buildBridgeServer(options, runner = new NodeCommandRunner()) {
|
|
|
518
518
|
});
|
|
519
519
|
}
|
|
520
520
|
function registerBridgeCommand(program2) {
|
|
521
|
-
const bridge = program2.command("bridge").description("Run the local agent bridge for
|
|
521
|
+
const bridge = program2.command("bridge").description("Run the local agent bridge for xNet's AI chat panel");
|
|
522
522
|
bridge.command("serve").description("Serve the agent bridge on loopback (default :31416), driving your own agent CLI").option("--agent <command>", "Agent CLI to drive (claude, codex, \u2026)", "claude").option("--host <host>", "Loopback host (default 127.0.0.1)").option("--port <n>", `Port (default ${DEFAULT_BRIDGE_PORT})`, parseIntOption2).option(
|
|
523
523
|
"--allow-origin <origin...>",
|
|
524
524
|
"Browser origins permitted (e.g. https://app.xnet.fyi for the web deployment)"
|
|
@@ -528,7 +528,7 @@ function registerBridgeCommand(program2) {
|
|
|
528
528
|
).option("--cwd <dir>", "Working directory the agent runs in (default current dir)").option(
|
|
529
529
|
"--upstream <url>",
|
|
530
530
|
"Front a raw OpenAI-compatible server (e.g. http://localhost:11434 for Ollama) instead of a CLI"
|
|
531
|
-
).option("--upstream-model <id>", "Model id to request from --upstream (default llama3.2)").option("--code", "Enable POST /run agentic code tasks (worktree \u2192 gate \u2192 checkpoint/PR)").option("--mcp", "Give the agent
|
|
531
|
+
).option("--upstream-model <id>", "Model id to request from --upstream (default llama3.2)").option("--code", "Enable POST /run agentic code tasks (worktree \u2192 gate \u2192 checkpoint/PR)").option("--mcp", "Give the agent xNet's workspace tools via `xnet mcp serve`").option(
|
|
532
532
|
"--mcp-api-url <url>",
|
|
533
533
|
"xNet local API URL the MCP server talks to (default http://127.0.0.1:31415)"
|
|
534
534
|
).action(async (options) => {
|
|
@@ -555,7 +555,7 @@ function registerBridgeCommand(program2) {
|
|
|
555
555
|
);
|
|
556
556
|
console.error(`Pairing code: ${handle.pairingToken}`);
|
|
557
557
|
console.error(
|
|
558
|
-
'In
|
|
558
|
+
'In xNet, open the AI panel, select "Local bridge", and paste the pairing code.'
|
|
559
559
|
);
|
|
560
560
|
const shutdown = () => {
|
|
561
561
|
void handle.stop().then(() => process.exit(0));
|
|
@@ -1032,8 +1032,8 @@ import {
|
|
|
1032
1032
|
} from "@xnetjs/sync";
|
|
1033
1033
|
async function getChalk() {
|
|
1034
1034
|
try {
|
|
1035
|
-
const
|
|
1036
|
-
return
|
|
1035
|
+
const chalk3 = await import("chalk");
|
|
1036
|
+
return chalk3.default;
|
|
1037
1037
|
} catch {
|
|
1038
1038
|
const identity = (s) => s;
|
|
1039
1039
|
return {
|
|
@@ -1102,114 +1102,114 @@ function formatDuration(ms) {
|
|
|
1102
1102
|
return `${(ms / 1e3).toFixed(2)}s`;
|
|
1103
1103
|
}
|
|
1104
1104
|
async function doctorCommand(options) {
|
|
1105
|
-
const
|
|
1106
|
-
console.log(
|
|
1107
|
-
console.log(
|
|
1105
|
+
const chalk3 = await getChalk();
|
|
1106
|
+
console.log(chalk3.bold("\nxNet Health Check\n"));
|
|
1107
|
+
console.log(chalk3.dim("\u2500".repeat(50)));
|
|
1108
1108
|
try {
|
|
1109
1109
|
let dataDir;
|
|
1110
1110
|
let changes = [];
|
|
1111
1111
|
try {
|
|
1112
1112
|
dataDir = findDataDir(options.dataDir);
|
|
1113
|
-
console.log(
|
|
1113
|
+
console.log(chalk3.gray(`Data directory: ${dataDir}`));
|
|
1114
1114
|
changes = loadChangesFromDir(dataDir);
|
|
1115
|
-
console.log(
|
|
1115
|
+
console.log(chalk3.gray(`Found ${changes.length} changes`));
|
|
1116
1116
|
} catch {
|
|
1117
|
-
console.log(
|
|
1118
|
-
console.log(
|
|
1119
|
-
console.log(
|
|
1117
|
+
console.log(chalk3.yellow("\nNote: No data directory found."));
|
|
1118
|
+
console.log(chalk3.gray("Use --data-dir to specify a data directory."));
|
|
1119
|
+
console.log(chalk3.gray("Running with demo data...\n"));
|
|
1120
1120
|
changes = generateDemoChanges();
|
|
1121
1121
|
}
|
|
1122
1122
|
console.log();
|
|
1123
|
-
console.log(
|
|
1123
|
+
console.log(chalk3.bold("Checking data integrity..."));
|
|
1124
1124
|
const report = options.quick ? await quickIntegrityCheck(changes) : await verifyIntegrity(changes);
|
|
1125
1125
|
if (options.json) {
|
|
1126
1126
|
console.log(JSON.stringify(report, null, 2));
|
|
1127
1127
|
return;
|
|
1128
1128
|
}
|
|
1129
|
-
printIntegrityResults(report,
|
|
1129
|
+
printIntegrityResults(report, chalk3, options.verbose);
|
|
1130
1130
|
console.log();
|
|
1131
|
-
console.log(
|
|
1131
|
+
console.log(chalk3.bold("Analyzing change chains..."));
|
|
1132
1132
|
const orphans = findOrphans(changes);
|
|
1133
1133
|
const roots = findRoots(changes);
|
|
1134
1134
|
const heads = findHeads(changes);
|
|
1135
1135
|
const depth = getChainDepth(changes);
|
|
1136
|
-
console.log(` ${
|
|
1137
|
-
console.log(` ${
|
|
1138
|
-
console.log(` ${
|
|
1136
|
+
console.log(` ${chalk3.green("\u2713")} Roots: ${roots.length}`);
|
|
1137
|
+
console.log(` ${chalk3.green("\u2713")} Heads: ${heads.length}`);
|
|
1138
|
+
console.log(` ${chalk3.green("\u2713")} Depth: ${depth}`);
|
|
1139
1139
|
if (orphans.length > 0) {
|
|
1140
|
-
console.log(` ${
|
|
1140
|
+
console.log(` ${chalk3.yellow("\u26A0")} Orphans: ${orphans.length}`);
|
|
1141
1141
|
} else {
|
|
1142
|
-
console.log(` ${
|
|
1142
|
+
console.log(` ${chalk3.green("\u2713")} Orphans: 0`);
|
|
1143
1143
|
}
|
|
1144
1144
|
console.log();
|
|
1145
|
-
console.log(
|
|
1146
|
-
console.log(
|
|
1145
|
+
console.log(chalk3.bold("Checking schema compatibility..."));
|
|
1146
|
+
console.log(chalk3.gray(" (Schema analysis requires @xnetjs/data integration)"));
|
|
1147
1147
|
console.log();
|
|
1148
|
-
console.log(
|
|
1149
|
-
console.log(
|
|
1148
|
+
console.log(chalk3.bold("Checking sync state..."));
|
|
1149
|
+
console.log(chalk3.gray(" (Sync analysis requires active SyncProvider)"));
|
|
1150
1150
|
console.log();
|
|
1151
|
-
console.log(
|
|
1151
|
+
console.log(chalk3.dim("\u2500".repeat(50)));
|
|
1152
1152
|
const hasErrors = report.summary.errors > 0;
|
|
1153
1153
|
const hasWarnings = report.summary.warnings > 0 || orphans.length > 0;
|
|
1154
1154
|
if (hasErrors) {
|
|
1155
|
-
console.log(
|
|
1156
|
-
console.log(
|
|
1155
|
+
console.log(chalk3.red(chalk3.bold("Status: UNHEALTHY")));
|
|
1156
|
+
console.log(chalk3.red(" Data integrity issues detected. Run `xnet repair` to fix."));
|
|
1157
1157
|
} else if (hasWarnings) {
|
|
1158
|
-
console.log(
|
|
1159
|
-
console.log(
|
|
1158
|
+
console.log(chalk3.yellow(chalk3.bold("Status: HEALTHY with warnings")));
|
|
1159
|
+
console.log(chalk3.gray(" Some issues detected but data is intact."));
|
|
1160
1160
|
} else {
|
|
1161
|
-
console.log(
|
|
1162
|
-
console.log(
|
|
1161
|
+
console.log(chalk3.green(chalk3.bold("Status: HEALTHY")));
|
|
1162
|
+
console.log(chalk3.green(" All checks passed."));
|
|
1163
1163
|
}
|
|
1164
1164
|
console.log();
|
|
1165
1165
|
} catch (error) {
|
|
1166
|
-
console.error(
|
|
1166
|
+
console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
1167
1167
|
process.exit(1);
|
|
1168
1168
|
}
|
|
1169
1169
|
}
|
|
1170
|
-
function printIntegrityResults(report,
|
|
1170
|
+
function printIntegrityResults(report, chalk3, verbose) {
|
|
1171
1171
|
const { checked, valid, issues, summary, durationMs } = report;
|
|
1172
1172
|
const percentage = checked > 0 ? Math.round(valid / checked * 100) : 100;
|
|
1173
1173
|
if (issues.length === 0) {
|
|
1174
|
-
console.log(` ${
|
|
1175
|
-
console.log(` ${
|
|
1176
|
-
console.log(` ${
|
|
1177
|
-
console.log(
|
|
1174
|
+
console.log(` ${chalk3.green("\u2713")} ${checked} changes verified (${percentage}% valid)`);
|
|
1175
|
+
console.log(` ${chalk3.green("\u2713")} Hash chains intact`);
|
|
1176
|
+
console.log(` ${chalk3.green("\u2713")} No issues detected`);
|
|
1177
|
+
console.log(chalk3.gray(` Completed in ${formatDuration(durationMs)}`));
|
|
1178
1178
|
} else {
|
|
1179
1179
|
console.log(
|
|
1180
|
-
` ${
|
|
1180
|
+
` ${chalk3.yellow("!")} ${checked} changes checked (${valid} valid, ${checked - valid} issues)`
|
|
1181
1181
|
);
|
|
1182
1182
|
if (summary.errors > 0) {
|
|
1183
|
-
console.log(` ${
|
|
1183
|
+
console.log(` ${chalk3.red("\u2717")} ${summary.errors} errors`);
|
|
1184
1184
|
}
|
|
1185
1185
|
if (summary.warnings > 0) {
|
|
1186
|
-
console.log(` ${
|
|
1186
|
+
console.log(` ${chalk3.yellow("\u26A0")} ${summary.warnings} warnings`);
|
|
1187
1187
|
}
|
|
1188
|
-
console.log(
|
|
1188
|
+
console.log(chalk3.gray(` Completed in ${formatDuration(durationMs)}`));
|
|
1189
1189
|
if (verbose) {
|
|
1190
1190
|
console.log();
|
|
1191
|
-
console.log(
|
|
1191
|
+
console.log(chalk3.bold("Issues by type:"));
|
|
1192
1192
|
for (const [type, count] of Object.entries(summary.byType)) {
|
|
1193
1193
|
if (count > 0) {
|
|
1194
1194
|
console.log(` - ${type}: ${count}`);
|
|
1195
1195
|
}
|
|
1196
1196
|
}
|
|
1197
1197
|
console.log();
|
|
1198
|
-
console.log(
|
|
1198
|
+
console.log(chalk3.bold("Details:"));
|
|
1199
1199
|
for (const issue of issues.slice(0, 10)) {
|
|
1200
|
-
const icon = issue.severity === "error" ?
|
|
1200
|
+
const icon = issue.severity === "error" ? chalk3.red("\u2717") : chalk3.yellow("\u26A0");
|
|
1201
1201
|
console.log(` ${icon} [${issue.type}] ${issue.details}`);
|
|
1202
1202
|
if (issue.repairAction) {
|
|
1203
|
-
console.log(
|
|
1203
|
+
console.log(chalk3.gray(` \u2192 ${issue.repairAction.description}`));
|
|
1204
1204
|
}
|
|
1205
1205
|
}
|
|
1206
1206
|
if (issues.length > 10) {
|
|
1207
|
-
console.log(
|
|
1207
|
+
console.log(chalk3.gray(` ... and ${issues.length - 10} more`));
|
|
1208
1208
|
}
|
|
1209
1209
|
}
|
|
1210
1210
|
if (report.repairable) {
|
|
1211
1211
|
console.log();
|
|
1212
|
-
console.log(
|
|
1212
|
+
console.log(chalk3.cyan(` Run \`xnet repair\` to fix these issues.`));
|
|
1213
1213
|
}
|
|
1214
1214
|
}
|
|
1215
1215
|
}
|
|
@@ -1246,8 +1246,8 @@ function generateDemoChanges() {
|
|
|
1246
1246
|
];
|
|
1247
1247
|
}
|
|
1248
1248
|
async function repairCommand(options) {
|
|
1249
|
-
const
|
|
1250
|
-
console.log(
|
|
1249
|
+
const chalk3 = await getChalk();
|
|
1250
|
+
console.log(chalk3.bold("\nxNet Data Repair\n"));
|
|
1251
1251
|
try {
|
|
1252
1252
|
let dataDir;
|
|
1253
1253
|
let changes = [];
|
|
@@ -1255,15 +1255,15 @@ async function repairCommand(options) {
|
|
|
1255
1255
|
dataDir = findDataDir(options.dataDir);
|
|
1256
1256
|
changes = loadChangesFromDir(dataDir);
|
|
1257
1257
|
} catch {
|
|
1258
|
-
console.log(
|
|
1259
|
-
console.log(
|
|
1258
|
+
console.log(chalk3.yellow("Note: No data directory found."));
|
|
1259
|
+
console.log(chalk3.gray("Using demo data for illustration.\n"));
|
|
1260
1260
|
dataDir = "";
|
|
1261
1261
|
changes = generateDemoChanges();
|
|
1262
1262
|
}
|
|
1263
|
-
console.log(
|
|
1263
|
+
console.log(chalk3.gray("Running integrity check..."));
|
|
1264
1264
|
const report = await verifyIntegrity(changes);
|
|
1265
1265
|
if (report.issues.length === 0) {
|
|
1266
|
-
console.log(
|
|
1266
|
+
console.log(chalk3.green("\n\u2713 No issues found. Nothing to repair."));
|
|
1267
1267
|
return;
|
|
1268
1268
|
}
|
|
1269
1269
|
console.log(`
|
|
@@ -1272,7 +1272,7 @@ Found ${report.issues.length} issues:`);
|
|
|
1272
1272
|
console.log(` - ${report.summary.warnings} warnings`);
|
|
1273
1273
|
console.log();
|
|
1274
1274
|
if (options.dryRun) {
|
|
1275
|
-
console.log(
|
|
1275
|
+
console.log(chalk3.yellow("DRY RUN - No changes will be made\n"));
|
|
1276
1276
|
}
|
|
1277
1277
|
const { remainingIssues, repairCount } = await attemptRepair(changes, report.issues);
|
|
1278
1278
|
if (options.json) {
|
|
@@ -1290,33 +1290,33 @@ Found ${report.issues.length} issues:`);
|
|
|
1290
1290
|
);
|
|
1291
1291
|
return;
|
|
1292
1292
|
}
|
|
1293
|
-
console.log(
|
|
1294
|
-
console.log(` ${
|
|
1295
|
-
console.log(` ${
|
|
1293
|
+
console.log(chalk3.bold("Repair results:"));
|
|
1294
|
+
console.log(` ${chalk3.green("\u2713")} Repaired: ${repairCount} issues`);
|
|
1295
|
+
console.log(` ${chalk3.yellow("!")} Remaining: ${remainingIssues.length} issues`);
|
|
1296
1296
|
if (remainingIssues.length > 0) {
|
|
1297
1297
|
console.log();
|
|
1298
|
-
console.log(
|
|
1298
|
+
console.log(chalk3.yellow("Issues that require manual intervention:"));
|
|
1299
1299
|
for (const issue of remainingIssues.slice(0, 5)) {
|
|
1300
1300
|
console.log(` - [${issue.type}] ${issue.details}`);
|
|
1301
1301
|
}
|
|
1302
1302
|
if (remainingIssues.length > 5) {
|
|
1303
|
-
console.log(
|
|
1303
|
+
console.log(chalk3.gray(` ... and ${remainingIssues.length - 5} more`));
|
|
1304
1304
|
}
|
|
1305
1305
|
}
|
|
1306
1306
|
if (!options.dryRun && dataDir && repairCount > 0) {
|
|
1307
1307
|
console.log();
|
|
1308
|
-
console.log(
|
|
1309
|
-
console.log(
|
|
1308
|
+
console.log(chalk3.gray("Note: Writing repaired changes requires NodeStore integration."));
|
|
1309
|
+
console.log(chalk3.gray("Changes are prepared but not written to disk."));
|
|
1310
1310
|
}
|
|
1311
1311
|
console.log();
|
|
1312
1312
|
} catch (error) {
|
|
1313
|
-
console.error(
|
|
1313
|
+
console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
1314
1314
|
process.exit(1);
|
|
1315
1315
|
}
|
|
1316
1316
|
}
|
|
1317
1317
|
async function exportCommand(options) {
|
|
1318
|
-
const
|
|
1319
|
-
console.log(
|
|
1318
|
+
const chalk3 = await getChalk();
|
|
1319
|
+
console.log(chalk3.bold("\nxNet Data Export\n"));
|
|
1320
1320
|
try {
|
|
1321
1321
|
let dataDir;
|
|
1322
1322
|
let changes = [];
|
|
@@ -1324,14 +1324,14 @@ async function exportCommand(options) {
|
|
|
1324
1324
|
dataDir = findDataDir(options.dataDir);
|
|
1325
1325
|
changes = loadChangesFromDir(dataDir);
|
|
1326
1326
|
} catch {
|
|
1327
|
-
console.log(
|
|
1328
|
-
console.log(
|
|
1327
|
+
console.log(chalk3.yellow("Note: No data directory found."));
|
|
1328
|
+
console.log(chalk3.gray("Using demo data for illustration.\n"));
|
|
1329
1329
|
dataDir = "";
|
|
1330
1330
|
changes = generateDemoChanges();
|
|
1331
1331
|
}
|
|
1332
1332
|
const outputPath = resolve6(process.cwd(), options.output);
|
|
1333
|
-
console.log(
|
|
1334
|
-
console.log(
|
|
1333
|
+
console.log(chalk3.gray(`Source: ${dataDir || "demo data"}`));
|
|
1334
|
+
console.log(chalk3.gray(`Output: ${outputPath}`));
|
|
1335
1335
|
console.log();
|
|
1336
1336
|
const exportData = {
|
|
1337
1337
|
version: 1,
|
|
@@ -1356,26 +1356,26 @@ async function exportCommand(options) {
|
|
|
1356
1356
|
} else {
|
|
1357
1357
|
writeFileSync3(outputPath, content, "utf-8");
|
|
1358
1358
|
}
|
|
1359
|
-
console.log(
|
|
1360
|
-
console.log(
|
|
1359
|
+
console.log(chalk3.green(`\u2713 Exported ${changes.length} changes`));
|
|
1360
|
+
console.log(chalk3.gray(` File size: ${Math.round(content.length / 1024)}KB`));
|
|
1361
1361
|
console.log();
|
|
1362
1362
|
} catch (error) {
|
|
1363
|
-
console.error(
|
|
1363
|
+
console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
1364
1364
|
process.exit(1);
|
|
1365
1365
|
}
|
|
1366
1366
|
}
|
|
1367
1367
|
async function importCommand(options) {
|
|
1368
|
-
const
|
|
1369
|
-
console.log(
|
|
1368
|
+
const chalk3 = await getChalk();
|
|
1369
|
+
console.log(chalk3.bold("\nxNet Data Import\n"));
|
|
1370
1370
|
try {
|
|
1371
1371
|
const inputPath = resolve6(process.cwd(), options.input);
|
|
1372
1372
|
if (!existsSync(inputPath)) {
|
|
1373
|
-
console.error(
|
|
1373
|
+
console.error(chalk3.red(`Error: Input file not found: ${inputPath}`));
|
|
1374
1374
|
process.exit(1);
|
|
1375
1375
|
}
|
|
1376
|
-
console.log(
|
|
1376
|
+
console.log(chalk3.gray(`Input: ${inputPath}`));
|
|
1377
1377
|
if (options.dryRun) {
|
|
1378
|
-
console.log(
|
|
1378
|
+
console.log(chalk3.yellow("\nDRY RUN - No changes will be applied\n"));
|
|
1379
1379
|
}
|
|
1380
1380
|
const content = readFileSync(inputPath, "utf-8");
|
|
1381
1381
|
let importData;
|
|
@@ -1390,43 +1390,43 @@ async function importCommand(options) {
|
|
|
1390
1390
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
1391
1391
|
importData = { changes: lines.map((l) => JSON.parse(l)) };
|
|
1392
1392
|
}
|
|
1393
|
-
console.log(
|
|
1393
|
+
console.log(chalk3.gray(`Found ${importData.changes.length} changes to import`));
|
|
1394
1394
|
console.log();
|
|
1395
1395
|
const changes = importData.changes.map((c) => ({
|
|
1396
1396
|
...c,
|
|
1397
1397
|
signature: typeof c.signature === "string" ? new Uint8Array(Buffer.from(c.signature, "base64")) : c.signature
|
|
1398
1398
|
}));
|
|
1399
|
-
console.log(
|
|
1399
|
+
console.log(chalk3.bold("Verifying import data..."));
|
|
1400
1400
|
const report = await quickIntegrityCheck(changes);
|
|
1401
1401
|
if (report.issues.length > 0) {
|
|
1402
|
-
console.log(
|
|
1402
|
+
console.log(chalk3.yellow(` \u26A0 ${report.issues.length} integrity issues found`));
|
|
1403
1403
|
if (report.summary.errors > 0) {
|
|
1404
|
-
console.log(
|
|
1404
|
+
console.log(chalk3.red(` ${report.summary.errors} errors may prevent import`));
|
|
1405
1405
|
}
|
|
1406
1406
|
} else {
|
|
1407
|
-
console.log(
|
|
1407
|
+
console.log(chalk3.green(" \u2713 All changes verified"));
|
|
1408
1408
|
}
|
|
1409
1409
|
if (options.applyMigrations) {
|
|
1410
1410
|
console.log();
|
|
1411
|
-
console.log(
|
|
1412
|
-
console.log(
|
|
1411
|
+
console.log(chalk3.bold("Checking for required migrations..."));
|
|
1412
|
+
console.log(chalk3.gray(" (Migration detection requires schema registry)"));
|
|
1413
1413
|
}
|
|
1414
1414
|
console.log();
|
|
1415
|
-
console.log(
|
|
1415
|
+
console.log(chalk3.bold("Import summary:"));
|
|
1416
1416
|
console.log(` Changes: ${changes.length}`);
|
|
1417
1417
|
console.log(` Roots: ${findRoots(changes).length}`);
|
|
1418
1418
|
console.log(` Heads: ${findHeads(changes).length}`);
|
|
1419
1419
|
console.log(` Chain depth: ${getChainDepth(changes)}`);
|
|
1420
1420
|
if (!options.dryRun) {
|
|
1421
1421
|
console.log();
|
|
1422
|
-
console.log(
|
|
1423
|
-
console.log(
|
|
1422
|
+
console.log(chalk3.gray("Note: Writing imported changes requires NodeStore integration."));
|
|
1423
|
+
console.log(chalk3.gray("Changes are validated but not written to storage."));
|
|
1424
1424
|
}
|
|
1425
1425
|
console.log();
|
|
1426
|
-
console.log(
|
|
1426
|
+
console.log(chalk3.green("\u2713 Import validation complete"));
|
|
1427
1427
|
console.log();
|
|
1428
1428
|
} catch (error) {
|
|
1429
|
-
console.error(
|
|
1429
|
+
console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
1430
1430
|
process.exit(1);
|
|
1431
1431
|
}
|
|
1432
1432
|
}
|
|
@@ -1755,8 +1755,8 @@ import { writeFileSync as writeFileSync4, readFileSync as readFileSync2, existsS
|
|
|
1755
1755
|
import { resolve as resolve7 } from "path";
|
|
1756
1756
|
async function getChalk2() {
|
|
1757
1757
|
try {
|
|
1758
|
-
const
|
|
1759
|
-
return
|
|
1758
|
+
const chalk3 = await import("chalk");
|
|
1759
|
+
return chalk3.default;
|
|
1760
1760
|
} catch {
|
|
1761
1761
|
const identity = (s) => s;
|
|
1762
1762
|
return {
|
|
@@ -1805,7 +1805,7 @@ function findSchema(schemaIRI, schemas) {
|
|
|
1805
1805
|
return null;
|
|
1806
1806
|
}
|
|
1807
1807
|
async function analyzeCommand(options) {
|
|
1808
|
-
const
|
|
1808
|
+
const chalk3 = await getChalk2();
|
|
1809
1809
|
try {
|
|
1810
1810
|
const fromParsed = parseSchemaIRI(options.from);
|
|
1811
1811
|
const toParsed = parseSchemaIRI(options.to);
|
|
@@ -1816,8 +1816,8 @@ async function analyzeCommand(options) {
|
|
|
1816
1816
|
const fromSchema = findSchema(options.from, schemas);
|
|
1817
1817
|
const toSchema = findSchema(options.to, schemas);
|
|
1818
1818
|
if (!fromSchema || !toSchema) {
|
|
1819
|
-
console.log(
|
|
1820
|
-
console.log(
|
|
1819
|
+
console.log(chalk3.yellow("\nNote: Schema file not provided. Showing example output.\n"));
|
|
1820
|
+
console.log(chalk3.gray("Use --schema-file to load actual schemas from a JSON file.\n"));
|
|
1821
1821
|
const mockFromSchema = {
|
|
1822
1822
|
"@id": `xnet://xnet.fyi/${fromParsed.name}@${fromParsed.version}`,
|
|
1823
1823
|
"@type": "xnet://xnet.fyi/Schema",
|
|
@@ -1846,72 +1846,72 @@ async function analyzeCommand(options) {
|
|
|
1846
1846
|
{ "@id": `#priority`, name: "priority", type: "text", required: true }
|
|
1847
1847
|
]
|
|
1848
1848
|
};
|
|
1849
|
-
printAnalysisResult(diffSchemas(mockFromSchema, mockToSchema), options.json,
|
|
1849
|
+
printAnalysisResult(diffSchemas(mockFromSchema, mockToSchema), options.json, chalk3);
|
|
1850
1850
|
return;
|
|
1851
1851
|
}
|
|
1852
1852
|
const diff = diffSchemas(fromSchema, toSchema);
|
|
1853
|
-
printAnalysisResult(diff, options.json,
|
|
1853
|
+
printAnalysisResult(diff, options.json, chalk3);
|
|
1854
1854
|
} catch (error) {
|
|
1855
|
-
console.error(
|
|
1855
|
+
console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
1856
1856
|
process.exit(1);
|
|
1857
1857
|
}
|
|
1858
1858
|
}
|
|
1859
|
-
function printAnalysisResult(diff, json,
|
|
1859
|
+
function printAnalysisResult(diff, json, chalk3) {
|
|
1860
1860
|
if (json) {
|
|
1861
1861
|
console.log(JSON.stringify(diff, null, 2));
|
|
1862
1862
|
return;
|
|
1863
1863
|
}
|
|
1864
|
-
console.log(
|
|
1864
|
+
console.log(chalk3.bold(`
|
|
1865
1865
|
Schema changes: ${diff.fromVersion} \u2192 ${diff.toVersion}
|
|
1866
1866
|
`));
|
|
1867
1867
|
if (diff.changes.length === 0) {
|
|
1868
|
-
console.log(
|
|
1868
|
+
console.log(chalk3.green("No changes detected."));
|
|
1869
1869
|
return;
|
|
1870
1870
|
}
|
|
1871
1871
|
const breaking = diff.changes.filter((c) => c.risk === "breaking");
|
|
1872
1872
|
const caution = diff.changes.filter((c) => c.risk === "caution");
|
|
1873
1873
|
const safe = diff.changes.filter((c) => c.risk === "safe");
|
|
1874
1874
|
if (breaking.length > 0) {
|
|
1875
|
-
console.log(
|
|
1875
|
+
console.log(chalk3.red(chalk3.bold("BREAKING CHANGES:")));
|
|
1876
1876
|
for (const change of breaking) {
|
|
1877
|
-
console.log(
|
|
1877
|
+
console.log(chalk3.red(` - ${change.type.toUpperCase()}: ${change.description}`));
|
|
1878
1878
|
}
|
|
1879
1879
|
console.log();
|
|
1880
1880
|
}
|
|
1881
1881
|
if (caution.length > 0) {
|
|
1882
|
-
console.log(
|
|
1882
|
+
console.log(chalk3.yellow(chalk3.bold("CAUTION:")));
|
|
1883
1883
|
for (const change of caution) {
|
|
1884
|
-
console.log(
|
|
1884
|
+
console.log(chalk3.yellow(` - ${change.type.toUpperCase()}: ${change.description}`));
|
|
1885
1885
|
}
|
|
1886
1886
|
console.log();
|
|
1887
1887
|
}
|
|
1888
1888
|
if (safe.length > 0) {
|
|
1889
|
-
console.log(
|
|
1889
|
+
console.log(chalk3.green(chalk3.bold("SAFE:")));
|
|
1890
1890
|
for (const change of safe) {
|
|
1891
|
-
console.log(
|
|
1891
|
+
console.log(chalk3.green(` - ${change.type.toUpperCase()}: ${change.description}`));
|
|
1892
1892
|
}
|
|
1893
1893
|
console.log();
|
|
1894
1894
|
}
|
|
1895
|
-
console.log(
|
|
1895
|
+
console.log(chalk3.bold("Summary:"));
|
|
1896
1896
|
console.log(
|
|
1897
|
-
` ${
|
|
1897
|
+
` ${chalk3.red(`${diff.summary.breaking} breaking`)} | ${chalk3.yellow(`${diff.summary.caution} caution`)} | ${chalk3.green(`${diff.summary.safe} safe`)}`
|
|
1898
1898
|
);
|
|
1899
1899
|
console.log();
|
|
1900
1900
|
if (diff.changes.some((c) => c.suggestedLens)) {
|
|
1901
|
-
console.log(
|
|
1902
|
-
console.log(
|
|
1901
|
+
console.log(chalk3.bold("Suggested lens:"));
|
|
1902
|
+
console.log(chalk3.dim("\u2500".repeat(40)));
|
|
1903
1903
|
console.log(generateLensSnippet(diff));
|
|
1904
|
-
console.log(
|
|
1904
|
+
console.log(chalk3.dim("\u2500".repeat(40)));
|
|
1905
1905
|
console.log();
|
|
1906
1906
|
}
|
|
1907
1907
|
if (diff.autoMigratable) {
|
|
1908
|
-
console.log(
|
|
1908
|
+
console.log(chalk3.green("\u2713 Automatic migration possible"));
|
|
1909
1909
|
} else {
|
|
1910
|
-
console.log(
|
|
1910
|
+
console.log(chalk3.yellow("\u26A0 Manual intervention required for some changes"));
|
|
1911
1911
|
}
|
|
1912
1912
|
}
|
|
1913
1913
|
async function generateCommand(options) {
|
|
1914
|
-
const
|
|
1914
|
+
const chalk3 = await getChalk2();
|
|
1915
1915
|
try {
|
|
1916
1916
|
const fromParsed = parseSchemaIRI(options.from);
|
|
1917
1917
|
const toParsed = parseSchemaIRI(options.to);
|
|
@@ -1924,7 +1924,7 @@ async function generateCommand(options) {
|
|
|
1924
1924
|
const from = fromSchema ?? createMockSchema(fromParsed.name, fromParsed.version, "from");
|
|
1925
1925
|
const to = toSchema ?? createMockSchema(toParsed.name, toParsed.version, "to");
|
|
1926
1926
|
if (!fromSchema || !toSchema) {
|
|
1927
|
-
console.log(
|
|
1927
|
+
console.log(chalk3.yellow("\nNote: Using mock schemas for demonstration.\n"));
|
|
1928
1928
|
}
|
|
1929
1929
|
const diff = diffSchemas(from, to);
|
|
1930
1930
|
const sourceIRI = from["@id"];
|
|
@@ -1939,24 +1939,24 @@ async function generateCommand(options) {
|
|
|
1939
1939
|
if (options.output) {
|
|
1940
1940
|
const outputPath = resolve7(process.cwd(), options.output);
|
|
1941
1941
|
if (existsSync2(outputPath) && !options.force) {
|
|
1942
|
-
console.error(
|
|
1943
|
-
console.error(
|
|
1942
|
+
console.error(chalk3.red(`Error: File already exists: ${outputPath}`));
|
|
1943
|
+
console.error(chalk3.gray("Use --force to overwrite."));
|
|
1944
1944
|
process.exit(1);
|
|
1945
1945
|
}
|
|
1946
1946
|
writeFileSync4(outputPath, generated.code, "utf-8");
|
|
1947
|
-
console.log(
|
|
1947
|
+
console.log(chalk3.green(`
|
|
1948
1948
|
\u2713 Generated lens written to: ${outputPath}`));
|
|
1949
1949
|
} else {
|
|
1950
1950
|
console.log(generated.code);
|
|
1951
1951
|
}
|
|
1952
1952
|
if (!generated.isComplete) {
|
|
1953
|
-
console.log(
|
|
1953
|
+
console.log(chalk3.yellow("\n\u26A0 Manual completion required:"));
|
|
1954
1954
|
for (const item of generated.manualItems) {
|
|
1955
|
-
console.log(
|
|
1955
|
+
console.log(chalk3.yellow(` - ${item}`));
|
|
1956
1956
|
}
|
|
1957
1957
|
}
|
|
1958
1958
|
} catch (error) {
|
|
1959
|
-
console.error(
|
|
1959
|
+
console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
1960
1960
|
process.exit(1);
|
|
1961
1961
|
}
|
|
1962
1962
|
}
|
|
@@ -1994,43 +1994,43 @@ function createMockSchema(name, version, type) {
|
|
|
1994
1994
|
}
|
|
1995
1995
|
}
|
|
1996
1996
|
async function runCommand(options) {
|
|
1997
|
-
const
|
|
1997
|
+
const chalk3 = await getChalk2();
|
|
1998
1998
|
if (!options.dryRun && !options.apply) {
|
|
1999
|
-
console.error(
|
|
1999
|
+
console.error(chalk3.red("Error: Must specify either --dry-run or --apply"));
|
|
2000
2000
|
process.exit(1);
|
|
2001
2001
|
}
|
|
2002
2002
|
if (options.dryRun && options.apply) {
|
|
2003
|
-
console.error(
|
|
2003
|
+
console.error(chalk3.red("Error: Cannot specify both --dry-run and --apply"));
|
|
2004
2004
|
process.exit(1);
|
|
2005
2005
|
}
|
|
2006
2006
|
try {
|
|
2007
|
-
console.log(
|
|
2007
|
+
console.log(chalk3.blue(`
|
|
2008
2008
|
Migration: ${options.from} \u2192 ${options.to}
|
|
2009
2009
|
`));
|
|
2010
2010
|
if (options.dryRun) {
|
|
2011
|
-
console.log(
|
|
2012
|
-
console.log(
|
|
2013
|
-
console.log(
|
|
2014
|
-
console.log(
|
|
2015
|
-
console.log(
|
|
2011
|
+
console.log(chalk3.yellow("DRY RUN - No changes will be applied\n"));
|
|
2012
|
+
console.log(chalk3.gray("Would migrate nodes:"));
|
|
2013
|
+
console.log(chalk3.gray(" - Scan data directory for nodes with schema " + options.from));
|
|
2014
|
+
console.log(chalk3.gray(" - Apply migration lens to each node"));
|
|
2015
|
+
console.log(chalk3.gray(" - Update schema version to " + options.to));
|
|
2016
2016
|
console.log();
|
|
2017
|
-
console.log(
|
|
2018
|
-
console.log(
|
|
2017
|
+
console.log(chalk3.dim("Note: Full migration requires --data-dir and --lens-file options"));
|
|
2018
|
+
console.log(chalk3.dim(" or a configured NodeStore with registered lenses."));
|
|
2019
2019
|
} else {
|
|
2020
|
-
console.log(
|
|
2021
|
-
console.log(
|
|
2022
|
-
console.log(
|
|
2023
|
-
console.log(
|
|
2024
|
-
console.log(
|
|
2020
|
+
console.log(chalk3.yellow("APPLY MODE - Changes will be written\n"));
|
|
2021
|
+
console.log(chalk3.gray("Would apply migration:"));
|
|
2022
|
+
console.log(chalk3.gray(" - Load nodes with schema " + options.from));
|
|
2023
|
+
console.log(chalk3.gray(" - Transform using migration lens"));
|
|
2024
|
+
console.log(chalk3.gray(" - Write updated nodes with schema " + options.to));
|
|
2025
2025
|
console.log();
|
|
2026
|
-
console.log(
|
|
2027
|
-
console.log(
|
|
2026
|
+
console.log(chalk3.dim("Note: Full migration requires --data-dir and --lens-file options"));
|
|
2027
|
+
console.log(chalk3.dim(" or a configured NodeStore with registered lenses."));
|
|
2028
2028
|
}
|
|
2029
2029
|
console.log();
|
|
2030
|
-
console.log(
|
|
2031
|
-
console.log(
|
|
2030
|
+
console.log(chalk3.green("\u2713 Migration command parsed successfully"));
|
|
2031
|
+
console.log(chalk3.gray(" Full implementation requires NodeStore integration."));
|
|
2032
2032
|
} catch (error) {
|
|
2033
|
-
console.error(
|
|
2033
|
+
console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
2034
2034
|
process.exit(1);
|
|
2035
2035
|
}
|
|
2036
2036
|
}
|
|
@@ -2047,6 +2047,78 @@ function registerMigrateCommand(program2) {
|
|
|
2047
2047
|
});
|
|
2048
2048
|
}
|
|
2049
2049
|
|
|
2050
|
+
// src/commands/publish.ts
|
|
2051
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
2052
|
+
import { dirname as dirname4, join as join8, resolve as resolve8 } from "path";
|
|
2053
|
+
import { buildStaticSite } from "@xnetjs/publish";
|
|
2054
|
+
import chalk2 from "chalk";
|
|
2055
|
+
function assertPublication(value) {
|
|
2056
|
+
if (typeof value !== "object" || value === null) {
|
|
2057
|
+
throw new Error("publication file must be a JSON object");
|
|
2058
|
+
}
|
|
2059
|
+
const file = value;
|
|
2060
|
+
if (!file.meta || typeof file.meta.siteUrl !== "string" || typeof file.meta.title !== "string") {
|
|
2061
|
+
throw new Error("publication file needs `meta.siteUrl` and `meta.title`");
|
|
2062
|
+
}
|
|
2063
|
+
if (!Array.isArray(file.posts)) throw new Error("publication file needs a `posts` array");
|
|
2064
|
+
for (const [i, post] of file.posts.entries()) {
|
|
2065
|
+
if (typeof post?.slug !== "string" || post.slug === "") {
|
|
2066
|
+
throw new Error(`posts[${i}] needs a non-empty \`slug\``);
|
|
2067
|
+
}
|
|
2068
|
+
if (typeof post.title !== "string") throw new Error(`posts[${i}] needs a \`title\``);
|
|
2069
|
+
if (typeof post.html !== "string") {
|
|
2070
|
+
throw new Error(`posts[${i}] needs rendered \`html\` (see renderPost() in @xnetjs/publish)`);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
function registerPublishCommand(program2) {
|
|
2075
|
+
const publish = program2.command("publish").description("Render a publication to a static site (HTML, RSS, sitemap)");
|
|
2076
|
+
publish.command("static", { isDefault: true }).description("Write a self-contained static site to a directory").requiredOption("--input <path>", "Publication JSON (meta + posts with rendered html)").option("--out <dir>", "Output directory", "dist/publish").option("--dry-run", "List the files that would be written without writing them").action(async (opts) => {
|
|
2077
|
+
const inputPath = resolve8(opts.input);
|
|
2078
|
+
const outDir = resolve8(opts.out);
|
|
2079
|
+
let parsed;
|
|
2080
|
+
try {
|
|
2081
|
+
parsed = JSON.parse(await readFile4(inputPath, "utf8"));
|
|
2082
|
+
} catch (error) {
|
|
2083
|
+
console.error(chalk2.red(`Could not read ${inputPath}: ${error.message}`));
|
|
2084
|
+
process.exitCode = 1;
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
let file;
|
|
2088
|
+
try {
|
|
2089
|
+
assertPublication(parsed);
|
|
2090
|
+
file = parsed;
|
|
2091
|
+
} catch (error) {
|
|
2092
|
+
console.error(chalk2.red(error.message));
|
|
2093
|
+
process.exitCode = 1;
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
const site = buildStaticSite({
|
|
2097
|
+
meta: file.meta,
|
|
2098
|
+
posts: file.posts,
|
|
2099
|
+
css: file.css,
|
|
2100
|
+
head: file.head
|
|
2101
|
+
});
|
|
2102
|
+
const published = file.posts.filter((p) => Boolean(p.publishedAt)).length;
|
|
2103
|
+
const drafts = file.posts.length - published;
|
|
2104
|
+
if (opts.dryRun) {
|
|
2105
|
+
for (const path of [...site.keys()].sort()) console.log(join8(outDir, path));
|
|
2106
|
+
console.log(chalk2.dim(`${site.size} file(s), not written (--dry-run)`));
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
for (const [path, contents] of site) {
|
|
2110
|
+
const target = join8(outDir, path);
|
|
2111
|
+
await mkdir4(dirname4(target), { recursive: true });
|
|
2112
|
+
await writeFile3(target, contents, "utf8");
|
|
2113
|
+
}
|
|
2114
|
+
console.log(chalk2.green(`Published ${published} post(s) to ${outDir}`));
|
|
2115
|
+
if (drafts > 0) {
|
|
2116
|
+
console.log(chalk2.dim(`${drafts} draft(s) skipped (no publishedAt)`));
|
|
2117
|
+
}
|
|
2118
|
+
console.log(chalk2.dim("Serve this directory from any static host \u2014 no hub required."));
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2050
2122
|
// src/commands/schema.ts
|
|
2051
2123
|
import { writeFileSync as writeFileSync5, readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
2052
2124
|
function extractSchemas() {
|
|
@@ -2210,4 +2282,5 @@ registerCodeCommand(program);
|
|
|
2210
2282
|
registerDataCommand(program);
|
|
2211
2283
|
registerPluginCommand(program);
|
|
2212
2284
|
registerConnectorCommand(program);
|
|
2285
|
+
registerPublishCommand(program);
|
|
2213
2286
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xnetjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "xNet CLI - Schema migrations, diagnostics, and development tools",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
},
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
@@ -31,18 +31,20 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"chalk": "^5.3.0",
|
|
33
33
|
"commander": "^12.0.0",
|
|
34
|
-
"@xnetjs/core": "
|
|
35
|
-
"@xnetjs/crypto": "
|
|
36
|
-
"@xnetjs/data": "
|
|
37
|
-
"@xnetjs/devkit": "1.0.
|
|
38
|
-
"@xnetjs/identity": "
|
|
39
|
-
"@xnetjs/plugins": "
|
|
40
|
-
"@xnetjs/
|
|
41
|
-
"@xnetjs/
|
|
42
|
-
"@xnetjs/
|
|
34
|
+
"@xnetjs/core": "3.0.0",
|
|
35
|
+
"@xnetjs/crypto": "3.0.0",
|
|
36
|
+
"@xnetjs/data": "3.0.0",
|
|
37
|
+
"@xnetjs/devkit": "1.0.1",
|
|
38
|
+
"@xnetjs/identity": "3.0.0",
|
|
39
|
+
"@xnetjs/plugins": "3.0.0",
|
|
40
|
+
"@xnetjs/publish": "0.1.0",
|
|
41
|
+
"@xnetjs/runtime": "0.6.0",
|
|
42
|
+
"@xnetjs/sqlite": "3.0.0",
|
|
43
|
+
"@xnetjs/sync": "3.0.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"tsup": "^8.0.0",
|
|
47
|
+
"yjs": "^13.6.0",
|
|
46
48
|
"tsx": "^4.0.0",
|
|
47
49
|
"typescript": "^5.4.0",
|
|
48
50
|
"vitest": "^4.0.0"
|