flakiness 0.197.0 → 0.198.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,56 +29,40 @@ flakiness whoami
29
29
  flakiness logout
30
30
  ```
31
31
 
32
- ### Project Linking
33
-
34
- Link your local repository to a flakiness.io project to show local reports in context of their cloud history.
35
-
36
- ```bash
37
- # Link using project URL
38
- flakiness link https://flakiness.io/my-org/my-project
39
-
40
- # Or using shorthand
41
- flakiness link flakiness.io/my-org/my-project
42
-
43
- # Check link status
44
- flakiness status
45
-
46
- # Unlink repository
47
- flakiness unlink
48
- ```
49
-
50
32
  ### Upload Reports
51
33
 
52
34
  Upload Flakiness report files to the service:
53
35
 
54
36
  ```bash
55
- # Upload using linked project
56
- flakiness upload ./flakiness-report/report.json
37
+ # Upload with access token
38
+ flakiness upload ./flakiness-report/report.json --access-token <token>
57
39
 
58
- # Upload with explicit access token
59
- flakiness upload ./report.json --access-token <token>
40
+ # Upload via GitHub OIDC (no token needed if report contains flakinessProject)
41
+ flakiness upload ./flakiness-report/report.json
60
42
 
61
43
  # Upload with custom endpoint
62
44
  flakiness upload ./report.json --endpoint https://custom.flakiness.io
63
45
  ```
64
46
 
47
+ The CLI supports **GitHub OIDC** authentication: when running in GitHub Actions with `id-token: write` permission and the report contains a `flakinessProject` field, the CLI automatically authenticates without an access token. Reporters set `flakinessProject` automatically; for JUnit XML, use `convert-junit --flakiness-project`.
48
+
65
49
  **Options:**
66
50
  - `-t, --access-token <token>` — Read-write access token (env: `FLAKINESS_ACCESS_TOKEN`)
67
51
  - `-e, --endpoint <url>` — Service endpoint (env: `FLAKINESS_ENDPOINT`)
68
52
 
69
53
  ### Download Reports
70
54
 
71
- Download reports from a linked project:
55
+ Download reports from a flakiness.io project:
72
56
 
73
57
  ```bash
74
58
  # Download all reports since a date
75
- flakiness download --since 2024-01-01
59
+ flakiness download --flakiness-project myorg/myproject --since 2024-01-01
76
60
 
77
61
  # Download a specific run
78
- flakiness download --run-id 123
62
+ flakiness download --flakiness-project myorg/myproject --run-id 123
79
63
 
80
64
  # Download with parallel jobs
