@qawolf/cli 1.3.2 → 1.4.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/dist/cli.js CHANGED
@@ -173598,7 +173598,7 @@ function registerAuthCommand(program2, signals) {
173598
173598
  // package.json
173599
173599
  var package_default = {
173600
173600
  name: "@qawolf/cli",
173601
- version: "1.3.2",
173601
+ version: "1.4.0",
173602
173602
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
173603
173603
  keywords: [
173604
173604
  "automation",
@@ -173658,7 +173658,7 @@ var package_default = {
173658
173658
  "@napi-rs/keyring": "1.3.0",
173659
173659
  "@oxc-node/core": "0.1.0",
173660
173660
  "@playwright/test": "1.60.0",
173661
- "@qawolf/api-contracts": "0.2.0",
173661
+ "@qawolf/api-contracts": "0.11.0",
173662
173662
  "@qawolf/emails": "1.1.1",
173663
173663
  "@qawolf/flow-targets": "1.0.0",
173664
173664
  "@qawolf/flows": "0.1.4",
@@ -175676,9 +175676,125 @@ var import_picomatch2 = __toESM(require_picomatch2(), 1);
175676
175676
 
175677
175677
  // node_modules/@qawolf/api-contracts/dist/v1/ids.js
175678
175678
  var defaultIdSchemas = {
175679
+ automation: exports_external.string().min(1).describe("The id of the automation."),
175679
175680
  environment: exports_external.string().min(1).describe("The id of the environment."),
175680
175681
  flow: exports_external.string().min(1).describe("The id of the flow."),
175681
- run: exports_external.string().min(1).describe("The id of the run.")
175682
+ issue: exports_external.string().min(1).describe("The id of the issue."),
175683
+ run: exports_external.string().min(1).describe("The id of the run."),
175684
+ workspace: exports_external.string().min(1).describe("The id of the workspace.")
175685
+ };
175686
+
175687
+ // node_modules/@qawolf/api-contracts/dist/v1/resource.js
175688
+ function resource(fields, { urlFieldDescription = "Absolute URL." } = {}) {
175689
+ return exports_external.object({
175690
+ ...fields,
175691
+ url: exports_external.url().describe(urlFieldDescription)
175692
+ });
175693
+ }
175694
+
175695
+ // node_modules/@qawolf/api-contracts/dist/v1/run/flowSelection.js
175696
+ var makeFlowSelectionSchema = (ids) => exports_external.object({
175697
+ flowIds: exports_external.array(ids.flow).default([]),
175698
+ tagNames: exports_external.array(exports_external.string().min(1)).default([])
175699
+ }).refine((selection) => selection.flowIds.length + selection.tagNames.length > 0, {
175700
+ message: "At least one flow or tag is required."
175701
+ });
175702
+
175703
+ // node_modules/@qawolf/api-contracts/dist/v1/run/index.js
175704
+ var makeCreateRunContract = (ids) => {
175705
+ const input = exports_external.object({
175706
+ environmentId: ids.environment,
175707
+ environmentVariables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
175708
+ ignoreRules: exports_external.boolean().default(false),
175709
+ pullRequestNumber: exports_external.number().int().positive().describe("The number of the pull request to associate with the run.").optional(),
175710
+ repository: exports_external.string().min(1).describe('The owner/name of the repository, e.g. "acme/web".').optional()
175711
+ }).and(makeFlowSelectionSchema(ids));
175712
+ const output = resource({ runId: ids.run }, { urlFieldDescription: "Absolute URL of the run page." });
175713
+ return {
175714
+ description: "Create a run for the selected flows and/or tags in an environment.",
175715
+ input,
175716
+ kind: "write",
175717
+ name: "run.create",
175718
+ output
175719
+ };
175720
+ };
175721
+
175722
+ // node_modules/@qawolf/api-contracts/dist/v1/automate/index.js
175723
+ var makeAutomateContract = (ids) => {
175724
+ const input = exports_external.object({
175725
+ environmentId: ids.environment,
175726
+ flowIds: exports_external.array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("Draft flows the automation builds."),
175727
+ patch: exports_external.string().min(1).max(1e6).optional().describe("Unified diff applied to the automation's working branch before it runs.")
175728
+ });
175729
+ const output = resource({ automationId: ids.automation }, { urlFieldDescription: "Absolute URL of the automation review page." });
175730
+ return {
175731
+ description: "Request an automation that builds the selected draft flows in an environment.",
175732
+ input,
175733
+ kind: "write",
175734
+ name: "automate",
175735
+ output
175736
+ };
175737
+ };
175738
+
175739
+ // node_modules/@qawolf/api-contracts/dist/v1/environment/index.js
175740
+ var makeEnvironmentResource = (environmentId) => resource({
175741
+ environmentId,
175742
+ flowCodeBranch: exports_external.discriminatedUnion("syncStatus", [
175743
+ exports_external.object({
175744
+ lastSyncedCommitHash: exports_external.string().describe("Hash of the latest reconciled commit."),
175745
+ name: exports_external.string().describe("The name of the environment's flow code branch."),
175746
+ syncStatus: exports_external.literal("reconciled").describe("The branch has reconciled at least one commit.")
175747
+ }),
175748
+ exports_external.object({
175749
+ name: exports_external.string().describe("The name of the environment's flow code branch."),
175750
+ syncStatus: exports_external.literal("not-reconciled").describe("The branch has not reconciled any commit yet.")
175751
+ })
175752
+ ]).optional().describe("The environment's flow code branch and its git reconciliation state, if it has one."),
175753
+ kind: exports_external.enum(["static", "preview"]).describe("Whether the environment is static or a preview."),
175754
+ name: exports_external.string(),
175755
+ status: exports_external.enum(["blocked", "needs-investigation", "ready", "running"]).describe("Standing health across the environment's runs: blocked (open blocking bugs), needs-investigation (failures awaiting triage), running (flows executing), ready (none of the above — the greenlit state)."),
175756
+ terminatedAt: exports_external.iso.datetime().optional().describe("When the environment was terminated, if it has been.")
175757
+ }, { urlFieldDescription: "Absolute URL of the environment page." });
175758
+ var makeGetEnvironmentContract = (ids) => {
175759
+ const input = exports_external.object({
175760
+ environmentId: ids.environment
175761
+ });
175762
+ const output = makeEnvironmentResource(ids.environment);
175763
+ return {
175764
+ description: "Read a single environment's name, kind, health status, and termination state.",
175765
+ input,
175766
+ kind: "read",
175767
+ name: "environment.get",
175768
+ output
175769
+ };
175770
+ };
175771
+ var makeListEnvironmentVariableNamesContract = (ids) => {
175772
+ const input = exports_external.object({
175773
+ environmentId: ids.environment
175774
+ });
175775
+ const output = exports_external.object({
175776
+ variableNames: exports_external.array(exports_external.string()).describe("Names of the environment's variables, sorted alphabetically. Values are never returned.")
175777
+ });
175778
+ return {
175779
+ description: "List the names of an environment's variables, sorted. Names only — values never leave the server.",
175780
+ input,
175781
+ kind: "read",
175782
+ name: "environment.listVariableNames",
175783
+ output
175784
+ };
175785
+ };
175786
+ var makeCreateEnvironmentContract = (ids) => {
175787
+ const input = exports_external.object({
175788
+ name: exports_external.string().min(1).max(255).regex(/\S/, "Environment name cannot be blank.").describe("Display name of the new environment.")
175789
+ });
175790
+ const output = makeEnvironmentResource(ids.environment);
175791
+ return {
175792
+ description: "Create an environment on the caller's team and return it in the environment.get shape.",
175793
+ input,
175794
+ kind: "write",
175795
+ name: "environment.create",
175796
+ output
175797
+ };
175682
175798
  };
175683
175799
 
175684
175800
  // node_modules/@qawolf/api-contracts/dist/v1/flow/index.js
@@ -175689,12 +175805,14 @@ var makeListFlowsContract = (ids) => {
175689
175805
  });
175690
175806
  const executionTarget = exports_external.union([exports_external.string().min(1), exports_external.record(exports_external.string(), exports_external.unknown())]).describe("The target the flow executes against, as a preset or object.");
175691
175807
  const output = exports_external.object({
175692
- flows: exports_external.array(exports_external.object({
175808
+ flows: exports_external.array(resource({
175693
175809
  executionTarget,
175694
175810
  flowId: ids.flow,
175695
175811
  name: exports_external.string(),
175696
175812
  path: exports_external.string().describe("The flow file path, relative to the repo."),
175697
175813
  tags: exports_external.array(exports_external.string())
175814
+ }, {
175815
+ urlFieldDescription: "Absolute URL of the flow page in its environment."
175698
175816
  }))
175699
175817
  });
175700
175818
  return {
@@ -175706,33 +175824,229 @@ var makeListFlowsContract = (ids) => {
175706
175824
  };
175707
175825
  };
175708
175826
 
175709
- // node_modules/@qawolf/api-contracts/dist/v1/run/index.js
175710
- var makeCreateRunContract = (ids) => {
175711
- const input = exports_external.object({
175827
+ // node_modules/@qawolf/api-contracts/dist/v1/pagination.js
175828
+ var makePaginationInputFields = (limits) => ({
175829
+ cursor: exports_external.string().min(1).optional().describe("Opaque cursor copied from the previous response's nextCursor. Omit to fetch the first page."),
175830
+ limit: exports_external.number().int().min(1).max(limits.maxLimit).default(limits.defaultLimit).describe(`Maximum rows per page. Defaults to ${limits.defaultLimit}, capped at ${limits.maxLimit}.`)
175831
+ });
175832
+ var nextCursorSchema = exports_external.string().min(1).optional().describe("Repeat the request with this value as cursor to fetch the next page. Absent when there are no further results.");
175833
+
175834
+ // node_modules/@qawolf/api-contracts/dist/v1/issue/index.js
175835
+ var issuePrioritySchema = exports_external.enum([
175836
+ "unprioritized",
175837
+ "low",
175838
+ "medium",
175839
+ "high",
175840
+ "urgent"
175841
+ ]);
175842
+ var issueStatusSchema = exports_external.enum([
175843
+ "pending",
175844
+ "inProgress",
175845
+ "paused",
175846
+ "resolved",
175847
+ "canceled",
175848
+ "archived"
175849
+ ]);
175850
+ var publicIssueTypeSchema = exports_external.enum(["bug", "coverageRequest", "maintenance"]);
175851
+ var openIssueStatuses = ["pending", "inProgress", "paused"];
175852
+ var makeIssueResourceSchema = (ids) => resource({
175853
+ createdAt: exports_external.coerce.date().describe("When the issue was created."),
175854
+ description: exports_external.string().describe("The issue description in Markdown."),
175855
+ issueId: ids.issue,
175856
+ name: exports_external.string(),
175857
+ number: exports_external.number().int(),
175858
+ priority: issuePrioritySchema,
175859
+ reproductions: exports_external.array(exports_external.object({
175712
175860
  environmentId: ids.environment,
175713
- environmentVariables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
175714
- flowIds: exports_external.array(ids.flow).min(1),
175715
- ignoreRules: exports_external.boolean().default(false)
175861
+ firstReproducedInRunId: ids.run,
175862
+ flowId: ids.flow,
175863
+ isActive: exports_external.boolean(),
175864
+ lastReproducedInRunId: ids.run
175865
+ })).describe("The environments, flows, and runs where the issue was reproduced."),
175866
+ status: issueStatusSchema,
175867
+ type: publicIssueTypeSchema
175868
+ }, { urlFieldDescription: "Absolute URL of the issue page." });
175869
+ var makeFindIssuesContract = (ids) => {
175870
+ const input = exports_external.object({
175871
+ ...makePaginationInputFields({ defaultLimit: 20, maxLimit: 100 }),
175872
+ statuses: exports_external.array(issueStatusSchema).min(1).default([...openIssueStatuses]).describe("Issue statuses to include. Defaults to pending, in-progress, and paused issues."),
175873
+ type: publicIssueTypeSchema.describe("The issue type to include: bug report, maintenance report, or coverage request.")
175874
+ });
175875
+ const output = exports_external.object({
175876
+ issues: exports_external.array(makeIssueResourceSchema(ids)).describe("The team's bug reports, maintenance reports, or coverage requests, newest first."),
175877
+ nextCursor: nextCursorSchema
175716
175878
  });
175717
- const output = exports_external.object({ runId: ids.run });
175718
175879
  return {
175719
- description: "Create a run for selected flows in an environment.",
175880
+ description: "List the team's bug reports, maintenance reports, or coverage requests, newest first.",
175881
+ input,
175882
+ kind: "read",
175883
+ name: "issue.find",
175884
+ output
175885
+ };
175886
+ };
175887
+ var makeGetIssueContract = (ids) => {
175888
+ const input = exports_external.object({
175889
+ issueId: ids.issue
175890
+ });
175891
+ const output = exports_external.object({
175892
+ issue: makeIssueResourceSchema(ids)
175893
+ });
175894
+ return {
175895
+ description: "Get an issue by id.",
175896
+ input,
175897
+ kind: "read",
175898
+ name: "issue.get",
175899
+ output
175900
+ };
175901
+ };
175902
+
175903
+ // node_modules/@qawolf/api-contracts/dist/v1/issue/create.js
175904
+ var makeCreateIssueContract = (ids) => {
175905
+ const commonFields = {
175906
+ description: exports_external.string().optional().describe("The issue description as plain text; each line becomes a paragraph. Markdown is not parsed."),
175907
+ name: exports_external.string().trim().min(1).max(255),
175908
+ priority: issuePrioritySchema.optional().describe('Defaults to "unprioritized".')
175909
+ };
175910
+ const input = exports_external.discriminatedUnion("type", [
175911
+ exports_external.object({
175912
+ ...commonFields,
175913
+ type: exports_external.literal("bug")
175914
+ }),
175915
+ exports_external.object({
175916
+ ...commonFields,
175917
+ estimatedDueDate: exports_external.iso.date().optional().describe("The calendar date the coverage is estimated to be complete, as YYYY-MM-DD."),
175918
+ type: exports_external.literal("coverageRequest")
175919
+ })
175920
+ ]);
175921
+ const output = exports_external.object({
175922
+ issue: makeIssueResourceSchema(ids)
175923
+ });
175924
+ return {
175925
+ description: "Create a bug or coverage request issue for the caller's team. " + "Maintenance issues cannot be created through the public API.",
175720
175926
  input,
175721
175927
  kind: "write",
175722
- name: "run.create",
175928
+ name: "issue.create",
175723
175929
  output
175724
175930
  };
175725
175931
  };
175726
175932
 
175727
- // node_modules/@qawolf/api-contracts/dist/v1/index.js
175728
- var makeContractsV1 = (ids) => ({
175729
- flow: {
175730
- list: makeListFlowsContract(ids)
175731
- },
175732
- run: {
175733
- create: makeCreateRunContract(ids)
175734
- }
175933
+ // node_modules/@qawolf/api-contracts/dist/v1/run/summary.js
175934
+ var makeRunSummaryFields = (ids) => ({
175935
+ completedAt: exports_external.iso.datetime().optional().describe("When the run finished executing. Absent while queued or running, and also absent for a terminal run that never completed execution (e.g. every flow was canceled or skipped)."),
175936
+ createdAt: exports_external.iso.datetime(),
175937
+ git: exports_external.object({
175938
+ branch: exports_external.string().optional(),
175939
+ commitMessage: exports_external.string().optional(),
175940
+ commitSha: exports_external.string().optional(),
175941
+ commitUrl: exports_external.string().optional().describe("Link to the commit on the code host.")
175942
+ }).describe("The branch and commit under test. The fields are present when a deploy notification started the run, and absent for runs started another way, for example manually or with run.create."),
175943
+ runId: ids.run,
175944
+ status: exports_external.enum(["queued", "running", "passed", "failed", "canceled"]).describe("Whole-run status. Terminal statuses are passed, failed, and canceled; poll until one is reached.")
175945
+ });
175946
+
175947
+ // node_modules/@qawolf/api-contracts/dist/v1/run/find.js
175948
+ var makeFindRunsContract = (ids) => {
175949
+ const input = exports_external.object({
175950
+ createdAfter: exports_external.iso.datetime({ offset: true }).optional().describe("Only include runs created after this instant, e.g. the runs since a deploy."),
175951
+ ...makePaginationInputFields({ defaultLimit: 20, maxLimit: 100 }),
175952
+ environmentId: ids.environment
175953
+ });
175954
+ const output = exports_external.object({
175955
+ nextCursor: nextCursorSchema,
175956
+ runs: exports_external.array(resource(makeRunSummaryFields(ids), {
175957
+ urlFieldDescription: "Absolute URL of the run page."
175958
+ })).describe("The environment's runs, newest first. Per-flow results are available via run.get.")
175959
+ });
175960
+ return {
175961
+ description: "List an environment's recent runs, newest first.",
175962
+ input,
175963
+ kind: "read",
175964
+ name: "run.find",
175965
+ output
175966
+ };
175967
+ };
175968
+
175969
+ // node_modules/@qawolf/api-contracts/dist/v1/run/get.js
175970
+ var makeGetRunContract = (ids) => {
175971
+ const input = exports_external.object({
175972
+ runId: ids.run
175973
+ });
175974
+ const failedFlow = exports_external.object({
175975
+ failure: exports_external.object({
175976
+ error: exports_external.string()
175977
+ }),
175978
+ flowId: ids.flow,
175979
+ name: exports_external.string(),
175980
+ status: exports_external.literal("failed")
175981
+ });
175982
+ const nonFailedFlow = exports_external.object({
175983
+ flowId: ids.flow,
175984
+ name: exports_external.string(),
175985
+ status: exports_external.enum(["queued", "running", "passed", "canceled"])
175986
+ });
175987
+ const flows = exports_external.array(exports_external.discriminatedUnion("status", [failedFlow, nonFailedFlow])).describe("The run's flows, ordered alphabetically by name.");
175988
+ const output = resource({
175989
+ ...makeRunSummaryFields(ids),
175990
+ flows
175991
+ }, { urlFieldDescription: "Absolute URL of the run page." });
175992
+ return {
175993
+ description: "Get a run's status, per-flow results, and links.",
175994
+ input,
175995
+ kind: "read",
175996
+ name: "run.get",
175997
+ output
175998
+ };
175999
+ };
176000
+
176001
+ // node_modules/@qawolf/api-contracts/dist/v1/tag/index.js
176002
+ var makeTagResourceSchema = () => resource({
176003
+ color: exports_external.string().regex(/^#[0-9a-fA-F]{6}$/).describe("The tag's display color as a hex code, assigned by QA Wolf."),
176004
+ name: exports_external.string()
176005
+ }, {
176006
+ urlFieldDescription: "Absolute URL of the team's tag settings page."
175735
176007
  });
176008
+ var makeCreateTagContract = () => {
176009
+ const input = exports_external.object({
176010
+ name: exports_external.string().trim().min(1).max(255).refine((name) => !name.includes(","), "Tag names cannot include commas.")
176011
+ });
176012
+ const output = makeTagResourceSchema();
176013
+ return {
176014
+ description: "Create a tag on the caller's team. Tags select flows in run.create.",
176015
+ input,
176016
+ kind: "write",
176017
+ name: "tag.create",
176018
+ output
176019
+ };
176020
+ };
176021
+
176022
+ // node_modules/@qawolf/api-contracts/dist/v1/index.js
176023
+ var makeContractsV1 = (ids) => {
176024
+ const resolvedIds = { ...defaultIdSchemas, ...ids };
176025
+ return {
176026
+ automate: makeAutomateContract(resolvedIds),
176027
+ environment: {
176028
+ create: makeCreateEnvironmentContract(resolvedIds),
176029
+ get: makeGetEnvironmentContract(resolvedIds),
176030
+ listVariableNames: makeListEnvironmentVariableNamesContract(resolvedIds)
176031
+ },
176032
+ flow: {
176033
+ list: makeListFlowsContract(resolvedIds)
176034
+ },
176035
+ issue: {
176036
+ create: makeCreateIssueContract(resolvedIds),
176037
+ find: makeFindIssuesContract(resolvedIds),
176038
+ get: makeGetIssueContract(resolvedIds)
176039
+ },
176040
+ run: {
176041
+ create: makeCreateRunContract(resolvedIds),
176042
+ find: makeFindRunsContract(resolvedIds),
176043
+ get: makeGetRunContract(resolvedIds)
176044
+ },
176045
+ tag: {
176046
+ create: makeCreateTagContract()
176047
+ }
176048
+ };
176049
+ };
175736
176050
  var publicContractsV1 = makeContractsV1(defaultIdSchemas);
175737
176051
 
175738
176052
  // src/domains/flows/listRemote.ts
@@ -179740,8 +180054,7 @@ async function dispatchFlow({
179740
180054
  run = { ...run, manifest: stamp };
179741
180055
  const durationMs = deps.now() - flowStart;
179742
180056
  const outcome = run.passed ? "pass" : "fail";
179743
- const attempts = run.attempts;
179744
- const attempt = pluralize(attempts, "attempt");
180057
+ const attempt = pluralize(run.attempts, "attempt");
179745
180058
  deps.logger?.info(`${outcome}: ${flow.name} (${durationMs}ms, ${attempt})`);
179746
180059
  return { run, durationMs };
179747
180060
  }
@@ -180923,7 +181236,7 @@ async function installBrowserList(ctx, browsers, deps) {
180923
181236
  await ctx.ui.withProgress(browsers.map((browser) => ({
180924
181237
  message: installMessages.installingBrowser(browser),
180925
181238
  task: async () => {
180926
- const args = buildArgs(browser, deps.platform);
181239
+ const args = buildArgs(browser, deps.platform, deps.browserDeps);
180927
181240
  const result = await deps.spawn(deps.playwrightCliPath, args);
180928
181241
  if (result.exitCode !== 0) {
180929
181242
  throw new Error(formatError2(browser, result));
@@ -180943,6 +181256,7 @@ async function installBrowsers(ctx, pattern, deps) {
180943
181256
  await installBrowserList(ctx, browsers, {
180944
181257
  spawn: deps.spawn,
180945
181258
  platform: deps.platform,
181259
+ browserDeps: deps.browserDeps,
180946
181260
  playwrightCliPath
180947
181261
  });
180948
181262
  }
@@ -180957,8 +181271,8 @@ async function collectBrowsers(files, peekFlowMeta) {
180957
181271
  }
180958
181272
  return [...seen].sort();
180959
181273
  }
180960
- function buildArgs(browser, platform) {
180961
- return platform === "linux" ? ["install", "--with-deps", browser] : ["install", browser];
181274
+ function buildArgs(browser, platform, browserDeps) {
181275
+ return platform === "linux" && browserDeps ? ["install", "--with-deps", browser] : ["install", browser];
180962
181276
  }
180963
181277
  function formatError2(browser, result) {
180964
181278
  if (result.exitCode < 0) {
@@ -182180,6 +182494,7 @@ function buildFlowsRunDeps(args) {
182180
182494
  installBrowsers: (innerCtx, browsers) => installBrowserList(innerCtx, browsers, {
182181
182495
  spawn: defaultSpawn,
182182
182496
  platform: process.platform,
182497
+ browserDeps: flags.browserDeps,
182183
182498
  playwrightCliPath: resolvePlaywrightCli(resolvedDir, process.platform)
182184
182499
  }),
182185
182500
  runWebFlow,
@@ -182364,7 +182679,7 @@ Examples:
182364
182679
  function registerFlowsRunCommand(flows, signals) {
182365
182680
  declareCommandKind(flows.command("run [pattern]"), "local", {
182366
182681
  kindNote: "read with --env"
182367
- }).description("Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment").option("--retries <n>", "Retry each failing flow up to this many times", parseInteger("--retries", { min: 0 }), 0).option("--bail", "Stop the run after the first failure", false).option("--workers <n>", "Parallel worker count for web flows", parseInteger("--workers", { min: 1 }), 1).option("--timeout <ms>", "Default timeout for actions and assertions, in milliseconds", parseInteger("--timeout", { min: 0 }), 30000).option("--video <mode>", "Record video: on | off | retain-on-failure", parseEnum("--video", videoModes), videoDefault).option("--trace <mode>", "Record Playwright trace: on | off | retain-on-failure", parseEnum("--trace", traceModes), traceDefault).option("--har <mode>", "Record HAR network log: on | off | retain-on-failure", parseEnum("--har", harModes), harDefault).option("--har-content <mode>", "HAR response bodies: omit | full (full uses more memory)", parseEnum("--har-content", harContentModes), harContentDefault).option("--output-dir <path>", "Directory for run artifacts (videos, traces, HAR)", defaultOutputDir).option("--junit [path]", "Write a JUnit XML report (default: <output-dir>/junit-report.xml)").option("--headed", "Show the browser window instead of headless", false).option("--env <env>", "Pull and run a flow from this environment (UUID or slug) if not cached locally").option("--deps <dir>", "Use this prepared dependency directory instead of auto-installing the runtime; or set QAWOLF_RUNTIME_DIR to relocate the managed runtime").addHelpText("after", runExamples).action((pattern, opts, command) => {
182682
+ }).description("Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment").option("--retries <n>", "Retry each failing flow up to this many times", parseInteger("--retries", { min: 0 }), 0).option("--bail", "Stop the run after the first failure", false).option("--workers <n>", "Parallel worker count for web flows", parseInteger("--workers", { min: 1 }), 1).option("--timeout <ms>", "Default timeout for actions and assertions, in milliseconds", parseInteger("--timeout", { min: 0 }), 30000).option("--video <mode>", "Record video: on | off | retain-on-failure", parseEnum("--video", videoModes), videoDefault).option("--trace <mode>", "Record Playwright trace: on | off | retain-on-failure", parseEnum("--trace", traceModes), traceDefault).option("--har <mode>", "Record HAR network log: on | off | retain-on-failure", parseEnum("--har", harModes), harDefault).option("--har-content <mode>", "HAR response bodies: omit | full (full uses more memory)", parseEnum("--har-content", harContentModes), harContentDefault).option("--output-dir <path>", "Directory for run artifacts (videos, traces, HAR)", defaultOutputDir).option("--junit [path]", "Write a JUnit XML report (default: <output-dir>/junit-report.xml)").option("--headed", "Show the browser window instead of headless", false).option("--env <env>", "Pull and run a flow from this environment (UUID or slug) if not cached locally").option("--deps <dir>", "Use this prepared dependency directory instead of auto-installing the runtime; or set QAWOLF_RUNTIME_DIR to relocate the managed runtime").option("--no-browser-deps", "Skip installing OS-level browser dependencies (Linux --with-deps, which needs root); requires the system libraries to already be present").addHelpText("after", runExamples).action((pattern, opts, command) => {
182368
182683
  if (opts.env !== undefined) {
182369
182684
  const hybridFlags = { ...opts, env: opts.env };
182370
182685
  return withAuthContext(signals, (ctx) => handleHybridFlowsRun(ctx, pattern, hybridFlags))(opts, command);
@@ -182777,13 +183092,15 @@ async function handleInstallAndroid(ctx, pattern, envDir) {
182777
183092
  }
182778
183093
 
182779
183094
  // src/commands/install/browsers.ts
182780
- async function handleInstallBrowsers(ctx, pattern, envDir) {
183095
+ async function handleInstallBrowsers(ctx, pattern, options) {
182781
183096
  const cwd = process.cwd();
182782
183097
  const { fs: fs9 } = ctx;
183098
+ const { envDir, browserDeps } = options;
182783
183099
  return installBrowsers(ctx, pattern, {
182784
183100
  cwd,
182785
183101
  spawn: defaultSpawn,
182786
183102
  platform: process.platform,
183103
+ browserDeps,
182787
183104
  expandPatterns: (patterns, dir) => expandPatterns(patterns, dir ?? cwd, undefined, fs9),
182788
183105
  peekFlowMeta: makePeekFlowMeta(fs9),
182789
183106
  resolvePlaywrightCliPath: async (files) => {
@@ -182848,18 +183165,27 @@ async function installAll(ctx, pattern, deps) {
182848
183165
  }
182849
183166
  return firstError;
182850
183167
  }
182851
- async function handleInstall(ctx, pattern) {
183168
+ async function handleInstall(ctx, pattern, options) {
182852
183169
  const { fs: fs9 } = ctx;
182853
183170
  return installAll(ctx, pattern, {
182854
183171
  cwd: process.cwd(),
182855
183172
  expandPatterns: (patterns, cwd) => expandPatterns(patterns, cwd ?? process.cwd(), undefined, fs9),
182856
183173
  peekFlowMeta: makePeekFlowMeta(fs9),
182857
183174
  resolveDepsRoot: (files) => resolveDepsRoot({ files, fs: fs9 }),
182858
- installBrowsers: handleInstallBrowsers,
183175
+ installBrowsers: (innerCtx, innerPattern, envDir) => handleInstallBrowsers(innerCtx, innerPattern, {
183176
+ envDir,
183177
+ browserDeps: options.browserDeps
183178
+ }),
182859
183179
  installAndroid: handleInstallAndroid
182860
183180
  });
182861
183181
  }
182862
183182
 
183183
+ // src/commands/install/browserDepsFlag.ts
183184
+ function mergedBrowserDeps(local, command) {
183185
+ const parent2 = command.parent?.opts().browserDeps;
183186
+ return local && parent2 !== false;
183187
+ }
183188
+
182863
183189
  // src/commands/install/clear.ts
182864
183190
  async function handleInstallClear(ctx, opts) {
182865
183191
  const dir = managedEnvBaseDir();
@@ -182887,18 +183213,22 @@ async function handleInstallClear(ctx, opts) {
182887
183213
  }
182888
183214
 
182889
183215
  // src/commands/install/index.ts
183216
+ var noBrowserDepsDescription = "Skip installing OS-level browser dependencies (Linux --with-deps, which needs root); requires the system libraries to already be present";
182890
183217
  function registerInstallCommand(program2, signals) {
182891
- const install = declareCommandKind(program2.command("install"), "local").description("Install every runtime dependency the project's flows need").argument("[pattern]", "Glob limiting which flows determine required dependencies").addHelpText("after", `
183218
+ const install = declareCommandKind(program2.command("install"), "local").description("Install every runtime dependency the project's flows need").argument("[pattern]", "Glob limiting which flows determine required dependencies").option("--no-browser-deps", noBrowserDepsDescription).addHelpText("after", `
182892
183219
  Examples:
182893
183220
  $ qawolf install
182894
183221
  $ qawolf install "flows/checkout/**"`).action((pattern, opts, command) => {
182895
- return withContext(signals, (ctx) => handleInstall(ctx, pattern))(opts, command);
183222
+ return withContext(signals, (ctx) => handleInstall(ctx, pattern, { browserDeps: opts.browserDeps }))(opts, command);
182896
183223
  });
182897
- declareCommandKind(install.command("browsers [pattern]"), "local").description("Install Playwright browsers used by the project's web flows").addHelpText("after", `
183224
+ declareCommandKind(install.command("browsers [pattern]"), "local").description("Install Playwright browsers used by the project's web flows").option("--no-browser-deps", noBrowserDepsDescription).addHelpText("after", `
182898
183225
  Examples:
182899
183226
  $ qawolf install browsers
182900
183227
  $ qawolf install browsers "flows/web/**"`).action((pattern, opts, command) => {
182901
- return withContext(signals, (ctx) => handleInstallBrowsers(ctx, pattern))(opts, command);
183228
+ return withContext(signals, (ctx) => handleInstallBrowsers(ctx, pattern, {
183229
+ envDir: undefined,
183230
+ browserDeps: mergedBrowserDeps(opts.browserDeps, command)
183231
+ }))(opts, command);
182902
183232
  });
182903
183233
  declareCommandKind(install.command("android [pattern]"), "local").description("Install Android system images, AVDs, and the Appium driver used by the project's Android flows").addHelpText("after", `
182904
183234
  Examples:
@@ -182914,8 +183244,7 @@ Examples:
182914
183244
  });
182915
183245
  }
182916
183246
 
182917
- // src/core/publicApi/flagSpecs.ts
182918
- var kebabCase = (field) => field.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
183247
+ // src/core/publicApi/flagKind.ts
182919
183248
  function flagKind(schema) {
182920
183249
  if (schema.type === "string")
182921
183250
  return { ok: true, kind: "string" };
@@ -182950,6 +183279,110 @@ function flagKind(schema) {
182950
183279
  reason: `type ${schema.type ?? "unknown"} cannot be expressed as a flag`
182951
183280
  };
182952
183281
  }
183282
+
183283
+ // src/core/publicApi/objectShape.ts
183284
+ var asObjectShape = (schema) => schema.type === "object" && schema.properties ? { properties: schema.properties, required: new Set(schema.required) } : undefined;
183285
+ function mergeIntersection(members) {
183286
+ const shape = { properties: {}, required: new Set };
183287
+ for (const member of members) {
183288
+ const memberShape = asObjectShape(member);
183289
+ if (!memberShape) {
183290
+ return {
183291
+ ok: false,
183292
+ field: "",
183293
+ reason: "intersection members must be object schemas"
183294
+ };
183295
+ }
183296
+ for (const [field, fieldSchema] of Object.entries(memberShape.properties)) {
183297
+ if (field in shape.properties) {
183298
+ return {
183299
+ ok: false,
183300
+ field,
183301
+ reason: "field appears in multiple intersection members"
183302
+ };
183303
+ }
183304
+ shape.properties[field] = fieldSchema;
183305
+ if (memberShape.required.has(field))
183306
+ shape.required.add(field);
183307
+ }
183308
+ }
183309
+ return { ok: true, shape };
183310
+ }
183311
+ function mergeUnion(branches) {
183312
+ const shapes = [];
183313
+ for (const branch of branches) {
183314
+ const branchShape = asObjectShape(branch);
183315
+ if (!branchShape) {
183316
+ return {
183317
+ ok: false,
183318
+ field: "",
183319
+ reason: "union branches must be object schemas"
183320
+ };
183321
+ }
183322
+ shapes.push(branchShape);
183323
+ }
183324
+ const shape = { properties: {}, required: new Set };
183325
+ for (const branchShape of shapes) {
183326
+ for (const [field, fieldSchema] of Object.entries(branchShape.properties)) {
183327
+ const existing = shape.properties[field];
183328
+ if (!existing) {
183329
+ shape.properties[field] = { ...fieldSchema };
183330
+ continue;
183331
+ }
183332
+ const kinds2 = [flagKind(existing), flagKind(fieldSchema)];
183333
+ if (kinds2[0]?.ok && kinds2[1]?.ok && kinds2[0].kind !== kinds2[1].kind) {
183334
+ return {
183335
+ ok: false,
183336
+ field,
183337
+ reason: "field has conflicting types across union branches"
183338
+ };
183339
+ }
183340
+ if (!existing.description && fieldSchema.description) {
183341
+ existing.description = fieldSchema.description;
183342
+ }
183343
+ }
183344
+ }
183345
+ const discriminator = Object.keys(shape.properties).find((field) => {
183346
+ const values = shapes.map((branch) => branch.properties[field]?.const);
183347
+ return values.every((value) => typeof value === "string") && new Set(values).size === shapes.length && shapes.every((branch) => branch.required.has(field));
183348
+ });
183349
+ if (discriminator === undefined) {
183350
+ return {
183351
+ ok: false,
183352
+ field: "",
183353
+ reason: "union branches must share a literal discriminator field"
183354
+ };
183355
+ }
183356
+ for (const [field, fieldSchema] of Object.entries(shape.properties)) {
183357
+ const occurrences = shapes.filter((branch) => (field in branch.properties));
183358
+ if (occurrences.length === shapes.length && occurrences.every((branch) => branch.required.has(field))) {
183359
+ shape.required.add(field);
183360
+ }
183361
+ if (field === discriminator) {
183362
+ const values = shapes.map((branch) => branch.properties[field]?.const);
183363
+ fieldSchema.description = `One of: ${values.join(", ")}`;
183364
+ }
183365
+ }
183366
+ return { ok: true, shape };
183367
+ }
183368
+ function toObjectShape(schema) {
183369
+ if (schema.allOf)
183370
+ return mergeIntersection(schema.allOf);
183371
+ const branches = schema.oneOf ?? schema.anyOf;
183372
+ if (branches)
183373
+ return mergeUnion(branches);
183374
+ const shape = asObjectShape(schema);
183375
+ if (shape)
183376
+ return { ok: true, shape };
183377
+ return {
183378
+ ok: false,
183379
+ field: "",
183380
+ reason: "contract input must be an object schema"
183381
+ };
183382
+ }
183383
+
183384
+ // src/core/publicApi/flagSpecs.ts
183385
+ var kebabCase = (field) => field.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
182953
183386
  function flagUsage(field, kind) {
182954
183387
  const name = `--${kebabCase(field)}`;
182955
183388
  if (kind === "boolean")
@@ -182964,16 +183397,11 @@ function buildFlagSpecs(inputSchema) {
182964
183397
  const jsonSchema = exports_external.toJSONSchema(inputSchema, {
182965
183398
  io: "input"
182966
183399
  });
182967
- if (jsonSchema.type !== "object" || !jsonSchema.properties) {
182968
- return {
182969
- ok: false,
182970
- field: "",
182971
- reason: "contract input must be an object schema"
182972
- };
182973
- }
182974
- const required2 = new Set(jsonSchema.required ?? []);
183400
+ const result = toObjectShape(jsonSchema);
183401
+ if (!result.ok)
183402
+ return result;
182975
183403
  const flags = [];
182976
- for (const [field, fieldSchema] of Object.entries(jsonSchema.properties)) {
183404
+ for (const [field, fieldSchema] of Object.entries(result.shape.properties)) {
182977
183405
  const kind = flagKind(fieldSchema);
182978
183406
  if (!kind.ok)
182979
183407
  return { ok: false, field, reason: kind.reason };
@@ -182981,7 +183409,7 @@ function buildFlagSpecs(inputSchema) {
182981
183409
  field,
182982
183410
  flag: flagUsage(field, kind.kind),
182983
183411
  description: fieldSchema.description ?? "",
182984
- required: required2.has(field),
183412
+ required: result.shape.required.has(field),
182985
183413
  kind: kind.kind
182986
183414
  });
182987
183415
  }
@@ -183159,4 +183587,4 @@ createProgram({ signals }).parseAsync().catch(() => {
183159
183587
  process.exitCode = 1;
183160
183588
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
183161
183589
 
183162
- //# debugId=724E44C8F9B9A9E264756E2164756E21
183590
+ //# debugId=F2AE77060354D60864756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
5
5
  "keywords": [
6
6
  "automation",
@@ -60,7 +60,7 @@
60
60
  "@napi-rs/keyring": "1.3.0",
61
61
  "@oxc-node/core": "0.1.0",
62
62
  "@playwright/test": "1.60.0",
63
- "@qawolf/api-contracts": "0.2.0",
63
+ "@qawolf/api-contracts": "0.11.0",
64
64
  "@qawolf/emails": "1.1.1",
65
65
  "@qawolf/flow-targets": "1.0.0",
66
66
  "@qawolf/flows": "0.1.4",
@@ -40,21 +40,31 @@ write on timeout: it may have reached the server the first time.
40
40
 
41
41
  <!-- commands-table:start — generated by `bun run generate`, do not edit -->
42
42
 
43
- | Command | Kind | What it does |
44
- | ------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
45
- | `qawolf auth login` | local | Authenticate with your QA Wolf API key |
46
- | `qawolf auth logout` | local | Remove stored credentials |
47
- | `qawolf auth whoami` | read | Show authentication status |
48
- | `qawolf doctor` | local | Diagnose problems running flows locally |
49
- | `qawolf flows list` | local (read with --remote) | List flows matching [pattern] from the local project, or from a QA Wolf environment with --remote |
50
- | `qawolf flows pull` | read | Download an environment's flows into the local .qawolf/<env>/ cache |
51
- | `qawolf flows run` | local (read with --env) | Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment |
52
- | `qawolf init` | local | Scaffold a QA Wolf project in the current directory |
53
- | `qawolf install` | local | Install every runtime dependency the project's flows need |
54
- | `qawolf install android` | local | Install Android system images, AVDs, and the Appium driver used by the project's Android flows |
55
- | `qawolf install browsers` | local | Install Playwright browsers used by the project's web flows |
56
- | `qawolf install clear` | local | Remove the managed runtime cache (all installed runtime versions) |
57
- | `qawolf run create` | write | Create a run for selected flows in an environment. |
43
+ | Command | Kind | What it does |
44
+ | -------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
45
+ | `qawolf auth login` | local | Authenticate with your QA Wolf API key |
46
+ | `qawolf auth logout` | local | Remove stored credentials |
47
+ | `qawolf auth whoami` | read | Show authentication status |
48
+ | `qawolf automate` | write | Request an automation that builds the selected draft flows in an environment. |
49
+ | `qawolf doctor` | local | Diagnose problems running flows locally |
50
+ | `qawolf environment create` | write | Create an environment on the caller's team and return it in the environment.get shape. |
51
+ | `qawolf environment get` | read | Read a single environment's name, kind, health status, and termination state. |
52
+ | `qawolf environment listVariableNames` | read | List the names of an environment's variables, sorted. Names only — values never leave the server. |
53
+ | `qawolf flows list` | local (read with --remote) | List flows matching [pattern] from the local project, or from a QA Wolf environment with --remote |
54
+ | `qawolf flows pull` | read | Download an environment's flows into the local .qawolf/<env>/ cache |
55
+ | `qawolf flows run` | local (read with --env) | Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment |
56
+ | `qawolf init` | local | Scaffold a QA Wolf project in the current directory |
57
+ | `qawolf install` | local | Install every runtime dependency the project's flows need |
58
+ | `qawolf install android` | local | Install Android system images, AVDs, and the Appium driver used by the project's Android flows |
59
+ | `qawolf install browsers` | local | Install Playwright browsers used by the project's web flows |
60
+ | `qawolf install clear` | local | Remove the managed runtime cache (all installed runtime versions) |
61
+ | `qawolf issue create` | write | Create a bug or coverage request issue for the caller's team. Maintenance issues cannot be created through the public API. |
62
+ | `qawolf issue find` | read | List the team's bug reports, maintenance reports, or coverage requests, newest first. |
63
+ | `qawolf issue get` | read | Get an issue by id. |
64
+ | `qawolf run create` | write | Create a run for the selected flows and/or tags in an environment. |
65
+ | `qawolf run find` | read | List an environment's recent runs, newest first. |
66
+ | `qawolf run get` | read | Get a run's status, per-flow results, and links. |
67
+ | `qawolf tag create` | write | Create a tag on the caller's team. Tags select flows in run.create. |
58
68
 
59
69
  <!-- commands-table:end -->
60
70