Explorar o código

fix(triage): parse SteamCMD app_info and respect manual Linux labels (#5063)

The Linux support check regex-matched the whole SteamCMD app_info dump, so
the Steamworks SDK redistributable depots (1004-1006) counted as Linux server
support and Windows-only servers were labelled "linux support confirmed".
Parse the app_info VDF and decide from common.oslist, launch entries and the
server's own depots, ignoring redistributable and shared install depots.

The check also overwrote manual corrections of the Linux status labels on
every issue event. If the last change to either label was made by a person,
keep it and note the override in the check comment.
Daniel Gibbs hai 3 días
pai
achega
907d271154
Modificáronse 1 ficheiros con 104 adicións e 18 borrados
  1. 104 18
      .github/workflows/action-issue-triage-automation.yml

+ 104 - 18
.github/workflows/action-issue-triage-automation.yml

@@ -236,6 +236,38 @@ jobs:
               return false;
               return false;
             }
             }
 
 
+            function parseVdf(text) {
+              const root = {};
+              const stack = [root];
+              let pendingKey = null;
+              const tokenPattern = /"((?:[^"\\]|\\.)*)"|([{}])/g;
+              let match;
+              while ((match = tokenPattern.exec(text)) !== null) {
+                const current = stack[stack.length - 1];
+                if (match[2] === '{') {
+                  const child = {};
+                  if (pendingKey !== null) current[pendingKey] = child;
+                  stack.push(child);
+                  pendingKey = null;
+                } else if (match[2] === '}') {
+                  if (stack.length > 1) stack.pop();
+                  pendingKey = null;
+                } else if (pendingKey === null) {
+                  pendingKey = match[1];
+                } else {
+                  current[pendingKey] = match[1];
+                  pendingKey = null;
+                }
+              }
+              return root;
+            }
+
+            function parseSteamAppInfo(output, appId) {
+              const start = output.search(new RegExp(`^"${appId}"\\s*$`, 'm'));
+              if (start === -1) return null;
+              return parseVdf(output.slice(start))[String(appId)] ?? null;
+            }
+
             function runSteamCmdLinuxCheck(appId) {
             function runSteamCmdLinuxCheck(appId) {
               if (!appId) {
               if (!appId) {
                 return { status: 'skipped', reason: 'No Steam AppID provided.' };
                 return { status: 'skipped', reason: 'No Steam AppID provided.' };
@@ -271,23 +303,44 @@ jobs:
                   maxBuffer: 10 * 1024 * 1024,
                   maxBuffer: 10 * 1024 * 1024,
                 });
                 });
 
 
-                const normalized = output.toLowerCase();
-                const linuxSignals = [
-                  /"oslist"\s+"linux"/i,
-                  /"oslist"\s+"linux,windows"/i,
-                  /"oslist"\s+"windows,linux"/i,
-                  /"platforms"[\s\S]*?"linux"\s+"1"/i,
-                  /linux32/i,
-                  /linux64/i,
-                ];
-                const windowsOnlySignals = [
-                  /"oslist"\s+"windows"/i,
-                  /"platforms"[\s\S]*?"windows"\s+"1"/i,
-                ];
-
-                const hasLinuxSignal = linuxSignals.some((re) => re.test(normalized));
+                const appInfo = parseSteamAppInfo(output, appId);
+                if (!appInfo) {
+                  return {
+                    status: 'unknown',
+                    reason: `SteamCMD app_info returned no data for AppID ${appId}.`,
+                  };
+                }
+
+                const osListOf = (node) => String(node?.config?.oslist ?? '').toLowerCase();
+                const isObject = (value) => value && typeof value === 'object';
+                const commonOsList = String(appInfo.common?.oslist ?? '').toLowerCase();
+                const launches = Object.values(appInfo.config?.launch ?? {}).filter(isObject);
+                const serverDepots = Object.entries(appInfo.depots ?? {}).filter(
+                  ([id, depot]) =>
+                    /^\d+$/.test(id) &&
+                    isObject(depot) &&
+                    !['1004', '1005', '1006'].includes(id) &&
+                    depot.depotfromapp === undefined &&
+                    depot.sharedinstall !== '1'
+                );
+
+                const hasLinuxSignal =
+                  commonOsList.includes('linux') ||
+                  launches.some(
+                    (launch) =>
+                      osListOf(launch).includes('linux') ||
+                      (!osListOf(launch) && /\.(sh|x86_64)$/i.test(launch.executable ?? ''))
+                  ) ||
+                  serverDepots.some(([, depot]) => osListOf(depot).includes('linux'));
                 const hasWindowsOnlySignal =
                 const hasWindowsOnlySignal =
