@levr-one/cli 0.9.27 → 0.9.28

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/cli.js CHANGED
@@ -213,7 +213,7 @@ Examples:
213
213
  aliases: { d: "device-code" }
214
214
  },
215
215
  loader: async () => {
216
- const { loginHandler } = await import("./loginHandler-CLyQKoYe.js");
216
+ const { loginHandler } = await import("./loginHandler-CnWrD3c5.js");
217
217
  return loginHandler;
218
218
  }
219
219
  });
@@ -252,7 +252,7 @@ Examples:
252
252
  },
253
253
  parameters: {},
254
254
  loader: async () => {
255
- const { statusHandler } = await import("./statusHandler-DUW3yPTK.js");
255
+ const { statusHandler } = await import("./statusHandler-BnmpOhLi.js");
256
256
  return statusHandler;
257
257
  }
258
258
  });
@@ -264,18 +264,23 @@ const pushCommand = buildCommand({
264
264
  brief: "Push test results to Levr",
265
265
  fullDescription: `Upload a test result file to Levr.
266
266
 
267
- The backend auto-detects the file format (JUnit XML, Gherkin, Cucumber JSON).
267
+ The backend auto-detects the file format (JUnit XML, Gherkin, Cucumber JSON, CTRF JSON).
268
268
  In CI environments, the automation source name and CI metadata are auto-detected.
269
269
 
270
+ An automation source is required: a name (--source, LEVR_SOURCE, or CI
271
+ auto-detection; created on first use) or the UUID of an existing source
272
+ (--automation-source or LEVR_AUTOMATION_SOURCE_ID). The UUID wins if both are set.
273
+
270
274
  Team ID is optional. When omitted, the server resolves the team from:
271
275
  1. The existing automation source's team (if --source matches a known source)
272
276
  2. The workspace's default team
277
+ With --automation-source the team is always that source's team.
273
278
 
274
279
  Examples:
275
- levr push ./test-results.xml
276
280
  levr push ./results.xml --source "backend-unit-tests"
277
- levr push ./report.json --team-id <uuid> # explicit team
278
- levr push ./report.json # uses LEVR_TOKEN env var, default team`
281
+ levr push ./results.xml --automation-source <uuid>
282
+ levr push ./report.json --source e2e --team-id <uuid> # explicit team
283
+ levr push ./test-results.xml # in CI: source auto-detected`
279
284
  },
280
285
  parameters: {
281
286
  positional: {
@@ -312,7 +317,7 @@ Examples:
312
317
  "automation-source": {
313
318
  kind: "parsed",
314
319
  parse: String,
315
- brief: "Automation source UUID. When set, routes to POST /v1/automation-run/ingest (synchronous, bypasses ImportJob queue) instead of POST /v1/imports.",
320
+ brief: "UUID of an existing automation source (never creates one). Used instead of --source.",
316
321
  placeholder: "uuid",
317
322
  optional: true
318
323
  },
@@ -364,7 +369,7 @@ Examples:
364
369
  }
365
370
  },
366
371
  loader: async () => {
367
- const { pushHandler } = await import("./pushHandler-anBd1pkW.js");
372
+ const { pushHandler } = await import("./pushHandler-Bjy-AQQ2.js");
368
373
  return pushHandler;
369
374
  }
370
375
  });
@@ -484,7 +489,7 @@ Examples:
484
489
  }
485
490
  },
486
491
  loader: async () => {
487
- const { importHandler } = await import("./importHandler-B1CGwwkp.js");
492
+ const { importHandler } = await import("./importHandler-CpkiXbDN.js");
488
493
  return importHandler;
489
494
  }
490
495
  });
@@ -505,7 +510,7 @@ Examples:
505
510
  },
506
511
  parameters: {},
507
512
  loader: async () => {
508
- const { listHandler } = await import("./listHandler-Dymi1kt4.js");
513
+ const { listHandler } = await import("./listHandler-l2hVaVtR.js");
509
514
  return listHandler;
510
515
  }
511
516
  });
@@ -538,7 +543,7 @@ Examples:
538
543
  flags: {}
539
544
  },
540
545
  loader: async () => {
541
- const { selectHandler } = await import("./selectHandler-DSNQdwPV.js");
546
+ const { selectHandler } = await import("./selectHandler-FnEPzYTj.js");
542
547
  return selectHandler;
543
548
  }
544
549
  });
@@ -562,7 +567,7 @@ Examples:
562
567
 
563
568
  //#endregion
564
569
  //#region package.json
