@xano/cli 1.0.4-beta.3 → 1.0.4-beta.4
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/commands/sandbox/pull/index.js +12 -2
- package/dist/commands/sandbox/push/index.d.ts +1 -1
- package/dist/commands/sandbox/push/index.js +17 -13
- package/dist/commands/workspace/pull/index.js +13 -2
- package/dist/commands/workspace/push/index.d.ts +1 -1
- package/dist/commands/workspace/push/index.js +15 -5
- package/dist/utils/knowledge-sync.d.ts +113 -0
- package/dist/utils/knowledge-sync.js +404 -0
- package/dist/utils/multidoc-push.d.ts +22 -0
- package/dist/utils/multidoc-push.js +242 -91
- package/oclif.manifest.json +2426 -2424
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { minimatch } from 'minimatch';
|
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
4
|
import { join, relative } from 'node:path';
|
|
5
5
|
import { buildDocumentKey, findFilesWithGuid, parseDocument } from './document-parser.js';
|
|
6
|
+
import { collectKnowledgeObjects, fetchKnowledge, knowledgePreview, pushKnowledge, syncGuidToFrontmatter, toPushItems, } from './knowledge-sync.js';
|
|
6
7
|
import { checkReferences, checkTableIndexes } from './reference-checker.js';
|
|
7
8
|
// Minimum total operations before a workspace mismatch is treated as worth interrupting for.
|
|
8
9
|
// Small change sets (e.g., editing a single function) aren't worth a reset prompt.
|
|
@@ -140,12 +141,15 @@ export function renderBadIndexes(badIndexes, log) {
|
|
|
140
141
|
const TYPE_LABELS = {
|
|
141
142
|
addon: 'Addons',
|
|
142
143
|
agent: 'Agents',
|
|
144
|
+
'agents.md': 'Knowledge: agents.md',
|
|
143
145
|
api_group: 'API Groups',
|
|
146
|
+
doc: 'Knowledge: Docs',
|
|
144
147
|
function: 'Functions',
|
|
145
148
|
mcp_server: 'MCP Servers',
|
|
146
149
|
middleware: 'Middleware',
|
|
147
150
|
query: 'API Endpoints',
|
|
148
151
|
realtime_channel: 'Realtime Channels',
|
|
152
|
+
skill: 'Knowledge: Skills',
|
|
149
153
|
table: 'Tables',
|
|
150
154
|
task: 'Tasks',
|
|
151
155
|
tool: 'Tools',
|
|
@@ -329,6 +333,47 @@ function syncGuidToFile(filePath, guid) {
|
|
|
329
333
|
fs.writeFileSync(filePath, lines.join('\n'), 'utf8');
|
|
330
334
|
return true;
|
|
331
335
|
}
|
|
336
|
+
// ── Knowledge Preview Helpers ─────────────────────────────────────────────────
|
|
337
|
+
/**
|
|
338
|
+
* Compute a knowledge change preview: prefer the server's `dry_run` response,
|
|
339
|
+
* and fall back to a client-side diff (fetch current objects → compare) when the
|
|
340
|
+
* server doesn't support it — mirroring how multidoc tolerates a missing dry-run.
|
|
341
|
+
*/
|
|
342
|
+
async function computeKnowledgePreview(listUrl, objects, branch, shouldDelete, accessToken, verboseFetch, verbose) {
|
|
343
|
+
const items = toPushItems(objects);
|
|
344
|
+
try {
|
|
345
|
+
const serverResult = await pushKnowledge(listUrl, accessToken, verboseFetch, verbose, {
|
|
346
|
+
branch,
|
|
347
|
+
delete: shouldDelete,
|
|
348
|
+
// eslint-disable-next-line camelcase -- external Metadata API field name
|
|
349
|
+
dry_run: true,
|
|
350
|
+
items,
|
|
351
|
+
});
|
|
352
|
+
if (serverResult.operations && serverResult.summary) {
|
|
353
|
+
return { operations: serverResult.operations, summary: serverResult.summary };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
// Server doesn't support dry_run yet; fall through to local diff.
|
|
358
|
+
}
|
|
359
|
+
const serverObjects = await fetchKnowledge(listUrl, branch, accessToken, verboseFetch, verbose);
|
|
360
|
+
return knowledgePreview(items, serverObjects, shouldDelete);
|
|
361
|
+
}
|
|
362
|
+
/** Fold a knowledge preview's summary + operations into the multidoc DryRunResult. */
|
|
363
|
+
function mergeKnowledgePreview(preview, knowledge) {
|
|
364
|
+
for (const [type, counts] of Object.entries(knowledge.summary)) {
|
|
365
|
+
preview.summary[type] = {
|
|
366
|
+
created: counts.created,
|
|
367
|
+
deleted: counts.deleted,
|
|
368
|
+
truncated: 0,
|
|
369
|
+
unchanged: counts.unchanged,
|
|
370
|
+
updated: counts.updated,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
for (const op of knowledge.operations) {
|
|
374
|
+
preview.operations.push({ action: op.action, details: '', name: op.name, type: op.type });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
332
377
|
// ── Main Push Logic ─────────────────────────────────────────────────────────
|
|
333
378
|
/**
|
|
334
379
|
* Execute a multidoc push with preview, validation, partial mode, and GUID sync.
|
|
@@ -337,27 +382,37 @@ function syncGuidToFile(filePath, guid) {
|
|
|
337
382
|
export async function executePush(ctx, target, flags) {
|
|
338
383
|
const { accessToken, command, inputDir, verboseFetch } = ctx;
|
|
339
384
|
const log = command.log.bind(command);
|
|
340
|
-
// ── Collect
|
|
385
|
+
// ── Collect knowledge entries (before file check so knowledge-only push works) ─
|
|
386
|
+
let knowledgeObjects = [];
|
|
387
|
+
if (ctx.knowledge) {
|
|
388
|
+
knowledgeObjects = collectKnowledgeObjects(ctx.knowledge.rootDir, flags.include, flags.exclude);
|
|
389
|
+
}
|
|
390
|
+
// ── Collect and filter .xs files ──────────────────────────────────────
|
|
341
391
|
const allFiles = collectFiles(inputDir);
|
|
342
392
|
const files = applyFilters(allFiles, inputDir, flags.include, flags.exclude, log);
|
|
343
|
-
|
|
393
|
+
const knowledgeOnly = files.length === 0 && (knowledgeObjects.length > 0 || ctx.knowledge !== undefined);
|
|
394
|
+
if (files.length === 0 && !knowledgeOnly) {
|
|
344
395
|
command.error(flags.include || flags.exclude
|
|
345
396
|
? `No .xs files remain after ${[flags.include ? `include ${flags.include.join(', ')}` : '', flags.exclude ? `exclude ${flags.exclude.join(', ')}` : ''].filter(Boolean).join(' and ')} in ${inputDir}`
|
|
346
397
|
: `No .xs files found in ${inputDir}`);
|
|
347
398
|
}
|
|
348
399
|
// ── Read documents ────────────────────────────────────────────────────
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
command.error(`All .xs files in ${inputDir} are empty`);
|
|
352
|
-
}
|
|
353
|
-
let multidoc = documentEntries.map((d) => d.content).join('\n---\n');
|
|
354
|
-
// ── Build document key → file path map (for GUID writeback) ───────────
|
|
400
|
+
let documentEntries = [];
|
|
401
|
+
let multidoc = '';
|
|
355
402
|
const documentFileMap = new Map();
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
403
|
+
if (!knowledgeOnly) {
|
|
404
|
+
documentEntries = readDocuments(files);
|
|
405
|
+
if (documentEntries.length === 0) {
|
|
406
|
+
command.error(`All .xs files in ${inputDir} are empty`);
|
|
407
|
+
}
|
|
408
|
+
multidoc = documentEntries.map((d) => d.content).join('\n---\n');
|
|
409
|
+
// ── Build document key → file path map (for GUID writeback) ─────────
|
|
410
|
+
for (const entry of documentEntries) {
|
|
411
|
+
const parsed = parseDocument(entry.content);
|
|
412
|
+
if (parsed) {
|
|
413
|
+
const key = buildDocumentKey(parsed.type, parsed.name, parsed.verb, parsed.apiGroup);
|
|
414
|
+
documentFileMap.set(key, entry.filePath);
|
|
415
|
+
}
|
|
361
416
|
}
|
|
362
417
|
}
|
|
363
418
|
// ── Resolve push mode ─────────────────────────────────────────────────
|
|
@@ -388,7 +443,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
388
443
|
};
|
|
389
444
|
// ── Dry-run / Preview ─────────────────────────────────────────────────
|
|
390
445
|
let dryRunPreview = null;
|
|
391
|
-
const dryRunUrl = target.buildDryRunUrl(queryParams);
|
|
446
|
+
const dryRunUrl = knowledgeOnly ? null : target.buildDryRunUrl(queryParams);
|
|
392
447
|
if (dryRunUrl && (flags['dry-run'] || !flags.force)) {
|
|
393
448
|
const dryRunParams = new URLSearchParams(queryParams);
|
|
394
449
|
// Always request delete info in dry-run to show remote-only items
|
|
@@ -401,15 +456,16 @@ export async function executePush(ctx, target, flags) {
|
|
|
401
456
|
headers: requestHeaders,
|
|
402
457
|
method: 'POST',
|
|
403
458
|
}, flags.verbose, accessToken);
|
|
404
|
-
if (
|
|
405
|
-
await handleDryRunError(dryRunResponse, command, flags, target);
|
|
406
|
-
// If we get here, the user confirmed to proceed without preview
|
|
407
|
-
}
|
|
408
|
-
else {
|
|
459
|
+
if (dryRunResponse.ok) {
|
|
409
460
|
const dryRunText = await dryRunResponse.text();
|
|
410
461
|
const preview = JSON.parse(dryRunText);
|
|
411
462
|
dryRunPreview = preview;
|
|
412
463
|
if (preview && preview.summary) {
|
|
464
|
+
// ── Merge knowledge preview into the combined DryRunResult ──────
|
|
465
|
+
if (ctx.knowledge && (knowledgeObjects.length > 0 || shouldDelete)) {
|
|
466
|
+
const knowledgeDryRun = await computeKnowledgePreview(ctx.knowledge.listUrl(), knowledgeObjects, ctx.branch, shouldDelete, accessToken, verboseFetch, flags.verbose);
|
|
467
|
+
mergeKnowledgePreview(preview, knowledgeDryRun);
|
|
468
|
+
}
|
|
413
469
|
renderPreview(preview, shouldDelete, target, flags.verbose, isPartial, log);
|
|
414
470
|
// Check for bad cross-references using dry-run operations to avoid false positives
|
|
415
471
|
const badRefs = checkReferences(documentEntries, preview.operations);
|
|
@@ -443,7 +499,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
443
499
|
}
|
|
444
500
|
log(ux.colorize('yellow', 'Proceeding anyway due to --force flag.'));
|
|
445
501
|
}
|
|
446
|
-
// Check for actual changes
|
|
502
|
+
// Check for actual changes (multidoc + knowledge combined)
|
|
447
503
|
const hasChanges = Object.values(preview.summary).some((c) => c.created > 0 || c.updated > 0 || (shouldDelete && c.deleted > 0) || c.truncated > 0);
|
|
448
504
|
// Detect local records
|
|
449
505
|
const tablesWithRecords = flags.records
|
|
@@ -531,6 +587,10 @@ export async function executePush(ctx, target, flags) {
|
|
|
531
587
|
await confirmOrAbort(command, log);
|
|
532
588
|
}
|
|
533
589
|
}
|
|
590
|
+
else {
|
|
591
|
+
await handleDryRunError(dryRunResponse, command, flags, target);
|
|
592
|
+
// If we get here, the user confirmed to proceed without preview
|
|
593
|
+
}
|
|
534
594
|
}
|
|
535
595
|
catch (error) {
|
|
536
596
|
// Ctrl+C or SIGINT
|
|
@@ -552,8 +612,39 @@ export async function executePush(ctx, target, flags) {
|
|
|
552
612
|
await confirmOrAbort(command, log);
|
|
553
613
|
}
|
|
554
614
|
}
|
|
615
|
+
else if (knowledgeOnly && (flags['dry-run'] || !flags.force)) {
|
|
616
|
+
// ── Knowledge-only dry-run / preview ──────────────────────────────────
|
|
617
|
+
const kPreview = await computeKnowledgePreview(ctx.knowledge.listUrl(), knowledgeObjects, ctx.branch, shouldDelete, accessToken, verboseFetch, flags.verbose);
|
|
618
|
+
const syntheticResult = {
|
|
619
|
+
operations: kPreview.operations.map((op) => ({ action: op.action, details: '', name: op.name, type: op.type })),
|
|
620
|
+
summary: Object.fromEntries(Object.entries(kPreview.summary).map(([type, counts]) => [
|
|
621
|
+
type,
|
|
622
|
+
{ created: counts.created, deleted: counts.deleted, truncated: 0, unchanged: counts.unchanged, updated: counts.updated },
|
|
623
|
+
])),
|
|
624
|
+
};
|
|
625
|
+
renderPreview(syntheticResult, shouldDelete, target, flags.verbose, true, log);
|
|
626
|
+
const hasChanges = Object.values(syntheticResult.summary).some((c) => c.created > 0 || c.updated > 0 || (shouldDelete && c.deleted > 0));
|
|
627
|
+
if (!hasChanges) {
|
|
628
|
+
log('');
|
|
629
|
+
log('No changes to push.');
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if (flags['dry-run']) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (process.stdin.isTTY) {
|
|
636
|
+
const confirmed = await confirm('Proceed with push?');
|
|
637
|
+
if (!confirmed) {
|
|
638
|
+
log('Push cancelled.');
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
command.error('Non-interactive environment detected. Use --force to skip confirmation.');
|
|
644
|
+
}
|
|
645
|
+
}
|
|
555
646
|
// ── Show bad references in force mode (preview mode shows them inline) ─
|
|
556
|
-
if (flags.force) {
|
|
647
|
+
if (flags.force && !knowledgeOnly) {
|
|
557
648
|
const badRefs = checkReferences(documentEntries);
|
|
558
649
|
if (badRefs.length > 0) {
|
|
559
650
|
log('');
|
|
@@ -561,96 +652,156 @@ export async function executePush(ctx, target, flags) {
|
|
|
561
652
|
}
|
|
562
653
|
}
|
|
563
654
|
// ── Partial push: filter to changed documents only ────────────────────
|
|
564
|
-
if (isPartial && dryRunPreview) {
|
|
655
|
+
if (!knowledgeOnly && isPartial && dryRunPreview) {
|
|
565
656
|
const filteredEntries = filterChangedEntries(documentEntries, dryRunPreview.operations, flags.records);
|
|
566
|
-
if (filteredEntries.length === 0) {
|
|
657
|
+
if (filteredEntries.length === 0 && knowledgeObjects.length === 0) {
|
|
567
658
|
log('No changes to push.');
|
|
568
659
|
return;
|
|
569
660
|
}
|
|
570
|
-
|
|
661
|
+
if (filteredEntries.length > 0) {
|
|
662
|
+
multidoc = filteredEntries.map((d) => d.content).join('\n---\n');
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
multidoc = '';
|
|
666
|
+
}
|
|
571
667
|
}
|
|
572
668
|
// ── Execute the actual push ───────────────────────────────────────────
|
|
573
|
-
const apiUrl = target.buildPushUrl(queryParams);
|
|
574
669
|
const startTime = Date.now();
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
let guidMap = [];
|
|
587
|
-
if (responseText && responseText !== 'null') {
|
|
588
|
-
try {
|
|
589
|
-
const responseJson = JSON.parse(responseText);
|
|
590
|
-
if (responseJson?.guid_map && Array.isArray(responseJson.guid_map)) {
|
|
591
|
-
guidMap = responseJson.guid_map;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
catch {
|
|
595
|
-
if (flags.verbose) {
|
|
596
|
-
log(`Server response is not JSON; skipping GUID sync\n${responseText}`);
|
|
597
|
-
}
|
|
670
|
+
let pushedDocCount = 0;
|
|
671
|
+
if (!knowledgeOnly && multidoc) {
|
|
672
|
+
const apiUrl = target.buildPushUrl(queryParams);
|
|
673
|
+
try {
|
|
674
|
+
const response = await verboseFetch(apiUrl, {
|
|
675
|
+
body: multidoc,
|
|
676
|
+
headers: requestHeaders,
|
|
677
|
+
method: 'POST',
|
|
678
|
+
}, flags.verbose, accessToken);
|
|
679
|
+
if (!response.ok) {
|
|
680
|
+
handlePushError(response, await response.text(), documentEntries, inputDir, command);
|
|
598
681
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
682
|
+
// Parse response for GUID map
|
|
683
|
+
const responseText = await response.text();
|
|
684
|
+
let guidMap = [];
|
|
685
|
+
if (responseText && responseText !== 'null') {
|
|
686
|
+
try {
|
|
687
|
+
const responseJson = JSON.parse(responseText);
|
|
688
|
+
if (responseJson?.guid_map && Array.isArray(responseJson.guid_map)) {
|
|
689
|
+
guidMap = responseJson.guid_map;
|
|
690
|
+
}
|
|
607
691
|
}
|
|
608
|
-
|
|
609
|
-
|
|
692
|
+
catch {
|
|
693
|
+
if (flags.verbose) {
|
|
694
|
+
log('Server response is not JSON; skipping GUID sync');
|
|
695
|
+
}
|
|
610
696
|
}
|
|
611
697
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const baseKey = `${entry.type}:${entry.name}`;
|
|
620
|
-
const basePath = baseKeyMap.get(baseKey);
|
|
621
|
-
if (basePath) {
|
|
622
|
-
filePath = basePath;
|
|
698
|
+
// Write GUIDs back to local files
|
|
699
|
+
if (flags.guids && guidMap.length > 0) {
|
|
700
|
+
const baseKeyMap = new Map();
|
|
701
|
+
for (const [key, fp] of documentFileMap) {
|
|
702
|
+
const baseKey = key.split(':').slice(0, 2).join(':');
|
|
703
|
+
if (baseKeyMap.has(baseKey)) {
|
|
704
|
+
baseKeyMap.set(baseKey, ''); // Mark as ambiguous
|
|
623
705
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (flags.verbose) {
|
|
627
|
-
log(` No local file found for ${entry.type} "${entry.name}", skipping GUID sync`);
|
|
706
|
+
else {
|
|
707
|
+
baseKeyMap.set(baseKey, fp);
|
|
628
708
|
}
|
|
629
|
-
continue;
|
|
630
709
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
if (
|
|
634
|
-
|
|
710
|
+
let updatedCount = 0;
|
|
711
|
+
for (const entry of guidMap) {
|
|
712
|
+
if (!entry.guid)
|
|
713
|
+
continue;
|
|
714
|
+
const key = buildDocumentKey(entry.type, entry.name, entry.verb, entry.api_group);
|
|
715
|
+
let filePath = documentFileMap.get(key);
|
|
716
|
+
if (!filePath) {
|
|
717
|
+
const baseKey = `${entry.type}:${entry.name}`;
|
|
718
|
+
const basePath = baseKeyMap.get(baseKey);
|
|
719
|
+
if (basePath) {
|
|
720
|
+
filePath = basePath;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (!filePath) {
|
|
724
|
+
if (flags.verbose) {
|
|
725
|
+
log(` No local file found for ${entry.type} "${entry.name}", skipping GUID sync`);
|
|
726
|
+
}
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
const updated = syncGuidToFile(filePath, entry.guid);
|
|
731
|
+
if (updated)
|
|
732
|
+
updatedCount++;
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
command.warn(`Failed to sync GUID to ${filePath}: ${error.message}`);
|
|
736
|
+
}
|
|
635
737
|
}
|
|
636
|
-
|
|
637
|
-
|
|
738
|
+
if (updatedCount > 0) {
|
|
739
|
+
log(`Synced ${updatedCount} GUIDs to local files`);
|
|
638
740
|
}
|
|
639
741
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
742
|
+
pushedDocCount = multidoc.split('\n---\n').length;
|
|
743
|
+
}
|
|
744
|
+
catch (error) {
|
|
745
|
+
if (error instanceof Error && 'oclif' in error)
|
|
746
|
+
throw error;
|
|
747
|
+
const elapsedMs = Date.now() - startTime;
|
|
748
|
+
command.error(`Failed to push multidoc: ${describeNetworkError(error, apiUrl, elapsedMs)}`);
|
|
643
749
|
}
|
|
644
750
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
751
|
+
// ── Push knowledge ────────────────────────────────────────────────────
|
|
752
|
+
let knowledgeImported = 0;
|
|
753
|
+
let knowledgeDeleted = 0;
|
|
754
|
+
if (ctx.knowledge && (knowledgeObjects.length > 0 || shouldDelete)) {
|
|
755
|
+
const listUrl = ctx.knowledge.listUrl();
|
|
756
|
+
try {
|
|
757
|
+
const result = await pushKnowledge(listUrl, accessToken, verboseFetch, flags.verbose, {
|
|
758
|
+
branch: ctx.branch,
|
|
759
|
+
delete: shouldDelete,
|
|
760
|
+
force: false,
|
|
761
|
+
items: toPushItems(knowledgeObjects),
|
|
762
|
+
});
|
|
763
|
+
knowledgeImported = result.imported ?? 0;
|
|
764
|
+
knowledgeDeleted = result.deleted ?? 0;
|
|
765
|
+
// Write GUIDs back into local frontmatter, matching server entries by name.
|
|
766
|
+
if (flags.guids && result.guid_map && result.guid_map.length > 0) {
|
|
767
|
+
const fileByName = new Map(knowledgeObjects.map((o) => [o.name, o.filePath]));
|
|
768
|
+
let kGuidCount = 0;
|
|
769
|
+
for (const entry of result.guid_map) {
|
|
770
|
+
const filePath = entry.guid && entry.name ? fileByName.get(entry.name) : undefined;
|
|
771
|
+
if (!filePath)
|
|
772
|
+
continue;
|
|
773
|
+
try {
|
|
774
|
+
const updated = syncGuidToFrontmatter(filePath, entry.guid);
|
|
775
|
+
if (updated)
|
|
776
|
+
kGuidCount++;
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
command.warn(`Failed to sync knowledge GUID to ${filePath}: ${error.message}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (kGuidCount > 0) {
|
|
783
|
+
log(`Synced ${kGuidCount} knowledge GUIDs to local files`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
catch (error) {
|
|
788
|
+
if (error instanceof Error && 'oclif' in error)
|
|
789
|
+
throw error;
|
|
790
|
+
const elapsedMs = Date.now() - startTime;
|
|
791
|
+
command.error(`Failed to push knowledge: ${describeNetworkError(error, listUrl, elapsedMs)}`);
|
|
792
|
+
}
|
|
650
793
|
}
|
|
651
794
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
652
|
-
const
|
|
653
|
-
|
|
795
|
+
const parts = [];
|
|
796
|
+
if (!knowledgeOnly)
|
|
797
|
+
parts.push(`${pushedDocCount} documents`);
|
|
798
|
+
if (ctx.knowledge && (knowledgeObjects.length > 0 || shouldDelete)) {
|
|
799
|
+
const kParts = [`${knowledgeImported} knowledge file${knowledgeImported === 1 ? '' : 's'}`];
|
|
800
|
+
if (shouldDelete && knowledgeDeleted > 0)
|
|
801
|
+
kParts.push(`${knowledgeDeleted} deleted`);
|
|
802
|
+
parts.push(kParts.join(', '));
|
|
803
|
+
}
|
|
804
|
+
log(`Pushed ${parts.join(' + ')} to ${target.label} from ${relative(process.cwd(), inputDir) || inputDir} in ${elapsed}s`);
|
|
654
805
|
}
|
|
655
806
|
// ── Error Handlers ──────────────────────────────────────────────────────────
|
|
656
807
|
/**
|
|
@@ -666,7 +817,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
666
817
|
* failing — a failure landing near a round boundary (e.g. ~300s) is a strong
|
|
667
818
|
* signal of a server-side or proxy timeout rather than a local network blip.
|
|
668
819
|
*/
|
|
669
|
-
function describeNetworkError(error, url, elapsedMs) {
|
|
820
|
+
export function describeNetworkError(error, url, elapsedMs) {
|
|
670
821
|
if (!(error instanceof Error))
|
|
671
822
|
return String(error);
|
|
672
823
|
let host = url;
|