@levr-one/cli 0.10.20 → 0.10.22
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/{addHandler-r0Yw1vu-.js → addHandler-DUulKXOX.js} +68 -68
- package/dist/appendHandler-u-NvHPDW.js +80 -0
- package/dist/artifact-upload-CF2iXUiR.js +296 -0
- package/dist/attachHandler-DgVCvhz7.js +151 -0
- package/dist/cli.js +237 -17
- package/dist/connect-DX41Im8c.js +36 -0
- package/dist/{currentHandler-BBHqC26D.js → currentHandler-B_sWXNeW.js} +1 -1
- package/dist/{importHandler-DAXXM42X.js → importHandler-CUUroX_n.js} +8 -8
- package/dist/{listHandler-qFOGgW2z.js → listHandler-CB_9cISi.js} +5 -5
- package/dist/{loginHandler-DMjKtKj5.js → loginHandler-DSznhtmk.js} +5 -10
- package/dist/{logoutHandler-BFutKash.js → logoutHandler-TSQlZGpU.js} +2 -2
- package/dist/{pushHandler-C7nxljuh.js → pushHandler-Q9Cbo2HC.js} +142 -9
- package/dist/{resolve-token-DbQsmn03.js → resolve-token-7xb7kG7h.js} +2 -2
- package/dist/{resolve-workspace--4K7N4VU.js → resolve-workspace-B3Gz3-Vh.js} +2 -2
- package/dist/{runHandler-gaYkPS79.js → runHandler-DmRAoyJc.js} +8 -8
- package/dist/{sdk-client-vQsoMCez.js → sdk-client-CTvYVr7D.js} +310 -70
- package/dist/{selectHandler-CPeH8ILF.js → selectHandler-CdOVuuTa.js} +5 -5
- package/dist/sleep-BpJ39Ypm.js +7 -0
- package/dist/{statusHandler-WkN652yu.js → statusHandler-p6CmwgjS.js} +3 -3
- package/dist/{token-refresh-Cu5RpkLJ.js → token-refresh-DeusLK8H.js} +1 -1
- package/package.json +1 -1
- /package/dist/{env-CHeKHu5S.js → env-CdwyPHGV.js} +0 -0
- /package/dist/{workspace-store-DDOxnut1.js → workspace-store-CnrxyYRB.js} +0 -0
package/dist/cli.js
CHANGED
|
@@ -175,7 +175,7 @@ Examples:
|
|
|
175
175
|
aliases: { y: "yes" }
|
|
176
176
|
},
|
|
177
177
|
loader: async () => {
|
|
178
|
-
const { mcpAddHandler } = await import("./addHandler-
|
|
178
|
+
const { mcpAddHandler } = await import("./addHandler-DUulKXOX.js");
|
|
179
179
|
return mcpAddHandler;
|
|
180
180
|
}
|
|
181
181
|
});
|
|
@@ -213,7 +213,7 @@ Examples:
|
|
|
213
213
|
aliases: { d: "device-code" }
|
|
214
214
|
},
|
|
215
215
|
loader: async () => {
|
|
216
|
-
const { loginHandler } = await import("./loginHandler-
|
|
216
|
+
const { loginHandler } = await import("./loginHandler-DSznhtmk.js");
|
|
217
217
|
return loginHandler;
|
|
218
218
|
}
|
|
219
219
|
});
|
|
@@ -232,7 +232,7 @@ Examples:
|
|
|
232
232
|
},
|
|
233
233
|
parameters: {},
|
|
234
234
|
loader: async () => {
|
|
235
|
-
const { logoutHandler } = await import("./logoutHandler-
|
|
235
|
+
const { logoutHandler } = await import("./logoutHandler-TSQlZGpU.js");
|
|
236
236
|
return logoutHandler;
|
|
237
237
|
}
|
|
238
238
|
});
|
|
@@ -252,7 +252,7 @@ Examples:
|
|
|
252
252
|
},
|
|
253
253
|
parameters: {},
|
|
254
254
|
loader: async () => {
|
|
255
|
-
const { statusHandler } = await import("./statusHandler-
|
|
255
|
+
const { statusHandler } = await import("./statusHandler-p6CmwgjS.js");
|
|
256
256
|
return statusHandler;
|
|
257
257
|
}
|
|
258
258
|
});
|
|
@@ -280,7 +280,18 @@ Examples:
|
|
|
280
280
|
levr push ./results.xml --source "backend-unit-tests"
|
|
281
281
|
levr push ./results.xml --automation-source <uuid>
|
|
282
282
|
levr push ./report.json --source e2e --team-id <uuid> # explicit team
|
|
283
|
-
levr push ./test-results.xml # in CI: source auto-detected
|
|
283
|
+
levr push ./test-results.xml # in CI: source auto-detected
|
|
284
|
+
levr push e2e-report.xml -s e2e --artifacts e2e-reports --output-results results.json
|
|
285
|
+
|
|
286
|
+
--artifacts <dir> uploads the screenshots, traces and videos the report
|
|
287
|
+
references (JUnit [[ATTACHMENT|path]], CTRF attachments[]) to their results.
|
|
288
|
+
Paths resolve against <dir> (then the report's directory) and must stay
|
|
289
|
+
inside the working directory. Files over 50 MiB are skipped. An artifact
|
|
290
|
+
problem prints a summary and never changes the push exit code.
|
|
291
|
+
|
|
292
|
+
--output-results <file> writes every imported result (id, name, suite,
|
|
293
|
+
classname, status, test_key, attempts, attachments) as JSON, for
|
|
294
|
+
levr attach --manifest or later uploads.`
|
|
284
295
|
},
|
|
285
296
|
parameters: {
|
|
286
297
|
positional: {
|
|
@@ -339,6 +350,20 @@ Examples:
|
|
|
339
350
|
brief: "File format (auto-detected if omitted)",
|
|
340
351
|
optional: true
|
|
341
352
|
},
|
|
353
|
+
artifacts: {
|
|
354
|
+
kind: "parsed",
|
|
355
|
+
parse: String,
|
|
356
|
+
brief: "Upload the artifacts the report references, resolved against this directory",
|
|
357
|
+
placeholder: "dir",
|
|
358
|
+
optional: true
|
|
359
|
+
},
|
|
360
|
+
"output-results": {
|
|
361
|
+
kind: "parsed",
|
|
362
|
+
parse: String,
|
|
363
|
+
brief: "Write the imported results (ids, test_key, attachments) to a JSON file",
|
|
364
|
+
placeholder: "file",
|
|
365
|
+
optional: true
|
|
366
|
+
},
|
|
342
367
|
verbose: {
|
|
343
368
|
kind: "boolean",
|
|
344
369
|
default: false,
|
|
@@ -356,7 +381,7 @@ Examples:
|
|
|
356
381
|
}
|
|
357
382
|
},
|
|
358
383
|
loader: async () => {
|
|
359
|
-
const { pushHandler } = await import("./pushHandler-
|
|
384
|
+
const { pushHandler } = await import("./pushHandler-Q9Cbo2HC.js");
|
|
360
385
|
return pushHandler;
|
|
361
386
|
}
|
|
362
387
|
});
|
|
@@ -476,7 +501,7 @@ Examples:
|
|
|
476
501
|
}
|
|
477
502
|
},
|
|
478
503
|
loader: async () => {
|
|
479
|
-
const { importHandler } = await import("./importHandler-
|
|
504
|
+
const { importHandler } = await import("./importHandler-CUUroX_n.js");
|
|
480
505
|
return importHandler;
|
|
481
506
|
}
|
|
482
507
|
});
|
|
@@ -497,7 +522,7 @@ Examples:
|
|
|
497
522
|
},
|
|
498
523
|
parameters: {},
|
|
499
524
|
loader: async () => {
|
|
500
|
-
const { listHandler } = await import("./listHandler-
|
|
525
|
+
const { listHandler } = await import("./listHandler-CB_9cISi.js");
|
|
501
526
|
return listHandler;
|
|
502
527
|
}
|
|
503
528
|
});
|
|
@@ -530,7 +555,7 @@ Examples:
|
|
|
530
555
|
flags: {}
|
|
531
556
|
},
|
|
532
557
|
loader: async () => {
|
|
533
|
-
const { selectHandler } = await import("./selectHandler-
|
|
558
|
+
const { selectHandler } = await import("./selectHandler-CdOVuuTa.js");
|
|
534
559
|
return selectHandler;
|
|
535
560
|
}
|
|
536
561
|
});
|
|
@@ -547,7 +572,7 @@ Examples:
|
|
|
547
572
|
},
|
|
548
573
|
parameters: {},
|
|
549
574
|
loader: async () => {
|
|
550
|
-
const { currentHandler } = await import("./currentHandler-
|
|
575
|
+
const { currentHandler } = await import("./currentHandler-B_sWXNeW.js");
|
|
551
576
|
return currentHandler;
|
|
552
577
|
}
|
|
553
578
|
});
|
|
@@ -616,14 +641,203 @@ Examples:
|
|
|
616
641
|
}
|
|
617
642
|
},
|
|
618
643
|
loader: async () => {
|
|
619
|
-
const { gatesRunHandler } = await import("./runHandler-
|
|
644
|
+
const { gatesRunHandler } = await import("./runHandler-DmRAoyJc.js");
|
|
620
645
|
return gatesRunHandler;
|
|
621
646
|
}
|
|
622
647
|
});
|
|
623
648
|
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region src/commands/attach.ts
|
|
651
|
+
const ATTACHMENT_KINDS = [
|
|
652
|
+
"screenshot",
|
|
653
|
+
"video",
|
|
654
|
+
"trace",
|
|
655
|
+
"stdout_log",
|
|
656
|
+
"stderr_log",
|
|
657
|
+
"har",
|
|
658
|
+
"report",
|
|
659
|
+
"attachment",
|
|
660
|
+
"other"
|
|
661
|
+
];
|
|
662
|
+
const attachCommand = buildCommand({
|
|
663
|
+
docs: {
|
|
664
|
+
brief: "Attach local files to a Levr entity",
|
|
665
|
+
fullDescription: `Upload local files (screenshots, trace.zip, videos, logs) and attach them to
|
|
666
|
+
an issue, test, run or execution result, without passing base64 through an
|
|
667
|
+
MCP call.
|
|
668
|
+
|
|
669
|
+
Targets:
|
|
670
|
+
ENG-42 an issue, by identifier
|
|
671
|
+
TC-5 a test case
|
|
672
|
+
TR-3 a run
|
|
673
|
+
<related_type>:<uuid> any entity with attachments, e.g.
|
|
674
|
+
run_result_variant:<uuid>
|
|
675
|
+
automation_run_result:<uuid>
|
|
676
|
+
<related_type>:<identifier> an explicit type, e.g. issue:TC-12 for a
|
|
677
|
+
team whose key is TC or TR
|
|
678
|
+
Spoke identifiers (ENG-JP-5) work in every form.
|
|
679
|
+
|
|
680
|
+
Files are private: only signed-in workspace members can open them. --embed
|
|
681
|
+
also adds a link to the entity so they render there: a new comment on an
|
|
682
|
+
issue, test or run, or an append to an execution result's actual result.
|
|
683
|
+
Files over 50 MiB are skipped.
|
|
684
|
+
|
|
685
|
+
--manifest <json> attaches a batch: [{ "target": "...", "files": ["..."],
|
|
686
|
+
"kind": "trace", "attempt": 1 }]. --kind and --attempt apply to
|
|
687
|
+
automation_run_result targets only.
|
|
688
|
+
|
|
689
|
+
Exit code 1 when any file fails, is skipped, or an embed write fails.
|
|
690
|
+
|
|
691
|
+
Examples:
|
|
692
|
+
levr attach ENG-42 screenshot.png
|
|
693
|
+
levr attach run_result_variant:<uuid> shot.png --embed
|
|
694
|
+
levr attach automation_run_result:<uuid> trace.zip --kind trace --attempt 0
|
|
695
|
+
levr attach --manifest extra-artifacts.json`
|
|
696
|
+
},
|
|
697
|
+
parameters: {
|
|
698
|
+
positional: {
|
|
699
|
+
kind: "array",
|
|
700
|
+
parameter: {
|
|
701
|
+
parse: String,
|
|
702
|
+
brief: "Target, then the files to attach",
|
|
703
|
+
placeholder: "target|file"
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
flags: {
|
|
707
|
+
"workspace-id": {
|
|
708
|
+
kind: "parsed",
|
|
709
|
+
parse: String,
|
|
710
|
+
brief: "Workspace ID (required for multi-workspace JWT auth)",
|
|
711
|
+
placeholder: "uuid",
|
|
712
|
+
optional: true
|
|
713
|
+
},
|
|
714
|
+
kind: {
|
|
715
|
+
kind: "enum",
|
|
716
|
+
values: ATTACHMENT_KINDS,
|
|
717
|
+
brief: "Artifact kind (automation_run_result targets only)",
|
|
718
|
+
optional: true
|
|
719
|
+
},
|
|
720
|
+
attempt: {
|
|
721
|
+
kind: "parsed",
|
|
722
|
+
parse: (v) => {
|
|
723
|
+
if (!/^\d+$/.test(v)) throw new Error("--attempt must be >= 0");
|
|
724
|
+
return Number(v);
|
|
725
|
+
},
|
|
726
|
+
brief: "Index of the earlier attempt (automation_run_result only; omit for the final attempt)",
|
|
727
|
+
placeholder: "n",
|
|
728
|
+
optional: true
|
|
729
|
+
},
|
|
730
|
+
embed: {
|
|
731
|
+
kind: "boolean",
|
|
732
|
+
default: false,
|
|
733
|
+
brief: "Also add a markdown link (comment, or actual-result append)"
|
|
734
|
+
},
|
|
735
|
+
manifest: {
|
|
736
|
+
kind: "parsed",
|
|
737
|
+
parse: String,
|
|
738
|
+
brief: "JSON file listing [{ target, files, kind?, attempt? }]",
|
|
739
|
+
placeholder: "file",
|
|
740
|
+
optional: true
|
|
741
|
+
},
|
|
742
|
+
verbose: {
|
|
743
|
+
kind: "boolean",
|
|
744
|
+
default: false,
|
|
745
|
+
brief: "Show detailed output"
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
aliases: {
|
|
749
|
+
w: "workspace-id",
|
|
750
|
+
k: "kind",
|
|
751
|
+
e: "embed",
|
|
752
|
+
m: "manifest",
|
|
753
|
+
v: "verbose"
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
loader: async () => {
|
|
757
|
+
const { attachHandler } = await import("./attachHandler-DgVCvhz7.js");
|
|
758
|
+
return attachHandler;
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
//#endregion
|
|
763
|
+
//#region src/commands/result/append.ts
|
|
764
|
+
const resultAppendCommand = buildCommand({
|
|
765
|
+
docs: {
|
|
766
|
+
brief: "Append to an execution result's actual result",
|
|
767
|
+
fullDescription: `Append text to an execution result's actual result, server-side (no
|
|
768
|
+
read-modify-write), optionally with local files attached first and embedded
|
|
769
|
+
as markdown links. Attached files are private: the links open for signed-in
|
|
770
|
+
workspace members.
|
|
771
|
+
|
|
772
|
+
The target is run_result_variant:<uuid> — the execution result id the levr
|
|
773
|
+
MCP tools and levr-browser return.
|
|
774
|
+
|
|
775
|
+
Examples:
|
|
776
|
+
levr result append run_result_variant:<uuid> --text "Checkout returns 500"
|
|
777
|
+
levr result append run_result_variant:<uuid> --file observed.md --attach shot.png trace.zip`
|
|
778
|
+
},
|
|
779
|
+
parameters: {
|
|
780
|
+
positional: {
|
|
781
|
+
kind: "tuple",
|
|
782
|
+
parameters: [{
|
|
783
|
+
parse: String,
|
|
784
|
+
brief: "run_result_variant:<uuid>",
|
|
785
|
+
placeholder: "target"
|
|
786
|
+
}]
|
|
787
|
+
},
|
|
788
|
+
flags: {
|
|
789
|
+
"workspace-id": {
|
|
790
|
+
kind: "parsed",
|
|
791
|
+
parse: String,
|
|
792
|
+
brief: "Workspace ID (required for multi-workspace JWT auth)",
|
|
793
|
+
placeholder: "uuid",
|
|
794
|
+
optional: true
|
|
795
|
+
},
|
|
796
|
+
text: {
|
|
797
|
+
kind: "parsed",
|
|
798
|
+
parse: String,
|
|
799
|
+
brief: "Text to append",
|
|
800
|
+
placeholder: "text",
|
|
801
|
+
optional: true
|
|
802
|
+
},
|
|
803
|
+
file: {
|
|
804
|
+
kind: "parsed",
|
|
805
|
+
parse: String,
|
|
806
|
+
brief: "Read the text to append from a file",
|
|
807
|
+
placeholder: "path",
|
|
808
|
+
optional: true
|
|
809
|
+
},
|
|
810
|
+
attach: {
|
|
811
|
+
kind: "parsed",
|
|
812
|
+
parse: String,
|
|
813
|
+
brief: "Files to attach and embed (repeatable)",
|
|
814
|
+
placeholder: "file",
|
|
815
|
+
variadic: true,
|
|
816
|
+
optional: true
|
|
817
|
+
},
|
|
818
|
+
verbose: {
|
|
819
|
+
kind: "boolean",
|
|
820
|
+
default: false,
|
|
821
|
+
brief: "Show detailed output"
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
aliases: {
|
|
825
|
+
w: "workspace-id",
|
|
826
|
+
t: "text",
|
|
827
|
+
f: "file",
|
|
828
|
+
a: "attach",
|
|
829
|
+
v: "verbose"
|
|
830
|
+
}
|
|
831
|
+
},
|
|
832
|
+
loader: async () => {
|
|
833
|
+
const { resultAppendHandler } = await import("./appendHandler-u-NvHPDW.js");
|
|
834
|
+
return resultAppendHandler;
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
|
|
624
838
|
//#endregion
|
|
625
839
|
//#region package.json
|
|
626
|
-
var version = "0.10.
|
|
840
|
+
var version = "0.10.22";
|
|
627
841
|
|
|
628
842
|
//#endregion
|
|
629
843
|
//#region src/app.ts
|
|
@@ -647,6 +861,10 @@ const gatesRoutes = buildRouteMap({
|
|
|
647
861
|
routes: { run: gatesRunCommand },
|
|
648
862
|
docs: { brief: "Run a deliverable's gates and report the raw results" }
|
|
649
863
|
});
|
|
864
|
+
const resultRoutes = buildRouteMap({
|
|
865
|
+
routes: { append: resultAppendCommand },
|
|
866
|
+
docs: { brief: "Work with execution results" }
|
|
867
|
+
});
|
|
650
868
|
const routes = buildRouteMap({
|
|
651
869
|
routes: {
|
|
652
870
|
mcp: buildRouteMap({
|
|
@@ -658,6 +876,8 @@ const routes = buildRouteMap({
|
|
|
658
876
|
gates: gatesRoutes,
|
|
659
877
|
push: pushCommand,
|
|
660
878
|
import: importCommand,
|
|
879
|
+
attach: attachCommand,
|
|
880
|
+
result: resultRoutes,
|
|
661
881
|
completion: completionCommand
|
|
662
882
|
},
|
|
663
883
|
docs: { brief: "The command-line interface for Levr" }
|
|
@@ -738,10 +958,10 @@ function envFilePath(cwd, env) {
|
|
|
738
958
|
* getting silence is the one case where quiet failure hides a real mistake.
|
|
739
959
|
*/
|
|
740
960
|
function loadEnvFile(cwd = process.cwd(), env = process.env) {
|
|
741
|
-
const path = envFilePath(cwd, env);
|
|
961
|
+
const path$1 = envFilePath(cwd, env);
|
|
742
962
|
const explicitlyRequested = Boolean(env["LEVR_ENV_FILE"]);
|
|
743
|
-
if (!existsSync(path)) {
|
|
744
|
-
if (explicitlyRequested) throw new Error(`LEVR_ENV_FILE points at "${path}", which does not exist.`);
|
|
963
|
+
if (!existsSync(path$1)) {
|
|
964
|
+
if (explicitlyRequested) throw new Error(`LEVR_ENV_FILE points at "${path$1}", which does not exist.`);
|
|
745
965
|
return {
|
|
746
966
|
path: null,
|
|
747
967
|
applied: []
|
|
@@ -749,7 +969,7 @@ function loadEnvFile(cwd = process.cwd(), env = process.env) {
|
|
|
749
969
|
}
|
|
750
970
|
let parsed;
|
|
751
971
|
try {
|
|
752
|
-
parsed = parse(readFileSync(path, "utf8"));
|
|
972
|
+
parsed = parse(readFileSync(path$1, "utf8"));
|
|
753
973
|
} catch (error) {
|
|
754
974
|
if (explicitlyRequested) throw error;
|
|
755
975
|
return {
|
|
@@ -765,7 +985,7 @@ function loadEnvFile(cwd = process.cwd(), env = process.env) {
|
|
|
765
985
|
applied.push(key);
|
|
766
986
|
}
|
|
767
987
|
return {
|
|
768
|
-
path,
|
|
988
|
+
path: path$1,
|
|
769
989
|
applied
|
|
770
990
|
};
|
|
771
991
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { client, configureClient } from "./sdk-client-CTvYVr7D.js";
|
|
2
|
+
import { resolveToken } from "./resolve-token-7xb7kG7h.js";
|
|
3
|
+
import { resolveWorkspace } from "./resolve-workspace-B3Gz3-Vh.js";
|
|
4
|
+
|
|
5
|
+
//#region src/utils/connect.ts
|
|
6
|
+
/**
|
|
7
|
+
* Authenticate the SDK client for a command (ENG-6164): resolve the token,
|
|
8
|
+
* and for a JWT the workspace. Reports the failure and sets exit code 1;
|
|
9
|
+
* returns false when the command must stop.
|
|
10
|
+
*/
|
|
11
|
+
async function connect(ctx, workspaceIdFlag) {
|
|
12
|
+
let auth;
|
|
13
|
+
try {
|
|
14
|
+
auth = await resolveToken();
|
|
15
|
+
} catch (err) {
|
|
16
|
+
ctx.logger.error(err instanceof Error ? err.message : "Authentication failed.");
|
|
17
|
+
ctx.process.exitCode = 1;
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
configureClient(auth);
|
|
21
|
+
if (auth.type === "jwt") try {
|
|
22
|
+
const ws = await resolveWorkspace(workspaceIdFlag);
|
|
23
|
+
client.setConfig({
|
|
24
|
+
...client.getConfig(),
|
|
25
|
+
workspaceId: ws.workspaceId
|
|
26
|
+
});
|
|
27
|
+
} catch (err) {
|
|
28
|
+
ctx.logger.error(err instanceof Error ? err.message : "Workspace resolution failed.");
|
|
29
|
+
ctx.process.exitCode = 1;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { connect };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import "./
|
|
4
|
-
import {
|
|
5
|
-
import "./
|
|
6
|
-
import {
|
|
1
|
+
import { authGetProfileV1, configureClient, teamFindAllV1, testCaseImportCommitV1, testCaseImportPreviewV1 } from "./sdk-client-CTvYVr7D.js";
|
|
2
|
+
import { getApiUrl, getTeamId } from "./env-CdwyPHGV.js";
|
|
3
|
+
import "./token-refresh-DeusLK8H.js";
|
|
4
|
+
import { resolveToken } from "./resolve-token-7xb7kG7h.js";
|
|
5
|
+
import "./workspace-store-CnrxyYRB.js";
|
|
6
|
+
import { resolveWorkspace } from "./resolve-workspace-B3Gz3-Vh.js";
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import { createReadStream } from "node:fs";
|
|
9
9
|
import { basename } from "node:path";
|
|
@@ -269,9 +269,9 @@ const MATCH_COLORS = {
|
|
|
269
269
|
manual: chalk.green
|
|
270
270
|
};
|
|
271
271
|
/** Stream the file into a Blob — no readFileSync, no sync stall. */
|
|
272
|
-
async function fileToBlob(path) {
|
|
272
|
+
async function fileToBlob(path$1) {
|
|
273
273
|
const chunks = [];
|
|
274
|
-
for await (const chunk of createReadStream(path)) chunks.push(chunk);
|
|
274
|
+
for await (const chunk of createReadStream(path$1)) chunks.push(chunk);
|
|
275
275
|
return new Blob(chunks);
|
|
276
276
|
}
|
|
277
277
|
function printMapping(logger, mapping) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import "./token-
|
|
5
|
-
import {
|
|
1
|
+
import { authGetSitesV1, configureClient } from "./sdk-client-CTvYVr7D.js";
|
|
2
|
+
import "./env-CdwyPHGV.js";
|
|
3
|
+
import "./token-refresh-DeusLK8H.js";
|
|
4
|
+
import { resolveToken } from "./resolve-token-7xb7kG7h.js";
|
|
5
|
+
import { loadWorkspace } from "./workspace-store-CnrxyYRB.js";
|
|
6
6
|
|
|
7
7
|
//#region src/commands/workspace/listHandler.ts
|
|
8
8
|
async function listHandler() {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import "./workspace-store-
|
|
4
|
-
import { autoSelectWorkspace } from "./resolve-workspace
|
|
1
|
+
import { configureClient } from "./sdk-client-CTvYVr7D.js";
|
|
2
|
+
import { CLI_CLIENT_ID, getApiUrl, getAuthUrl, setSessionApiUrl, writeCredentials } from "./env-CdwyPHGV.js";
|
|
3
|
+
import "./workspace-store-CnrxyYRB.js";
|
|
4
|
+
import { autoSelectWorkspace } from "./resolve-workspace-B3Gz3-Vh.js";
|
|
5
|
+
import { sleep } from "./sleep-BpJ39Ypm.js";
|
|
5
6
|
import chalk from "chalk";
|
|
6
7
|
import { createHash, randomBytes } from "node:crypto";
|
|
7
8
|
import { createServer } from "node:http";
|
|
@@ -153,12 +154,6 @@ function startCallbackServer(options = {}) {
|
|
|
153
154
|
};
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region src/utils/sleep.ts
|
|
158
|
-
function sleep(ms) {
|
|
159
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
157
|
//#endregion
|
|
163
158
|
//#region src/auth/device-flow.ts
|
|
164
159
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { deleteCredentials, getPatToken } from "./env-
|
|
2
|
-
import { clearWorkspace } from "./workspace-store-
|
|
1
|
+
import { deleteCredentials, getPatToken } from "./env-CdwyPHGV.js";
|
|
2
|
+
import { clearWorkspace } from "./workspace-store-CnrxyYRB.js";
|
|
3
3
|
|
|
4
4
|
//#region src/commands/auth/logoutHandler.ts
|
|
5
5
|
function logoutHandler() {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import "./
|
|
4
|
-
import {
|
|
5
|
-
import "./
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
1
|
+
import { client, configureClient, uploadImport } from "./sdk-client-CTvYVr7D.js";
|
|
2
|
+
import { getApiUrl, getAutomationSourceIdOverride, getSourceOverride, getTeamId } from "./env-CdwyPHGV.js";
|
|
3
|
+
import "./token-refresh-DeusLK8H.js";
|
|
4
|
+
import { resolveToken } from "./resolve-token-7xb7kG7h.js";
|
|
5
|
+
import "./workspace-store-CnrxyYRB.js";
|
|
6
|
+
import { resolveWorkspace } from "./resolve-workspace-B3Gz3-Vh.js";
|
|
7
|
+
import "./sleep-BpJ39Ypm.js";
|
|
8
|
+
import { resolveArtifactPath, uploadArtifactFile } from "./artifact-upload-CF2iXUiR.js";
|
|
9
|
+
import { readFileSync, statSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { basename, dirname, resolve } from "node:path";
|
|
9
11
|
import { execSync } from "node:child_process";
|
|
10
12
|
import ora from "ora";
|
|
11
13
|
|
|
@@ -356,6 +358,98 @@ function getCiMetadata() {
|
|
|
356
358
|
return detectCiEnvironment() ?? void 0;
|
|
357
359
|
}
|
|
358
360
|
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region src/utils/push-artifacts.ts
|
|
363
|
+
const ARTIFACT_UPLOAD_CONCURRENCY = 4;
|
|
364
|
+
function formatArtifactSummary(s) {
|
|
365
|
+
return `Artifacts: uploaded ${s.uploaded}, deduplicated ${s.deduplicated}, skipped ${s.skipped} (too large), missing ${s.missing}, rejected ${s.rejected} (outside the working directory), failed ${s.failed}`;
|
|
366
|
+
}
|
|
367
|
+
async function uploadReportArtifacts(options) {
|
|
368
|
+
const summary = {
|
|
369
|
+
uploaded: 0,
|
|
370
|
+
deduplicated: 0,
|
|
371
|
+
skipped: 0,
|
|
372
|
+
missing: 0,
|
|
373
|
+
rejected: 0,
|
|
374
|
+
failed: 0
|
|
375
|
+
};
|
|
376
|
+
const tasks = [];
|
|
377
|
+
for (const result of options.results) for (const att of result.attachments) {
|
|
378
|
+
if (att.stored || !att.path) continue;
|
|
379
|
+
const ref = att.path;
|
|
380
|
+
const upload = async () => {
|
|
381
|
+
let resolved = resolveArtifactPath(ref, options.artifactsDir, options.root);
|
|
382
|
+
if (resolved && resolved.status === "missing") resolved = resolveArtifactPath(ref, options.reportDir, options.root) ?? resolved;
|
|
383
|
+
if (resolved === null) {
|
|
384
|
+
summary.rejected += 1;
|
|
385
|
+
options.onProblem?.(ref, "rejected");
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (resolved.status === "missing") {
|
|
389
|
+
summary.missing += 1;
|
|
390
|
+
options.onProblem?.(ref, { status: "missing" });
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const outcome = await uploadArtifactFile({
|
|
394
|
+
target: {
|
|
395
|
+
relatedType: "automation_run_result",
|
|
396
|
+
relatedId: result.id
|
|
397
|
+
},
|
|
398
|
+
filePath: resolved.path,
|
|
399
|
+
validated: resolved,
|
|
400
|
+
name: att.name ?? void 0,
|
|
401
|
+
kind: att.kind,
|
|
402
|
+
attemptIndex: att.attempt_index
|
|
403
|
+
});
|
|
404
|
+
switch (outcome.status) {
|
|
405
|
+
case "uploaded":
|
|
406
|
+
summary.uploaded += 1;
|
|
407
|
+
return;
|
|
408
|
+
case "deduplicated":
|
|
409
|
+
summary.deduplicated += 1;
|
|
410
|
+
return;
|
|
411
|
+
case "skipped":
|
|
412
|
+
summary.skipped += 1;
|
|
413
|
+
break;
|
|
414
|
+
case "missing":
|
|
415
|
+
summary.missing += 1;
|
|
416
|
+
break;
|
|
417
|
+
case "rejected":
|
|
418
|
+
summary.rejected += 1;
|
|
419
|
+
break;
|
|
420
|
+
case "failed":
|
|
421
|
+
summary.failed += 1;
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
options.onProblem?.(ref, outcome);
|
|
425
|
+
};
|
|
426
|
+
tasks.push(async () => {
|
|
427
|
+
try {
|
|
428
|
+
await upload();
|
|
429
|
+
} catch (err) {
|
|
430
|
+
summary.failed += 1;
|
|
431
|
+
options.onProblem?.(ref, {
|
|
432
|
+
status: "failed",
|
|
433
|
+
message: err instanceof Error ? err.message : String(err)
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
await runBounded(tasks, options.concurrency ?? ARTIFACT_UPLOAD_CONCURRENCY);
|
|
439
|
+
return summary;
|
|
440
|
+
}
|
|
441
|
+
/** Run tasks with at most `limit` in flight. Tasks must not throw. */
|
|
442
|
+
async function runBounded(tasks, limit) {
|
|
443
|
+
let next = 0;
|
|
444
|
+
const worker = async () => {
|
|
445
|
+
while (next < tasks.length) {
|
|
446
|
+
const task = tasks[next++];
|
|
447
|
+
if (task) await task();
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
await Promise.all(Array.from({ length: Math.min(limit, tasks.length) }, worker));
|
|
451
|
+
}
|
|
452
|
+
|
|
359
453
|
//#endregion
|
|
360
454
|
//#region src/commands/pushHandler.ts
|
|
361
455
|
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
@@ -425,6 +519,9 @@ async function pushHandler(flags, file) {
|
|
|
425
519
|
if (automationSourceId && sourceName && sourceOrigin !== "auto-detected") this.logger.warning(`--automation-source (${automationSourceOrigin}) takes precedence; ignoring source name "${sourceName}" (${sourceOrigin}).`);
|
|
426
520
|
const uploadTeamId = automationSourceId && !flags["team-id"] ? void 0 : teamId;
|
|
427
521
|
const ciMeta = getCiMetadata();
|
|
522
|
+
const outputResults = flags["output-results"];
|
|
523
|
+
const artifactsDir = flags.artifacts;
|
|
524
|
+
const includeResults = Boolean(outputResults || artifactsDir);
|
|
428
525
|
if (flags.verbose) {
|
|
429
526
|
this.logger.debug(`Team: ${uploadTeamId ?? (automationSourceId ? "(automation source's team)" : "(server default)")}`);
|
|
430
527
|
this.logger.debug(`File: ${file} (${formatBytes(fileStat.size)})`);
|
|
@@ -453,7 +550,8 @@ async function pushHandler(flags, file) {
|
|
|
453
550
|
format: flags.format,
|
|
454
551
|
runName: flags["run-name"],
|
|
455
552
|
...automationSourceId ? { automationSourceId } : { automationSource: sourceName },
|
|
456
|
-
importMetadata: ciMeta
|
|
553
|
+
importMetadata: ciMeta,
|
|
554
|
+
includeResults
|
|
457
555
|
});
|
|
458
556
|
spinner.stop();
|
|
459
557
|
if (result?.status === "failed") {
|
|
@@ -489,6 +587,11 @@ async function pushHandler(flags, file) {
|
|
|
489
587
|
const ciLabel = ciMeta.ci_build_id ? `${prettyProvider} #${ciMeta.ci_build_id}` : prettyProvider;
|
|
490
588
|
this.process.stdout.write(` CI: ${ciLabel}\n`);
|
|
491
589
|
}
|
|
590
|
+
if (includeResults) await handleResultArtifacts(this, result, {
|
|
591
|
+
outputResults,
|
|
592
|
+
artifactsDir,
|
|
593
|
+
reportFile: file
|
|
594
|
+
});
|
|
492
595
|
if (flags.verbose && result.result?.stats) {
|
|
493
596
|
const s = result.result.stats;
|
|
494
597
|
this.process.stdout.write("\n Details:\n");
|
|
@@ -506,6 +609,36 @@ async function pushHandler(flags, file) {
|
|
|
506
609
|
this.process.exitCode = 1;
|
|
507
610
|
}
|
|
508
611
|
}
|
|
612
|
+
/**
|
|
613
|
+
* ENG-6164: write the results file and upload the report's artifacts.
|
|
614
|
+
* fail_fallback(warn) throughout — nothing here changes the push exit code
|
|
615
|
+
* (DD5, ENG-591): every problem becomes a warning or a summary count.
|
|
616
|
+
*/
|
|
617
|
+
async function handleResultArtifacts(ctx, result, options) {
|
|
618
|
+
const results = result.result?.results ?? [];
|
|
619
|
+
if (options.outputResults) try {
|
|
620
|
+
writeFileSync(options.outputResults, `${JSON.stringify(results, null, 2)}\n`);
|
|
621
|
+
ctx.process.stdout.write(` Results: ${results.length} written to ${options.outputResults}\n`);
|
|
622
|
+
} catch (err) {
|
|
623
|
+
ctx.logger.warning(`Could not write ${options.outputResults}: ${err instanceof Error ? err.message : String(err)}`);
|
|
624
|
+
}
|
|
625
|
+
if (options.artifactsDir) try {
|
|
626
|
+
const summary = await uploadReportArtifacts({
|
|
627
|
+
results,
|
|
628
|
+
artifactsDir: resolve(options.artifactsDir),
|
|
629
|
+
reportDir: dirname(resolve(options.reportFile)),
|
|
630
|
+
onProblem: (path$1, outcome) => {
|
|
631
|
+
const why = outcome === "rejected" ? "outside the working directory" : outcome.status === "skipped" ? "larger than 50 MiB" : "message" in outcome ? outcome.message : outcome.status;
|
|
632
|
+
ctx.logger.debug(`artifact ${path$1}: ${why}`);
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
const line = formatArtifactSummary(summary);
|
|
636
|
+
if (summary.skipped + summary.missing + summary.rejected + summary.failed > 0) ctx.logger.warning(line);
|
|
637
|
+
else ctx.process.stdout.write(` ${line}\n`);
|
|
638
|
+
} catch (err) {
|
|
639
|
+
ctx.logger.warning(`Artifact upload stopped: ${err instanceof Error ? err.message : String(err)}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
509
642
|
|
|
510
643
|
//#endregion
|
|
511
644
|
export { pushHandler };
|