565
- var version = "0.9.27";
570
+ var version = "0.9.28";
566
571
 
567
572
  //#endregion
568
573
  //#region src/app.ts
@@ -1,7 +1,7 @@
1
1
  import { getApiUrl, getTeamId } from "./env-CHeKHu5S.js";
2
- import { authGetProfileV1, configureClient, teamFindAllV1, testCaseImportCommitV1, testCaseImportPreviewV1 } from "./sdk-client-DtubAZgv.js";
2
+ import { authGetProfileV1, configureClient, teamFindAllV1, testCaseImportCommitV1, testCaseImportPreviewV1 } from "./sdk-client-4ILxBaMA.js";
3
3
  import "./workspace-store-DDOxnut1.js";
4
- import { resolveWorkspace } from "./resolve-workspace-CLhu0X0z.js";
4
+ import { resolveWorkspace } from "./resolve-workspace-BbprgqcC.js";
5
5
  import "./token-refresh-Cu5RpkLJ.js";
6
6
  import { resolveToken } from "./resolve-token-DbQsmn03.js";
7
7
  import chalk from "chalk";
@@ -1,5 +1,5 @@
1
1
  import "./env-CHeKHu5S.js";
2
- import { authGetSitesV1, configureClient } from "./sdk-client-DtubAZgv.js";
2
+ import { authGetSitesV1, configureClient } from "./sdk-client-4ILxBaMA.js";
3
3
  import { loadWorkspace } from "./workspace-store-DDOxnut1.js";
4
4
  import "./token-refresh-Cu5RpkLJ.js";
5
5
  import { resolveToken } from "./resolve-token-DbQsmn03.js";
@@ -1,7 +1,7 @@
1
1
  import { CLI_CLIENT_ID, getApiUrl, getAuthUrl, setSessionApiUrl, writeCredentials } from "./env-CHeKHu5S.js";
2
- import { configureClient } from "./sdk-client-DtubAZgv.js";
2
+ import { configureClient } from "./sdk-client-4ILxBaMA.js";
3
3
  import "./workspace-store-DDOxnut1.js";
4
- import { autoSelectWorkspace } from "./resolve-workspace-CLhu0X0z.js";
4
+ import { autoSelectWorkspace } from "./resolve-workspace-BbprgqcC.js";
5
5
  import chalk from "chalk";
6
6
  import { createHash, randomBytes } from "node:crypto";
7
7
  import { createServer } from "node:http";
@@ -1,7 +1,7 @@
1
1
  import { getApiUrl, getAutomationSourceIdOverride, getSourceOverride, getTeamId } from "./env-CHeKHu5S.js";
2
- import { client, configureClient, uploadAutomationIngest, uploadImport } from "./sdk-client-DtubAZgv.js";
2
+ import { client, configureClient, uploadImport } from "./sdk-client-4ILxBaMA.js";
3
3
  import "./workspace-store-DDOxnut1.js";
4
- import { resolveWorkspace } from "./resolve-workspace-CLhu0X0z.js";
4
+ import { resolveWorkspace } from "./resolve-workspace-BbprgqcC.js";
5
5
  import "./token-refresh-Cu5RpkLJ.js";
6
6
  import { resolveToken } from "./resolve-token-DbQsmn03.js";
7
7
  import { readFileSync, statSync } from "node:fs";
