diff --git a/index.html b/index.html
index 3244767..50f1772 100644
--- a/index.html
+++ b/index.html
@@ -15,7 +15,7 @@
--card-foreground: #e6edf3;
--popover: #1a2028;
--popover-foreground: #e6edf3;
- --primary: #2f81f7;
+ --primary: #1f6feb;
--primary-foreground: #ffffff;
--secondary: #242d38;
--secondary-foreground: #e6edf3;
@@ -63,8 +63,8 @@
--card-foreground: #2a313a;
--popover: #eef1f4;
--popover-foreground: #2a313a;
- --primary: #2f6feb;
- --primary-foreground: #f8fbff;
+ --primary: #0969da;
+ --primary-foreground: #ffffff;
--secondary: #e7ebf0;
--secondary-foreground: #2a313a;
--muted: #e2e7ed;
@@ -5302,8 +5302,8 @@
- Guia rápido
+
+ Guia rápido
Use F4 para ocultar/mostrar este painel.
Clique em uma tabela com Cmd/Ctrl para inserir o nome.
@@ -5321,6 +5321,7 @@
Schema
Arraste a borda direita do cabeçalho para aumentar/diminuir. Duplo clique na borda restaura a largura automática.
Ctrl/Cmd+clique no cabeçalho congela da primeira coluna até a coluna clicada.
Arraste o cabeçalho da coluna para mudar sua posição.
+
Backup local: as configurações e os dados salvos da área de trabalho ficam apenas neste navegador. Exporte periodicamente um backup JSON em Configurações para evitar perdas ou continuar em outro navegador. Ele pode conter SQL; revise-o antes de compartilhar.
@@ -5913,19 +5914,22 @@ 4. Resultados
Ordenação multi-coluna, paginação, seleção de linhas, exportação CSV/JSON e cabeçalho sempre visível.
- 5. Persistência
- Sessão, histórico, favoritos e preferências ficam no navegador (localStorage/IndexedDB).
+ 5. Dados locais e backups
+ Dados da sessão, histórico, favoritas e preferências ficam armazenados apenas no perfil atual deste navegador. Exporte-os periodicamente em Configurações para evitar perdas. Importe esse backup JSON para continuar em outro navegador. Limpar os dados do site, usar uma janela privativa ou mudar de perfil pode tornar os dados locais indisponíveis. Backups podem conter textos SQL, identificadores, histórico, favoritas e conteúdo das abas abertas; revise-os antes de compartilhar.
6. Modo offline
O arquivo oficial deve abrir direto no navegador como um HTML standalone, inclusive em file://.
- 7. Reporte de bug
- Use o botão Reportar Erros para abrir o GitHub Issues do projeto.
+ 7. Problemas e sugestões
+ Use Reportar erro para defeitos reproduzíveis. Sugerir melhoria abre o formulário específico Feature request do GitHub.
-
@@ -28232,6 +28236,8 @@ Modo offline indisponível
schemaOtherMenu: () => schemaOtherMenu,
schemaPanel: () => schemaPanel,
schemaPanelController: () => schemaPanelController,
+ schemaQuickGuide: () => schemaQuickGuide,
+ schemaQuickGuideSummary: () => schemaQuickGuideSummary,
schemaSearch: () => schemaSearch,
schemaToggleRail: () => schemaToggleRail,
schemaTypeAllBtn: () => schemaTypeAllBtn,
@@ -28286,6 +28292,7 @@ Modo offline indisponível
shortcutsHelpBtn: () => shortcutsHelpBtn,
shortcutsHelpModal: () => shortcutsHelpModal,
shouldExecuteSqlAfterFileOpen: () => shouldExecuteSqlAfterFileOpen,
+ shouldKeepSchemaQuickGuideOpen: () => shouldKeepSchemaQuickGuideOpen,
showSqlMapEdgeTooltip: () => showSqlMapEdgeTooltip,
showToast: () => showToast,
shuffleList: () => shuffleList,
@@ -28374,6 +28381,7 @@ Modo offline indisponível
summarizeSqlMapCompatibilityReason: () => summarizeSqlMapCompatibilityReason,
switchSqlTab: () => switchSqlTab2,
syncActiveResultToTab: () => syncActiveResultToTab,
+ syncSchemaQuickGuide: () => syncSchemaQuickGuide,
syncSqlMapFieldDragUi: () => syncSqlMapFieldDragUi,
syncTabNamePresetSelectValue: () => syncTabNamePresetSelectValue,
t: () => t,
@@ -30107,7 +30115,7 @@ Modo offline indisponível
["editor.ariaLabel", "SQL statement editor", "Editor de instru\xE7\xF5es SQL", "Editor de sentencias SQL"],
["editor.findNext", "Next occurrence", "Pr\xF3xima ocorr\xEAncia", "Coincidencia siguiente"],
["history.quickLabel", "Quick query history", "Hist\xF3rico r\xE1pido de consultas", "Historial r\xE1pido de consultas"],
- ["schema.quickGuideHtml", 'Use F4 to show or hide this panel.
Click a table while holding Cmd/Ctrl to insert its name. Click a column to insert table.column.
Autocomplete: table and column suggestions follow the active SQL context. Ctrl/Cmd+Space opens suggestions when the context is valid.
Execution: long queries run in a worker so the interface remains responsive. Cancel stops the worker.
Results: use Shift+click for multi-column sorting, drag column edges to resize, and Ctrl/Cmd+click to freeze columns.', 'Use F4 para mostrar ou ocultar este painel.
Clique em uma tabela com Cmd/Ctrl para inserir o nome. Clique em uma coluna para inserir tabela.coluna.
Autocompletar: as sugest\xF5es de tabelas e colunas seguem o contexto SQL ativo. Ctrl/Cmd+Espa\xE7o abre as sugest\xF5es quando o contexto \xE9 v\xE1lido.
Execu\xE7\xE3o: consultas longas rodam em um worker para manter a interface responsiva. Cancelar encerra o worker.
Resultados: use Shift+clique para ordenar por v\xE1rias colunas, arraste as bordas para redimensionar e use Ctrl/Cmd+clique para congelar colunas.', 'Use F4 para mostrar u ocultar este panel.
Haga clic en una tabla con Cmd/Ctrl para insertar su nombre. Haga clic en una columna para insertar tabla.columna.
Autocompletar: las sugerencias de tablas y columnas siguen el contexto SQL activo. Ctrl/Cmd+Espacio abre las sugerencias cuando el contexto es v\xE1lido.
Ejecuci\xF3n: las consultas largas se ejecutan en un worker para mantener la interfaz receptiva. Cancelar detiene el worker.
Resultados: use May\xFAs+clic para ordenar por varias columnas, arrastre los bordes para cambiar el tama\xF1o y use Ctrl/Cmd+clic para inmovilizar columnas.'],
+ ["schema.quickGuideHtml", 'Use F4 to show or hide this panel.
Click a table while holding Cmd/Ctrl to insert its name. Click a column to insert table.column.
Autocomplete: table and column suggestions follow the active SQL context. Ctrl/Cmd+Space opens suggestions when the context is valid.
Execution: long queries run in a worker so the interface remains responsive. Cancel stops the worker.
Results: use Shift+click for multi-column sorting, drag column edges to resize, and Ctrl/Cmd+click to freeze columns.
Local backup: settings and saved workspace data stay only in this browser. Periodically export a JSON backup from Settings to avoid loss or continue in another browser. It may contain SQL; review it before sharing.', 'Use F4 para mostrar ou ocultar este painel.
Clique em uma tabela com Cmd/Ctrl para inserir o nome. Clique em uma coluna para inserir tabela.coluna.
Autocompletar: as sugest\xF5es de tabelas e colunas seguem o contexto SQL ativo. Ctrl/Cmd+Espa\xE7o abre as sugest\xF5es quando o contexto \xE9 v\xE1lido.
Execu\xE7\xE3o: consultas longas rodam em um worker para manter a interface responsiva. Cancelar encerra o worker.
Resultados: use Shift+clique para ordenar por v\xE1rias colunas, arraste as bordas para redimensionar e use Ctrl/Cmd+clique para congelar colunas.
Backup local: as configura\xE7\xF5es e os dados salvos da \xE1rea de trabalho ficam apenas neste navegador. Exporte periodicamente um backup JSON em Configura\xE7\xF5es para evitar perdas ou continuar em outro navegador. Ele pode conter SQL; revise-o antes de compartilhar.', 'Use F4 para mostrar u ocultar este panel.
Haga clic en una tabla con Cmd/Ctrl para insertar su nombre. Haga clic en una columna para insertar tabla.columna.
Autocompletar: las sugerencias de tablas y columnas siguen el contexto SQL activo. Ctrl/Cmd+Espacio abre las sugerencias cuando el contexto es v\xE1lido.
Ejecuci\xF3n: las consultas largas se ejecutan en un worker para mantener la interfaz receptiva. Cancelar detiene el worker.
Resultados: use May\xFAs+clic para ordenar por varias columnas, arrastre los bordes para cambiar el tama\xF1o y use Ctrl/Cmd+clic para inmovilizar columnas.
Copia local: la configuraci\xF3n y los datos guardados del \xE1rea de trabajo permanecen solo en este navegador. Exporte peri\xF3dicamente una copia JSON desde Configuraci\xF3n para evitar p\xE9rdidas o continuar en otro navegador. Puede contener SQL; rev\xEDsela antes de compartirla.'],
["sqlMap.description", "Interactive ER diagram with SELECT and JOIN generation from declared foreign keys and session virtual relationships.", "DER interativo com gera\xE7\xE3o de SELECT e JOINs a partir de FKs declaradas e relacionamentos virtuais da sess\xE3o.", "Diagrama ER interactivo con generaci\xF3n de SELECT y JOIN a partir de claves for\xE1neas declaradas y relaciones virtuales de la sesi\xF3n."],
["sqlMap.selection", "Selection", "Sele\xE7\xE3o", "Selecci\xF3n"],
["sqlMap.virtualHint", "Drag one field onto another to create a virtual relationship. Hover over a dotted line to inspect its technical details.", "Arraste um campo sobre outro para criar um relacionamento virtual. Passe o mouse sobre a linha pontilhada para ver os detalhes t\xE9cnicos.", "Arrastre un campo sobre otro para crear una relaci\xF3n virtual. Pase el puntero sobre una l\xEDnea de puntos para ver sus detalles t\xE9cnicos."],
@@ -30130,7 +30138,7 @@ Modo offline indisponível
["shortcuts.quickHistory", "Quick history", "Hist\xF3rico r\xE1pido", "Historial r\xE1pido"],
["help.title", "hSQLite Editor help", "Ajuda do hSQLite Editor", "Ayuda de hSQLite Editor"],
["help.intro", "A local-first SQLite analysis tool for support, teaching, and technical investigation.", "Uma ferramenta local-first para an\xE1lise de SQLite em suporte, ensino e investiga\xE7\xE3o t\xE9cnica.", "Una herramienta local-first para analizar SQLite en soporte, docencia e investigaci\xF3n t\xE9cnica."],
- ["help.gridHtml", '1. Getting started
Open a SQLite file, write SQL, and press F5.
2. SQL workspace
Use independent tabs, history, favorites, SQL files, and keyboard shortcuts.
3. Schema
Filter objects by name and type. Select columns to insert qualified identifiers.
4. Results
Sort, paginate, select, and export rows as CSV or JSON.
5. Local data
Session, history, favorites, and preferences stay in this browser.
6. Offline mode
The official standalone HTML works directly from file://.
7. Issues
Use Report a bug to open the project issue tracker.
', '1. Primeiros passos
Abra um arquivo SQLite, escreva SQL e pressione F5.
2. \xC1rea SQL
Use abas independentes, hist\xF3rico, favoritas, arquivos SQL e atalhos de teclado.
3. Schema
Filtre objetos por nome e tipo. Selecione colunas para inserir identificadores qualificados.
4. Resultados
Ordene, pagine, selecione e exporte registros como CSV ou JSON.
5. Dados locais
Sess\xE3o, hist\xF3rico, favoritas e prefer\xEAncias permanecem neste navegador.
6. Modo offline
O HTML standalone oficial funciona diretamente em file://.
7. Problemas
Use Reportar erro para abrir o rastreador de issues do projeto.
', '1. Primeros pasos
Abra un archivo SQLite, escriba SQL y pulse F5.
2. \xC1rea SQL
Use pesta\xF1as independientes, historial, favoritas, archivos SQL y atajos de teclado.
3. Esquema
Filtre objetos por nombre y tipo. Seleccione columnas para insertar identificadores cualificados.
4. Resultados
Ordene, pagine, seleccione y exporte filas como CSV o JSON.
5. Datos locales
La sesi\xF3n, el historial, las favoritas y las preferencias permanecen en este navegador.
6. Modo sin conexi\xF3n
El HTML aut\xF3nomo oficial funciona directamente desde file://.
7. Problemas
Use Informar de un error para abrir el gestor de incidencias del proyecto.
'],
+ ["help.gridHtml", '1. Getting started
Open a SQLite file, write SQL, and press F5.
2. SQL workspace
Use independent tabs, history, favorites, SQL files, and keyboard shortcuts.
3. Schema
Filter objects by name and type. Select columns to insert qualified identifiers.
4. Results
Sort, paginate, select, and export rows as CSV or JSON.
5. Local data and backups
Session data, history, favorites, and preferences are stored only in the current browser profile. Export them periodically from Settings to prevent loss. Import that JSON backup to continue in another browser. Clearing site data, using a private window, or changing browser profiles can make local data unavailable. Backups may contain SQL text, identifiers, history, favorites, and open-tab content; review them before sharing.
6. Offline mode
The official standalone HTML works directly from file://.
7. Issues and suggestions
Use Report a bug for reproducible defects. Suggest improvement opens the scoped GitHub Feature request form.
', '1. Primeiros passos
Abra um arquivo SQLite, escreva SQL e pressione F5.
2. \xC1rea SQL
Use abas independentes, hist\xF3rico, favoritas, arquivos SQL e atalhos de teclado.
3. Schema
Filtre objetos por nome e tipo. Selecione colunas para inserir identificadores qualificados.
4. Resultados
Ordene, pagine, selecione e exporte registros como CSV ou JSON.
5. Dados locais e backups
Dados da sess\xE3o, hist\xF3rico, favoritas e prefer\xEAncias ficam armazenados apenas no perfil atual deste navegador. Exporte-os periodicamente em Configura\xE7\xF5es para evitar perdas. Importe esse backup JSON para continuar em outro navegador. Limpar os dados do site, usar uma janela privativa ou mudar de perfil pode tornar os dados locais indispon\xEDveis. Backups podem conter textos SQL, identificadores, hist\xF3rico, favoritas e conte\xFAdo das abas abertas; revise-os antes de compartilhar.
6. Modo offline
O HTML standalone oficial funciona diretamente em file://.
7. Problemas e sugest\xF5es
Use Reportar erro para defeitos reproduz\xEDveis. Sugerir melhoria abre o formul\xE1rio espec\xEDfico Feature request do GitHub.
', '1. Primeros pasos
Abra un archivo SQLite, escriba SQL y pulse F5.
2. \xC1rea SQL
Use pesta\xF1as independientes, historial, favoritas, archivos SQL y atajos de teclado.
3. Esquema
Filtre objetos por nombre y tipo. Seleccione columnas para insertar identificadores cualificados.
4. Resultados
Ordene, pagine, seleccione y exporte filas como CSV o JSON.
5. Datos locales y copias de seguridad
Los datos de la sesi\xF3n, el historial, las favoritas y las preferencias se almacenan solo en el perfil actual de este navegador. Exp\xF3rtelos peri\xF3dicamente desde Configuraci\xF3n para evitar p\xE9rdidas. Importe esa copia JSON para continuar en otro navegador. Borrar los datos del sitio, usar una ventana privada o cambiar de perfil puede hacer que los datos locales no est\xE9n disponibles. Las copias pueden contener texto SQL, identificadores, historial, favoritas y contenido de las pesta\xF1as abiertas; rev\xEDselas antes de compartirlas.
6. Modo sin conexi\xF3n
El HTML aut\xF3nomo oficial funciona directamente desde file://.
7. Problemas y sugerencias
Use Informar de un error para defectos reproducibles. Sugerir una mejora abre el formulario espec\xEDfico Feature request de GitHub.
'],
["offline.description", "This artifact is expected to work entirely offline. This screen means the file is incomplete or corrupted.", "Este artefato deve funcionar totalmente offline. Esta tela indica que o arquivo est\xE1 incompleto ou corrompido.", "Este artefacto debe funcionar totalmente sin conexi\xF3n. Esta pantalla indica que el archivo est\xE1 incompleto o da\xF1ado."],
["offline.guideHtml", "Expected: the standalone HTML contains every required resource.
Verify:- Build the artifact again with the repository build command.
- Replace old HTML copies with a fresh artifact from the same release.
- If the file was modified manually, restore the official generated artifact.
Then open the local file again.", "Esperado: o HTML standalone cont\xE9m todos os recursos necess\xE1rios.
Verifique:- Gere o artefato novamente com o comando de build do reposit\xF3rio.
- Substitua c\xF3pias antigas do HTML por um artefato novo do mesmo release.
- Se o arquivo foi modificado manualmente, restaure o artefato oficial gerado.
Depois abra o arquivo local novamente.", "Esperado: el HTML aut\xF3nomo contiene todos los recursos necesarios.
Compruebe:- Genere de nuevo el artefacto con el comando de compilaci\xF3n del repositorio.
- Sustituya las copias antiguas del HTML por un artefacto nuevo de la misma versi\xF3n.
- Si el archivo se modific\xF3 manualmente, restaure el artefacto oficial generado.
Despu\xE9s vuelva a abrir el archivo local."],
["tabs.renameLabel", "Rename tab", "Renomear aba", "Renombrar pesta\xF1a"],
@@ -30459,6 +30467,7 @@ Modo offline indisponível
"release.whatsNew": "What's new",
"help.label": "Help",
"issues.report": "Report a bug",
+ "issues.suggest": "Suggest improvement",
"database.none": "No database loaded",
"database.file": "Database file",
"database.recent": "Recent",
@@ -30466,6 +30475,7 @@ Modo offline indisponível
"session.description": "When enabled, hSQLite Editor stores open tabs, draft queries, history, favorites, and preferences locally in this browser. Use Settings to export or import them on another browser or device.",
"theme.toggle": "Switch light or dark theme",
"issues.tooltip": "Report a bug in GitHub Issues",
+ "issues.suggestTooltip": "Suggest improvement: open the GitHub Feature request form in a new tab",
"database.actions": "Database file actions",
"database.openRecent": "Open recent databases",
"database.openFile": "Choose a SQLite file",
@@ -30587,6 +30597,7 @@ Modo offline indisponível
"release.whatsNew": "Novidades",
"help.label": "Ajuda",
"issues.report": "Reportar erro",
+ "issues.suggest": "Sugerir melhoria",
"database.none": "Nenhum banco carregado",
"database.file": "Arquivo de dados",
"database.recent": "Recentes",
@@ -30594,6 +30605,7 @@ Modo offline indisponível
"session.description": "Quando ativado, o hSQLite Editor armazena localmente neste navegador as abas abertas, consultas em edi\xE7\xE3o, hist\xF3rico, favoritas e prefer\xEAncias. Use Configura\xE7\xF5es para exportar ou importar esses dados em outro navegador ou dispositivo.",
"theme.toggle": "Alternar tema claro ou escuro",
"issues.tooltip": "Reportar um erro no GitHub Issues",
+ "issues.suggestTooltip": "Sugerir melhoria: abrir o formul\xE1rio Feature request do GitHub em uma nova aba",
"database.actions": "A\xE7\xF5es do arquivo de banco de dados",
"database.openRecent": "Abrir bancos recentes",
"database.openFile": "Escolher um arquivo SQLite",
@@ -30715,6 +30727,7 @@ Modo offline indisponível
"release.whatsNew": "Novedades",
"help.label": "Ayuda",
"issues.report": "Informar de un error",
+ "issues.suggest": "Sugerir una mejora",
"database.none": "No hay ninguna base de datos cargada",
"database.file": "Archivo de datos",
"database.recent": "Recientes",
@@ -30722,6 +30735,7 @@ Modo offline indisponível
"session.description": "Cuando est\xE1 activado, hSQLite Editor guarda localmente en este navegador las pesta\xF1as abiertas, consultas en edici\xF3n, historial, favoritas y preferencias. Usa Configuraci\xF3n para exportar o importar estos datos en otro navegador o dispositivo.",
"theme.toggle": "Cambiar entre tema claro y oscuro",
"issues.tooltip": "Informar de un error en GitHub Issues",
+ "issues.suggestTooltip": "Sugerir una mejora: abrir el formulario Feature request de GitHub en una pesta\xF1a nueva",
"database.actions": "Acciones del archivo de base de datos",
"database.openRecent": "Abrir bases de datos recientes",
"database.openFile": "Elegir un archivo SQLite",
@@ -30937,6 +30951,8 @@ Modo offline indisponível
var resultHScroll = document.getElementById("resultHScroll");
var resultHScrollRange = document.getElementById("resultHScrollRange");
var schemaList = document.getElementById("schemaList");
+ var schemaQuickGuide = document.getElementById("schemaQuickGuide");
+ var schemaQuickGuideSummary = document.getElementById("schemaQuickGuideSummary");
var schemaSearch = document.getElementById("schemaSearch");
var schemaTypeFilters = document.getElementById("schemaTypeFilters");
var schemaTypeAllBtn = document.getElementById("schemaTypeAllBtn");
@@ -33986,6 +34002,14 @@ Modo offline indisponível
}
return count;
}
+ function shouldKeepSchemaQuickGuideOpen() {
+ return !getActiveDatabase() && Object.keys(getSchemaObjects()).length === 0;
+ }
+ function syncSchemaQuickGuide() {
+ if (schemaQuickGuide && shouldKeepSchemaQuickGuideOpen()) {
+ schemaQuickGuide.open = true;
+ }
+ }
var schemaController = {
load() {
const activeDb = getActiveDatabase();
@@ -34063,6 +34087,7 @@ Modo offline indisponível
}).join("");
},
render() {
+ syncSchemaQuickGuide();
const filter = schemaSearch.value.trim().toLowerCase();
const schemaFilterState = getSchemaFilterState();
const entries = Object.entries(getSchemaObjects()).filter(([table, meta]) => {
@@ -34127,6 +34152,18 @@ Modo offline indisponível
schemaController.updateSearchClearButton();
}
function bindSchemaUi() {
+ if (schemaQuickGuideSummary) {
+ schemaQuickGuideSummary.addEventListener("click", (event) => {
+ if (!shouldKeepSchemaQuickGuideOpen()) return;
+ event.preventDefault();
+ schemaQuickGuide.open = true;
+ });
+ }
+ if (schemaQuickGuide) {
+ schemaQuickGuide.addEventListener("toggle", () => {
+ syncSchemaQuickGuide();
+ });
+ }
schemaSearch.addEventListener("input", () => {
updateSchemaSearchClearButton();
renderSchema();
@@ -34214,6 +34251,7 @@ Modo offline indisponível
loadSchemaFiltersPreference();
schemaController.renderTypeFilters();
updateSchemaSearchClearButton();
+ syncSchemaQuickGuide();
}
// src/capabilities/10-sql-execution.js
diff --git a/package-lock.json b/package-lock.json
index 015ade1..8b37456 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,6 +19,7 @@
"@playwright/test": "1.61.1",
"acorn": "8.17.0",
"esbuild": "0.28.1",
+ "snappyjs": "0.7.0",
"sql.js": "1.14.1"
},
"engines": {
@@ -728,6 +729,13 @@
"node": ">=18"
}
},
+ "node_modules/snappyjs": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/snappyjs/-/snappyjs-0.7.0.tgz",
+ "integrity": "sha512-u5iEEXkMe2EInQio6Wv9LWHOQYRDbD2O9hzS27GpT/lwfIQhTCnHCTqedqHIHe9ZcvQo+9au6vngQayipz1NYw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/sql.js": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.1.tgz",
diff --git a/package.json b/package.json
index 07c2536..edfd13c 100644
--- a/package.json
+++ b/package.json
@@ -81,6 +81,7 @@
"@playwright/test": "1.61.1",
"acorn": "8.17.0",
"esbuild": "0.28.1",
+ "snappyjs": "0.7.0",
"sql.js": "1.14.1"
}
}
diff --git a/sbom.spdx.json b/sbom.spdx.json
index 9a642a5..40294cb 100644
--- a/sbom.spdx.json
+++ b/sbom.spdx.json
@@ -877,6 +877,25 @@
}
]
},
+ {
+ "name": "snappyjs",
+ "SPDXID": "SPDXRef-Package-snappyjs-0.7.0",
+ "versionInfo": "0.7.0",
+ "downloadLocation": "https://registry.npmjs.org/snappyjs/-/snappyjs-0.7.0.tgz",
+ "filesAnalyzed": false,
+ "licenseConcluded": "MIT",
+ "licenseDeclared": "MIT",
+ "copyrightText": "NOASSERTION",
+ "primaryPackagePurpose": "LIBRARY",
+ "comment": "Development or release dependency from package-lock.json.",
+ "externalRefs": [
+ {
+ "referenceCategory": "PACKAGE-MANAGER",
+ "referenceType": "purl",
+ "referenceLocator": "pkg:npm/snappyjs@0.7.0"
+ }
+ ]
+ },
{
"name": "sql.js",
"SPDXID": "SPDXRef-Package-sql.js-1.14.1",
@@ -1231,6 +1250,11 @@
"relationshipType": "DEV_DEPENDENCY_OF",
"relatedSpdxElement": "SPDXRef-Package-hSQLite-Editor"
},
+ {
+ "spdxElementId": "SPDXRef-Package-snappyjs-0.7.0",
+ "relationshipType": "DEV_DEPENDENCY_OF",
+ "relatedSpdxElement": "SPDXRef-Package-hSQLite-Editor"
+ },
{
"spdxElementId": "SPDXRef-Package-hSQLite-Editor",
"relationshipType": "DEPENDS_ON",
diff --git a/scripts/contract-tests/github-controls.test.mjs b/scripts/contract-tests/github-controls.test.mjs
index 226bec1..faddf5f 100644
--- a/scripts/contract-tests/github-controls.test.mjs
+++ b/scripts/contract-tests/github-controls.test.mjs
@@ -4,11 +4,15 @@ import { spawnSync } from "node:child_process";
import test from "node:test";
import { fileURLToPath } from "node:url";
import {
+ decodeAttestationBundle,
evaluateGithubControls,
+ hydrateAttestationResponse,
+ isTrustedAttestationBundleUrl,
renderPolicyTemplate,
selectGithubToken,
VERDICTS
} from "../validate-github-controls.mjs";
+import SnappyJS from "snappyjs";
const policy = {
repository: "helbertm/hSQLite-Editor",
@@ -251,6 +255,112 @@ test("prefers GH_TOKEN without exposing or rewriting credential values", () => {
assert.equal(selectGithubToken({}), "");
});
+test("accepts only credential-free HTTPS attestation bundle URLs", () => {
+ assert.equal(
+ isTrustedAttestationBundleUrl("https://example.blob.core.windows.net/attestations/bundle.json.sn?sig=redacted"),
+ true
+ );
+ for (const unsafeUrl of [
+ "http://example.com/bundle.json",
+ "https://user:secret@example.com/bundle.json",
+ "file:///tmp/bundle.json",
+ "not-a-url"
+ ]) {
+ assert.equal(isTrustedAttestationBundleUrl(unsafeUrl), false);
+ }
+});
+
+test("decodes bounded Snappy attestation bundles from the current GitHub API", () => {
+ const bundle = attestation("https://slsa.dev/provenance/v1").bundle;
+ const compressed = SnappyJS.compress(Buffer.from(JSON.stringify(bundle)));
+ const decoded = decodeAttestationBundle(compressed, "application/x-snappy");
+ assert.deepEqual(decoded, bundle);
+ assert.throws(
+ () => decodeAttestationBundle(Buffer.alloc((1024 * 1024) + 1), "application/x-snappy"),
+ /size is outside the accepted range/
+ );
+});
+
+test("hydrates bundle-url-only attestations without changing policy evaluation", async () => {
+ const responses = passingResponses();
+ const bundleUrl = "https://example.blob.core.windows.net/attestations/bundle.json.sn?sig=redacted";
+ const provenance = attestation("https://slsa.dev/provenance/v1");
+ const hydrated = await hydrateAttestationResponse(
+ {
+ status: 200,
+ body: { attestations: [{ repository_id: 42, bundle_url: bundleUrl }] }
+ },
+ async url => {
+ assert.equal(url, bundleUrl);
+ return { status: 200, body: provenance.bundle };
+ }
+ );
+ responses.attestations.provenance = hydrated;
+ const result = evaluateGithubControls({
+ policy,
+ version,
+ responses,
+ manualConfirmations: { pagesAdminBypassDisabled: true }
+ });
+ assert.equal(result.exitCode, 0);
+});
+
+test("treats unavailable bundle URLs as transport failures", async () => {
+ const response = await hydrateAttestationResponse(
+ {
+ status: 200,
+ body: {
+ attestations: [{
+ repository_id: 42,
+ bundle_url: "https://example.blob.core.windows.net/attestations/missing.json.sn"
+ }]
+ }
+ },
+ async () => ({ status: 503, body: null })
+ );
+ assert.equal(response.transportError, true);
+ const responses = passingResponses();
+ responses.attestations.provenance = response;
+ const result = evaluateGithubControls({
+ policy,
+ version,
+ responses,
+ manualConfirmations: { pagesAdminBypassDisabled: true }
+ });
+ assert.equal(result.exitCode, 3);
+});
+
+test("keeps predicate and digest checks strict after bundle URL hydration", async () => {
+ for (const bundle of [
+ attestation("https://spdx.dev/Document/v2.3").bundle,
+ attestation("https://slsa.dev/provenance/v1", `sha256:${"d".repeat(64)}`).bundle
+ ]) {
+ const responses = passingResponses();
+ responses.attestations.provenance = await hydrateAttestationResponse(
+ {
+ status: 200,
+ body: {
+ attestations: [{
+ repository_id: 42,
+ bundle_url: "https://example.blob.core.windows.net/attestations/invalid.json.sn"
+ }]
+ }
+ },
+ async () => ({ status: 200, body: bundle })
+ );
+ const result = evaluateGithubControls({
+ policy,
+ version,
+ responses,
+ manualConfirmations: { pagesAdminBypassDisabled: true }
+ });
+ assert.equal(result.exitCode, 1);
+ assert.ok(result.findings.some(finding =>
+ finding.control === "provenance attestation" && finding.verdict === VERDICTS.FAIL
+ ));
+ }
+});
+
test("release policy templates reject unsafe version input", () => {
assert.equal(renderPolicyTemplate("hSQLite-Editor-v{version}.html", version), "hSQLite-Editor-v0.3.143.html");
for (const unsafeVersion of ["0.3.143/../../x", "0.3.143%2f..", "0.3.143