@flakiness/sdk 0.131.0 → 0.132.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/lib/cli/cli.js CHANGED
@@ -742,7 +742,7 @@ import path11 from "path";
742
742
  // ../package.json
743
743
  var package_default = {
744
744
  name: "flakiness",
745
- version: "0.131.0",
745
+ version: "0.132.0",
746
746
  private: true,
747
747
  scripts: {
748
748
  minor: "./version.mjs minor",
@@ -1515,6 +1515,23 @@ async function cmdDownload(session2, project, runId) {
1515
1515
  console.log(`\u2714\uFE0F Saved as ${rootDir}`);
1516
1516
  }
1517
1517
 
1518
+ // src/cli/cmd-link.ts
1519
+ async function cmdLink(session2, slug) {
1520
+ const [orgSlug, projectSlug] = slug.split("/");
1521
+ const project = await session2.api.project.findProject.GET({
1522
+ orgSlug,
1523
+ projectSlug
1524
+ });
1525
+ if (!project) {
1526
+ console.log(`Failed to find project ${slug}`);
1527
+ process.exit(1);
1528
+ }
1529
+ const config = FlakinessConfig.createEmpty();
1530
+ config.setProjectPublicId(project.projectPublicId);
1531
+ await config.save();
1532
+ console.log(`\u2713 Linked to ${session2.endpoint()}/${project.org.orgSlug}/${project.projectSlug}`);
1533
+ }
1534
+
1518
1535
  // ../server/lib/common/knownClientIds.js
1519
1536
  var KNOWN_CLIENT_IDS = {
1520
1537
  OFFICIAL_WEB: "flakiness-io-official-cli",
@@ -1532,7 +1549,7 @@ async function cmdLogout() {
1532
1549
  return;
1533
1550
  const currentSession = await session2.api.user.currentSession.GET().catch((e) => void 0);
1534
1551
  if (currentSession)
1535
- await session2.api.user.logoutSession.POST({ sessionId: currentSession.sessionPublicId });
1552
+ await session2.api.user.logoutSession.POST({ sessionId: currentSession.sessionPublicId }).catch((e) => void 0);
1536
1553
  await FlakinessSession.remove();
1537
1554
  }
1538
1555
 
@@ -1578,40 +1595,6 @@ async function cmdLogin(endpoint = DEFAULT_FLAKINESS_ENDPOINT) {
1578
1595
  return session2;
1579
1596
  }
1580
1597
 
1581
- // src/cli/cmd-link.ts
1582
- async function cmdLink(slugOrUrl) {
1583
- let slug = slugOrUrl;
1584
- let endpoint = DEFAULT_FLAKINESS_ENDPOINT;
1585
- if (slugOrUrl.startsWith("http://") || slugOrUrl.startsWith("https://")) {
1586
- const url = URL.parse(slugOrUrl);
1587
- if (!url) {
1588
- console.error(`Invalid URL: ${slugOrUrl}`);
1589
- process.exit(1);
1590
- }
1591
- slug = url.pathname.substring(1);
1592
- endpoint = url.origin;
1593
- } else if (slugOrUrl.startsWith("flakiness.io/")) {
1594
- endpoint = "https://flakiness.io";
1595
- slug = slugOrUrl.substring("flakiness.io/".length);
1596
- }
1597
- let session2 = await FlakinessSession.load();
1598
- if (!session2 || session2.endpoint() !== endpoint)
1599
- session2 = await cmdLogin(endpoint);
1600
- const [orgSlug, projectSlug] = slug.split("/");
1601
- const project = await session2.api.project.findProject.GET({
1602
- orgSlug,
1603
- projectSlug
1604
- });
1605
- if (!project) {
1606
- console.log(`Failed to find project ${slug}`);
1607
- process.exit(1);
1608
- }
1609
- const config = FlakinessConfig.createEmpty();
1610
- config.setProjectPublicId(project.projectPublicId);
1611
- await config.save();
1612
- console.log(`\u2713 Linked to ${session2.endpoint()}/${project.org.orgSlug}/${project.projectSlug}`);
1613
- }
1614
-
1615
1598
  // src/cli/cmd-show-report.ts
1616
1599
  import chalk from "chalk";
1617
1600
  import open2 from "open";
@@ -1648,8 +1631,10 @@ async function listLocalCommits(gitRoot, head, count) {
1648
1631
  // %at: Author date as a Unix timestamp (seconds since epoch)
1649
1632
  "%an",
1650
1633
  // %an: Author name
1651
- "%s"
1634
+ "%s",
1652
1635
  // %s: Subject (the first line of the commit message)
1636
+ "%P"
1637
+ // %P: Parent hashes (space-separated)
1653
1638
  ].join(FIELD_SEPARATOR);
1654
1639
  const command = `git log ${head} -n ${count} --pretty=format:"${prettyFormat}" -z`;
1655
1640
  try {
@@ -1658,13 +1643,14 @@ async function listLocalCommits(gitRoot, head, count) {
1658
1643
  return [];
1659
1644
  }
1660
1645
  return stdout.trim().split(RECORD_SEPARATOR).filter((record) => record).map((record) => {
1661
- const [commitId, timestampStr, author, message] = record.split(FIELD_SEPARATOR);
1646
+ const [commitId, timestampStr, author, message, parentsStr] = record.split(FIELD_SEPARATOR);
1647
+ const parents = parentsStr ? parentsStr.split(" ").filter((p) => p) : [];
1662
1648
  return {
1663
1649
  commitId,
1664
1650
  timestamp: parseInt(timestampStr, 10) * 1e3,
1665
- // Convert timestamp from seconds to milliseconds
1666
1651
  author,
1667
1652
  message,
1653
+ parents,
1668
1654
  walkIndex: 0
1669
1655
  };
1670
1656
  });
@@ -2204,29 +2190,31 @@ import path10 from "path";
2204
2190
  var warn = (txt) => console.warn(chalk2.yellow(`[flakiness.io] WARN: ${txt}`));
2205
2191
  var err = (txt) => console.error(chalk2.red(`[flakiness.io] Error: ${txt}`));
2206
2192
  var log = (txt) => console.log(`[flakiness.io] ${txt}`);
2207
- async function cmdUpload(relativePath, options) {
2208
- const fullPath = path10.resolve(relativePath);
2209
- if (!await fs10.access(fullPath, fs10.constants.F_OK).then(() => true).catch(() => false)) {
2210
- err(`Path ${fullPath} is not accessible!`);
2211
- process.exit(1);
2212
- }
2213
- const text = await fs10.readFile(fullPath, "utf-8");
2214
- const report = JSON.parse(text);
2215
- const attachmentsDir = options.attachmentsDir ?? path10.dirname(fullPath);
2216
- const { attachmentIdToPath, missingAttachments } = await resolveAttachmentPaths(report, attachmentsDir);
2217
- if (missingAttachments.length) {
2218
- warn(`Missing ${missingAttachments.length} attachments`);
2219
- }
2193
+ async function cmdUpload(relativePaths, options) {
2220
2194
  const uploader = new ReportUploader({
2221
2195
  flakinessAccessToken: options.accessToken,
2222
2196
  flakinessEndpoint: options.endpoint
2223
2197
  });
2224
- const upload = uploader.createUpload(report, Array.from(attachmentIdToPath.values()));
2225
- const uploadResult = await upload.upload();
2226
- if (!uploadResult.success) {
2227
- err(`Failed to upload to ${options.endpoint}: ${uploadResult.message}`);
2228
- } else {
2229
- log(`\u2713 Uploaded ${uploadResult.reportUrl ?? uploadResult.message ?? ""}`);
2198
+ for (const relativePath of relativePaths) {
2199
+ const fullPath = path10.resolve(relativePath);
2200
+ if (!await fs10.access(fullPath, fs10.constants.F_OK).then(() => true).catch(() => false)) {
2201
+ err(`Path ${fullPath} is not accessible!`);
2202
+ process.exit(1);
2203
+ }
2204
+ const text = await fs10.readFile(fullPath, "utf-8");
2205
+ const report = JSON.parse(text);
2206
+ const attachmentsDir = options.attachmentsDir ?? path10.dirname(fullPath);
2207
+ const { attachmentIdToPath, missingAttachments } = await resolveAttachmentPaths(report, attachmentsDir);
2208
+ if (missingAttachments.length) {
2209
+ warn(`Missing ${missingAttachments.length} attachments`);
2210
+ }
2211
+ const upload = uploader.createUpload(report, Array.from(attachmentIdToPath.values()));
2212
+ const uploadResult = await upload.upload();
2213
+ if (!uploadResult.success) {
2214
+ err(`Failed to upload to ${options.endpoint}: ${uploadResult.message}`);
2215
+ } else {
2216
+ log(`\u2713 Uploaded ${uploadResult.reportUrl ?? uploadResult.message ?? ""}`);
2217
+ }
2230
2218
  }
2231
2219
  }
2232
2220
 
@@ -2283,11 +2271,8 @@ async function ensureAccessToken(options) {
2283
2271
  program.command("upload-playwright-json", { hidden: true }).description("Upload Playwright Test JSON report to the flakiness.io service").argument("<relative-path-to-json>", "Path to the Playwright JSON report file").addOption(optAccessToken).addOption(optEndpoint).action(async (relativePath, options) => runCommand(async () => {
2284
2272
  await cmdUploadPlaywrightJson(relativePath, await ensureAccessToken(options));
2285
2273
  }));
2286
- var optLink = new Option("--link <org/proj>", "A project to link to");
2287
- program.command("login").description("Login to the Flakiness.io service").addOption(optEndpoint).addOption(optLink).action(async (options) => runCommand(async () => {
2274
+ program.command("login").description("Login to the Flakiness.io service").addOption(optEndpoint).action(async (options) => runCommand(async () => {
2288
2275
  await cmdLogin(options.endpoint);
2289
- if (options.link)
2290
- await cmdLink(options.link);
2291
2276
  }));
2292
2277
  program.command("logout").description("Logout from current session").action(async () => runCommand(async () => {
2293
2278
  await cmdLogout();
@@ -2296,10 +2281,24 @@ program.command("whoami").description("Show current logged in user information")
2296
2281
  await cmdWhoami();
2297
2282
  }));
2298
2283
  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 () => {
2299
- const session2 = await FlakinessSession.load();
2300
- if (!session2)
2301
- await cmdLogin(options.endpoint);
2302
- await cmdLink(slugOrUrl);
2284
+ let slug = slugOrUrl;
2285
+ let endpoint = options.endpoint;
2286
+ if (slugOrUrl.startsWith("http://") || slugOrUrl.startsWith("https://")) {
2287
+ const url = URL.parse(slugOrUrl);
2288
+ if (!url) {
2289
+ console.error(`Invalid URL: ${slugOrUrl}`);
2290
+ process.exit(1);
2291
+ }
2292
+ slug = url.pathname.substring(1);
2293
+ endpoint = url.origin;
2294
+ } else if (slugOrUrl.startsWith("flakiness.io/")) {
2295
+ endpoint = "https://flakiness.io";
2296
+ slug = slugOrUrl.substring("flakiness.io/".length);
2297
+ }
2298
+ let session2 = await FlakinessSession.load();
2299
+ if (!session2 || session2.endpoint() !== endpoint || await session2.api.user.whoami.GET().catch((e) => void 0) === void 0)
2300
+ session2 = await cmdLogin(endpoint);
2301
+ await cmdLink(session2, slug);
2303
2302
  }));
2304
2303
  program.command("unlink").description("Unlink repository from the flakiness project").action(async () => runCommand(async () => {
2305
2304
  await cmdUnlink();
@@ -2352,9 +2351,9 @@ program.command("download").description("Download run").addOption(optSince).addO
2352
2351
  }
2353
2352
  await Promise.all(downloaders);
2354
2353
  }));
2355
- program.command("upload").description("Upload Flakiness report to the flakiness.io service").argument("<relative-path>", "Path to the Flakiness report file").addOption(optAccessToken).addOption(optEndpoint).addOption(optAttachmentsDir).action(async (relativePath, options) => {
2354
+ 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).addOption(optAttachmentsDir).action(async (relativePaths, options) => {
2356
2355
  await runCommand(async () => {
2357
- await cmdUpload(relativePath, await ensureAccessToken(options));
2356
+ await cmdUpload(relativePaths, await ensureAccessToken(options));
2358
2357
  });
2359
2358
  });
2360
2359
  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 () => {
@@ -185,152 +185,8 @@ var FlakinessConfig = class _FlakinessConfig {
185
185
  }
186
186
  };
187
187
 
188
- // src/flakinessSession.ts
189
- import fs2 from "fs/promises";
190
- import os from "os";
191
- import path3 from "path";
192
-
193
- // src/serverapi.ts
194
- import { TypedHTTP } from "@flakiness/shared/common/typedHttp.js";
195
- function createServerAPI(endpoint, options) {
196
- endpoint += "/api/";
197
- const fetcher = options?.auth ? (url, init) => fetch(url, {
198
- ...init,
199
- headers: {
200
- ...init.headers,
201
- "Authorization": `Bearer ${options.auth}`
202
- }
203
- }) : fetch;
204
- if (options?.retries)
205
- return TypedHTTP.createClient(endpoint, (url, init) => retryWithBackoff(() => fetcher(url, init), options.retries));
206
- return TypedHTTP.createClient(endpoint, fetcher);
207
- }
208
-
209
- // src/flakinessSession.ts
210
- var CONFIG_DIR = (() => {
211
- const configDir = process.platform === "darwin" ? path3.join(os.homedir(), "Library", "Application Support", "flakiness") : process.platform === "win32" ? path3.join(os.homedir(), "AppData", "Roaming", "flakiness") : path3.join(os.homedir(), ".config", "flakiness");
212
- return configDir;
213
- })();
214
- var CONFIG_PATH = path3.join(CONFIG_DIR, "config.json");
215
- var FlakinessSession = class _FlakinessSession {
216
- constructor(_config) {
217
- this._config = _config;
218
- this.api = createServerAPI(this._config.endpoint, { auth: this._config.token });
219
- }
220
- static async loadOrDie() {
221
- const session = await _FlakinessSession.load();
222
- if (!session)
223
- throw new Error(`Please login first with 'npx flakiness login'`);
224
- return session;
225
- }
226
- static async load() {
227
- const data = await fs2.readFile(CONFIG_PATH, "utf-8").catch((e) => void 0);
228
- if (!data)
229
- return void 0;
230
- const json = JSON.parse(data);
231
- return new _FlakinessSession(json);
232
- }
233
- static async remove() {
234
- await fs2.unlink(CONFIG_PATH).catch((e) => void 0);
235
- }
236
- api;
237
- endpoint() {
238
- return this._config.endpoint;
239
- }
240
- path() {
241
- return CONFIG_PATH;
242
- }
243
- sessionToken() {
244
- return this._config.token;
245
- }
246
- async save() {
247
- await fs2.mkdir(CONFIG_DIR, { recursive: true });
248
- await fs2.writeFile(CONFIG_PATH, JSON.stringify(this._config, null, 2));
249
- }
250
- };
251
-
252
- // ../server/lib/common/knownClientIds.js
253
- var KNOWN_CLIENT_IDS = {
254
- OFFICIAL_WEB: "flakiness-io-official-cli",
255
- OFFICIAL_CLI: "flakiness-io-official-website"
256
- };
257
-
258
- // src/cli/cmd-login.ts
259
- import open from "open";
260
- import os2 from "os";
261
-
262
- // src/cli/cmd-logout.ts
263
- async function cmdLogout() {
264
- const session = await FlakinessSession.load();
265
- if (!session)
266
- return;
267
- const currentSession = await session.api.user.currentSession.GET().catch((e) => void 0);
268
- if (currentSession)
269
- await session.api.user.logoutSession.POST({ sessionId: currentSession.sessionPublicId });
270
- await FlakinessSession.remove();
271
- }
272
-
273
- // src/cli/cmd-login.ts
274
- var DEFAULT_FLAKINESS_ENDPOINT = "https://flakiness.io";
275
- async function cmdLogin(endpoint = DEFAULT_FLAKINESS_ENDPOINT) {
276
- await cmdLogout();
277
- const api = createServerAPI(endpoint);
278
- const data = await api.deviceauth.createRequest.POST({
279
- clientId: KNOWN_CLIENT_IDS.OFFICIAL_CLI,
280
- name: os2.hostname()
281
- });
282
- await open(new URL(data.verificationUrl, endpoint).href);
283
- console.log(`Please navigate to ${new URL(data.verificationUrl, endpoint)}`);
284
- let token;
285
- while (Date.now() < data.deadline) {
286
- await new Promise((x) => setTimeout(x, 2e3));
287
- const result = await api.deviceauth.getToken.GET({ deviceCode: data.deviceCode }).catch((e) => void 0);
288
- if (!result) {
289
- console.error(`Authorization request was rejected.`);
290
- process.exit(1);
291
- }
292
- token = result.token;
293
- if (token)
294
- break;
295
- }
296
- if (!token) {
297
- console.log(`Failed to login.`);
298
- process.exit(1);
299
- }
300
- const session = new FlakinessSession({
301
- endpoint,
302
- token
303
- });
304
- try {
305
- const user = await session.api.user.whoami.GET();
306
- await session.save();
307
- console.log(`\u2713 Logged in as ${user.userName} (${user.userLogin})`);
308
- } catch (e) {
309
- const message = e instanceof Error ? e.message : String(e);
310
- console.error(`x Failed to login:`, message);
311
- }
312
- return session;
313
- }
314
-
315
188
  // src/cli/cmd-link.ts
316
- async function cmdLink(slugOrUrl) {
317
- let slug = slugOrUrl;
318
- let endpoint = DEFAULT_FLAKINESS_ENDPOINT;
319
- if (slugOrUrl.startsWith("http://") || slugOrUrl.startsWith("https://")) {
320
- const url = URL.parse(slugOrUrl);
321
- if (!url) {
322
- console.error(`Invalid URL: ${slugOrUrl}`);
323
- process.exit(1);
324
- }
325
- slug = url.pathname.substring(1);
326
- endpoint = url.origin;
327
- } else if (slugOrUrl.startsWith("flakiness.io/")) {
328
- endpoint = "https://flakiness.io";
329
- slug = slugOrUrl.substring("flakiness.io/".length);
330
- }
331
- let session = await FlakinessSession.load();
332
- if (!session || session.endpoint() !== endpoint)
333
- session = await cmdLogin(endpoint);
189
+ async function cmdLink(session, slug) {
334
190
  const [orgSlug, projectSlug] = slug.split("/");
335
191
  const project = await session.api.project.findProject.GET({
336
192
  orgSlug,
@@ -171,7 +171,7 @@ async function cmdLogout() {
171
171
  return;
172
172
  const currentSession = await session.api.user.currentSession.GET().catch((e) => void 0);
173
173
  if (currentSession)
174
- await session.api.user.logoutSession.POST({ sessionId: currentSession.sessionPublicId });
174
+ await session.api.user.logoutSession.POST({ sessionId: currentSession.sessionPublicId }).catch((e) => void 0);
175
175
  await FlakinessSession.remove();
176
176
  }
177
177
 
@@ -161,7 +161,7 @@ async function cmdLogout() {
161
161
  return;
162
162
  const currentSession = await session.api.user.currentSession.GET().catch((e) => void 0);
163
163
  if (currentSession)
164
- await session.api.user.logoutSession.POST({ sessionId: currentSession.sessionPublicId });
164
+ await session.api.user.logoutSession.POST({ sessionId: currentSession.sessionPublicId }).catch((e) => void 0);
165
165
  await FlakinessSession.remove();
166
166
  }
167
167
  export {
@@ -320,8 +320,10 @@ async function listLocalCommits(gitRoot, head, count) {
320
320
  // %at: Author date as a Unix timestamp (seconds since epoch)
321
321
  "%an",
322
322
  // %an: Author name
323
- "%s"
323
+ "%s",
324
324
  // %s: Subject (the first line of the commit message)
325
+ "%P"
326
+ // %P: Parent hashes (space-separated)
325
327
  ].join(FIELD_SEPARATOR);
326
328
  const command = `git log ${head} -n ${count} --pretty=format:"${prettyFormat}" -z`;
327
329
  try {
@@ -330,13 +332,14 @@ async function listLocalCommits(gitRoot, head, count) {
330
332
  return [];
331
333
  }
332
334
  return stdout.trim().split(RECORD_SEPARATOR).filter((record) => record).map((record) => {
333
- const [commitId, timestampStr, author, message] = record.split(FIELD_SEPARATOR);
335
+ const [commitId, timestampStr, author, message, parentsStr] = record.split(FIELD_SEPARATOR);
336
+ const parents = parentsStr ? parentsStr.split(" ").filter((p) => p) : [];
334
337
  return {
335
338
  commitId,
336
339
  timestamp: parseInt(timestampStr, 10) * 1e3,
337
- // Convert timestamp from seconds to milliseconds
338
340
  author,
339
341
  message,
342
+ parents,
340
343
  walkIndex: 0
341
344
  };
342
345
  });
@@ -288,29 +288,31 @@ var ReportUpload = class {
288
288
  var warn = (txt) => console.warn(chalk.yellow(`[flakiness.io] WARN: ${txt}`));
289
289
  var err = (txt) => console.error(chalk.red(`[flakiness.io] Error: ${txt}`));
290
290
  var log = (txt) => console.log(`[flakiness.io] ${txt}`);
291
- async function cmdUpload(relativePath, options) {
292
- const fullPath = path2.resolve(relativePath);
293
- if (!await fs3.access(fullPath, fs3.constants.F_OK).then(() => true).catch(() => false)) {
294
- err(`Path ${fullPath} is not accessible!`);
295
- process.exit(1);
296
- }
297
- const text = await fs3.readFile(fullPath, "utf-8");
298
- const report = JSON.parse(text);
299
- const attachmentsDir = options.attachmentsDir ?? path2.dirname(fullPath);
300
- const { attachmentIdToPath, missingAttachments } = await resolveAttachmentPaths(report, attachmentsDir);
301
- if (missingAttachments.length) {
302
- warn(`Missing ${missingAttachments.length} attachments`);
303
- }
291
+ async function cmdUpload(relativePaths, options) {
304
292
  const uploader = new ReportUploader({
305
293
  flakinessAccessToken: options.accessToken,
306
294
  flakinessEndpoint: options.endpoint
307
295
  });
308
- const upload = uploader.createUpload(report, Array.from(attachmentIdToPath.values()));
309
- const uploadResult = await upload.upload();
310
- if (!uploadResult.success) {
311
- err(`Failed to upload to ${options.endpoint}: ${uploadResult.message}`);
312
- } else {
313
- log(`\u2713 Uploaded ${uploadResult.reportUrl ?? uploadResult.message ?? ""}`);
296
+ for (const relativePath of relativePaths) {
297
+ const fullPath = path2.resolve(relativePath);
298
+ if (!await fs3.access(fullPath, fs3.constants.F_OK).then(() => true).catch(() => false)) {
299
+ err(`Path ${fullPath} is not accessible!`);
300
+ process.exit(1);
301
+ }
302
+ const text = await fs3.readFile(fullPath, "utf-8");
303
+ const report = JSON.parse(text);
304
+ const attachmentsDir = options.attachmentsDir ?? path2.dirname(fullPath);
305
+ const { attachmentIdToPath, missingAttachments } = await resolveAttachmentPaths(report, attachmentsDir);
306
+ if (missingAttachments.length) {
307
+ warn(`Missing ${missingAttachments.length} attachments`);
308
+ }
309
+ const upload = uploader.createUpload(report, Array.from(attachmentIdToPath.values()));
310
+ const uploadResult = await upload.upload();
311
+ if (!uploadResult.success) {
312
+ err(`Failed to upload to ${options.endpoint}: ${uploadResult.message}`);
313
+ } else {
314
+ log(`\u2713 Uploaded ${uploadResult.reportUrl ?? uploadResult.message ?? ""}`);
315
+ }
314
316
  }
315
317
  }
316
318
  export {
package/lib/localGit.js CHANGED
@@ -12,8 +12,10 @@ async function listLocalCommits(gitRoot, head, count) {
12
12
  // %at: Author date as a Unix timestamp (seconds since epoch)
13
13
  "%an",
14
14
  // %an: Author name
15
- "%s"
15
+ "%s",
16
16
  // %s: Subject (the first line of the commit message)
17
+ "%P"
18
+ // %P: Parent hashes (space-separated)
17
19
  ].join(FIELD_SEPARATOR);
18
20
  const command = `git log ${head} -n ${count} --pretty=format:"${prettyFormat}" -z`;
19
21
  try {
@@ -22,13 +24,14 @@ async function listLocalCommits(gitRoot, head, count) {
22
24
  return [];
23
25
  }
24
26
  return stdout.trim().split(RECORD_SEPARATOR).filter((record) => record).map((record) => {
25
- const [commitId, timestampStr, author, message] = record.split(FIELD_SEPARATOR);
27
+ const [commitId, timestampStr, author, message, parentsStr] = record.split(FIELD_SEPARATOR);
28
+ const parents = parentsStr ? parentsStr.split(" ").filter((p) => p) : [];
26
29
  return {
27
30
  commitId,
28
31
  timestamp: parseInt(timestampStr, 10) * 1e3,
29
- // Convert timestamp from seconds to milliseconds
30
32
  author,
31
33
  message,
34
+ parents,
32
35
  walkIndex: 0
33
36
  };
34
37
  });
@@ -18,8 +18,10 @@ async function listLocalCommits(gitRoot, head, count) {
18
18
  // %at: Author date as a Unix timestamp (seconds since epoch)
19
19
  "%an",
20
20
  // %an: Author name
21
- "%s"
21
+ "%s",
22
22
  // %s: Subject (the first line of the commit message)
23
+ "%P"
24
+ // %P: Parent hashes (space-separated)
23
25
  ].join(FIELD_SEPARATOR);
24
26
  const command = `git log ${head} -n ${count} --pretty=format:"${prettyFormat}" -z`;
25
27
  try {
@@ -28,13 +30,14 @@ async function listLocalCommits(gitRoot, head, count) {
28
30
  return [];
29
31
  }
30
32
  return stdout.trim().split(RECORD_SEPARATOR).filter((record) => record).map((record) => {
31
- const [commitId, timestampStr, author, message] = record.split(FIELD_SEPARATOR);
33
+ const [commitId, timestampStr, author, message, parentsStr] = record.split(FIELD_SEPARATOR);
34
+ const parents = parentsStr ? parentsStr.split(" ").filter((p) => p) : [];
32
35
  return {
33
36
  commitId,
34
37
  timestamp: parseInt(timestampStr, 10) * 1e3,
35
- // Convert timestamp from seconds to milliseconds
36
38
  author,
37
39
  message,
40
+ parents,
38
41
  walkIndex: 0
39
42
  };
40
43
  });
@@ -29,8 +29,10 @@ async function listLocalCommits(gitRoot, head, count) {
29
29
  // %at: Author date as a Unix timestamp (seconds since epoch)
30
30
  "%an",
31
31
  // %an: Author name
32
- "%s"
32
+ "%s",
33
33
  // %s: Subject (the first line of the commit message)
34
+ "%P"
35
+ // %P: Parent hashes (space-separated)
34
36
  ].join(FIELD_SEPARATOR);
35
37
  const command = `git log ${head} -n ${count} --pretty=format:"${prettyFormat}" -z`;
36
38
  try {
@@ -39,13 +41,14 @@ async function listLocalCommits(gitRoot, head, count) {
39
41
  return [];
40
42
  }
41
43
  return stdout.trim().split(RECORD_SEPARATOR).filter((record) => record).map((record) => {
42
- const [commitId, timestampStr, author, message] = record.split(FIELD_SEPARATOR);
44
+ const [commitId, timestampStr, author, message, parentsStr] = record.split(FIELD_SEPARATOR);
45
+ const parents = parentsStr ? parentsStr.split(" ").filter((p) => p) : [];
43
46
  return {
44
47
  commitId,
45
48
  timestamp: parseInt(timestampStr, 10) * 1e3,
46
- // Convert timestamp from seconds to milliseconds
47
49
  author,
48
50
  message,
51
+ parents,
49
52
  walkIndex: 0
50
53
  };
51
54
  });
@@ -474,8 +474,10 @@ async function listLocalCommits(gitRoot, head, count) {
474
474
  // %at: Author date as a Unix timestamp (seconds since epoch)
475
475
  "%an",
476
476
  // %an: Author name
477
- "%s"
477
+ "%s",
478
478
  // %s: Subject (the first line of the commit message)
479
+ "%P"
480
+ // %P: Parent hashes (space-separated)
479
481
  ].join(FIELD_SEPARATOR);
480
482
  const command = `git log ${head} -n ${count} --pretty=format:"${prettyFormat}" -z`;
481
483
  try {
@@ -484,13 +486,14 @@ async function listLocalCommits(gitRoot, head, count) {
484
486
  return [];
485
487
  }
486
488
  return stdout.trim().split(RECORD_SEPARATOR).filter((record) => record).map((record) => {
487
- const [commitId, timestampStr, author, message] = record.split(FIELD_SEPARATOR);
489
+ const [commitId, timestampStr, author, message, parentsStr] = record.split(FIELD_SEPARATOR);
490
+ const parents = parentsStr ? parentsStr.split(" ").filter((p) => p) : [];
488
491
  return {
489
492
  commitId,
490
493
  timestamp: parseInt(timestampStr, 10) * 1e3,
491
- // Convert timestamp from seconds to milliseconds
492
494
  author,
493
495
  message,
496
+ parents,
494
497
  walkIndex: 0
495
498
  };
496
499
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flakiness/sdk",
3
- "version": "0.131.0",
3
+ "version": "0.132.0",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "flakiness": "./lib/cli/cli.js"
@@ -50,7 +50,7 @@
50
50
  "author": "Degu Labs, Inc",
51
51
  "license": "Fair Source 100",
52
52
  "devDependencies": {
53
- "@flakiness/server": "0.131.0",
53
+ "@flakiness/server": "0.132.0",
54
54
  "@playwright/test": "^1.54.0",
55
55
  "@types/babel__code-frame": "^7.0.6",
56
56
  "@types/compression": "^1.8.1",
@@ -58,8 +58,8 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "@babel/code-frame": "^7.26.2",
61
- "@flakiness/report": "0.131.0",
62
- "@flakiness/shared": "0.131.0",
61
+ "@flakiness/report": "0.132.0",
62
+ "@flakiness/shared": "0.132.0",
63
63
  "@rgrove/parse-xml": "^4.2.0",
64
64
  "body-parser": "^1.20.3",
65
65
  "chalk": "^5.6.2",