@xano/cli 1.3.4 → 1.3.5-beta.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 +69 -2
- package/dist/base-command.d.ts +12 -1
- package/dist/base-command.js +36 -22
- package/dist/commands/branch/set_live/index.d.ts +14 -0
- package/dist/commands/branch/set_live/index.js +38 -0
- package/dist/commands/ephemeral/push/index.js +6 -1
- package/dist/commands/function/create/index.d.ts +15 -0
- package/dist/commands/function/create/index.js +39 -0
- package/dist/commands/function/edit/index.d.ts +15 -0
- package/dist/commands/function/edit/index.js +39 -0
- package/dist/commands/function/run/index.js +5 -11
- package/dist/commands/policy/catalogue/index.d.ts +16 -0
- package/dist/commands/policy/catalogue/index.js +60 -0
- package/dist/commands/policy/coverage/index.d.ts +17 -0
- package/dist/commands/policy/coverage/index.js +33 -0
- package/dist/commands/policy/create/index.d.ts +18 -0
- package/dist/commands/policy/create/index.js +44 -0
- package/dist/commands/policy/delete/index.d.ts +19 -0
- package/dist/commands/policy/delete/index.js +61 -0
- package/dist/commands/policy/evaluate/index.d.ts +32 -0
- package/dist/commands/policy/evaluate/index.js +71 -0
- package/dist/commands/policy/list/index.d.ts +14 -0
- package/dist/commands/policy/list/index.js +21 -0
- package/dist/commands/policy/parse/index.d.ts +19 -0
- package/dist/commands/policy/parse/index.js +18 -0
- package/dist/commands/policy/publish/index.d.ts +20 -0
- package/dist/commands/policy/publish/index.js +38 -0
- package/dist/commands/policy/runs/index.d.ts +54 -0
- package/dist/commands/policy/runs/index.js +273 -0
- package/dist/commands/policy/status/index.d.ts +21 -0
- package/dist/commands/policy/status/index.js +54 -0
- package/dist/commands/release/create/index.js +4 -0
- package/dist/commands/release/deploy/index.d.ts +19 -0
- package/dist/commands/release/deploy/index.js +68 -6
- package/dist/commands/release/import/index.js +5 -1
- package/dist/commands/release/list/index.d.ts +7 -0
- package/dist/commands/release/list/index.js +46 -9
- package/dist/commands/release/push/index.js +19 -1
- package/dist/commands/sandbox/push/index.js +6 -1
- package/dist/commands/skills/pull/index.d.ts +30 -0
- package/dist/commands/skills/pull/index.js +118 -0
- package/dist/commands/tenant/create/index.d.ts +3 -0
- package/dist/commands/tenant/create/index.js +21 -0
- package/dist/commands/tenant/deploy_release/index.d.ts +40 -0
- package/dist/commands/tenant/deploy_release/index.js +182 -4
- package/dist/commands/tenant/edit/index.d.ts +2 -0
- package/dist/commands/tenant/edit/index.js +39 -4
- package/dist/commands/tenant_deploy_request/bypass/index.d.ts +31 -0
- package/dist/commands/tenant_deploy_request/bypass/index.js +109 -0
- package/dist/commands/tenant_deploy_request/create/index.d.ts +21 -0
- package/dist/commands/tenant_deploy_request/create/index.js +108 -0
- package/dist/commands/tenant_deploy_request/edit/index.d.ts +22 -0
- package/dist/commands/tenant_deploy_request/edit/index.js +103 -0
- package/dist/commands/tenant_deploy_request/get/index.d.ts +25 -0
- package/dist/commands/tenant_deploy_request/get/index.js +113 -0
- package/dist/commands/tenant_deploy_request/list/index.d.ts +20 -0
- package/dist/commands/tenant_deploy_request/list/index.js +122 -0
- package/dist/commands/tenant_deploy_request/revision/index.d.ts +21 -0
- package/dist/commands/tenant_deploy_request/revision/index.js +83 -0
- package/dist/commands/tenant_deploy_request/set_status/index.d.ts +38 -0
- package/dist/commands/tenant_deploy_request/set_status/index.js +159 -0
- package/dist/commands/workspace/pull/index.d.ts +10 -0
- package/dist/commands/workspace/pull/index.js +70 -7
- package/dist/commands/workspace/push/index.d.ts +19 -0
- package/dist/commands/workspace/push/index.js +135 -6
- package/dist/policy-command.d.ts +91 -0
- package/dist/policy-command.js +95 -0
- package/dist/utils/document-parser.d.ts +10 -0
- package/dist/utils/document-parser.js +21 -0
- package/dist/utils/multidoc-push.d.ts +57 -2
- package/dist/utils/multidoc-push.js +102 -33
- package/dist/utils/policy/catalogue.d.ts +85 -0
- package/dist/utils/policy/catalogue.js +196 -0
- package/dist/utils/policy/coverage.d.ts +19 -0
- package/dist/utils/policy/coverage.js +93 -0
- package/dist/utils/policy/errors.d.ts +20 -0
- package/dist/utils/policy/errors.js +93 -0
- package/dist/utils/policy/feedback.d.ts +90 -0
- package/dist/utils/policy/feedback.js +210 -0
- package/dist/utils/policy/findings.d.ts +29 -0
- package/dist/utils/policy/findings.js +82 -0
- package/dist/utils/policy/gate.d.ts +138 -0
- package/dist/utils/policy/gate.js +193 -0
- package/dist/utils/policy/permission.d.ts +7 -0
- package/dist/utils/policy/permission.js +55 -0
- package/dist/utils/policy/push-gate.d.ts +5 -0
- package/dist/utils/policy/push-gate.js +43 -0
- package/dist/utils/policy/release.d.ts +85 -0
- package/dist/utils/policy/release.js +117 -0
- package/dist/utils/policy/request.d.ts +55 -0
- package/dist/utils/policy/request.js +119 -0
- package/dist/utils/policy/runs.d.ts +30 -0
- package/dist/utils/policy/runs.js +101 -0
- package/dist/utils/policy/status.d.ts +55 -0
- package/dist/utils/policy/status.js +127 -0
- package/dist/utils/policy/template.d.ts +66 -0
- package/dist/utils/policy/template.js +152 -0
- package/dist/utils/policy/types.d.ts +295 -0
- package/dist/utils/policy/types.js +1 -0
- package/oclif.manifest.json +5782 -2573
- package/package.json +9 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ux } from '@oclif/core';
|
|
1
|
+
import { Errors, ux } from '@oclif/core';
|
|
2
2
|
import { minimatch } from 'minimatch';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
4
|
import { join, relative } from 'node:path';
|
|
@@ -7,9 +7,21 @@ import { compareLocalToExport, indexLocalDocuments, parseExportDocuments, resolv
|
|
|
7
7
|
import { flattenBundleFile } from './flatten.js';
|
|
8
8
|
import { collectKnowledgeObjects, fetchKnowledge, knowledgePreview, pushKnowledge, syncGuidToFrontmatter, toPushItems, } from './knowledge-sync.js';
|
|
9
9
|
import { fetchFormattedObjects, } from './object-fetch.js';
|
|
10
|
+
import { PushPolicyGateError } from './policy/push-gate.js';
|
|
10
11
|
import { checkReferences, checkTableIndexes } from './reference-checker.js';
|
|
11
12
|
import { invalidateSandboxMarkerForOrigin, readMarkerToken, sandboxMarkerExists, writeMarkerToken, } from './sandbox-marker.js';
|
|
12
13
|
import { hashBytes, hashFile, instanceHost, loadSyncState, recordEntriesForContents, resolveTarget, sandboxTargetIdentity, saveSyncState, syncStateExists, toRelPath, } from './sync-state.js';
|
|
14
|
+
/**
|
|
15
|
+
* A push that failed after its multidoc import landed. The import is not rolled back, so the error
|
|
16
|
+
* carries what it did for the calling command to report beside the failure.
|
|
17
|
+
*/
|
|
18
|
+
export class FailedAfterImportError extends Errors.CLIError {
|
|
19
|
+
imported;
|
|
20
|
+
constructor(message, imported) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.imported = imported;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
13
25
|
/**
|
|
14
26
|
* Filter document entries down to the ones the dry-run preview reports as changed,
|
|
15
27
|
* used to send only the changed documents during a partial (non-`--sync`) push.
|
|
@@ -712,7 +724,9 @@ function mergeKnowledgePreview(preview, knowledge) {
|
|
|
712
724
|
*/
|
|
713
725
|
export async function executePush(ctx, target, flags) {
|
|
714
726
|
const { accessToken, command, inputDir, verboseFetch } = ctx;
|
|
715
|
-
const log = command.log.bind(command);
|
|
727
|
+
const log = ctx.log ?? command.log.bind(command);
|
|
728
|
+
let dryRunPreview = null;
|
|
729
|
+
const stop = (stopped) => ({ knowledge: { deleted: 0, imported: 0 }, preview: dryRunPreview, sent: [], stopped });
|
|
716
730
|
if (flags.fast && flags.sync) {
|
|
717
731
|
command.error('--fast cannot be used with --sync');
|
|
718
732
|
}
|
|
@@ -781,7 +795,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
781
795
|
const doFlatten = await confirm('Flatten these file(s) in place and continue the push?');
|
|
782
796
|
if (!doFlatten) {
|
|
783
797
|
log('Push cancelled. Run `xano flatten <file>` yourself, or re-run to be prompted again.');
|
|
784
|
-
return;
|
|
798
|
+
return stop('cancelled');
|
|
785
799
|
}
|
|
786
800
|
}
|
|
787
801
|
else {
|
|
@@ -949,7 +963,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
949
963
|
}
|
|
950
964
|
log('');
|
|
951
965
|
log('No changes to push.');
|
|
952
|
-
return;
|
|
966
|
+
return stop('no-changes');
|
|
953
967
|
}
|
|
954
968
|
documentEntries = candidates;
|
|
955
969
|
recordSourceEntries = [...documentEntries];
|
|
@@ -994,7 +1008,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
994
1008
|
recordSourceEntries = [...documentEntries];
|
|
995
1009
|
if (documentEntries.length === 0 && knowledgeObjects.length === 0) {
|
|
996
1010
|
log('No local changes since last sync');
|
|
997
|
-
return;
|
|
1011
|
+
return stop('no-changes');
|
|
998
1012
|
}
|
|
999
1013
|
multidoc = documentEntries.map((entry) => entry.content).join('\n---\n');
|
|
1000
1014
|
documentFileMap.clear();
|
|
@@ -1048,7 +1062,6 @@ export async function executePush(ctx, target, flags) {
|
|
|
1048
1062
|
'Content-Type': 'text/x-xanoscript',
|
|
1049
1063
|
};
|
|
1050
1064
|
// ── Dry-run / Preview ─────────────────────────────────────────────────
|
|
1051
|
-
let dryRunPreview = null;
|
|
1052
1065
|
const dryRunUrl = knowledgeOnly ? null : target.buildDryRunUrl(queryParams);
|
|
1053
1066
|
if (dryRunUrl && (flags['dry-run'] || !flags.force)) {
|
|
1054
1067
|
const dryRunParams = new URLSearchParams(queryParams);
|
|
@@ -1140,7 +1153,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
1140
1153
|
log('');
|
|
1141
1154
|
log(ux.colorize('red', `Push blocked: ${criticalOps.length} critical error(s) found.`));
|
|
1142
1155
|
if (!flags.force) {
|
|
1143
|
-
return;
|
|
1156
|
+
return stop('blocked');
|
|
1144
1157
|
}
|
|
1145
1158
|
log(ux.colorize('yellow', 'Proceeding anyway due to --force flag.'));
|
|
1146
1159
|
}
|
|
@@ -1173,10 +1186,10 @@ export async function executePush(ctx, target, flags) {
|
|
|
1173
1186
|
if (persistRecord) {
|
|
1174
1187
|
commitSyncRecordWithSandbox(ctx, inputDir, resolvedTarget, recordEntriesFor(recordSourceEntries.map((entry) => entry.filePath)), { pruneMissing, replace: replaceRecord() }, (message) => command.warn(message));
|
|
1175
1188
|
}
|
|
1176
|
-
return;
|
|
1189
|
+
return stop('no-changes');
|
|
1177
1190
|
}
|
|
1178
1191
|
if (flags['dry-run']) {
|
|
1179
|
-
return;
|
|
1192
|
+
return stop('dry-run');
|
|
1180
1193
|
}
|
|
1181
1194
|
// Warn when the sandbox currently holds a different source workspace than the one being
|
|
1182
1195
|
// pushed. The backend compares the source workspace id we sent against the id stored on
|
|
@@ -1194,7 +1207,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
1194
1207
|
const proceed = await confirm('Continue with push anyway?');
|
|
1195
1208
|
if (!proceed) {
|
|
1196
1209
|
log('Push cancelled. Run `xano sandbox reset` then retry.');
|
|
1197
|
-
return;
|
|
1210
|
+
return stop('cancelled');
|
|
1198
1211
|
}
|
|
1199
1212
|
mismatchConfirmed = true;
|
|
1200
1213
|
}
|
|
@@ -1215,7 +1228,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
1215
1228
|
const confirmed = await confirm(message);
|
|
1216
1229
|
if (!confirmed) {
|
|
1217
1230
|
log('Push cancelled.');
|
|
1218
|
-
return;
|
|
1231
|
+
return stop('cancelled');
|
|
1219
1232
|
}
|
|
1220
1233
|
}
|
|
1221
1234
|
else {
|
|
@@ -1232,15 +1245,17 @@ export async function executePush(ctx, target, flags) {
|
|
|
1232
1245
|
}
|
|
1233
1246
|
}
|
|
1234
1247
|
else {
|
|
1235
|
-
await handleDryRunError(dryRunResponse, command,
|
|
1248
|
+
await handleDryRunError(dryRunResponse, command, target, { log, verbose: flags.verbose });
|
|
1236
1249
|
// If we get here, the user confirmed to proceed without preview
|
|
1237
1250
|
}
|
|
1238
1251
|
}
|
|
1239
1252
|
catch (error) {
|
|
1240
1253
|
// Ctrl+C or SIGINT
|
|
1254
|
+
if (error instanceof PushPolicyGateError)
|
|
1255
|
+
throw error;
|
|
1241
1256
|
if (error.name === 'AbortError' || error.code === 'ERR_USE_AFTER_CLOSE') {
|
|
1242
1257
|
log('\nPush cancelled.');
|
|
1243
|
-
return;
|
|
1258
|
+
return stop('cancelled');
|
|
1244
1259
|
}
|
|
1245
1260
|
// Re-throw oclif errors
|
|
1246
1261
|
if (error instanceof Error && 'oclif' in error) {
|
|
@@ -1271,16 +1286,16 @@ export async function executePush(ctx, target, flags) {
|
|
|
1271
1286
|
if (!hasChanges) {
|
|
1272
1287
|
log('');
|
|
1273
1288
|
log('No changes to push.');
|
|
1274
|
-
return;
|
|
1289
|
+
return stop('no-changes');
|
|
1275
1290
|
}
|
|
1276
1291
|
if (flags['dry-run']) {
|
|
1277
|
-
return;
|
|
1292
|
+
return stop('dry-run');
|
|
1278
1293
|
}
|
|
1279
1294
|
if (process.stdin.isTTY) {
|
|
1280
1295
|
const confirmed = await confirm('Proceed with push?');
|
|
1281
1296
|
if (!confirmed) {
|
|
1282
1297
|
log('Push cancelled.');
|
|
1283
|
-
return;
|
|
1298
|
+
return stop('cancelled');
|
|
1284
1299
|
}
|
|
1285
1300
|
}
|
|
1286
1301
|
else {
|
|
@@ -1296,24 +1311,24 @@ export async function executePush(ctx, target, flags) {
|
|
|
1296
1311
|
}
|
|
1297
1312
|
}
|
|
1298
1313
|
// ── Partial push: filter to changed documents only ────────────────────
|
|
1299
|
-
let
|
|
1314
|
+
let sent = documentEntries;
|
|
1300
1315
|
if (!knowledgeOnly && isPartial && dryRunPreview) {
|
|
1301
|
-
|
|
1302
|
-
if (
|
|
1316
|
+
sent = filterChangedEntries(documentEntries, dryRunPreview.operations, flags.records);
|
|
1317
|
+
if (sent.length === 0 && knowledgeObjects.length === 0) {
|
|
1303
1318
|
log('No changes to push.');
|
|
1304
1319
|
if (persistRecord) {
|
|
1305
1320
|
commitSyncRecordWithSandbox(ctx, inputDir, resolvedTarget, recordEntriesFor(recordSourceEntries.map((entry) => entry.filePath)), { pruneMissing: false, replace: replaceRecord() }, (message) => command.warn(message));
|
|
1306
1321
|
}
|
|
1307
|
-
return;
|
|
1322
|
+
return stop('no-changes');
|
|
1308
1323
|
}
|
|
1309
|
-
multidoc =
|
|
1324
|
+
multidoc = sent.map((d) => d.content).join('\n---\n');
|
|
1310
1325
|
}
|
|
1311
1326
|
const persist = () => {
|
|
1312
1327
|
if (!persistRecord || !ctx.syncTarget)
|
|
1313
1328
|
return;
|
|
1314
1329
|
const paths = [
|
|
1315
1330
|
...recordSourceEntries.map((entry) => entry.filePath),
|
|
1316
|
-
...
|
|
1331
|
+
...sent.map((entry) => entry.filePath),
|
|
1317
1332
|
];
|
|
1318
1333
|
commitSyncRecordWithSandbox(ctx, inputDir, resolvedTarget, recordEntriesFor(paths), { pruneMissing, replace: replaceRecord() }, (message) => command.warn(message));
|
|
1319
1334
|
};
|
|
@@ -1325,6 +1340,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
1325
1340
|
}
|
|
1326
1341
|
const startTime = Date.now();
|
|
1327
1342
|
let pushedDocCount = 0;
|
|
1343
|
+
let pushResponse;
|
|
1328
1344
|
if (!knowledgeOnly && multidoc) {
|
|
1329
1345
|
const apiUrl = target.buildPushUrl(queryParams);
|
|
1330
1346
|
try {
|
|
@@ -1334,14 +1350,19 @@ export async function executePush(ctx, target, flags) {
|
|
|
1334
1350
|
method: 'POST',
|
|
1335
1351
|
}, flags.verbose, accessToken);
|
|
1336
1352
|
if (!response.ok) {
|
|
1337
|
-
|
|
1353
|
+
const errorText = await response.text();
|
|
1354
|
+
refuseIfExplained(command, target, response.status, errorText);
|
|
1355
|
+
handlePushError(response, errorText, documentEntries, inputDir, command);
|
|
1338
1356
|
}
|
|
1339
1357
|
// Parse response for GUID map
|
|
1340
1358
|
const responseText = await response.text();
|
|
1341
1359
|
let guidMap = [];
|
|
1360
|
+
pushResponse = {};
|
|
1342
1361
|
if (responseText && responseText !== 'null') {
|
|
1343
1362
|
try {
|
|
1344
1363
|
const responseJson = JSON.parse(responseText);
|
|
1364
|
+
if (responseJson && typeof responseJson === 'object')
|
|
1365
|
+
pushResponse = responseJson;
|
|
1345
1366
|
if (responseJson?.guid_map && Array.isArray(responseJson.guid_map)) {
|
|
1346
1367
|
guidMap = responseJson.guid_map;
|
|
1347
1368
|
}
|
|
@@ -1367,7 +1388,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
1367
1388
|
let updatedCount = 0;
|
|
1368
1389
|
let canonicalCount = 0;
|
|
1369
1390
|
for (const entry of guidMap) {
|
|
1370
|
-
if (!entry.guid)
|
|
1391
|
+
if (!entry.guid || entry.type === 'policy')
|
|
1371
1392
|
continue;
|
|
1372
1393
|
const key = buildDocumentKey(entry.type, entry.name, entry.verb, entry.api_group);
|
|
1373
1394
|
let filePath = documentFileMap.get(key);
|
|
@@ -1416,6 +1437,8 @@ export async function executePush(ctx, target, flags) {
|
|
|
1416
1437
|
pushedDocCount = multidoc.split('\n---\n').length;
|
|
1417
1438
|
}
|
|
1418
1439
|
catch (error) {
|
|
1440
|
+
if (error instanceof PushPolicyGateError)
|
|
1441
|
+
throw error;
|
|
1419
1442
|
if (error instanceof Error && 'oclif' in error)
|
|
1420
1443
|
throw error;
|
|
1421
1444
|
const elapsedMs = Date.now() - startTime;
|
|
@@ -1435,8 +1458,9 @@ export async function executePush(ctx, target, flags) {
|
|
|
1435
1458
|
includeRecords: flags.records,
|
|
1436
1459
|
inputDir,
|
|
1437
1460
|
isLiveBranch: resolved.isLiveBranch,
|
|
1461
|
+
log,
|
|
1438
1462
|
pushedBytes,
|
|
1439
|
-
pushedEntries,
|
|
1463
|
+
pushedEntries: sent,
|
|
1440
1464
|
resolvedTarget,
|
|
1441
1465
|
syncState: loadedState,
|
|
1442
1466
|
target,
|
|
@@ -1455,6 +1479,12 @@ export async function executePush(ctx, target, flags) {
|
|
|
1455
1479
|
// ── Push knowledge ────────────────────────────────────────────────────
|
|
1456
1480
|
let knowledgeImported = 0;
|
|
1457
1481
|
let knowledgeDeleted = 0;
|
|
1482
|
+
const pushed = () => ({
|
|
1483
|
+
knowledge: { deleted: knowledgeDeleted, imported: knowledgeImported },
|
|
1484
|
+
preview: dryRunPreview,
|
|
1485
|
+
response: pushResponse,
|
|
1486
|
+
sent: pushResponse ? sent : [],
|
|
1487
|
+
});
|
|
1458
1488
|
if (ctx.knowledge && (knowledgeObjects.length > 0 || shouldDelete)) {
|
|
1459
1489
|
const listUrl = ctx.knowledge.listUrl();
|
|
1460
1490
|
try {
|
|
@@ -1492,7 +1522,10 @@ export async function executePush(ctx, target, flags) {
|
|
|
1492
1522
|
if (error instanceof Error && 'oclif' in error)
|
|
1493
1523
|
throw error;
|
|
1494
1524
|
const elapsedMs = Date.now() - startTime;
|
|
1495
|
-
|
|
1525
|
+
const message = `Failed to push knowledge: ${describeNetworkError(error, listUrl, elapsedMs)}`;
|
|
1526
|
+
if (pushResponse)
|
|
1527
|
+
throw new FailedAfterImportError(message, pushed());
|
|
1528
|
+
command.error(message);
|
|
1496
1529
|
}
|
|
1497
1530
|
}
|
|
1498
1531
|
const elapsed = (Math.max(0, Date.now() - startTime - writebackMs) / 1000).toFixed(1);
|
|
@@ -1507,11 +1540,12 @@ export async function executePush(ctx, target, flags) {
|
|
|
1507
1540
|
}
|
|
1508
1541
|
persist();
|
|
1509
1542
|
log(`Pushed ${parts.join(' + ')} to ${target.label} from ${relative(process.cwd(), inputDir) || inputDir} in ${elapsed}s`);
|
|
1543
|
+
return pushed();
|
|
1510
1544
|
}
|
|
1511
1545
|
export async function writeBackFormattedDocuments(args) {
|
|
1512
1546
|
const started = Date.now();
|
|
1513
1547
|
const { accessToken, branch, command, includeEnv, includeRecords, inputDir, isLiveBranch = !branch, pushedBytes, resolvedTarget, syncState, target, verbose, verboseFetch, workspaceId, writebackDiff, writebackObjectLimit, } = args;
|
|
1514
|
-
const log = command.log.bind(command);
|
|
1548
|
+
const log = args.log ?? command.log.bind(command);
|
|
1515
1549
|
const elapsedLabel = () => ` (${((Date.now() - started) / 1000).toFixed(1)}s)`;
|
|
1516
1550
|
const ids = {};
|
|
1517
1551
|
const written = [];
|
|
@@ -1774,10 +1808,37 @@ function formatFailureDuration(elapsedMs) {
|
|
|
1774
1808
|
? ` (failed after ~${human}, near a common ${boundaries.find((b) => Math.abs(totalSeconds - b) <= b * 0.05)}s timeout — likely a server or proxy cutoff)`
|
|
1775
1809
|
: ` (failed after ${human})`;
|
|
1776
1810
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1811
|
+
/** Stop with the target's own guidance when it recognises this refusal. */
|
|
1812
|
+
function refuseIfExplained(command, target, status, body) {
|
|
1813
|
+
let message = body;
|
|
1814
|
+
let payload;
|
|
1815
|
+
try {
|
|
1816
|
+
const parsed = JSON.parse(body);
|
|
1817
|
+
if (typeof parsed?.message === 'string')
|
|
1818
|
+
message = parsed.message;
|
|
1819
|
+
payload = parsed?.payload;
|
|
1820
|
+
}
|
|
1821
|
+
catch {
|
|
1822
|
+
// Not JSON
|
|
1823
|
+
}
|
|
1824
|
+
const refusal = payload;
|
|
1825
|
+
if (status === 403 && refusal?.code === 'policy_gate')
|
|
1826
|
+
throw new PushPolicyGateError(refusal, message === body ? undefined : message);
|
|
1827
|
+
// The plan's policy cap: the platform's message names the plan, its cap and the remedy, and the
|
|
1828
|
+
// push itself would be refused the same way, so the preview stops here rather than skipping.
|
|
1829
|
+
if (status === 403 && refusal?.code === 'policy_plan_limit')
|
|
1830
|
+
command.error(`Push refused (${status}): ${message}`);
|
|
1831
|
+
if (refusal?.code === 'policy_gate_transaction_required') {
|
|
1832
|
+
command.error('Push refused: the live branch policy gate requires a transaction. Remove --no-transaction and retry. Nothing was imported.');
|
|
1833
|
+
}
|
|
1834
|
+
const guidance = target.explainRefusal?.(status, payload);
|
|
1835
|
+
if (guidance)
|
|
1836
|
+
command.error(`Push refused (${status}): ${message}${guidance}`);
|
|
1837
|
+
}
|
|
1838
|
+
async function handleDryRunError(response, command, target, { log, verbose }) {
|
|
1839
|
+
const errorText = await response.text();
|
|
1840
|
+
refuseIfExplained(command, target, response.status, errorText);
|
|
1779
1841
|
if (response.status === 404) {
|
|
1780
|
-
const errorText = await response.text();
|
|
1781
1842
|
try {
|
|
1782
1843
|
const errorJson = JSON.parse(errorText);
|
|
1783
1844
|
if (errorJson.message) {
|
|
@@ -1795,7 +1856,6 @@ async function handleDryRunError(response, command, flags, target) {
|
|
|
1795
1856
|
log('');
|
|
1796
1857
|
}
|
|
1797
1858
|
else {
|
|
1798
|
-
const errorText = await response.text();
|
|
1799
1859
|
// Check if push is disabled
|
|
1800
1860
|
try {
|
|
1801
1861
|
const errorJson = JSON.parse(errorText);
|
|
@@ -1818,8 +1878,17 @@ async function handleDryRunError(response, command, flags, target) {
|
|
|
1818
1878
|
catch {
|
|
1819
1879
|
// Not JSON, fall through
|
|
1820
1880
|
}
|
|
1821
|
-
|
|
1822
|
-
|
|
1881
|
+
let reason = '';
|
|
1882
|
+
try {
|
|
1883
|
+
const errorJson = JSON.parse(errorText);
|
|
1884
|
+
if (typeof errorJson?.message === 'string' && errorJson.message.trim())
|
|
1885
|
+
reason = `: ${errorJson.message.trim().replace(/\.$/, '')}`;
|
|
1886
|
+
}
|
|
1887
|
+
catch {
|
|
1888
|
+
// Not JSON
|
|
1889
|
+
}
|
|
1890
|
+
command.warn(`Push preview failed (${response.status})${reason}. Skipping preview.`);
|
|
1891
|
+
if (verbose) {
|
|
1823
1892
|
log(ux.colorize('dim', errorText));
|
|
1824
1893
|
}
|
|
1825
1894
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { PolicyTemplate } from './template.js';
|
|
2
|
+
import type { PolicyCatalogueEntry } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* `--check` narrows the catalogue to one check. An id that is not in it names the closest matches
|
|
5
|
+
* instead of reprinting the whole list: the usual mistake is a typo one character from a real id.
|
|
6
|
+
*/
|
|
7
|
+
export declare function selectCatalogueCheck(checks: PolicyCatalogueEntry[], id: string): PolicyCatalogueEntry[];
|
|
8
|
+
/** `--recipe` narrows the catalogue's recipes to one, refusing an unknown id as `--check` does. */
|
|
9
|
+
export declare function selectCatalogueRecipe(recipes: PolicyRecipe[], id: string): PolicyRecipe[];
|
|
10
|
+
/**
|
|
11
|
+
* The checks as a table of id, label and description, object kinds and required params, each row
|
|
12
|
+
* followed by the check's `uses`: a goal, the params that achieve it (one line of JSON to copy) and
|
|
13
|
+
* its note.
|
|
14
|
+
*/
|
|
15
|
+
export declare function policyCatalogueSummary(checks: PolicyCatalogueEntry[]): string[];
|
|
16
|
+
/** The catalogue's `template_categories`: the groups templates are filed under, in the order to offer them. */
|
|
17
|
+
export interface PolicyTemplateCategory {
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
}
|
|
21
|
+
/** The catalogue's `template_frameworks`: the framework tags a template can carry, in order, with their labels. */
|
|
22
|
+
export interface PolicyTemplateFramework {
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The catalogue's templates, grouped under their `category` in the order `template_categories`
|
|
28
|
+
* lists them, each group named by its label. A category the list does not name follows in the order
|
|
29
|
+
* first seen, named by its id; a template without one (an older instance) is listed under "Other".
|
|
30
|
+
* Each row names the id `policy create --template` takes, the title, what it would create and its
|
|
31
|
+
* framework tags by label (an unknown id as itself). When any row has a tag, the catalogue's
|
|
32
|
+
* `template_frameworks_note` follows the heading, worded by the platform rather than here; an
|
|
33
|
+
* instance that serves no note gets no tags, since a tag must never show without it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function policyTemplateSummary(templates: PolicyTemplate[], categories?: PolicyTemplateCategory[], frameworks?: PolicyTemplateFramework[], frameworksNote?: string): string[];
|
|
36
|
+
/**
|
|
37
|
+
* One of the catalogue's `recipes`: a goal spanning several templates, served as data. There is no
|
|
38
|
+
* recipe object and no create action: an author creates its `templates` in order, one policy each,
|
|
39
|
+
* then its `rules` together in a policy of their own.
|
|
40
|
+
*/
|
|
41
|
+
export interface PolicyRecipe {
|
|
42
|
+
/** Framework tag ids, as a template's: topics it relates to, never a compliance claim. */
|
|
43
|
+
frameworks?: string[];
|
|
44
|
+
id: string;
|
|
45
|
+
keywords?: string[];
|
|
46
|
+
/** `not_checkable` ids: what no rule in the recipe can see. */
|
|
47
|
+
limits?: string[];
|
|
48
|
+
/** What the author does first: the tags, fields and functions the rules depend on. */
|
|
49
|
+
prerequisites?: string[];
|
|
50
|
+
/** Rules no template covers, shaped like a template's; `needs` are the params the author fills. */
|
|
51
|
+
rules?: Array<{
|
|
52
|
+
check: string;
|
|
53
|
+
needs?: string[];
|
|
54
|
+
params?: Record<string, unknown> | unknown[];
|
|
55
|
+
title: string;
|
|
56
|
+
}>;
|
|
57
|
+
summary?: string;
|
|
58
|
+
/** Template ids, in the order to create them. */
|
|
59
|
+
templates?: string[];
|
|
60
|
+
/** The goal, never a framework name. */
|
|
61
|
+
title: string;
|
|
62
|
+
}
|
|
63
|
+
/** One of the catalogue's `not_checkable` entries: a topic no check can see, and why. */
|
|
64
|
+
export interface PolicyNotCheckable {
|
|
65
|
+
id: string;
|
|
66
|
+
keywords?: string[];
|
|
67
|
+
topic: string;
|
|
68
|
+
why: string;
|
|
69
|
+
}
|
|
70
|
+
/** What the recipe section reads beside the recipes: templates by id, tags, and the not-checkable topics. */
|
|
71
|
+
export interface PolicyRecipeContext {
|
|
72
|
+
frameworks?: PolicyTemplateFramework[];
|
|
73
|
+
frameworksNote?: string;
|
|
74
|
+
notCheckable?: PolicyNotCheckable[];
|
|
75
|
+
templates?: PolicyTemplate[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The catalogue's recipes, each with its title, tags, summary, what to do first, its templates in
|
|
79
|
+
* order (each with its key and title, so `policy create --template` can take it from here), the rules
|
|
80
|
+
* no template covers (their params as JSON and what the author fills in) and the topics no rule in it
|
|
81
|
+
* can see. The framework note follows the heading when any recipe shows a tag, as for templates.
|
|
82
|
+
*/
|
|
83
|
+
export declare function policyRecipeSummary(recipes: PolicyRecipe[], context?: PolicyRecipeContext): string[];
|
|
84
|
+
/** The catalogue's `not_checkable`: each topic no check can see, by id, with why. */
|
|
85
|
+
export declare function policyNotCheckableSummary(entries: PolicyNotCheckable[]): string[];
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--check` narrows the catalogue to one check. An id that is not in it names the closest matches
|
|
3
|
+
* instead of reprinting the whole list: the usual mistake is a typo one character from a real id.
|
|
4
|
+
*/
|
|
5
|
+
export function selectCatalogueCheck(checks, id) {
|
|
6
|
+
return selectById(checks, id, 'a policy check');
|
|
7
|
+
}
|
|
8
|
+
/** `--recipe` narrows the catalogue's recipes to one, refusing an unknown id as `--check` does. */
|
|
9
|
+
export function selectCatalogueRecipe(recipes, id) {
|
|
10
|
+
return selectById(recipes, id, 'a recipe');
|
|
11
|
+
}
|
|
12
|
+
function selectById(entries, id, noun) {
|
|
13
|
+
const wanted = id.trim().toLowerCase();
|
|
14
|
+
const exact = entries.filter(entry => entry.id.toLowerCase() === wanted);
|
|
15
|
+
if (exact.length > 0)
|
|
16
|
+
return exact;
|
|
17
|
+
const near = entries.map(entry => entry.id).filter(known => {
|
|
18
|
+
const lower = known.toLowerCase();
|
|
19
|
+
return lower.includes(wanted) || wanted.includes(lower) || editDistanceWithin(lower, wanted, 2);
|
|
20
|
+
}).sort();
|
|
21
|
+
throw new Error(`"${id}" is not ${noun}.${near.length > 0
|
|
22
|
+
? ` Did you mean ${near.join(', ')}?`
|
|
23
|
+
: ' Run `xano policy catalogue` for the full list.'}`);
|
|
24
|
+
}
|
|
25
|
+
/** Cheap bounded Levenshtein: true when `a` and `b` are at most `max` edits apart. */
|
|
26
|
+
function editDistanceWithin(a, b, max) {
|
|
27
|
+
if (Math.abs(a.length - b.length) > max)
|
|
28
|
+
return false;
|
|
29
|
+
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
30
|
+
for (let i = 1; i <= a.length; i++) {
|
|
31
|
+
const current = [i];
|
|
32
|
+
for (let j = 1; j <= b.length; j++) {
|
|
33
|
+
current[j] = a[i - 1] === b[j - 1]
|
|
34
|
+
? previous[j - 1]
|
|
35
|
+
: 1 + Math.min(previous[j - 1], previous[j], current[j - 1]);
|
|
36
|
+
}
|
|
37
|
+
if (Math.min(...current) > max)
|
|
38
|
+
return false;
|
|
39
|
+
previous = current;
|
|
40
|
+
}
|
|
41
|
+
return previous[b.length] <= max;
|
|
42
|
+
}
|
|
43
|
+
/** How wide a full-width line (a use, a recipe) is wrapped; the check table is its own columns. */
|
|
44
|
+
const LINE_WIDTH = 120;
|
|
45
|
+
/**
|
|
46
|
+
* The checks as a table of id, label and description, object kinds and required params, each row
|
|
47
|
+
* followed by the check's `uses`: a goal, the params that achieve it (one line of JSON to copy) and
|
|
48
|
+
* its note.
|
|
49
|
+
*/
|
|
50
|
+
export function policyCatalogueSummary(checks) {
|
|
51
|
+
if (checks.length === 0)
|
|
52
|
+
return ['No policy checks found.'];
|
|
53
|
+
const rows = [
|
|
54
|
+
['Check ID', 'Label / Description', 'Object kinds', 'Required params'],
|
|
55
|
+
...checks.map(check => [
|
|
56
|
+
check.id,
|
|
57
|
+
[check.label.trim(), check.description, check.fix_hint && `Fix hint: ${check.fix_hint}`].filter(Boolean).join('\n'),
|
|
58
|
+
(check.object_kinds ?? []).join(', ') || '—',
|
|
59
|
+
requiredParams(check),
|
|
60
|
+
]),
|
|
61
|
+
];
|
|
62
|
+
const widths = rows[0].map((_, column) => Math.min([36, 46, 24, 32][column], Math.max(...rows.map(row => row[column].length))));
|
|
63
|
+
return rows.flatMap((row, index) => {
|
|
64
|
+
const cells = row.map((cell, column) => cell.split('\n').flatMap(paragraph => wrap(paragraph, widths[column])));
|
|
65
|
+
const lines = Array.from({ length: Math.max(...cells.map(cell => cell.length)) }, (_, line) => cells.map((cell, column) => (cell[line] ?? '').padEnd(widths[column])).join(' ').trimEnd());
|
|
66
|
+
return index === 0 ? lines : [...lines, ...useLines(checks[index - 1].uses)];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** A check's uses under its row: `Use: goal`, the params as JSON, then `Note: …` when it has one. */
|
|
70
|
+
function useLines(uses) {
|
|
71
|
+
if (!Array.isArray(uses))
|
|
72
|
+
return [];
|
|
73
|
+
return uses.flatMap(use => [
|
|
74
|
+
...hanging(' Use: ', use.goal),
|
|
75
|
+
` ${JSON.stringify(Array.isArray(use.params) ? {} : use.params ?? {})}`,
|
|
76
|
+
...(use.note ? hanging(' Note: ', use.note, ' ') : []),
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
/** Words wrapped at `width`; a word longer than the width keeps a line of its own. */
|
|
80
|
+
function wrap(text, width) {
|
|
81
|
+
const lines = [''];
|
|
82
|
+
for (const word of text.split(/\s+/)) {
|
|
83
|
+
const last = lines.length - 1;
|
|
84
|
+
if (lines[last] && lines[last].length + word.length + 1 > width)
|
|
85
|
+
lines.push(word);
|
|
86
|
+
else
|
|
87
|
+
lines[last] += `${lines[last] ? ' ' : ''}${word}`;
|
|
88
|
+
}
|
|
89
|
+
return lines;
|
|
90
|
+
}
|
|
91
|
+
/** `text` after `first`, wrapped at the line width, its later lines indented as `rest` (default: as wide as `first`). */
|
|
92
|
+
function hanging(first, text, rest = ' '.repeat(first.length)) {
|
|
93
|
+
return wrap(text, LINE_WIDTH - first.length).map((line, index) => `${index === 0 ? first : rest}${line}`);
|
|
94
|
+
}
|
|
95
|
+
/** The params a rule must set: each required one, and any "one of" group. */
|
|
96
|
+
function requiredParams(check) {
|
|
97
|
+
const required = Object.entries(check.params ?? {}).filter(([, schema]) => schema.required)
|
|
98
|
+
.map(([name, schema]) => `${name}: ${schema.type ?? 'any'}`);
|
|
99
|
+
const oneOf = check.requires_one_of ?? [];
|
|
100
|
+
if (oneOf.length > 0)
|
|
101
|
+
required.push(`one of: ${oneOf.join(' | ')}`);
|
|
102
|
+
return required.join(', ') || 'none';
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The catalogue's templates, grouped under their `category` in the order `template_categories`
|
|
106
|
+
* lists them, each group named by its label. A category the list does not name follows in the order
|
|
107
|
+
* first seen, named by its id; a template without one (an older instance) is listed under "Other".
|
|
108
|
+
* Each row names the id `policy create --template` takes, the title, what it would create and its
|
|
109
|
+
* framework tags by label (an unknown id as itself). When any row has a tag, the catalogue's
|
|
110
|
+
* `template_frameworks_note` follows the heading, worded by the platform rather than here; an
|
|
111
|
+
* instance that serves no note gets no tags, since a tag must never show without it.
|
|
112
|
+
*/
|
|
113
|
+
export function policyTemplateSummary(templates, categories = [], frameworks = [], frameworksNote = '') {
|
|
114
|
+
if (templates.length === 0)
|
|
115
|
+
return [];
|
|
116
|
+
const labels = new Map(categories.map(category => [category.id, category.label]));
|
|
117
|
+
const tags = frameworkTags(frameworks, frameworksNote);
|
|
118
|
+
const note = templates.some(template => tags(template).length > 0) ? [frameworksNote.trim()] : [];
|
|
119
|
+
const groups = new Map(categories.map(category => [category.id, []]));
|
|
120
|
+
for (const template of templates) {
|
|
121
|
+
const category = template.category?.trim() || '';
|
|
122
|
+
groups.set(category, [...(groups.get(category) ?? []), template]);
|
|
123
|
+
}
|
|
124
|
+
const width = Math.min(36, Math.max(...templates.map(template => template.id.length)));
|
|
125
|
+
return [
|
|
126
|
+
'Templates (xano policy create --template <id>):',
|
|
127
|
+
...note,
|
|
128
|
+
...[...groups].filter(([, members]) => members.length > 0).flatMap(([category, members]) => [
|
|
129
|
+
'',
|
|
130
|
+
labels.get(category) ?? (category || 'Other'),
|
|
131
|
+
...members.map(template => {
|
|
132
|
+
const rules = template.rules.length === 1 ? '1 rule' : `${template.rules.length} rules`;
|
|
133
|
+
const tagged = tags(template).length > 0 ? ` [${tags(template).join(', ')}]` : '';
|
|
134
|
+
return ` ${template.id.padEnd(width)} ${template.title} (${template.key}, ${template.severity}, ${rules})${tagged}`;
|
|
135
|
+
}),
|
|
136
|
+
]),
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Framework tag labels for a template or recipe (an unknown id as itself). Tags are shown only
|
|
141
|
+
* beside the platform's sentence on what they mean: an instance that serves no note gets none.
|
|
142
|
+
*/
|
|
143
|
+
function frameworkTags(frameworks, frameworksNote) {
|
|
144
|
+
const labels = new Map(frameworks.map(framework => [framework.id, framework.label]));
|
|
145
|
+
return entry => frameworksNote.trim() === '' || !Array.isArray(entry.frameworks) ? [] : entry.frameworks.map(id => labels.get(id) ?? id);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The catalogue's recipes, each with its title, tags, summary, what to do first, its templates in
|
|
149
|
+
* order (each with its key and title, so `policy create --template` can take it from here), the rules
|
|
150
|
+
* no template covers (their params as JSON and what the author fills in) and the topics no rule in it
|
|
151
|
+
* can see. The framework note follows the heading when any recipe shows a tag, as for templates.
|
|
152
|
+
*/
|
|
153
|
+
export function policyRecipeSummary(recipes, context = {}) {
|
|
154
|
+
if (recipes.length === 0)
|
|
155
|
+
return [];
|
|
156
|
+
const { frameworks = [], frameworksNote = '', notCheckable = [], templates = [] } = context;
|
|
157
|
+
const tags = frameworkTags(frameworks, frameworksNote);
|
|
158
|
+
const note = recipes.some(recipe => tags(recipe).length > 0) ? [frameworksNote.trim()] : [];
|
|
159
|
+
const byId = new Map(templates.map(template => [template.id, template]));
|
|
160
|
+
const topics = new Map(notCheckable.map(entry => [entry.id, entry.topic]));
|
|
161
|
+
return [
|
|
162
|
+
'Recipes (goals that span several templates; nothing creates a recipe: create its templates, then its rules):',
|
|
163
|
+
...note,
|
|
164
|
+
...recipes.flatMap(recipe => {
|
|
165
|
+
const tagged = tags(recipe).length > 0 ? ` [${tags(recipe).join(', ')}]` : '';
|
|
166
|
+
const steps = recipe.templates ?? [];
|
|
167
|
+
const rules = recipe.rules ?? [];
|
|
168
|
+
const limits = recipe.limits ?? [];
|
|
169
|
+
return [
|
|
170
|
+
'',
|
|
171
|
+
` ${recipe.id} ${recipe.title}${tagged}`,
|
|
172
|
+
...(recipe.summary ? hanging(' ', recipe.summary) : []),
|
|
173
|
+
...((recipe.prerequisites ?? []).length > 0 ? [' First:', ...(recipe.prerequisites ?? []).flatMap(step => hanging(' - ', step))] : []),
|
|
174
|
+
...(steps.length > 0 ? [' Templates, in order (xano policy create --template <id>, one policy each):', ...steps.map((id, index) => {
|
|
175
|
+
const template = byId.get(id);
|
|
176
|
+
return ` ${index + 1}. ${id}${template ? ` ${template.title} (${template.key})` : ''}`;
|
|
177
|
+
})] : []),
|
|
178
|
+
...(rules.length > 0 ? [' Rules no template covers, together in one policy of your own:', ...rules.flatMap(rule => [
|
|
179
|
+
...hanging(' - ', `${rule.title}: ${rule.check}`),
|
|
180
|
+
` ${JSON.stringify(Array.isArray(rule.params) ? {} : rule.params ?? {})}`,
|
|
181
|
+
...((rule.needs ?? []).length > 0 ? [` You fill in: ${(rule.needs ?? []).join(', ')}`] : []),
|
|
182
|
+
])] : []),
|
|
183
|
+
...(limits.length > 0 ? hanging(' No rule here can see: ', `${limits.map(id => topics.get(id) ?? id).join('; ')}.`, ' ') : []),
|
|
184
|
+
];
|
|
185
|
+
}),
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
/** The catalogue's `not_checkable`: each topic no check can see, by id, with why. */
|
|
189
|
+
export function policyNotCheckableSummary(entries) {
|
|
190
|
+
if (entries.length === 0)
|
|
191
|
+
return [];
|
|
192
|
+
return [
|
|
193
|
+
'What no check can see (recipes name these under "No rule here can see"):',
|
|
194
|
+
...entries.flatMap(entry => ['', ` ${entry.id} ${entry.topic}`, ...hanging(' ', entry.why)]),
|
|
195
|
+
];
|
|
196
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ObjectCoverage } from './types.js';
|
|
2
|
+
/** An object as findings name it, `type:id` (`query:42`), or `null` when the text is not one. */
|
|
3
|
+
export declare function parseObjectRef(text: string): null | {
|
|
4
|
+
id: number;
|
|
5
|
+
type: string;
|
|
6
|
+
};
|
|
7
|
+
/** The object coverage chip, worded as Studio's object panel words it. */
|
|
8
|
+
export declare function coverageChip(summary: ObjectCoverage['summary']): string;
|
|
9
|
+
/**
|
|
10
|
+
* The policies that apply with no finding here, counted from the summary as Studio's panel words its
|
|
11
|
+
* line under the findings: "6 passing · 1 couldn't check · 1 not checked yet". Empty when it counts none.
|
|
12
|
+
*/
|
|
13
|
+
export declare function coverageCounts(summary: ObjectCoverage['summary']): string;
|
|
14
|
+
/**
|
|
15
|
+
* An object's coverage on stdout: the chip, the run it reads, the findings Blocking then Advisory,
|
|
16
|
+
* each with its fix, the policies that apply (counted, then each with its status and why), and the
|
|
17
|
+
* platform's note.
|
|
18
|
+
*/
|
|
19
|
+
export declare function coverageLines(answer: ObjectCoverage): string[];
|