81
- flakiness download --since 2024-01-01 -j 4
65
+ flakiness download --flakiness-project myorg/myproject --since 2024-01-01 -j 4
82
66
  ```
83
67
 
84
68
  ### View Reports
@@ -115,12 +99,14 @@ flakiness convert-junit ./junit.xml \
115
99
  - `--env-name <name>` — Environment name for the report (default: `junit`)
116
100
  - `--commit-id <id>` — Git commit ID (auto-detected if not provided)
117
101
  - `--output-dir <dir>` — Output directory (default: `flakiness-report`)
102
+ - `--flakiness-project <org/project>` — Flakiness.io project slug for OIDC uploads (env: `FLAKINESS_PROJECT`)
118
103
 
119
104
  ## Environment Variables
120
105
 
121
106
  | Variable | Description |
122
107
  |----------|-------------|
123
108
  | `FLAKINESS_ACCESS_TOKEN` | Read-write access token for authentication |
109
+ | `FLAKINESS_PROJECT` | Flakiness.io project in `org/project` format (for OIDC and `convert-junit`) |
124
110
  | `FLAKINESS_ENDPOINT` | Custom service endpoint URL |
125
111
 
126
112
  ## License
package/lib/cli/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/cli.ts
4
- import { FlakinessProjectConfig as FlakinessProjectConfig4, showReport } from "@flakiness/sdk";
4
+ import { showReport } from "@flakiness/sdk";
5
5
 
6
6
  // ../node_modules/.pnpm/vlq@2.0.4/node_modules/vlq/src/index.js
7
7
  var char_to_integer = {};
@@ -738,6 +738,51 @@ var Ranges;
738
738
  Ranges2.sequence = sequence;
739
739
  })(Ranges || (Ranges = {}));
740
740
 
741
+ // src/cli/cli.ts
742
+ import { Command, Option } from "commander";
743
+ import debug2 from "debug";
744
+ import fs6 from "fs";
745
+ import ora2 from "ora";
746
+ import path6 from "path";
747
+
748
+ // ../package.json
749
+ var package_default = {
750
+ name: "flakiness",
751
+ version: "0.198.0",
752
+ type: "module",
753
+ private: true,
754
+ scripts: {
755
+ minor: "./version.mjs minor",
756
+ patch: "./version.mjs patch",
757
+ dev: "pnpm kubik --env-file=.env.dev -w $(find . -name build.mts) ./app.mts ./github_webhooks.mts",
758
+ "dev+billing": "pnpm kubik --env-file=.env.dev+billing -w $(find . -name build.mts) ./app.mts ./stripe_webhooks.mts ./github_webhooks.mts",
759
+ prod: "pnpm kubik --env-file=.env.prodlocal -w ./cli/build.mts ./server.mts ./web/build.mts ./experimental/build.mts ./landing/build.mts ./github_webhooks.mts",
760
+ build: "pnpm kubik $(find . -name build.mts)",
761
+ perf: "node --max-old-space-size=10240 --enable-source-maps --env-file=.env.prodlocal experimental/lib/perf_filter.js"
762
+ },
763
+ engines: {
764
+ node: ">=24"
765
+ },
766
+ author: "Degu Labs, Inc",
767
+ license: "Fair Source 100",
768
+ devDependencies: {
769
+ "@flakiness/playwright": "catalog:",
770
+ "@playwright/test": "catalog:",
771
+ "@types/node": "^22.19.3",
772
+ esbuild: "^0.27.2",
773
+ glob: "catalog:",
774
+ kubik: "^0.24.0",
775
+ "smee-client": "^5.0.0",
776
+ tsx: "^4.21.0",
777
+ typescript: "^5.9.3"
778
+ }
779
+ };
780
+
781
+ // src/flakinessSession.ts
782
+ import fs from "fs/promises";
783
+ import os from "os";
784
+ import path from "path";
785
+
741
786
  // ../shared/lib/common/typedHttp.js
742
787
  var TypedHTTP;
743
788
  ((TypedHTTP2) => {
@@ -815,11 +860,11 @@ var TypedHTTP;
815
860
  ...TypedHTTP2.StatusCodes.ClientErrors,
816
861
  ...TypedHTTP2.StatusCodes.ServerErrors
817
862
  };
818
- function assert3(value, code, message) {
863
+ function assert2(value, code, message) {
819
864
  if (!value)
820
865
  throw HttpError.withCode(code, message);
821
866
  }
822
- TypedHTTP2.assert = assert3;
867
+ TypedHTTP2.assert = assert2;
823
868
  class HttpError extends Error {
824
869
  constructor(status, message) {
825
870
  super(message);
@@ -968,52 +1013,6 @@ var TypedHTTP;
968
1013
  TypedHTTP2.createClient = createClient;
969
1014
  })(TypedHTTP || (TypedHTTP = {}));
970
1015
 
971
- // src/cli/cli.ts
972
- import assert2 from "assert";
973
- import { Command, Option } from "commander";
974
- import debug2 from "debug";
975
- import fs6 from "fs";
976
- import ora2 from "ora";
977
- import path6 from "path";
978
-
979
- // ../package.json
980
- var package_default = {
981
- name: "flakiness",
982
- version: "0.197.0",
983
- type: "module",
984
- private: true,
985
- scripts: {
986
- minor: "./version.mjs minor",
987
- patch: "./version.mjs patch",
988
- dev: "pnpm kubik --env-file=.env.dev -w $(find . -name build.mts) ./app.mts ./github_webhooks.mts",
989
- "dev+billing": "pnpm kubik --env-file=.env.dev+billing -w $(find . -name build.mts) ./app.mts ./stripe_webhooks.mts ./github_webhooks.mts",
990
- prod: "pnpm kubik --env-file=.env.prodlocal -w ./cli/build.mts ./server.mts ./web/build.mts ./experimental/build.mts ./landing/build.mts ./github_webhooks.mts",
991
- build: "pnpm kubik $(find . -name build.mts)",
992
- perf: "node --max-old-space-size=10240 --enable-source-maps --env-file=.env.prodlocal experimental/lib/perf_filter.js"
993
- },
994
- engines: {
995
- node: ">=24"
996
- },
997
- author: "Degu Labs, Inc",
998
- license: "Fair Source 100",
999
- devDependencies: {
1000
- "@flakiness/playwright": "catalog:",
1001
- "@playwright/test": "catalog:",
1002
- "@types/node": "^22.19.3",
1003
- esbuild: "^0.27.2",
1004
- glob: "catalog:",
1005
- kubik: "^0.24.0",
1006
- "smee-client": "^5.0.0",
1007
- tsx: "^4.21.0",
1008
- typescript: "^5.9.3"
1009
- }
1010
- };
1011
-
1012
- // src/flakinessSession.ts
1013
- import fs from "fs/promises";
1014
- import os from "os";
1015
- import path from "path";
1016
-
1017
1016
  // src/serverapi.ts
1018
1017
  import debug from "debug";
1019
1018
  var log = debug("fk:server_api");
@@ -1325,6 +1324,8 @@ async function cmdConvert(junitPath, options) {
1325
1324
  runStartTimestamp: Date.now(),
1326
1325
  runDuration: 0
1327
1326
  });
1327
+ if (options.flakinessProject)
1328
+ report.flakinessProject = options.flakinessProject;
1328
1329
  await writeReport(report, attachments, options.outputDir);
1329
1330
  console.log(`\u2713 Saved to ${options.outputDir}`);
1330
1331
  }
@@ -1379,24 +1380,6 @@ async function cmdDownload(session2, project, runId, rootDir) {
1379
1380
  await Promise.all(workerPromises);
1380
1381
  }
1381
1382
 
1382
- // src/cli/cmd-link.ts
1383
- import { FlakinessProjectConfig } from "@flakiness/sdk";
1384
- async function cmdLink(session2, slug) {
1385
- const [orgSlug, projectSlug] = slug.split("/");
1386
- const project = await session2.api.project.findProject.GET({
1387
- orgSlug,
1388
- projectSlug
1389
- });
1390
- if (!project) {
1391
- console.log(`Failed to find project ${slug}`);
1392
- process.exit(1);
1393
- }
1394
- const config = FlakinessProjectConfig.createEmpty();
1395
- config.setProjectPublicId(project.projectPublicId);
1396
- await config.save();
1397
- console.log(`\u2713 Linked to ${session2.endpoint()}/${project.org.orgSlug}/${project.projectSlug}`);
1398
- }
1399
-
1400
1383
  // ../server/lib/common/knownClientIds.js
1401
1384
  var KNOWN_CLIENT_IDS = {
1402
1385
  OFFICIAL_WEB: "flakiness-io-official-cli",
@@ -1460,36 +1443,6 @@ async function cmdLogin(endpoint = DEFAULT_FLAKINESS_ENDPOINT) {
1460
1443
  return session2;
1461
1444
  }
1462
1445
 
1463
- // src/cli/cmd-status.ts
1464
- import { FlakinessProjectConfig as FlakinessProjectConfig2 } from "@flakiness/sdk";
1465
- async function cmdStatus() {
1466
- const session2 = await FlakinessSession.load();
1467
- if (!session2) {
1468
- console.log(`user: not logged in`);
1469
- return;
1470
- }
1471
- const user = await session2.api.user.whoami.GET();
1472
- console.log(`user: ${user.userName} (${user.userLogin})`);
1473
- const config = await FlakinessProjectConfig2.load();
1474
- const projectPublicId = config.projectPublicId();
1475
- if (!projectPublicId) {
1476
- console.log(`project: <not linked>`);
1477
- return;
1478
- }
1479
- const project = await session2.api.project.getProject.GET({ projectPublicId });
1480
- console.log(`project: ${session2.endpoint()}/${project.org.orgSlug}/${project.projectSlug}`);
1481
- }
1482
-
1483
- // src/cli/cmd-unlink.ts
1484
- import { FlakinessProjectConfig as FlakinessProjectConfig3 } from "@flakiness/sdk";
1485
- async function cmdUnlink() {
1486
- const config = await FlakinessProjectConfig3.load();
1487
- if (!config.projectPublicId())
1488
- return;
1489
- config.setProjectPublicId(void 0);
1490
- await config.save();
1491
- }
1492
-
1493
1446
  // src/cli/cmd-upload.ts
1494
1447
  import { readReport, uploadReport } from "@flakiness/sdk";
1495
1448
  import chalk from "chalk";
@@ -1512,10 +1465,14 @@ async function cmdUpload(relativePaths, options) {
1512
1465
  const { report, attachments, missingAttachments } = await readReport(path5.dirname(fullPath));
1513
1466
  if (missingAttachments.length)
1514
1467
  warn(`Missing ${missingAttachments.length} attachments`);
1515
- await uploadReport(report, attachments, {
1468
+ const status = await uploadReport(report, attachments, {
1516
1469
  flakinessAccessToken: options.accessToken,
1517
1470
  flakinessEndpoint: options.endpoint
1518
1471
  });
1472
+ if (status.status === "failed" || status.status === "skipped") {
1473
+ spinner?.stop();
1474
+ throw new Error(`Upload ${status.status}: report was not uploaded`);
1475
+ }
1519
1476
  ++uploaded;
1520
1477
  if (spinner)
1521
1478
  spinner.text = `Uploaded ${Math.floor(uploaded / total * 100)}% [${uploaded}/${total}] reports`;
@@ -1551,29 +1508,6 @@ async function runCommand(callback) {
1551
1508
  }
1552
1509
  }
1553
1510
  var program = new Command().name("flakiness").description("Flakiness CLI tool").version(package_default.version);
1554
- async function ensureAccessToken(options) {
1555
- let accessToken = options.accessToken;
1556
- const config = await FlakinessProjectConfig4.load();
1557
- const projectPublicId = config.projectPublicId();
1558
- if (session && projectPublicId && accessToken) {
1559
- console.log(`NOTE: using FLAKINESS_ACCESS_TOKEN instead of a linked project`);
1560
- } else if (!accessToken && session && projectPublicId) {
1561
- try {
1562
- accessToken = (await session.api.project.getProject.GET({ projectPublicId })).readWriteAccessToken;
1563
- } catch (e) {
1564
- if (e instanceof TypedHTTP.HttpError && e.status === 404) {
1565
- throw new Error("Failed to find load linked project; try re-linking.");
1566
- } else {
1567
- throw e;
1568
- }
1569
- }
1570
- }
1571
- assert2(accessToken, `Please either pass FLAKINESS_ACCESS_TOKEN or run login + link`);
1572
- return {
1573
- ...options,
1574
- accessToken
1575
- };
1576
- }
1577
1511
  program.command("login").description("Login to the Flakiness.io service").addOption(optEndpoint).action(async (options) => runCommand(async () => {
1578
1512
  await cmdLogin(options.endpoint);
1579
1513
  }));
@@ -1583,32 +1517,6 @@ program.command("logout").description("Logout from current session").action(asyn
1583
1517
  program.command("whoami").description("Show current logged in user information").action(async () => runCommand(async () => {
1584
1518
  await cmdWhoami();
1585
1519
  }));
1586
- program.command("link").description("Link repository to the flakiness project").addOption(optEndpoint).argument("flakiness.io/org/project", "A URL of the Flakiness.io project").action(async (slugOrUrl, options) => runCommand(async () => {
1587
- let slug = slugOrUrl;
1588
- let endpoint = options.endpoint;
1589
- if (slugOrUrl.startsWith("http://") || slugOrUrl.startsWith("https://")) {
1590
- const url = URL.parse(slugOrUrl);
1591
- if (!url) {
1592
- console.error(`Invalid URL: ${slugOrUrl}`);
1593
- process.exit(1);
1594
- }
1595
- slug = url.pathname.substring(1);
1596
- endpoint = url.origin;
1597
- } else if (slugOrUrl.startsWith("flakiness.io/")) {
1598
- endpoint = "https://flakiness.io";
1599
- slug = slugOrUrl.substring("flakiness.io/".length);
1600
- }
1601
- let session2 = await FlakinessSession.load();
1602
- if (!session2 || session2.endpoint() !== endpoint || await session2.api.user.whoami.GET().catch((e) => void 0) === void 0)
1603
- session2 = await cmdLogin(endpoint);
1604
- await cmdLink(session2, slug);
1605
- }));
1606
- program.command("unlink").description("Unlink repository from the flakiness project").action(async () => runCommand(async () => {
1607
- await cmdUnlink();
1608
- }));
1609
- program.command("status").description("Status repository from the flakiness project").action(async () => runCommand(async () => {
1610
- await cmdStatus();
1611
- }));
1612
1520
  var optRunId = new Option("--run-id <runId>", "RunId flakiness.io access token").argParser((value) => {
1613
1521
  const parsed = parseInt(value, 10);
1614
1522
  if (isNaN(parsed) || parsed < 1) {
@@ -1630,15 +1538,15 @@ var optParallel = new Option("-j, --parallel <date>", "Parallel jobs to run").ar
1630
1538
  }
1631
1539
  return parsed;
1632
1540
  });
1633
- program.command("download").description("Download run").addOption(optSince).addOption(optRunId).addOption(optParallel).action(async (options) => runCommand(async () => {
1634
- const config = await FlakinessProjectConfig4.load();
1541
+ var optFlakinessProject = new Option("--flakiness-project <org/project>", "Flakiness.io project slug (e.g. myorg/myproject)").makeOptionMandatory();
1542
+ program.command("download").description("Download run").addOption(optFlakinessProject).addOption(optSince).addOption(optRunId).addOption(optParallel).action(async (options) => runCommand(async () => {
1543
+ const [orgSlug, projectSlug] = options.flakinessProject.split("/");
1544
+ if (!orgSlug || !projectSlug)
1545
+ throw new Error(`Invalid project slug '${options.flakinessProject}'; expected format: org/project`);
1635
1546
  const session2 = await FlakinessSession.loadOrDie();
1636
- const projectPublicId = config.projectPublicId();
1637
- if (!projectPublicId)
1638
- throw new Error(`Please link to flakiness project with 'npx flakiness link'`);
1639
- const project = await session2.api.project.getProject.GET({ projectPublicId }).catch((e) => void 0);
1547
+ const project = await session2.api.project.findProject.GET({ orgSlug, projectSlug });
1640
1548
  if (!project)
1641
- throw new Error(`Failed to fetch linked project; please re-link with 'npx flakiness link'`);
1549
+ throw new Error(`Failed to find project '${orgSlug}/${projectSlug}'`);
1642
1550
  let runIds = [];
1643
1551
  if (options.runId) {
1644
1552
  runIds = [options.runId, options.runId];
@@ -1675,14 +1583,14 @@ program.command("download").description("Download run").addOption(optSince).addO
1675
1583
  }));
1676
1584
  program.command("upload").description("Upload Flakiness report to the flakiness.io service").argument("<relative-paths...>", "Paths to the Flakiness report files").addOption(optAccessToken).addOption(optEndpoint).action(async (relativePaths, options) => {
1677
1585
  await runCommand(async () => {
1678
- await cmdUpload(relativePaths, await ensureAccessToken(options));
1586
+ await cmdUpload(relativePaths, options);
1679
1587
  });
1680
1588
  });
1681
1589
  program.command("show").description("Show flakiness report").argument("[relative-path]", "Path to the Flakiness report file or folder that contains `report.json`. (default: flakiness-report)").action(async (arg) => runCommand(async () => {
1682
1590
  const dir = path6.resolve(arg ?? "flakiness-report");
1683
1591
  await showReport(dir);
1684
1592
  }));
1685
- program.command("convert-junit").description("Convert JUnit XML report(s) to Flakiness report format").argument("<junit-root-dir-path>", "Path to JUnit XML file or directory containing XML files").option("--env-name <name>", "Environment name for the report", "junit").option("--commit-id <id>", "Git commit ID (auto-detected if not provided)").option("--output-dir <dir>", "Output directory for the report", "flakiness-report").action(async (junitPath, options) => {
1593
+ program.command("convert-junit").description("Convert JUnit XML report(s) to Flakiness report format").argument("<junit-root-dir-path>", "Path to JUnit XML file or directory containing XML files").option("--env-name <name>", "Environment name for the report", "junit").option("--commit-id <id>", "Git commit ID (auto-detected if not provided)").option("--output-dir <dir>", "Output directory for the report", "flakiness-report").addOption(new Option("--flakiness-project <org/project>", "Flakiness.io project slug for OIDC uploads (e.g. myorg/myproject)").env("FLAKINESS_PROJECT")).action(async (junitPath, options) => {
1686
1594
  await runCommand(async () => {
1687
1595
  await cmdConvert(junitPath, options);
1688
1596
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flakiness",
3
- "version": "0.197.0",
3
+ "version": "0.198.0",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "flakiness": "./lib/cli/cli.js"
@@ -22,12 +22,12 @@
22
22
  "@playwright/test": "^1.57.0",
23
23
  "@types/debug": "^4.1.12",
24
24
  "@types/express": "^4.17.20",
25
- "@flakiness/server": "0.197.0",
26
- "@flakiness/shared": "0.197.0"
25
+ "@flakiness/server": "0.198.0",
26
+ "@flakiness/shared": "0.198.0"
27
27
  },
28
28
  "dependencies": {
29
29
  "@flakiness/flakiness-report": "^0.25.0",
30
- "@flakiness/sdk": "^1.1.0",
30
+ "@flakiness/sdk": "^2.0.0",
31
31
  "@rgrove/parse-xml": "^4.2.0",
32
32
  "chalk": "^5.6.2",
33
33
  "commander": "^14.0.0",