norn-cli 2.6.1 → 2.8.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/CHANGELOG.md +37 -0
- package/README.md +37 -2
- package/dist/cli.js +804 -16
- package/foo.ps1 +1 -0
- package/package.json +81 -10
- package/AGENTS.md +0 -95
- package/demos/tests-showcase/scripts/fake-sql-adapter.js +0 -70
- package/scripts/__pycache__/reddit_signal_miner.cpython-312.pyc +0 -0
- package/scripts/generate-coding-bed.mjs +0 -243
- package/scripts/reddit_signal_miner.py +0 -490
- package/scripts/validate-skills.mjs +0 -50
package/dist/cli.js
CHANGED
|
@@ -101065,10 +101065,10 @@ var require_resolveCommand = __commonJS({
|
|
|
101065
101065
|
}
|
|
101066
101066
|
return resolved;
|
|
101067
101067
|
}
|
|
101068
|
-
function
|
|
101068
|
+
function resolveCommand2(parsed) {
|
|
101069
101069
|
return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
|
|
101070
101070
|
}
|
|
101071
|
-
module2.exports =
|
|
101071
|
+
module2.exports = resolveCommand2;
|
|
101072
101072
|
}
|
|
101073
101073
|
});
|
|
101074
101074
|
|
|
@@ -101152,19 +101152,19 @@ var require_parse2 = __commonJS({
|
|
|
101152
101152
|
"node_modules/cross-spawn/lib/parse.js"(exports2, module2) {
|
|
101153
101153
|
"use strict";
|
|
101154
101154
|
var path20 = require("path");
|
|
101155
|
-
var
|
|
101155
|
+
var resolveCommand2 = require_resolveCommand();
|
|
101156
101156
|
var escape2 = require_escape();
|
|
101157
101157
|
var readShebang = require_readShebang();
|
|
101158
101158
|
var isWin = process.platform === "win32";
|
|
101159
101159
|
var isExecutableRegExp = /\.(?:com|exe)$/i;
|
|
101160
101160
|
var isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
|
|
101161
101161
|
function detectShebang(parsed) {
|
|
101162
|
-
parsed.file =
|
|
101162
|
+
parsed.file = resolveCommand2(parsed);
|
|
101163
101163
|
const shebang = parsed.file && readShebang(parsed.file);
|
|
101164
101164
|
if (shebang) {
|
|
101165
101165
|
parsed.args.unshift(parsed.file);
|
|
101166
101166
|
parsed.command = shebang;
|
|
101167
|
-
return
|
|
101167
|
+
return resolveCommand2(parsed);
|
|
101168
101168
|
}
|
|
101169
101169
|
return parsed.file;
|
|
101170
101170
|
}
|
|
@@ -101266,7 +101266,7 @@ var require_cross_spawn = __commonJS({
|
|
|
101266
101266
|
var cp = require("child_process");
|
|
101267
101267
|
var parse5 = require_parse2();
|
|
101268
101268
|
var enoent = require_enoent();
|
|
101269
|
-
function
|
|
101269
|
+
function spawn6(command, args, options) {
|
|
101270
101270
|
const parsed = parse5(command, args, options);
|
|
101271
101271
|
const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
|
|
101272
101272
|
enoent.hookChildProcess(spawned, parsed);
|
|
@@ -101278,8 +101278,8 @@ var require_cross_spawn = __commonJS({
|
|
|
101278
101278
|
result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
|
|
101279
101279
|
return result;
|
|
101280
101280
|
}
|
|
101281
|
-
module2.exports =
|
|
101282
|
-
module2.exports.spawn =
|
|
101281
|
+
module2.exports = spawn6;
|
|
101282
|
+
module2.exports.spawn = spawn6;
|
|
101283
101283
|
module2.exports.sync = spawnSync3;
|
|
101284
101284
|
module2.exports._parse = parse5;
|
|
101285
101285
|
module2.exports._enoent = enoent;
|
|
@@ -134283,6 +134283,635 @@ function applyRequestTimeoutForPath(startPath, overrideMs) {
|
|
|
134283
134283
|
return timeoutMs;
|
|
134284
134284
|
}
|
|
134285
134285
|
|
|
134286
|
+
// src/k8s/k8sRunner.ts
|
|
134287
|
+
var import_child_process4 = require("child_process");
|
|
134288
|
+
|
|
134289
|
+
// src/k8s/k8sParser.ts
|
|
134290
|
+
function tokenizeCommandLine(line2) {
|
|
134291
|
+
const tokens = [];
|
|
134292
|
+
let current = "";
|
|
134293
|
+
let quote;
|
|
134294
|
+
let escaped = false;
|
|
134295
|
+
for (const char of line2) {
|
|
134296
|
+
if (escaped) {
|
|
134297
|
+
current += char;
|
|
134298
|
+
escaped = false;
|
|
134299
|
+
continue;
|
|
134300
|
+
}
|
|
134301
|
+
if (char === "\\" && quote !== "'") {
|
|
134302
|
+
escaped = true;
|
|
134303
|
+
continue;
|
|
134304
|
+
}
|
|
134305
|
+
if (quote) {
|
|
134306
|
+
if (char === quote) {
|
|
134307
|
+
quote = void 0;
|
|
134308
|
+
} else {
|
|
134309
|
+
current += char;
|
|
134310
|
+
}
|
|
134311
|
+
continue;
|
|
134312
|
+
}
|
|
134313
|
+
if (char === '"' || char === "'") {
|
|
134314
|
+
quote = char;
|
|
134315
|
+
continue;
|
|
134316
|
+
}
|
|
134317
|
+
if (/\s/.test(char)) {
|
|
134318
|
+
if (current) {
|
|
134319
|
+
tokens.push(current);
|
|
134320
|
+
current = "";
|
|
134321
|
+
}
|
|
134322
|
+
continue;
|
|
134323
|
+
}
|
|
134324
|
+
current += char;
|
|
134325
|
+
}
|
|
134326
|
+
if (escaped) {
|
|
134327
|
+
current += "\\";
|
|
134328
|
+
}
|
|
134329
|
+
if (quote) {
|
|
134330
|
+
return { tokens, error: "Unterminated quoted argument" };
|
|
134331
|
+
}
|
|
134332
|
+
if (current) {
|
|
134333
|
+
tokens.push(current);
|
|
134334
|
+
}
|
|
134335
|
+
return { tokens };
|
|
134336
|
+
}
|
|
134337
|
+
function parseCommonOptions(tokens) {
|
|
134338
|
+
const remaining = [];
|
|
134339
|
+
let namespace;
|
|
134340
|
+
let context;
|
|
134341
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
134342
|
+
const token = tokens[index];
|
|
134343
|
+
if (token === "-n" || token === "--namespace") {
|
|
134344
|
+
const value = tokens[++index];
|
|
134345
|
+
if (!value) {
|
|
134346
|
+
return { remaining, error: `${token} requires a namespace` };
|
|
134347
|
+
}
|
|
134348
|
+
namespace = value;
|
|
134349
|
+
continue;
|
|
134350
|
+
}
|
|
134351
|
+
if (token.startsWith("--namespace=")) {
|
|
134352
|
+
namespace = token.slice("--namespace=".length);
|
|
134353
|
+
if (!namespace) {
|
|
134354
|
+
return { remaining, error: "--namespace requires a namespace" };
|
|
134355
|
+
}
|
|
134356
|
+
continue;
|
|
134357
|
+
}
|
|
134358
|
+
if (token === "--context") {
|
|
134359
|
+
const value = tokens[++index];
|
|
134360
|
+
if (!value) {
|
|
134361
|
+
return { remaining, error: "--context requires a context name" };
|
|
134362
|
+
}
|
|
134363
|
+
context = value;
|
|
134364
|
+
continue;
|
|
134365
|
+
}
|
|
134366
|
+
if (token.startsWith("--context=")) {
|
|
134367
|
+
context = token.slice("--context=".length);
|
|
134368
|
+
if (!context) {
|
|
134369
|
+
return { remaining, error: "--context requires a context name" };
|
|
134370
|
+
}
|
|
134371
|
+
continue;
|
|
134372
|
+
}
|
|
134373
|
+
remaining.push(token);
|
|
134374
|
+
}
|
|
134375
|
+
return { remaining, namespace, context };
|
|
134376
|
+
}
|
|
134377
|
+
function parseK8sCommand(rawLine, line2) {
|
|
134378
|
+
const raw = stripInlineComment(rawLine).trim();
|
|
134379
|
+
const captureMatch = raw.match(/^var\s+([a-zA-Z_][a-zA-Z0-9_-]*)\s*=\s*(.+)$/);
|
|
134380
|
+
if (captureMatch) {
|
|
134381
|
+
const [, captureVar, expression] = captureMatch;
|
|
134382
|
+
const parts = expression.split("|");
|
|
134383
|
+
if (parts.length !== 2) {
|
|
134384
|
+
return { error: { line: line2, message: "Expected: var <name> = get pods [-l selector] | first" } };
|
|
134385
|
+
}
|
|
134386
|
+
if (parts[1].trim() !== "first") {
|
|
134387
|
+
return { error: { line: line2, message: "Capture only supports '| first'" } };
|
|
134388
|
+
}
|
|
134389
|
+
const inner = parseK8sCommand(parts[0].trim(), line2);
|
|
134390
|
+
if (inner.error) {
|
|
134391
|
+
return { error: inner.error };
|
|
134392
|
+
}
|
|
134393
|
+
if (!inner.command || inner.command.kind !== "getPods") {
|
|
134394
|
+
return { error: { line: line2, message: "Capture must read from a get pods command" } };
|
|
134395
|
+
}
|
|
134396
|
+
if (inner.command.allNamespaces) {
|
|
134397
|
+
return { error: { line: line2, message: "Capture cannot use -A/--all-namespaces because the captured pod name would lose its namespace" } };
|
|
134398
|
+
}
|
|
134399
|
+
return { command: { ...inner.command, raw, captureVar, capturePick: "first" } };
|
|
134400
|
+
}
|
|
134401
|
+
const tokenized = tokenizeCommandLine(raw);
|
|
134402
|
+
if (tokenized.error) {
|
|
134403
|
+
return { error: { line: line2, message: tokenized.error } };
|
|
134404
|
+
}
|
|
134405
|
+
const tokens = tokenized.tokens;
|
|
134406
|
+
if (tokens.length === 0) {
|
|
134407
|
+
return {};
|
|
134408
|
+
}
|
|
134409
|
+
if (tokens[0] === "run") {
|
|
134410
|
+
if (tokens.length !== 2 || !/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(tokens[1])) {
|
|
134411
|
+
return { error: { line: line2, message: "Expected: run <SequenceName>" } };
|
|
134412
|
+
}
|
|
134413
|
+
return {
|
|
134414
|
+
command: {
|
|
134415
|
+
kind: "run",
|
|
134416
|
+
raw,
|
|
134417
|
+
line: line2,
|
|
134418
|
+
sequenceName: tokens[1]
|
|
134419
|
+
}
|
|
134420
|
+
};
|
|
134421
|
+
}
|
|
134422
|
+
if (tokens[0] === "get") {
|
|
134423
|
+
if (tokens[1] !== "pods") {
|
|
134424
|
+
return { error: { line: line2, message: "Expected: get pods [-l selector] [-A | -n namespace] [--context name]" } };
|
|
134425
|
+
}
|
|
134426
|
+
const commonTokens = [];
|
|
134427
|
+
let selector;
|
|
134428
|
+
let allNamespaces = false;
|
|
134429
|
+
const rest = tokens.slice(2);
|
|
134430
|
+
for (let index = 0; index < rest.length; index++) {
|
|
134431
|
+
const token = rest[index];
|
|
134432
|
+
if (token === "-l" || token === "--selector") {
|
|
134433
|
+
const value = rest[++index];
|
|
134434
|
+
if (!value) {
|
|
134435
|
+
return { error: { line: line2, message: `${token} requires a label selector` } };
|
|
134436
|
+
}
|
|
134437
|
+
selector = value;
|
|
134438
|
+
continue;
|
|
134439
|
+
}
|
|
134440
|
+
if (token.startsWith("--selector=") || token.startsWith("-l=")) {
|
|
134441
|
+
selector = token.slice(token.indexOf("=") + 1);
|
|
134442
|
+
if (!selector) {
|
|
134443
|
+
return { error: { line: line2, message: "Label selector flag requires a value" } };
|
|
134444
|
+
}
|
|
134445
|
+
continue;
|
|
134446
|
+
}
|
|
134447
|
+
if (token === "-A" || token === "--all-namespaces") {
|
|
134448
|
+
allNamespaces = true;
|
|
134449
|
+
continue;
|
|
134450
|
+
}
|
|
134451
|
+
commonTokens.push(token);
|
|
134452
|
+
}
|
|
134453
|
+
const options = parseCommonOptions(commonTokens);
|
|
134454
|
+
if (allNamespaces && options.namespace) {
|
|
134455
|
+
return { error: { line: line2, message: "get pods cannot combine -A/--all-namespaces with -n/--namespace" } };
|
|
134456
|
+
}
|
|
134457
|
+
if (options.error || options.remaining.length > 0) {
|
|
134458
|
+
return { error: { line: line2, message: options.error ?? "Expected: get pods [-l selector] [-A | -n namespace] [--context name]" } };
|
|
134459
|
+
}
|
|
134460
|
+
return {
|
|
134461
|
+
command: {
|
|
134462
|
+
kind: "getPods",
|
|
134463
|
+
raw,
|
|
134464
|
+
line: line2,
|
|
134465
|
+
selector,
|
|
134466
|
+
allNamespaces,
|
|
134467
|
+
namespace: options.namespace,
|
|
134468
|
+
context: options.context
|
|
134469
|
+
}
|
|
134470
|
+
};
|
|
134471
|
+
}
|
|
134472
|
+
if (tokens[0] === "describe") {
|
|
134473
|
+
if (tokens[1] !== "pod") {
|
|
134474
|
+
return { error: { line: line2, message: "Expected: describe pod <name> [-n namespace] [--context name]" } };
|
|
134475
|
+
}
|
|
134476
|
+
const pod = tokens[2];
|
|
134477
|
+
if (!pod) {
|
|
134478
|
+
return { error: { line: line2, message: "Expected: describe pod <name> [-n namespace] [--context name]" } };
|
|
134479
|
+
}
|
|
134480
|
+
const options = parseCommonOptions(tokens.slice(3));
|
|
134481
|
+
if (options.error || options.remaining.length > 0) {
|
|
134482
|
+
return { error: { line: line2, message: options.error ?? "Expected: describe pod <name> [-n namespace] [--context name]" } };
|
|
134483
|
+
}
|
|
134484
|
+
return {
|
|
134485
|
+
command: {
|
|
134486
|
+
kind: "describe",
|
|
134487
|
+
raw,
|
|
134488
|
+
line: line2,
|
|
134489
|
+
pod,
|
|
134490
|
+
namespace: options.namespace,
|
|
134491
|
+
context: options.context
|
|
134492
|
+
}
|
|
134493
|
+
};
|
|
134494
|
+
}
|
|
134495
|
+
if (tokens[0] === "logs") {
|
|
134496
|
+
const pod = tokens[1];
|
|
134497
|
+
if (!pod) {
|
|
134498
|
+
return { error: { line: line2, message: "Expected: logs <pod> [--tail N] [-n namespace] [--context name]" } };
|
|
134499
|
+
}
|
|
134500
|
+
const commonTokens = [];
|
|
134501
|
+
let tail;
|
|
134502
|
+
for (let index = 2; index < tokens.length; index++) {
|
|
134503
|
+
const token = tokens[index];
|
|
134504
|
+
if (token === "--tail") {
|
|
134505
|
+
const value = tokens[++index];
|
|
134506
|
+
if (!value || !/^\d+$/.test(value) && !/^\{\{[^}]+\}\}$/.test(value)) {
|
|
134507
|
+
return { error: { line: line2, message: "--tail requires a non-negative integer" } };
|
|
134508
|
+
}
|
|
134509
|
+
tail = /^\d+$/.test(value) ? Number(value) : value;
|
|
134510
|
+
continue;
|
|
134511
|
+
}
|
|
134512
|
+
if (token.startsWith("--tail=")) {
|
|
134513
|
+
const value = token.slice("--tail=".length);
|
|
134514
|
+
if (!/^\d+$/.test(value) && !/^\{\{[^}]+\}\}$/.test(value)) {
|
|
134515
|
+
return { error: { line: line2, message: "--tail requires a non-negative integer" } };
|
|
134516
|
+
}
|
|
134517
|
+
tail = /^\d+$/.test(value) ? Number(value) : value;
|
|
134518
|
+
continue;
|
|
134519
|
+
}
|
|
134520
|
+
commonTokens.push(token);
|
|
134521
|
+
}
|
|
134522
|
+
const options = parseCommonOptions(commonTokens);
|
|
134523
|
+
if (options.error || options.remaining.length > 0) {
|
|
134524
|
+
return { error: { line: line2, message: options.error ?? "Expected: logs <pod> [--tail N] [-n namespace] [--context name]" } };
|
|
134525
|
+
}
|
|
134526
|
+
return {
|
|
134527
|
+
command: {
|
|
134528
|
+
kind: "logs",
|
|
134529
|
+
raw,
|
|
134530
|
+
line: line2,
|
|
134531
|
+
pod,
|
|
134532
|
+
tail,
|
|
134533
|
+
namespace: options.namespace,
|
|
134534
|
+
context: options.context
|
|
134535
|
+
}
|
|
134536
|
+
};
|
|
134537
|
+
}
|
|
134538
|
+
if (tokens[0] === "restart") {
|
|
134539
|
+
const resource = tokens[1];
|
|
134540
|
+
const options = parseCommonOptions(tokens.slice(2));
|
|
134541
|
+
if (!resource?.startsWith("deployment/") || resource.length === "deployment/".length || options.error || options.remaining.length > 0) {
|
|
134542
|
+
return { error: { line: line2, message: options.error ?? "Expected: restart deployment/<name> [-n namespace] [--context name]" } };
|
|
134543
|
+
}
|
|
134544
|
+
return {
|
|
134545
|
+
command: {
|
|
134546
|
+
kind: "restart",
|
|
134547
|
+
raw,
|
|
134548
|
+
line: line2,
|
|
134549
|
+
resource,
|
|
134550
|
+
namespace: options.namespace,
|
|
134551
|
+
context: options.context
|
|
134552
|
+
}
|
|
134553
|
+
};
|
|
134554
|
+
}
|
|
134555
|
+
return { error: { line: line2, message: `Unsupported Kubernetes command: ${tokens[0]}` } };
|
|
134556
|
+
}
|
|
134557
|
+
function parseK8sDocument(text) {
|
|
134558
|
+
const lines = text.split(/\r?\n/);
|
|
134559
|
+
const document2 = {
|
|
134560
|
+
sequences: [],
|
|
134561
|
+
standaloneCommands: [],
|
|
134562
|
+
errors: []
|
|
134563
|
+
};
|
|
134564
|
+
let currentSequence;
|
|
134565
|
+
const sequenceNames = /* @__PURE__ */ new Set();
|
|
134566
|
+
for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {
|
|
134567
|
+
const raw = lines[lineNumber];
|
|
134568
|
+
const line2 = stripInlineComment(raw).trim();
|
|
134569
|
+
if (!line2) {
|
|
134570
|
+
continue;
|
|
134571
|
+
}
|
|
134572
|
+
const sequenceMatch = line2.match(/^(runbook\s+)?sequence\s+([a-zA-Z_][a-zA-Z0-9_-]*)\s*$/);
|
|
134573
|
+
if (sequenceMatch) {
|
|
134574
|
+
if (currentSequence) {
|
|
134575
|
+
document2.errors.push({ line: lineNumber, message: "Nested sequences are not supported" });
|
|
134576
|
+
continue;
|
|
134577
|
+
}
|
|
134578
|
+
const name = sequenceMatch[2];
|
|
134579
|
+
if (sequenceNames.has(name)) {
|
|
134580
|
+
document2.errors.push({ line: lineNumber, message: `Duplicate sequence '${name}'` });
|
|
134581
|
+
}
|
|
134582
|
+
sequenceNames.add(name);
|
|
134583
|
+
currentSequence = {
|
|
134584
|
+
name,
|
|
134585
|
+
isRunbook: Boolean(sequenceMatch[1]),
|
|
134586
|
+
startLine: lineNumber,
|
|
134587
|
+
endLine: lineNumber,
|
|
134588
|
+
commands: []
|
|
134589
|
+
};
|
|
134590
|
+
continue;
|
|
134591
|
+
}
|
|
134592
|
+
if (line2 === "end sequence") {
|
|
134593
|
+
if (!currentSequence) {
|
|
134594
|
+
document2.errors.push({ line: lineNumber, message: "Unexpected end sequence" });
|
|
134595
|
+
continue;
|
|
134596
|
+
}
|
|
134597
|
+
currentSequence.endLine = lineNumber;
|
|
134598
|
+
document2.sequences.push(currentSequence);
|
|
134599
|
+
currentSequence = void 0;
|
|
134600
|
+
continue;
|
|
134601
|
+
}
|
|
134602
|
+
if (line2.startsWith("namespace ")) {
|
|
134603
|
+
if (currentSequence) {
|
|
134604
|
+
document2.errors.push({ line: lineNumber, message: "namespace is a file-level directive and cannot appear inside a sequence" });
|
|
134605
|
+
continue;
|
|
134606
|
+
}
|
|
134607
|
+
const value = line2.slice("namespace ".length).trim();
|
|
134608
|
+
if (!value) {
|
|
134609
|
+
document2.errors.push({ line: lineNumber, message: "namespace requires a value" });
|
|
134610
|
+
} else if (document2.namespace) {
|
|
134611
|
+
document2.errors.push({ line: lineNumber, message: "Only one namespace directive is allowed per file" });
|
|
134612
|
+
} else {
|
|
134613
|
+
document2.namespace = { value, line: lineNumber };
|
|
134614
|
+
}
|
|
134615
|
+
continue;
|
|
134616
|
+
}
|
|
134617
|
+
const parsed = parseK8sCommand(raw, lineNumber);
|
|
134618
|
+
if (parsed.error) {
|
|
134619
|
+
document2.errors.push(parsed.error);
|
|
134620
|
+
continue;
|
|
134621
|
+
}
|
|
134622
|
+
if (!parsed.command) {
|
|
134623
|
+
continue;
|
|
134624
|
+
}
|
|
134625
|
+
if (currentSequence) {
|
|
134626
|
+
currentSequence.commands.push(parsed.command);
|
|
134627
|
+
} else if (parsed.command.kind === "run") {
|
|
134628
|
+
document2.errors.push({ line: lineNumber, message: "run <SequenceName> is only valid inside a sequence" });
|
|
134629
|
+
} else if (parsed.command.captureVar) {
|
|
134630
|
+
document2.errors.push({ line: lineNumber, message: "var capture is only valid inside a sequence" });
|
|
134631
|
+
} else {
|
|
134632
|
+
document2.standaloneCommands.push(parsed.command);
|
|
134633
|
+
}
|
|
134634
|
+
}
|
|
134635
|
+
if (currentSequence) {
|
|
134636
|
+
document2.errors.push({ line: currentSequence.startLine, message: `Sequence '${currentSequence.name}' is missing end sequence` });
|
|
134637
|
+
}
|
|
134638
|
+
return document2;
|
|
134639
|
+
}
|
|
134640
|
+
|
|
134641
|
+
// src/k8s/k8sRunner.ts
|
|
134642
|
+
function quoteDisplayArg(value) {
|
|
134643
|
+
return /^[a-zA-Z0-9_./:=@-]+$/.test(value) ? value : JSON.stringify(value);
|
|
134644
|
+
}
|
|
134645
|
+
function unresolvedVariables(text) {
|
|
134646
|
+
return Array.from(text.matchAll(/\{\{([^}]+)\}\}/g), (match) => match[1]);
|
|
134647
|
+
}
|
|
134648
|
+
function substituteOrThrow(text, variables, description) {
|
|
134649
|
+
const substituted = substituteVariables(text, variables);
|
|
134650
|
+
const unresolved = unresolvedVariables(substituted);
|
|
134651
|
+
if (unresolved.length > 0) {
|
|
134652
|
+
throw new Error(`Unresolved variable${unresolved.length === 1 ? "" : "s"} in ${description}: ${unresolved.join(", ")}`);
|
|
134653
|
+
}
|
|
134654
|
+
return substituted;
|
|
134655
|
+
}
|
|
134656
|
+
function resolveCommand(command, variables) {
|
|
134657
|
+
const substituted = substituteOrThrow(command.raw, variables, `line ${command.line + 1}`);
|
|
134658
|
+
const parsed = parseK8sCommand(substituted, command.line);
|
|
134659
|
+
if (parsed.error || !parsed.command) {
|
|
134660
|
+
throw new Error(parsed.error?.message ?? `Invalid Kubernetes command on line ${command.line + 1}`);
|
|
134661
|
+
}
|
|
134662
|
+
return parsed.command;
|
|
134663
|
+
}
|
|
134664
|
+
function buildKubectlInvocation(command, defaultNamespace, selectedContext) {
|
|
134665
|
+
const args = [];
|
|
134666
|
+
const context = command.context ?? selectedContext;
|
|
134667
|
+
const namespace = command.namespace ?? defaultNamespace;
|
|
134668
|
+
if (context) {
|
|
134669
|
+
args.push("--context", context);
|
|
134670
|
+
}
|
|
134671
|
+
if (command.allNamespaces) {
|
|
134672
|
+
args.push("-A");
|
|
134673
|
+
} else if (namespace) {
|
|
134674
|
+
args.push("-n", namespace);
|
|
134675
|
+
}
|
|
134676
|
+
switch (command.kind) {
|
|
134677
|
+
case "getPods":
|
|
134678
|
+
args.push("get", "pods");
|
|
134679
|
+
if (command.selector) {
|
|
134680
|
+
args.push("-l", command.selector);
|
|
134681
|
+
}
|
|
134682
|
+
break;
|
|
134683
|
+
case "logs":
|
|
134684
|
+
args.push("logs", command.pod, "--tail", String(command.tail ?? 200));
|
|
134685
|
+
break;
|
|
134686
|
+
case "restart":
|
|
134687
|
+
args.push("rollout", "restart", command.resource);
|
|
134688
|
+
break;
|
|
134689
|
+
case "describe":
|
|
134690
|
+
args.push("describe", "pod", command.pod);
|
|
134691
|
+
break;
|
|
134692
|
+
case "run":
|
|
134693
|
+
throw new Error("Sequence calls cannot be converted directly to kubectl commands");
|
|
134694
|
+
}
|
|
134695
|
+
return { args, command };
|
|
134696
|
+
}
|
|
134697
|
+
function executeKubectl(args, options) {
|
|
134698
|
+
const command = options.kubectlCommand ?? "kubectl";
|
|
134699
|
+
const startTime = Date.now();
|
|
134700
|
+
return new Promise((resolve16) => {
|
|
134701
|
+
const child = (0, import_child_process4.spawn)(command, args, {
|
|
134702
|
+
cwd: options.workingDirectory,
|
|
134703
|
+
env: options.env ?? process.env,
|
|
134704
|
+
shell: false
|
|
134705
|
+
});
|
|
134706
|
+
let stdout = "";
|
|
134707
|
+
let stderr = "";
|
|
134708
|
+
let settled = false;
|
|
134709
|
+
const finish = (result) => {
|
|
134710
|
+
if (settled) {
|
|
134711
|
+
return;
|
|
134712
|
+
}
|
|
134713
|
+
settled = true;
|
|
134714
|
+
resolve16(result);
|
|
134715
|
+
};
|
|
134716
|
+
child.stdout.on("data", (data) => {
|
|
134717
|
+
const text = data.toString();
|
|
134718
|
+
stdout += text;
|
|
134719
|
+
options.onOutput?.(text, "stdout");
|
|
134720
|
+
});
|
|
134721
|
+
child.stderr.on("data", (data) => {
|
|
134722
|
+
const text = data.toString();
|
|
134723
|
+
stderr += text;
|
|
134724
|
+
options.onOutput?.(text, "stderr");
|
|
134725
|
+
});
|
|
134726
|
+
child.on("error", (error2) => {
|
|
134727
|
+
const message = `Failed to start ${command}: ${error2.message}`;
|
|
134728
|
+
options.onOutput?.(`${message}
|
|
134729
|
+
`, "stderr");
|
|
134730
|
+
finish({
|
|
134731
|
+
success: false,
|
|
134732
|
+
command,
|
|
134733
|
+
args,
|
|
134734
|
+
stdout,
|
|
134735
|
+
stderr: stderr || message,
|
|
134736
|
+
exitCode: 1,
|
|
134737
|
+
duration: Date.now() - startTime
|
|
134738
|
+
});
|
|
134739
|
+
});
|
|
134740
|
+
child.on("close", (code) => {
|
|
134741
|
+
finish({
|
|
134742
|
+
success: code === 0,
|
|
134743
|
+
command,
|
|
134744
|
+
args,
|
|
134745
|
+
stdout,
|
|
134746
|
+
stderr,
|
|
134747
|
+
exitCode: code ?? 1,
|
|
134748
|
+
duration: Date.now() - startTime
|
|
134749
|
+
});
|
|
134750
|
+
});
|
|
134751
|
+
});
|
|
134752
|
+
}
|
|
134753
|
+
async function captureFirstPod(command, defaultNamespace, options) {
|
|
134754
|
+
const context = command.context ?? options.selectedContext;
|
|
134755
|
+
const namespace = command.namespace ?? defaultNamespace;
|
|
134756
|
+
const args = [];
|
|
134757
|
+
if (context) {
|
|
134758
|
+
args.push("--context", context);
|
|
134759
|
+
}
|
|
134760
|
+
if (command.allNamespaces) {
|
|
134761
|
+
args.push("-A");
|
|
134762
|
+
} else if (namespace) {
|
|
134763
|
+
args.push("-n", namespace);
|
|
134764
|
+
}
|
|
134765
|
+
args.push("get", "pods");
|
|
134766
|
+
if (command.selector) {
|
|
134767
|
+
args.push("-l", command.selector);
|
|
134768
|
+
}
|
|
134769
|
+
args.push("-o", "name");
|
|
134770
|
+
const binary = options.kubectlCommand ?? "kubectl";
|
|
134771
|
+
options.onCommand?.([binary, ...args].map(quoteDisplayArg).join(" "));
|
|
134772
|
+
const result = options.execute ? await options.execute(args) : await executeKubectl(args, options);
|
|
134773
|
+
if (!result.success) {
|
|
134774
|
+
throw new Error(result.stderr.trim() || `kubectl exited with code ${result.exitCode}`);
|
|
134775
|
+
}
|
|
134776
|
+
const names = result.stdout.split(/\r?\n/).map((name) => name.trim().replace(/^pod\//, "")).filter(Boolean);
|
|
134777
|
+
if (names.length === 0) {
|
|
134778
|
+
throw new Error(command.selector ? `No pods matched selector ${command.selector}` : "No pods found to capture");
|
|
134779
|
+
}
|
|
134780
|
+
return { result, podName: names[0] };
|
|
134781
|
+
}
|
|
134782
|
+
async function authorizeRestart(command, options) {
|
|
134783
|
+
if (options.allowRestart) {
|
|
134784
|
+
return true;
|
|
134785
|
+
}
|
|
134786
|
+
if (options.confirmRestart) {
|
|
134787
|
+
return options.confirmRestart(command);
|
|
134788
|
+
}
|
|
134789
|
+
return false;
|
|
134790
|
+
}
|
|
134791
|
+
async function executeCommand(sourceCommand, defaultNamespace, options) {
|
|
134792
|
+
const command = resolveCommand(sourceCommand, options.variables ?? {});
|
|
134793
|
+
const confirmationCommand = {
|
|
134794
|
+
...command,
|
|
134795
|
+
namespace: command.namespace ?? defaultNamespace,
|
|
134796
|
+
context: command.context ?? options.selectedContext
|
|
134797
|
+
};
|
|
134798
|
+
if (command.kind === "restart" && !await authorizeRestart(confirmationCommand, options)) {
|
|
134799
|
+
throw new Error(`Restart requires confirmation for ${command.resource}`);
|
|
134800
|
+
}
|
|
134801
|
+
const invocation = buildKubectlInvocation(command, defaultNamespace, options.selectedContext);
|
|
134802
|
+
const binary = options.kubectlCommand ?? "kubectl";
|
|
134803
|
+
options.onCommand?.([binary, ...invocation.args].map(quoteDisplayArg).join(" "));
|
|
134804
|
+
return options.execute ? options.execute(invocation.args) : executeKubectl(invocation.args, options);
|
|
134805
|
+
}
|
|
134806
|
+
function formatParseErrors(document2) {
|
|
134807
|
+
return document2.errors.map((error2) => `Line ${error2.line + 1}: ${error2.message}`);
|
|
134808
|
+
}
|
|
134809
|
+
async function runSequenceCommands(sequence, sequencesByName, defaultNamespace, options, callStack, results) {
|
|
134810
|
+
if (callStack.includes(sequence.name)) {
|
|
134811
|
+
return `Circular sequence call: ${[...callStack, sequence.name].join(" -> ")}`;
|
|
134812
|
+
}
|
|
134813
|
+
const nextStack = [...callStack, sequence.name];
|
|
134814
|
+
for (const command of sequence.commands) {
|
|
134815
|
+
if (command.kind === "run") {
|
|
134816
|
+
const target = sequencesByName.get(command.sequenceName);
|
|
134817
|
+
if (!target) {
|
|
134818
|
+
return `Line ${command.line + 1}: Sequence '${command.sequenceName}' not found`;
|
|
134819
|
+
}
|
|
134820
|
+
const nestedError = await runSequenceCommands(target, sequencesByName, defaultNamespace, options, nextStack, results);
|
|
134821
|
+
if (nestedError) {
|
|
134822
|
+
return nestedError;
|
|
134823
|
+
}
|
|
134824
|
+
continue;
|
|
134825
|
+
}
|
|
134826
|
+
if (command.captureVar) {
|
|
134827
|
+
try {
|
|
134828
|
+
const resolved = resolveCommand(command, options.variables ?? {});
|
|
134829
|
+
const { result, podName } = await captureFirstPod(resolved, defaultNamespace, options);
|
|
134830
|
+
results.push(result);
|
|
134831
|
+
(options.variables ??= {})[command.captureVar] = podName;
|
|
134832
|
+
options.onOutput?.(`# ${command.captureVar} = ${podName}
|
|
134833
|
+
`, "stdout");
|
|
134834
|
+
} catch (error2) {
|
|
134835
|
+
return `Line ${command.line + 1}: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
134836
|
+
}
|
|
134837
|
+
continue;
|
|
134838
|
+
}
|
|
134839
|
+
try {
|
|
134840
|
+
const result = await executeCommand(command, defaultNamespace, options);
|
|
134841
|
+
results.push(result);
|
|
134842
|
+
if (!result.success) {
|
|
134843
|
+
return `Line ${command.line + 1}: kubectl exited with code ${result.exitCode}${result.stderr.trim() ? `: ${result.stderr.trim()}` : ""}`;
|
|
134844
|
+
}
|
|
134845
|
+
} catch (error2) {
|
|
134846
|
+
return `Line ${command.line + 1}: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
134847
|
+
}
|
|
134848
|
+
}
|
|
134849
|
+
return void 0;
|
|
134850
|
+
}
|
|
134851
|
+
function resolveDefaultNamespace(document2, variables) {
|
|
134852
|
+
if (!document2.namespace) {
|
|
134853
|
+
return void 0;
|
|
134854
|
+
}
|
|
134855
|
+
return substituteOrThrow(document2.namespace.value, variables, `namespace directive on line ${document2.namespace.line + 1}`);
|
|
134856
|
+
}
|
|
134857
|
+
async function runK8sSequence(text, sequenceName, options = {}) {
|
|
134858
|
+
const startTime = Date.now();
|
|
134859
|
+
const document2 = parseK8sDocument(text);
|
|
134860
|
+
const errors = formatParseErrors(document2);
|
|
134861
|
+
const commands = [];
|
|
134862
|
+
const sequence = document2.sequences.find((candidate) => candidate.name === sequenceName);
|
|
134863
|
+
if (!sequence) {
|
|
134864
|
+
errors.push(`Sequence '${sequenceName}' not found`);
|
|
134865
|
+
}
|
|
134866
|
+
if (errors.length > 0 || !sequence) {
|
|
134867
|
+
return { name: sequenceName, success: false, commands, errors, duration: Date.now() - startTime };
|
|
134868
|
+
}
|
|
134869
|
+
try {
|
|
134870
|
+
const runOptions = {
|
|
134871
|
+
...options,
|
|
134872
|
+
variables: copyEnvironmentScope({ ...options.variables ?? {} }, options.variables ?? {})
|
|
134873
|
+
};
|
|
134874
|
+
const defaultNamespace = resolveDefaultNamespace(document2, runOptions.variables ?? {});
|
|
134875
|
+
const runtimeError = await runSequenceCommands(
|
|
134876
|
+
sequence,
|
|
134877
|
+
new Map(document2.sequences.map((candidate) => [candidate.name, candidate])),
|
|
134878
|
+
defaultNamespace,
|
|
134879
|
+
runOptions,
|
|
134880
|
+
[],
|
|
134881
|
+
commands
|
|
134882
|
+
);
|
|
134883
|
+
if (runtimeError) {
|
|
134884
|
+
errors.push(runtimeError);
|
|
134885
|
+
}
|
|
134886
|
+
} catch (error2) {
|
|
134887
|
+
errors.push(error2 instanceof Error ? error2.message : String(error2));
|
|
134888
|
+
}
|
|
134889
|
+
return {
|
|
134890
|
+
name: sequenceName,
|
|
134891
|
+
success: errors.length === 0,
|
|
134892
|
+
commands,
|
|
134893
|
+
errors,
|
|
134894
|
+
duration: Date.now() - startTime
|
|
134895
|
+
};
|
|
134896
|
+
}
|
|
134897
|
+
async function runK8sRunbooks(text, options = {}) {
|
|
134898
|
+
const document2 = parseK8sDocument(text);
|
|
134899
|
+
if (document2.errors.length > 0) {
|
|
134900
|
+
return [{
|
|
134901
|
+
name: "parse",
|
|
134902
|
+
success: false,
|
|
134903
|
+
commands: [],
|
|
134904
|
+
errors: formatParseErrors(document2),
|
|
134905
|
+
duration: 0
|
|
134906
|
+
}];
|
|
134907
|
+
}
|
|
134908
|
+
const results = [];
|
|
134909
|
+
for (const sequence of document2.sequences.filter((candidate) => candidate.isRunbook)) {
|
|
134910
|
+
results.push(await runK8sSequence(text, sequence.name, options));
|
|
134911
|
+
}
|
|
134912
|
+
return results;
|
|
134913
|
+
}
|
|
134914
|
+
|
|
134286
134915
|
// src/cli.ts
|
|
134287
134916
|
function handleImportResolutionErrors(errors, colors) {
|
|
134288
134917
|
const { blockingErrors, warningErrors } = splitImportResolutionErrors(errors);
|
|
@@ -134407,7 +135036,8 @@ function parseArgs(args) {
|
|
|
134407
135036
|
tagsFilter: [],
|
|
134408
135037
|
insecure: false,
|
|
134409
135038
|
refactorRegionPattern: false,
|
|
134410
|
-
writeRefactor: false
|
|
135039
|
+
writeRefactor: false,
|
|
135040
|
+
yes: false
|
|
134411
135041
|
};
|
|
134412
135042
|
for (let i = 0; i < args.length; i++) {
|
|
134413
135043
|
const arg = args[i];
|
|
@@ -134439,6 +135069,15 @@ function parseArgs(args) {
|
|
|
134439
135069
|
options.refactorRegionPattern = true;
|
|
134440
135070
|
} else if (arg === "--write") {
|
|
134441
135071
|
options.writeRefactor = true;
|
|
135072
|
+
} else if (arg === "--context") {
|
|
135073
|
+
const contextName = args[++i];
|
|
135074
|
+
if (!contextName || contextName.startsWith("-")) {
|
|
135075
|
+
console.error("Error: --context requires a Kubernetes context name.");
|
|
135076
|
+
process.exit(1);
|
|
135077
|
+
}
|
|
135078
|
+
options.k8sContext = contextName;
|
|
135079
|
+
} else if (arg === "--yes" || arg === "-y") {
|
|
135080
|
+
options.yes = true;
|
|
134442
135081
|
} else if (arg === "--no-fail") {
|
|
134443
135082
|
options.failOnError = false;
|
|
134444
135083
|
} else if (arg === "--no-redact") {
|
|
@@ -134471,18 +135110,20 @@ function parseArgs(args) {
|
|
|
134471
135110
|
}
|
|
134472
135111
|
function printHelp() {
|
|
134473
135112
|
console.log(`
|
|
134474
|
-
Norn CLI - Run HTTP requests
|
|
135113
|
+
Norn CLI - Run HTTP requests, test sequences, and Kubernetes runbooks
|
|
134475
135114
|
|
|
134476
|
-
Usage: norn <file.norn|directory> [options]
|
|
135115
|
+
Usage: norn <file.norn|file.nornk8s|directory> [options]
|
|
134477
135116
|
norn secrets <command> [options]
|
|
134478
135117
|
|
|
134479
|
-
When given a directory, recursively discovers
|
|
134480
|
-
|
|
135118
|
+
When given a directory, recursively discovers .norn test sequences and
|
|
135119
|
+
.nornk8s runbook sequences within that directory and subdirectories.
|
|
134481
135120
|
|
|
134482
135121
|
Options:
|
|
134483
135122
|
-s, --sequence <name> Run a specific sequence by name (single file only)
|
|
134484
135123
|
-r, --request <name> Run a specific named request (single file only)
|
|
134485
135124
|
-e, --env <name> Use environment from .nornenv (e.g., dev, prod)
|
|
135125
|
+
--context <name> Use a Kubernetes context for .nornk8s execution
|
|
135126
|
+
-y, --yes Allow restart commands in .nornk8s execution
|
|
134486
135127
|
-t, --timeout <time> Request timeout override (e.g. 180s, 3m, 300000ms; default: norn.config.json or 30s)
|
|
134487
135128
|
--insecure Disable TLS certificate verification (dev/self-signed only)
|
|
134488
135129
|
-j, --json Output results as JSON (for CI/CD)
|
|
@@ -134540,6 +135181,8 @@ Examples:
|
|
|
134540
135181
|
norn api-tests.norn --html report.html # Generate HTML report (explicit)
|
|
134541
135182
|
norn api-tests.norn --insecure # Allow self-signed/local TLS certs
|
|
134542
135183
|
norn api-tests.norn --no-redact # Show all data (no redaction)
|
|
135184
|
+
norn triage.nornk8s --context docker-desktop
|
|
135185
|
+
norn triage.nornk8s -s Triage --context prelive --yes
|
|
134543
135186
|
norn .nornenv --refactor-region-pattern # Print refactored .nornenv
|
|
134544
135187
|
norn .nornenv --refactor-region-pattern --write
|
|
134545
135188
|
norn secrets keygen --name team-main # Generate shared key and cache locally
|
|
@@ -134596,6 +135239,109 @@ function runNornenvRegionRefactor(filePath, options) {
|
|
|
134596
135239
|
}
|
|
134597
135240
|
process.exit(0);
|
|
134598
135241
|
}
|
|
135242
|
+
function redactK8sResult(result, redaction) {
|
|
135243
|
+
return {
|
|
135244
|
+
...result,
|
|
135245
|
+
errors: result.errors.map((error2) => redactString(error2, redaction)),
|
|
135246
|
+
commands: result.commands.map((command) => ({
|
|
135247
|
+
...command,
|
|
135248
|
+
args: command.args.map((arg) => redactString(arg, redaction)),
|
|
135249
|
+
stdout: redactString(command.stdout, redaction),
|
|
135250
|
+
stderr: redactString(command.stderr, redaction)
|
|
135251
|
+
}))
|
|
135252
|
+
};
|
|
135253
|
+
}
|
|
135254
|
+
function writeK8sProcessOutput(text, stream4) {
|
|
135255
|
+
if (!text) {
|
|
135256
|
+
return;
|
|
135257
|
+
}
|
|
135258
|
+
stream4.write(text);
|
|
135259
|
+
if (!text.endsWith("\n")) {
|
|
135260
|
+
stream4.write("\n");
|
|
135261
|
+
}
|
|
135262
|
+
}
|
|
135263
|
+
async function runK8sCliFile(filePath, options, colors) {
|
|
135264
|
+
if (options.request) {
|
|
135265
|
+
console.error("Error: --request is not supported for .nornk8s files");
|
|
135266
|
+
return false;
|
|
135267
|
+
}
|
|
135268
|
+
if (options.junitOutput || options.htmlOutput || options.outputDir) {
|
|
135269
|
+
console.error(colors.warning("Warning: reports are not supported for .nornk8s files in the MVP."));
|
|
135270
|
+
}
|
|
135271
|
+
const secretUnlockResult = await ensureCliSecretsUnlocked(filePath);
|
|
135272
|
+
if (!secretUnlockResult.ok) {
|
|
135273
|
+
if (secretUnlockResult.errors.length > 0) {
|
|
135274
|
+
printSecretResolutionErrors(secretUnlockResult.errors, secretUnlockResult.envFilePath);
|
|
135275
|
+
} else {
|
|
135276
|
+
console.error("Unable to unlock encrypted secrets.");
|
|
135277
|
+
}
|
|
135278
|
+
return false;
|
|
135279
|
+
}
|
|
135280
|
+
const resolvedEnv = resolveEnvironmentForPath(filePath, options.env);
|
|
135281
|
+
if (resolvedEnv.envNotFound) {
|
|
135282
|
+
console.error(`Error: Environment '${resolvedEnv.envNotFound}' not found in .nornenv`);
|
|
135283
|
+
console.error(`Available environments: ${resolvedEnv.availableEnvironments.join(", ") || "none"}`);
|
|
135284
|
+
return false;
|
|
135285
|
+
}
|
|
135286
|
+
if (resolvedEnv.secretErrors.length > 0) {
|
|
135287
|
+
for (const error2 of resolvedEnv.secretErrors) {
|
|
135288
|
+
console.error(`Error: ${error2}`);
|
|
135289
|
+
}
|
|
135290
|
+
return false;
|
|
135291
|
+
}
|
|
135292
|
+
if (!resolvedEnv.envFilePath && options.env) {
|
|
135293
|
+
console.error(colors.warning("Warning: --env specified but no .nornenv file found"));
|
|
135294
|
+
} else if (resolvedEnv.envFilePath && options.env && options.verbose) {
|
|
135295
|
+
console.log(colors.info(`Using environment: ${options.env}`));
|
|
135296
|
+
}
|
|
135297
|
+
const redaction = createRedactionOptions(resolvedEnv.secretNames, resolvedEnv.secretValues, !options.noRedact);
|
|
135298
|
+
const fileContent = fs19.readFileSync(filePath, "utf-8");
|
|
135299
|
+
const parsed = parseK8sDocument(fileContent);
|
|
135300
|
+
const variables = attachEnvironmentScope({ ...resolvedEnv.variables }, resolvedEnv.variables);
|
|
135301
|
+
const executionOptions = {
|
|
135302
|
+
variables,
|
|
135303
|
+
selectedContext: options.k8sContext,
|
|
135304
|
+
workingDirectory: path19.dirname(filePath),
|
|
135305
|
+
allowRestart: options.yes,
|
|
135306
|
+
onCommand: options.output === "pretty" ? (command) => console.log(colors.dim(`$ ${redactString(command, redaction)}`)) : void 0
|
|
135307
|
+
};
|
|
135308
|
+
let results;
|
|
135309
|
+
if (options.sequence) {
|
|
135310
|
+
results = [await runK8sSequence(fileContent, options.sequence, executionOptions)];
|
|
135311
|
+
} else {
|
|
135312
|
+
results = await runK8sRunbooks(fileContent, executionOptions);
|
|
135313
|
+
if (results.length === 0) {
|
|
135314
|
+
if (options.output === "json") {
|
|
135315
|
+
console.log(JSON.stringify({ success: true, results: [] }, null, 2));
|
|
135316
|
+
} else {
|
|
135317
|
+
const sequenceCount = parsed.sequences.length;
|
|
135318
|
+
console.log(colors.info(
|
|
135319
|
+
sequenceCount > 0 ? 'No Kubernetes runbooks found. Use "runbook sequence" to mark sequences for CLI execution.' : "No Kubernetes sequences found."
|
|
135320
|
+
));
|
|
135321
|
+
}
|
|
135322
|
+
return true;
|
|
135323
|
+
}
|
|
135324
|
+
}
|
|
135325
|
+
const redactedResults = results.map((result) => redactK8sResult(result, redaction));
|
|
135326
|
+
const success2 = results.every((result) => result.success);
|
|
135327
|
+
if (options.output === "json") {
|
|
135328
|
+
console.log(JSON.stringify({ success: success2, results: redactedResults }, null, 2));
|
|
135329
|
+
} else {
|
|
135330
|
+
for (const result of redactedResults) {
|
|
135331
|
+
for (const command of result.commands) {
|
|
135332
|
+
writeK8sProcessOutput(command.stdout, process.stdout);
|
|
135333
|
+
writeK8sProcessOutput(command.stderr, process.stderr);
|
|
135334
|
+
}
|
|
135335
|
+
const status = result.success ? colors.checkmark : colors.cross;
|
|
135336
|
+
console.log(`${status} ${result.name} (${formatDuration(result.duration)})`);
|
|
135337
|
+
for (const error2 of result.errors) {
|
|
135338
|
+
const restartHint = error2.includes("Restart requires confirmation") ? `${error2}. Re-run with --yes to allow mutations.` : error2;
|
|
135339
|
+
console.error(` ${colors.bullet} ${colors.error(restartHint)}`);
|
|
135340
|
+
}
|
|
135341
|
+
}
|
|
135342
|
+
}
|
|
135343
|
+
return success2;
|
|
135344
|
+
}
|
|
134599
135345
|
async function runSingleRequest(fileContent, variables, cookieJar, apiDefinitions, filePath, envContext) {
|
|
134600
135346
|
const lines = fileContent.split("\n");
|
|
134601
135347
|
const requestLines = [];
|
|
@@ -134643,6 +135389,24 @@ function discoverNornFiles(dirPath) {
|
|
|
134643
135389
|
walkDir(dirPath);
|
|
134644
135390
|
return files.sort();
|
|
134645
135391
|
}
|
|
135392
|
+
function discoverNornK8sFiles(dirPath) {
|
|
135393
|
+
const files = [];
|
|
135394
|
+
function walkDir(currentPath) {
|
|
135395
|
+
const entries = fs19.readdirSync(currentPath, { withFileTypes: true });
|
|
135396
|
+
for (const entry of entries) {
|
|
135397
|
+
const fullPath = path19.join(currentPath, entry.name);
|
|
135398
|
+
if (entry.isDirectory()) {
|
|
135399
|
+
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
135400
|
+
walkDir(fullPath);
|
|
135401
|
+
}
|
|
135402
|
+
} else if (entry.isFile() && entry.name.endsWith(".nornk8s")) {
|
|
135403
|
+
files.push(fullPath);
|
|
135404
|
+
}
|
|
135405
|
+
}
|
|
135406
|
+
}
|
|
135407
|
+
walkDir(dirPath);
|
|
135408
|
+
return files.sort();
|
|
135409
|
+
}
|
|
134646
135410
|
function countTestSequences(fileContent, tagFilterOptions) {
|
|
134647
135411
|
const allSequences = extractSequences(fileContent);
|
|
134648
135412
|
const testSequences = allSequences.filter((seq) => seq.isTest);
|
|
@@ -134771,15 +135535,20 @@ async function main() {
|
|
|
134771
135535
|
}
|
|
134772
135536
|
runNornenvRegionRefactor(inputPath, options);
|
|
134773
135537
|
}
|
|
135538
|
+
if (!isDirectory && path19.extname(inputPath).toLowerCase() === ".nornk8s") {
|
|
135539
|
+
const success2 = await runK8sCliFile(inputPath, options, colors);
|
|
135540
|
+
process.exit(success2 || !options.failOnError ? 0 : 1);
|
|
135541
|
+
}
|
|
135542
|
+
const k8sFilesToRun = isDirectory ? discoverNornK8sFiles(inputPath) : [];
|
|
134774
135543
|
let filesToRun;
|
|
134775
135544
|
if (isDirectory) {
|
|
134776
135545
|
filesToRun = discoverNornFiles(inputPath);
|
|
134777
|
-
if (filesToRun.length === 0) {
|
|
134778
|
-
console.log(colors.info(`No .norn files found in ${inputPath}`));
|
|
135546
|
+
if (filesToRun.length === 0 && k8sFilesToRun.length === 0) {
|
|
135547
|
+
console.log(colors.info(`No .norn or .nornk8s files found in ${inputPath}`));
|
|
134779
135548
|
process.exit(0);
|
|
134780
135549
|
}
|
|
134781
135550
|
if (options.verbose) {
|
|
134782
|
-
console.log(colors.info(`Discovered ${filesToRun.length} .norn file(s) in ${inputPath}`));
|
|
135551
|
+
console.log(colors.info(`Discovered ${filesToRun.length} .norn and ${k8sFilesToRun.length} .nornk8s file(s) in ${inputPath}`));
|
|
134783
135552
|
}
|
|
134784
135553
|
} else {
|
|
134785
135554
|
filesToRun = [inputPath];
|
|
@@ -134806,6 +135575,22 @@ async function main() {
|
|
|
134806
135575
|
console.error("Error: --sequence and --request flags require a specific file, not a directory");
|
|
134807
135576
|
process.exit(1);
|
|
134808
135577
|
}
|
|
135578
|
+
if (isDirectory && k8sFilesToRun.length > 0 && options.output === "json") {
|
|
135579
|
+
console.error("Error: --json is not supported for directory runs containing .nornk8s files in the MVP");
|
|
135580
|
+
process.exit(1);
|
|
135581
|
+
}
|
|
135582
|
+
for (const filePath of k8sFilesToRun) {
|
|
135583
|
+
if (options.output !== "json") {
|
|
135584
|
+
console.log(colors.info(`
|
|
135585
|
+
\u2501\u2501\u2501 ${path19.relative(inputPath, filePath)} \u2501\u2501\u2501`));
|
|
135586
|
+
}
|
|
135587
|
+
if (!await runK8sCliFile(filePath, options, colors)) {
|
|
135588
|
+
overallSuccess = false;
|
|
135589
|
+
}
|
|
135590
|
+
}
|
|
135591
|
+
if (filesToRun.length === 0) {
|
|
135592
|
+
process.exit(overallSuccess || !options.failOnError ? 0 : 1);
|
|
135593
|
+
}
|
|
134809
135594
|
if (options.sequence || options.request) {
|
|
134810
135595
|
const filePath = filesToRun[0];
|
|
134811
135596
|
applyCliRequestTimeout(filePath, options, colors);
|
|
@@ -135000,6 +135785,9 @@ ${fileContent}` : fileContent;
|
|
|
135000
135785
|
filteredTestCount += counts.filtered;
|
|
135001
135786
|
}
|
|
135002
135787
|
if (filteredTestCount === 0) {
|
|
135788
|
+
if (k8sFilesToRun.length > 0 && totalTestCount === 0) {
|
|
135789
|
+
process.exit(overallSuccess || !options.failOnError ? 0 : 1);
|
|
135790
|
+
}
|
|
135003
135791
|
if (totalTestCount > 0 && tagFilterOptions) {
|
|
135004
135792
|
console.log(colors.info(`No test sequences match the tag filter (${totalTestCount} total test sequences found)`));
|
|
135005
135793
|
} else if (totalTestCount === 0) {
|