-                  !hasLinuxSignal && windowsOnlySignals.some((re) => re.test(normalized));
+                  !hasLinuxSignal &&
+                  (commonOsList === 'windows' ||
+                    (launches.length > 0 &&
+                      launches.every(
+                        (launch) =>
+                          osListOf(launch) === 'windows' ||
+                          /\.(exe|bat|cmd)$/i.test(launch.executable ?? '')
+                      )));
 
 
                 if (hasLinuxSignal) {
                 if (hasLinuxSignal) {
                   return {
                   return {
@@ -1010,8 +1063,36 @@ jobs:
               const CONFIRMED_LINUX_LABEL = 'status: linux support confirmed';
               const CONFIRMED_LINUX_LABEL = 'status: linux support confirmed';
               const LINUX_MARKER = '<!-- linux-support-check -->';
               const LINUX_MARKER = '<!-- linux-support-check -->';
               const steamDbLink = steamAppId ? `https://steamdb.info/app/${steamAppId}/` : null;
               const steamDbLink = steamAppId ? `https://steamdb.info/app/${steamAppId}/` : null;
-              const shouldApplyNoLinuxLabel = confirmedNoLinux || suggestsNoLinux;
-              const shouldApplyConfirmedLinuxLabel = confirmedLinuxSupport && !confirmedNoLinux && !suggestsNoLinux;
+
+              let linuxLabelOverrideBy = null;
+              try {
+                const issueEvents = await github.paginate(github.rest.issues.listEvents, {
+                  owner,
+                  repo,
+                  issue_number: issueNumber,
+                  per_page: 100,
+                });
+                const lastLinuxLabelEvent = issueEvents
+                  .filter(
+                    (e) =>
+                      ['labeled', 'unlabeled'].includes(e.event) &&
+                      [NO_LINUX_LABEL, CONFIRMED_LINUX_LABEL].includes(e.label?.name)
+                  )
+                  .at(-1);
+                if (lastLinuxLabelEvent && lastLinuxLabelEvent.actor?.type !== 'Bot') {
+                  linuxLabelOverrideBy = lastLinuxLabelEvent.actor?.login ?? 'a maintainer';
+                  console.log(`Linux support label last set by ${linuxLabelOverrideBy}; keeping it.`);
+                }
+              } catch (err) {
+                console.log(`Could not read issue events: ${err.message}`);
+              }
+
+              const shouldApplyNoLinuxLabel = linuxLabelOverrideBy
+                ? existingLabels.has(NO_LINUX_LABEL)
+                : confirmedNoLinux || suggestsNoLinux;
+              const shouldApplyConfirmedLinuxLabel = linuxLabelOverrideBy
+                ? existingLabels.has(CONFIRMED_LINUX_LABEL) && !existingLabels.has(NO_LINUX_LABEL)
+                : confirmedLinuxSupport && !confirmedNoLinux && !suggestsNoLinux;
 
 
               if (shouldApplyNoLinuxLabel) {
               if (shouldApplyNoLinuxLabel) {
                 // Auto-create the label if it does not exist yet.
                 // Auto-create the label if it does not exist yet.
@@ -1162,9 +1243,14 @@ jobs:
                 ? `**Label applied:** ${CONFIRMED_LINUX_LABEL}\n\n`
                 ? `**Label applied:** ${CONFIRMED_LINUX_LABEL}\n\n`
                 : '';
                 : '';
 
 
+              const overrideBlock = linuxLabelOverrideBy
+                ? `**Maintainer override:** the Linux support label was set manually by @${linuxLabelOverrideBy} and is no longer changed automatically.\n\n`
+                : '';
+
               const linuxCommentBody =
               const linuxCommentBody =
                 `${LINUX_MARKER}\n` +
                 `${LINUX_MARKER}\n` +
                 `${linuxCommentHeader}\n\n` +
                 `${linuxCommentHeader}\n\n` +
+                `${overrideBlock}` +
                 `${verdictLine}\n\n` +
                 `${verdictLine}\n\n` +
                 `${confirmedLinuxLabelBlock}` +
                 `${confirmedLinuxLabelBlock}` +
                 `${steamBlock}` +
                 `${steamBlock}` +