diff --git a/.github/scripts/database-issue-catalog.mjs b/.github/scripts/database-issue-catalog.mjs new file mode 100644 index 0000000000..402b4c1c4c --- /dev/null +++ b/.github/scripts/database-issue-catalog.mjs @@ -0,0 +1,111 @@ +import fs from "node:fs"; + +const extraAliases = { + mysql: [ + "mariadb", + "percona", + "tidb", + "greatsql", + "oceanbase mysql", + "oceanbase mysql mode", + "oceanbase mysql 模式", + "tdsql mysql", + "tdsql-mysql", + "polardb mysql", + "polardb for mysql", + "polardb-x", + "polardbx", + ], + postgres: [ + "postgresql", + "postgres", + "pgsql", + "pg", + "hologres", + "cloudberry", + "apache cloudberry", + "cockroachdb", + "cockroach db", + "polardb postgresql", + "polardb for postgresql", + "polardb postgres", + "polardb for postgres", + ], + sqlite: ["sqlite3", "sql lite"], + clickhouse: ["click house", "ch"], + sqlserver: ["sql server", "mssql", "microsoft sql server", "sqlservice"], + mongodb: ["mongo", "mongodb"], + oracle: ["oracle database"], + elasticsearch: ["elastic search"], + chromadb: ["chroma"], + doris: ["selectdb"], + manticoresearch: ["manticore"], + dameng: ["dm8", "dameng", "达梦"], + kingbase: ["kingbasees", "kingbase", "人大金仓", "金仓"], + highgo: ["瀚高"], + yashandb: ["yashan", "崖山"], + saphana: ["hana", "sap hana"], + opengauss: ["open gauss"], + "oceanbase-oracle": ["oceanbase oracle", "oceanbase oracle mode", "oceanbase oracle 模式"], + gbase: ["gbase 8a", "gbase8a", "gbase 8s", "gbase8s"], + access: ["microsoft access", "ms access"], + vastbase: ["vastbase g", "vastbaseg"], + prestosql: ["presto", "presto sql"], + hive: ["apache hive"], + db2: ["ibm db2"], + informix: ["ibm informix"], + bigquery: ["google bigquery"], + kylin: ["apache kylin"], + oscar: ["shentong", "oscar", "神通"], + xugu: ["xugudb", "xugu", "虚谷"], + zookeeper: ["zoo keeper", "apache zookeeper"], + mq: ["message queue", "pulsar", "kafka", "rabbitmq", "rocketmq"], + iotdb: ["apache iotdb"], + iris: ["intersystems iris", "intersystems cache", "intersystems caché", "cache", "caché", "ensemble"], + jdbc: ["dremio", "jdbcx"], + spark: ["apache spark"], +}; + +const supplementalDrivers = [ + { dbType: "turso", label: "Turso", aliases: ["libsql", "lib sql"] }, + { dbType: "nacos", label: "Nacos", aliases: ["r-nacos", "rnacos"] }, + { dbType: "cloudberry", label: "Apache Cloudberry" }, + { dbType: "mariadb", label: "MariaDB" }, + { dbType: "tidb", label: "TiDB" }, + { dbType: "oceanbase", label: "OceanBase" }, + { dbType: "tdsql", label: "TDSQL" }, + { dbType: "polardb", label: "PolarDB", aliases: ["polardb-x", "polardbx"] }, + { dbType: "greatsql", label: "GreatSQL" }, + { dbType: "selectdb", label: "SelectDB" }, + { dbType: "cockroachdb", label: "CockroachDB", aliases: ["cockroach db"] }, + { dbType: "dremio", label: "Dremio" }, + { dbType: "jdbcx", label: "JDBCX" }, + { dbType: "pulsar", label: "Apache Pulsar" }, + { dbType: "kafka", label: "Apache Kafka" }, + { dbType: "rocketmq", label: "Apache RocketMQ" }, + { dbType: "rabbitmq", label: "RabbitMQ" }, +]; + +const manifestUrl = new URL("../../crates/dbx-core/assets/database-drivers.manifest.json", import.meta.url); +const manifest = JSON.parse(fs.readFileSync(manifestUrl, "utf8")); +const catalogEntries = [...manifest.drivers, ...supplementalDrivers]; +const duplicateDbTypes = catalogEntries + .map((driver) => driver.dbType) + .filter((dbType, index, values) => values.indexOf(dbType) !== index); + +if (duplicateDbTypes.length > 0) { + throw new Error(`Duplicate database issue catalog entries: ${[...new Set(duplicateDbTypes)].join(", ")}`); +} + +export const databaseIssueDrivers = catalogEntries.map((driver) => ({ + dbType: driver.dbType, + label: driver.label, + aliases: [ + ...new Set([ + driver.dbType, + driver.label, + ...(driver.aliases || []), + ...(extraAliases[driver.dbType] || []), + ]), + ], +})); diff --git a/.github/scripts/label-issue-database.mjs b/.github/scripts/label-issue-database.mjs index a4aedfa405..f28aa38448 100644 --- a/.github/scripts/label-issue-database.mjs +++ b/.github/scripts/label-issue-database.mjs @@ -1,10 +1,14 @@ #!/usr/bin/env node import fs from "node:fs"; +import { pathToFileURL } from "node:url"; + +import { databaseIssueDrivers } from "./database-issue-catalog.mjs"; const LABEL_PREFIX = "db/"; const USER_PRIORITY_PREFIX = "user-priority/"; const API_VERSION = "2022-11-28"; const dryRun = process.env.DRY_RUN === "1" || process.env.DRY_RUN === "true"; +const syncDatabaseLabels = process.env.SYNC_DATABASE_LABELS === "1" || process.env.SYNC_DATABASE_LABELS === "true"; const USER_PRIORITY_LABELS = { P0: { color: "b60205", description: "User-reported priority: urgent" }, @@ -28,42 +32,6 @@ const LABEL_PALETTE = [ "c5def5", ]; -const extraAliases = { - mysql: ["mariadb", "percona", "tidb"], - postgres: ["postgresql", "postgres", "pgsql", "pg", "hologres"], - sqlite: ["sqlite3", "sql lite"], - clickhouse: ["click house", "ch"], - sqlserver: ["sql server", "mssql", "microsoft sql server", "sqlservice"], - mongodb: ["mongo", "mongodb"], - oracle: ["oracle database"], - elasticsearch: ["elastic search"], - chromadb: ["chroma"], - manticoresearch: ["manticore"], - dameng: ["dm8", "dameng", "达梦"], - kingbase: ["kingbasees", "kingbase", "人大金仓", "金仓"], - highgo: ["瀚高"], - yashandb: ["yashan", "崖山"], - saphana: ["hana", "sap hana"], - opengauss: ["open gauss"], - "oceanbase-oracle": ["oceanbase", "oceanbase oracle"], - gbase: ["gbase 8a", "gbase8a", "gbase 8s", "gbase8s"], - access: ["microsoft access", "ms access"], - vastbase: ["vastbase g", "vastbaseg"], - prestosql: ["presto", "presto sql"], - hive: ["apache hive"], - db2: ["ibm db2"], - informix: ["ibm informix"], - bigquery: ["google bigquery"], - kylin: ["apache kylin"], - oscar: ["shentong", "oscar", "神通"], - xugu: ["xugudb", "xugu", "虚谷"], - zookeeper: ["zoo keeper", "apache zookeeper"], - mq: ["message queue", "kafka", "rabbitmq", "rocketmq"], - iotdb: ["apache iotdb"], - iris: ["intersystems iris", "intersystems cache", "intersystems caché", "cache", "caché", "ensemble"], - spark: ["apache spark"], -}; - const globallyAmbiguousAliases = new Set([ "access", "ch", @@ -91,13 +59,7 @@ const genericDatabaseValues = [ "不适用", ]; -const manifestUrl = new URL("../../crates/dbx-core/assets/database-drivers.manifest.json", import.meta.url); -const manifest = JSON.parse(fs.readFileSync(manifestUrl, "utf8")); -const drivers = manifest.drivers.map((driver) => ({ - dbType: driver.dbType, - label: driver.label, - aliases: [...new Set([driver.dbType, driver.label, ...(extraAliases[driver.dbType] || [])])], -})); +const drivers = databaseIssueDrivers; const exactAsciiAliases = new Set( drivers.flatMap((driver) => driver.aliases.map((alias) => compactAscii(alias)).filter((alias) => alias.length >= 5)), ); @@ -432,15 +394,34 @@ async function githubRequest(method, path, body) { return payload; } -async function ensureLabel(name, description, color) { +function databaseLabelSpec(driver) { + return { + name: `${LABEL_PREFIX}${driver.dbType}`, + description: `Database: ${driver.label}`, + color: stablePaletteColor(driver.dbType), + }; +} + +export function allDatabaseLabelSpecs() { + return drivers.map(databaseLabelSpec); +} + +async function ensureLabel({ name, description, color }, { updateExisting = false } = {}) { try { const dbType = name.startsWith(LABEL_PREFIX) ? name.slice(LABEL_PREFIX.length) : name; await githubRequest("POST", "/labels", { name, color: color || stablePaletteColor(dbType), description }); console.log(`Created label ${name}`); } catch (error) { - // GitHub returns 422 when the label already exists; that is the common path - // after the first issue for a database type has been triaged. if (error.status === 422) { + if (updateExisting) { + await githubRequest("PATCH", `/labels/${encodeURIComponent(name)}`, { + new_name: name, + color, + description, + }); + console.log(`Updated label ${name}`); + return; + } console.log(`Label ${name} already exists`); return; } @@ -466,115 +447,146 @@ async function updateIssueTitle(issueNumber, title) { await githubRequest("PATCH", `/issues/${issueNumber}`, { title }); } -const issue = loadIssue(); -if (issue.pull_request) { - console.log("Skipping pull request event"); - process.exit(0); -} +export function triageIssue(issue) { + if (!issue.number) { + throw new Error("Issue number is required"); + } -if (!issue.number) { - throw new Error("Issue number is required"); -} + const databaseField = extractDatabaseField(issue.body || ""); + const hasDatabaseField = databaseField.length > 0; + const fieldIsGeneric = hasDatabaseField && isGenericDatabaseValue(databaseField); + const sourceText = hasDatabaseField ? databaseField : issue.title || ""; + const matchedDrivers = isGenericDatabaseValue(sourceText) + ? [] + : uniqueByDbType(matchDrivers(sourceText, { allowAmbiguous: hasDatabaseField })); + const titleMatchedDrivers = uniqueByDbType(matchDrivers(issue.title || "", { allowAmbiguous: false })); + if (hasDatabaseField && titleMatchedDrivers.length > 0) { + matchedDrivers.push(...titleMatchedDrivers); + } + if (hasDatabaseField && matchedDrivers.length === 0 && !fieldIsGeneric) { + matchedDrivers.push( + ...uniqueByDbType(matchDrivers(`${issue.title || ""}\n${databaseField}`, { allowAmbiguous: false })), + ); + } -const databaseField = extractDatabaseField(issue.body || ""); -const hasDatabaseField = databaseField.length > 0; -const fieldIsGeneric = hasDatabaseField && isGenericDatabaseValue(databaseField); -// Free-form bodies often mention comparison databases or examples; the title -// and issue-form database field are the reliable labeling sources. -const sourceText = hasDatabaseField ? databaseField : issue.title || ""; -const matchedDrivers = isGenericDatabaseValue(sourceText) - ? [] - : uniqueByDbType(matchDrivers(sourceText, { allowAmbiguous: hasDatabaseField })); -const titleMatchedDrivers = uniqueByDbType(matchDrivers(issue.title || "", { allowAmbiguous: false })); -if (hasDatabaseField && titleMatchedDrivers.length > 0) { - matchedDrivers.push(...titleMatchedDrivers); -} -if (hasDatabaseField && matchedDrivers.length === 0 && !fieldIsGeneric) { - // Some issue forms contain only a version in the database field and put the - // actual database name in the title; keep that fallback conservative. - matchedDrivers.push( - ...uniqueByDbType(matchDrivers(`${issue.title || ""}\n${databaseField}`, { allowAmbiguous: false })), + const uniqueMatchedDrivers = uniqueByDbType(matchedDrivers); + const targetLabels = uniqueMatchedDrivers.map((driver) => `${LABEL_PREFIX}${driver.dbType}`); + const targetLabelColors = Object.fromEntries( + uniqueMatchedDrivers.map((driver) => [`${LABEL_PREFIX}${driver.dbType}`, stablePaletteColor(driver.dbType)]), ); -} -const uniqueMatchedDrivers = uniqueByDbType(matchedDrivers); -const targetLabels = uniqueMatchedDrivers.map((driver) => `${LABEL_PREFIX}${driver.dbType}`); -const targetLabelColors = Object.fromEntries( - uniqueMatchedDrivers.map((driver) => [`${LABEL_PREFIX}${driver.dbType}`, stablePaletteColor(driver.dbType)]), -); -const priorityField = extractPriorityField(issue.body || ""); -const targetPriorityValue = matchPriorityLabel(priorityField); -const targetPriorityLabel = targetPriorityValue ? `${USER_PRIORITY_PREFIX}${targetPriorityValue}` : null; -if (targetPriorityValue) { - targetLabelColors[targetPriorityLabel] = USER_PRIORITY_LABELS[targetPriorityValue].color; -} -const existingLabels = labelNames(issue.labels); -const titleToUpdate = inferIssueTitle(issue.title || "", issue.body || ""); -const existingDatabaseLabels = existingLabels.filter((name) => name.startsWith(LABEL_PREFIX)); -const staleDatabaseLabels = hasDatabaseField - ? existingDatabaseLabels.filter((name) => !targetLabels.includes(name)) - : []; -const existingPriorityLabels = existingLabels.filter((name) => name.startsWith(USER_PRIORITY_PREFIX)); -const stalePriorityLabels = targetPriorityLabel - ? existingPriorityLabels.filter((name) => name !== targetPriorityLabel) - : []; -const labelsToAdd = [...targetLabels, targetPriorityLabel].filter(Boolean).filter((name) => !existingLabels.includes(name)); -const labelSpecs = [ - ...uniqueMatchedDrivers.map((driver) => ({ - name: `${LABEL_PREFIX}${driver.dbType}`, - description: `Database: ${driver.label}`, - color: stablePaletteColor(driver.dbType), - })), - ...(targetPriorityValue - ? [{ name: targetPriorityLabel, ...USER_PRIORITY_LABELS[targetPriorityValue] }] - : []), -]; + const priorityField = extractPriorityField(issue.body || ""); + const targetPriorityValue = matchPriorityLabel(priorityField); + const targetPriorityLabel = targetPriorityValue ? `${USER_PRIORITY_PREFIX}${targetPriorityValue}` : null; + if (targetPriorityValue) { + targetLabelColors[targetPriorityLabel] = USER_PRIORITY_LABELS[targetPriorityValue].color; + } -console.log( - JSON.stringify( - { - issue: issue.number, - databaseField: hasDatabaseField ? databaseField : null, - titleMatched: titleMatchedDrivers.map(({ dbType, label, matchedAlias }) => ({ dbType, label, matchedAlias })), - matched: uniqueMatchedDrivers.map(({ dbType, label, matchedAlias }) => ({ dbType, label, matchedAlias })), - priorityField: priorityField || null, - priorityLabel: targetPriorityLabel, - priorityValue: targetPriorityValue, - titleToUpdate, - labelsToAdd, - targetLabelColors, - staleDatabaseLabels, - stalePriorityLabels, - }, - null, - 2, - ), -); + const existingLabels = labelNames(issue.labels); + const titleToUpdate = inferIssueTitle(issue.title || "", issue.body || ""); + const existingDatabaseLabels = existingLabels.filter((name) => name.startsWith(LABEL_PREFIX)); + const staleDatabaseLabels = hasDatabaseField + ? existingDatabaseLabels.filter((name) => !targetLabels.includes(name)) + : []; + const existingPriorityLabels = existingLabels.filter((name) => name.startsWith(USER_PRIORITY_PREFIX)); + const stalePriorityLabels = targetPriorityLabel + ? existingPriorityLabels.filter((name) => name !== targetPriorityLabel) + : []; + const labelsToAdd = [...targetLabels, targetPriorityLabel] + .filter(Boolean) + .filter((name) => !existingLabels.includes(name)); + const labelSpecs = [ + ...uniqueMatchedDrivers.map(databaseLabelSpec), + ...(targetPriorityValue + ? [{ name: targetPriorityLabel, ...USER_PRIORITY_LABELS[targetPriorityValue] }] + : []), + ]; + const summary = { + issue: issue.number, + databaseField: hasDatabaseField ? databaseField : null, + titleMatched: titleMatchedDrivers.map(({ dbType, label, matchedAlias }) => ({ dbType, label, matchedAlias })), + matched: uniqueMatchedDrivers.map(({ dbType, label, matchedAlias }) => ({ dbType, label, matchedAlias })), + priorityField: priorityField || null, + priorityLabel: targetPriorityLabel, + priorityValue: targetPriorityValue, + titleToUpdate, + labelsToAdd, + targetLabelColors, + staleDatabaseLabels, + stalePriorityLabels, + }; -if (dryRun) { - console.log("Dry run enabled; no GitHub API calls were made"); - process.exit(0); + return { + issue, + summary, + labelSpecs, + labelsToAdd, + staleDatabaseLabels, + stalePriorityLabels, + titleToUpdate, + }; } -for (const labelSpec of labelSpecs) { - await ensureLabel(labelSpec.name, labelSpec.description, labelSpec.color); +async function runDatabaseLabelSync() { + const labelSpecs = allDatabaseLabelSpecs(); + console.log(JSON.stringify({ databaseLabels: labelSpecs.map((label) => label.name) }, null, 2)); + if (dryRun) { + console.log("Dry run enabled; no GitHub API calls were made"); + return; + } + + for (const labelSpec of labelSpecs) { + await ensureLabel(labelSpec, { updateExisting: true }); + } } -if (labelsToAdd.length > 0) { - await addLabels(issue.number, labelsToAdd); - console.log(`Added labels: ${labelsToAdd.join(", ")}`); -} else { - console.log("No database labels to add"); +async function runIssueTriage(issue) { + const result = triageIssue(issue); + console.log(JSON.stringify(result.summary, null, 2)); + if (dryRun) { + console.log("Dry run enabled; no GitHub API calls were made"); + return; + } + + for (const labelSpec of result.labelSpecs) { + await ensureLabel(labelSpec); + } + + if (result.labelsToAdd.length > 0) { + await addLabels(issue.number, result.labelsToAdd); + console.log(`Added labels: ${result.labelsToAdd.join(", ")}`); + } else { + console.log("No database labels to add"); + } + + for (const label of result.staleDatabaseLabels) { + await removeLabel(issue.number, label); + } + + for (const label of result.stalePriorityLabels) { + await removeLabel(issue.number, label); + } + + if (result.titleToUpdate) { + await updateIssueTitle(issue.number, result.titleToUpdate); + console.log(`Updated title: ${result.titleToUpdate}`); + } } -for (const label of staleDatabaseLabels) { - await removeLabel(issue.number, label); +async function main() { + if (syncDatabaseLabels) { + await runDatabaseLabelSync(); + return; + } + + const issue = loadIssue(); + if (issue.pull_request) { + console.log("Skipping pull request event"); + return; + } + + await runIssueTriage(issue); } -for (const label of stalePriorityLabels) { - await removeLabel(issue.number, label); -} - -if (titleToUpdate) { - await updateIssueTitle(issue.number, titleToUpdate); - console.log(`Updated title: ${titleToUpdate}`); +if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) { + await main(); } diff --git a/.github/scripts/label-issue-database.test.mjs b/.github/scripts/label-issue-database.test.mjs new file mode 100644 index 0000000000..74f48a2d16 --- /dev/null +++ b/.github/scripts/label-issue-database.test.mjs @@ -0,0 +1,69 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import test from "node:test"; + +import { databaseIssueDrivers } from "./database-issue-catalog.mjs"; +import { allDatabaseLabelSpecs, triageIssue } from "./label-issue-database.mjs"; + +function databaseTypeNames() { + const source = fs.readFileSync(new URL("../../apps/desktop/src/types/database.ts", import.meta.url), "utf8"); + const declaration = source.match(/export type DatabaseType\s*=([\s\S]*?);/u)?.[1] || ""; + return [...declaration.matchAll(/"([^"]+)"/gu)].map((match) => match[1]); +} + +function issueWithDatabase(database, title = "[Bug]") { + return { + number: 1, + title, + body: `### 数据库类型和版本\n\n${database}\n`, + labels: [], + }; +} + +function matchedLabels(database, title) { + return triageIssue(issueWithDatabase(database, title)).summary.labelsToAdd + .filter((label) => label.startsWith("db/")) + .sort(); +} + +test("database issue catalog covers every DatabaseType", () => { + const catalogTypes = new Set(databaseIssueDrivers.map((driver) => driver.dbType)); + const missingTypes = databaseTypeNames().filter((dbType) => !catalogTypes.has(dbType)); + assert.deepEqual(missingTypes, []); +}); + +test("database label synchronization includes every catalog entry once", () => { + const labelNames = allDatabaseLabelSpecs().map((label) => label.name); + assert.equal(new Set(labelNames).size, databaseIssueDrivers.length); + assert.deepEqual( + labelNames.sort(), + databaseIssueDrivers.map((driver) => `db/${driver.dbType}`).sort(), + ); +}); + +test("labels native and compatibility database products with their families", () => { + const cases = [ + ["Turso", ["db/turso"]], + ["Nacos 2.0.1", ["db/nacos"]], + ["r-nacos", ["db/nacos"]], + ["Apache Cloudberry", ["db/cloudberry", "db/postgres"]], + ["TiDB v8.5", ["db/mysql", "db/tidb"]], + ["OceanBase", ["db/oceanbase"]], + ["OceanBase MySQL Mode", ["db/mysql", "db/oceanbase"]], + ["OceanBase Oracle Mode", ["db/oceanbase", "db/oceanbase-oracle", "db/oracle"]], + ["TDSQL", ["db/tdsql"]], + ["PolarDB-X 2.0 MySQL", ["db/mysql", "db/polardb"]], + ["SelectDB", ["db/doris", "db/selectdb"]], + ["Dremio", ["db/dremio", "db/jdbc"]], + ["Apache Kafka", ["db/kafka", "db/mq"]], + ["RabbitMQ", ["db/mq", "db/rabbitmq"]], + ]; + + for (const [database, expected] of cases) { + assert.deepEqual(matchedLabels(database), expected, database); + } +}); + +test("generic OceanBase reports no longer default to Oracle mode", () => { + assert.equal(matchedLabels("OceanBase").includes("db/oceanbase-oracle"), false); +}); diff --git a/.github/scripts/suggest-similar-issues.mjs b/.github/scripts/suggest-similar-issues.mjs index 587662c616..1415ddc8b9 100644 --- a/.github/scripts/suggest-similar-issues.mjs +++ b/.github/scripts/suggest-similar-issues.mjs @@ -2,6 +2,8 @@ import fs from "node:fs"; import { pathToFileURL } from "node:url"; +import { databaseIssueDrivers } from "./database-issue-catalog.mjs"; + const API_VERSION = "2022-11-28"; const COMMENT_MARKER = ""; const MAX_QUERY_LENGTH = 480; @@ -85,26 +87,7 @@ const identifierQueryNoiseTokens = new Set([ ]); const cjkSegmenter = new Intl.Segmenter("zh-CN", { granularity: "word" }); -const driverManifest = JSON.parse( - fs.readFileSync(new URL("../../crates/dbx-core/assets/database-drivers.manifest.json", import.meta.url), "utf8"), -); -const extraDatabaseAliases = { - mysql: ["mariadb", "percona", "tidb"], - postgres: ["postgresql", "pgsql", "hologres"], - sqlserver: ["sql server", "mssql"], - mongodb: ["mongo"], - dameng: ["dm8", "达梦"], - kingbase: ["kingbasees", "人大金仓", "金仓"], - highgo: ["瀚高"], - yashandb: ["崖山"], - opengauss: ["open gauss"], - "oceanbase-oracle": ["oceanbase oracle"], - gbase: ["gbase8a", "gbase8s"], -}; -const databaseDrivers = driverManifest.drivers.map((driver) => ({ - dbType: driver.dbType, - aliases: [driver.dbType, driver.label, ...(extraDatabaseAliases[driver.dbType] || [])], -})); +const databaseDrivers = databaseIssueDrivers; function loadIssue() { if (process.env.GITHUB_EVENT_PATH && fs.existsSync(process.env.GITHUB_EVENT_PATH)) { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40ccb99cb8..1b7c59afd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,21 @@ jobs: - name: Frontend check run: pnpm check + github-scripts: + needs: changes + if: needs.changes.outputs.github_scripts == 'true' + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22.13.0 + + - name: GitHub script tests + run: node --test .github/scripts/*.test.mjs + packages: needs: changes if: needs.changes.outputs.packages == 'true' @@ -357,6 +372,7 @@ jobs: agents: ${{ steps.filter.outputs.agents }} nix: ${{ steps.filter.outputs.nix }} windows_win7_bundle: ${{ steps.filter.outputs.windows_win7_bundle }} + github_scripts: ${{ steps.filter.outputs.github_scripts }} steps: - uses: actions/checkout@v5 with: @@ -430,6 +446,11 @@ jobs: - 'Cargo.toml' - 'Cargo.lock' - 'vendor/ctor/**' + github_scripts: + - '.github/scripts/**' + - '.github/workflows/ci.yml' + - 'apps/desktop/src/types/database.ts' + - 'crates/dbx-core/assets/database-drivers.manifest.json' - name: Select Rust feature coverage id: rust-mode diff --git a/.github/workflows/issue-database-label.yml b/.github/workflows/issue-database-label.yml index df17320ae9..a7a65a13b5 100644 --- a/.github/workflows/issue-database-label.yml +++ b/.github/workflows/issue-database-label.yml @@ -3,9 +3,18 @@ name: Issue Triage Labels on: issues: types: [opened, edited, reopened] + push: + branches: [main] + paths: + - .github/scripts/database-issue-catalog.mjs + - .github/scripts/label-issue-database.mjs + - .github/workflows/issue-database-label.yml + - apps/desktop/src/types/database.ts + - crates/dbx-core/assets/database-drivers.manifest.json + workflow_dispatch: concurrency: - group: issue-database-label-${{ github.event.issue.number }} + group: issue-database-label-${{ github.event_name }}-${{ github.event.issue.number || github.ref }} cancel-in-progress: true permissions: @@ -15,7 +24,7 @@ permissions: jobs: label: runs-on: ubuntu-latest - if: ${{ !github.event.issue.pull_request }} + if: ${{ github.event_name == 'issues' && !github.event.issue.pull_request }} steps: - uses: actions/checkout@v5 @@ -28,3 +37,20 @@ jobs: run: node .github/scripts/label-issue-database.mjs env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + sync: + runs-on: ubuntu-latest + if: ${{ github.event_name != 'issues' }} + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22.13.0 + + - name: Synchronize database labels + run: node .github/scripts/label-issue-database.mjs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SYNC_DATABASE_LABELS: "1"