@@ -412,24 +412,26 @@ async function pushHandler(flags, file) {
412
412
  if (flags.source) sourceOrigin = "explicit";
413
413
  else if (getSourceOverride()) sourceOrigin = "LEVR_SOURCE";
414
414
  else if (sourceName) sourceOrigin = "auto-detected";
415
- if (!sourceName) {
416
- this.logger.error("Error: --source is required. Provide it explicitly with --source, set the LEVR_SOURCE env var, or run in a supported CI environment for auto-detection.");
415
+ const automationSourceId = flags["automation-source"] ?? getAutomationSourceIdOverride();
416
+ const automationSourceOrigin = flags["automation-source"] ? "explicit" : automationSourceId ? "LEVR_AUTOMATION_SOURCE_ID" : void 0;
417
+ if (!sourceName && !automationSourceId) {
418
+ this.logger.error("Error: --source is required. Provide it explicitly with --source (or an existing source UUID with --automation-source), set the LEVR_SOURCE env var, or run in a supported CI environment for auto-detection.");
417
419
  this.logger.error("");
418
420
  this.logger.error("Example:");
419
421
  this.logger.error(" levr push results.xml --source backend-unit-tests");
420
422
  this.process.exitCode = 1;
421
423
  return;
422
424
  }
423
- const automationSourceId = flags["automation-source"] ?? getAutomationSourceIdOverride();
424
- const automationSourceOrigin = flags["automation-source"] ? "explicit" : automationSourceId ? "LEVR_AUTOMATION_SOURCE_ID" : void 0;
425
+ if (automationSourceId && sourceName && sourceOrigin !== "auto-detected") this.logger.warning(`--automation-source (${automationSourceOrigin}) takes precedence; ignoring source name "${sourceName}" (${sourceOrigin}).`);
426
+ const uploadTeamId = automationSourceId && !flags["team-id"] ? void 0 : teamId;
425
427
  const ciMeta = getCiMetadata();
426
428
  if (flags.verbose) {
427
- this.logger.debug(`Team: ${teamId ?? "(server default)"}`);
429
+ this.logger.debug(`Team: ${uploadTeamId ?? (automationSourceId ? "(automation source's team)" : "(server default)")}`);
428
430
  this.logger.debug(`File: ${file} (${formatBytes(fileStat.size)})`);
429
431
  if (flags.format) this.logger.debug(`Format: ${flags.format}`);
430
432
  if (flags["update-mode"]) this.logger.debug(`Update mode: ${flags["update-mode"]}`);
431
- if (sourceName) this.logger.debug(`Source: ${sourceName} (${sourceOrigin})`);
432
- if (automationSourceId) this.logger.debug(`Automation source: ${automationSourceId} (${automationSourceOrigin}) → POST /v1/automation-run/ingest`);
433
+ if (automationSourceId) this.logger.debug(`Automation source: ${automationSourceId} (${automationSourceOrigin})`);
434
+ else if (sourceName) this.logger.debug(`Source: ${sourceName} (${sourceOrigin})`);
433
435
  if (ciMeta) {
434
436
  this.logger.debug(`CI detected: ${ciMeta.ci_provider?.replace(/_/g, " ") ?? "unknown"}`);
435
437
  if (ciMeta.branch) this.logger.debug(`Branch: ${ciMeta.branch}`);
@@ -445,39 +447,15 @@ async function pushHandler(flags, file) {
445
447
  }).start();
446
448
  try {
447
449
  const fileBuffer = readFileSync(file);
448
- const fileObj = new File([fileBuffer], fileName);
449
- if (automationSourceId) {
450
- const ingestResult = await uploadAutomationIngest({
451
- file: fileObj,
452
- fileName,
453
- automationSourceId,
454
- runName: flags["run-name"],
455
- format: flags.format,
456
- externalRunKey: ciMeta?.ci_build_id,
457
- importMetadata: ciMeta
458
- });
459
- spinner.stop();
460
- this.process.stdout.write("\nAutomation run ingested!\n\n");
461
- this.process.stdout.write(` Run ID: ${ingestResult.automation_run_id}\n`);
462
- this.process.stdout.write(` Source: ${automationSourceId}\n`);
463
- this.process.stdout.write(` Results: ${ingestResult.passed} passed, ${ingestResult.failed} failed, ${ingestResult.errored} errored, ${ingestResult.skipped} skipped\n`);
464
- this.process.stdout.write(` Total: ${ingestResult.total_tests}\n`);
465
- if (ciMeta) {
466
- const prettyProvider = ciMeta.ci_provider?.replace(/_/g, " ") ?? "CI";
467
- const ciLabel = ciMeta.ci_build_id ? `${prettyProvider} #${ciMeta.ci_build_id}` : prettyProvider;
468
- this.process.stdout.write(` CI: ${ciLabel}\n`);
469
- }
470
- return;
471
- }
472
450
  const result = await uploadImport({
473
- teamId,
474
- file: fileObj,
451
+ teamId: uploadTeamId,
452
+ file: new File([fileBuffer], fileName),
475
453
  fileName,
476
454
  format: flags.format,
477
455
  parentFolderId: flags["parent-folder-id"],
478
456
  runName: flags["run-name"],
479
457
  updateMode: flags["update-mode"],
480
- automationSource: sourceName,
458
+ ...automationSourceId ? { automationSourceId } : { automationSource: sourceName },
481
459
  importMetadata: ciMeta
482
460
  });
483
461
  spinner.stop();
@@ -491,12 +469,19 @@ async function pushHandler(flags, file) {
491
469
  if (result) {
492
470
  if (result.team_id) this.process.stdout.write(` Team: ${result.team_id}\n`);
493
471
  if (result.format) this.process.stdout.write(` Format: ${result.format}\n`);
494
- if (sourceName) this.process.stdout.write(` Source: ${sourceName}${sourceOrigin ? ` (${sourceOrigin})` : ""}\n`);
472
+ if (automationSourceId) this.process.stdout.write(` Source: ${result.automation_source_name ?? automationSourceId} (${automationSourceOrigin})\n`);
473
+ else if (sourceName) this.process.stdout.write(` Source: ${sourceName}${sourceOrigin ? ` (${sourceOrigin})` : ""}\n`);
495
474
  if (result.result?.stats) {
496
475
  const { tests_created, tests_updated } = result.result.stats;
497
476
  this.process.stdout.write(` Tests: ${tests_created} created, ${tests_updated} updated\n`);
498
477
  }
499
- if (result.result?.run_id) this.process.stdout.write(` Run: ${result.result.run_id}\n`);
478
+ if (result.result?.run_id) {
479
+ this.process.stdout.write(` Run: ${result.result.run_id}\n`);
480
+ if (result.result.stats) {
481
+ const s = result.result.stats;
482
+ this.process.stdout.write(` Results: ${s.passed} passed, ${s.failed} failed, ${s.errored} errored, ${s.skipped} skipped\n`);
483
+ }
484
+ }
500
485
  if (result.status === "completed_with_warnings" && result.result?.warnings?.length) {
501
486
  this.process.stdout.write("\n");
502
487
  const warnings = result.result.warnings;
@@ -1,4 +1,4 @@
1
- import { authGetSitesV1 } from "./sdk-client-DtubAZgv.js";
1
+ import { authGetSitesV1 } from "./sdk-client-4ILxBaMA.js";
2
2
  import { clearWorkspace, loadWorkspace, saveWorkspace } from "./workspace-store-DDOxnut1.js";
3
3
 
4
4
  //#region src/workspace/resolve-workspace.ts
@@ -3166,30 +3166,6 @@ const zAutomationRunUnarchiveV1Data = z.object({
3166
3166
  url: z.literal("/v1/automation-run/{id}/unarchive")
3167
3167
  });
3168
3168
 
3169
- //#endregion
3170
- //#region ../sdk/dist/gen/automation-run-ingest/functions.js
3171
- /**
3172
- * Accepts a CTRF/JUnit file plus an automation_source_id and writes automation_run + automation_run_result rows directly. Bypasses the ImportJob queue used by POST /v1/imports — returns the created automation_run_id synchronously. Use this from CI when you want a direct, non-queued ingest.
3173
- */
3174
- const automationRunIngestIngestV1 = (options) => {
3175
- return (options?.client ?? client).post({
3176
- security: [{
3177
- scheme: "bearer",
3178
- type: "http"
3179
- }],
3180
- url: "/v1/automation-run/ingest",
3181
- bodySerializer: (body) => {
3182
- const fd = new FormData();
3183
- if (body && typeof body === "object") for (const [k, v] of Object.entries(body)) {
3184
- if (v == null) continue;
3185
- fd.append(k, v instanceof Blob ? v : String(v));
3186
- }
3187
- return fd;
3188
- },
3189
- ...options
3190
- });
3191
- };
3192
-
3193
3169
  //#endregion
3194
3170
  //#region ../sdk/dist/gen/automation-run-ingest/zod.js
3195
3171
  const zAutomationRunIngestIngestV1Body = z.object({
@@ -15152,6 +15128,7 @@ const zImportCreateV1Body = z.object({
15152
15128
  run_name: z.string().optional().describe(""),
15153
15129
  update_mode: z.enum(["update", "create_new"]).optional().describe(""),
15154
15130
  automation_source: z.string().optional().describe(""),
15131
+ automation_source_id: z.string().optional().describe(""),
15155
15132
  import_metadata: z.string().optional().describe("")
15156
15133
  });
15157
15134
  const zImportResponseDto = z.object({
@@ -31991,6 +31968,7 @@ async function uploadImport(options) {
31991
31968
  run_name: options.runName,
31992
31969
  update_mode: options.updateMode,
31993
31970
  automation_source: options.automationSource,
31971
+ automation_source_id: options.automationSourceId,
31994
31972
  import_metadata: options.importMetadata ? JSON.stringify(options.importMetadata) : void 0
31995
31973
  },
31996
31974
  requestValidator: void 0
@@ -32000,6 +31978,7 @@ async function uploadImport(options) {
32000
31978
  switch (status) {
32001
31979
  case 401: throw new Error("Authentication failed. Check your token or run 'levr auth login'.");
32002
31980
  case 403: throw new Error("Permission denied. Check your team access.");
31981
+ case 404: throw new Error(tryReadMessage(result.error) ?? "Not found. Check --automation-source and --team-id.");
32003
31982
  case 422: throw new Error("File could not be processed. Check the file format.");
32004
31983
  case 429: throw new Error("Rate limited. Please try again later.");
32005
31984
  default: throw new Error(`Import failed (${String(status ?? "unknown")}): ${JSON.stringify(result.error)}`);
@@ -32007,39 +31986,6 @@ async function uploadImport(options) {
32007
31986
  }
32008
31987
  return result.data;
32009
31988
  }
32010
- /**
32011
- * Direct synchronous automation run ingest. Bypasses the ImportJob queue
32012
- * used by uploadImport — calls POST /v1/automation-run/ingest and returns
32013
- * the created automation_run_id immediately. Use when an
32014
- * automation_source_id is known (from --automation-source flag or
32015
- * LEVR_AUTOMATION_SOURCE_ID env var).
32016
- */
32017
- async function uploadAutomationIngest(options) {
32018
- const result = await automationRunIngestIngestV1({
32019
- body: {
32020
- file: options.file,
32021
- automation_source_id: options.automationSourceId,
32022
- run_name: options.runName,
32023
- format: options.format,
32024
- external_run_key: options.externalRunKey,
32025
- import_metadata: options.importMetadata ? JSON.stringify(options.importMetadata) : void 0
32026
- },
32027
- requestValidator: void 0
32028
- });
32029
- if (result.error) {
32030
- const status = result.response?.status;
32031
- switch (status) {
32032
- case 400: throw new Error(`Bad request: ${tryReadMessage(result.error) ?? "check --automation-source value and file"}`);
32033
- case 401: throw new Error("Authentication failed. Check your token or run 'levr auth login'.");
32034
- case 403: throw new Error("Permission denied. Check your workspace access.");
32035
- case 404: throw new Error(`automation_source ${options.automationSourceId} not found in your workspace.`);
32036
- case 422: throw new Error(`File could not be parsed: ${tryReadMessage(result.error) ?? "check the file and --format hint"}`);
32037
- case 429: throw new Error("Rate limited. Please try again later.");
32038
- default: throw new Error(`Automation ingest failed (${String(status ?? "unknown")}): ${JSON.stringify(result.error)}`);
32039
- }
32040
- }
32041
- return result.data;
32042
- }
32043
31989
  function tryReadMessage(err) {
32044
31990
  if (err && typeof err === "object" && "message" in err) {
32045
31991
  const m = err.message;
@@ -32048,4 +31994,4 @@ function tryReadMessage(err) {
32048
31994
  }
32049
31995
 
32050
31996
  //#endregion
32051
- export { authGetProfileV1, authGetSitesV1, client, configureClient, teamFindAllV1, testCaseImportCommitV1, testCaseImportPreviewV1, uploadAutomationIngest, uploadImport };
31997
+ export { authGetProfileV1, authGetSitesV1, client, configureClient, teamFindAllV1, testCaseImportCommitV1, testCaseImportPreviewV1, uploadImport };
@@ -1,5 +1,5 @@
1
1
  import "./env-CHeKHu5S.js";
2
- import { authGetSitesV1, configureClient } from "./sdk-client-DtubAZgv.js";
2
+ import { authGetSitesV1, configureClient } from "./sdk-client-4ILxBaMA.js";
3
3
  import { saveWorkspace } from "./workspace-store-DDOxnut1.js";
4
4
  import "./token-refresh-Cu5RpkLJ.js";
5
5
  import { resolveToken } from "./resolve-token-DbQsmn03.js";
@@ -1,5 +1,5 @@
1
1
  import { getApiUrl, getPatToken, readCredentials } from "./env-CHeKHu5S.js";
2
- import { authGetProfileV1, configureClient } from "./sdk-client-DtubAZgv.js";
2
+ import { authGetProfileV1, configureClient } from "./sdk-client-4ILxBaMA.js";
3
3
  import { isTokenExpired } from "./token-refresh-Cu5RpkLJ.js";
4
4
  import chalk from "chalk";
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levr-one/cli",
3
- "version": "0.9.27",
3
+ "version": "0.9.28",
4
4
  "description": "The command-line interface for Levr",
5
5
  "type": "module",
6
6
  "license": "MIT",