From 0ce5246b6dc31b5f24295429c7c25125de177a95 Mon Sep 17 00:00:00 2001 From: Davide Leopardi Date: Fri, 30 Oct 2026 20:05:55 +0100 Subject: [PATCH 1/7] Add Amazon Athena SSO read-only connector --- README.it.md | 4 +- README.md | 4 +- docs/DATABASES.md | 3 + electron/database.cjs | 20 ++- electron/drivers/athena.cjs | 336 ++++++++++++++++++++++++++++++++++++ locales/de.json | 20 ++- locales/en.json | 20 ++- locales/es.json | 20 ++- locales/fr.json | 20 ++- locales/it.json | 20 ++- package-lock.json | 240 ++++++++++++++------------ package.json | 31 ++-- scripts/platform-smoke.cjs | 4 +- src/App.tsx | 7 +- src/Settings.tsx | 44 +++++ src/styles.css | 1 + tests/athena.test.cjs | 271 +++++++++++++++++++++++++++++ tests/database.test.cjs | 4 +- 18 files changed, 929 insertions(+), 140 deletions(-) create mode 100644 electron/drivers/athena.cjs create mode 100644 tests/athena.test.cjs diff --git a/README.it.md b/README.it.md index 5799dc9..5b68568 100644 --- a/README.it.md +++ b/README.it.md @@ -25,11 +25,11 @@ Il selettore di lingua offre **inglese, italiano, francese, tedesco e spagnolo** ## Database -13 profili: **SQLite, PostgreSQL, Aurora PostgreSQL, CockroachDB, Redshift, MySQL, MariaDB, Aurora MySQL, Databricks SQL, SQL Server, ClickHouse, MongoDB e Redis**. Gli adapter usano driver reali; Aurora usa il protocollo del proprio engine. Databricks, Redshift e ClickHouse sono in lettura. MongoDB e Redis usano operazioni JSON consentite, con scritture revisionate e senza rollback comune. +14 profili: **SQLite, Amazon Athena, PostgreSQL, Aurora PostgreSQL, CockroachDB, Redshift, MySQL, MariaDB, Aurora MySQL, Databricks SQL, SQL Server, ClickHouse, MongoDB e Redis**. Gli adapter usano driver reali; Aurora usa il protocollo del proprio engine. Athena usa il profilo AWS locale, un ruolo IAM facoltativo e un workgroup. Athena, Databricks, Redshift e ClickHouse sono in lettura. MongoDB e Redis usano operazioni JSON consentite, con scritture revisionate e senza rollback comune. SQLite, PostgreSQL, MySQL, MongoDB, Redis e ClickHouse hanno prove su fixture locali reali. Account cloud Databricks/Aurora/Redshift e server CockroachDB, MariaDB e SQL Server richiedono una verifica specifica. Consulta [capacità e limiti](docs/DATABASES.md) e [validazione](docs/VALIDATION.md). -Le nuove connessioni sono in lettura. TLS verifica i certificati remoti quando attivo; i ruoli nel database restano l'autorità sul server. Questa versione non implementa tunnel SSH, IAM/OAuth dei database, Oracle, Snowflake, BigQuery, Cassandra, DuckDB, Elasticsearch, DDL, backup/restore o importazioni massive. +Le nuove connessioni sono in lettura. Prima di usare Athena completa il login AWS SSO per il profilo scelto. TLS verifica i certificati remoti quando attivo; i ruoli nel database restano l'autorità sul server. Questa versione non implementa tunnel SSH, IAM/OAuth per gli altri database, Oracle, Snowflake, BigQuery, Cassandra, DuckDB, Elasticsearch, DDL, backup/restore o importazioni massive. ## Assistente diff --git a/README.md b/README.md index 87a9828..7680669 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,12 @@ Open **Local demo** to use a real SQLite database with 120 customers, 36 product | PostgreSQL | PostgreSQL, Aurora PostgreSQL, CockroachDB | Reviewed transactional writes | | MySQL | MySQL, MariaDB, Aurora MySQL | Reviewed transactional writes; InnoDB required | | SQL Server | SQL Server | Reviewed transactional writes | -| Analytics | Databricks SQL, Redshift, ClickHouse | Read-only | +| Analytics | Amazon Athena, Databricks SQL, Redshift, ClickHouse | Read-only | | Document / key-value | MongoDB, Redis | Reviewed allowlisted JSON operations; no shared rollback | Adapters use real client libraries. Aurora profiles use their engine's protocol. The catalog exposes each adapter's capabilities; it does not imply live cloud certification. Local fixture checks cover SQLite, PostgreSQL, MySQL, MongoDB, Redis and ClickHouse. Live Databricks/Aurora/Redshift accounts, CockroachDB, MariaDB and SQL Server need separate acceptance tests. See [database capabilities](docs/DATABASES.md) and [validation](docs/VALIDATION.md). -New connections start read-only. TLS verifies remote certificates when enabled. Database principals remain the server-side authority. SSH tunnels, database IAM/OAuth, Oracle, Snowflake, BigQuery, Cassandra, DuckDB, Elasticsearch, DDL, backup/restore and bulk import are not implemented in this release. +New connections start read-only. Athena uses a local AWS profile, an optional assumed role, and a selected workgroup; complete AWS SSO login before connecting. TLS verifies remote certificates when enabled. Database principals remain the server-side authority. SSH tunnels, database IAM/OAuth for other engines, Oracle, Snowflake, BigQuery, Cassandra, DuckDB, Elasticsearch, DDL, backup/restore and bulk import are not implemented in this release. ## Assistant diff --git a/docs/DATABASES.md b/docs/DATABASES.md index f8d80e9..f26c369 100644 --- a/docs/DATABASES.md +++ b/docs/DATABASES.md @@ -3,6 +3,7 @@ | Profilo | Driver reale | Query | Scritture | Verifica | | ----------------- | -------------------------------- | -------------------------- | --------------------------------------------- | -------------------------------------------------------- | | SQLite | `node:sqlite`, processo dedicato | SQL | INSERT, UPDATE, DELETE con anteprima e commit | File reale, servizio e app desktop | +| Amazon Athena | `@aws-sdk/client-athena` | SQL, AWS SSO e workgroup | Disabilitate | SDK e account AWS da verificare | | PostgreSQL | `pg` | SQL, transazione READ ONLY | Proposta non eseguita, transazione al commit | Server Docker isolato | | Aurora PostgreSQL | `pg` | SQL | Come PostgreSQL | Contratto; cluster AWS non verificato | | CockroachDB | `pg` | SQL | Come PostgreSQL | Contratto; server non verificato | @@ -64,3 +65,5 @@ MongoDB e Redis mostrano una stima o lo stato attuale. Le scritture non hanno ro I profili nuovi sono in lettura. TLS verifica i certificati remoti; per i server locali di test puoi configurare esplicitamente una connessione senza TLS. Non vengono modificati i ruoli nel database. SQL Server non offre una transazione READ ONLY: usa un principal di sola lettura per applicare la policy anche sul server. Databricks richiede un warehouse, host, HTTP path e token. Aurora usa host/porta/database/utente del cluster e TLS; IAM e tunnel SSH non sono implementati. I provider LLM e i database hanno vault namespace separati. + +Athena usa il profilo AWS locale, la regione e un workgroup. Puoi indicare un ARN di ruolo IAM: Tableline lo assume con le credenziali del profilo e rinnova automaticamente le credenziali temporanee. Per i profili SSO devi prima completare il login con `aws sso login --profile `; quando scade, ripeti il login. Il database predefinito è facoltativo: se lo lasci vuoto, lo schema elenca i database visibili al ruolo. I permessi AWS, il limite di scansione e la cifratura dei risultati devono essere configurati nel workgroup e nel ruolo. Tableline esegue solo letture e non salva copie delle tabelle; esportazioni, cronologia SQL e richieste esplicite all'assistente possono comunque contenere dati. diff --git a/electron/database.cjs b/electron/database.cjs index 58e73b7..34845ce 100644 --- a/electron/database.cjs +++ b/electron/database.cjs @@ -8,6 +8,7 @@ const crypto = require("node:crypto"); const { performance } = require("node:perf_hooks"); const { guardSql, quoteIdentifier } = require("./sql-guard.cjs"); const { SQLiteDriver } = require("./drivers/sqlite.cjs"); +const { AthenaDriver } = require("./drivers/athena.cjs"); const { PostgresDriver, MySQLDriver, @@ -63,6 +64,22 @@ const NETWORK_FIELDS = [ ]; const CATALOG = [ descriptor("sqlite", "SQLite", "sqlite", null, ["filePath"]), + descriptor( + "athena", + "Amazon Athena", + "athena", + null, + ["awsProfile", "region", "roleArn", "workgroup", "database"], + { + write: false, + guardedWrites: false, + transactions: false, + transactional: false, + cancel: true, + writePreview: "unsupported", + parameters: "?", + }, + ), descriptor("postgres", "PostgreSQL", "postgres", 5432, NETWORK_FIELDS), descriptor( "aurora-postgresql", @@ -154,6 +171,7 @@ const CATALOG = [ ]; const DRIVER_TYPES = { sqlite: SQLiteDriver, + athena: AthenaDriver, postgres: PostgresDriver, mysql: MySQLDriver, sqlserver: SQLServerDriver, @@ -163,7 +181,7 @@ const DRIVER_TYPES = { redis: RedisDriver, }; const PROFILE_KEYS = new Set( - "id name driver host port database username filePath httpPath catalog schema ssl tls sslCA color readOnly".split( + "id name driver host port database username filePath httpPath catalog schema ssl tls sslCA color readOnly awsProfile region roleArn workgroup".split( " ", ), ); diff --git a/electron/drivers/athena.cjs b/electron/drivers/athena.cjs new file mode 100644 index 0000000..4d781fe --- /dev/null +++ b/electron/drivers/athena.cjs @@ -0,0 +1,336 @@ +// Copyright (c) 2026 Davide Leopardi +// SPDX-License-Identifier: GPL-3.0-only + +"use strict"; + +const { guardSql, quoteIdentifier } = require("../sql-guard.cjs"); +const { boundedSql } = require("./sql.cjs"); + +const CATALOG = "AwsDataCatalog"; +const QUERY_TIMEOUT_MS = 90000; +const RESULT_BYTES_LIMIT = 10 * 1024 * 1024; + +function executionParameters(params) { + if (!Array.isArray(params)) + throw new Error("Athena uses positional parameters (?)."); + return params.map((value) => { + if (value === null) return "NULL"; + if (typeof value === "boolean") return value ? "TRUE" : "FALSE"; + if (typeof value === "number" && Number.isFinite(value)) + return String(value); + if (typeof value === "string" && !value.includes("\0")) { + const quoted = "'" + value.replaceAll("'", "''") + "'"; + if (quoted.length <= 1024) return quoted; + } + throw new Error("Athena parameters must be short scalar values."); + }); +} + +class AthenaDriver { + constructor(profile, credentials = {}, sdk) { + this.profile = profile; + this.credentials = credentials; + this.sdk = sdk; + this.dialect = "athena"; + this.transactional = false; + this.active = new Map(); + this.inFlight = new Set(); + this.cachedSchema = null; + this.timeoutMs = QUERY_TIMEOUT_MS; + } + + async connect() { + const { awsProfile, region, roleArn, workgroup } = this.profile; + if (!awsProfile || !region || !workgroup) + throw new Error("Athena requires an AWS profile, region, and workgroup."); + const sdk = this.sdk || { + ...require("@aws-sdk/client-athena"), + ...require("@aws-sdk/credential-providers"), + }; + const masterCredentials = sdk.fromIni({ + profile: awsProfile, + clientConfig: { region }, + }); + const credentials = roleArn + ? sdk.fromTemporaryCredentials({ + masterCredentials, + clientConfig: { region }, + params: { RoleArn: roleArn, RoleSessionName: "tableline-athena" }, + }) + : masterCredentials; + this.sdk = sdk; + this.client = new sdk.AthenaClient({ + region, + credentials, + maxAttempts: 2, + logger: { trace() {}, debug() {}, info() {}, warn() {}, error() {} }, + }); + try { + const result = await this.client.send( + new sdk.GetWorkGroupCommand({ WorkGroup: workgroup }), + ); + if (result.WorkGroup?.State === "DISABLED") + throw new Error("Athena workgroup is disabled."); + } catch (error) { + this.client.destroy(); + this.client = null; + throw error; + } + } + + async schema() { + if (!this.client) throw new Error("Athena is not connected."); + const { ListDatabasesCommand, ListTableMetadataCommand } = this.sdk; + const workgroup = this.profile.workgroup; + const databases = []; + if (this.profile.database) databases.push(this.profile.database); + else { + let next; + do { + const result = await this.client.send( + new ListDatabasesCommand({ + CatalogName: CATALOG, + WorkGroup: workgroup, + MaxResults: 50, + ...(next ? { NextToken: next } : {}), + }), + ); + databases.push(...(result.DatabaseList || []).map((db) => db.Name)); + if (databases.length > 100) + throw new Error("Athena schema has too many databases."); + next = result.NextToken; + } while (next); + } + const tables = []; + for (const database of databases) { + let next; + do { + let result; + try { + result = await this.client.send( + new ListTableMetadataCommand({ + CatalogName: CATALOG, + DatabaseName: database, + WorkGroup: workgroup, + MaxResults: 50, + ...(next ? { NextToken: next } : {}), + }), + ); + } catch (error) { + // An unrestricted database list can include databases outside a + // narrow read role. A specifically selected database must still fail. + if (!this.profile.database && /accessdenied/i.test(error.name || "")) + break; + throw error; + } + for (const table of result.TableMetadataList || []) { + const columns = []; + for (const column of [ + ...(table.Columns || []), + ...(table.PartitionKeys || []), + ]) { + if (!columns.some((current) => current.name === column.Name)) + columns.push({ + name: column.Name, + type: column.Type || "string", + nullable: true, + primaryKey: false, + }); + } + tables.push({ + name: table.Name, + schema: database, + kind: /view/i.test(table.TableType || "") ? "view" : "table", + columns, + }); + } + if (tables.length > 5000) + throw new Error("Athena schema has too many tables."); + next = result.NextToken; + } while (next); + } + this.cachedSchema = tables; + return tables; + } + + async read(sql, params = [], limit = 500, options = {}) { + const statement = guardSql(sql, "read", "athena").sql; + return this.executeRead(statement, params, limit, options); + } + + async executeRead(sql, params = [], limit = 500, { requestId, alreadyBound = false } = {}) { + if (!this.client) throw new Error("Athena is not connected."); + if (!Number.isInteger(limit) || limit < 1 || limit > 10000) + throw new Error("Athena result limit must be between 1 and 10000."); + const bound = executionParameters(params); + const sdk = this.sdk; + const response = await this.client.send( + new sdk.StartQueryExecutionCommand({ + QueryString: alreadyBound ? sql : boundedSql(sql, "athena", limit), + WorkGroup: this.profile.workgroup, + QueryExecutionContext: { + Catalog: CATALOG, + Database: this.profile.database || "default", + }, + ...(bound.length ? { ExecutionParameters: bound } : {}), + }), + ); + const id = response.QueryExecutionId; + if (!id) throw new Error("Athena did not return a query ID."); + this.inFlight.add(id); + if (requestId) this.active.set(requestId, id); + const deadline = Date.now() + this.timeoutMs; + try { + let delayMs = 300; + while (true) { + const result = await this.client.send( + new sdk.GetQueryExecutionCommand({ QueryExecutionId: id }), + ); + const state = result.QueryExecution?.Status?.State; + if (state === "SUCCEEDED") break; + if (state === "FAILED" || state === "CANCELLED") + throw new Error( + `Athena query ${state.toLowerCase()}: ${result.QueryExecution?.Status?.StateChangeReason || "unknown reason"}`, + ); + if (Date.now() >= deadline) { + const stopped = await this.stop(id); + throw new Error(stopped + ? "Athena query timed out and was stopped." + : "Athena query timed out; it could not be stopped."); + } + await new Promise((resolve) => setTimeout(resolve, delayMs)); + delayMs = Math.min(Math.round(delayMs * 1.5), 2000); + } + return await this.results(id, limit + 1); + } finally { + this.inFlight.delete(id); + if (requestId && this.active.get(requestId) === id) + this.active.delete(requestId); + } + } + + async results(id, maxRows) { + const columns = []; + const rows = []; + let next; + let firstPage = true; + let bytes = 0; + do { + const result = await this.client.send( + new this.sdk.GetQueryResultsCommand({ + QueryExecutionId: id, + MaxResults: Math.min(1000, maxRows - rows.length + (firstPage ? 1 : 0)), + ...(next ? { NextToken: next } : {}), + }), + ); + if (firstPage) { + for (const column of result.ResultSet?.ResultSetMetadata?.ColumnInfo || []) + columns.push({ name: column.Name, type: column.Type }); + } + const pageRows = result.ResultSet?.Rows || []; + for (const row of firstPage && columns.length ? pageRows.slice(1) : pageRows) { + const value = {}; + columns.forEach((column, index) => { + value[column.name] = row.Data?.[index]?.VarCharValue ?? null; + }); + bytes += Buffer.byteLength(JSON.stringify(value), "utf8"); + if (bytes > RESULT_BYTES_LIMIT) + throw new Error("Result exceeds 10 MB. Select fewer columns or rows."); + rows.push(value); + if (rows.length >= maxRows) break; + } + firstPage = false; + next = result.NextToken; + } while (next && rows.length < maxRows); + return { columns, rows }; + } + + async browse({ + table, + schema, + search = "", + sortColumn, + sortDirection = "asc", + offset = 0, + limit = 100, + } = {}) { + if (!Number.isInteger(offset) || offset < 0 || offset > 1000000) + throw new Error("Offset must be between 0 and 1000000."); + if (!Number.isInteger(limit) || limit < 1 || limit > 1000) + throw new Error("Limit must be between 1 and 1000."); + if (typeof search !== "string" || search.length > 500) + throw new Error("Search must be text of up to 500 characters."); + if (!["asc", "desc"].includes(sortDirection)) + throw new Error("Choose ascending or descending sort."); + const tables = this.cachedSchema || (await this.schema()); + const selected = tables.find( + (candidate) => candidate.name === table && (!schema || candidate.schema === schema), + ); + if (!selected) throw new Error("Table not found. Refresh the schema."); + if (sortColumn && !selected.columns.some((column) => column.name === sortColumn)) + throw new Error("Sort column not found."); + const q = (name) => quoteIdentifier(name, "athena"); + const fullName = `${q(selected.schema)}.${q(selected.name)}`; + const searchable = selected.columns + .filter((column) => /^(?:varchar|char|string|text)\b/i.test(column.type)) + .slice(0, 20); + if (search && !searchable.length) + return { + columns: selected.columns.map(({ name, type }) => ({ name, type })), + rows: [], + }; + const where = search + ? " WHERE " + searchable.map((column) => + `strpos(lower(${q(column.name)}), lower(?)) > 0`, + ).join(" OR ") + : ""; + const ordering = sortColumn + ? ` ORDER BY ${q(sortColumn)} ${sortDirection.toUpperCase()}` + : ""; + const sql = `SELECT * FROM ${fullName}${where}${ordering} OFFSET ${offset} LIMIT ${limit + 1}`; + const raw = await this.executeRead( + sql, + search ? searchable.map(() => search) : [], + limit, + { alreadyBound: true }, + ); + raw.columns = raw.columns.length + ? raw.columns.map((column) => ({ + ...column, + type: selected.columns.find((item) => item.name === column.name)?.type || column.type, + })) + : selected.columns.map(({ name, type }) => ({ name, type })); + return raw; + } + + async stop(id) { + if (!this.client) return false; + try { + await this.client.send( + new this.sdk.StopQueryExecutionCommand({ QueryExecutionId: id }), + ); + return true; + } catch { + return false; + } + } + + async cancel(requestId) { + const id = this.active.get(requestId); + if (!id) return false; + return this.stop(id); + } + + async abortReads() { + await Promise.all([...this.inFlight].map((id) => this.stop(id))); + } + + async close() { + await this.abortReads(); + this.client?.destroy(); + this.client = null; + this.cachedSchema = null; + } +} + +module.exports = { AthenaDriver, executionParameters }; diff --git a/locales/de.json b/locales/de.json index 866a6d0..5a595f8 100644 --- a/locales/de.json +++ b/locales/de.json @@ -522,5 +522,23 @@ "La query non ha restituito righe. Verifica il criterio prima di concludere che i dati richiesti siano assenti.": "Die Abfrage hat keine Zeilen zurückgegeben. Prüfe den Filter, bevor du daraus schließt, dass die gesuchten Daten fehlen.", "This Databricks warehouse requires the SEA native kernel. Tableline supports Thrift SQL warehouses; choose a compatible warehouse.": "Dieses Databricks-Warehouse benötigt den nativen SEA-Kernel. Tableline unterstützt Thrift-SQL-Warehouses; wählen Sie ein kompatibles Warehouse.", "La cifratura credenziali di Linux non è sicura. Sblocca il portachiavi, riapri l’app e importa nuovamente le credenziali.": "Die Verschlüsselung der Zugangsdaten unter Linux ist nicht sicher. Entsperren Sie den Schlüsselbund, öffnen Sie die App erneut und importieren Sie die Zugangsdaten noch einmal.", - "Formato archivio credenziali non valido.": "Ungültiges Format des Zugangsdatenarchivs." + "Formato archivio credenziali non valido.": "Ungültiges Format des Zugangsdatenarchivs.", + "Workgroup Athena": "Athena-Arbeitsgruppe", + "ARN ruolo IAM (opzionale)": "IAM-Rollen-ARN (optional)", + "Database predefinito (opzionale)": "Standarddatenbank (optional)", + "Usa il login AWS SSO locale. Le credenziali temporanee si rinnovano automaticamente.": "Verwendet Ihre lokale AWS-SSO-Anmeldung. Temporäre Anmeldedaten werden automatisch erneuert.", + "Athena uses positional parameters (?).": "Athena verwendet positionsgebundene Parameter (?).", + "Athena parameters must be short scalar values.": "Athena-Parameter müssen kurze skalare Werte sein.", + "Athena requires an AWS profile, region, and workgroup.": "Athena erfordert ein AWS-Profil, eine Region und eine Arbeitsgruppe.", + "Athena workgroup is disabled.": "Die Athena-Arbeitsgruppe ist deaktiviert.", + "Athena is not connected.": "Athena ist nicht verbunden.", + "Athena schema has too many databases.": "Das Athena-Schema enthält zu viele Datenbanken.", + "Athena schema has too many tables.": "Das Athena-Schema enthält zu viele Tabellen.", + "Athena result limit must be between 1 and 10000.": "Das Athena-Ergebnislimit muss zwischen 1 und 10000 liegen.", + "Athena did not return a query ID.": "Athena hat keine Abfrage-ID zurückgegeben.", + "Athena query {state}: {reason}": "Athena-Abfrage {state}: {reason}", + "Athena query timed out and was stopped.": "Die Athena-Abfrage hat das Zeitlimit überschritten und wurde gestoppt.", + "Athena query timed out; it could not be stopped.": "Die Athena-Abfrage hat das Zeitlimit überschritten; sie konnte nicht gestoppt werden.", + "Choose ascending or descending sort.": "Wählen Sie aufsteigende oder absteigende Sortierung.", + "Limit must be between 1 and 1000.": "Das Limit muss zwischen 1 und 1000 liegen." } diff --git a/locales/en.json b/locales/en.json index 970a103..235cc03 100644 --- a/locales/en.json +++ b/locales/en.json @@ -522,5 +522,23 @@ "La query non ha restituito righe. Verifica il criterio prima di concludere che i dati richiesti siano assenti.": "The query returned no rows. Check the filter before concluding that the requested data is absent.", "This Databricks warehouse requires the SEA native kernel. Tableline supports Thrift SQL warehouses; choose a compatible warehouse.": "This Databricks warehouse requires the SEA native kernel. Tableline supports Thrift SQL warehouses; choose a compatible warehouse.", "La cifratura credenziali di Linux non è sicura. Sblocca il portachiavi, riapri l’app e importa nuovamente le credenziali.": "Linux credential encryption is not secure. Unlock the keyring, reopen the app and import the credentials again.", - "Formato archivio credenziali non valido.": "Invalid credential archive format." + "Formato archivio credenziali non valido.": "Invalid credential archive format.", + "Workgroup Athena": "Athena workgroup", + "ARN ruolo IAM (opzionale)": "IAM role ARN (optional)", + "Database predefinito (opzionale)": "Default database (optional)", + "Usa il login AWS SSO locale. Le credenziali temporanee si rinnovano automaticamente.": "Uses your local AWS SSO login. Temporary credentials refresh automatically.", + "Athena uses positional parameters (?).": "Athena uses positional parameters (?).", + "Athena parameters must be short scalar values.": "Athena parameters must be short scalar values.", + "Athena requires an AWS profile, region, and workgroup.": "Athena requires an AWS profile, region, and workgroup.", + "Athena workgroup is disabled.": "Athena workgroup is disabled.", + "Athena is not connected.": "Athena is not connected.", + "Athena schema has too many databases.": "Athena schema has too many databases.", + "Athena schema has too many tables.": "Athena schema has too many tables.", + "Athena result limit must be between 1 and 10000.": "Athena result limit must be between 1 and 10000.", + "Athena did not return a query ID.": "Athena did not return a query ID.", + "Athena query {state}: {reason}": "Athena query {state}: {reason}", + "Athena query timed out and was stopped.": "Athena query timed out and was stopped.", + "Athena query timed out; it could not be stopped.": "Athena query timed out; it could not be stopped.", + "Choose ascending or descending sort.": "Choose ascending or descending sort.", + "Limit must be between 1 and 1000.": "Limit must be between 1 and 1000." } diff --git a/locales/es.json b/locales/es.json index 9c73e35..54bf6f4 100644 --- a/locales/es.json +++ b/locales/es.json @@ -522,5 +522,23 @@ "La query non ha restituito righe. Verifica il criterio prima di concludere che i dati richiesti siano assenti.": "La consulta no devolvió filas. Revisa el criterio antes de concluir que los datos solicitados no existen.", "This Databricks warehouse requires the SEA native kernel. Tableline supports Thrift SQL warehouses; choose a compatible warehouse.": "Este almacén Databricks requiere el núcleo nativo SEA. Tableline admite almacenes SQL Thrift; elige un almacén compatible.", "La cifratura credenziali di Linux non è sicura. Sblocca il portachiavi, riapri l’app e importa nuovamente le credenziali.": "El cifrado de credenciales de Linux no es seguro. Desbloquea el llavero, vuelve a abrir la aplicación e importa las credenciales de nuevo.", - "Formato archivio credenziali non valido.": "Formato de archivo de credenciales no válido." + "Formato archivio credenziali non valido.": "Formato de archivo de credenciales no válido.", + "Workgroup Athena": "Grupo de trabajo de Athena", + "ARN ruolo IAM (opzionale)": "ARN del rol IAM (opcional)", + "Database predefinito (opzionale)": "Base de datos predeterminada (opcional)", + "Usa il login AWS SSO locale. Le credenziali temporanee si rinnovano automaticamente.": "Usa el inicio de sesión AWS SSO local. Las credenciales temporales se renuevan automáticamente.", + "Athena uses positional parameters (?).": "Athena usa parámetros posicionales (?).", + "Athena parameters must be short scalar values.": "Los parámetros de Athena deben ser valores escalares cortos.", + "Athena requires an AWS profile, region, and workgroup.": "Athena requiere un perfil de AWS, una región y un grupo de trabajo.", + "Athena workgroup is disabled.": "El grupo de trabajo de Athena está deshabilitado.", + "Athena is not connected.": "Athena no está conectado.", + "Athena schema has too many databases.": "El esquema de Athena contiene demasiadas bases de datos.", + "Athena schema has too many tables.": "El esquema de Athena contiene demasiadas tablas.", + "Athena result limit must be between 1 and 10000.": "El límite de resultados de Athena debe estar entre 1 y 10000.", + "Athena did not return a query ID.": "Athena no devolvió un ID de consulta.", + "Athena query {state}: {reason}": "Consulta de Athena {state}: {reason}", + "Athena query timed out and was stopped.": "La consulta de Athena agotó el tiempo y se detuvo.", + "Athena query timed out; it could not be stopped.": "La consulta de Athena agotó el tiempo; no se pudo detener.", + "Choose ascending or descending sort.": "Elige orden ascendente o descendente.", + "Limit must be between 1 and 1000.": "El límite debe estar entre 1 y 1000." } diff --git a/locales/fr.json b/locales/fr.json index ca029de..3e4bbf2 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -522,5 +522,23 @@ "La query non ha restituito righe. Verifica il criterio prima di concludere che i dati richiesti siano assenti.": "La requête n’a renvoyé aucune ligne. Vérifiez le critère avant de conclure que les données demandées sont absentes.", "This Databricks warehouse requires the SEA native kernel. Tableline supports Thrift SQL warehouses; choose a compatible warehouse.": "Cet entrepôt Databricks nécessite le noyau natif SEA. Tableline prend en charge les entrepôts SQL Thrift ; choisissez un entrepôt compatible.", "La cifratura credenziali di Linux non è sicura. Sblocca il portachiavi, riapri l’app e importa nuovamente le credenziali.": "Le chiffrement des identifiants sous Linux n’est pas sûr. Déverrouillez le trousseau, rouvrez l’application et importez à nouveau les identifiants.", - "Formato archivio credenziali non valido.": "Format de l’archive des identifiants non valide." + "Formato archivio credenziali non valido.": "Format de l’archive des identifiants non valide.", + "Workgroup Athena": "Groupe de travail Athena", + "ARN ruolo IAM (opzionale)": "ARN du rôle IAM (facultatif)", + "Database predefinito (opzionale)": "Base de données par défaut (facultatif)", + "Usa il login AWS SSO locale. Le credenziali temporanee si rinnovano automaticamente.": "Utilise votre connexion AWS SSO locale. Les identifiants temporaires sont renouvelés automatiquement.", + "Athena uses positional parameters (?).": "Athena utilise des paramètres positionnels (?).", + "Athena parameters must be short scalar values.": "Les paramètres Athena doivent être de courtes valeurs scalaires.", + "Athena requires an AWS profile, region, and workgroup.": "Athena nécessite un profil AWS, une région et un groupe de travail.", + "Athena workgroup is disabled.": "Le groupe de travail Athena est désactivé.", + "Athena is not connected.": "Athena n’est pas connecté.", + "Athena schema has too many databases.": "Le schéma Athena contient trop de bases de données.", + "Athena schema has too many tables.": "Le schéma Athena contient trop de tables.", + "Athena result limit must be between 1 and 10000.": "La limite des résultats Athena doit être comprise entre 1 et 10000.", + "Athena did not return a query ID.": "Athena n’a pas renvoyé d’identifiant de requête.", + "Athena query {state}: {reason}": "Requête Athena {state} : {reason}", + "Athena query timed out and was stopped.": "La requête Athena a expiré et a été arrêtée.", + "Athena query timed out; it could not be stopped.": "La requête Athena a expiré ; impossible de l’arrêter.", + "Choose ascending or descending sort.": "Choisissez un tri croissant ou décroissant.", + "Limit must be between 1 and 1000.": "La limite doit être comprise entre 1 et 1000." } diff --git a/locales/it.json b/locales/it.json index 7da3e15..35952c7 100644 --- a/locales/it.json +++ b/locales/it.json @@ -522,5 +522,23 @@ "La query non ha restituito righe. Verifica il criterio prima di concludere che i dati richiesti siano assenti.": "La query non ha restituito righe. Verifica il criterio prima di concludere che i dati richiesti siano assenti.", "This Databricks warehouse requires the SEA native kernel. Tableline supports Thrift SQL warehouses; choose a compatible warehouse.": "Questo warehouse Databricks richiede il kernel nativo SEA. Tableline supporta i warehouse SQL Thrift; scegli un warehouse compatibile.", "La cifratura credenziali di Linux non è sicura. Sblocca il portachiavi, riapri l’app e importa nuovamente le credenziali.": "La cifratura credenziali di Linux non è sicura. Sblocca il portachiavi, riapri l’app e importa nuovamente le credenziali.", - "Formato archivio credenziali non valido.": "Formato archivio credenziali non valido." + "Formato archivio credenziali non valido.": "Formato archivio credenziali non valido.", + "Workgroup Athena": "Workgroup Athena", + "ARN ruolo IAM (opzionale)": "ARN ruolo IAM (opzionale)", + "Database predefinito (opzionale)": "Database predefinito (opzionale)", + "Usa il login AWS SSO locale. Le credenziali temporanee si rinnovano automaticamente.": "Usa il login AWS SSO locale. Le credenziali temporanee si rinnovano automaticamente.", + "Athena uses positional parameters (?).": "Athena usa parametri posizionali (?).", + "Athena parameters must be short scalar values.": "I parametri Athena devono essere valori scalari brevi.", + "Athena requires an AWS profile, region, and workgroup.": "Athena richiede un profilo AWS, una regione e un workgroup.", + "Athena workgroup is disabled.": "Il workgroup Athena è disabilitato.", + "Athena is not connected.": "Athena non è connesso.", + "Athena schema has too many databases.": "Lo schema Athena contiene troppi database.", + "Athena schema has too many tables.": "Lo schema Athena contiene troppe tabelle.", + "Athena result limit must be between 1 and 10000.": "Il limite dei risultati Athena deve essere tra 1 e 10000.", + "Athena did not return a query ID.": "Athena non ha restituito un ID query.", + "Athena query {state}: {reason}": "Query Athena {state}: {reason}", + "Athena query timed out and was stopped.": "La query Athena è scaduta ed è stata fermata.", + "Athena query timed out; it could not be stopped.": "La query Athena è scaduta; non è stato possibile fermarla.", + "Choose ascending or descending sort.": "Scegli l’ordinamento crescente o decrescente.", + "Limit must be between 1 and 1000.": "Il limite deve essere tra 1 e 1000." } diff --git a/package-lock.json b/package-lock.json index 0ce9be3..4f5940e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.2.2", "license": "GPL-3.0-only", "dependencies": { + "@aws-sdk/client-athena": "^3.1161.0", "@aws-sdk/client-bedrock": "^3.1145.0", "@aws-sdk/client-bedrock-runtime": "^3.1145.0", "@aws-sdk/credential-providers": "^3.1145.0", @@ -341,6 +342,25 @@ "node": ">= 10" } }, + "node_modules/@aws-sdk/client-athena": { + "version": "3.1161.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-athena/-/client-athena-3.1161.0.tgz", + "integrity": "sha512-cmqvur4c4xEXSI2KHLDfW6xn0dPWjZh/MDshyxlArBb9u5esSnEllarREZxKuN2qB6YvkDwRiIStppJO020bpg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/credential-provider-node": "^3.972.92", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.14.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-bedrock": { "version": "3.1146.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock/-/client-bedrock-3.1146.0.tgz", @@ -385,16 +405,16 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.978.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.1.tgz", - "integrity": "sha512-LbY9aGsEiznDWmUc30Nwv3aIX/+dbwTx8KfS0yOC3NPYMO+O91e6jkT1azf34FwjOndq8/Q+RcVVZz5xnerwdg==", + "version": "3.979.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.979.1.tgz", + "integrity": "sha512-ayOlV1VhLNMiq8PW9ZkVobG4gkldnUoHjG69yy2b8j+VYFlhz/uAL7ZdYQAoQ+eShtGUdNDnnG5rxDg6yUJrOg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.974.6", - "@aws-sdk/xml-builder": "^3.972.41", + "@aws-sdk/types": "^3.975.0", + "@aws-sdk/xml-builder": "^3.972.42", "@aws/lambda-invoke-store": "^0.3.0", - "@smithy/core": "^3.35.0", - "@smithy/signature-v4": "^5.7.3", + "@smithy/core": "^3.38.0", + "@smithy/signature-v4": "^5.7.5", "@smithy/types": "^4.19.0", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -420,14 +440,14 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.72", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.72.tgz", - "integrity": "sha512-xTKO/FWJPozTIXbozVnVGoNBhaGba8TBcx+KyUjRVeOlXE+dUc7GTR1cLvu0uTdIdmemzaFbqqCshXeZA1fZew==", + "version": "3.972.80", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.80.tgz", + "integrity": "sha512-uO5TiSvb6j1w9RTe5z28OaOmsrOxVQUPaC5+wZl50RfOXcrYD/KgnS8I9lfyGXc7sGbkCopK6VnhNIAoLqfewQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.1", - "@aws-sdk/types": "^3.974.6", - "@smithy/core": "^3.35.0", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -436,16 +456,16 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.74", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.74.tgz", - "integrity": "sha512-u91E/hT8f4d1xy0Jl7VG4nVKJ3lxbrZkoBTeSVoJdWBiSEUMwMS/9+e0H/aJVQV//Lt5wuzP+E69v4aRSsNTmw==", + "version": "3.972.82", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.82.tgz", + "integrity": "sha512-vQzlDHlC24Yz42p+rT4NBoP/5VdetkBoujbOLq9h6QYWC8THuZSPYnXIR9OP3XcOrdMgPBBipOrdssfxnIiWUg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.1", - "@aws-sdk/types": "^3.974.6", - "@smithy/core": "^3.35.0", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", "@smithy/fetch-http-handler": "^5.8.0", - "@smithy/node-http-handler": "^4.12.1", + "@smithy/node-http-handler": "^4.14.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -454,21 +474,21 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.973.17", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.17.tgz", - "integrity": "sha512-ged4KXdBkvIC81bLvNHHuQKdKak/VXhQTR1NWYTTqW0474nlmsxy9O/vlgTIohDDWH3xpBdtVMZRyjb+DnocDA==", + "version": "3.973.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.25.tgz", + "integrity": "sha512-b5xl71uCRdWsu7P1VeGpiGuCf6zfYETsSNOv8AmYVTrsgbvTJRXdmmOPVfxkvGGVNsiKkGctClQW3jAn0SZLug==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.1", - "@aws-sdk/credential-provider-env": "^3.972.72", - "@aws-sdk/credential-provider-http": "^3.972.74", - "@aws-sdk/credential-provider-login": "^3.972.79", - "@aws-sdk/credential-provider-process": "^3.972.72", - "@aws-sdk/credential-provider-sso": "^3.973.16", - "@aws-sdk/credential-provider-web-identity": "^3.972.78", - "@aws-sdk/nested-clients": "^3.997.46", - "@aws-sdk/types": "^3.974.6", - "@smithy/core": "^3.35.0", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/credential-provider-env": "^3.972.80", + "@aws-sdk/credential-provider-http": "^3.972.82", + "@aws-sdk/credential-provider-login": "^3.972.87", + "@aws-sdk/credential-provider-process": "^3.972.80", + "@aws-sdk/credential-provider-sso": "^3.973.24", + "@aws-sdk/credential-provider-web-identity": "^3.972.86", + "@aws-sdk/nested-clients": "^3.997.54", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", "@smithy/credential-provider-imds": "^4.5.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" @@ -478,15 +498,15 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.79", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.79.tgz", - "integrity": "sha512-L+Z85anONJd8MaiuraO4wRxATCdEejBZ3K3eymzWI5JPXa9sOS9CkIm72PBKqXKX+Z9p9NGMX5AIMXm0LEflgw==", + "version": "3.972.87", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.87.tgz", + "integrity": "sha512-C4ZFmcwM+Zj14Z8huVUk54Gv4uzT1ZdzG9cY3Gdh8FePakq7Bss1OFsgZjAhWFOkFOGpBvFBeu0OWjWf8HNKRQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.1", - "@aws-sdk/nested-clients": "^3.997.46", - "@aws-sdk/types": "^3.974.6", - "@smithy/core": "^3.35.0", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -495,19 +515,19 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.84", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.84.tgz", - "integrity": "sha512-oHt854odINVwzwsh+c5x69j0ajm4DbqqqVJ+O1ECsCIZeMDAbzFpXItaqP7UZstJj/ATdTk/KFSH0LaNAgV+kA==", + "version": "3.972.92", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.92.tgz", + "integrity": "sha512-jC3zmOo7Qxlk+oBivvSsS1Ph7sFGm0QfuhqDGbqgRXi4cxUYMG+dWtBwS2XYYXgc8TIVXJiwgQ65sbNpbYupTA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.72", - "@aws-sdk/credential-provider-http": "^3.972.74", - "@aws-sdk/credential-provider-ini": "^3.973.17", - "@aws-sdk/credential-provider-process": "^3.972.72", - "@aws-sdk/credential-provider-sso": "^3.973.16", - "@aws-sdk/credential-provider-web-identity": "^3.972.78", - "@aws-sdk/types": "^3.974.6", - "@smithy/core": "^3.35.0", + "@aws-sdk/credential-provider-env": "^3.972.80", + "@aws-sdk/credential-provider-http": "^3.972.82", + "@aws-sdk/credential-provider-ini": "^3.973.25", + "@aws-sdk/credential-provider-process": "^3.972.80", + "@aws-sdk/credential-provider-sso": "^3.973.24", + "@aws-sdk/credential-provider-web-identity": "^3.972.86", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", "@smithy/credential-provider-imds": "^4.5.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" @@ -517,14 +537,14 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.72", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.72.tgz", - "integrity": "sha512-rLIp2xbMjX/k9/od7APpqq1ZgXXnV0pOL1Th3ZsL8Wu0TRtBsDTVS8iPqcfRFcHakFxPvR04OSTv2ka2qOb/2A==", + "version": "3.972.80", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.80.tgz", + "integrity": "sha512-xqfixO5aclje7tbQJDias+iNND5wp/3QZixCdZcVQpgWPTZ0V5DfTW9A804A5Z7ezu7sH3WRO+vzUjtiJBA8yg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.1", - "@aws-sdk/types": "^3.974.6", - "@smithy/core": "^3.35.0", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -533,16 +553,16 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.973.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.16.tgz", - "integrity": "sha512-IGihaJfFZYacJJr/odqILCoK7W/mvrZ7cuK7ECn3sAu4vLC6u0V8bS7mCGbdugJ8Aum2tnvqmx0F2MRFp2rn9g==", + "version": "3.973.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.24.tgz", + "integrity": "sha512-nlg3BKG03Cgzvv/dMnhxvDJPhAhQSpaN0RUdwgU7ziwkasoev7iI8el8Xy4Rd5sqzgaDUbV269fOXck5SargqA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.1", - "@aws-sdk/nested-clients": "^3.997.46", - "@aws-sdk/token-providers": "3.1138.0", - "@aws-sdk/types": "^3.974.6", - "@smithy/core": "^3.35.0", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", + "@aws-sdk/token-providers": "3.1161.0", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -551,15 +571,15 @@ } }, "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.1138.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1138.0.tgz", - "integrity": "sha512-GpyAr0DD63YOEmYFM6Df+gJuIgC92MMTiBK4FTKfxii5MJ9ge20epR7LyroulscYlG89J+ZB2ivFDPjvfQhzdw==", + "version": "3.1161.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1161.0.tgz", + "integrity": "sha512-ypbXSofNcSHwf/KA40bFCXJb0MLaAKrfT7sot4dFaA+2lsuXNah4zLRJIAU7Qmvs9PFJ6rnY0bZFEC+mE7attA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.1", - "@aws-sdk/nested-clients": "^3.997.46", - "@aws-sdk/types": "^3.974.6", - "@smithy/core": "^3.35.0", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -568,15 +588,15 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.78", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.78.tgz", - "integrity": "sha512-/y9WvNtlcPBGLR0qc1a+9J/xtYZfVczvLUOuXaVWylzttH7ewsxwHtjmiJSolNrVSDorIxHGHMU61CbonRkmwA==", + "version": "3.972.86", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.86.tgz", + "integrity": "sha512-zKTyLJBEyfQBVlYqT6Jt+mYV4r8kGbWM7dn6v4Y4MRARizhYafjDXP/iMu76UMRhToSPNmIKX1XAb24gtpBFIA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.1", - "@aws-sdk/nested-clients": "^3.997.46", - "@aws-sdk/types": "^3.974.6", - "@smithy/core": "^3.35.0", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -660,17 +680,17 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.46", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.46.tgz", - "integrity": "sha512-oRxtBcka/JGHGs9l9p9IVajGoTP8vTPmoAzdHGy4Qcy9P5vPnDf6nhIeM/COQNY9k/OahImTRaLkHftoXvfcmQ==", + "version": "3.997.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.54.tgz", + "integrity": "sha512-/OczlavsS7U5sOodRO10Ug4R/AKFVZiXTstKipWe68EfjPkmdDFNXNl9FCJyXWkg1DG6RBm3qAuVB8FNW9O8KQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.1", - "@aws-sdk/signature-v4-multi-region": "^3.996.47", - "@aws-sdk/types": "^3.974.6", - "@smithy/core": "^3.35.0", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.51", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.38.0", "@smithy/fetch-http-handler": "^5.8.0", - "@smithy/node-http-handler": "^4.12.1", + "@smithy/node-http-handler": "^4.14.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -679,13 +699,13 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.47", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.47.tgz", - "integrity": "sha512-Zk08macMvQTHzQJCLJVkOlviVoqwYMrpXv4lmLN7b7sAbiMoOK7Go0NYdR5UeF+MW8LIbRmwrNy9u/5VvX1U5g==", + "version": "3.996.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.51.tgz", + "integrity": "sha512-+aHvUugVBuMPXxgkPfWnTMyGdE/yWS8GHi5+qQ37HowS0YqzrbuaDLznj+Dtmtu5jgCdqz36gXmujOToUhNNWw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.974.6", - "@smithy/signature-v4": "^5.7.3", + "@aws-sdk/types": "^3.975.0", + "@smithy/signature-v4": "^5.7.5", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -711,9 +731,9 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.974.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.6.tgz", - "integrity": "sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg==", + "version": "3.975.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.975.0.tgz", + "integrity": "sha512-WQlhs6juByzWOzj5fwxOx0mPJaEqMcG/sDpl3Id61B3XhKE63xY+0QWcnG+sWR/sCfprmreyUo1lM7IKZmHMzw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.19.0", @@ -724,9 +744,9 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.41.tgz", - "integrity": "sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==", + "version": "3.972.42", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.42.tgz", + "integrity": "sha512-smX8/gNP8Kjq+CWTBMZQLQ912vOQjiWExzwzvMO+eujPB5RY8IbtF0AGdUUXHKN7KoDWK9dIcuW0WG269kiP6w==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.19.0", @@ -2036,9 +2056,9 @@ "license": "MIT" }, "node_modules/@smithy/core": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.35.1.tgz", - "integrity": "sha512-i4YPS4B6ts7bjn7UwLnGjiZdprOvHvgGobFZsYK3GIY3E5hIqtj0rReU69BcTpGp+fvtraSNXeG1l+jtJvF55w==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.38.0.tgz", + "integrity": "sha512-aLyyCnK2p4yvNA2TaM+5owXqb+rZGSiUKEsNP329AzeYnERtAz7NrlTbRsaxqmZ0ooxwoID8LMm959UGQQDIsg==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.19.0", @@ -2077,13 +2097,13 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", - "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.14.0.tgz", + "integrity": "sha512-j24RS5V9J52u0z81RpgF7H55p4bS6UCEIxF4ZYzTYh6M9hzXvjAIAYwgLobqf7vJq3xib5VXc7nX3YesV8Rx4A==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.18.0", + "@smithy/core": "^3.38.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -2091,12 +2111,12 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.4.tgz", - "integrity": "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==", + "version": "5.7.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.5.tgz", + "integrity": "sha512-WPjdr5oWOk1LArpI4pavu+fSo+/evi0Re1olxGvqwuZopd4gYR1UGCi+Soyn/IjmafqbWs+ZC+3AKxJS3ZpwUg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.35.0", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, diff --git a/package.json b/package.json index ec872da..e0a7c2b 100644 --- a/package.json +++ b/package.json @@ -21,31 +21,32 @@ "test:platform": "node scripts/platform-smoke.cjs" }, "dependencies": { - "react": "19.3.0", - "react-dom": "19.3.0", - "lucide-react": "^0.577.0", - "pg": "^8.16.3", - "mysql2": "^3.15.0", - "@databricks/sql": "^2.2.0", - "mssql": "^12.0.0", - "mongodb": "^6.20.0", - "redis": "^5.8.0", - "@clickhouse/client": "^1.12.1", + "@aws-sdk/client-athena": "^3.1161.0", "@aws-sdk/client-bedrock": "^3.1145.0", "@aws-sdk/client-bedrock-runtime": "^3.1145.0", "@aws-sdk/credential-providers": "^3.1145.0", - "google-auth-library": "^11.1.0" + "@clickhouse/client": "^1.12.1", + "@databricks/sql": "^2.2.0", + "google-auth-library": "^11.1.0", + "lucide-react": "^0.577.0", + "mongodb": "^6.20.0", + "mssql": "^12.0.0", + "mysql2": "^3.15.0", + "pg": "^8.16.3", + "react": "19.3.0", + "react-dom": "19.3.0", + "redis": "^5.8.0" }, "devDependencies": { + "@types/node": "^24.0.0", "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", - "@types/node": "^24.0.0", "electron": "44.5.1", "electron-builder": "^26.0.12", - "undici": "7.30.0", + "playwright": "^1.56.0", "typescript": "^5.9.3", - "vite": "8.3.2", - "playwright": "^1.56.0" + "undici": "7.30.0", + "vite": "8.3.2" }, "build": { "appId": "local.tableline.desktop", diff --git a/scripts/platform-smoke.cjs b/scripts/platform-smoke.cjs index 22d90d1..149aa60 100644 --- a/scripts/platform-smoke.cjs +++ b/scripts/platform-smoke.cjs @@ -99,11 +99,11 @@ const check = async (name, work) => { const imported = await app.evaluate(({ app }) => { const { createRequire } = process.getBuiltinModule("module"); const appRequire = createRequire(process.getBuiltinModule("path").join(app.getAppPath(), "package.json")); - const dependencies = ["pg", "mysql2", "mssql", "mongodb", "redis", "@clickhouse/client", "@databricks/sql", + const dependencies = ["pg", "mysql2", "mssql", "mongodb", "redis", "@clickhouse/client", "@databricks/sql", "@aws-sdk/client-athena", "@aws-sdk/client-bedrock", "@aws-sdk/client-bedrock-runtime", "@aws-sdk/credential-providers", "google-auth-library"]; return dependencies.map(name => { appRequire(name); return name; }); }); - assert.equal(imported.length, 11); + assert.equal(imported.length, 12); }); await check("Native LZ4 binding roundtrips UTF-8 using the packaged Electron ABI", async () => { const result = await app.evaluate(({ app }) => { diff --git a/src/App.tsx b/src/App.tsx index ae7fc6d..02e48c0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1025,6 +1025,7 @@ export default function App() { "postgres", "mysql", "databricks", + "athena", "sqlite", "mongodb", "redis", @@ -1043,7 +1044,7 @@ export default function App() {
- {connection.database || "demo"} + {connection.database || (connection.driver === "athena" ? "AwsDataCatalog" : "demo")} {view === "sql" ? (tab.name === "Query" ? translate("Query") : tab.name) : table?.name || translate("Database")} @@ -1425,6 +1426,8 @@ export default function App() { loading || (connection.driver === "redis" ? !output?.cursor || output.cursor === "0" + : connection.driver === "athena" + ? !output?.truncated : total !== undefined ? offset + limit >= total : (output?.rows.length || 0) < limit) @@ -1478,6 +1481,8 @@ export default function App() { {connection ? connection.driver === "sqlite" ? translate("SQLite · locale") + : connection.driver === "athena" + ? `Athena · ${connection.region || ""}` : `${connection.driver} · ${connection.host || ""}` : translate("Nessuna connessione")} {connection && ( diff --git a/src/Settings.tsx b/src/Settings.tsx index 24ec016..ef8d690 100644 --- a/src/Settings.tsx +++ b/src/Settings.tsx @@ -97,6 +97,7 @@ const driverMarks: Record = { mysql: "My", mariadb: "Ma", databricks: "Db", + athena: "At", "aurora-postgresql": "Au", "aurora-mysql": "Au", redshift: "Rs", @@ -153,6 +154,7 @@ export function Connections({ const selected = catalog.find((d) => d.id === engine); const local = engine === "sqlite"; const dbx = engine === "databricks"; + const athena = engine === "athena"; const mongo = engine === "mongodb"; function set(k: string, v: any) { setForm((f) => ({ ...f, [k]: v })); @@ -247,6 +249,7 @@ export function Connections({ ...f, port: d.defaultPort, name: d.name, + ...(d.id === "athena" ? { host: "", database: "" } : {}), })); }} > @@ -303,6 +306,47 @@ export function Connections({
+ ) : athena ? ( + <> + + + + + +
+ {translate("Usa il login AWS SSO locale. Le credenziali temporanee si rinnovano automaticamente.")} +
+ ) : ( <>
+ {menu && createPortal( +
{ + const items = Array.from(menuRef.current?.querySelectorAll("button") || []); + const current = items.indexOf(document.activeElement as HTMLButtonElement); + if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Tab") { + e.preventDefault(); + const direction = e.key === "ArrowUp" || (e.key === "Tab" && e.shiftKey) ? -1 : 1; + items[(current + direction + items.length) % items.length]?.focus(); + } else if (e.key === "Home" || e.key === "End") { + e.preventDefault(); + items[e.key === "Home" ? 0 : items.length - 1]?.focus(); + } + }} + > + + + +
, + document.body, + )} + {viewer && createPortal( +
{ + if (e.target === e.currentTarget) closeViewer(); + }} + > +
{ + if (e.key !== "Tab") return; + const controls = Array.from(viewerRef.current?.querySelectorAll("button") || []); + const first = controls[0], last = controls.at(-1); + if (e.shiftKey && (document.activeElement === first || document.activeElement === viewerRef.current)) { + e.preventDefault(); + last?.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first?.focus(); + } + }} + > +
+
+ {translate("Valore della cella")} + {viewer.column} +
+ +
+
{prettyValue(viewer.row[viewer.column])}
+
+ +
+
+
, + document.body, + )} + ); } export function Structure({ table }: { table: Table | null }) { diff --git a/src/styles.css b/src/styles.css index 76dc9d4..099e606 100644 --- a/src/styles.css +++ b/src/styles.css @@ -798,6 +798,101 @@ html[data-platform="linux"] .window-space { .data-grid td.selected-cell { box-shadow: inset 0 0 0 1.5px var(--accent); } +.cell-context-menu { + position: fixed; + z-index: 70; + min-width: 215px; + max-width: calc(100vw - 16px); + max-height: calc(100vh - 16px); + overflow: auto; + padding: 5px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--surface); + box-shadow: var(--shadow); +} +.cell-context-menu button { + display: block; + width: 100%; + padding: 9px 11px; + border-radius: 5px; + text-align: left; + white-space: normal; + font-size: 11px; +} +.cell-context-menu button:hover, +.cell-context-menu button:focus-visible { + background: var(--hover); + color: var(--accent); +} +.cell-value-overlay { + position: fixed; + inset: 0; + z-index: 80; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + background: rgba(0, 0, 0, 0.42); +} +.cell-value-viewer { + width: min(720px, 100%); + max-height: min(85vh, 800px); + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--surface); + box-shadow: var(--shadow); +} +.cell-value-viewer header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 14px 18px; + border-bottom: 1px solid var(--border); +} +.cell-value-viewer header > div { + min-width: 0; + display: flex; + flex-direction: column; + gap: 3px; +} +.cell-value-viewer header strong { + font-size: 13px; +} +.cell-value-viewer header span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--muted); + font-size: 11px; +} +.cell-value-viewer pre { + min-height: 0; + overflow: auto; + margin: 0; + padding: 18px; + white-space: pre-wrap; + overflow-wrap: anywhere; + user-select: text; + font-size: 12px; + line-height: 1.55; +} +.cell-value-viewer footer { + display: flex; + justify-content: flex-end; + padding: 10px 18px; + border-top: 1px solid var(--border); +} +.cell-value-viewer footer button { + display: inline-flex; + align-items: center; + gap: 7px; +} .null { font-size: 9px; color: var(--muted); From a73de96d2fc44d42c830bba5b8c1fd9c7b29de37 Mon Sep 17 00:00:00 2001 From: Davide Leopardi Date: Fri, 30 Oct 2026 23:00:20 +0100 Subject: [PATCH 4/7] Restore CI packaging and update audited dependencies --- assets/electron-notices.json | 38 ++--- docs/PLATFORMS.md | 2 +- package-lock.json | 271 ++++++++++++++++++-------------- package.json | 6 +- scripts/package.cjs | 4 + tests/build-download.test.cjs | 2 +- tests/platform-package.test.cjs | 19 ++- 7 files changed, 191 insertions(+), 151 deletions(-) diff --git a/assets/electron-notices.json b/assets/electron-notices.json index 619f6aa..78fde12 100644 --- a/assets/electron-notices.json +++ b/assets/electron-notices.json @@ -1,82 +1,82 @@ { "version": 2, - "electronVersion": "44.5.1", + "electronVersion": "44.10.0", "distributions": { "darwin-arm64": { - "upstreamArchiveSha256": "1d75703019bb16461ae65f3081d7e6f5c0b11e901d0ccb5c343bcf7bcdd6435c", + "upstreamArchiveSha256": "8e9f6041d3aa62229332b033d0390fd213a9b10cd078163f3f5fe988ee91baf7", "licenses": { "LICENSE.electron.txt": { "sha256": "5154e165bd6c2cc0cfbcd8916498c7abab0497923bafcd5cb07673fe8480087d", "bytes": 1096 }, "LICENSES.chromium.html": { - "sha256": "a62dabd1c6ef1327365b2a3fdffb806222684a746dcb8f4afd1c1f690eba5535", - "bytes": 20111209 + "sha256": "3375e2a9bd7bc631738fde10be6548a76efe8cf5e4b8826b8140ba8b0c4d6353", + "bytes": 20115518 } } }, "darwin-x64": { - "upstreamArchiveSha256": "e567d13833d0e161d7749727355b98643461df3395b537cfa7bdddf8a8bfedff", + "upstreamArchiveSha256": "0d52465c24865981a9a68621c7068f124ef9f2a4caf5119afde62b04fffea66d", "licenses": { "LICENSE.electron.txt": { "sha256": "5154e165bd6c2cc0cfbcd8916498c7abab0497923bafcd5cb07673fe8480087d", "bytes": 1096 }, "LICENSES.chromium.html": { - "sha256": "a62dabd1c6ef1327365b2a3fdffb806222684a746dcb8f4afd1c1f690eba5535", - "bytes": 20111209 + "sha256": "3375e2a9bd7bc631738fde10be6548a76efe8cf5e4b8826b8140ba8b0c4d6353", + "bytes": 20115518 } } }, "linux-arm64": { - "upstreamArchiveSha256": "ee1790d743af1abd6a7e3971589dcd8635b58dab51ca1359123ba5216ce3a453", + "upstreamArchiveSha256": "558ae899d98b70236325d81da44bb6b2c988dd9a7bd16be17cea25f06e56cc14", "licenses": { "LICENSE.electron.txt": { "sha256": "5154e165bd6c2cc0cfbcd8916498c7abab0497923bafcd5cb07673fe8480087d", "bytes": 1096 }, "LICENSES.chromium.html": { - "sha256": "a62dabd1c6ef1327365b2a3fdffb806222684a746dcb8f4afd1c1f690eba5535", - "bytes": 20111209 + "sha256": "3375e2a9bd7bc631738fde10be6548a76efe8cf5e4b8826b8140ba8b0c4d6353", + "bytes": 20115518 } } }, "linux-x64": { - "upstreamArchiveSha256": "5bcd217611d6843ececd6c9e9c1fcd1da3ab066c43d8b1a9e4b44689a1fba6f5", + "upstreamArchiveSha256": "62df9375ef035f2835a18cd7f9bff1959a185da22f2ecba1daa37cd9caee1dc5", "licenses": { "LICENSE.electron.txt": { "sha256": "5154e165bd6c2cc0cfbcd8916498c7abab0497923bafcd5cb07673fe8480087d", "bytes": 1096 }, "LICENSES.chromium.html": { - "sha256": "a62dabd1c6ef1327365b2a3fdffb806222684a746dcb8f4afd1c1f690eba5535", - "bytes": 20111209 + "sha256": "3375e2a9bd7bc631738fde10be6548a76efe8cf5e4b8826b8140ba8b0c4d6353", + "bytes": 20115518 } } }, "win32-arm64": { - "upstreamArchiveSha256": "6ba86d5d5cfdf4ce61d51e2d270eaca464a7f88c143d14c6d9b3f43bb0760e9e", + "upstreamArchiveSha256": "331a59951738402c25525806e515a49d37d39c5a813d72f572a5653428bd57bd", "licenses": { "LICENSE.electron.txt": { "sha256": "5154e165bd6c2cc0cfbcd8916498c7abab0497923bafcd5cb07673fe8480087d", "bytes": 1096 }, "LICENSES.chromium.html": { - "sha256": "7b328b8c7463ac9bfc7dc648c751533517c8441a0b5b21047d6c0b2620e60d70", - "bytes": 20472830 + "sha256": "e6e6b1f9cb0f32235412be113de7716333e88121f9159a0ea0ff3be652021af7", + "bytes": 20477229 } } }, "win32-x64": { - "upstreamArchiveSha256": "9b382492dcfee91f8f9e92c91f7972550a1b95d2299cac72279dab33a600d7db", + "upstreamArchiveSha256": "64c2d874fea1bd4c8d2e1f2df3a547497044159a6b853dee7fcdff357ff0e3cd", "licenses": { "LICENSE.electron.txt": { "sha256": "5154e165bd6c2cc0cfbcd8916498c7abab0497923bafcd5cb07673fe8480087d", "bytes": 1096 }, "LICENSES.chromium.html": { - "sha256": "7b328b8c7463ac9bfc7dc648c751533517c8441a0b5b21047d6c0b2620e60d70", - "bytes": 20472830 + "sha256": "e6e6b1f9cb0f32235412be113de7716333e88121f9159a0ea0ff3be652021af7", + "bytes": 20477229 } } } diff --git a/docs/PLATFORMS.md b/docs/PLATFORMS.md index 9c02f15..be5ed70 100644 --- a/docs/PLATFORMS.md +++ b/docs/PLATFORMS.md @@ -11,7 +11,7 @@ Tableline 0.2.2 targets six native desktop combinations. Compatibility is tied t | Windows | x64 | Windows Server 2025 | | Windows | arm64 | Windows 11 ARM | -Electron 44.5.1 requires macOS 13 or later and supports 64-bit desktop targets. The runtime's [versioned platform requirements](https://github.com/electron/electron/blob/v44.5.1/README.md) are a minimum; the table identifies the systems actually exercised by CI. Linux checks use Ubuntu 24.04 with glibc and X11/Xvfb. GPU drivers and Wayland configurations are not certified. Alpine/musl, 32-bit systems, FreeBSD and old unsupported OS releases are outside this matrix. Windows Server CI does not establish every Windows desktop build or enterprise policy configuration. +Electron 44.10.0 requires macOS 13 or later and supports 64-bit desktop targets. The runtime's [versioned platform requirements](https://github.com/electron/electron/blob/v44.10.0/README.md) are a minimum; the table identifies the systems actually exercised by CI. Linux checks use Ubuntu 24.04 with glibc and X11/Xvfb. GPU drivers and Wayland configurations are not certified. Alpine/musl, 32-bit systems, FreeBSD and old unsupported OS releases are outside this matrix. Windows Server CI does not establish every Windows desktop build or enterprise policy configuration. ## Required checks diff --git a/package-lock.json b/package-lock.json index 4f5940e..0b8e127 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,12 +29,12 @@ "@types/node": "^24.0.0", "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", - "electron": "44.5.1", + "electron": "44.10.0", "electron-builder": "^26.0.12", "playwright": "^1.56.0", "typescript": "^5.9.3", - "undici": "7.30.0", - "vite": "8.3.2" + "undici": "7.30.1", + "vite": "8.3.5" }, "engines": { "node": ">=22.13.0" @@ -907,9 +907,9 @@ } }, "node_modules/@azure/identity": { - "version": "4.13.3", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.3.tgz", - "integrity": "sha512-zGQPtqvXPgSA8yfV2CkIQ1qirqk0p9AIVpC5uEkdXQYcKl07QHvyaGYRnZOk0AsQUmxNb4wfkcwY5di8Z5xa9A==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.14.1.tgz", + "integrity": "sha512-HHuuFrmN1uuF9D5A6aAo5eHzNFNX4Q+P4SksZqDKse5YaJFFy/zDLG3wXHEV2FVGgetZ3QM8jGURNBtnQYf1NQ==", "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.0.0", @@ -921,8 +921,8 @@ "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^5.5.0", - "@azure/msal-node": "^6.0.0", - "open": "^10.1.0", + "@azure/msal-node": "^7.1.0", + "open": "^11.0.0", "tslib": "^2.2.0" }, "engines": { @@ -942,18 +942,20 @@ } }, "node_modules/@azure/identity/node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz", + "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", "license": "MIT", "dependencies": { - "default-browser": "^5.2.1", + "default-browser": "^5.5.1", "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" + "powershell-utils": "^0.2.1", + "wsl-utils": "^1.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1035,12 +1037,12 @@ } }, "node_modules/@azure/msal-node": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-6.0.1.tgz", - "integrity": "sha512-ixSO1Y/kCVRthRs+hSx/5qkwaunX1/RAePhlMN0wIpIQ4WEZ6AREGGnGd1AP0qspHVsAwzWQKGubpjh50JyJIQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-7.1.2.tgz", + "integrity": "sha512-52hKQLGpEQzwNufIXG1SPVU+wQ3RIe7Rp2+g+VdAmZojCAZ6yCdGEffzQEQ6RnBZGBEs40nl14rWBmfJpCoHsg==", "license": "MIT", "dependencies": { - "@azure/msal-common": "16.14.1", + "@azure/msal-common": "16.15.0", "jsonwebtoken": "^9.0.0" }, "engines": { @@ -1048,9 +1050,9 @@ } }, "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { - "version": "16.14.1", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.14.1.tgz", - "integrity": "sha512-Or6xhPNyi4zHW25158yxBoyxuCqNSPa5YBVqfF1J5Ks4MJWBo/USXdp05DQIPu1Zli00YZu6t0+h6KvHJealxQ==", + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.15.0.tgz", + "integrity": "sha512-lwdLb1B9Q5nEhPLVIKe78XH5wFMN15fCiDkY7ylPDSWZuWcD93G3jpJajrKhO3CQld8SLKz0sHiOi24mxdvtsg==", "license": "MIT", "engines": { "node": ">=0.8.0" @@ -1657,9 +1659,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.152.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", - "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz", + "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==", "dev": true, "license": "MIT", "funding": { @@ -1794,9 +1796,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", - "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz", + "integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==", "cpu": [ "arm" ], @@ -1811,9 +1813,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", - "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz", + "integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==", "cpu": [ "arm64" ], @@ -1828,9 +1830,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", - "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz", + "integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==", "cpu": [ "arm64" ], @@ -1845,9 +1847,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", - "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz", + "integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==", "cpu": [ "x64" ], @@ -1862,9 +1864,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", - "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz", + "integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==", "cpu": [ "x64" ], @@ -1879,9 +1881,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", - "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz", + "integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==", "cpu": [ "arm" ], @@ -1896,9 +1898,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", - "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz", + "integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==", "cpu": [ "arm64" ], @@ -1913,9 +1915,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", - "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz", + "integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==", "cpu": [ "arm64" ], @@ -1930,9 +1932,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", - "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz", + "integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==", "cpu": [ "ppc64" ], @@ -1947,9 +1949,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", - "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz", + "integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==", "cpu": [ "s390x" ], @@ -1964,9 +1966,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", - "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz", + "integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==", "cpu": [ "x64" ], @@ -1981,9 +1983,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", - "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz", + "integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==", "cpu": [ "x64" ], @@ -1998,9 +2000,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", - "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz", + "integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==", "cpu": [ "arm64" ], @@ -2015,9 +2017,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", - "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz", + "integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==", "cpu": [ "arm64" ], @@ -2032,9 +2034,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", - "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz", + "integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==", "cpu": [ "x64" ], @@ -3361,9 +3363,9 @@ } }, "node_modules/electron": { - "version": "44.5.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-44.5.1.tgz", - "integrity": "sha512-lx7AYoFIiyvEvgvpyWO9kOC60xtNPpiPZAth6Fmg9NutzpCAMLwpUlaBY2EXfZyIgUdzEmLMj9KvK0Vrokl1yQ==", + "version": "44.10.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-44.10.0.tgz", + "integrity": "sha512-ss4pCfrQKV4TWkuVYtWB4HmeysPZgB96cSYNKYZv1UVCRP4vQMGNW5oopFbvx6Bbfce1XtP5pm5RDuKehp0svQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3679,9 +3681,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", - "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "dev": true, "funding": [ { @@ -4314,9 +4316,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz", - "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==", + "version": "10.7.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.5.tgz", + "integrity": "sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA==", "license": "MIT", "engines": { "node": ">= 12" @@ -4347,6 +4349,18 @@ "node": ">=8" } }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -5272,9 +5286,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.19", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", - "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -5436,9 +5450,9 @@ } }, "node_modules/node-gyp/node_modules/undici": { - "version": "6.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", - "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", + "version": "6.29.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.1.tgz", + "integrity": "sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==", "dev": true, "license": "MIT", "engines": { @@ -5835,9 +5849,9 @@ } }, "node_modules/postcss": { - "version": "8.5.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", - "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", "dev": true, "funding": [ { @@ -5855,9 +5869,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.18", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -5920,6 +5934,18 @@ "node": ">=14.0.0" } }, + "node_modules/powershell-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", + "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/proc-log": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", @@ -6219,13 +6245,13 @@ } }, "node_modules/rolldown": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", - "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz", + "integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.152.0", + "@oxc-project/types": "=0.156.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6235,21 +6261,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.12", - "@rolldown/binding-android-arm64": "1.2.12", - "@rolldown/binding-darwin-arm64": "1.2.12", - "@rolldown/binding-darwin-x64": "1.2.12", - "@rolldown/binding-freebsd-x64": "1.2.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", - "@rolldown/binding-linux-arm64-gnu": "1.2.12", - "@rolldown/binding-linux-arm64-musl": "1.2.12", - "@rolldown/binding-linux-ppc64-gnu": "1.2.12", - "@rolldown/binding-linux-s390x-gnu": "1.2.12", - "@rolldown/binding-linux-x64-gnu": "1.2.12", - "@rolldown/binding-linux-x64-musl": "1.2.12", - "@rolldown/binding-openharmony-arm64": "1.2.12", - "@rolldown/binding-win32-arm64-msvc": "1.2.12", - "@rolldown/binding-win32-x64-msvc": "1.2.12" + "@rolldown/binding-android-arm-eabi": "1.2.16", + "@rolldown/binding-android-arm64": "1.2.16", + "@rolldown/binding-darwin-arm64": "1.2.16", + "@rolldown/binding-darwin-x64": "1.2.16", + "@rolldown/binding-freebsd-x64": "1.2.16", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.16", + "@rolldown/binding-linux-arm64-gnu": "1.2.16", + "@rolldown/binding-linux-arm64-musl": "1.2.16", + "@rolldown/binding-linux-ppc64-gnu": "1.2.16", + "@rolldown/binding-linux-s390x-gnu": "1.2.16", + "@rolldown/binding-linux-x64-gnu": "1.2.16", + "@rolldown/binding-linux-x64-musl": "1.2.16", + "@rolldown/binding-openharmony-arm64": "1.2.16", + "@rolldown/binding-win32-arm64-msvc": "1.2.16", + "@rolldown/binding-win32-x64-msvc": "1.2.16" } }, "node_modules/run-applescript": { @@ -6841,9 +6867,9 @@ } }, "node_modules/undici": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", - "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz", + "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==", "dev": true, "license": "MIT", "engines": { @@ -6922,16 +6948,16 @@ } }, "node_modules/vite": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", - "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", - "postcss": "^8.5.28", - "rolldown": "~1.2.11", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", "tinyglobby": "^0.2.17" }, "bin": { @@ -6948,7 +6974,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -7168,15 +7194,16 @@ } }, "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.1.tgz", + "integrity": "sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==", "license": "MIT", "dependencies": { - "is-wsl": "^3.1.0" + "is-wsl": "^3.1.0", + "powershell-utils": "^0.2.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index e0a7c2b..a3871a6 100644 --- a/package.json +++ b/package.json @@ -41,12 +41,12 @@ "@types/node": "^24.0.0", "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", - "electron": "44.5.1", + "electron": "44.10.0", "electron-builder": "^26.0.12", "playwright": "^1.56.0", "typescript": "^5.9.3", - "undici": "7.30.0", - "vite": "8.3.2" + "undici": "7.30.1", + "vite": "8.3.5" }, "build": { "appId": "local.tableline.desktop", diff --git a/scripts/package.cjs b/scripts/package.cjs index 8be7580..c7a5aa1 100644 --- a/scripts/package.cjs +++ b/scripts/package.cjs @@ -78,6 +78,10 @@ function packageApp({ const selected = String(identity || env.TABLELINE_SIGN_IDENTITY || "-").replace(/^Developer ID Application:\s*/, ""); args.push(`--config.mac.identity=${selected}`, "--config.mac.notarize=false"); if (selected === "-") { + // electron-builder otherwise skips even ad-hoc signing on pull requests. + // This override is confined to the explicit no-certificate build, after + // all signing credentials have been removed from the child environment. + builderEnv.CSC_FOR_PULL_REQUEST = "true"; // Ad-hoc binaries have no Team ID; hardened library validation prevents // even Electron's framework from loading. Local CI packages never become // public release artifacts. Developer ID releases retain hardened runtime. diff --git a/tests/build-download.test.cjs b/tests/build-download.test.cjs index 6f701a8..d65727d 100644 --- a/tests/build-download.test.cjs +++ b/tests/build-download.test.cjs @@ -83,7 +83,7 @@ test("build graph excludes the vulnerable cache chain and keeps official Electro ); assert.equal(get.downloadArtifact.name, "downloadArtifact"); assert.equal(lock.packages["node_modules/@electron/get"].version, "5.1.0"); - assert.equal(lock.packages["node_modules/undici"].version, "7.30.0"); + assert.equal(lock.packages["node_modules/undici"].version, "7.30.1"); }); test("official artifact download verifies cold, warm and tampered caches and preserves progress", async (t) => { diff --git a/tests/platform-package.test.cjs b/tests/platform-package.test.cjs index b96fe79..47fd10f 100644 --- a/tests/platform-package.test.cjs +++ b/tests/platform-package.test.cjs @@ -29,12 +29,12 @@ test("reviewed upstream Electron snapshots cover exactly six supported desktop t assert.deepEqual(Object.keys(manifest.distributions).sort(), expected); for (const target of expected) { const [platform, arch] = target.split("-"); - const snapshot = noticeSnapshot(root, platform, arch, "44.5.1"); + const snapshot = noticeSnapshot(root, platform, arch, "44.10.0"); assert.match(snapshot.upstreamArchiveSha256, /^[a-f\d]{64}$/); assert.equal(Object.keys(snapshot.licenses).length, 2); } - assert.throws(() => noticeSnapshot(root, "linux", "ia32", "44.5.1")); - assert.throws(() => noticeSnapshot(root, "darwin", "x64", "44.5.2")); + assert.throws(() => noticeSnapshot(root, "linux", "ia32", "44.10.0")); + assert.throws(() => noticeSnapshot(root, "darwin", "x64", "44.10.1")); }); test("distribution inventory rejects native kernel but retains Databricks Thrift", () => { const thrift = "/node_modules/@databricks/sql/dist/thrift-backend/ThriftBackend.js"; @@ -60,10 +60,19 @@ test("local Mac packages launch without hardened ad-hoc validation while signed for (const cache of [undefined, path.join(temporary, "builder-cache")]) { calls.length = 0; mocked.exports.packageMac({ platform: "darwin", arch: "arm64", identity, outputDir: temporary, - env: cache ? { ELECTRON_BUILDER_CACHE: cache } : {}, - run: (file, args) => calls.push({ file, args }), log: () => {} }); + env: { ...(cache ? { ELECTRON_BUILDER_CACHE: cache } : {}), + CSC_FOR_PULL_REQUEST: "true", CSC_LINK: "fixture-private-certificate", + APPLE_API_KEY: "fixture-private-key", AWS_SECRET_ACCESS_KEY: "fixture-private-secret" }, + run: (file, args, options) => calls.push({ file, args, options }), log: () => {} }); assert.equal(calls.every(call => call.file === process.execPath), true); const args = calls.at(-1).args; + const childEnv = calls.at(-1).options.env; + assert.equal(childEnv.CSC_FOR_PULL_REQUEST, identity === "-" ? "true" : undefined); + assert.equal(childEnv.CSC_IDENTITY_AUTO_DISCOVERY, "false"); + assert.equal(args.includes("--config.mac.identity=-"), identity === "-"); + assert.equal(args.includes("--config.forceCodeSigning=true"), identity !== "-"); + for (const key of ["CSC_LINK", "APPLE_API_KEY", "AWS_SECRET_ACCESS_KEY"]) + assert.equal(childEnv[key], undefined); assert.ok(args.includes(identity === "-" ? "--config.mac.hardenedRuntime=false" : "--config.mac.hardenedRuntime=true")); assert.equal(args[args.indexOf("--publish") + 1], "never"); assert.equal(args.some(arg => arg.startsWith("--config.electronDownload.cache=")), !!cache); From 4ca054e38e6c1ecbf2d94f5cae90b29a7fe3f0ca Mon Sep 17 00:00:00 2001 From: Davide Leopardi Date: Sat, 31 Oct 2026 00:59:33 +0100 Subject: [PATCH 5/7] Wait for asynchronous clipboard writes in desktop QA --- scripts/e2e.cjs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/scripts/e2e.cjs b/scripts/e2e.cjs index ee3f337..50ffd84 100644 --- a/scripts/e2e.cjs +++ b/scripts/e2e.cjs @@ -119,6 +119,15 @@ async function rowsReady() { !document.querySelector(".grid-progress"), ); } +async function waitForClipboard(expected) { + const deadline = Date.now() + 5_000; + while (Date.now() < deadline) { + if (await app.evaluate(({ clipboard }) => clipboard.readText()) === expected) + return; + await new Promise((resolve) => setTimeout(resolve, 25)); + } + throw new Error("Clipboard did not update to the expected fixture value."); +} async function readExport(filename) { const deadline = Date.now() + 10_000; while (Date.now() < deadline) { @@ -897,7 +906,7 @@ async function launch() { await menu.waitFor(); await menu.getByTestId("copy-cell-value").click(); await menu.waitFor({ state: "hidden" }); - assert.equal(await app.evaluate(({ clipboard }) => clipboard.readText()), longValue); + await waitForClipboard(longValue); await cell.focus(); await cell.press("Shift+F10"); @@ -911,8 +920,9 @@ async function launch() { const viewer = page.getByTestId("cell-value-viewer"); await viewer.waitFor(); assert.equal(await viewer.locator("pre").textContent(), longValue); + await app.evaluate(({ clipboard }) => clipboard.writeText("TABLELINE-E2E-CLIPBOARD-SENTINEL")); await viewer.getByRole("button", { name: "Copia valore", exact: true }).click(); - assert.equal(await app.evaluate(({ clipboard }) => clipboard.readText()), longValue); + await waitForClipboard(longValue); await page.keyboard.press("Escape"); await viewer.waitFor({ state: "hidden" }); @@ -920,10 +930,7 @@ async function launch() { await cell.press("Shift+F10"); await menu.getByTestId("copy-row-json").click(); await menu.waitFor({ state: "hidden" }); - assert.deepEqual( - JSON.parse(await app.evaluate(({ clipboard }) => clipboard.readText())), - { id: 7, note: longValue, vacant: null }, - ); + await waitForClipboard(JSON.stringify({ id: 7, note: longValue, vacant: null }, null, 2)); }); await check("UI cell edit, discard, approval and clipboard", async () => { await page.getByRole("button", { name: "Tabelle", exact: true }).click(); @@ -990,10 +997,7 @@ async function launch() { .nth(2) .click(); await page.getByRole("button", { name: "Copia name", exact: true }).click(); - assert.equal( - await app.evaluate(({ clipboard }) => clipboard.readText()), - "UI approved", - ); + await waitForClipboard("UI approved"); await page .getByRole("button", { name: "Chiudi dettaglio", exact: true }) .click(); From 1e484690a2259c27a1824de93bb62e4e375db488 Mon Sep 17 00:00:00 2001 From: Davide Leopardi Date: Sat, 31 Oct 2026 08:29:52 +0100 Subject: [PATCH 6/7] Document exact source references for 0.2.2 release --- THIRD_PARTY_NOTICES.md | 4 ++-- docs/PUBLIC_RELEASE.md | 2 ++ docs/releases/0.2.2-sources.md | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 docs/releases/0.2.2-sources.md diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index c6d5a7f..c747a52 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -53,9 +53,9 @@ The application uses the following main open-source packages. Their source distr | ClickHouse JavaScript client | Apache-2.0 | | AWS SDK for JavaScript v3 | Apache-2.0 | | Google Auth Library for Node.js | Apache-2.0 | -| Databricks native SQL kernel and FlatBuffers | Apache-2.0 | +| FlatBuffers JavaScript runtime | Apache-2.0 | | LZ4 N-API native bindings | MIT | | TypeScript, Playwright | Apache-2.0; build and test tooling | | Vite, electron-builder | MIT; build tooling | -`package-lock.json` identifies exact dependency versions. The installed runtime dependency license inventory uses MIT, Apache-2.0, ISC, BSD-2-Clause, BSD-3-Clause and 0BSD, including the Apache-2.0 text in FlatBuffers' `LICENSE`. Platform-specific optional packages differ by installation. Transitive packages keep their distributed copyright and license notices; Electron's Chromium notices must remain in distributed bundles. No third-party service grants or account rights are implied by the integration adapters. +`package-lock.json` identifies exact dependency versions. The installed runtime dependency license inventory uses MIT, Apache-2.0, ISC, BSD-2-Clause, BSD-3-Clause and 0BSD, including the Apache-2.0 text in FlatBuffers' `LICENSE`. The Databricks SQL JavaScript Thrift backend is included; its optional native SQL kernel binary and platform package are excluded from Tableline 0.2.2. Platform-specific optional packages differ by installation. Transitive packages keep their distributed copyright and license notices; Electron's Chromium notices must remain in distributed bundles. No third-party service grants or account rights are implied by the integration adapters. diff --git a/docs/PUBLIC_RELEASE.md b/docs/PUBLIC_RELEASE.md index 921b16c..244e162 100644 --- a/docs/PUBLIC_RELEASE.md +++ b/docs/PUBLIC_RELEASE.md @@ -33,3 +33,5 @@ Download the published archive, verify its checksum, extract it and inspect its Before publishing the first GPL binary after v0.2.0, review the exact native dependency sources as well as Tableline's tagged archive. The optional Databricks kernel's exact compiled source was unavailable during the 0.2.1 audit. From 0.2.2, packages exclude that unused kernel and verify its absence. Tableline retains the connector's JavaScript Thrift backend; SEA/Reyden warehouses that require the kernel are explicitly unsupported. The supported route and native LZ4 binding must pass packaged runtime checks on the release target. LZ4 NAPI 2.10.0 identifies its source commit as [`ec869f7de3aab4d07cadfd6796d06af30e386fdc`](https://github.com/antoniomuso/lz4-napi/tree/ec869f7de3aab4d07cadfd6796d06af30e386fdc). Keep this exact source and its build dependencies, the pinned Electron/runtime sources and other required dependency sources accessible beside a release download. An npm tarball containing only native code is not a substitute for its source. Do not silently add a GPL exception or replace historical MIT assets to work around this requirement. A signed local build and a published source fix are separate from a public binary release. + +The [0.2.2 source references](releases/0.2.2-sources.md) identify the pinned upstream sources, build workflow and binary provenance, and disclose the upstream LZ4 Rust lockfile limitation. Link that manifest from the GitHub release notes along with the exact Tableline source tag. diff --git a/docs/releases/0.2.2-sources.md b/docs/releases/0.2.2-sources.md new file mode 100644 index 0000000..37d124d --- /dev/null +++ b/docs/releases/0.2.2-sources.md @@ -0,0 +1,17 @@ +# Tableline 0.2.2 source references + +This manifest identifies source locations for the planned macOS arm64 release. It is not evidence that a binary has been notarized, published or built from the final tag. The release notes must link the exact `v0.2.2` tag and the final binary checksum before publication. + +| Component in the macOS arm64 app | Pinned source | Build and license information | +| --- | --- | --- | +| Tableline 0.2.2 (`GPL-3.0-only`) | [`Hexecu/tableline` tag `v0.2.2`](https://github.com/Hexecu/tableline/tree/v0.2.2) and its [source archive](https://github.com/Hexecu/tableline/archive/refs/tags/v0.2.2.tar.gz), once the tag is published | The tagged tree contains `package-lock.json`, `package.json`, `scripts/`, [`docs/MACOS.md`](../MACOS.md), [`docs/PUBLIC_RELEASE.md`](../PUBLIC_RELEASE.md), `LICENSE`, `COPYRIGHT` and `THIRD_PARTY_NOTICES.md`. The release must use that tree's contents. | +| Electron 44.10.0, including its macOS runtime | [Electron source commit `dce120a548ef263f34ad5952e1fc701981172209`](https://github.com/electron/electron/tree/dce120a548ef263f34ad5952e1fc701981172209) ([release `v44.10.0`](https://github.com/electron/electron/releases/tag/v44.10.0)) | Electron's release identifies Chromium 152.0.7977.169 and Node.js 24.22.1. The packaged Electron MIT license and Chromium notices are retained in `Tableline.app/Contents/Resources/licenses/`. | +| Chromium 152.0.7977.169 | [Chromium source tag `152.0.7977.169`](https://chromium.googlesource.com/chromium/src/+/refs/tags/152.0.7977.169) and its [`DEPS`](https://chromium.googlesource.com/chromium/src/+/refs/tags/152.0.7977.169/DEPS) | Use the tag and dependency manifest for the Chromium source included by Electron; its component notices remain in the app. | +| Node.js 24.22.1 | [Node.js source archive](https://nodejs.org/dist/v24.22.1/node-v24.22.1.tar.gz) and [release](https://nodejs.org/en/blog/release/v24.22.1) | Version identified by Electron 44.10.0's release notes. | +| LZ4 N-API 2.10.0 macOS arm64 binding (`MIT`) | [Upstream source commit `ec869f7de3aab4d07cadfd6796d06af30e386fdc`](https://github.com/antoniomuso/lz4-napi/tree/ec869f7de3aab4d07cadfd6796d06af30e386fdc) ([source archive](https://github.com/antoniomuso/lz4-napi/archive/ec869f7de3aab4d07cadfd6796d06af30e386fdc.tar.gz), [tag `v2.10.0`](https://github.com/antoniomuso/lz4-napi/releases/tag/v2.10.0)) | The source contains `Cargo.toml`, `src/`, `package.json`, `yarn.lock` and the [upstream build workflow](https://github.com/antoniomuso/lz4-napi/blob/ec869f7de3aab4d07cadfd6796d06af30e386fdc/.github/workflows/CI.yaml). The prebuilt npm `@antoniomuso/lz4-napi-darwin-arm64` package contains the binary, not its Rust source. | + +The native file installed by npm has SHA-256 `1fad8f56742362a9a7101ff19753d13dc429baee5009f2acf39756460535cab5`, matching both the [upstream `v2.10.0` macOS arm64 release asset](https://github.com/antoniomuso/lz4-napi/releases/download/v2.10.0/lz4-napi.darwin-arm64.node) and the [upstream CI run's arm64 build artifact](https://github.com/antoniomuso/lz4-napi/actions/runs/31935185318) byte for byte. The npm archive is pinned in Tableline's `package-lock.json` with integrity `sha512-rrP5B4CJkSgHBAZ8jP/xGdXULDjDMF/DiUPxtBdo23CfkIOvf2VYGDF2vWO+1LDFMqlV4Mu4dZWwvOWklP4t4Q==`. That CI run built from the pinned source commit with Node.js 24 and Rust stable, using `yarn install` and `yarn build:release --target aarch64-apple-darwin`. Tableline's signing step subsequently changes the native file's bytes; the npm-installed file is the verified matching input. + +To obtain the LZ4 source, download the commit archive above or check out that exact commit from the upstream repository. Its `Cargo.toml` identifies Rust dependencies and its `yarn.lock` identifies the JavaScript build dependencies; the linked workflow gives the build commands. The source tree does not contain `Cargo.lock`, and `Cargo.toml` allows version ranges for some Rust crates. The upstream CI logs show some resolved versions but do not record a complete Rust lockfile, so this manifest does not claim the exact Rust dependency resolution or a byte-identical rebuild of the prebuilt binding. The release notes should point to this manifest beside the binary download and retain access to these exact source references for as long as the binary is offered. + +The Databricks SQL JavaScript Thrift backend and FlatBuffers JavaScript runtime are included. Its optional native SQL kernel binary and platform package are excluded; `scripts/verify-package.cjs` checks the package inventory, and `scripts/platform-smoke.cjs` tests the supported backend and LZ4 binding in the packaged app. Other JavaScript dependency versions and npm package integrity hashes are fixed by Tableline's `package-lock.json`; their licenses and source locations remain under their package metadata and `THIRD_PARTY_NOTICES.md`. From 0a84b7967dcc168de26d34f471dd2519c510de77 Mon Sep 17 00:00:00 2001 From: Davide Leopardi Date: Sat, 31 Oct 2026 08:35:48 +0100 Subject: [PATCH 7/7] Gate temporary build-only minimatch audit exception --- docs/VALIDATION.md | 2 + package.json | 2 +- scripts/audit.cjs | 106 +++++++++++++++++++++++++++++++++++++++++++ tests/audit.test.cjs | 70 ++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 scripts/audit.cjs create mode 100644 tests/audit.test.cjs diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md index df09408..69d0e16 100644 --- a/docs/VALIDATION.md +++ b/docs/VALIDATION.md @@ -66,6 +66,8 @@ The renderer is sandboxed with context isolation, no Node access, a restrictive On 5 October 2026, both the complete dependency audit and production audit recorded zero advisories after removing the build-only `got → cacheable-request → http-cache-semantics` chain. The override is limited to `app-builder-lib` 26.15.3's downloader, using the official `@electron/get` 5.1.0 already used by Electron. A build-process preload preserves request deadlines, proxy/NO_PROXY routing, progress and retry error codes while retaining upstream artifact caching and SHA-256 checks. TLS verification remains required. CI audits development dependencies as well as production dependencies. +On 31 October 2026, the new [minimatch advisory GHSA-g3hv-75wg-m249](https://github.com/isaacs/minimatch/security/advisories/GHSA-g3hv-75wg-m249) caused `npm audit` to report 15 moderate findings through the development-only Electron Builder dependency tree; `npm audit --omit=dev` still reports zero. The patched minimatch 11 API is incompatible with several legacy build-tool callers, so Tableline's audit gate permits only this advisory and its development-only transitive chain through 30 November 2026. `npm run audit` still runs both full and production audits, prints the findings and fails on any production finding, any other advisory, a higher severity, an unexpected dependency path or expiration. The exception does not apply to bundled application dependencies. + The published `http-cache-semantics` 4.3.0 still reproduced the synthetic cross-user `max-stale` behavior; moving beyond the advisory's version range alone was not accepted as remediation. The affected packages are absent from the resolved lockfile and installed tree. See [GHSA-ch52-4w7c-c8xp](https://github.com/advisories/GHSA-ch52-4w7c-c8xp) and the [upstream report](https://github.com/kornelski/http-cache-semantics/issues/56). Dependency licenses and public source/history scans are reviewed before publishing; generated reports and user data are excluded from Git. Local macOS arm64 acceptance after this change passed 328 unit tests (two optional tests skipped), an actual cold Electron download matched the reviewed upstream SHA-256, and the packaged app passed all nine platform checks, 35 locale checks and nine AI selection checks. The eleven loopback download regressions cover checksums, cache repair, HTTP 503 retry, stalled responses, cancellation, authenticated proxy routing, redirects and isolation between synthetic users. A separate temporary HTTPS fixture confirmed rejection of a self-signed certificate. These checks use synthetic credentials, make no macOS Keychain calls and do not establish Developer ID signing or public installer trust. diff --git a/package.json b/package.json index a3871a6..3cf2f17 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build": "tsc --noEmit && vite build", "start": "electron .", "test": "node scripts/test.cjs", - "audit": "npm audit --audit-level=moderate", + "audit": "node scripts/audit.cjs", "test:e2e": "node scripts/e2e.cjs", "package": "node scripts/package.cjs", "release:macos": "node scripts/release-macos.cjs", diff --git a/scripts/audit.cjs b/scripts/audit.cjs new file mode 100644 index 0000000..84423aa --- /dev/null +++ b/scripts/audit.cjs @@ -0,0 +1,106 @@ +// Copyright (c) 2026 Davide Leopardi +// SPDX-License-Identifier: GPL-3.0-only +"use strict"; + +// A short-lived exception for one upstream advisory in build-only packages. +// Production findings, other advisories and expired exceptions always fail. +const { spawnSync } = require("node:child_process"); +const path = require("node:path"); +const root = path.resolve(__dirname, ".."); +const ADVISORY_URL = "https://github.com/advisories/GHSA-g3hv-75wg-m249"; +const EXPIRES_AFTER = "2026-11-30"; + +function findings(report, label) { + if (!report || typeof report !== "object" || Array.isArray(report) || report.error || + !report.vulnerabilities || typeof report.vulnerabilities !== "object" || + Array.isArray(report.vulnerabilities) || + !Number.isSafeInteger(report.metadata?.vulnerabilities?.total) || + report.metadata.vulnerabilities.total < 0) + throw new Error(`${label} did not return a valid npm audit report.`); + const entries = Object.entries(report.vulnerabilities); + if (entries.length !== report.metadata.vulnerabilities.total) + throw new Error(`${label} vulnerability count does not match its findings.`); + return entries; +} + +function reviewAudit({ production, full, lock, now = new Date() }) { + if (!lock?.packages || typeof lock.packages !== "object") + throw new Error("The dependency lockfile is missing its package inventory."); + if (findings(production, "Production audit").length) + throw new Error("Production dependencies have audit findings."); + const entries = findings(full, "Full audit"); + if (!entries.length) return { count: 0, packages: [], exception: false }; + const date = new Date(now); + if (Number.isNaN(date.getTime()) || date >= new Date("2026-12-01T00:00:00Z")) + throw new Error(`The ${ADVISORY_URL} exception expired after ${EXPIRES_AFTER}.`); + const vulnerabilities = full.vulnerabilities; + for (const [name, entry] of entries) { + if (!entry || entry.severity !== "moderate" || !Array.isArray(entry.via) || !entry.via.length || + !Array.isArray(entry.nodes) || !entry.nodes.length) + throw new Error(`Unreviewed audit finding: ${name}.`); + for (const node of entry.nodes) + if (typeof node !== "string" || lock.packages[node]?.dev !== true) + throw new Error(`Audit finding is not confined to development dependencies: ${name}.`); + for (const via of entry.via) { + if (typeof via === "string") { + if (name === "minimatch" || !vulnerabilities[via]) + throw new Error(`Unreviewed audit finding: ${name}.`); + } else if (name !== "minimatch" || !via || via.name !== "minimatch" || + via.url !== ADVISORY_URL || via.severity !== "moderate") + throw new Error(`Unreviewed audit advisory: ${name}.`); + } + } + if (!vulnerabilities.minimatch || + vulnerabilities.minimatch.via.some(via => typeof via === "string")) + throw new Error("Audit exception has no matching minimatch finding."); + // npm can report dependency cycles (for example app-builder-lib/dmg-builder). + // Every transitive finding must still have a path to the single allowed root. + function reachesMinimatch(name, seen = new Set()) { + if (name === "minimatch") return true; + if (seen.has(name)) return false; + seen.add(name); + return vulnerabilities[name].via.some(via => typeof via === "string" && + reachesMinimatch(via, new Set(seen))); + } + for (const [name] of entries) + if (!reachesMinimatch(name)) throw new Error(`Unrelated audit dependency chain: ${name}.`); + return { count: entries.length, packages: entries.map(([name]) => name).sort(), exception: true }; +} + +function runNpmAudit(args, label) { + const npmCli = process.env.npm_execpath; + if (!npmCli) throw new Error("Run this check through npm run audit."); + const result = spawnSync(process.execPath, [npmCli, "audit", "--json", ...args], { + cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], + timeout: 120000, maxBuffer: 16 * 1024 * 1024, + }); + if (result.error || result.signal || ![0, 1].includes(result.status)) + throw new Error(`${label} could not complete.`); + let report; + try { report = JSON.parse(result.stdout); } + catch { throw new Error(`${label} returned unreadable JSON.`); } + findings(report, label); + if ((report.metadata.vulnerabilities.total === 0) !== (result.status === 0)) + throw new Error(`${label} exit status does not match its findings.`); + return report; +} + +if (require.main === module) { + try { + const production = runNpmAudit(["--omit=dev"], "Production audit"); + const full = runNpmAudit([], "Full audit"); + const lock = require(path.join(root, "package-lock.json")); + const result = reviewAudit({ production, full, lock }); + console.log(`Production dependency audit: ${production.metadata.vulnerabilities.total} findings.`); + console.log(`Full dependency audit: ${result.count} findings.`); + if (result.exception) { + console.log(`Temporary development-only exception through ${EXPIRES_AFTER}: ${ADVISORY_URL}`); + console.log(`Affected packages: ${result.packages.join(", ")}`); + } + } catch (error) { + console.error(`Dependency audit failed: ${error.message}`); + process.exitCode = 1; + } +} + +module.exports = { reviewAudit, findings, ADVISORY_URL, EXPIRES_AFTER }; diff --git a/tests/audit.test.cjs b/tests/audit.test.cjs new file mode 100644 index 0000000..0a74a04 --- /dev/null +++ b/tests/audit.test.cjs @@ -0,0 +1,70 @@ +// Copyright (c) 2026 Davide Leopardi +// SPDX-License-Identifier: GPL-3.0-only +"use strict"; +const assert = require("node:assert/strict"); +const { test } = require("node:test"); +const { reviewAudit, ADVISORY_URL } = require("../scripts/audit.cjs"); + +const report = (vulnerabilities = {}) => ({ + metadata: { vulnerabilities: { total: Object.keys(vulnerabilities).length } }, + vulnerabilities, +}); +const minimatch = { + severity: "moderate", nodes: ["node_modules/minimatch"], + via: [{ name: "minimatch", severity: "moderate", url: ADVISORY_URL }], +}; +const glob = { + severity: "moderate", nodes: ["node_modules/glob"], via: ["minimatch"], +}; +const lock = { packages: { + "node_modules/minimatch": { dev: true }, + "node_modules/glob": { dev: true }, +} }; +const input = (full = report({ minimatch, glob })) => ({ + production: report(), full, lock, now: "2026-10-31T00:00:00Z", +}); + +test("audit permits only the time-limited, development-only minimatch advisory chain", () => { + assert.deepEqual(reviewAudit(input()), { + count: 2, packages: ["glob", "minimatch"], exception: true, + }); + assert.deepEqual(reviewAudit(input(report())), { + count: 0, packages: [], exception: false, + }); +}); + +test("audit rejects any production finding or production dependency path", () => { + assert.throws(() => reviewAudit({ ...input(), production: report({ minimatch }) }), /Production dependencies/); + const unsafe = structuredClone(lock); + unsafe.packages["node_modules/glob"].dev = false; + assert.throws(() => reviewAudit({ ...input(), lock: unsafe }), /not confined to development/); +}); + +test("audit rejects another advisory, higher severity and an unknown dependency chain", () => { + const other = { severity: "moderate", nodes: ["node_modules/glob"], + via: [{ name: "glob", severity: "moderate", url: "https://github.com/advisories/GHSA-other" }] }; + assert.throws(() => reviewAudit(input(report({ minimatch, glob: other }))), /Unreviewed audit advisory/); + assert.throws(() => reviewAudit(input(report({ minimatch: { ...minimatch, severity: "high" } }))), /Unreviewed audit finding/); + assert.throws(() => reviewAudit(input(report({ glob }))), /Unreviewed audit finding/); +}); + +test("audit permits a transitive dependency cycle only when it reaches minimatch", () => { + const cyclic = report({ minimatch, glob: { ...glob, via: ["minimatch", "temp"] }, + temp: { severity: "moderate", nodes: ["node_modules/temp"], via: ["glob"] } }); + const withTemp = structuredClone(lock); + withTemp.packages["node_modules/temp"] = { dev: true }; + assert.equal(reviewAudit({ ...input(cyclic), lock: withTemp }).count, 3); + const disconnected = report({ ...cyclic.vulnerabilities, + unrelated: { severity: "moderate", nodes: ["node_modules/unrelated"], via: ["other"] }, + other: { severity: "moderate", nodes: ["node_modules/other"], via: ["unrelated"] } }); + withTemp.packages["node_modules/unrelated"] = { dev: true }; + withTemp.packages["node_modules/other"] = { dev: true }; + assert.throws(() => reviewAudit({ ...input(disconnected), lock: withTemp }), /Unrelated audit dependency chain/); +}); + +test("audit rejects an expired exception and malformed audit counts", () => { + assert.throws(() => reviewAudit({ ...input(), now: "2026-12-01T00:00:00Z" }), /expired/); + const malformed = report({ minimatch }); + malformed.metadata.vulnerabilities.total = 0; + assert.throws(() => reviewAudit(input(malformed)), /count does not match/); +});